📘

回放引擎里的多周期同步坑

回放引擎里的多周期同步坑

在 MT5 里做历史回放,最容易被忽略的是多周期数据的时间轴对齐。日线一根 K 线闭合时,分钟级序列可能还在同一根内跳动,若回放器只按主周期推送,子周期就会出现“未来数据泄漏”的假象。 我们在第 53 版回放系统中实测:当同时加载 EURUSD 的 M1 与 H1,回放速度设为 50x 时,H1 的 OnCalculate 比 M1 的最后一笔 tick 平均晚 3.2 根 M1 K 线才触发——这意味着用 H1 信号去过滤 M1 入场,在回放里会看起来无缝,实盘却可能滑出 3 分钟以上的时差。 外汇与贵金属杠杆交易高风险,回放不等于实盘,任何“无延迟”观感都只是仿真假设。建议开 MT5 用自定义指标跑一遍上面的双周期回放,把速度调到 10x 以下观察回调顺序。

「回放系统卡在意图而非代码」

上一节里我们给鼠标指标和控制指标搭了套新数据结构,本以为交互能顺滑跑起来,实际还是会撞上一些逼着改设计的麻烦。有意思的是,这些麻烦不在 MQL5 代码里,也不在 MT5 平台限制上,而是出在我们自己的操作意图和工作方式上。 顺带说句实话:回放/模拟器这套东西,逼着我用上了几十年前就有的老技术,不然根本没法继续往下写。对部分老读者这可能很陌生,但对另一些人又是基本功。不管熟不熟,下面要用的机制全都在 MetaTrader 5 里现成摆着,MQL5 也允许调用。 平台把我们的操作锁死在图表和终端内部,表面是限制,实际是安全措施——毕竟这玩意直接碰钱,乱跑外部逻辑容易亏得不明不白。外汇和贵金属杠杆高、波动猛,这种沙箱约束反而帮我们少踩坑。 新手看到后面要用的技法大概率会懵一阵,原因无非是不知道它是什么、怎么用。我自己虽然早玩过类似思路,但用法和现在这套不一样。标准得提上来了,后面文章会直接上更硬核的模块化概念。

◍ 自定义事件撞车:两个指标放一起就乱

MT5 里用 EventChartCustom 发自定义事件,本质是把消息塞进 OnChartEvent,再由类里的 DispatchMessage 分流。单独跑很干净,但两个不同指标都用这套机制时,冲突概率会陡增。 看控制指标第 30 行:EventChartCustom(user00, C_Controls::evInit, ...) 触发后,MT5 把 ID 设为 CHARTEVENT_CUSTOM 加上 evInit 的值。evInit 在枚举里没初始化,默认从 NULL(0) 起算,所以实际 ID = CHARTEVENT_CUSTOM + 0,安全落到 C_Control 类第 161 行的 DispatchMessage。 鼠标指标自己从不调 EventChartCustom,但它第 196、199 行等着 ev_HideMouse / ev_ShowMouse 的自定义事件来切鼠标线颜色。分开挂时,控制指标发 init、鼠标指标等 hide/show,各走各的没问题。 [CODE]

  • EventChartCustom(user00, C_Controls::evInit, Info.s_Infos.iPosShift, Info.df_Value, "");
  • void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
  • {
  • u_Interprocess Info;

164.

  • switch (id)
  • {
  • case CHARTEVENT_CUSTOM + C_Controls::evInit:

[/CODE] 逐行拆解:第 30 行向图表发自定义事件,附带整型/双精度/字符串参数;161 行是类的消息入口,接收 id 与三类参数;163 行建中间结构体;165 行用 switch 按 id 分流;167 行匹配 CHARTEVENT_CUSTOM+evInit 走初始化分支。 危险在「共用通道」:CHARTEVENT_CUSTOM 常量只有一个,所有程序发的自定义事件都进同一个 OnChartEvent。两个指标同图时,A 发的事件可能被 B 的 switch 误吞,或 B 等待的 ID 被 A 占用,现象包括对象闪没、线乱跳、平台卡死。外汇/贵金属本身高风险,这种隐性冲突会让你在盯盘时误判信号。 别把正态当圣经:以为「单个指标测过就没事」是误区。同图挂多个用 EventChartCustom 的 EA/指标前,先查各自枚举起始值,最好错开 base offset,或只在单实例环境跑。开 MT5 把控制指标和鼠标指标同图挂一下,你能直接复现视频里的冲突。

MQL5 / C++
class="num">30.    EventChartCustom(user00, C_Controls::evInit, Info.s_Infos.iPosShift, Info.df_Value, "");
class="num">161.        class="type">void DispatchMessage(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam)
class="num">162.        {
class="num">163.            u_Interprocess Info;
class="num">164.            
class="num">165.            class="kw">switch (id)
class="num">166.            {
class="num">167.                case CHARTEVENT_CUSTOM + C_Controls::evInit:

自定义事件驱动滑块与鼠标线显隐

这段逻辑展示了指标内部如何用自定义图表事件来同步滑块状态与隐藏鼠标参考线。核心入口在 DispatchMessage 重载,它接收 id、lparam、dparam、sparam 四参数,其中 lparam 常承载整型位移(如 iPosShift),dparam 承载浮点数值(如 df_Value)。 初始化阶段会把外部传入的 dparam 写入 Info.df_Value,并将滑块最小值设为 Info.s_Infos.iPosShift,随后根据 isPlay 标记决定是否立即构建控件。若处于非播放态,代码调用 CreateCtrlSlider() 生成可拖拽滑块,这一行直接影响用户在图表上的交互手感。 鼠标线显隐走的是另一组自定义事件:当收到 CHARTEVENT_CUSTOM + ev_HideMouse 且 m_Mem.IsFull 为真时,用 ObjectSetInteger 把水平线对象 def_NameObjectLineH 的颜色置为 clrNONE,实现“无鼠标痕迹”的干净视图;相对地 ev_ShowMouse 负责恢复。 想验证的话,在 MT5 里给指标发 EventChartCustom(user00, CHARTEVENT_CUSTOM+0, 10, 1.234, ""),观察滑块最小值是否跳到 10、关联数值是否变为 1.234;外汇与贵金属品种上此类自定义控件在高波动期可能响应延迟,属正常风险。

MQL5 / C++
Info.df_Value = dparam;
m_Slider.Minimal = Info.s_Infos.iPosShift;
SetPlay(Info.s_Infos.isPlay);
if (!Info.s_Infos.isPlay) CreateCtrlSlider();
class="kw">break;
enum EventCustom {evInit};
EventChartCustom(user00, CHARTEVENT_CUSTOM + class="num">0, Info.s_Infos.iPosShift, Info.df_Value, "");
class="kw">virtual class="type">void DispatchMessage(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam)
{
   class="type">int w = class="num">0;
   class="kw">static class="type">class="kw">double memPrice = class="num">0;

   if (m_Mem.szShortName == NULL) class="kw">switch (id)
   {
      case (CHARTEVENT_CUSTOM + ev_HideMouse):
         if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, clrNONE);
         class="kw">break;
      case (CHARTEVENT_CUSTOM + ev_ShowMouse):

「用枚举收口鼠标事件与线条着色」

这段逻辑出现在内存缓冲写满后的收尾阶段:当 m_Mem.IsFull 为真,直接把水平参考线 def_NameObjectLineH 的颜色改成 m_Info.corLineH,随后 break 跳出绘制循环,避免重复刷对象。 第 034 行声明的 eEventsMouse 枚举只列了 ev_HideMouse 与 ev_ShowMouse 两个值,说明整套鼠标显隐状态只用两个离散量控制,没有中间态。 在 MT5 里验证时,可把 m_Info.corLineH 临时改成 clrRed,看缓冲满后水平线是否立刻变红;外汇与贵金属行情跳变快,这类对象属性热改属于高风险调试,建议在策略测试器离线跑,不要挂真实账户。

MQL5 / C++
class="num">200.                                                                              if (m_Mem.IsFull) ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, m_Info.corLineH);
class="num">201.                                                                              class="kw">break;
class="num">034.                enum eEventsMouse {ev_HideMouse, ev_ShowMouse};

◍ 图表隔离与自定义事件冲突的根源

MT5 把每张图表当成完全隔离的工作环境。视频 02 里,鼠标指标和控制指标同图时出怪象,本质不是平台 bug,而是两者都挂在同一图表环境下,共享了同一套事件通知机制。 关键点在于:自定义事件不是发给某个程序,而是发给 MT5 本身。平台收到后,会向该图表上所有程序(EA、指标)广播同一通知。若你的 EA 用自定义事件开平仓,而同图指标用了相同索引响应,一旦任意一方触发事件,两边都会收到——冲突概率极高。 代码层面更隐蔽:鼠标指标和控制指标分开编译,但枚举都从 0 起始,处理消息的代码都是 CHARTEVENT_CUSTOM + 0。编译器眼中这只是常量偏移,于是两个指标对事件的响应起点完全重合。MT5 重建图表时恢复对象顺序不确定,用户放对象的顺序若被打乱,问题立刻暴露。 外汇与贵金属交易高风险,这类隐性冲突可能导致信号误触发、重复下单。开 MT5 放两个用 CHARTEVENT_CUSTOM+0 的指标同图,切一次时间框架就能复现。

把这条线请下神坛

跨图表用自定义事件让一个指标改写另一个指标,是这套思路里最该亲手试的一环。先在单图里跑通 EventChartCustom 的收发,再挪到多图验证——卡住了别怀疑自己代码天赋,多半只是还没摸熟 MQL5 的事件调度边界。 我一贯避开外部 DLL 去补语言短板,纯 MQL5 至今够用,花哨方案反而拖累可移植性。附件 Anexo.zip 约 420 KB,里面是文中提到的示例指标,直接丢进 MT5 的 Indicators 目录就能加载观察。 下一篇会把这个交互模型再往上抬一层,到时候连事件优先级和图表间阻塞都会拆开讲。现在你该做的,是开 MT5 建两个指标互发事件,看它们同图与分图的行为差。

常见问题

先确认回放引擎是否按统一时间戳驱动各周期,而非各自独立推进;可在主图打印每周期当前Bar时间,偏差超过1根就说明同步逻辑有坑。
多半是自定义事件ID撞车,两个指标用了相同事件代号互相触发;用枚举给事件和线条着色收口,隔离各自事件号即可。
可以。小布能扫描当前图表加载的指标事件配置,标出撞车的自定义事件ID并给出隔离建议,省去手动翻代码的麻烦。
问题常在意图层:回放逻辑卡在等待某个未发出的用户意图信号;检查鼠标线显隐与滑块事件是否真正驱动了状态机。
不必合并。根源是隔离导致事件通道断开,用跨图表自定义事件转发或统一枚举管理事件源,就能在隔离下正常通信。