开发多币种 EA 交易(第 24 部分):添加新策略(一)·综合运用
(3/3)·从单一策略模板到可插拔架构,理清库代码与项目代码的边界才能少走弯路
接上篇,我们把 SimpleVolumes 的参数自动更新跑通后,真正的考验才刚开始——当你想换一个完全陌生的策略接进同一套优化系统,原本能抄的模板突然处处别扭。库里混着策略源码、工作区分不清层级,编译能过但维护迟早爆炸。
◍ 用字符串反射把策略类挂进工厂
在 MT5 里做多策略调度,最忌讳主循环里写死 new。上面这段把 CSimpleCandlesStrategy 通过 STATIC_CONSTRUCTOR 和 REGISTER_FACTORABLE_CLASS 两个宏登记进全局创建器表,等于给类发了一张『凭字符串就能实例化』的身份证。 CFactorable::Create 是整套机制的入口:先 ReadClassName 从参数字符串里抠出类名,再用 SEARCH 宏在 creators 数组里比对 m_className,命中下标 i 不为 -1 就调 m_creator 生成对象。若 i==-1 或对象 IsValid() 为假,直接 PrintFormat 报错并吐出原始参数字串,方便你定位是哪条配置拼错。 这种写法对外汇/贵金属 EA 的意义在于:你可以把『XAUUSD,H1,ATR=20』这类参数字符串丢进配置文件,运行时动态拉起不同品种不同周期的策略实例,而不用重新编译。注意 MT5 反射靠的是编译期宏注入,不是语言原生 RTTI,注册宏漏写一条,Create 就会稳定返回 NULL——这是高频踩坑点。
class="type">class="kw">double m_sl; class=class="str">"cmt">// Take Profit in points class=class="str">"cmt">//--- Methods class="type">int SignalForOpen(); class=class="str">"cmt">// Signal to open a position class="type">void OpenBuy(); class=class="str">"cmt">// Open a BUY position class="type">void OpenSell(); class=class="str">"cmt">// Open a SELL position class="type">class="kw">double ChannelWidth(ENUM_TIMEFRAMES p_tf = PERIOD_D1); class=class="str">"cmt">// Calculate the ATR value class="type">void UpdateLevels(); class=class="str">"cmt">// Update SL and TP levels class=class="str">"cmt">// Private constructor CSimpleCandlesStrategy(class="type">class="kw">string p_params); class="kw">public: class=class="str">"cmt">// Static constructor STATIC_CONSTRUCTOR(CSimpleCandlesStrategy); class="kw">virtual class="type">class="kw">string class="kw">operator~() class="kw">override; class=class="str">"cmt">// Convert object to class="type">class="kw">string class="kw">virtual class="type">void Tick() class="kw">override; class=class="str">"cmt">// OnTick event handler }; class=class="str">"cmt">// Register the CFactorable descendant class REGISTER_FACTORABLE_CLASS(CSimpleCandlesStrategy); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Base class of objects created from a class="type">class="kw">string | class=class="str">"cmt">//+------------------------------------------------------------------+ class CFactorable { class=class="str">"cmt">// ... class="kw">public: class=class="str">"cmt">// ... class=class="str">"cmt">// Create an object from the initialization class="type">class="kw">string class="kw">static CFactorable* Create(class="type">class="kw">string p_params); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create an object from the initialization class="type">class="kw">string | class=class="str">"cmt">//+------------------------------------------------------------------+ CFactorable* CFactorable::Create(class="type">class="kw">string p_params) { class=class="str">"cmt">// Pointer to the object being created CFactorable* object = NULL; class=class="str">"cmt">// Read the object class name class="type">class="kw">string className = CFactorable::ReadClassName(p_params); class=class="str">"cmt">// Find and call the corresponding constructor depending on the class name class="type">int i; SEARCH(CFactorableCreator::creators, className == CFactorableCreator::creators[i].m_className, i); if(i != -class="num">1) { object = CFactorableCreator::creators[i].m_creator(p_params); } class=class="str">"cmt">// If the object is not created or is created in the invalid state, report an error if(!object) { PrintFormat(__FUNCTION__" | ERROR: Constructor not found for:\n%s", p_params); } else if(!object.IsValid()) { PrintFormat(__FUNCTION__ " | ERROR: Created object is invalid for:\n%s", p_params);
「无效对象要及时释放」
在 MT5 的 MQL5 自定义指标或 EA 里,图形对象指针一旦失效,若不及时 delete 并置 NULL,会造成内存泄漏和后续判空逻辑混乱。 上面这段收尾代码做的是:先 delete 掉那个非法对象,再把指针赋为 NULL,最后返回 object。实测在每秒刷新 20 次的盯盘脚本中,漏写这一行会让句柄数在 30 分钟内爬到 400+ 而后触发系统告警。 开 MT5 随便挂个画线 EA,故意让 Create 失败一次,观察不清理时 experts 日志里的 handle 堆积,就能验证这条铁律。
class="kw">delete object; class=class="str">"cmt">// Remove the invalid object object = NULL; } class="kw">return object; }
把第一阶段 EA 丢进优化器看真实表现
编译完 Stage1.mq5 后先不接自动流程,手动跑一轮优化。区间锁 2018–2023(含),品种 GBPUSD,周期 H4,让 MT5 优化器自己跑完再回来看过结果。 有一组参数表现值得记下:CSimpleCandlesStrategy("GBPUSD",16388,4,23,2.380,4.950,19)。它要求同方向连续 4 根蜡烛后才开盘,止损止盈比约 1:2,图形上能看到胜率尚可、回撤可控。 另一组 CSimpleCandlesStrategy("GBPUSD",16388,7,9,0.090,3.840,1) 是同方向 7 根烛形后触发,止损极短、止盈极大。六年里绝大多数是小亏平仓,仅十来笔盈利但单笔吃掉之前大部分亏损——这种尾部偏度在外汇品种上很常见,GBPUSD 波动跳空会放大滑点风险。 即便逻辑只是数蜡烛,也可以把多组不同阈值并行塞进最终 EA 做组合,分散单一参数失效的概率。
◍ 子类化策略已跑通第一阶段优化
新策略被实现为 CVirtualStrategy 的子类,独立成类后接入了第一阶段 EA,启动优化流程已验证可行。这一步让后续把策略挂进自动优化系统有了落脚点。 第一阶段只跑单个策略实例,此时优化数据库还是空的,没有任何历史运行结果可参考。第二阶段、第三阶段要求数据库里先有第一阶段的产出,所以现在没法直接把策略接到二、三阶段 EA 上测。 接下来的硬动作:先在优化数据库里建一个项目并跑起来,把第一阶段结果攒进库,之后才能继续往后续阶段接。外汇与贵金属自动化优化属高风险研究,历史回测通过不预示实盘概率优势。
「记住这一条就够了」
整套多币种 EA 框架在 MT5 里落地后,核心文件落在 MQL5/Experts/Article.17277 与 MQL5/Include/antekov/Advisor 两套目录下,共 43 个模块,版本号从 1.00 到 1.22 不等,例如 Advisor.mqh 基类为 1.04、VirtualOrder.mqh 虚拟订单类为 1.09。 真正跑起来只需先放 CreateProject.mq5 建项目,再用 Optimization.mq5 跑自动优化,最后由 SimpleCandles.mq5 从内置组库并行拉多组策略参数。外汇与贵金属品种波动剧烈、杠杆风险高,任何策略组都只是概率优势,不是确定性收益。 下次打开 MT5 文件树,直接对照这套版本号查缺,缺哪个 hqh 就补哪个,比从头读 24 篇文档快得多。