基于MQL5和Python的自优化EA(第五部分):深度马尔可夫模型·综合运用
「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信号平。开仓前还要先确认无持仓且价格进入盈利区间,若用户指定均线平仓,则必须等价格回到均线正确一侧才允许开新仓,这套顺序能避开追在反转尖尖上。
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] 的位置关系配合系统方向。外汇与贵金属杠杆高,这类硬编码阈值在实盘前需用历史数据回测验证胜率倾向。
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 区间配合均线过滤后的成交频次是否过低。
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] 就能看到实时值。
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 自优化跑通,再等后面的多策略文章。