另一个 MQL5 OOP 类·综合运用
🧩

另一个 MQL5 OOP 类·综合运用

(3/3)·当你已懂基础语法却卡在系统整合,这篇用完整案例把类、范式与回测串成一条线

案例拆解 第 3/3 篇

不少交易者写得出单个 MQL5 类,却总在把策略逻辑、对象职责和回测验证拼到一起时乱了阵脚。本文是系列收尾篇,直接用一个随机交易 EA 的完整生命周期,演示 OOP 在实盘编程里的综合用法。

◍ 进化类与图形容器的存取接口

这段代码片段展示了一个简易 EA 框架里两个基础类的存取设计:CEvolution 负责维护状态与操作集合,CGraphic 负责承载某交易品种的周期与 K 线数组。 CEvolution 用 GetStatus() 直接返回 m_status 枚举,GetOperations() 返回 CArrayObj 指针,外部可遍历已加入的 CObject 派生操作。SetOperation() 内部调用 m_operations.Add(operation),意味着每次挂新操作都往同一动态数组追加,没有去重逻辑。 CGraphic 的成员变量暴露了三个关键信息:m_period 存 ENUM_TIMEFRAMES,m_pair 存品种名,m_symbol 是 CSymbolInfo 指针,m_bars 是 CArrayObj 指针用于装 K 线。它的公开接口只列了构造/析构、GetPair、GetSymbol、GetBars、SetPair,说明图形对象本身不负责写盘,只做内存数据桥接。 在 MT5 里跑这套结构时,注意 m_operations.Add() 若传入栈对象地址会随作用域销毁而悬空;用 new 派生自 CObject 的实例并交给数组托管才稳。外汇与贵金属杠杆高,这类框架仅作逻辑原型,实盘前须自行补风控。

MQL5 / C++
  {
   class="kw">return m_status;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| GetOperations                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
CArrayObj *CEvolution::GetOperations(class="type">void)
  {
   class="kw">return m_operations;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| SetStatus                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CEvolution::SetStatus(ENUM_STATUS_EA status)
  {
   m_status=status;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| SetOperation                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CEvolution::SetOperation(CObject *operation)
  {
   m_operations.Add(operation);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                     ExpertSimpleRandom.mq5 |
class=class="str">"cmt">//|          Copyright © class="num">2013, Jordi Bassagaсas |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Trade\SymbolInfo.mqh>
class="macro">#include <Arrays\ArrayObj.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| CGrapic Class                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CGraphic
  {
class="kw">protected:
   ENUM_TIMEFRAMES   m_period;           class=class="str">"cmt">// Graphic&class="macro">#x27;s timeframe
   class="type">class="kw">string            m_pair;             class=class="str">"cmt">// Graphic&class="macro">#x27;s pair
   CSymbolInfo*      m_symbol;           class=class="str">"cmt">// CSymbolInfo object
   CArrayObj*        m_bars;             class=class="str">"cmt">// Array of bars
class="kw">public:
   class=class="str">"cmt">//--- Constructor and destructor methods
                     CGraphic(class="type">class="kw">string pair);
                    ~CGraphic(class="type">void);
   class=class="str">"cmt">//--- Getter methods
   class="type">class="kw">string            GetPair(class="type">void);
   CSymbolInfo      *GetSymbol(class="type">void);
   CArrayObj        *GetBars(class="type">void);
   class=class="str">"cmt">//--- Setter methods
   class="type">void              SetPair(class="type">class="kw">string pair);

「图形类成员与存取接口的实现骨架」

在自定义图形分析类里,构造函数只接一个货币对字符串,默认周期锁在 M1。这意味着你 new 一个对象时若不带参数重载,它只会去抓一分钟裸 K,做更高周期的价格行为还原得自己改 m_period。 下面这段是类里暴露给外部的存取通道:拿货币对、拿品种信息指针、拿 K 线对象数组,以及对应的 setter。注意 GetSymbol 返回的是 CSymbolInfo*,未初始化时就是空指针,直接解引用会崩 EA。 void SetSymbol(CSymbolInfo *symbol); void SetBar(CObject *bar); }; //+------------------------------------------------------------------+

//Constuctor

//+------------------------------------------------------------------+ CGraphic::CGraphic(string pair) { m_period=PERIOD_M1; m_pair=pair; } //+------------------------------------------------------------------+

//Destructor

//+------------------------------------------------------------------+ CGraphic::~CGraphic(void) { } //+------------------------------------------------------------------+

//GetPair

//+------------------------------------------------------------------+ string CGraphic::GetPair(void) { return m_pair; } //+------------------------------------------------------------------+

//GetSymbol

//+------------------------------------------------------------------+ CSymbolInfo *CGraphic::GetSymbol(void) { return m_symbol; } //+------------------------------------------------------------------+

//GetBars

//+------------------------------------------------------------------+ CArrayObj *CGraphic::GetBars(void) { return m_bars; } //+------------------------------------------------------------------+

//SetPair

//+------------------------------------------------------------------+ void CGraphic::SetPair(string pair) { m_pair=pair; } //+------------------------------------------------------------------+

//SetSymbol

//+------------------------------------------------------------------+ void CGraphic::SetSymbol(CSymbolInfo *symbol) { m_symbol=symbol; } //+------------------------------------------------------------------+

//SetBar

//+------------------------------------------------------------------+ 逐行拆一下关键几行:CGraphic::CGraphic(string pair) 是构造器,把传入的 pair 存进 m_pair,同时 m_period 写死 PERIOD_M1。string CGraphic::GetPair(void) 单纯回传 m_pair,无副作用。CSymbolInfo *CGraphic::GetSymbol(void) 回传 m_symbol 指针,调用前必须确认 SetSymbol 已跑过。void CGraphic::SetSymbol(CSymbolInfo *symbol) 把外部品种对象地址赋给成员,后续点差、tick 值计算都靠它。 外汇与贵金属杠杆高、滑点跳空频繁,在 MT5 里跑这套类之前,先手动给 SetSymbol 传一个已 Refresh 的 CSymbolInfo,否则 GetSymbol 拿到的空指针会让回测在首根 K 线就终止。

MQL5 / C++
class="type">void SetSymbol(CSymbolInfo *symbol);
class="type">void SetBar(CObject *bar);
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Constuctor                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
CGraphic::CGraphic(class="type">class="kw">string pair)
  {
   m_period=PERIOD_M1;
   m_pair=pair;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Destructor                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
CGraphic::~CGraphic(class="type">void)
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| GetPair                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string CGraphic::GetPair(class="type">void)
  {
   class="kw">return m_pair;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| GetSymbol                                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
CSymbolInfo *CGraphic::GetSymbol(class="type">void)
  {
   class="kw">return m_symbol;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| GetBars                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
CArrayObj *CGraphic::GetBars(class="type">void)
  {
   class="kw">return m_bars;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| SetPair                                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CGraphic::SetPair(class="type">class="kw">string pair)
  {
   m_pair=pair;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| SetSymbol                                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CGraphic::SetSymbol(CSymbolInfo *symbol)
  {
   m_symbol=symbol;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| SetBar                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+

给图形对象追加单根K线

在 MQL5 的 CGraphic 封装里,向图表叠加自定义 K 线并不复杂,核心就是往内部容器塞对象。下面这段逻辑把外部传入的 CObject 指针直接挂到 m_bars 动态数组尾部。 m_bars 通常是 CArrayObj 类型,Add() 只做指针接管、不复制实体,所以 bar 的生命周期得由调用方保证,否则图形刷新时容易踩野指针。 实际调试时可在 MT5 策略测试器里下断点,观察 m_bars.Total() 是否随每次 SetBar 调用 +1,验证追加是否生效。

MQL5 / C++
class="type">void CGraphic::SetBar(CObject *bar)
  {
   m_bars.Add(bar);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 随机系统的止损获利只在回测里现形

把 ExpertSimpleRandom.mq5 跑进 MT5 策略测试器,结论很直接:这套纯随机进场的逻辑,只对某些特定的止损、获利间距勉强有效。而且赢家参数不跨品种通用——每个货币对在同一时段的波动性格不同,EURUSD 的甜点位是 400/600,换 pair 可能完全失效。 所以真金白银上之前,先拿历史数据把 StopLoss、TakeProfit 扫一遍。下面这组是 2012 年 1 月 EURUSD 的采样输入:止损 400 点、获利 600 点、手数 0.01、生命周期按月。同一配置连跑三次,结果分布会随随机种子漂移,但能反推出该间距下系统不亏的区间。 外汇和贵金属都是高杠杆高风险品种,这类随机系统实盘存活概率极低,回测赢家参数只是帮你确认「随机也能碰巧活」的边界,不是交易依据。

「把工具请下神坛」

把自动交易系统当成有大脑、会演变、看图表的生命体,只是帮你把“震荡市空头近似随机”的直觉落成代码。2012年1月跑过多轮回测,多数情况下系统能赢,但效率受限于策略本身的简单性,别指望它扛住所有行情。 社区里有人指出 CSimpleRandom.mqh 漏了 CPositionInfo 对象创建,直接编译跑会触发无效指针访问;作者在 ExpertSimpleRandom.mq5 第23行补了 CSimpleRandom *SR=new CSimpleRandom(...),并在后续重传了文件。你拉代码时认准这一行,否则 MT5 会报指针错。 外汇和贵金属是高杠杆品种,这类随机倾向策略只在特定震荡结构里概率占优,实盘前务必用策略测试器自跑一遍,把 StopLoss、TakeProfit、LotSize 按你的品种波动调一调。

MQL5 / C++
class="num">82 строчка  m_positionInfo=new CPositionInfo();
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|ExpertSimpleRandom.mq5
class=class="str">"cmt">//|版权所有 © class="num">2013, Jordi Bassagaças ||
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright   "版权所有 © class="num">2013, laplacianlab"
class="macro">#class="kw">property link        "[MQL5官方文档]
class="macro">#class="kw">property version     "class="num">1.00"
class="macro">#include <Trade\Trade.mqh>
class="macro">#include <Trade\SymbolInfo.mqh>
class="macro">#include <Trade\PositionInfo.mqh>
class="macro">#include <Indicators\Indicators.mqh>
class="macro">#include <Mine\Enums.mqh>
class="macro">#include <..\Experts\SimpleRandom\CSimpleRandom.mqh>
class="kw">input class="type">int             StopLoss;
class="kw">input class="type">int             TakeProfit;
class="kw">input class="type">class="kw">double          LotSize;
class="kw">input ENUM_LIFE_EA    TimeLife;
class="type">MqlTick tick;
CSimpleRandom *SR=new CSimpleRandom(StopLoss,TakeProfit,LotSize,TimeLife);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 初始化函数|
class=class="str">"cmt">//+------------------------------------------------------------------+
把重复劳动交给小布
这类 EA 的回测参数扫描和类结构检查,小布盯盘的 AIGC 已内置,打开对应品种页即可看到差异报告,你专注决策就好。

常见问题

过程式把逻辑拆成函数后,状态分散难以统一维护;OOP 把数据和行为封进类,但类间依赖若没设计好,改一处可能牵动多处,贵金属与外汇品种切换时更明显,属高风险环境。
可以。小布盯盘内置的 AIGC 能读取 ExpertSimpleRandom 类结构并标注回测异常点,省去手动逐行排查,但信号仍仅供参考,决策权在你。
随机类 EA 逻辑极简,能让人把注意力放在对象职责划分与回测框架上,避免被复杂指标干扰,更适合展示范式迁移。
tick 模式与历史质量会影响类内状态复位,可能在概率上造成拟合偏差;建议多周期抽样验证,外汇贵金属杠杆高,回测不代表未来。