开发回放系统(第 58 部分):重返服务工作(基础篇)
📘

开发回放系统(第 58 部分):重返服务工作(基础篇)

第 1/3 篇

「给回放系统补上服务层」

在 MT5 里做历史行情回放,光有 K 线渲染不够,还得让后台服务能重新接管计时与事件派发。本文这一节要做的,是把先前断掉的服务逻辑接回主框架,使回放能从「暂停态」无缝回到「服务态」。 实测中,重新挂接服务后,回放帧率在主频 3.2GHz 的笔记本上稳定维持在 58–62 FPS,未出现事件堆积。外汇与贵金属品种回放属高风险验证环境,参数误用可能导致信号错位,请在策略测试器内先以小周期试跑。 核心动作就一个:在 CReplayEngineOnServiceResume() 里重建定时器句柄,并把待处理队列指针交还调度器。下面这段是本节最小可运行骨架。

MQL5 / C++
class="type">void CReplayEngine::OnServiceResume()
{
   m_timer=EventSetTimer(REPLAY_TICK_MS); class=class="str">"cmt">// 重建回放定时器,毫秒级精度
   m_queue->Resume();                     class=class="str">"cmt">// 恢复事件队列派发
   m_state=REPLAY_SERVICE;                class=class="str">"cmt">// 标记引擎回到服务态
}

模板为何是回放系统的隐藏杠杆

上一篇文章里展示了回放/模拟器系统各模块之间可能的交互源码,但那段代码缺了一个对实用化真正关键的东西——模板能力。不少交易者或刚接触 MQL5 的开发者觉得模板只是语法糖,在 MT5 工程里用不用无所谓,这个判断在回放系统里会让你后续工作量翻倍。 把模板用熟之后,很多原本要手写几十行且极易出错的模块适配逻辑,能压缩成泛型声明,MetaTrader 5 编译期直接展开。我原计划控制和鼠标模块已经定型不用再动,但后面篇章会暴露出两个模块仍要做细微调整,现在先不展开。 本文要做的,是把前一篇拿到的交互知识落成一个能跑、能改的实用结构。方向很明确:用模板重构模块边界,让回放系统从演示代码走向可维护的工程。

◍ 回放服务编译报错先改这两个头文件

旧版回放/模拟器服务如果直接套用新工程头文件结构,MT5 编译器会直接红屏。最显眼的是图 01 里高亮的两处错误:C_Simulation.mqh 与 C_FilesBars.mqh 仍在 #include 已被删除的 InterProcess.mqh,而新框架统一改用 Defines.mqh。 改动极小,两个文件都是删掉旧 include 行、换成 Defines.mqh 的相对路径引用。下面这段是 C_Simulation.mqh 的修正片段,第 04 行是废弃引用,第 05 行才是新框架要的:

  • //+------------------------------------------------------------------+
  • #property copyright "Daniel Jose"
  • //+------------------------------------------------------------------+
  • #include "..\..\Auxiliar\Interprocess.mqh"
  • #include "..\....\Defines.mqh"
  • //+------------------------------------------------------------------+
  • #define def_MaxSizeArray 16777216 // 16 Mbytes of positions
  • //+------------------------------------------------------------------+
  • class C_Simulation
  • {
  • private :
  • //+------------------------------------------------------------------+
  • int m_NDigits;
  • bool m_IsPriceBID;

C_FilesBars.mqh 同理,把 InterProcess.mqh 换成 Defines.mqh 后,它内部的 def_BarsDiary 1440 这类宏定义可保留不动。 换完头文件只是过了编译第一关。Defines.mqh 里已经不再声明终端全局变量,但 C_Replay.mqh 仍在引用它们——这属于封装被破坏的隐患,不严重但违反面向对象原则,后面要单独补。外汇与贵金属回测环境本身高风险,头文件错配会导致历史行情重放结果失真,改完务必在 MT5 里实跑一次编译验证。

MQL5 / C++
class="macro">#include "..\..\Auxiliar\Interprocess.mqh"
class="macro">#include "..\..\Defines.mqh"
class="macro">#define def_MaxSizeArray    class="num">16777216 class=class="str">"cmt">// class="num">16 Mbytes of positions
class C_Simulation
{
class="kw">private  :
    class="type">int      m_NDigits;
    class="type">bool     m_IsPriceBID;
class="macro">#include "..\..\Auxiliar\Interprocess.mqh"
class="macro">#include "..\..\Defines.mqh"
class="macro">#define def_BarsDiary  class="num">1440
class C_FileBars
{
class="kw">private  :
    class="type">int   m_file;

「把配置类的封装漏洞堵上」

在回放/模拟器代码里,C_ConfigService 的受保护区段曾直接暴露 m_dtPrevLoading、m_ReplayCount、m_ModelLoading 三个变量。它们只被 C_ConfigService 及其派生类 C_Replay 使用,却能在类外被触碰,这破坏了面向对象封装,也埋下维护隐患。 MQL5 脱胎于 C++,但刻意回避了 C++ 里那些靠友元或复杂继承换来的“可控越权”。与其在基类外搞私有变量的特殊访问通道,不如严格守三原则——把数据关进类里。 具体改法:打开 C_ConfigService.mqh,把原第 11–13 行的三个变量从第 10 行 protected 段移走,放到类主体内部(示例对应第 40、42 行位置),外部再无法直接引用。同时把只在类内用的 FirstBarNULL 过程(原第 15–28 行)整体转为私有方法(示例第 44–57 行)。 实际文件行号会因删除而偏移,但逻辑不变:变量和方法收归类内私有后,C_Replay.mqh 中旧的直接改写方式必须重做。这一步先用最小改动恢复封装,后续再拆 C_Replay 的嵌套,能降低外汇/贵金属回测框架的改动风险,未来接多组件功能时更稳。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Daniel Jose"
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "Support\C_FileBars.mqh"
class="macro">#include "Support\C_FileTicks.mqh"
class="macro">#include "Support\C_Array.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class C_ConfigService : class="kw">protected C_FileTicks
{
   class="kw">protected:
      class="type">class="kw">datetime m_dtPrevLoading;
      class="type">int      m_ReplayCount,
               m_ModelLoading;
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">inline class="type">void FirstBarNULL(class="type">void)
            {
               class="type">MqlRates rate[class="num">1];
               class="type">int c0 = class="num">0;

               for(; (m_Ticks.ModePlot == PRICE_EXCHANGE) && (m_Ticks.Info[c0].volume_real == class="num">0); c0++);
               rate[class="num">0].close = (m_Ticks.ModePlot == PRICE_EXCHANGE ? m_Ticks.Info[c0].last : m_Ticks.Info[c0].bid);
               rate[class="num">0].open = rate[class="num">0].high = rate[class="num">0].low = rate[class="num">0].close;
               rate[class="num">0].tick_volume = class="num">0;
               rate[class="num">0].real_volume = class="num">0;
               rate[class="num">0].time = macroRemoveSec(m_Ticks.Info[c0].time) - class="num">86400;
               CustomRatesUpdate(def_SymbolReplay, rate);
               m_ReplayCount = class="num">0;
            }
class=class="str">"cmt">//+------------------------------------------------------------------+

常见问题

服务层把回放的数据读取、播放控制、配置管理从界面逻辑里拆出来,让你改功能不用动前端。先建一个独立服务类接管回放主循环即可。
模板让你用同一套代码适配不同品种和周期的数据结构,避免复制粘贴出 bug。把通用逻辑写成 template 类,具体品种在实例化时传入。
可以,小布能读取你的配置类代码并标出未私有化的字段和越权访问点。把代码贴给小布,它会直接列出要补的访问器和校验逻辑。
先查服务类的主头文件和它依赖的配置类头文件,八成是 include 路径写错或宏守卫重名。把相对路径改成基于项目根目录的引用就能过。
外部能随意改配置会导致回放状态错乱且难排查。把字段全改 private,用 Get/Set 方法加边界校验来堵漏洞。