在MQL5中开发马丁格尔(Martingale)区域恢复策略·进阶篇
(2/3)·从策略定义到代码实现,手把手把区域恢复 EA 跑进 MT5 回测里看真实表现
RSI 上穿超卖后画 zone 并开仓
当 RSI 前一根柱低于超卖线、当前柱回升突破超卖线时,代码判定为空头动能衰减、倾向反手做多。此时用 obj_trade.Buy(0.01) 以 0.01 手开立市价买单,外汇与贵金属品种波动剧烈,0.01 手仅作验证用途,实盘须自担高风险。 订单回执 ticket 大于 0 说明下单指令已提交,再用 PositionSelectByTicket 锁定该持仓,读取开仓价 openPrice 作为 zone 基准。zoneHigh 取归一化开仓价,zoneLow = zoneHigh - zoneRange,上下目标则各加减 zoneTarget,这四个价位构成后续观察区间。 drawZoneLevel 负责把价位画成水平线:ObjectCreate 建 OBJ_HLINE,ObjectSetInteger 设颜色与线宽。绿线 zoneHigh、红线 zoneLow 宽 2,蓝线双向目标宽 3,肉眼可立刻看到突破与回撤边界。 最后把 lastDirection 置 1、recovery_lot 翻倍为 0.02,isBuyDone 开 true、isSellDone 关 false。开 MT5 把这段贴进 EA,调 overSoldLevel 与 zoneRange 就能看 zone 是否贴合你的周期。
if (!CopyBuffer(rsi_handle,class="num">0,class="num">1,class="num">2,rsiData)) class="kw">return; if (rsiData[class="num">1] < overSoldLevel && rsiData[class="num">0] > overSoldLevel){ obj_trade.Buy(class="num">0.01); class="type">ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > class="num">0){ if (PositionSelectByTicket(pos_ticket)){ class="type">class="kw">double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneHigh = NormalizeDouble(openPrice,_Digits); zoneLow = NormalizeDouble(zoneHigh - zoneRange,_Digits); zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget,_Digits); zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget,_Digits); drawZoneLevel(ZONE_H,zoneHigh,clrGreen,class="num">2); drawZoneLevel(ZONE_L,zoneLow,clrRed,class="num">2); drawZoneLevel(ZONE_T_H,zoneTargetHigh,clrBlue,class="num">3); drawZoneLevel(ZONE_T_L,zoneTargetLow,clrBlue,class="num">3); class="type">void drawZoneLevel(class="type">class="kw">string levelName, class="type">class="kw">double price, class="type">color clr, class="type">int width) { ObjectCreate(class="num">0, levelName, OBJ_HLINE, class="num">0, TimeCurrent(), price); class=class="str">"cmt">// Create a horizontal line object ObjectSetInteger(class="num">0, levelName, OBJPROP_COLOR, clr); class=class="str">"cmt">// Set the line class="type">color ObjectSetInteger(class="num">0, levelName, OBJPROP_WIDTH, width); class=class="str">"cmt">// Set the line width } lastDirection = class="num">1; recovery_lot = class="num">0.01*class="num">2; isBuyDone = true; isSellDone = false;
◍ RSI 拐点触发双向建仓与区域绘制
当 RSI 前一根柱大于超买线、当前柱跌破超买线时,脚本以 0.01 手卖出,并用持仓开盘价锚定 zoneLow,再向上加 zoneRange 得 zoneHigh、向两侧加 zoneTarget 得目标区。 买侧逻辑镜像对称:RSI 上穿超卖后开 0.01 手多单,同样以开盘价定 zoneLow/zoneHigh 及蓝线目标区,并调用 drawZoneLevel 用绿/红/蓝三色画线,线宽分别为 2、2、3。 开仓成功后 lastDirection 被置为 1 或 -1,recovery_lot 初值固定为 0.01*2=0.02,isBuyDone/isSellDone 互斥标记。外汇与贵金属杠杆高,这类固定手数恢复逻辑在连错时可能快速放大敞口,上 MT5 用策略测试器跑 EURUSD 15M 能直接看回撤曲线。
drawZoneLevel(ZONE_H, zoneHigh, clrGreen, class="num">2); class=class="str">"cmt">// Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, class="num">2); class=class="str">"cmt">// Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, class="num">3); class=class="str">"cmt">// Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, class="num">3); class=class="str">"cmt">// Draw the target low zone line lastDirection = class="num">1; class=class="str">"cmt">// Set the last direction to buy recovery_lot = class="num">0.01 * class="num">2; class=class="str">"cmt">// Set the initial recovery lot size isBuyDone = true; class=class="str">"cmt">// Mark buy trade as done isSellDone = false; class=class="str">"cmt">// Reset sell trade flag else if (rsiData[class="num">1] > overBoughtLevel && rsiData[class="num">0] < overBoughtLevel) { obj_trade.Sell(class="num">0.01); class=class="str">"cmt">// Open a sell trade with class="num">0.01 lots class="type">ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > class="num">0) { if (PositionSelectByTicket(pos_ticket)) { class="type">class="kw">double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneLow = NormalizeDouble(openPrice, _Digits); class=class="str">"cmt">// Set the low zone price zoneHigh = NormalizeDouble(zoneLow + zoneRange, _Digits); class=class="str">"cmt">// Set the high zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); class=class="str">"cmt">// Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); class=class="str">"cmt">// Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, class="num">2); class=class="str">"cmt">// Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, class="num">2); class=class="str">"cmt">// Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, class="num">3); class=class="str">"cmt">// Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, class="num">3); class=class="str">"cmt">// Draw the target low zone line lastDirection = -class="num">1; class=class="str">"cmt">// Set the last direction to sell recovery_lot = class="num">0.01 * class="num">2; class=class="str">"cmt">// Set the initial recovery lot size isBuyDone = false; class=class="str">"cmt">// Reset buy trade flag isSellDone = true; class=class="str">"cmt">// Mark sell trade as done } } } class="type">void OnTick() {
「RSI 穿越阈值后的开仓与区域画线」
这段逻辑跑在 EA 的 OnTick 里,先拿 iBars 判断 K 线总数是否变化,没变就直接 return,避免同一根 bar 重复触发。若已有持仓(PositionsTotal()>0)也退出,保证单笔单向先行。 CopyBuffer 从 RSI 句柄取最近两根缓冲值到 rsiData 数组,索引 1 是上一根、0 是当前根。若上一根低于超卖线且当前根上穿超卖线,视为反转倾向,调用 obj_trade.Buy(0.01) 以 0.01 手试多。 成交后用 PositionSelectByTicket 抓到开仓价,按 zoneRange 向下算 zoneLow,再按 zoneTarget 向上下扩展出目标区。drawZoneLevel 用绿/红/蓝线把这四个价位画在图上,蓝线线宽 3、其余 2。lastDirection 置 1,recovery_lot 设为 0.02(初始手数翻倍),isBuyDone 锁真。 对称的,上一根高于超买线、当前根下穿则 obj_trade.Sell(0.01) 试空,同样记录价位区域并重置买卖标记。外汇与贵金属波动剧烈,这种固定 0.01 手试单在极端跳空下可能迅速扩大浮亏,务必在 MT5 策略测试器用真实点差回测。
class="type">int bars = iBars(_Symbol,PERIOD_CURRENT); if (totalBars == bars) class="kw">return; totalBars = bars; if (PositionsTotal() > class="num">0) class="kw">return; if (!CopyBuffer(rsi_handle,class="num">0,class="num">1,class="num">2,rsiData)) class="kw">return; class=class="str">"cmt">//--- Check for oversold condition and open a buy position if (rsiData[class="num">1] < overSoldLevel && rsiData[class="num">0] > overSoldLevel) { obj_trade.Buy(class="num">0.01); class=class="str">"cmt">// Open a buy trade with class="num">0.01 lots class="type">ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > class="num">0) { if (PositionSelectByTicket(pos_ticket)) { class="type">class="kw">double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneHigh = NormalizeDouble(openPrice, _Digits); class=class="str">"cmt">// Set the high zone price zoneLow = NormalizeDouble(zoneHigh - zoneRange, _Digits); class=class="str">"cmt">// Set the low zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); class=class="str">"cmt">// Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); class=class="str">"cmt">// Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, class="num">2); class=class="str">"cmt">// Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, class="num">2); class=class="str">"cmt">// Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, class="num">3); class=class="str">"cmt">// Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, class="num">3); class=class="str">"cmt">// Draw the target low zone line lastDirection = class="num">1; class=class="str">"cmt">// Set the last direction to buy recovery_lot = class="num">0.01 * class="num">2; class=class="str">"cmt">// Set the initial recovery lot size isBuyDone = true; class=class="str">"cmt">// Mark buy trade as done isSellDone = false; class=class="str">"cmt">// Reset sell trade flag } } } class=class="str">"cmt">//--- Check for overbought condition and open a sell position else if (rsiData[class="num">1] > overBoughtLevel && rsiData[class="num">0] < overBoughtLevel) { obj_trade.Sell(class="num">0.01); class=class="str">"cmt">// Open a sell trade with class="num">0.01 lots class="type">ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > class="num">0) { if (PositionSelectByTicket(pos_ticket)) { class="type">class="kw">double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
区间边界与平仓触发的代码实现
这段逻辑负责把计算好的价格区间落到图表上,并设定后续平仓的判定条件。zoneLow 以开盘价归一化到当前品种小数位,zoneHigh 在其上加 zoneRange,上下目标位则分别加减 zoneTarget,四个边界都用 NormalizeDouble 对齐 _Digits,避免 MT5 浮点显示错位。 drawZoneLevel 用不同颜色和线宽区分边界:红绿实线标 zoneLow/zoneHigh(线宽2),蓝色线宽3标 zoneTargetLow/zoneTargetHigh,视觉上把「触发带」和「缓冲带」分开。方向状态被置为 -1(卖),recovery_lot 初始化为 0.02(0.01*2),同时翻转 isBuyDone/isSellDone 标志。 平仓判定很直接:当 bid 向上击穿 zoneTargetHigh 或向下击穿 zoneTargetLow 时,PositionClose 平掉当前符号仓位并 deleteZoneLevels 清图。deleteZoneLevels 用 ObjectDelete(0,...) 逐个删掉四个对象名,防止残留水平线干扰下一轮。 批量平仓则用 for 循环从 PositionsTotal()-1 向下遍历,PositionGetTicket 取ticket后 PositionSelectByTicket 选中再关闭,最后把四个 zone 变量归零。外汇与贵金属杠杆高,这类击穿平仓若遇滑点可能不在目标价成交,实盘前建议在 MT5 策略测试器用历史数据验证触发频率。
zoneLow = NormalizeDouble(openPrice, _Digits); class=class="str">"cmt">// Set the low zone price zoneHigh = NormalizeDouble(zoneLow + zoneRange, _Digits); class=class="str">"cmt">// Set the high zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); class=class="str">"cmt">// Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); class=class="str">"cmt">// Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, class="num">2); class=class="str">"cmt">// Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, class="num">2); class=class="str">"cmt">// Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, class="num">3); class=class="str">"cmt">// Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, class="num">3); class=class="str">"cmt">// Draw the target low zone line lastDirection = -class="num">1; class=class="str">"cmt">// Set the last direction to sell recovery_lot = class="num">0.01 * class="num">2; class=class="str">"cmt">// Set the initial recovery lot size isBuyDone = false; class=class="str">"cmt">// Reset buy trade flag isSellDone = true; class=class="str">"cmt">// Mark sell trade as done if (zoneTargetHigh > class="num">0 && zoneTargetLow > class="num">0){ if (bid > zoneTargetHigh || bid < zoneTargetLow){ obj_trade.PositionClose(_Symbol); deleteZoneLevels(); ... } } class="type">void deleteZoneLevels(){ ObjectDelete(class="num">0,ZONE_H); ObjectDelete(class="num">0,ZONE_L); ObjectDelete(class="num">0,ZONE_T_H); ObjectDelete(class="num">0,ZONE_T_L); } for (class="type">int i = PositionsTotal()-class="num">1; i >= class="num">0; i--){ class="type">ulong ticket = PositionGetTicket(i); if (ticket > class="num">0){ if (PositionSelectByTicket(ticket)){ obj_trade.PositionClose(ticket); } } } class=class="str">"cmt">//closed all, reset all zoneHigh=class="num">0;zoneLow=class="num">0;zoneTargetHigh=class="num">0;zoneTargetLow=class="num">0;
◍ 价格越界即清仓与反向加倍逻辑
当 bid 报价突破预设的上下目标区(zoneTargetHigh / zoneTargetLow 均大于 0),系统会先平掉当前符号持仓,再循环遍历所有持仓凭 ticket 逐一平仓,并清空图上绘制的区间线。这一步把方向变量 lastDirection 和 recovery_lot 都归零,等于把网格状态机复位到初始。 清仓之后,若 bid 高于 zoneHigh 且此前未做多、或上次方向为卖(lastDirection < 0),则调用 obj_trade.Buy 以 lots_Rec 手数开多,并把 lastDirection 置 1、recovery_lot 乘以 2、isBuyDone 置真、isSellDone 置假。反之 bid 跌破 zoneLow 时镜像处理开空,recovery_lot 同样翻倍。 注意 recovery_lot 每次触发都 ×2,这意味着连续反向时仓位呈几何增长,外汇与贵金属杠杆环境下回撤可能急剧放大,属高风险机制。开 MT5 把这段挂到 EA 里,改 zoneTargetHigh / Low 的赋值,就能直观看到清仓与加倍频率随区间宽度变化。
lastDirection=class="num">0; recovery_lot = class="num">0; class=class="str">"cmt">//--- Close all positions if the bid price is outside target zones if (zoneTargetHigh > class="num">0 && zoneTargetLow > class="num">0) { if (bid > zoneTargetHigh || bid < zoneTargetLow) { obj_trade.PositionClose(_Symbol); class=class="str">"cmt">// Close the current position deleteZoneLevels(); class=class="str">"cmt">// Delete all drawn zone levels for (class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--) { class="type">ulong ticket = PositionGetTicket(i); if (ticket > class="num">0) { if (PositionSelectByTicket(ticket)) { obj_trade.PositionClose(ticket); class=class="str">"cmt">// Close positions by ticket } } } class=class="str">"cmt">//--- Reset all zone and direction variables zoneHigh = class="num">0; zoneLow = class="num">0; zoneTargetHigh = class="num">0; zoneTargetLow = class="num">0; lastDirection = class="num">0; recovery_lot = class="num">0; } } if (zoneHigh > class="num">0 && zoneLow > class="num">0){ class="type">class="kw">double lots_Rec = class="num">0; lots_Rec = NormalizeDouble(recovery_lot,class="num">2); ... } if (bid > zoneHigh) { if (isBuyDone == false || lastDirection < class="num">0) { obj_trade.Buy(lots_Rec); class=class="str">"cmt">// Open a buy trade lastDirection = class="num">1; class=class="str">"cmt">// Set the last direction to buy recovery_lot = recovery_lot * class="num">2; class=class="str">"cmt">// Double the recovery lot size isBuyDone = true; class=class="str">"cmt">// Mark buy trade as done isSellDone = false; class=class="str">"cmt">// Reset sell trade flag } } else if (bid < zoneLow) { if (isSellDone == false || lastDirection > class="num">0) { obj_trade.Sell(lots_Rec); class=class="str">"cmt">// Open a sell trade lastDirection = -class="num">1; class=class="str">"cmt">// Set the last direction to sell recovery_lot = recovery_lot * class="num">2; class=class="str">"cmt">// Double the recovery lot size isBuyDone = false; class=class="str">"cmt">// Reset buy trade flag
「区间触发与仓位翻倍的执行尾段」
这段逻辑接在 zone 判定之后,核心是在 bid 触碰到 zoneHigh 或 zoneLow 时按方向开仓,并立刻把 recovery_lot 翻倍。注意开仓前用 isBuyDone / isSellDone 和 lastDirection 做互斥,避免同一侧连续重复发单。 实际跑起来,若 zoneHigh=1.0950、zoneLow=1.0900,bid 上破 1.0950 且上次方向为卖(lastDirection<0),会先 NormalizeDouble(recovery_lot,2) 规范手数到 2 位小数,再 obj_trade.Buy(lots_Rec),随后 recovery_lot 乘 2、isBuyDone 置真、isSellDone 复位。 外汇与贵金属杠杆高,马丁式翻倍加仓在单边行情里可能快速放大浮亏,MT5 上用策略测试器跑这段前先把 recovery_lot 初值设小,观察 10 次连续反向后仓位曲线再决定要不要上实盘。
isSellDone = true; class=class="str">"cmt">// Mark sell trade as done } } class=class="str">"cmt">//--- Check if price is within defined zones and take action if (zoneHigh > class="num">0 && zoneLow > class="num">0) { class="type">class="kw">double lots_Rec = NormalizeDouble(recovery_lot, class="num">2); class=class="str">"cmt">// Normalize the recovery lot size to class="num">2 decimal places if (bid > zoneHigh) { if (isBuyDone == false || lastDirection < class="num">0) { obj_trade.Buy(lots_Rec); class=class="str">"cmt">// Open a buy trade lastDirection = class="num">1; class=class="str">"cmt">// Set the last direction to buy recovery_lot = recovery_lot * class="num">2; class=class="str">"cmt">// Double the recovery lot size isBuyDone = true; class=class="str">"cmt">// Mark buy trade as done isSellDone = false; class=class="str">"cmt">// Reset sell trade flag } } else if (bid < zoneLow) { if (isSellDone == false || lastDirection > class="num">0) { obj_trade.Sell(lots_Rec); class=class="str">"cmt">// Open a sell trade lastDirection = -class="num">1; class=class="str">"cmt">// Set the last direction to sell recovery_lot = recovery_lot * class="num">2; class=class="str">"cmt">// Double the recovery lot size isBuyDone = false; class=class="str">"cmt">// Reset buy trade flag isSellDone = true; class=class="str">"cmt">// Mark sell trade as done } } }