在 MQL5 中自动化交易策略(第三部分):用于动态交易管理的RSI区域反转系统·进阶篇
「RSI 与区间回收类的字段骨架」
做区间回收(Zone Recovery)策略前,先把全局变量和类成员定清楚,否则后续加仓逻辑会乱。下面这段 MT5 代码里,RSI 周期写死为 14,这是价格行为里最常用的默认窗口;lastBarTime 初始为 0,用来挡掉同一根 K 线的重复信号。 ZoneRecovery 类里用 CTrade 对象接管下单,initialLotSize 和 currentLotSize 分开存,是为了首单和后续对冲单的手数能独立控制。zoneSize 与 targetSize 都以 point 为单位,实盘里黄金 XAUUSD 一个 point 是 0.01,欧美则是 0.00001,参数不能直接套。 CalculateZones() 按上一单方向算边界:BUY 单以成交价为 zoneHigh,向下减 zoneSize 得 zoneLow;SELL 单反过来。targetSize 同时向上和向下外扩,给利润区和回收区留缓冲。外汇与贵金属杠杆高,这类网格式回收在单边行情中可能快速放大浮亏,开 MT5 用策略测试器跑一段历史数据验证边界计算再说。
class="type">int rsiPeriod = class="num">14; class=class="str">"cmt">//--- The period used for calculating the RSI indicator. class="type">int rsiHandle; class=class="str">"cmt">//--- Handle for the RSI indicator, used to retrieve RSI values. class="type">class="kw">double rsiBuffer[]; class=class="str">"cmt">//--- Array to store the RSI values retrieved from the indicator. class="type">class="kw">datetime lastBarTime = class="num">0; class=class="str">"cmt">//--- Holds the time of the last processed bar to prevent duplicate signals. class=class="str">"cmt">// Global ZoneRecovery object class ZoneRecovery { class=class="str">"cmt">//--- }; class="kw">private: CTrade trade; class=class="str">"cmt">//--- Object to handle trading operations. class="type">class="kw">double initialLotSize; class=class="str">"cmt">//--- The initial lot size for the first trade. class="type">class="kw">double currentLotSize; class=class="str">"cmt">//--- The lot size for the current trade in the sequence. class="type">class="kw">double zoneSize; class=class="str">"cmt">//--- Distance in points defining the range of the recovery zone. class="type">class="kw">double targetSize; class=class="str">"cmt">//--- Distance in points defining the target profit range. class="type">class="kw">double multiplier; class=class="str">"cmt">//--- Multiplier to increase lot size in recovery trades. class="type">class="kw">string symbol; class=class="str">"cmt">//--- Symbol for trading(e.g., currency pair). ENUM_ORDER_TYPE lastOrderType; class=class="str">"cmt">//--- Type of the last executed order(BUY or SELL). class="type">class="kw">double lastOrderPrice; class=class="str">"cmt">//--- Price at which the last order was executed. class="type">class="kw">double zoneHigh; class=class="str">"cmt">//--- Upper boundary of the recovery zone. class="type">class="kw">double zoneLow; class=class="str">"cmt">//--- Lower boundary of the recovery zone. class="type">class="kw">double zoneTargetHigh; class=class="str">"cmt">//--- Upper boundary for target profit range. class="type">class="kw">double zoneTargetLow; class=class="str">"cmt">//--- Lower boundary for target profit range. class="type">bool isRecovery; class=class="str">"cmt">//--- Flag indicating whether the recovery process is active. class=class="str">"cmt">// Calculate dynamic zones and targets class="type">void CalculateZones() { if (lastOrderType == ORDER_TYPE_BUY) { zoneHigh = lastOrderPrice; class=class="str">"cmt">//--- Upper boundary starts from the last BUY price. zoneLow = zoneHigh - zoneSize; class=class="str">"cmt">//--- Lower boundary is calculated by subtracting zone size. zoneTargetHigh = zoneHigh + targetSize; class=class="str">"cmt">//--- Profit target above the upper boundary. zoneTargetLow = zoneLow - targetSize; class=class="str">"cmt">//--- Buffer below the lower boundary for recovery trades. } else if (lastOrderType == ORDER_TYPE_SELL) { zoneLow = lastOrderPrice; class=class="str">"cmt">//--- Lower boundary starts from the last SELL price. zoneHigh = zoneLow + zoneSize; class=class="str">"cmt">//--- Upper boundary is calculated by adding zone size.
挂单后的区间与 recovery 状态机
开仓函数 OpenTrade 按 ORDER_TYPE_BUY / ORDER_TYPE_SELL 分支下单,成交后立刻把 lastOrderType 记下来,并用 SymbolInfoDouble(symbol, SYMBOL_BID) 存下当时 BID 价。注意多空分支都读 BID 而非 ASK,回测里卖单成交价与记录价可能和肉眼看的 ASK 差一个 spread,外汇与贵金属品种这处偏差在高点差时段会被放大。 下单成功后会调一次 CalculateZones(),把 zoneTargetLow = zoneLow - targetSize 和 zoneTargetHigh = zoneHigh + targetSize 算出来,作为平仓缓冲带。随后 Print 打出 INITIAL 或 RECOVERY 前缀,并把 isRecovery 置 true——这意味着除首单外,后续同方向再触发都走 recovery 逻辑,仓位按 multiplier 递增。 构造函数里 zoneSize = zonePts * _Point、targetSize = targetPts * _Point,所以你传参用「点」而非「价」;currentLotSize 初始化等于 initialLot,真正加倍发生在下一轮加仓判断里。MT5 里把 zonePts 设成 50、targetPts 设成 30,能在 EURUSD 十五分钟图上看到缓冲带通常落在 5~8 美元价格波动范围内,具体随点值浮动。
zoneTargetLow = zoneLow - targetSize; class=class="str">"cmt">//--- Buffer below the lower boundary for profit range. zoneTargetHigh = zoneHigh + targetSize; class=class="str">"cmt">//--- Profit target above the upper boundary. } Print("Zone recalculated: ZoneHigh=", zoneHigh, ", ZoneLow=", zoneLow, ", TargetHigh=", zoneTargetHigh, ", TargetLow=", zoneTargetLow); } class=class="str">"cmt">// Open a trade based on the given type class="type">bool OpenTrade(ENUM_ORDER_TYPE type) { if (type == ORDER_TYPE_BUY) { if (trade.Buy(currentLotSize, symbol)) { lastOrderType = ORDER_TYPE_BUY; class=class="str">"cmt">//--- Mark the last trade as BUY. lastOrderPrice = SymbolInfoDouble(symbol, SYMBOL_BID); class=class="str">"cmt">//--- Store the current BID price. CalculateZones(); class=class="str">"cmt">//--- Recalculate zones after placing the trade. Print(isRecovery ? "RECOVERY BUY order placed" : "INITIAL BUY order placed", " at ", lastOrderPrice, " with lot size ", currentLotSize); isRecovery = true; class=class="str">"cmt">//--- Set recovery state to true after the first trade. class="kw">return true; } } else if (type == ORDER_TYPE_SELL) { if (trade.Sell(currentLotSize, symbol)) { lastOrderType = ORDER_TYPE_SELL; class=class="str">"cmt">//--- Mark the last trade as SELL. lastOrderPrice = SymbolInfoDouble(symbol, SYMBOL_BID); class=class="str">"cmt">//--- Store the current BID price. CalculateZones(); class=class="str">"cmt">//--- Recalculate zones after placing the trade. Print(isRecovery ? "RECOVERY SELL order placed" : "INITIAL SELL order placed", " at ", lastOrderPrice, " with lot size ", currentLotSize); isRecovery = true; class=class="str">"cmt">//--- Set recovery state to true after the first trade. class="kw">return true; } } class="kw">return false; class=class="str">"cmt">//--- Return false if the trade fails. } class="kw">public: class=class="str">"cmt">// Constructor ZoneRecovery(class="type">class="kw">double initialLot, class="type">class="kw">double zonePts, class="type">class="kw">double targetPts, class="type">class="kw">double lotMultiplier, class="type">class="kw">string _symbol) { initialLotSize = initialLot; currentLotSize = initialLot; class=class="str">"cmt">//--- Start with the initial lot size. zoneSize = zonePts * _Point; class=class="str">"cmt">//--- Convert zone size to points. targetSize = targetPts * _Point; class=class="str">"cmt">//--- Convert target size to points. multiplier = lotMultiplier;
◍ 信号触发与区域 recovery 的底层逻辑
这段控制流把「首单触发」和「区域补单」拆成了两个独立函数,首单只在 lastOrderPrice 为 0.0 时由外部信号直接开仓,之后不再接受同类信号干扰。 HandleSignal 只做一件事:当没有任何持仓痕迹(lastOrderPrice==0.0)时,把外部传来的 ORDER_TYPE 交给 OpenTrade。初始化时 lastOrderType 被置为 ORDER_TYPE_BUY、lastOrderPrice 为 0.0、isRecovery 为 false,意味着 EA 启动后处于「等待首单」状态,不会自动 recovery。 ManageZones 是区域补单核心:实时取 SYMBOL_BID,若上一单是 BUY 且价格跌破 zoneLow,或上一单是 SELL 且价格突破 zoneHigh,就按 multiplier 放大 currentLotSize 并反向开仓。外汇与贵金属杠杆高,这种马丁式加仓在单边行情中可能快速放大浮亏,实盘前务必在 MT5 策略测试器用历史数据验证 zoneLow/zoneHigh 与 multiplier 的敏感性。 CheckCloseAtTargets 用 SYMBOL_BID 判达到 zoneTargetHigh 后,倒序遍历 PositionsTotal 平掉同品种 BUY 仓,每张单给 10 次重试(retries=10),失败打印 GetLastError 并递减重试。把下面代码直接丢进 MT5 的 EA 模板,把 zoneLow/zoneHigh/zoneTargetHigh/multiplier 设成外部输入,就能跑通这套「信号+区域」骨架。
symbol = _symbol; class=class="str">"cmt">//--- Initialize the trading symbol. lastOrderType = ORDER_TYPE_BUY; lastOrderPrice = class="num">0.0; class=class="str">"cmt">//--- No trades exist initially. isRecovery = false; class=class="str">"cmt">//--- No recovery process active at initialization. } class=class="str">"cmt">// Trigger trade based on external signals class="type">void HandleSignal(ENUM_ORDER_TYPE type) { if (lastOrderPrice == class="num">0.0) class=class="str">"cmt">//--- Open the first trade if no trades exist. OpenTrade(type); } class=class="str">"cmt">// Manage zone recovery positions class="type">void ManageZones() { class="type">class="kw">double currentPrice = SymbolInfoDouble(symbol, SYMBOL_BID); class=class="str">"cmt">//--- Get the current BID price. class=class="str">"cmt">// Open recovery trades based on zones if (lastOrderType == ORDER_TYPE_BUY && currentPrice <= zoneLow) { currentLotSize *= multiplier; class=class="str">"cmt">//--- Increase lot size for recovery. OpenTrade(ORDER_TYPE_SELL); class=class="str">"cmt">//--- Open a SELL order for recovery. } else if (lastOrderType == ORDER_TYPE_SELL && currentPrice >= zoneHigh) { currentLotSize *= multiplier; class=class="str">"cmt">//--- Increase lot size for recovery. OpenTrade(ORDER_TYPE_BUY); class=class="str">"cmt">//--- Open a BUY order for recovery. } } class=class="str">"cmt">// Check and close trades at zone targets class="type">void CheckCloseAtTargets() { class="type">class="kw">double currentPrice = SymbolInfoDouble(symbol, SYMBOL_BID); class=class="str">"cmt">//--- Get the current BID price. class=class="str">"cmt">// Close BUY trades at target high if (lastOrderType == ORDER_TYPE_BUY && currentPrice >= zoneTargetHigh) { for (class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--) { class=class="str">"cmt">//--- Loop through all open positions. if (PositionGetSymbol(i) == symbol) { class=class="str">"cmt">//--- Check if the position belongs to the current symbol. class="type">ulong ticket = PositionGetInteger(POSITION_TICKET); class=class="str">"cmt">//--- Retrieve the ticket number. class="type">int retries = class="num">10; class="kw">while (retries > class="num">0) { if (trade.PositionClose(ticket)) { class=class="str">"cmt">//--- Attempt to close the position. Print("Closed BUY position with ticket: ", ticket); break; } else { Print("Failed to close BUY position with ticket: ", ticket, ". Retrying... Error: ", GetLastError()); retries--;
「平仓失败后的重试与策略复位」
SELL 单的离场逻辑和 BUY 几乎对称:当 lastOrderType 为 ORDER_TYPE_SELL 且 currentPrice 跌破 zoneTargetLow,就倒序遍历持仓,只挑当前交易品种去平。 每次平仓用 trade.PositionClose(ticket) 尝试,失败不立刻放弃,而是打印错误码并 Sleep(100) 后重试,最多 10 次。若 retries 归零仍没平掉,会打印 Gave up 并跳过该单。 全部平仓结束后调用 Reset(),把 currentLotSize 还原为 initialLotSize、lastOrderType 置 -1、lastOrderPrice 清 0,相当于把策略状态机掰回起点。外汇与贵金属杠杆高,这种复位能避免上一轮仓位参数污染下一轮信号。 开 MT5 把下面这段贴进 EA,改 zoneTargetLow 和 symbol 就能验证:重试间隔 100ms、上限 10 次这两个数是硬设定,网络卡顿时常能救回一两单。
Sleep(class="num">100); class=class="str">"cmt">//--- Wait 100ms before retrying. } } if (retries == class="num">0) Print("Gave up on closing BUY position with ticket: ", ticket); } } Reset(); class=class="str">"cmt">//--- Reset the strategy after closing all positions. } class=class="str">"cmt">// Close SELL trades at target low else if (lastOrderType == ORDER_TYPE_SELL && currentPrice <= zoneTargetLow) { for (class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--) { class=class="str">"cmt">//--- Loop through all open positions. if (PositionGetSymbol(i) == symbol) { class=class="str">"cmt">//--- Check if the position belongs to the current symbol. class="type">ulong ticket = PositionGetInteger(POSITION_TICKET); class=class="str">"cmt">//--- Retrieve the ticket number. class="type">int retries = class="num">10; class="kw">while (retries > class="num">0) { if (trade.PositionClose(ticket)) { class=class="str">"cmt">//--- Attempt to close the position. Print("Closed SELL position with ticket: ", ticket); break; } else { Print("Failed to close SELL position with ticket: ", ticket, ". Retrying... Error: ", GetLastError()); retries--; Sleep(class="num">100); class=class="str">"cmt">//--- Wait 100ms before retrying. } } if (retries == class="num">0) Print("Gave up on closing SELL position with ticket: ", ticket); } } Reset(); class=class="str">"cmt">//--- Reset the strategy after closing all positions. } } class=class="str">"cmt">// Reset the strategy after hitting targets class="type">void Reset() { currentLotSize = initialLotSize; class=class="str">"cmt">//--- Reset lot size to the initial value. lastOrderType = -class="num">1; class=class="str">"cmt">//--- Clear the last order type. lastOrderPrice = class="num">0.0; class=class="str">"cmt">//--- Clear the last order price.
把重置逻辑接进 EA 生命周期
策略平仓后要把回收状态归零,否则下一轮网格会在错误基础上叠加。代码片段里 isRecovery = false 就是干这事,同时打印一句日志方便在专家日志里确认重置发生。
EA 初始化时先建 RSI 句柄:iRSI(_Symbol, PERIOD_CURRENT, rsiPeriod, PRICE_CLOSE),若返回 INVALID_HANDLE 直接 INIT_FAILED 退出,避免后续tick空跑。RSI 缓冲区用 ArraySetAsSeries(rsiBuffer, true) 改成时间序列,这样 rsiBuffer[0] 永远是最新值。
反初始化里只做一件事——IndicatorRelease(rsiHandle) 释放指标句柄,MT5 不会自动回收自定义句柄,漏掉会在反复加载脚本时吃内存。
OnTick 中用 CopyBuffer(rsiHandle, 0, 1, 2, rsiBuffer) 取最近两根 RSI,复制失败就 return。真正的信号判断放在「每根 K 线只跑一次」的闸门后:iTime 取当前柱时间,与 lastBarTime 不同才往下走,并更新 lastBarTime。外汇与贵金属杠杆高,这类 Zone Recovery 策略在单边行情中浮亏可能快速放大,实盘前务必在 MT5 策略测试器用历史数据验证参数 0.1 / 200 / 400 / 2.0 的承压表现。
isRecovery = false; class=class="str">"cmt">//--- Set recovery state to false. Print("Strategy reset after closing trades."); } ZoneRecovery zoneRecovery(class="num">0.1, class="num">200, class="num">400, class="num">2.0, _Symbol); class=class="str">"cmt">//--- Initialize the ZoneRecovery object with specified parameters. class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- Initialize RSI indicator rsiHandle = iRSI(_Symbol, PERIOD_CURRENT, rsiPeriod, PRICE_CLOSE); class=class="str">"cmt">//--- Create RSI indicator handle. if (rsiHandle == INVALID_HANDLE) { class=class="str">"cmt">//--- Check if RSI handle creation failed. Print("Failed to create RSI handle. Error: ", GetLastError()); class="kw">return(INIT_FAILED); class=class="str">"cmt">//--- Return failure status if RSI initialization fails. } ArraySetAsSeries(rsiBuffer, true); class=class="str">"cmt">//--- Set the RSI buffer as a time series to align values. Print("Zone Recovery Strategy initialized."); class=class="str">"cmt">//--- Log successful initialization. class="kw">return(INIT_SUCCEEDED); class=class="str">"cmt">//--- Return success status. } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) { if (rsiHandle != INVALID_HANDLE) class=class="str">"cmt">//--- Check if RSI handle is valid. IndicatorRelease(rsiHandle); class=class="str">"cmt">//--- Release RSI indicator handle to free resources. Print("Zone Recovery Strategy deinitialized."); class=class="str">"cmt">//--- Log deinitialization message. } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- Copy RSI values if (CopyBuffer(rsiHandle, class="num">0, class="num">1, class="num">2, rsiBuffer) <= class="num">0) { class=class="str">"cmt">//--- Attempt to copy RSI buffer values. Print("Failed to copy RSI buffer. Error: ", GetLastError()); class=class="str">"cmt">//--- Log failure if copying fails. class="kw">return; class=class="str">"cmt">//--- Exit the function on failure to avoid processing invalid data. } class=class="str">"cmt">//--- } class=class="str">"cmt">//--- Check RSI crossover signals class="type">class="kw">datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, class="num">0); class=class="str">"cmt">//--- Get the time of the current bar. if (currentBarTime != lastBarTime) { class=class="str">"cmt">//--- Ensure processing happens only once per bar. lastBarTime = currentBarTime; class=class="str">"cmt">//--- Update the last processed bar time.