开发回放系统(第 43 部分):Chart Trade 项目(II)·综合运用
(3/3)· 很多人以为指标更新离不开对象重绘,其实换条路就能让盘口轻十倍
鼠标命中面板后的坐标判定
在自定义指标或 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 主线程处理事件有概率丢帧,高频移动时建议加坐标去重,避免重复触发。
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 这一张图操作,避免影响同终端其他品种窗口。
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 新建脚本粘这段前后文时,优先确认分号与花括号配对,别被分隔线干扰。
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,从你最常用的图表对象开始写第一版模板函数,比看十篇文档都实在。