开发回放系统(第 35 部分):进行调整 (一)·综合运用
📘

开发回放系统(第 35 部分):进行调整 (一)·综合运用

第 3/3 篇

回放控件的指针与事件清理

在 MT5 自定义回放面板里,C_Controls 的构造与析构直接决定了图表对象会不会残留。构造函数先判 Terminal 指针有效性,无效就 SetUserError 抛错,再把播放键和滑条相关对象名统一置 NULL,避免半初始化状态。 析构时若指针仍有效,会关掉 CHART_EVENT_OBJECT_DELETE 监听并 ObjectsDeleteAll 按前缀清掉所有自建图形。这一行很关键:不关事件开关就删对象,可能触发递归删除死循环。 Init 里用 m_szBtnPlay != NULL 做幂等 guard,重复调用直接 return;随后开鼠标移动与对象删除事件,创建播放键,未播放态才建滑条。DispatchMessage 对 CHARTEVENT_CUSTOM+ev_WaitOn/Off 做暂停等待切换,对象被删时若命中滑条前缀就 Remove 再 Crete 重建。 下面这段是原文核心逻辑,逐行拆一下: if (CheckPointer(Terminal = arg) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid); // 指针无效报错 m_szBtnPlay = NULL; // 播放键对象名清空 m_Slider.szBarSlider = NULL; // 滑条背景清空 m_Slider.szBtnPin = NULL; // 滑条锚点清空 m_Slider.szBtnLeft = NULL; // 左移键清空 m_Slider.szBtnRight = NULL; // 右移键清空 if (CheckPointer(Terminal) == POINTER_INVALID) return; // 析构前再判一次 ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_OBJECT_DELETE, false); // 关删除事件 ObjectsDeleteAll(def_InfoTerminal.ID, def_PrefixObjectName); // 按前缀全删 外汇与贵金属图表加载这类 EA 控件属高风险操作,建议先在模拟盘验证对象生命周期。

MQL5 / C++
if (CheckPointer(Terminal = arg) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid);
   m_szBtnPlay         = NULL;
   m_Slider.szBarSlider = NULL;
   m_Slider.szBtnPin    = NULL;
   m_Slider.szBtnLeft   = NULL;
   m_Slider.szBtnRight  = NULL;
}
~C_Controls()
{
   if (CheckPointer(Terminal) == POINTER_INVALID) class="kw">return;
   ChartSetInteger(def_InfoTerminal.ID, CHART_EVENT_OBJECT_DELETE, false);
   ObjectsDeleteAll(def_InfoTerminal.ID, def_PrefixObjectName);
}

◍ 回放面板的点击与拖拽事件处理

这段逻辑挂在自定义指标的事件分发里,专门处理回放控制面板的播放按钮、滑块箭头和鼠标拖拽。CHARTEVENT_OBJECT_CLICK 分支先判断 m_bWait 锁,避免历史数据未就绪时误触;点播放键后读 OBJPROP_STATE 决定 isPlay,暂停态调 CreteCtrlSlider 建滑块,播放态则 RemoveCtrlSlider 并清空 szBtnPin。 滑块位置用 ushort 存进 iPosShift,同时 GlobalVariableSet 写全局变量 def_GlobalVariableReplay,方便 EA 跨程序读回放进度。左右箭头各加减 1 个像素步长,PositionPinSlider 内部会做边界夹紧。 鼠标移动分支才是拖拽核心:只有当左键按下且 szBtnPin 非空才进入。命中区是纵向 ±14 像素、横向滑块基准 +86 到 +114 的窄条,首次抓住记 six 和 sps 并关掉 CHART_MOUSE_SCROLL 防止图表乱滚;之后每帧按位移差重算 PositionPinSlider(sps + x - six)。松开或移出命中区就把 six 置 -1 并恢复滚动。 开 MT5 把这段塞进你的回放指标 OnChartEvent,拖一下滑块就能直观看到 six/sps 的抓取与跟随;外汇和贵金属回测界面卡顿概率偏高,建议先在小周期离线图表验证。

MQL5 / C++
      CreateBtnPlayPause(Info.s_Infos.isPlay);
      }
      ChartRedraw();
      }
      break;
      case CHARTEVENT_OBJECT_CLICK:
         if (m_bWait) break;
         if (sparam == m_szBtnPlay)
         {
            Info.s_Infos.isPlay = (class="type">bool) ObjectGetInteger(def_InfoTerminal.ID, m_szBtnPlay, OBJPROP_STATE);
            if (!Info.s_Infos.isPlay) CreteCtrlSlider(); else
            {
               RemoveCtrlSlider();
               m_Slider.szBtnPin = NULL;
            }
            Info.s_Infos.iPosShift = (class="type">class="kw">ushort) m_Slider.posPinSlider;
            GlobalVariableSet(def_GlobalVariableReplay, Info.u_Value.df_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);
         break;
      case CHARTEVENT_MOUSE_MOVE:
         if (GetInfoMouse().ExecStudy) class="kw">return;
         if ((CheckClick(C_Mouse::eClickLeft)) && (m_Slider.szBtnPin != NULL))
         {
            x = GetInfoMouse().Position.X;
            y = GetInfoMouse().Position.Y;
            px1 = m_Slider.posPinSlider + def_PosXObjects + class="num">86;
            px2 = m_Slider.posPinSlider + def_PosXObjects + class="num">114;
            if ((y >= (m_Slider.posY - class="num">14)) && (y <= (m_Slider.posY + class="num">14)) && (x >= px1) && (x <= px2) && (six == -class="num">1))
            {
               six = x;
               sps = m_Slider.posPinSlider;
               ChartSetInteger(def_InfoTerminal.ID, CHART_MOUSE_SCROLL, false);
            }
            if (six > class="num">0) PositionPinSlider(sps + x - six);
         }else if (six > class="num">0)
         {
            six = -class="num">1;
            ChartSetInteger(def_InfoTerminal.ID, CHART_MOUSE_SCROLL, true);
         }
         break;
   }
}

「一点提醒」

把为 EA 写的类和方法移植进指标,本质是为了让重放/模拟系统在 MT5 里更好用。附件里给了四组 zip:BOLSA 约 1.36 MB、FOREX 约 3.74 MB、FUTUROS 约 11.4 MB、Market_Replay 约 131 KB,分别对应不同市场的测试场景,直接丢进 MT5 的 MQL5 目录就能跑。 刚学编程的人别急着改架构,作者明确说下一篇要动底层结构、加新东西,现在硬啃只会增加不必要的门槛。 外汇和贵金属这类高波动品种,用回放系统验证策略只是降低试错成本,实盘仍可能因滑点和流动性断裂失效,概率上不保证任何收益。 常开 MT5 把这三组文件轮流加载、反复重放,比读文档更能摸清系统是怎么搭出来的。

常见问题

在鼠标移出事件里手动把当前指针对象置空并触发一次重绘,避免旧指针残留导致界面假死。
在按下时记录起点坐标,松开时比对位移阈值,小于阈值算点击、大于阈值走拖拽逻辑。
可以,小布能扫描你的回放面板代码,标出未释放的指针和重复绑定的点击事件,直接给修改位置。
拖拽回调里只更新了数值没发重绘指令,补一句主动重绘调用即可让画面实时跟手。
先核对时间轴缩放比和起始偏移量,多数错位来自这两处单位换算没统一。