开发回放系统(第 77 部分):新 Chart Trade(四)(基础篇)
📘

开发回放系统(第 77 部分):新 Chart Trade(四)(基础篇)

第 1/3 篇

◍ 在 MT5 里搭一个可回放的图表交易框架

MetaTrader 5 的回放系统做到第 77 部分时,核心落点是把历史行情变成可交互的 Chart Trade 环境。也就是说,你不用等实盘,就能在离线图表上用和线上一致的下单逻辑跑一遍策略。 这套思路的价值在于:外汇与贵金属属高风险品种,实盘试错成本不低;用回放把同一段行情重复播放,你能稳定复现滑点、点差和成交拒单的边界情况。 原文示例发布于 13 十月 2025 08:43,帖内标记浏览量 472、评论 0,说明该回放框架仍属小众验证阶段,建议直接开 MT5 自建测试工程而非照搬社区参数。

「通信协议前先打的补丁」

上篇我们把 DispatchMessage 里最关键的一段讲透了,也铺开了通信过程该怎么设计——或者说通信协议该长什么样。那套逻辑到目前为止仍然成立,不用推倒重来。 不过真要进本文重点之前,得先对前面给过的代码做一处微调。这不是功能新增,而是为了系统在 MT5 实盘里跑得稳,不改就容易在消息高频来回时出诡异卡顿。 补完这个稳定点,后面才能放心啃真正的通信协议设计,不会刚调通就被底层抖动带偏。

鼠标指标与图表交易的点击冲突怎么消

鼠标指标和 Chart Trade 面板同时挂在图表上时,点击某些控件偶尔会失灵。现象很具体:若先加载鼠标指标,再操作 Chart Trade,部分按钮无响应,只能删掉指标重插才恢复,这种交互缺陷在调试阶段很难追溯根因。 实测有效的做法是把 CHARTEVENT_OBJECT_CLICK 事件从 DispatchMessage 的处理逻辑里拿掉。对应到上篇代码,需删除 381 到 403 行整段;同时把原 314、341 行移入 346 行的条件测试,并在 297、300、303、306、309、312、336 行统一引用静态变量 sz 做状态判定。 下面这段来自 C_ChartFloatingRAD.mqh 的 DispatchMessage 片段展示了调整后的核心结构,注意 273 行用 static short sz=-1 承接跨事件状态,宏里只做边界裁剪不触发对象点击: 别把对象点击当万能钩子 Chart Trade 控件依赖自身事件链,外挂指标抢 CHARTEVENT_OBJECT_CLICK 会打断这条链。删掉它之后,外汇与贵金属图表上的悬浮面板稳定性倾向明显提升,但 MT5 环境仍属高风险,参数改动前建议在模拟盘验证。

MQL5 / C++
class="num">259. class=class="str">"cmt">//+------------------------------------------------------------------+
class="num">260.    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">261.      {
class="num">262. class="macro">#define macro_AdjustMinX(A, B)      {                          \
class="num">263.            B = (A + m_Info.Regions[MSG_TITLE_IDE].w) > x;   \
class="num">264.            mx = x - m_Info.Regions[MSG_TITLE_IDE].w;        \
class="num">265.            A = (B ? (mx > class="num">0 ? mx : class="num">0) : A);                 \
class="num">266.            }
class="num">267. class="macro">#define macro_AdjustMinY(A, B)  {                          \
class="num">268.            B = (A + m_Info.Regions[MSG_TITLE_IDE].h) > y;   \
class="num">269.            my = y - m_Info.Regions[MSG_TITLE_IDE].h;        \
class="num">270.            A = (B ? (my > class="num">0 ? my : class="num">0) : A);                 \
class="num">271.            }
class="num">272.                                                       
class="num">273.            class="kw">static class="type">short sx = -class="num">1, sy = -class="num">1, sz = -class="num">1;
class="num">274.            class="kw">static eObjectsIDE obj = MSG_NULL;
class="num">275.            class="type">short   x, y, mx, my;
class="num">276.            class="type">class="kw">double dvalue;
class="num">277.            class="type">bool b1, b2, b3, b4;
class="num">278.            class="type">class="kw">ushort ev = evChartTradeCloseAll;
class="num">279.   
class="num">280.            class="kw">switch (id)
class="num">281.            {
class="num">282.            case CHARTEVENT_CHART_CHANGE:
class="num">283.               x = (class="type">short)ChartGetInteger(GetInfoTerminal().ID, CHART_WIDTH_IN_PIXELS);
class="num">284.               y = (class="type">short)ChartGetInteger(GetInfoTerminal().ID, CHART_HEIGHT_IN_PIXELS);

◍ 用鼠标事件接管面板的状态切换

这段逻辑挂在图表事件回调里,靠 CHARTEVENT_MOUSE_MOVE 和左键点击状态去驱动面板内部字段的改写。CheckClick 先确认是左键,再把 lparam / dparam 强转成 short 交给 CheckMousePosition 定位命中了哪个控件区。 命中 MSG_MAX_MIN 时,只要滚轮方向 sz<0 就翻转 IsMaximized 标记(第297行),相当于在面板上点一下切换最大化。而 MSG_DAY_TRADE、MSG_LEVERAGE_VALUE、MSG_TAKE_VALUE 这几个分支都额外卡了 IsMaximized 为真且 sz<0 才响应,说明它们只在最大化展开态下允许交互。 前面那段宏调整坐标的代码(285–289行)也值得注意:b1~b4 任一为真就调 AdjustTemplate(),意味着面板位置或最小边界被拖动后模板会重绘。开 MT5 把这段塞进自己的 C_Mouse 派生类,滚轮滚一下看 IsMaximized 是否按预期翻转,就能验证命中区有没有画错。

MQL5 / C++
macro_AdjustMinX(m_Info.x, b1);
macro_AdjustMinY(m_Info.y, b2);
macro_AdjustMinX(m_Info.minx, b3);
macro_AdjustMinY(m_Info.miny, b4);
if (b1 || b2 || b3 || b4) AdjustTemplate();
class="kw">break;
case CHARTEVENT_MOUSE_MOVE:
   if ((*m_Mouse).CheckClick(C_Mouse::eClickLeft))
   {
      class="kw">switch (CheckMousePosition(x = (class="type">short)lparam, y = (class="type">short)dparam))
      {
         case MSG_MAX_MIN:
            if (sz < class="num">0) m_Info.IsMaximized = (m_Info.IsMaximized ? class="kw">false : true);
            class="kw">break;
         case MSG_DAY_TRADE:
            if ((m_Info.IsMaximized) && (sz < class="num">0)) m_Info.IsDayTrade = (m_Info.IsDayTrade ? class="kw">false : true);
            class="kw">break;
         case MSG_LEVERAGE_VALUE:
            if ((m_Info.IsMaximized) && (sz < class="num">0)) CreateObjectEditable(obj = MSG_LEVERAGE_VALUE, m_Info.Leverage);
            class="kw">break;
         case MSG_TAKE_VALUE:
            if ((m_Info.IsMaximized) && (sz < class="num">0)) CreateObjectEditable(obj = MSG_TAKE_VALUE, m_Info.FinanceTake);
            class="kw">break;
         case MSG_STOP_VALUE:

常见问题

先做好通信协议前的补丁,再用鼠标指标接管面板状态切换,避免点击冲突,就能在回放里用图表交易。
用鼠标事件过滤掉指标自身的点击,只把有效点击交给交易面板,冲突就消了。
可以,小布能读你的面板逻辑并标出鼠标事件接管有问题的地方,省去手动逐行查。
补的是事件队列和状态标记,让面板在回放模式下不依赖实时 tick 也能切换。
多是状态机没重置,每次回放开始要清掉上次的鼠标锁定标志才能正常切。