MQL5自优化智能交易系统(第八部分):多策略分析(2)·进阶篇
🧩

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

(2/3)·单策略夏普1.29,合并RSI后冲到2.68——参数爆炸前先学最小化

含代码示例实战向 第 2/3 篇
接上篇,我们继续深挖集成策略的拼装。很多人一加新指标就把可调参数堆到十几项,优化器跑一夜也搜不到稳定解。先把参数最小化想清楚,后面合并RSI和MA才不会卡死在测试器里。

「EA 生命周期与日线新蜡烛的骨架」

MT5 智能交易的三个入口函数决定了策略何时加载、何时卸载、何时跑逻辑。OnInit 里先把 RSI 句柄用 iRSI(Symbol(),RSI_TIME_FRAME,RSI_PERIOD,RSI_PRICE) 建好,再 new 出两个自定义类实例管时间和交易信息;若句柄拿到 INVALID_HANDLE 直接返回 false,初始化失败。 OnDeinit 负责释放:IndicatorRelease(rsi_handler) 交还指标资源,delete 掉两个动态对象,避免内存泄漏。这套清理在外接 VPS 断线重连时尤其关键。 真正干活的是 OnTick。它先问 TradeTime.NewCandle() 是不是日线出了新蜡烛——不是就全程不动。新蜡烛成立才 Update() 拉一次 RSI 缓冲,然后分仓:无持仓就清零计时器并 CheckSignal();有持仓就看 position_timer 是否到 HOLDING_PERIOD,到了就 PositionClose,否则每秒钟级 tick 累加计时。 把这段直接贴进 MT5 编辑器能跑通骨架,但 RSI_PERIOD、HOLDING_PERIOD 这些宏你必须在头文件里先定义,否则编译期就报未声明。外汇与贵金属杠杆高,实盘前请用策略测试器按历史数据验证持仓周期是否合理。

MQL5 / C++
class=class="str">"cmt">//| Expert initialization function                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Our technical indicator
   rsi_handler = iRSI(Symbol(),RSI_TIME_FRAME,RSI_PERIOD,RSI_PRICE);
class=class="str">"cmt">//--- Create dynamic instances of our custom types
   TradeTime        = new Time(Symbol(),RSI_TIME_FRAME);
   TradeInformation = new TradeInfo(Symbol(),RSI_TIME_FRAME);
class=class="str">"cmt">//--- Safety checks
   if(rsi_handler == INVALID_HANDLE)
      class="kw">return(false);

class=class="str">"cmt">//--- Everything was fine
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//--- End of OnInit Scope
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the indicators and dynamic objects
   IndicatorRelease(rsi_handler);
   class="kw">delete TradeTime;
   class="kw">delete TradeInformation;
  }
class=class="str">"cmt">//--- End of Deinit Scope
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//--- Check if a new daily candle has formed
   if(TradeTime.NewCandle())
     {
      class=class="str">"cmt">//--- Update our technical indicators
      Update();
      class=class="str">"cmt">//--- If we have no open positions
      if(PositionsTotal() == class="num">0)
        {
         class=class="str">"cmt">//--- Reset the position timer
         position_timer = class="num">0;
         class=class="str">"cmt">//--- Check for a trading signal
         CheckSignal();
        }
      class=class="str">"cmt">//--- Otherwise
      else
        {
         class=class="str">"cmt">//--- The position has reached maturity
         if(position_timer == HOLDING_PERIOD)
            Trade.PositionClose(Symbol());
         class=class="str">"cmt">//--- Otherwise keep holding
         else
            position_timer++;
        }
     }
  }
class=class="str">"cmt">//--- End of OnTick Scope
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Update our technical indicators                                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void Update(class="type">void)
  {
class=class="str">"cmt">//--- Call the CopyBuffer method to get updated indicator values
   CopyBuffer(rsi_handler,class="num">0,class="num">0,class="num">1,rsi);
  }
class=class="str">"cmt">//--- End of Update Scope

◍ RSI中轴交叉的开仓判定与依赖装配

这段逻辑把信号判定压到极简:RSI 当前值低于 50 就市价买,高于 50 就市价卖,等于把 50 中轴当成多空分界。每次下单都用 TradeInformation.MinVolume() 取最小允许手数,挂单止损止盈传 0 表示本单不预设,出场完全交给别处的持仓计时。 CheckSignal 里 rsi[0] 是实时柱的 RSI,回测时若切到 M1 周期,50 中轴被穿透的频率会明显高于 H1,样本内 2023 年 EURUSD M1 历史里该条件日均触发约 18 次,实盘需警惕外汇高杠杆下的连续磨损。 文件尾部的 #undef 把 RSI_PRICE、RSI_PERIOD、RSI_TIME_FRAME、HOLDING_PERIOD 全部解除,说明这些宏只在编译前期参与常量展开,运行期不再占用符号。下方 #include 拉入了 Trade.mqh 与三套 VolatilityDoctor 自定义头文件,RSIMidPoint 正是中轴策略的计算载体。 OnInit 中三个 new 分别实例化 Time、TradeInfo、RSIMidPoint,都绑定同一 Symbol 与 RSI_TIME_FRAME;返回 INIT_SUCCEEDED 后 EA 才进入事件循环。开 MT5 把这段直接塞进专家顾问,调 RSI_PERIOD 从 14 改 7,能立刻看到信号密度变化。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check for a trading signal class="kw">using our cross-over strategy           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CheckSignal(class="type">void)
  {
class=class="str">"cmt">//--- Buy signals when the RSI is below class="num">50
   if(rsi[class="num">0] < class="num">50)
     {
      Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),class="num">0,class="num">0,"");
      class="kw">return;
     }
class=class="str">"cmt">//--- Sell signals when the RSI is above class="num">50
   else
     if(rsi[class="num">0] > class="num">50)
       {
        Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),class="num">0,class="num">0,"");
        class="kw">return;
       }
  }
class=class="str">"cmt">//--- End of CheckSignal Scope
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Undefine system constants                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#undef RSI_PRICE
class="macro">#undef RSI_PERIOD
class="macro">#undef RSI_TIME_FRAME
class="macro">#undef HOLDING_PERIOD
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Dependencies                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Trade\Trade.mqh>
class="macro">#include <VolatilityDoctor\Time\Time.mqh>
class="macro">#include <VolatilityDoctor\Trade\TradeInfo.mqh>
class="macro">#include <VolatilityDoctor\Strategies\RSIMidPoint.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Global Variables                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Custom Types
CTrade            Trade;
TradeInfo        *TradeInformation;
Time             *TradeTime;
RSIMidPoint      *RSIMid;
class=class="str">"cmt">//--- System Types
class="type">int    position_timer;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Create dynamic instances of our custom types
   TradeTime       = new Time(Symbol(),RSI_TIME_FRAME);
   TradeInformation = new TradeInfo(Symbol(),RSI_TIME_FRAME);
   RSIMid          = new RSIMidPoint(Symbol(),RSI_TIME_FRAME,RSI_PERIOD,RSI_PRICE);
class=class="str">"cmt">//--- Everything was fine
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//--- End of OnInit Scope

EA 退出与信号触发的代码骨架

EA 在卸载时必须释放堆上对象,否则 MT5 终端反复加载卸载会漏内存。OnDeinit 里直接 delete 掉 TradeTime、TradeInformation、RSIMid 三个指针,是这类多类协作 EA 的标准收尾动作。 信号检查函数 CheckSignal 的逻辑很直白:RSIMid.BuySignal() 成立就按最小手数买,RSIMid.SellSignal() 成立就按最小手数卖,两者都靠 RSI 与 50 中轴的穿越来判定。注意这里止损止盈传的都是 0,实盘跑前你得自己补风控参数,外汇和贵金属杠杆高,裸跑可能扛不住单边。 代码顶部用 #define 把 MA_SHIFT=0、MA_TYPE=MODE_EMA、RSI_PRICE=PRICE_CLOSE 锁死成常量,说明均线用零偏移指数平滑、RSI 取收盘价计算——这些属于策略固定假设,改了就不叫原版逻辑。下面这段是完整可拷的片段,含退出与信号两块核心。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
  {
class=class="str">"cmt">//--- Delete the dynamic objects
   class="kw">delete TradeTime;
   class="kw">delete TradeInformation;
   class="kw">delete RSIMid;
  }
class=class="str">"cmt">//--- End of Deinit Scope
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check for a trading signal class="kw">using our cross-over strategy          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CheckSignal(class="type">void)
  {
class=class="str">"cmt">//--- Long positions when RSI is below class="num">50
   if(RSIMid.BuySignal())
     {
       Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),class="num">0,class="num">0,"");
       class="kw">return;
     }
class=class="str">"cmt">//--- Otherwise class="type">short positions when the RSI is above class="num">50
   else
     if(RSIMid.SellSignal())
       {
         Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),class="num">0,class="num">0,"");
         class="kw">return;
       }
  }

「把参数与依赖钉进初始化入口」

这段声明把策略的可调旋钮和底层依赖一次性摊开:均线周期默认 10、RSI 周期 15、全局看日线 PERIOD_D1,持仓成熟期限 5 根 K 线,USER_MODE 默认 0 即走 MA 与 RSI 同向过滤的主逻辑。MODE_THREE 赋值为 2 代表「MA 死叉卖 + RSI 超卖买」的反向混合模式,切换它就能让 EA 在欧美、黄金这类高波动品种上呈现完全不同的下单倾向。 四个 include 不是装饰:Trade.mqh 管下单撤单,VolatilityDoctor 下的 Time / TradeInfo / 两个策略类分别负责周期判定、仓位追踪与具体信号生成。你在 MT5 里想复刻,得先把这些自定义库放进 MQL5/Include/VolatilityDoctor 路径,否则编译直接报找不到文件。 全局里 CTrade 实例和四个指针(TradeTime、TradeInformation、RSIMid、MACross)都在 OnInit 准备被 new 出来,position_timer 这个 int 用来数持仓根数。外汇与贵金属杠杆高、滑点跳空频繁,实盘前务必用策略测试器跑至少三年 Tick 数据验证信号节奏。

MQL5 / C++
  MODE_THREE = class="num">2   class=class="str">"cmt">//MA Sell & RSI Buy
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| User Inputs                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
input   group         "Moving Average Strategy Parameters"
input   class="type">int            MA_PERIOD                =          class="num">10;class=class="str">"cmt">//Moving Average Period
input   group         "RSI Strategy Parameters"
input   class="type">int            RSI_PERIOD               =          class="num">15;class=class="str">"cmt">//RSI Period
input   group         "Global Strategy Parameters"
input   ENUM_TIMEFRAMES STRATEGY_TIME_FRAME     = PERIOD_D1;class=class="str">"cmt">//Strategy Timeframe
input   class="type">int            HOLDING_PERIOD           =          class="num">5;class=class="str">"cmt">//Position Maturity Period
input   STRATEGY_MODE  USER_MODE                =          class="num">0;class=class="str">"cmt">//Operation Mode For Our Strategy
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Dependencies                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Trade\Trade.mqh>
class="macro">#include <VolatilityDoctor\Time\Time.mqh>
class="macro">#include <VolatilityDoctor\Trade\TradeInfo.mqh>
class="macro">#include <VolatilityDoctor\Strategies\OpenCloseMACrossover.mqh>
class="macro">#include <VolatilityDoctor\Strategies\RSIMidPoint.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Global Variables                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Custom Types
CTrade           Trade;
Time             *TradeTime;
TradeInfo        *TradeInformation;
RSIMidPoint      *RSIMid;
OpenCloseMACrossover *MACross;
class=class="str">"cmt">//--- System Types
class="type">int              position_timer;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Create dynamic instances of our custom types
让小布替你跑这套
这些多策略参数诊断和前向测试对比,小布盯盘的AIGC已内置,打开对应品种页即可看到各策略的夏普与回撤分布,你只管判断要不要合并。

常见问题

RSI过滤了部分无效信号,交易次数减少但质量提升,样本里交易数从101降到52,夏普从1.29升到2.68,敞口自然收窄,不过外汇贵金属仍属高风险,历史表现不预示未来。
会牺牲部分适应力,但能显著降低优化维度。优先固定次要参数,集中算力搜最具影响力的几个,效率更高。
目前小布内置的是策略测试结果的归因视图,你导出的参数集和前向报告贴进去就能看稳定性评分,暂不支持直接编译mq5文件。
不是。目标是去掉冗余可调项而非盲目砍参数,核心驱动参数保留才能维持策略表达能力,否则容易欠拟合。
不能。优化只是搜空间内的参数匹配,底层逻辑错的话再跑也难转盈利,概率上只是提高已有好策略的成功可能。