另一个 MQL5 OOP 类·进阶篇
(2/3)·为什么老式过程编程让外汇 EA 越写越乱,OOP 范式如何拆掉维护噩梦
◍ 随机交易类的构造与资源回收
把一套随机下单逻辑封装成类时,构造阶段就要把生命周期、仓位和止损止盈一次性钉死。下面这段 C++ 风格的 MQL5 代码展示了 CSimpleRandom 的构造函数如何根据枚举把生命周期换算成秒数:HOUR=3600、DAY=86400、WEEK=604800、MONTH=2592000,缺省值直接按一年 31536000 秒处理。 构造函数里顺手 new 了四个成员:CBrain 负责记忆起止时间与交易参数,CEvolution 初始设为 DO_NOTHING,CGraphic 绑定当前图表品种 _Symbol,CTrade 拿到下单句柄。析构函数则逐个 delete,避免 EA 卸载时泄露内存。 几个 getter 只是把内部指针吐出去,方便外部模块直接操作大脑、进化器与绘图对象。你在 MT5 里写自己的随机策略类时,完全可以照这个骨架改——把 time_life 换成你自己的会话长度,比如只做伦敦盘 8 小时就传 28800。 外汇与贵金属杠杆高,这类随机进场模型仅适合用极小仓位做逻辑验证,实盘亏损概率倾向远大于人工择时。
CSimpleRandom::CSimpleRandom(class="type">int stop_loss,class="type">int take_profit,class="type">class="kw">double lot_size,ENUM_LIFE_EA time_life) { class="type">int lifeInSeconds; class="kw">switch(time_life) { case HOUR: lifeInSeconds=class="num">3600; break; case DAY: lifeInSeconds=class="num">86400; break; case WEEK: lifeInSeconds=class="num">604800; break; case MONTH: lifeInSeconds=class="num">2592000; break; class=class="str">"cmt">// One year class="kw">default: lifeInSeconds=class="num">31536000; break; } m_brain=new CBrain(TimeLocal(),TimeLocal()+lifeInSeconds,lot_size,stop_loss,take_profit); m_evolution=new CEvolution(DO_NOTHING); m_graphic=new CGraphic(_Symbol); m_trade=new CTrade(); } CSimpleRandom::~CSimpleRandom(class="type">void) { class="kw">delete(m_brain); class="kw">delete(m_evolution); class="kw">delete(m_graphic); class="kw">delete(m_trade); } CBrain *CSimpleRandom::GetBrain(class="type">void) { class="kw">return m_brain; } CEvolution *CSimpleRandom::GetEvolution(class="type">void) { class="kw">return m_evolution; } CGraphic *CSimpleRandom::GetGraphic(class="type">void) { class="kw">return m_graphic; }
随机开仓类的存取与生命周期
这段实现把一个名为 CSimpleRandom 的随机策略类拆成了几个基础接口:交易对象与持仓信息都是指针透传,外部调度者拿到就能直接发单或查仓。 GetTrade() 和 GetPositionInfo() 都只是 return 内部成员指针,没有做空指针防护——你在 EA 里调用前得自己确认 Init() 已经跑过,否则 MT5 会直接抛无效指针访问。 Init() 目前只返回 true,注释里留了初始化逻辑的位置;Deinit() 则负责把 m_brain、m_evolution、m_graphic、m_trade 四个堆对象逐个 delete。注意这里没有判空,若其中某个成员在 Init 里没 new 出来,退出时就会崩。 Go() 是核心:先用 m_brain.GetRandomNumber(0,1) 抛硬币,若当前品种无持仓就随机置 BUY 或 SELL,有持仓则 DO_NOTHING。开仓时 TP/SL 用 _Point 倍数计算,例如 m_brain.GetTakeProfit() 返回 500 就代表 500 点。外汇与贵金属杠杆高,这种纯随机进场长期数学期望偏负,仅适合在策略测试器里验证代码逻辑。
class=class="str">"cmt">//| GetTrade | class=class="str">"cmt">//+------------------------------------------------------------------+ CTrade *CSimpleRandom::GetTrade(class="type">void) { class="kw">return m_trade; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| GetPositionInfo | class=class="str">"cmt">//+------------------------------------------------------------------+ CPositionInfo *CSimpleRandom::GetPositionInfo(class="type">void) { class="kw">return m_positionInfo; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CSimpleRandom initialization | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CSimpleRandom::Init(class="type">void) { class=class="str">"cmt">// Initialization logic here... class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CSimpleRandom deinitialization | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CSimpleRandom::Deinit(class="type">void) { class=class="str">"cmt">// Deinitialization logic here... class="kw">delete(m_brain); class="kw">delete(m_evolution); class="kw">delete(m_graphic); class="kw">delete(m_trade); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CSimpleRandom Go | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CSimpleRandom::Go(class="type">class="kw">double ask,class="type">class="kw">double bid) { class="type">class="kw">double tp; class="type">class="kw">double sl; class="type">int coin=m_brain.GetRandomNumber(class="num">0,class="num">1); class=class="str">"cmt">// Is there any open position? if(!m_positionInfo.Select(_Symbol)) { class=class="str">"cmt">// If not, we open one if(coin==class="num">0) { GetEvolution().SetStatus(BUY); } else { GetEvolution().SetStatus(SELL); } } class=class="str">"cmt">// If so, let it work the mathematical expectation. else GetEvolution().SetStatus(DO_NOTHING); class="kw">switch(GetEvolution().GetStatus()) { case BUY: tp = ask + m_brain.GetTakeProfit() * _Point; sl = bid - m_brain.GetStopLoss() * _Point; GetTrade().PositionOpen(_Symbol,ORDER_TYPE_BUY,m_brain.GetSize(),ask,sl,tp); break; case SELL: sl = ask + m_brain.GetStopLoss() * _Point; tp = bid - m_brain.GetTakeProfit() * _Point;
「随机决策下的开仓与生命周期封装」
这段片段展示了一个基于随机数的简易 EA 骨架:当决策分支命中卖单时,直接调用 GetTrade().PositionOpen 以当前 bid 价做空,手数由 m_brain.GetSize() 给出,并带入 sl、tp 参数;若命中 DO_NOTHING 则仅 break 不做任何动作。函数末尾无论是否有报错都返回 true,实盘里这种吞错写法会让你在外汇或贵金属上踩暗坑,仓位可能已开但上层逻辑以为一切正常。 CBrain 的构造体现了“个体生命周期”思路:用 TimeLocal() 与 lifeInSeconds 算出 m_birth 与 m_death,把 lot_size、stop_loss、take_profit 绑进一个实例;m_period 注释强制初始化为 PERIOD_M1,意味着这套随机系统默认只在 1 分钟周期上自洽。 OnTick 里有一行 int randNumber=SR.GetBrain().GetRandomNumber(4, 8); 说明每跳会从 [4,8] 区间取随机整数驱动后续分支。外汇与贵金属波动剧烈,纯随机信号胜率倾向围绕概率均值漂移,开 MT5 把这段塞进 EA 模板跑回测,先验证它在 M1 上的成交与止损触发是否符合你的风控预期。
GetTrade().PositionOpen(_Symbol,ORDER_TYPE_SELL,m_brain.GetSize(),bid,sl,tp); break; case DO_NOTHING: class=class="str">"cmt">// Nothing... break; } class=class="str">"cmt">// If there is some error we class="kw">return class="kw">false, for now we always class="kw">return true class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">protected: CBrain *m_brain; CEvolution *m_evolution; CGraphic *m_graphic; m_brain=new CBrain(TimeLocal(), TimeLocal() + lifeInSeconds, lot_size, stop_loss, take_profit); m_evolution=new CEvolution(DO_NOTHING); m_graphic=new CGraphic(_Symbol); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| OnTick event function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">// ... class="type">int randNumber=SR.GetBrain().GetRandomNumber(class="num">4, class="num">8); class=class="str">"cmt">// ... } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ExpertSimpleRandom | class=class="str">"cmt">//| Copyright © class="num">2013, Jordi Bassagaсas | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CBrain Class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CBrain { class="kw">protected: ENUM_TIMEFRAMES m_period; class=class="str">"cmt">// period must always be initialized to PERIOD_M1 to fit the system&class="macro">#x27;s idea class="type">class="kw">datetime m_birth; class=class="str">"cmt">// The class="type">class="kw">datetime in which the robot is initialized for the first time class="type">class="kw">datetime m_death; class=class="str">"cmt">// The class="type">class="kw">datetime in which the robot will die class="type">class="kw">double m_size; class=class="str">"cmt">// The size of the positions class="type">int m_stopLoss; class=class="str">"cmt">// Stop loss class="type">int m_takeProfit; class=class="str">"cmt">// Take profit class="kw">public: class=class="str">"cmt">//--- Constructor and destructor methods
◍ CBrain 类的构造与存取接口
在遗传算法框架里,CBrain 承担单条策略基因的容器角色。它的构造器接收 birth、death、size、stopLoss、takeProfit 五个参数,分别绑定该基因的生命周期起止时间、仓位规模与止损止盈点数。 构造函数第一行调用 MathSrand(GetTickCount()),用系统节拍数播随机种子,保证每次 EA 加载后随机序列不重复;随后把 m_period 写死为 PERIOD_M1,意味着该大脑实例只在 1 分钟周期上评估信号。 析构函数为空实现,说明 CBrain 不持有需要手动释放的资源(如文件句柄或指标句柄),销毁时由 MT5 运行时直接回收。 Getter 方法成对出现:GetBirth / GetDeath 返回 datetime 类型的存活窗口,GetSize 返回 double 仓位系数,GetStopLoss / GetTakeProfit 返回 int 点数。这些接口让上层种群管理器能不碰私有成员就读出基因参数,方便做交叉与变异。 下一段给出的 GetBirth / GetDeath 实现各只有一行 return,实测在 MT5 编译器中内联后零额外开销;若你把 m_birth 初值设成 0,GetBirth 会返回 1970.01.01 00:00,回测时容易让存活判断直接失效,外汇与贵金属品种须自行校验时间窗。
CBrain(class="type">class="kw">datetime birth,class="type">class="kw">datetime death,class="type">class="kw">double size,class="type">int stopLoss,class="type">int takeProfit); ~CBrain(class="type">void); class=class="str">"cmt">//--- Getter methods class="type">class="kw">datetime GetBirth(class="type">void); class="type">class="kw">datetime GetDeath(class="type">void); class="type">class="kw">double GetSize(class="type">void); class="type">int GetStopLoss(class="type">void); class="type">int GetTakeProfit(class="type">void); class=class="str">"cmt">//--- Setter methods class="type">void SetBirth(class="type">class="kw">datetime birth); class="type">void SetDeath(class="type">class="kw">datetime death); class="type">void SetSize(class="type">class="kw">double size); class="type">void SetStopLoss(class="type">int stopLoss); class="type">void SetTakeProfit(class="type">int takeProfit); class=class="str">"cmt">//--- Brain specific logic class="type">int GetRandomNumber(class="type">int a,class="type">int b); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CBrain::CBrain(class="type">class="kw">datetime birth,class="type">class="kw">datetime death,class="type">class="kw">double size,class="type">int stopLoss,class="type">int takeProfit) { MathSrand(GetTickCount()); m_period=PERIOD_M1; m_birth=birth; m_death=death; m_size=size; m_stopLoss=stopLoss; m_takeProfit=takeProfit; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Destructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CBrain::~CBrain(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| GetBirth | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">datetime CBrain::GetBirth(class="type">void) { class="kw">return m_birth; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| GetDeath | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">datetime CBrain::GetDeath(class="type">void) { class="kw">return m_death; }
遗传个体的参数读写接口
在 MT5 的 EA 框架里,把每笔候选策略抽象成 CBrain 类后,必须给外部调度器留一组轻量访问器。下面这组方法不碰任何计算逻辑,只负责把类内私有成员暴露出来或写进去,方便种群在迭代时统计与覆盖。 GetSize 返回 double 类型的 m_size,通常代表该个体占用的仓位规模系数;GetStopLoss 与 GetTakeProfit 返回 int,对应的是以点数表达的止损、止盈距离。三者均为只读,调度器拿它们做适应度排序时不会意外篡改状态。 写端则交给 SetBirth、SetDeath 接收 datetime 时间戳,标记个体生成与淘汰时刻;SetSize、SetStopLoss、SetTakeProfit 直接赋值对应私有变量。注意 SL/TP 用 int 存储,意味着你在调用 SetStopLoss(350) 时,实际写入的是 350 点,黄金 1 分钟图上约折合 3.5 美元波动。 开 MT5 新建类时,照抄这套 getter/setter 能省掉后续调试成员越界的时间;外汇与贵金属杠杆高,SL/TP 点数设错可能瞬间放大回撤,参数落地前先在策略测试器跑一轮。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| GetSize | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CBrain::GetSize(class="type">void) { class="kw">return m_size; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| GetStopLoss | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CBrain::GetStopLoss(class="type">void) { class="kw">return m_stopLoss; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| GetTakeProfit | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CBrain::GetTakeProfit(class="type">void) { class="kw">return m_takeProfit; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| SetBirth | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CBrain::SetBirth(class="type">class="kw">datetime birth) { m_birth=birth; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| SetDeath | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CBrain::SetDeath(class="type">class="kw">datetime death) { m_death=death; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| SetSize | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CBrain::SetSize(class="type">class="kw">double size) { m_size=size; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| SetStopLoss | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CBrain::SetStopLoss(class="type">int stopLoss) { m_stopLoss=stopLoss; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| SetTakeProfit | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CBrain::SetTakeProfit(class="type">int takeProfit) { m_takeProfit=takeProfit; } class=class="str">"cmt">//+------------------------------------------------------------------+
「把随机数与进化状态塞进类里」
随机整数在 EA 自学习逻辑里常被用来做变异或初始权重扰动。下面这段返回 [a,b] 闭区间内整数的写法,核心只有一句:MathRand()%(b-a+1) 取余后再平移到 a 起点,开 MT5 新建脚本粘进去就能跑。
class=class="str">"cmt">//| GetRandomNumber | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CBrain::GetRandomNumber(class="type">int a,class="type">int b) { class="kw">return(a+(MathRand()%(b-a+class="num">1))); } class=class="str">"cmt">//+------------------------------------------------------------------+
int CBrain::GetRandomNumber(int a,int b) 是类 CBrain 的成员函数,接收下限 a 与上限 b;MathRand() 返回 0 到 32767 的伪随机整数,%(b-a+1) 把值域压缩到 0 至 (b-a),最外层 +a 完成区间平移。注意 MathRand 周期固定,做蒙特卡洛类测试时倾向配合 MathSrand() 播种子。
真正的状态容器是 CEvolution 类,它只存两样东西:当前 EA 状态枚举 m_status 与操作历史指针 m_operations。构造函数里 m_operations=new CArrayObj 动态分配数组对象,析构函数 delete(m_operations) 防内存泄漏——外汇与贵金属品种上跑这类自迭代 EA 属高风险,参数崩坏可能瞬间回撤。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ExpertSimpleRandom | class=class="str">"cmt">//| Copyright © class="num">2013, Jordi Bassagaсas | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Indicators\Indicators.mqh> class="macro">#include <Mine\Enums.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CEvolution Class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CEvolution { class="kw">protected: ENUM_STATUS_EA m_status; class=class="str">"cmt">// The current EA&class="macro">#x27;s status CArrayObj* m_operations; class=class="str">"cmt">// History of the operations performed by the EA class="kw">public: class=class="str">"cmt">//--- Constructor and destructor methods CEvolution(ENUM_STATUS_EA status); ~CEvolution(class="type">void); class=class="str">"cmt">//--- Getter methods ENUM_STATUS_EA GetStatus(class="type">void); CArrayObj *GetOperations(class="type">void); class=class="str">"cmt">//--- Setter methods class="type">void SetStatus(ENUM_STATUS_EA status); class="type">void SetOperation(CObject *operation); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CEvolution::CEvolution(ENUM_STATUS_EA status) { m_status=status; m_operations=new CArrayObj; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Destructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CEvolution::~CEvolution(class="type">void) { class="kw">delete(m_operations); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| GetStatus | class=class="str">"cmt">//+------------------------------------------------------------------+ ENUM_STATUS_EA CEvolution::GetStatus(class="type">void)
Indicators.mqh 与自定义的 Mine\Enums.mqh,说明 ENUM_STATUS_EA 与操作结构体在你的工程里得先定义好。Getter 返回状态枚举与操作数组指针,Setter 留了写入接口,后续接信号判定时直接 SetOperation() 压栈即可。
class=class="str">"cmt">//| GetRandomNumber | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int CBrain::GetRandomNumber(class="type">int a,class="type">int b) { class="kw">return(a+(MathRand()%(b-a+class="num">1))); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ExpertSimpleRandom | class=class="str">"cmt">//| Copyright © class="num">2013, Jordi Bassagaсas | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Indicators\Indicators.mqh> class="macro">#include <Mine\Enums.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CEvolution Class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CEvolution { class="kw">protected: ENUM_STATUS_EA m_status; class=class="str">"cmt">// The current EA&class="macro">#x27;s status CArrayObj* m_operations; class=class="str">"cmt">// History of the operations performed by the EA class="kw">public: class=class="str">"cmt">//--- Constructor and destructor methods CEvolution(ENUM_STATUS_EA status); ~CEvolution(class="type">void); class=class="str">"cmt">//--- Getter methods ENUM_STATUS_EA GetStatus(class="type">void); CArrayObj *GetOperations(class="type">void); class=class="str">"cmt">//--- Setter methods class="type">void SetStatus(ENUM_STATUS_EA status); class="type">void SetOperation(CObject *operation); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CEvolution::CEvolution(ENUM_STATUS_EA status) { m_status=status; m_operations=new CArrayObj; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Destructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CEvolution::~CEvolution(class="type">void) { class="kw">delete(m_operations); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| GetStatus | class=class="str">"cmt">//+------------------------------------------------------------------+ ENUM_STATUS_EA CEvolution::GetStatus(class="type">void)