使用面向对象的方法来编写EA的模式·综合运用
🧩

使用面向对象的方法来编写EA的模式·综合运用

(3/3)·从开发阶段矩阵到七种MQL5运行模式,一篇收束前两部分未尽的工程化拼图

案例拆解 第 3/3 篇
不少人把EA写完能跑就算交付,却没留意调试、分析、回测、实盘是七种不同运行态。同一份代码在发布模式和调试模式下行为可能完全不同,混淆它们容易误判策略表现。

◍ 策略基类的虚函数与信号检查骨架

在 MT5 里写一套可复用的 EA 框架,第一步往往是把「主流程」和「信号判定」拆进基类。下面这段 CModeBase 的声明,把开仓、平仓、移动止损都留成虚函数,子类只重写逻辑,不碰调度。 公开区里 Main/Open/Close/Trail 全是 virtual void,等于给后代类留了四个空插槽;CheckMqlMode 是静态方法,用来在初始化时判断当前是实时还是回测环境。SetTrailing 接收两个 uint 参数,分别控制止损绝对值与步进,调参时直接改这两个数就能切换激进或保守风控。 保护区的 CheckOpenSignal / CheckCloseSignal / CheckTrailSignal 统一返回 ENUM_ORDER_TYPE,把「该不该动手」收敛成枚举,避免满屏 bool 旗标;GetMacdVal 带一个 _is_main 默认 true,说明主线和信号线取值走同一入口。私有区的 NormalPrice / NormalSL / NormalTP / NormalLot 负责把外部传来的浮点按品种精度与仓位规则做裁剪,外汇与贵金属点差跳动大,这类标准化函数能少踩很多坑。 CModeTester 直接继承 CModeBase,注释写明专用于测试模式,意味着你新写的策略先丢进这个类跑历史数据,验证信号链没断再换实盘派生类。外汇贵金属高风险,回测通过不等于实盘概率占优,开 MT5 用策略测试器跑一遍 EURUSD 的 2023 年 tick 数据最直观。

MQL5 / C++
class="kw">virtual class="type">void      Main(class="type">void){};
class=class="str">"cmt">//--- 程序
class="kw">virtual class="type">void      Open(class="type">void){};
class="kw">virtual class="type">void      Close(class="type">void){};
class="kw">virtual class="type">void      Trail(class="type">void){};
class=class="str">"cmt">//--- 服务
class="kw">static ENUM_MQL_MODE CheckMqlMode(class="type">void);
ENUM_MQL_MODE    GetMqlMode(class="type">void);
class="type">void             SetMqlMode(class="kw">const ENUM_MQL_MODE _mode);
class="type">void             SetTrailing(class="kw">const class="type">uint _trailing,class="kw">const class="type">uint _trail_step);
class="kw">protected:
class=class="str">"cmt">//--- 函数
ENUM_ORDER_TYPE  CheckOpenSignal(class="kw">const ENUM_ORDER_TYPE _open_sig);
ENUM_ORDER_TYPE  CheckCloseSignal(class="kw">const ENUM_ORDER_TYPE _close_sig);
ENUM_ORDER_TYPE  CheckTrailSignal(class="kw">const ENUM_ORDER_TYPE _trail_sig,class="type">class="kw">double &_sl_pr);
class=class="str">"cmt">//---
class="type">class="kw">double           GetMacdVal(class="kw">const class="type">int _idx,class="kw">const class="type">bool _is_main=true);
class="kw">private:
class=class="str">"cmt">//--- 宏
class="type">bool             RefreshIndicatorData(class="type">void);
class=class="str">"cmt">//--- 标准化
class="type">class="kw">double           NormalPrice(class="type">class="kw">double d);
class="type">class="kw">double           NormalDbl(class="type">class="kw">double d,class="type">int n=-class="num">1);
class="type">class="kw">double           NormalSL(class="kw">const ENUM_ORDER_TYPE _ord_type,class="type">class="kw">double op,class="type">class="kw">double pr,
                         class="type">uint SL,class="type">class="kw">double stop);
class="type">class="kw">double           NormalTP(class="kw">const ENUM_ORDER_TYPE _ord_type,class="type">class="kw">double op,class="type">class="kw">double pr,
                         class="type">uint _TP,class="type">class="kw">double stop);
class="type">class="kw">double           NormalLot(class="kw">const class="type">class="kw">double _lot);
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CModeTester 类                                                  |
class=class="str">"cmt">//| 目的:用于测试模式的类                                          |
class=class="str">"cmt">//| 继承于CModeBase类                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CModeTester : class="kw">public CModeBase
  {
class=class="str">"cmt">//--- === 方法 === --- 
class="kw">public:
  class=class="str">"cmt">//--- 构造函数/析构函数

「把交易循环塞进一个 Main 里跑」

回测框架里最省事的做法,是把平仓、开仓、追踪止损三个动作收进一个虚函数 Main,每根 K 线只调一次 this.Main()。下面这段 CModeTester 的声明和 Main 实现,就是典型骨架。 [CODE] void CModeTester(void){}; void ~CModeTester(void){}; //--- 模块 virtual void Main(void); //--- 程序 virtual void Open(void); virtual void Close(void); virtual void Trail(void); }; //+------------------------------------------------------------------+

//主模块

//+------------------------------------------------------------------+ void CModeTester::Main(void) { //--- 1) 平仓 this.Close(); //--- 2) 开仓 this.Open(); //--- 3) 滑动止损 this.Trail(); } [/CODE] 逐行看:前两句是构造和析构的空实现;Main、Open、Close、Trail 都标了 virtual,意味着子类能各自重写逻辑。Main 函数体内严格按 平仓→开仓→追踪 的顺序调用,避免先开仓再平旧仓造成的手数叠加。 实盘日志里能直接验证这套顺序。2014.11.14 22:15:00 这根 bar,先打印 Signal to open: SELL,紧接着 FI 行显示倒数第二根 bar 的 main 值 0.002117、signal 值 0.002109,DL 行显示最后一根 bar main 0.002001、signal 0.002118——signal 上穿触发卖单。随后 LO 行成交 market sell 0.03 EURUSD.e,价格 1.25242,点差 0.9 点(1.25251-1.25242)。 外汇和贵金属杠杆高,这类按 bar 触发的回测信号在实盘可能因滑点、停牌而偏离;上述 0.03 手 EURUSD.e 成交仅作框架验证,不暗示任何收益。开 MT5 用策略测试器跑同时间段,把 Main 里的三个调用顺序调换,能直观看到平仓滞后带来的浮亏扩大。

MQL5 / C++
class="type">void CModeTester(class="type">void){};
class="type">void ~CModeTester(class="type">void){};
class=class="str">"cmt">//--- 模块
class="kw">virtual class="type">void Main(class="type">void);
class=class="str">"cmt">//--- 程序
class="kw">virtual class="type">void Open(class="type">void);
class="kw">virtual class="type">void Close(class="type">void);
class="kw">virtual class="type">void Trail(class="type">void);
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 主模块                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CModeTester::Main(class="type">void)
  {
class=class="str">"cmt">//--- class="num">1) 平仓
   this.Close();
class=class="str">"cmt">//--- class="num">2) 开仓
   this.Open();
class=class="str">"cmt">//--- class="num">3) 滑动止损
   this.Trail();
  }

从成交日志反推平仓信号触发

一段真实的 EURUSD.e 日志能拆出策略的平仓逻辑。2014.11.14 22:15 以 1.25242 市价卖空 0.03 手,持仓到 2014.11.17 13:30:20 才收到 SELL 平仓信号,随即以 1.25047 买入平仓,单边滑点显示成交价落在卖一 1.25039 与买一 1.25047 之间。 触发依据是两根bar的主线与信号线差值:倒数第二根 main -0.001218 / signal -0.001148,最后一根 main -0.001123 / signal -0.001189。信号线在末根 bar 下穿幅度扩大( signal 从 -0.001148 走到 -0.001189),主线收敛,组合被判定为趋势衰竭。 这类日志直接暴露了 EA 的离场偏好:不在实时 tick 平仓,而是等当前 bar 走完、用已闭合的两根 bar 做交叉确认。外汇与贵金属杠杆高,这种延迟确认在跳空行情中可能扩大回撤,建议在 MT5 策略测试器里把同段日志导出来比对成交时间,验证你自己的平仓延迟参数。

◍ 把测试下单记录画到图上

从一段真实回测日志能看到,2014.11.17 13:30:20 在 EURUSD.e 以 1.25047 市价买入 0.03 手,这条记录由 CTrade::OrderSend 打出,说明策略在可视化测试模式下确实触发了开仓。 要把这类信号留在图表上供肉眼复核,可继承 CModeTester 写一个 CModeVisual 类。它内部用 CArrayObj 存图形对象,并用 m_subwindow_max / m_subwindow_min 记录子窗口的上下边界,方便把信号线缩放到正确位置。 类里公开了 Open() 与 Close() 两个虚函数,分别对应测试开始与结束时的绘图动作;私有方法 CreateSignalLine() 负责按是否开仓信号、是否新 K 线来画线,CreateRectangle() 按订单类型画矩形区,RefreshRectangles() 做批量刷新。外汇与贵金属回测含高杠杆风险,图形验证仅辅助判断信号逻辑,不预示实盘盈亏。

MQL5 / C++
class CModeVisual : class="kw">public CModeTester
  {
class=class="str">"cmt">//--- === 数据成员 === --- 
class="kw">private:
   CArrayObj         m_objects_arr;
   class="type">class="kw">double            m_subwindow_max;
   class="type">class="kw">double            m_subwindow_min;
   
class=class="str">"cmt">//--- === 方法 === --- 
class="kw">public:
   class=class="str">"cmt">//--- 构造函数/析构函数
   class="type">void              CModeVisual(class="type">void);
   class="type">void             ~CModeVisual(class="type">void);
   class=class="str">"cmt">//--- 程序
   class="kw">virtual class="type">void      Open(class="type">void);
   class="kw">virtual class="type">void      Close(class="type">void);
class="kw">private:
   class="type">bool              CreateSignalLine(class="kw">const class="type">bool _is_open_sig,class="kw">const class="type">bool _is_new_bar=true);
   class="type">bool              CreateRectangle(class="kw">const ENUM_ORDER_TYPE _signal);
   class="type">void              RefreshRectangles(class="type">void);
   };

「多模式开发的下一步落点」

把交易算法拆成多模式来写,确实会多出不少开发开销,但每一步都能逼你摸清楚 MT5 和 MQL5 各个阶段的细节,面向对象写法在这儿能省掉大量重复劳动。 本次附带的 CisNewBar.mqh(6.99 KB)与 Modes.mqh(31.24 KB)已经把新 K 线判定和模式切换的基础框架搭好,Test1_Modes_EA.mq5(4.75 KB)和 Test2_Modes_EA.mq5(5.04 KB)可直接丢进策略测试器跑通两种模式。 优化逻辑和时间框架聚合模式留到后续统计特性文章再展开,有兴趣的现在就能把 ZIP 里的五个 mq5 拖进 MT5 验证一遍基础架子的行为。外汇与贵金属波动剧烈、杠杆风险高,任何多模式 EA 都先在模拟盘确认稳定性再谈实盘。

把模式自检交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到当前EA处于何种运行态,你只需专注策略逻辑本身。

常见问题

变量矩阵描述开发各阶段所需的工具、知识与人力的组合维度;只有高代码质量实现的盈利策略才进入算法交易阶段,矩阵用来定位当前短板。
一类需要源文件与可执行文件并存,如调试和分析模式;另一类仅需可执行文件,如发布模式与部分测试模式。
可以,小布盯盘的品种页已集成运行态识别,加载EA后会标注其当前模式,省去手动打印ENUM_MQL_MODE的麻烦。
测试器借助其他模式获取诊断信息便于修正,而实盘成型程序以发布模式运行以避免调试开销并保证标准行为。
通过分层类封装各阶段职责,在循环回路上只替换具体实现,不改动外层调用,从而渐进演进且保持结构稳定。