MQL5 回放系统基础:用自然语言式编程拆解 C_Mouse 类(I)(基础篇)
🖱️

MQL5 回放系统基础:用自然语言式编程拆解 C_Mouse 类(I)(基础篇)

(1/2)· 从汇编到助记符的演进里,藏着让初学者读懂 MQL5 鼠标类代码的钥匙

案例拆解 第 1/2 篇
不少老手把 #define 残留定义当成无所谓的小瑕疵,等到多文件项目里符号冲突才排查半天。还有人死磕机器式写法,让接手的人看不懂自己写的回放逻辑。把代码写得像自然语言,不是花活,是给协作和排错留后路。

「给鼠标事件写个可复用的 C_Mouse 类」

在 MT5 回放系统里,鼠标交互是复盘时定位 K 线、拖拽标注的基础。上一节搭了类骨架,这一节把事件抓取和坐标换算补进 C_Mouse,让 EA 能实时拿到光标在图表上的位置与按键状态。 类内部维护 m_mouse_x、m_mouse_y 以及 m_mouse_state 三个成员,分别在 OnChartEvent 里由 CHARTEVENT_MOUSE_MOVE 和 CHARTEVENT_CLICK 事件刷新。注意 MT5 的鼠标坐标是以像素为单位的窗口坐标,必须配合 ChartXYToTimePrice 才能转成时间和价格,否则画出来的线会偏出当前可视区域。 下面这段是核心事件的接收与存储,直接塞进 EA 的 OnChartEvent 即可跑通:

MQL5 / C++
class="type">void OnChartEvent(const class="type">int id, const class="type">long &lparam, const class="type">class="kw">double &dparam, const class="type">class="kw">string &sparam)
  {
   if(id==CHARTEVENT_MOUSE_MOVE) class=class="str">"cmt">// 鼠标移动事件
     {
      Mouse.SetX((class="type">int)lparam);   class=class="str">"cmt">// 存像素X
      Mouse.SetY((class="type">int)dparam);   class=class="str">"cmt">// 存像素Y
     }
   if(id==CHARTEVENT_CLICK)      class=class="str">"cmt">// 鼠标点击事件
     {
      Mouse.SetState((class="type">int)lparam);class=class="str">"cmt">// 存按键状态
     }
  }

把 MQL5 写成人话给谁看

老派程序员多半觉得把代码往自然语言靠是多此一举。但真相是:你写的不是给机器跑的,是给下一个读你代码的人看的。机器只认编译结果,人却要在你离开后靠源码续命。 从二进制到汇编,再到第一代高级语言,编程史就是一部「让人少头疼」的进化史。MQL5 脱胎于 C/C++,本身就允许用更像说话的方式组织逻辑,这正好拿来演示怎么让初学者少卡壳。 我试过多年标准写法教人做 EA,发现很多人卡在助记符和组合思维上。后来改了表达:编译器不在乎你写得像不像散文,爱好者却因接近自然语言而秒懂。外汇和贵金属杠杆高、滑点狠,读不懂自己 EA 的逻辑就上线,爆仓只是时间问题。 所以这一篇起,咱们用 MQL5 但撇开僵硬范式,直接以口语化结构写类和方法,你打开 MT5 新建脚本照抄就能编译通过。

◍ 用 Defines.mqh 把 MQL5 助记符翻成白话

MQL5 从 C/C++ 派生,原生符号对不熟悉底层的人像象形文字。前两篇提过 #define 在文件末尾不取消定义的特殊用法,这里落地的办法就是建一个 Defines.mqh,把 =、!=、==、&& 这类符号替换成 equal、different、identical、and 等单词,纯替换不发明新语法,只为让人眼可读。 代码示例里能看到基础定义块:equal 映射 =,different 映射 !=,identical 映射 ==,and 映射 &&。加上版权注释和两行 include,整个文件就是一套可读性层。经验不足的人看 if (m_Info.Study identical eStudyExecute) 也比裸符号好懂,作者自己隔月回来也能维护。 关键在包含路径的写法。C_Terminal.mqh 里写 #include "Macros.mqh" 表示同目录找;写 #include "..\..\Defines.mqh" 时,..\ 每出现一次向上退一层,这里退两层到项目根目录。只要根目录结构不变,挪动子目录也不影响编译定位。 光有 Defines.mqh 不够,要让编译器认更高层写法还得补两行:#define if_case if( 与 #define then )。加上后,if_case m_Info.Study identical eStudyExecute then ExecuteStudy(memPrice); 和原始 if 写法编译出完全一样的代码,但这一行最接近自然语言,属于同一逻辑里的「高级代码」——编译器无感,人眼省力。外汇贵金属 EA 开发高风险,可读结构只降维护成本,不改策略胜率。

MQL5 / C++
class="macro">#class="kw">property copyright "Daniel Jose"
class=class="str">"cmt">/*+------------------------------------------------------------------+
Definitions for increasing the level of the coded language in MQL5, for
more details see the article at the following link:
[MQL5官方文档]
+------------------------------------------------------------------+*/
class="macro">#define equal =
class="macro">#define different !=
class="macro">#define identical ==
class="macro">#define and &&
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "Macros.mqh"
class="macro">#include "..\..\Defines.mqh"
if (m_Info.Study == eStudyExecute) ExecuteStudy(memPrice);
if (m_Info.Study identical eStudyExecute) ExecuteStudy(memPrice);
if_case m_Info.Study identical eStudyExecute then ExecuteStudy(memPrice);
class="macro">#define if_case if(
class="macro">#define then )

「事件派发与价格对齐的实操落点」

MetaTrader 5 是 REAL TIME 型事件程序,凡是需要从平台接事件的类都会挂一个 DispatchMessage。把事件集中到消息处理函数里转发,能少写重复代码,也降低漏处理的概率。 价格对齐是订单不被服务器拒的关键一步。下面这行把传入价格按 PointPerTick 取整再 NormalizeDouble,等于强制贴合交易服务器的报价粒度: const double AdjustPrice(const double arg) const { return NormalizeDouble(round(arg / m_Infos.PointPerTick) * m_Infos.PointPerTick, m_Infos.nDigits); } 原文实测中,未对齐的价格经常引发 reject,回放和实盘都要求价格正确,所以这个因子分解要保留。 DispatchMessage 里先转给 C_Terminal 处理公共事件,再 switch 本地消息。CHARTEVENT_CUSTOM+ev_HideMouse 用 ObjectSetInteger 把横线颜色设成 clrNONE 实现隐藏,ev_ShowMouse 则还原成 m_Info.corLineH。 鼠标移动事件里 ChartXYToTimePrice 把图坐标 X/Y(来自 lparam、dparam)转成时间价格坐标。注意原代码里写的是 Position.X equal (int)lparam 这种近自然语言写法,编译器照常解释,但你在 MT5 里抄写时要改成赋值号 = 才能编译过。 别把正态当圣经 MQL5 允许把代码写得接近自然语言提升可读性,但编译器只认语法。复制上面片段时,equal 必须手改成 =,否则 MetaEditor 直接报错,别以为能原样粘贴运行。 开 MT5 建个 EA 骨架,把 AdjustPrice 和这段 DispatchMessage 塞进你的 C_Mouse 类,挂 EURUSD 图表看横线隐藏/显示消息是否生效,就能验证事件链路通不通。外汇与贵金属杠杆高,回放验证不等于实盘安全,参数改动请先在模拟账户试。

MQL5 / C++
const class="type">class="kw">double AdjustPrice(const class="type">class="kw">double arg) const { class="kw">return NormalizeDouble(round(arg / m_Infos.PointPerTick) * m_Infos.PointPerTick, m_Infos.nDigits); }
class="kw">virtual class="type">void DispatchMessage(const class="type">int id, const class="type">long &lparam, const class="type">class="kw">double &dparam, const class="type">class="kw">string &sparam)
{
      class="type">int w equal class="num">0;
      class="kw">static class="type">class="kw">double memPrice equal class="num">0;
      C_Terminal::DispatchMessage(id, lparam, dparam, sparam);
      class="kw">switch (id)
      {
         case (CHARTEVENT_CUSTOM + ev_HideMouse):
            ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, clrNONE);
            class="kw">break;
         case (CHARTEVENT_CUSTOM + ev_ShowMouse):
            ObjectSetInteger(GetInfoTerminal().ID, def_NameObjectLineH, OBJPROP_COLOR, m_Info.corLineH);
            class="kw">break;
         case CHARTEVENT_MOUSE_MOVE:
            ChartXYToTimePrice(GetInfoTerminal().ID, m_Info.Data.Position.X equal(class="type">int)lparam, m_Info.Data.Position.Y equal(class="type">int)dparam, w, m_Info.Data.Position.dt, m_Info.Data.Position.Price);
交给小布盯盘看盘口
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到,你不必自己写脚本去抓盘口异常。把重复劳动交给小布,你专注决策。

常见问题

编译器只认语法和指令,宏与类封装最终都会翻译成标准结构,写法贴近自然语言不影响编译,反而降低人工阅读门槛。
跨文件引用时可能让同名宏在后续模块产生非预期替换,引发逻辑错误,概率随项目规模上升而增加。
目前小布盯盘侧重行情诊断与异动提示,EA 回放需你在终端自行部署;但盘口重放所需的异常标记小布已预置。
它承担鼠标事件向系统外传递的通道,把内部坐标与状态变化转成可响应的消息,是回放交互的基础环节。