使用单独模块构建智能交易系统·综合运用
(3/3)·从混乱绑定的实战坑到清晰模块交互,一篇看清独立组件如何拼出易维护智能交易系统
「用上一根K线收阴直接空的逻辑骨架」
这段 MQL5 片段给出了一个极简策略类的成员结构和触发入口:只要上一根当前周期 K 线收盘低于开盘,就走 Sell(),不依赖任何指标过滤。 进场函数里先判断 m_tb 是否为 0,非零说明已有挂单或持仓标记,直接 return false 放弃本次信号,避免重复触发。接着取 PERIOD_CURRENT 下第 1 根(已收盘)K 线的开盘价 o 与收盘价 c,c < o 即阴线才允许卖出。 初始化子类 CInit1 用 CheckPointer 校验 pparam 与 psnap 指针有效性,任一为 POINTER_INVALID 就返回 false,通过后把三个模块指针挂到实例上。外汇与贵金属杠杆高,这种裸 K 反手在震荡市可能频繁止损,上 MT5 用策略测试器跑 EURUSD 15M 能看到信号密度。
CParam* m_pparam; CSnap* m_psnap; CTradeMod* m_ptrade; class="type">class="kw">ulong m_tb; }; class="kw">static class="type">class="kw">string CRealStrat1::m_name = "Real Strategy class="num">1"; class="type">bool CRealStrat1::MayAndEnter() { if (tb != class="num">0) class="kw">return false; class="type">class="kw">double o = iOpen(NULL,PERIOD_CURRENT,class="num">1); class="type">class="kw">double c = iClose(NULL,PERIOD_CURRENT,class="num">1); if (c < o) { m_ptrade.Sell(); class="kw">return true; } class="kw">return false; } class CInit1: class="kw">public CInitializeStruct { class="kw">public: class="type">bool Initialize(CParam* pparam, CSnap* psnap, CTradeMod* ptrade) { if (CheckPointer(pparam) == POINTER_INVALID || CheckPointer(psnap) == POINTER_INVALID) class="kw">return false; m_pparam = pparam; m_psnap = psnap; m_ptrade = ptrade; class="kw">return true; } CParam* m_pparam; CSnap* m_psnap; CTradeMod* m_ptrade; };
◍ 初始化与报价响应的骨架怎么搭
EA 的生命周期里,OnInit() 只做一件事:new 一个 CRealStrat1 实例,并用 CInit1 对象把参数、快照、交易接口三个全局模块挂上去。挂失败就返回 INIT_FAILED,MT5 会在日志里直接报初始化错误,EA 不会跑。 OnDeinit() 里用 CheckPointer 判一下指针有效性再 delete,避免重复释放或野指针。这个写法在切换周期、重载 EA 时不会抛 'object already deleted' 异常。 OnTick() 被压到极简:IsNewCandle() 为真才刷新快照并跑策略判断。实测在 M1 上每根 K 线只进一次逻辑,CPU 占用比每 tick 都算低一个数量级,回测时也少很多无意义计算。 这种结构留了个口子——你想让 EA 等用户点按钮再交易,直接在 OnTick 里加个全局开关判断就行,不用动策略类内部。外汇和贵金属杠杆高,这类开关能帮你避开开盘瞬间的滑点乱单,但仍是概率性防护,不保证不亏损。
CParam par; CSnap snap; CTradeMod trade; CStrategy* pS1; class="type">int OnInit() { ... pS1 = new CRealStrat1(); CInit1 ci; if (!ci.Initialize(GetPointer(par), GetPointer(snap), GetPointer(trade)) ) class="kw">return (INIT_FAILED); pS1.Initialize(GetPointer(ci)); class="kw">return (INIT_SUCCEEDED); } class="type">void OnDeinit(const class="type">int reason) { if (CheckPointer(pS1) != POINTER_INVALID) class="kw">delete pS1; } class="type">void OnTick() { if (IsNewCandle() ){ pS1.CreateSnap(); pS1.MayAndEnter(); } }
在一个EA里热插拔多套策略
把日程表或策略写死在原有类里,后期调试和维护会迅速失控。更干净的做法是把模块做成可替换对象:为同一逻辑准备“测试版”和“发布版”,再用一个管理器控制加载哪一个。 沿这套思路能走得更远——直接动态替换 EA 的内核策略。比如派生自 CStrategy 的 CRealStrat1 负责趋势,再挂一个横盘策略类,甚至能在亚洲时段切出第三套逻辑。一个 EA 内跑多个智能系统并按条件切换,在 MT5 上并非难事。 具体落地四步:写决策类继承 CStrategy(如 CRealStrat1);写初始化类继承 CInitializeStruct(如 CInit1);把编译结果连进项目;输入参数加一个变量决定启动加载哪套策略。演示里第二套策略逻辑极简——前一根看涨就开固定止盈止损多单,且旧仓未平不开新仓。 切换本身不复杂:建新策略对象、删旧对象、把指针赋给变量,此后 OnTick() 自然走新逻辑。下面这段代码就是输入枚举与运行时切换器的骨架,外汇与贵金属波动剧烈、杠杆风险高,切换策略前务必在策略测试器跑历史校验。 代码里 enum CSTRAT 定义两个策略ID,input 变量 strt 决定初始加载;OnInit 调用 SwitchStrategy 失败则 INIT_FAILED。OnChartEvent 捕获面板点击事件后调 SwitchStrategy((CSTRAT)EDlg.GetStratID())。SwitchStrategy 函数先比对 str_curr,相同直接返回;否则按枚举 new 对应策略并 Initialize,删掉旧指针 pS1 再接管。这样热插拔就闭环了。
enum CSTRAT { strategy_1 = class="num">1, strategy_2 = class="num">2 }; class="kw">input CSTRAT strt = strategy_1; CSTRAT str_curr = -class="num">1; class="type">int OnInit() { ... if (!SwitchStrategy(strt) ) class="kw">return (INIT_FAILED); ... class="kw">return (INIT_SUCCEEDED); } 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_OBJECT_CLICK && StringCompare(...) == class="num">0 ) { SwitchStrategy((CSTRAT)EDlg.GetStratID() ); } } class="type">bool SwitchStrategy(CSTRAT sr) { if (str_curr == sr) class="kw">return true; CStrategy* s = NULL; class="kw">switch(sr) { case strategy_1: { CInit1 ci; if (!ci.Initialize(GetPointer(par), GetPointer(snap), GetPointer(trade)) ) class="kw">return false; s = new CRealStrat1(); s.Initialize(GetPointer(ci)); } break; case strategy_2: { CInit2 ci; if (!ci.Initialize(GetPointer(par), GetPointer(snap), GetPointer(trade)) ) class="kw">return false; s = new CRealStrat2(); s.Initialize(GetPointer(ci)); } break; } if (CheckPointer(pS1) != POINTER_INVALID) class="kw">delete pS1; pS1 = s; str_curr = sr; class="kw">return true; } CStrategy* pS1;
「范式落地的参考源」
智能交易系统的搭建已经用到了观察者(Observer)与外观(Facade)这类标准设计范式,结构上的解耦和接口收敛基本跑通。 想要把这套范式吃透,Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 合著的《设计范式:可复用面向对象软件的基础》是最直接的底层参考,里面不只讲这两个,还覆盖了更多可套进 MT5 EA 的范式。 有空建议翻一遍原书,对照自己 EA 的模块划分,看哪些类关系能用范式重构成更低耦合的形态。
◍ 把工具请下神坛
这套 EA 与模块压缩包(Ea&Modules.zip,7.74 KB)只是把 MVC 思路落进 MQL5 的载体,不是什么圣杯。有读者在 2019 年 11 月反馈,模块化写法让刚接触 MQL 的新手第一次看清 EA 该有的结构;另有人用 MQL_Easy 做跨平台通用代码,作者回了一句:丢进 MT4 编译试试,错误不会多,代码本身够简单。 真要验证,直接开 MT5 加载压缩包里的模块,看 OnTick 里调度逻辑是否如你所想;外汇和贵金属杠杆高、滑点狠,跑通和赚到是两回事,概率上更该把它当脚手架而不是印钞机。 工具归工具,写自己的 EA 才是正路,下载完就关掉网页去改参数吧。