回放系统C_Mouse类并行开发:用非继承模型给主代码上保险(基础篇)
🖱️

回放系统C_Mouse类并行开发:用非继承模型给主代码上保险(基础篇)

(1/3)· 在已测试EA里硬塞新函数常引发诡异崩溃,本篇用双路径模型把风险隔离在主线之外

实战向 第 1/3 篇
在跑顺的EA核心代码中间插入未验证的研究函数,是多数回放系统莫名崩盘的根源。把新东西关进平行项目里跑,主线才能稳。本篇给一套不靠继承也能扩展C_Mouse类的实操思路。

「用 C_Mouse 接管 MT5 的鼠标事件」

在 MT5 里做交互式 EA,绕不开对鼠标行为的捕获。C_Mouse 类把平台底层的鼠标消息做了封装,让开发者不用每次都去翻事件池里的 CHARTEVENT_MOUSE_MOVECHARTEVENT_CLICK 原始参数。 实际跑起来时,图表上每发生一次鼠标移动,OnChartEvent 会推入一个事件,C_Mouse 内部维护当前坐标与按键状态。根据 2024 年 5 月的社区测试记录,该类在 30 篇系列的第 4 部分已能稳定处理左键拖拽与悬浮判定,未出现坐标漂移。 外汇与贵金属品种波动大、点值敏感,用鼠标框选区间做手动风控时,这类封装能降低误触概率,但杠杆交易本身高风险,任何交互辅助都不替代仓位管理。

◍ 用并行扩展避开半成品污染主代码

做 MT5 EA 时,OOP 适合已上线、要严控行为的生产项目,但研究期函数若直接塞进已测好的代码中间,常会引出不可预测的失败。尤其当你后来发现某个新函数根本不该进主项目,想剥掉它比回退到早期版本还麻烦。 一个务实做法是把新研究当成「副线」并行开发:主类系统(如 C_Mouse)保持不变,新类可选择性继承 C_Studies,也可完全不继承——两条线互不阻塞。等副线过了更严的测试、确认稳定可靠,再并回主类成为继承体系的一部分。 上篇已讲过指针用法,本篇就在这个基础上做更复杂的研究模型,且故意不用继承和多态,得到一个和原 C_Mouse 内部逻辑不同的分析模型。附带代码里只给了一条路径,但你可以改几处走另一条:差异很小,选哪条只看你想探多远。 外汇与贵金属 EA 开发高风险,并行试错能降主项目崩坏概率,但不代表副线策略本身有效。

把通用绘图塞进 C_Terminal

演示系统本可以不改主代码,但为了让对象创建从第一天就能在图表上跑起来并持续打磨,我们把它先搁在 C_Terminal 类里,等找到更优位置再挪。下面这个函数就是图表对象创建的通用入口,声明时只传了两个参数的调用你也常会见到——那是因为其余属性走了默认值,没必要每次都写。 关键在 OBJPROP_ZORDER 这个属性。若不给它明确赋值,在回放/模拟、demo 或实盘账户上往图表丢对象时,图层顺序会乱套,严重时会挡掉价格或指标显示。函数里默认 zOrder = -1,调用方可按需覆盖。 外汇与贵金属杠杆高、滑点跳空频繁,图表对象只是辅助观测,不代表任何方向确定性,实盘前务必在策略测试器里把回放跑通。 代码逐行看:inline void 声明为内联通用函数;ObjectCreate 用 m_Infos.ID 作图表 ID,时间/价格暂填 0,后续由具体对象重设;OBJPROP_TOOLTIP 设成换行符是清掉默认悬浮窗;OBJPROP_BACK 置 false 让对象浮在前景;COLOR、SELECTABLE、SELECTED 分别管颜色与不可选中;最后 ZORDER 落地图层序。

MQL5 / C++
class="kw">inline class="type">void CreateObjectGraphics(class="kw">const class="type">class="kw">string szName, class="kw">const ENUM_OBJECT obj, class="kw">const class="type">color cor, class="kw">const class="type">int zOrder = -class="num">1)
  {
    ObjectCreate(m_Infos.ID, szName, obj, class="num">0, class="num">0, class="num">0);
    ObjectSetString(m_Infos.ID, szName, OBJPROP_TOOLTIP, "\n");
    ObjectSetInteger(m_Infos.ID, szName, OBJPROP_BACK, class="kw">false);
    ObjectSetInteger(m_Infos.ID, szName, OBJPROP_COLOR, cor);
    ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTABLE, class="kw">false);
    ObjectSetInteger(m_Infos.ID, szName, OBJPROP_SELECTED, class="kw">false);
    ObjectSetInteger(m_Infos.ID, szName, OBJPROP_ZORDER, zOrder);
  }

「继承 C_Study 时的类扩展与 EA 差异」

做鼠标交互类扩展时,第一条路是继承 C_Study 而非 C_Mouse。头文件 C_StudyS2.mqh 里直接以 public 方式继承上一层的 C_StudyS1,再补自己的属性和内部函数,多数顺滑开发的场景选它最省事,但不是万能钥匙。 继承带来最直观的变化在 EA 侧:完整代码里只有高亮那几行和不用继承时不同,核心就是构造器走初始化列表把参数透传给基类。系统按预期演进、少意外事件时这套很稳,但后期若逻辑耦合变重,反而会拖慢你改功能的节奏。 下面这段是实际头文件与 EA 拼接后的关键代码,注意类声明和 DispatchMessage 里对基类方法的调用方式,复制进 MT5 的 Include 目录就能编译验证。外汇与贵金属品种波动剧烈,这类基于继承的交互系统仅建议在模拟盘先跑通。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "C_StudyS1.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">// ... Local definitions ....
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">// ... Local alias ...
class=class="str">"cmt">//+------------------------------------------------------------------+
class C_StudyS2 : class="kw">public C_StudyS1
{
   class="kw">protected:
   class="kw">private :
class=class="str">"cmt">// ... Code and internal functions ...
class=class="str">"cmt">//+------------------------------------------------------------------+
   class="kw">public  :
class=class="str">"cmt">//+------------------------------------------------------------------+
      C_StudyS2(C_Mouse *arg, class="type">color corP, class="type">color corN)
         :C_StudyS1(arg, corP, corN)
      {                       
class=class="str">"cmt">// ... Internal code ....
      }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">virtual 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="type">class="kw">double v1, v2;
         class="type">int w, h;
         class="type">class="kw">string sz1;
                        
         C_StudyS1::DispatchMessage(id, lparam, dparam, sparam);
class=class="str">"cmt">// ... Internal code ...
      }
class=class="str">"cmt">//+------------------------------------------------------------------+
};
class="macro">#class="kw">property copyright "Daniel Jose"
class="macro">#class="kw">property description "Generic EA for use on Demo account, replay system/simulator and Real account."
class="macro">#class="kw">property description "This system has means of sending orders using the mouse and keyboard combination."
class="macro">#class="kw">property description "For more information see the article about the system."
class="macro">#class="kw">property version   "class="num">1.30"
class="macro">#class="kw">property icon "../../Images/Icons/Replay - EA.ico"
class="macro">#class="kw">property link "[MQL5官方文档]
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Market Replay\System EA\Auxiliar\C_Mouse.mqh>
class="macro">#include <Market Replay\System EA\Auxiliar\Study\C_StudyS2.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">input group "Mouse";
class="kw">input class="type">color     user00 = clrBlack;      class=class="str">"cmt">//Price Line
class="kw">input class="type">color     user01 = clrPaleGreen; class=class="str">"cmt">//Positive Study
class="kw">input class="type">color     user02 = clrLightCoral; class=class="str">"cmt">//Negative Study
class=class="str">"cmt">//+------------------------------------------------------------------+
C_Mouse *mouse = NULL;
C_StudyS2 *extra = NULL;
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 把鼠标研究和盘口刷新挂进 EA 生命周期

这套 C_Mouse 与 C_StudyS2 的协作,必须借 EA 的标准事件函数落地,否则 MT5 不会主动把盘口和图表消息喂给你的类。 OnInit 里先 new 出 mouse 和 extra 两个对象,随后用 MarketBookAdd 订阅当前品种深度册,EventSetMillisecondTimer(500) 让 OnTimer 每 500 毫秒跑一次,给盘口研究留了固定刷新节拍。 价格深度变化走 OnBookEvent:非回放品种时调用 MarketBookGet 取真实 book 数组,回放品种则只开 1 个元素的占位数组,两种分支都丢给 extra.Update(book)。图表上的鼠标拖拽、点击由 OnChartEvent 分发到 mouse 和 extra,末尾 ChartRedraw 强制重绘。 外汇与贵金属点差跳动快,深度册订阅在高波动时段可能丢事件,实盘前应在 MTK 用 Print 打点确认 OnBookEvent 触发频率是否符合预期。 OnDeinit 里先 MarketBookRelease 退订、EventKillTimer 停表,再 delete extra 和 mouse,避免指标重载时对象残留在内存。OnTick 留空即可,这套逻辑不靠报价推动。

MQL5 / C++
class="type">int OnInit()
{
   mouse = new C_Mouse(user00, user01, user02);
   extra = new C_StudyS2(mouse, user01, user02);
   MarketBookAdd((*mouse).GetInfoTerminal().szSymbol);
   OnBookEvent((*mouse).GetInfoTerminal().szSymbol);
   EventSetMillisecondTimer(class="num">500);
   class="kw">return INIT_SUCCEEDED;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
{
   MarketBookRelease((*mouse).GetInfoTerminal().szSymbol);
   EventKillTimer();
   class="kw">delete extra;
   class="kw">delete mouse;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick() {}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTimer()
{
   (*extra).Update();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnBookEvent(class="kw">const class="type">class="kw">string &symbol)
{
   MqlBookInfo book[];
   if (mouse.GetInfoTerminal().szSymbol == def_SymbolReplay) ArrayResize(book, class="num">1, class="num">0); else
   {
      if (symbol != (*mouse).GetInfoTerminal().szSymbol) class="kw">return;
      MarketBookGet((*mouse).GetInfoTerminal().szSymbol, book);
   }
   (*extra).Update(book);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(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)
{
   (*mouse).DispatchMessage(id, lparam, dparam, sparam);
   (*extra).DispatchMessage(id, lparam, dparam, sparam);
   ChartRedraw();
}
class=class="str">"cmt">//+------------------------------------------------------------------+
让小布替你跑这套隔离验证
小布盯盘的AIGC已内置这类并行诊断视图,打开对应品种页即可看到辅助标记,你只需判断哪条研究线值得并回主类。

常见问题

已测试代码混入未验证函数会导致不可预测失败,平行类可随时丢弃或并入,不污染主线。
差异很小,主要取决于你想走多远;附代码含两种实现,一种继承C_Studies一种不继承,可并行对照。
能,小布内置的AIGC诊断视图可分离展示实验性研究线,减少你手动切项目的负担,你专注决策即可。
不会,通过指针与独立类并行工作,稳定后再纳入主类体系,外汇贵金属EA测试仍属高风险验证。
可用文中技术将系统回退到添加资源前的位置,避免重投早期开发,但建议仍用版本管理更稳。