基于MQL5和Python的自优化EA(第五部分):深度马尔可夫模型·综合运用
📘

基于MQL5和Python的自优化EA(第五部分):深度马尔可夫模型·综合运用

第 3/3 篇

「RSI自动策略的EA骨架怎么搭」

把RSI结合均线写进MT5专家顾问,第一步是把交易库和指标句柄先备齐。上面代码里 #include <Trade\Trade.mqh> 挂了CTrade对象,全局区定义了 rsi_handler、ma_handler 两个指标句柄以及 bid/ask 等市场变量,这是后续所有动作的底盘。 输入参数区用 input group 把技术参数、资金管理和交易规则分了三组:RSI周期与均线周期默认都是20,成交手数写死0.3,平仓方式用枚举 close_conditions 让用户选 RSI_Close 或 MA_Close。注意手数0.3对小微账户已是重仓,外汇和贵金属杠杆高,实盘前务必按自己净值重算。 OnInit 里只做两件事——用 iRSI 在M1周期加载RSI(收盘价计算),用 iMA 加载周期20、零偏移、EMA模式的均线。句柄若返回无效值,后面所有 CopyBuffer 都会读不到数据,所以初始化阶段就该加一句校验,别等下单时才发现指标是空壳。 平仓逻辑分两条路:选了MA_Close,就等价格穿越均线再平;选了RSI_Close,则按RSI信号平。开仓前还要先确认无持仓且价格进入盈利区间,若用户指定均线平仓,则必须等价格回到均线正确一侧才允许开新仓,这套顺序能避开追在反转尖尖上。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 Auto RSI.mq5 |
class=class="str">"cmt">//|                                                     Gamuchirai Zororo Ndawana |
class=class="str">"cmt">//|                                        [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Gamuchirai Zororo 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">//| Libraries                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <Trade\Trade.mqh>
CTrade Trade;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Global variables                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int rsi_handler;
class="type">class="kw">double rsi_buffer[];
class="type">int ma_handler;
class="type">int system_state;
class="type">class="kw">double ma_buffer[];
class="type">class="kw">double bid,ask;
class=class="str">"cmt">//--- Custom enumeration
enum close_conditions
{
MA_Close  = class="num">0,
RSI_Close
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Inputs                                                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
input group "Technical Indicators"
input class="type">int rsi_period = class="num">20; class=class="str">"cmt">//RSI Period
input class="type">int ma_period = class="num">20; class=class="str">"cmt">//MA Period
input group "Money Management"
input class="type">class="kw">double trading_volume = class="num">0.3; class=class="str">"cmt">//Lot size
input group "Trading Rules"
input close_conditions user_close = RSI_Close; class=class="str">"cmt">//How should we close the positions?
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- Load the indicator
   rsi_handler = iRSI(_Symbol,PERIOD_M1,rsi_period,PRICE_CLOSE);
   ma_handler = iMA(_Symbol,PERIOD_M1,ma_period,class="num">0,MODE_EMA,PRICE_CLOSE);

◍ 指标句柄失效与持仓管理的代码骨架

EA 初始化阶段必须校验 RSI 与 MA 的句柄是否有效,任一为 INVALID_HANDLE 就直接在图表上提示并 return(INIT_FAILED),否则返回 INIT_SUCCEEDED。这个判断能避免后续 OnTick 里读到空缓冲区导致异常平仓。 OnDeinit 里用 IndicatorRelease 分别释放 rsi_handler 和 ma_handler,MT5 上不手动释放指标句柄可能让终端在反复加载 EA 时堆积内存占用。 OnTick 的逻辑很直白:先 update() 刷新行情与指标数据,无持仓就跑 check_setup() 找信号,有持仓就进 manage_setup() 管退出。 manage_setup 里给了两种平仓触发:选 RSI_Close 时,system_state==1 且当前 RSI 上穿 71、80 根 K 前 RSI 不高于 80,平多;system_state==-1 且当前 RSI 高于 11、80 根前不高于 20,平空。选 MA_Close 时则看收盘价与 ma_buffer[0] 的位置关系配合系统方向。外汇与贵金属杠杆高,这类硬编码阈值在实盘前需用历史数据回测验证胜率倾向。

MQL5 / C++
class=class="str">"cmt">//--- Validate our technical indicators
  if(rsi_handler == INVALID_HANDLE || ma_handler == INVALID_HANDLE)
    {
      class=class="str">"cmt">//--- We failed to load the rsi
      Comment("Failed to load the RSI Indicator");
      class="kw">return(INIT_FAILED);
    }
class=class="str">"cmt">//---
  class="kw">return(INIT_SUCCEEDED);
  }
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">//--- Release our technical indicators
  IndicatorRelease(rsi_handler);
  IndicatorRelease(ma_handler);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
  {
class=class="str">"cmt">//--- Update market and technical data
  update();
class=class="str">"cmt">//--- Check if we have any open positions
  if(PositionsTotal() == class="num">0)
    {
      check_setup();
    }
  if(PositionsTotal() > class="num">0)
    {
      manage_setup();
    }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Manage our open setups                                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void manage_setup(class="type">void)
  {
  if(user_close == RSI_Close)
    {
      if((system_state == class="num">1) && ((rsi_buffer[class="num">0] > class="num">71) && (rsi_buffer[class="num">80] <= class="num">80)))
        {
          PositionSelect(Symbol());
          Trade.PositionClose(PositionGetTicket(class="num">0));
          class="kw">return;
        }
      if((system_state == -class="num">1) && ((rsi_buffer[class="num">0] > class="num">11) && (rsi_buffer[class="num">80] <= class="num">20)))
        {
          PositionSelect(Symbol());
          Trade.PositionClose(PositionGetTicket(class="num">0));
          class="kw">return;
        }
    }
  else
    if(user_close == MA_Close)
      {
        if((iClose(_Symbol,PERIOD_CURRENT,class="num">0) > ma_buffer[class="num">0]) && (system_state == -class="num">1))
          {
            PositionSelect(Symbol());

RSI 配合均线过滤的开平仓触发

这段逻辑把平仓和建仓拆成两个独立函数,平仓只看价格与均线的位置关系和持仓方向状态 system_state,建仓则叠加 RSI 阈值。外汇与贵金属杠杆高,这类条件单在跳空时可能滑点放大,实盘前务必在 MT5 策略测试器跑一遍。 当收盘价站上 ma_buffer[0] 且 system_state 为 -1 时,代码会选中当前品种并平掉 PositionGetTicket(0) 对应的多单;反过来收盘价低于均线且 state 为 1 则平空。注意它永远只处理持仓池里序号 0 的那一张单,多单同开需自行改逻辑。 check_setup 里给了两种模式:user_close 为 RSI_Close 时,RSI 落在 71–80 做空、11–20 做多,状态分别置为 -1 / 1;若为 MA_Close,则要求 RSI 超买且收盘价低于均线才卖,超卖且收盘价高于均线才买。71、80、11、20 这几个边界是直接写死的,调参时改这里就行。 让小布替你跑这套:把下方代码粘进 EA 的对应函数,把 trading_volume 设成 0.01,先用 XAUUSD 的 M15 回测 2023 年全年,观察 RSI 在 11–20 区间配合均线过滤后的成交频次是否过低。

MQL5 / C++
Trade.PositionClose(PositionGetTicket(class="num">0));
class="kw">return;
      }
      if((iClose(_Symbol,PERIOD_CURRENT,class="num">0) < ma_buffer[class="num">0]) && (system_state == class="num">1))
      {
         PositionSelect(Symbol());
         Trade.PositionClose(PositionGetTicket(class="num">0));
         class="kw">return;
      }
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Find if we have any setups to trade                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void check_setup(class="type">void)
  {
   if(user_close == RSI_Close)
     {
      if((rsi_buffer[class="num">0] > class="num">71) && (rsi_buffer[class="num">0] <= class="num">80))
        {
         Trade.Sell(trading_volume,_Symbol,bid,class="num">0,class="num">0,"Auto RSI");
         system_state = -class="num">1;
        }
      if((rsi_buffer[class="num">0] > class="num">11) && (rsi_buffer[class="num">0] <= class="num">20))
        {
         Trade.Buy(trading_volume,_Symbol,ask,class="num">0,class="num">0,"Auto RSI");
         system_state = class="num">1;
        }
     }
   if(user_close == MA_Close)
     {
      if(((rsi_buffer[class="num">0] > class="num">71) && (rsi_buffer[class="num">0] <= class="num">80)) && (iClose(_Symbol,PERIOD_CURRENT,class="num">0) < ma_buffer[class="num">0]))
        {
         Trade.Sell(trading_volume,_Symbol,bid,class="num">0,class="num">0,"Auto RSI");
         system_state = -class="num">1;
        }
      if(((rsi_buffer[class="num">0] > class="num">11) && (rsi_buffer[class="num">0] <= class="num">20)) && (iClose(_Symbol,PERIOD_CURRENT,class="num">0) > ma_buffer[class="num">0]))
        {
         Trade.Buy(trading_volume,_Symbol,ask,class="num">0,class="num">0,"Auto RSI");
         system_state = class="num">1;
        }
     }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Fetch market quotes and technical data                           |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void update(class="type">void)
  {
   bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
   ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);

「把指标值拷进缓冲区」

在 MT5 里用 iRSI、iMA 拿到指标句柄后,真正能拿来计算的是缓冲区数据,不是句柄本身。上面两行把最新一根 K 线的 RSI 和 MA 数值分别塞进 rsi_buffer 与 ma_buffer,长度取 1 根就够做实时判断。 CopyBuffer 的参数是:句柄、缓冲区索引(主线通常是 0)、起始位置(0 代表当前柱)、要复制的数量、目标数组。只取 1 根能省内存,也避免不必要的历史遍历。 外汇和贵金属波动快,这种单根拷贝适合做 EA 的 OnTick 轻量判断;若拷多了,回测时可能拖慢节奏。开 MT5 把这段接在你自己的句柄后面,打印 rsi_buffer[0] 和 ma_buffer[0] 就能看到实时值。

MQL5 / C++
  CopyBuffer(rsi_handler,class="num">0,class="num">0,class="num">1,rsi_buffer);
  CopyBuffer(ma_handler,class="num">0,class="num">0,class="num">1,ma_buffer);
}

◍ 一点提醒

这套系列走到这一步,核心结论其实很克制:简单概率模型在 EURUSD 上仍未被我们后来尝试的修正方法超越,但方向是对的——与其直接对价格建模,不如先积累一组更易建模、信息量却不缩水的算法。 附带的两个文件值得你直接开 MT5 验证:Auto_RSI.mq5 仅 6.11 KB,RSI_Self_Optimizer.ipynb 为 131.78 KB,前者是可直接挂的 EA 骨架,后者跑自优化逻辑。外汇与贵金属杠杆高、滑点随机,任何算法都只是概率倾向,实盘前请用策略测试器跑至少三年 tick 数据。 下一篇若真去碰「超越简单马尔可夫」的课题,重点会落在如何把多个弱模型加权成投票机制,而不是单模型硬拟合。你可以先把自己这份 RSI 自优化跑通,再等后面的多策略文章。

常见问题

核心是初始化指标句柄、OnTick里拷贝指标缓冲、用均线过滤后判断开平仓,并单独处理句柄失效与持仓管理。
多为品种切换或图表周期变动导致句柄失效,需在OnTick检测句柄并返回失败,重建句柄后再拷贝缓冲区。
可以,小布能实时监控EA运行日志与盘口状态,发现句柄失效或持仓管理异常时直接提示你定位代码位置。
先拷RSI与均线缓冲,当RSI超卖且价格在均线上方倾向多单,反向则倾向空单,再调OrderSend开仓。
常因CopyBuffer的起始位或数量参数设错,确认用0起始并拷贝足够柱数,且句柄有效才不会丢数据。