开发回放系统  市场模拟(第 07 部分):首次改进(II)·综合运用
⚙️

开发回放系统 市场模拟(第 07 部分):首次改进(II)·综合运用

(3/3)·从指标驻留到退出拦截,把前两篇埋下的不稳定隐患逐一堵死

含代码示例 第 3/3 篇

很多交易者在市场开放时跑回放练手,却没意识到控制指标一旦被误删,回放服务可能干扰平台正常功能。本篇接上篇,继续把这类细微崩溃点逐个消除,让系统更接近可实盘伴随的状态。

回放指标的事件接管与防重入初始化

在 MT5 里做行情回放工具,第一步是把图表事件开关打开。ChartSetInteger 对 CHART_EVENT_MOUSE_MOVE 和 CHART_EVENT_OBJECT_DELETE 置 true 后,鼠标位移和对象删除才会触发 OnChartEvent,否则你的播放按钮点了没反应。 CHARTEVENT_OBJECT_CLICK 分支里靠 sparam 比对按钮名来分流:点播放键读 OBJPROP_STATE 拿到布尔状态,暂停时 CreteCtrlSlider 拉出进度滑条,播放时 RemoveCtrlSlider 并清空 szBtnPin。左右步进按钮直接对 posPinSlider 做 ±1 位移,这个粒度就是单根 K 线步进。 OnInit 用全局变量 def_GlobalVariableReplay 做跨实例握手。若已存在且 IsUsing 为真,说明另一副本在跑,ChartIndicatorDelete 自杀并返回 INIT_FAILED,避免两个回放实例抢同一图表。外汇与贵金属波动剧烈,这类自管全局态的工具在跳空时段可能丢事件,上机前先在模拟盘验证。 初始化末尾 Control.Init(isPlay) 把状态交给控制器,返回 INIT_SUCCEEDED 才算挂载成功。全局变量首次不存在时 Info.Value 清零,保证新会话从 0 帧开始。

MQL5 / C++
ChartSetInteger(m_id, CHART_EVENT_MOUSE_MOVE, true);
ChartSetInteger(m_id, CHART_EVENT_OBJECT_DELETE, true);
CreateBtnPlayPause(state);
GlobalVariableTemp(def_GlobalVariableReplay);
if (!state) CreteCtrlSlider();
ChartRedraw();
Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
Info.s_Infos.IsUsing = true;
GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
}
case CHARTEVENT_OBJECT_CLICK:
   if (sparam == m_szBtnPlay)
   {
      Info.s_Infos.isPlay =(class="type">bool) ObjectGetInteger(m_id, m_szBtnPlay, OBJPROP_STATE);
      if (!Info.s_Infos.isPlay) CreteCtrlSlider(); else
      {
         RemoveCtrlSlider();
         m_Slider.szBtnPin = NULL;
      }
      Info.s_Infos.IsUsing = true;
      Info.s_Infos.iPosShift = m_Slider.posPinSlider;
      GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
      ChartRedraw();
   }else if (sparam == m_Slider.szBtnLeft) PositionPinSlider(m_Slider.posPinSlider - class="num">1);
   else if (sparam == m_Slider.szBtnRight) PositionPinSlider(m_Slider.posPinSlider + class="num">1);
   class="kw">break;
class="type">int OnInit()
{
class="macro">#define def_ShortName "Market Replay"
   u_Interprocess Info;
   IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName);
   if(GlobalVariableCheck(def_GlobalVariableReplay))
   {
      Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
      if (Info.s_Infos.IsUsing)
      {
         ChartIndicatorDelete(ChartID(), class="num">0, def_ShortName);
         class="kw">return INIT_FAILED;
      }
   } else Info.Value = class="num">0;
   Control.Init(Info.s_Infos.isPlay);
   class="kw">return INIT_SUCCEEDED;
class="macro">#undef def_ShortName
}
class="type">void OnDeinit(class="kw">const class="type">int reason)
{
   u_Interprocess Info;

「用全局变量接管图表重绘与销毁」

在 EA 的 OnChartEvent 回调里,图表属性变动(REASON_CHARTCHANGE)不该触发重播逻辑重置。下面这段处理把回放状态从全局变量里读出来,避免界面抖动把历史重播进度洗掉。 当检测到 def_GlobalVariableReplay 这个全局变量存在时,直接把它的数值赋给 Info.Value,并把 IsUsing 标记置为 false,再原样写回全局变量。这样即便 MT5 图表被缩放、拖动导致 CHARTCHANGE 事件频发,回放游标也不会归零。 图表被移除或关闭(REASON_REMOVE / REASON_CHARTCLOSE)时,必须先 GlobalVariableDel 清掉回放全局变量,再 ChartClose 关掉当前图表 ID。顺序反了可能在终端残留死全局变量,下次加载 EA 会读到脏数据。 外汇与贵金属品种波动剧烈,这类全局变量回放机制仅用于本地复盘验证,实盘加载前请在策略测试器跑通事件分支,高风险品种勿直接挂真仓。

MQL5 / C++
   class="kw">switch (reason)
   {
      case REASON_CHARTCHANGE:
         if(GlobalVariableCheck(def_GlobalVariableReplay))
         {
            Info.Value = GlobalVariableGet(def_GlobalVariableReplay);
            Info.s_Infos.IsUsing = false;
            GlobalVariableSet(def_GlobalVariableReplay, Info.Value);
         }
         class="kw">break;
      case REASON_REMOVE:
      case REASON_CHARTCLOSE:
         GlobalVariableDel(def_GlobalVariableReplay);
         ChartClose(ChartID());
         class="kw">break;
   }

◍ 记住这一条就够了

这套用控制指标约束回放系统的思路,能挡掉一部分因指标滥用导致的异常,但覆盖不了全部故障类型,实际跑起来仍可能撞上更隐蔽的问题。 视频里暴露出的那个比表面更复杂的坑,大概率和指标状态在重放节奏下的同步有关,建议直接解压 Market_Replay.zip(约 12.8 MB)在 MT5 里单步复现。 外汇与贵金属回放测试本身高风险,结论只作技术验证参考,别当成实盘依据。

把指标存续检查交给小布
小布盯盘已内置对关键指标存续的监测逻辑,打开对应品种页即可看到控制指标是否在场,不必自己写 DeInit 拦截代码。

常见问题

MQL5 中周期切换或参数修改会先逆初始化再重新初始化,因此 OnDeInit 不仅出现在删除时,需结合逆初始化原因代码区分场景。
REASON_CHART_CLOSE 与 REASON_REMOVE 通常对应图表关闭和指标移除,可用来判断控制指标确实离场并停止服务。
可以,小布盯盘的品种页内置了关键指标存续检测,控制指标消失会即时提示,省去手动轮询图表。
外汇贵金属属高风险品类,回放仅作历史模拟练习,真实成交仍受市场滑点等影响,系统稳定不意味胜率保证,概率上更稳妥的是先离线熟练。