MQL5自优化智能交易系统(第八部分):多策略分析(2)(基础篇)
📘

MQL5自优化智能交易系统(第八部分):多策略分析(2)(基础篇)

第 1/3 篇

◍ 合并策略前先按住参数膨胀

把不同策略塞进同一个EA,最容易踩的坑不是信号冲突,而是参数爆炸。组件每多一个,可变维度就往上叠,等优化器跑起来往往卡死在组合爆炸里。经验做法是把次要参数钉死成常数,只留真正影响盈亏的几个去交给MT5遗传优化器跑。 拿已落地的两组前向测试结果对照:原MA交叉策略101笔交易、利润133.51美元、夏普1.29;新做的RSI策略仅52笔交易、利润214.08美元、夏普2.68。交易次数砍掉近半,利润和夏普反而更高,说明低频次、低敞口的设计在贵金属与外汇品种上更抗回撤。 但别神化策略测试器。MT5的优化器不会把根子坏掉的策略救活,该亏还是亏;好策略的前提下,优化也只是提高成功概率,不是保证更好。开MT5把RSI周期和超买超卖阈值先固定,只放进场阈值去跑一遍遗传优化,你能直接验证这组2.68夏普是不是可复现。外汇与贵金属杠杆高,验证时务必用极小仓位或纯模拟。

用类封装RSI中点信号并跑通基准

先把RSI中点策略写成一个独立的类。它继承自所有策略共用的基类,因此父类里声明的虚方法(update、CheckSignal等)在子类里必须再次标成virtual,否则多态调用会断链。类成员很少,核心只持有一个自定义RSI对象的指针。 构造时由调用方传入品种、周期、RSI长度、应用价格这四个参数,随即new一个自定义RSI实例;析构时delete该指针。注意这个自定义RSI类型和MT5内置RSI功能一致,但挂了若干实用方法,方便后续复用。 update方法只做一件事:从RSI处理器复制缓冲区,且只取当前读数那一根,不拷历史。CheckSignal逻辑极简——读数低于50出买信号,高于50出卖信号。 为证明类没写错,先用硬编码EA跑2022.01.01–2025.05.01的日线基准(随机延迟开启),记下资金曲线与详细统计。再换用类版本、参数全同重跑,结果两图完全重合、统计数字一致,说明类实现等价于硬编码。 接着把MA交叉与RSI合并。用枚举定义三种模式:0=投票(两策略同向才开仓)、1=RSI管买MA管卖、2=MA管买RSI管卖。输入参数压到5个,避免参数空间爆炸。OnTick逻辑沿用,update分别调两个子策略,CheckSignal先判模式再下单。 优化时设Forward=1/2,遗传算法用一半数据训练、另一半做样本外验证。步长别写1,大区间用2/4/5/10更现实。散点图里纵轴为利润,样本外资金曲线仍为正,投票模式最稳。合并后系统盈利能力超过单策略版本,但外汇/贵金属杠杆高,实盘前务必自测滑点。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                  RSIMidPoint.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=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Dependencies                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <VolatilityDoctor\Indicators\RSI.mqh>

「RSI中轴策略的类骨架与信号逻辑」

把 RSI 中轴策略封装成一个继承自 Strategy 的子类,核心只持有一个 RSI 指针成员 my_rsi,构造时传入品种、周期、RSI 周期数与应用价格,析构交还给父类管理。 Update 方法先调用 my_rsi.SetIndicatorValues 按 Strategy::GetIndicatorBufferSize() 的长度刷新缓冲,再判断 GetCurrentReading 不为 0 才返回 true;读数为 0 视为数据异常,直接返回 false 阻断后续信号。 买卖信号极简:BuySignal 在 RSI 当前值小于 50 时返回 true,SellSignal 在大于 50 时返回 true。也就是说,价格行为上倾向把 50 中轴当作多空分水岭,但外汇与贵金属波动剧烈,这类裸信号在单边行情中可能出现连续假突破,实盘前务必在 MT5 用历史数据验证。 下面这段是类声明与三个方法的原文,逐行看结构比读文档快: #include 引入父策略头文件,限定命名空间 VolatilityDoctor\Strategies\Parent。 class RSIMidPoint : public Strategy 定义子类,private 区只放 RSI *my_rsi 指针。 public 区列构造、析构与三个虚函数 override 声明,Update/BuySignal/SellSignal 均由具体策略实现。 Update 内 my_rsi.SetIndicatorValues(Strategy::GetIndicatorBufferSize(),true) 刷新指标数组;if(my_rsi.GetCurrentReading() != 0) return(true) 确认有有效读数。 BuySignal 内 return(my_rsi.GetCurrentReading()<50) 即 RSI 跌破中轴判多。 SellSignal 内 return(my_rsi.GetCurrentReading()>50) 即 RSI 站上中轴判空。

MQL5 / C++
class="macro">#include <VolatilityDoctor\Strategies\Parent\Strategy.mqh>
class RSIMidPoint : class="kw">public Strategy
  {
class="kw">private:
                      class=class="str">"cmt">//--- The instance of the RSI used in this strategy
                      RSI *my_rsi;
class="kw">public:
                      class=class="str">"cmt">//--- Class constructor 
                      RSIMidPoint(class="type">class="kw">string user_symbol,ENUM_TIMEFRAMES user_timeframe,class="type">int user_period,ENUM_APPLIED_PRICE user_price);
                      
                      class=class="str">"cmt">//--- Class destructor
                      ~RSIMidPoint();
                      
                      class=class="str">"cmt">//--- Class overrides
                      class="kw">virtual class="type">bool Update(class="type">void);
                      class="kw">virtual class="type">bool BuySignal(class="type">void);
                      class="kw">virtual class="type">bool SellSignal(class="type">void);
  };

class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Our strategy update method                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool RSIMidPoint::Update(class="type">void)
  {
   class=class="str">"cmt">//--- Set the indicator value
   my_rsi.SetIndicatorValues(Strategy::GetIndicatorBufferSize(),true);
   
   class=class="str">"cmt">//--- Check readings are valid
   if(my_rsi.GetCurrentReading() != class="num">0) class="kw">return(true);
   
   class=class="str">"cmt">//--- Something went wrong
   class="kw">return(false);
  }  
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check for our buy signal                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool RSIMidPoint::BuySignal(class="type">void)
  {
   class=class="str">"cmt">//--- Buy signals when the RSI is below class="num">50
   class="kw">return(my_rsi.GetCurrentReading()<class="num">50);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check for our sell signal                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool RSIMidPoint::SellSignal(class="type">void)
  {
   class=class="str">"cmt">//--- Sell signals when the RSI is above class="num">50
   class="kw">return(my_rsi.GetCurrentReading()>class="num">50);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ RSI中线策略的构造与全局常量落地

把 RSI 中线策略封装成类,核心在构造与析构里把指标句柄的生命周期管起来。构造函数接收交易品种、周期、RSI 参数与应用价格,内部 new 一个 RSI 对象并打印加载信息;析构函数负责 delete,避免 MT5 EA 退出时内存泄漏。 RSIMidPoint::RSIMidPoint(string user_symbol,ENUM_TIMEFRAMES user_timeframe,int user_period,ENUM_APPLIED_PRICE user_price) { my_rsi = new RSI(user_symbol,user_timeframe,user_period,user_price); Print("RSI-Mid-Point Strategy Loaded."); } RSIMidPoint::~RSIMidPoint() { delete my_rsi; } 全局层用宏写死了回测基线:RSI 取收盘价、周期 15、日线 PERIOD_D1、持仓 5 根 K 线。这组参数直接决定策略在日线级别对中线偏离的敏感度,改 RSI_PERIOD 或 HOLDING_PERIOD 就能在 MT5 里复跑对照。 依赖引入了 Trade.mqh 与自研的 TradeInfo、Time 类,全局挂了 CTrade 实例和 rsi[]、ma_close[] 数组,rsi_handler 存指标句柄,position_timer 管持仓计时。外汇与贵金属杠杆高,这类日线持仓策略遇跳空可能触发非预期滑点,实盘前先用策略测试器跑历史段验证。

MQL5 / C++
RSIMidPoint::RSIMidPoint(class="type">class="kw">string user_symbol,ENUM_TIMEFRAMES user_timeframe,class="type">int user_period,ENUM_APPLIED_PRICE user_price)
  {
   my_rsi = new RSI(user_symbol,user_timeframe,user_period,user_price);
   Print("RSI-Mid-Point Strategy Loaded.");
  }
RSIMidPoint::~RSIMidPoint()
  {
   class="kw">delete my_rsi;
  }
class="macro">#define RSI_PRICE       PRICE_CLOSE
class="macro">#define RSI_PERIOD      class="num">15
class="macro">#define RSI_TIME_FRAME  PERIOD_D1
class="macro">#define HOLDING_PERIOD  class="num">5
class="macro">#include <Trade\Trade.mqh>
class="macro">#include <VolatilityDoctor\Trade\TradeInfo.mqh>
class="macro">#include <VolatilityDoctor\Time\Time.mqh>
CTrade    Trade;
TradeInfo *TradeInformation;
Time      *TradeTime;
class="type">class="kw">double rsi[],ma_close[];
class="type">int    rsi_handler;
class="type">int    position_timer;

常见问题

先冻结非核心参数,只放开真正影响多策略协同的少数变量,用类封装隔离各策略状态,避免无意义的参数膨胀。
先只加载单一品种、固定周期跑基准回测,确认类中轴信号与开平仓分支无报错,再考虑并入其他策略。
小布可扫描你的策略类结构,标出冗余参数并给出精简建议,让你在合并前先按住参数膨胀。
把中轴值、周期等写进全局常量,类内直接引用,改一处即可全策略同步,降低手写硬代码带来的误改风险。
多卡在构造函数没初始化缓冲区和信号标志,导致首根K线就越界;先跑通空类再填逻辑最稳。