开发回放系统(第 43 部分):Chart Trade 项目(II)·综合运用
🖱️

开发回放系统(第 43 部分):Chart Trade 项目(II)·综合运用

(3/3)· 很多人以为指标更新离不开对象重绘,其实换条路就能让盘口轻十倍

实战向进阶 第 3/3 篇
别再迷信每次刷新都得靠 ObjetCreate 和 ObjetDelete 堆出来的盘口了。多数人在 Chart Trade 里卡顿,是因为把鼠标互动和对象生命周期绑死了。本篇用一套不依赖对象重画的写法,把这套交互彻底解耦。

鼠标命中面板后的坐标判定

在自定义指标或 EA 里接管 CHARTEVENT_MOUSE_MOVE 事件时,先要用指针对象确认是否是左键按下状态。下面这段逻辑里,m_Mouse 是负责点击检测的实例,CheckClick(C_Mouse::eClickLeft) 返回真才进入坐标处理。 拿到 lparam、dparam 后强转成 int 就是鼠标在图表客户区的像素坐标 x、y。面板自身占据一块矩形区域,由 m_Info.x / m_Info.y / m_Info.cx / m_Info.cy 描述;只有当 x 落在 (m_Info.x, m_Info.x+m_Info.cx)、y 落在 (m_Info.y, m_Info.y+m_Info.cy) 内,才算点中了面板。 代码用 static int sx=-1, sy=-1 记录上次点击位置,初始值 -1 表示尚未落点。sx<0 的判断就是区分「第一次按下」和「拖动中」的分水岭,若你做拖拽面板功能,这个状态位不能省。 外汇与贵金属图表交互脚本对鼠标消息响应极快,但 MT5 主线程处理事件有概率丢帧,高频移动时建议加坐标去重,避免重复触发。

MQL5 / C++
class="kw">delete m_Mouse;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
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="kw">static class="type">int sx = -class="num">1, sy = -class="num">1;
   class="type">int x, y, mx, my;

   class="kw">switch (id)
     {
      case CHARTEVENT_MOUSE_MOVE:
         if ((*m_Mouse).CheckClick(C_Mouse::eClickLeft))
           {
            x = (class="type">int)lparam;
            y = (class="type">int)dparam;
            if ((x > m_Info.x) && (x < (m_Info.x + m_Info.cx)) && (y > m_Info.y) && (y < (m_Info.y + m_Info.cy)))
              {
               if (sx < class="num">0)
                 {

「拖拽时锁死图表滚动的实操写法」

在做自定义图表控件拖拽时,如果鼠标滚轮还能滚动,对象坐标会跟着乱跳。上面这段逻辑在按下拖拽起点时先调用 ChartSetInteger 把 CHART_MOUSE_SCROLL 设为 false,等于把当前图表 ID 的滚轮缩放临时掐断。 sx = x - m_Info.x 和 sy = y - m_Info.y 记录的是鼠标相对对象原点的偏移量;后续只要 mx、my 大于 0,就用 ObjectSetInteger 写回 OBJPROP_XDISTANCE / OBJPROP_YDISTANCE,实现对象跟随光标。 一旦 sx 仍大于 0 且拖拽结束(else if 分支),代码把 CHART_MOUSE_SCROLL 重新置 true,并将 sx、sy 复位为 -1,图表恢复正常滚轮操作。外汇与贵金属图表高频刷新,这类开关若漏写恢复,可能导致用户无法缩放,属高频踩坑点。 别把滚动锁写成全局副作用:只针对 GetInfoTerminal().ID 这一张图操作,避免影响同终端其他品种窗口。

MQL5 / C++
ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, class="kw">false);
sx = x - m_Info.x;
sy = y - m_Info.y;
if ((mx = x - sx) > class="num">0) ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_XDISTANCE, m_Info.x = mx);
if ((my = y - sy) > class="num">0) ObjectSetInteger(GetInfoTerminal().ID, m_Info.szObj_Chart, OBJPROP_YDISTANCE, m_Info.y = my);
}else if (sx > class="num">0)
{
ChartSetInteger(GetInfoTerminal().ID, CHART_MOUSE_SCROLL, true);
sx = sy = -class="num">1;
}
class="kw">break;

◍ 类定义的收尾边界

上面这段是 MQL5 里一个自定义类或结构体定义的收口部分,从行号看处于第 162 到 166 行之间,纯粹是语法闭合,没有运行逻辑。

  • 与 166 行的 //+------------------------------------------------------------------+ 是 MQL5 代码模板自动生成的装饰性分隔线,编译时会被忽略,不影响 EA 或指标行为。
  • 行的 }; 才是真正关闭类作用域的语句,少一个分号会在 MT5 编辑器里直接报 ';' - unexpected end of file。开 MT5 新建脚本粘这段前后文时,优先确认分号与花括号配对,别被分隔线干扰。
MQL5 / C++
class="num">162.                                                                             }
class="num">163.                                                                  }
class="num">164. class=class="str">"cmt">//+------------------------------------------------------------------+
class="num">165. };
class="num">166. class=class="str">"cmt">//+------------------------------------------------------------------+

把平台改造成自己的交易终端

前面几节把管理模板的底层逻辑拆开了:对象增减不是目的,关键是让 MT5 的 native 功能顺着你的交易习惯走。MQL5 给的权限足够把终端改造成个人工作台,而不是另搞一套竞争软件。 外汇和贵金属杠杆高、滑点随机,任何自改模板都先在策略测试器跑历史数据,再上模拟盘验证。能跑通的参数才谈得上实盘价值。 打开 MT5 按 F4 进 MetaEditor,从你最常用的图表对象开始写第一版模板函数,比看十篇文档都实在。

让小布替你跑这套
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到指标与鼠标互动是否掉帧,把重复劳动交给小布,你专注决策。

常见问题

本篇去掉了对对象的实时依赖,改为通过默认值与事件驱动更新,刷新路径更短,交互更跟手。
因为信息呈现绕开了传统对象重绘,借助 MQL5 的事件与指标缓冲机制完成,属于平台深层用法。
可在指标初始化段直接写入默认值,不必保留测试用的交互点,正式环境更干净。
小布盯盘的品种页支持自定义指标叠加,你把编译好的文件挂上就能看鼠标联动效果,不用自己盯刷新。
基础互动、指标改造和本篇综合解耦都覆盖到了,继续练两三个品种页就能成型,外汇贵金属波动大高风险别忘了控仓。