在 MQL5 中构建自优化智能交易系统(第八部分):多策略分析(基础篇)
「为什么把价格预测换成指标预测」
做算法交易的人大多踩过同一个坑:直接拿模型去猜下一根 K 线的收盘价,胜率常年趴窝。本系列前面的实测表明,同一个统计工具,只要把任务从「预测价格」改成「预测技术指标的后续值」,模型表现会稳定占优。 原因不复杂——价格是混沌系统的表层输出,噪声占比极高;而均线、RSI 这类指标本身就是对价格的平滑与压缩,序列自相关性更强,统计模型更容易抓到结构。换问题框架不等于换引擎,工具没变,只是靶子更好打。 这一篇要验证一个具体设想:把三种独立策略(均线交叉延续、RSI 动量、威廉 %R 趋势突破)塞进一个应用,让它定期只挑当前最赚钱的那一种来跑,而不是三策略并行或固定加权。外汇与贵金属市场高杠杆、滑点随机,这类切换逻辑能否真跑出比「全跟」更好的曲线,需要用 MT5 的前向测试来证伪。 基准必须先立。我们拿三策略原始组合当标杆绩效,统计模型只有稳定超越它才有存在价值。MT5 自带的快速 / 慢速遗传优化器配合前向测试,能滚出新的参数集反复验,比单纯回测更能暴露过拟合。
◍ 用类封装把均线交叉策略拆干净
把多个策略硬塞进一个 EA 的弊端很明显:逻辑耦合后,任何一处改动都可能引发意料之外的副作用。更稳的做法是模仿人工搭配——一个类管开仓、另一个类管平仓,各自只解决一小块问题。在 MT5 里落地,就是给所有策略抽一个 Parent 基类,通用功能(参数更新、买卖信号检查)放基类,子类用 virtual 重写信号判定。 第一个落地类是 OpenCloseMACrossover:两条同周期 MA,分别挂开盘价和收盘价数据源。规则很直白——收盘 MA 在开盘 MA 之上,倾向多头;反之倾向空头。因为平均收盘价高于平均开盘价时,价格行为偏看涨,趋势延续概率更高。外汇与贵金属波动大,这种基础信号只作概率参考,不构成方向保证。 验证不能省。我们把类模块和硬编码版本在相同 D1 区间回测对比:类版本完成 127 笔交易,多空比与硬编码版一致,夏普比率高度接近,资金曲线几乎重合。这说明封装没引入偏差,后续可放心寻优。 寻优阶段把系统常量改成输入参数,用 MT5 策略测试器的「快速遗传算法」跑 Forward=1/2(前半训练、后半验证)。优化器只看得见训练段,散点图每个点是一次迭代;三维条形图显示更高时间周期夏普倾向更好。前向结果与回测相似,是稳定性尚可的信号,若两者最佳参数脱节则策略可能过拟合。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Strategy.mqh | class=class="str">"cmt">//| Gamuchirai Ndawana | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Gamuchirai Ndawana" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class Strategy { class="kw">private: class="type">int buffer_size; class="type">bool buy;
策略基类的信号接口与初始化缓冲
在 MT5 里搭一套可扩展的交易策略框架,第一步往往是写一个父类把信号接口先占住。下面这段把 buy / sell 两个布尔标志、默认指标缓冲大小,以及买卖信号虚函数都封进一个 Strategy 类,子类之后只需重写 BuySignal 与 SellSignal。 构造函数里把 buy、sell 初始化为 false,buffer_size 给到 10——意思是默认预留 10 根 K 线的指标缓冲。GetIndicatorBufferSize 直接返回这个整型值,方便后续数组申请时对齐长度。 BuySignal 的父类实现是个安全闸:若子类忘了重写,它只会 Print 一条警告并 return false,避免裸跑出无意义的真信号。外汇与贵金属波动剧烈、杠杆高风险,这种‘默认不交易’的fail-safe设计能少踩很多坑。 把这段代码贴进 MT5 的 Include 目录编译,自己建个子类重写那两个虚函数,就能在策略测试器里验证信号触发逻辑是否如预期。
class="type">bool sell; class="kw">public: class=class="str">"cmt">//--- Class constructors and destructors Strategy(class="type">void); ~Strategy(class="type">void); class=class="str">"cmt">//--- Check if we have any valid trading signals from our strategy class="kw">virtual class="type">bool BuySignal(class="type">void); class="kw">virtual class="type">bool SellSignal(class="type">void); class=class="str">"cmt">//--- Update the technical indicators in our strategy class="kw">virtual class="type">bool Update(class="type">void); class=class="str">"cmt">//--- Get the size of the technical indicator buffers class="type">int GetIndicatorBufferSize(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| The only way to create an object of the class | class=class="str">"cmt">//+------------------------------------------------------------------+ Strategy::Strategy(class="type">void) { class=class="str">"cmt">//--- Upon initialization, both flags should be false buy = false; sell = false; buffer_size = class="num">10; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| The size of our indicator buffer | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int Strategy::GetIndicatorBufferSize(class="type">void) { class="type">int res = buffer_size; class="kw">return(res); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check if our strategy is giving us any buy signals | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool Strategy::BuySignal(class="type">void) { class=class="str">"cmt">//--- The user is intended to overwrite the function in the child class class=class="str">"cmt">//--- Otherwise, failing to do so will always class="kw">return false as a safety feature Print("[WARNING] This function has only been implemented in the parent: ",__FUNCSIG__,"\nKindly make the necessary corrections to the child class"); class="kw">return(false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check if our strategy is giving us any sell signals | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool Strategy::SellSignal(class="type">void) {
「父类留的空方法和子类怎么接」
策略基类里把 Update() 写成虚函数并留了默认实现:如果用户没在子类重写,它就直接 Print 一条警告,提示“此函数仅在父类实现,请修正子类”,然后 return(false)。这是一道安全闸,避免你忘了重写却误以为策略在跑。 看下面这段父类默认代码,逻辑很直白:先打警告带 __FUNCSIG__ 函数签名,再返回 false 阻断后续。析构函数当前是空的,暂时不用管资源释放。
class=class="str">"cmt">//--- The user is intended to overwrite the function in the child class class=class="str">"cmt">//--- Otherwise, failing to do so will always class="kw">return false as a safety feature Print("[WARNING] This function has only been implemented in the parent: ",__FUNCSIG__,"\nKindly make the necessary corrections to the child class"); class="kw">return(false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Update our strategy parameters | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool Strategy::Update(class="type">void) { class=class="str">"cmt">//--- The user is intended to overwrite the function in the child class Print("[WARNING] This function has only been implemented in the parent: ",__FUNCSIG__,"\nKindly make the necessary corrections to the child class"); class="kw">return(false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| The class destructor is currently empty | class=class="str">"cmt">//+------------------------------------------------------------------+ Strategy::~Strategy(class="type">void) { }
class=class="str">"cmt">//--- The user is intended to overwrite the function in the child class class=class="str">"cmt">//--- Otherwise, failing to do so will always class="kw">return false as a safety feature Print("[WARNING] This function has only been implemented in the parent: ",__FUNCSIG__,"\nKindly make the necessary corrections to the child class"); class="kw">return(false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Update our strategy parameters | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool Strategy::Update(class="type">void) { class=class="str">"cmt">//--- The user is intended to overwrite the function in the child class Print("[WARNING] This function has only been implemented in the parent: ",__FUNCSIG__,"\nKindly make the necessary corrections to the child class"); class="kw">return(false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| The class destructor is currently empty | class=class="str">"cmt">//+------------------------------------------------------------------+ Strategy::~Strategy(class="type">void) { }