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

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

第 1/2 篇

◍ 给图表交易对象加一个可视边框

在 MT5 的 Chart Trade 扩展里,给每笔挂单或持仓画一个可视边框,能直接看出它在图表上的作用范围,而不是只盯着交易池里的数字。这个边框本质是往图表对象集合里塞了一个矩形,坐标跟着订单的挂单价和止损价走。 实现上,核心是在对象创建后写入 OBJPROP_COLOR、OBJPROP_STYLE 等属性,并用 ChartRedraw 强制重绘。下面这段是边框初始化的关键片段,跑之前先在 EURUSD 的 H1 上挂一单验证,外汇杠杆品种波动快,边框位置可能几秒就偏,属正常高风险现象。 边框画出来后,若价格穿过矩形下沿,说明原挂单逻辑已被击穿,此时靠肉眼比翻终端窗口更快。注意这仅是提高读图效率,不预示任何方向,行情反向概率始终存在。

MQL5 / C++
class="type">void CreateOrderBorder(class="type">long chart_id, class="type">class="kw">double price, class="type">class="kw">double sl)
{
   class="type">class="kw">string name = "OrderBorder_" + (class="type">class="kw">string)price;
   ObjectCreate(chart_id, name, OBJ_RECTANGLE, class="num">0, TimeCurrent(), price, TimeCurrent()+class="num">3600, sl);
   ObjectSetInteger(chart_id, name, OBJPROP_COLOR, clrDodgerBlue);
   ObjectSetInteger(chart_id, name, OBJPROP_STYLE, STYLE_DASHDOT);
   ChartRedraw(chart_id);
}

DispatchMessage 为何要单独拆开讲

上篇讲 C_ChartFloatingRAD 类时,我把 DispatchMessage 塞进了头文件 C_ChartFloatingRAD.mqh 里顺带提过,但那篇材料密度太高,只够说清类结构的皮毛。真正要把它讲透,必须单独拎出来配合另一个关联主题一起看,否则你只知道函数存在、不知道事件是怎么被它接住和分发的。 DispatchMessage 是 C_ChartFloatingRAD 里最核心的过程,MetaTrader 5 发给 Chart Trade 的所有事件——鼠标、图表重绘、对象交互——都靠它生成响应。它一旦逻辑写错,浮动面板就变成死面板,行情动了也没反应。 这篇不是从零开始的。它和上篇、再上篇共同构成 Chart Trade 指标的完整概念底座。如果你没啃完前面两篇里关于 C_ChartFloatingRAD 基础成员和事件注册的内容,直接读这里会卡在半路。建议先开 MT5 把前两篇的头文件对照加载一遍,再回来跟这节的拆解。

「DispatchMessage 只管事件不管画图」

Chart Trade 的浮动面板不是靠代码逐个画对象的。它先由交易者在 MT5 里手动摆好元素、存成模板,程序侧只负责响应 MetaTrader 5 抛过来的事件。头文件 C_ChartFloatingRAD.mqh 里原先留空的片段,就是补齐事件分发逻辑——插到指定行号之后即可,无需新建任何图形对象,唯一例外是 OBJ_EDIT,因为文本输入的交互逻辑太麻烦,只能以编程方式创建。 DispatchMessage 里处理了五类事件。第 273–278 行先声明变量:标了 static 的跨调用保留值,第 278 行那个非静态变量在声明时直接初始化,对应 Defines.mqh 里的事件常量,避免误用。CHARTEVENT_CHART_CHANGE(282–290 行)在图表变动时重算 OBJ_CHART 位置;CHARTEVENT_MOUSE_MOVE(291–338 行)最长,且 MT5 默认不发,必须用鼠标指标显式申请。 鼠标事件触发极频繁,部分场景下频率甚至高于 OnTick,所以默认关闭是有道理的——处理不当会明显拖慢终端。第 292 行先判左键,再向鼠标指标确认有效性,因为指标可能处于“研究”模式,那种情况下不该响应点击。第 329–335 行处理无效移动:给 sz 赋负、清掉 sx/sy,防止第 334 行反复通知 MT5 可拖图表。 314–328 行是三个交易按钮的逻辑。市场买入、市场卖出、平仓按顺序判断,第 317 行必须检查前一个事件是否已改了 ev 变量,否则只触发最后一个。第 319 行查 Chart Trade 是否最大化,避免按钮被遮却仍由自家代码误触自定义事件——真实账户上若最大化就别在按钮前挡东西。真正的触发在第 321–325 行:用 StringFormat 把短整型、双精度数值拼进 sparam 字符串,因为 lparam/dparam 装不下这么多信息。 为什么非用字符串不可?MQL5 字符串和 C/C++ 一样以 NULL(0000 0000)结尾。short 杠杆值 1 的 16 位形式是 0000 0000 0000 0001,塞进单字节字符流的话第一个字节就是 NULL,字符串直接终止。所以必须把二进制数值转成可打印字符传出去,对面再还原。这是新手最容易踩的坑。

◍ 图表尺寸变化时的面板边界钳制

在 MT5 自定义面板里,图表缩放或窗口拖拽会触发 CHARTEVENT_CHART_CHANGE,若不动手钳制坐标,悬浮控件可能被挤到可视区外。上面这段 DispatchMessage 的处理逻辑,核心就是用两个宏把面板左上角与最小边界都限制在图表像素范围内。 macro_AdjustMinX 先判断「当前坐标 A 加上标题区宽度是否超出图表宽 x」,若超出就把 mx 算成 x 减去标题宽,再把 A 重设为 mx 与 0 的较大值;macro_AdjustMinY 对高度做完全对称的操作。两个宏分别对 m_Info.x / m_Info.y(主位置)和 m_Info.minx / m_Info.miny(最小边界)各跑一次,覆盖 4 个 bool 标记 b1~b4。 实盘中可验证:在 1920×1080 副屏把图表拖到只剩 400 像素宽,若标题区宽 120,则 m_Info.x 会被钳到 280 而非原值。外汇与贵金属图表多屏布局常见这种极端尺寸,边界逻辑不稳可能导致右键菜单点不到——直接开 MT5 挂上这段改几个 Region 宽度就能复现。

MQL5 / C++
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="macro">#define macro_AdjustMinX(A, B)     {                                    \
         B = (A + m_Info.Regions[MSG_TITLE_IDE].w) > x;                 \
         mx = x - m_Info.Regions[MSG_TITLE_IDE].w;                      \
         A = (B ? (mx > class="num">0 ? mx : class="num">0) : A);                              \
       }
class="macro">#define macro_AdjustMinY(A, B)  {                                       \
         B = (A + m_Info.Regions[MSG_TITLE_IDE].h) > y;                 \
         my = y - m_Info.Regions[MSG_TITLE_IDE].h;                      \
         A = (B ? (my > class="num">0 ? my : class="num">0) : A);                              \
       }

         class="kw">static class="type">class="kw">short sx = -class="num">1, sy = -class="num">1, sz = -class="num">1;
         class="kw">static eObjectsIDE obj = MSG_NULL;
         class="type">class="kw">short   x, y, mx, my;
         class="type">class="kw">double dvalue;
         class="type">bool b1, b2, b3, b4;
         class="type">class="kw">ushort ev = evChartTradeCloseAll;

         class="kw">switch (id)
         {
         case CHARTEVENT_CHART_CHANGE:
            x = (class="type">class="kw">short)ChartGetInteger(GetInfoTerminal().ID, CHART_WIDTH_IN_PIXELS);
            y = (class="type">class="kw">short)ChartGetInteger(GetInfoTerminal().ID, CHART_HEIGHT_IN_PIXELS);
            macro_AdjustMinX(m_Info.x, b1);
            macro_AdjustMinY(m_Info.y, b2);
            macro_AdjustMinX(m_Info.minx, b3);
            macro_AdjustMinY(m_Info.miny, b4);

常见问题

用矩形对象按交易区间坐标绘制边框,并设置背景透明、置底显示,避免遮挡价格走势。
因为事件分发逻辑独立后,便于维护消息路由,也方便后续扩展不同类型的图表交互。
可以,小布能实时监控图表尺寸变化并提示面板边界钳制是否生效,省去你手动拖拽验证。
画图逻辑在独立的渲染函数里,检查坐标计算和尺寸钳制代码,而不是消息分发部分。
在尺寸变化回调里取当前图表宽高,将面板坐标限制在可视范围内并强制重绘。