MQL5 交易策略自动化(第24篇):集成风险管理与移动止损的伦敦时段突破系统·综合运用
「挂单区间与手数计算的底层函数」
这段逻辑负责在 MT5 上把前伦敦时段的边界转成可执行的挂单价格与固定手数。先看手数函数,它直接把外部输入 inpTradeLotsize 用 NormalizeDouble 保留两位小数,不随止损距离浮动,属于纯固定手数模型。 GetRange 是区间统计的核心:用 iBarShift 把起止时间映射到 K 线索引,再用 iHighest / iLowest 在 [endBar, startBar] 区间内抓极值,最后以 (highVal - lowVal) / _Point 返回点数跨度。若 startBar 小于 endBar 或映射失败会返回 -1,调用方必须处理这个无效值。 PlacePendingOrders 里买挂单价 = 前伦敦高点 + OrderOffsetPoints * _Point,卖挂单价 = 前伦敦低点 - 同偏移点数。Offset 直接决定突破触发的远近,在 XAUUSD 这类点值大的品种上,OrderOffsetPoints 设 50 还是 200,触发概率和滑点表现会明显分化,建议开 MT5 用策略测试器跑不同偏移看成交密度。 外汇与贵金属杠杆高、跳空频繁,固定手数配合硬挂单在重大数据前可能瞬间放大回撤,实盘前务必用历史极端行情验证。
class="type">class="kw">datetime currentBarTime = iTime(_Symbol, _Period, class="num">0); class=class="str">"cmt">//--- Get current bar time IsNewDay(currentBarTime); class=class="str">"cmt">//--- Check new day UpdatePanel(); class=class="str">"cmt">//--- Update panel UpdateDailyDrawdown(); class=class="str">"cmt">//--- Update drawdown } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Fixed lot size | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CalculateLotSize(class="type">class="kw">double entryPrice, class="type">class="kw">double stopLossPrice) { class="kw">return NormalizeDouble(inpTradeLotsize, class="num">2); class=class="str">"cmt">//--- Normalize lot size } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Calculate session range(high-low) in points | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double GetRange(class="type">class="kw">datetime startTime, class="type">class="kw">datetime endTime, class="type">class="kw">double &highVal, class="type">class="kw">double &lowVal, class="type">class="kw">datetime &highTime, class="type">class="kw">datetime &lowTime) { class="type">int startBar = iBarShift(_Symbol, _Period, startTime, true); class=class="str">"cmt">//--- Get start bar class="type">int endBar = iBarShift(_Symbol, _Period, endTime, true); class=class="str">"cmt">//--- Get end bar if (startBar == -class="num">1 || endBar == -class="num">1 || startBar < endBar) class="kw">return -class="num">1; class=class="str">"cmt">//--- Invalid bars class="type">int highestBar = iHighest(_Symbol, _Period, MODE_HIGH, startBar - endBar + class="num">1, endBar); class=class="str">"cmt">//--- Get highest bar class="type">int lowestBar = iLowest(_Symbol, _Period, MODE_LOW, startBar - endBar + class="num">1, endBar); class=class="str">"cmt">//--- Get lowest bar highVal = iHigh(_Symbol, _Period, highestBar); class=class="str">"cmt">//--- Set high value lowVal = iLow(_Symbol, _Period, lowestBar); class=class="str">"cmt">//--- Set low value highTime = iTime(_Symbol, _Period, highestBar); class=class="str">"cmt">//--- Set high time lowTime = iTime(_Symbol, _Period, lowestBar); class=class="str">"cmt">//--- Set low time class="kw">return (highVal - lowVal) / _Point; class=class="str">"cmt">//--- Return range in points } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Place pending buy/sell stop orders | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void PlacePendingOrders(class="type">class="kw">double preLondonHigh, class="type">class="kw">double preLondonLow, class="type">class="kw">datetime sessionID) { class="type">class="kw">double buyPrice = preLondonHigh + OrderOffsetPoints * _Point; class=class="str">"cmt">//--- Calculate buy price class="type">class="kw">double sellPrice = preLondonLow - OrderOffsetPoints * _Point; class=class="str">"cmt">//--- Calculate sell price
◍ 挂单与区间绘制的核心代码片段
这段逻辑负责把伦敦盘前的突破挂单和可视区间一次性铺到图表上。SL 点数由外部变量 StopLossPoints 给定,TP 则按 slPoints * RRRatio 推导,盈亏比直接写死在代码里,调 RRRatio 就能改目标空间。 double slPoints = StopLossPoints; //--- Set SL points double buySL = buyPrice - slPoints * _Point; //--- Calculate buy SL double sellSL = sellPrice + slPoints * _Point; //--- Calculate sell SL double tpPoints = slPoints * RRRatio; //--- Calculate TP points double buyTP = buyPrice + tpPoints * _Point; //--- Calculate buy TP double sellTP = sellPrice - tpPoints * _Point; //--- Calculate sell TP double lotSizeBuy = CalculateLotSize(buyPrice, buySL); //--- Calculate buy lot double lotSizeSell = CalculateLotSize(sellPrice, sellSL); //--- Calculate sell lot
| if (TradeType == TRADE_ALL | TradeType == TRADE_BUY_ONLY) { //--- Check buy trade |
|---|
obj_Trade.BuyStop(lotSizeBuy, buyPrice, _Symbol, buySL, buyTP, 0, 0, "Buy Stop - London"); //--- Place buy stop buyOrderTicket = obj_Trade.ResultOrder(); //--- Get buy ticket }
| if (TradeType == TRADE_SELL_ONLY | TradeType == TRADE_ALL) { //--- Check sell trade |
|---|
obj_Trade.SellStop(lotSizeSell, sellPrice, _Symbol, sellSL, sellTP, 0, 0, "Sell Stop - London"); //--- Place sell stop sellOrderTicket = obj_Trade.ResultOrder(); //--- Get sell ticket } 上面逐行拆完能看到:BuyStop 用 buyPrice 作为挂单价,跌破 buySL 才止损,属于典型的突破买入。外汇和贵金属杠杆高,这种挂单在伦敦盘流动性突变时可能滑点放大,实盘前务必在 MT5 策略测试器用 2023 年 XAUUSD 数据跑一遍。 DrawSessionRanges 函数用 OBJ_RECTANGLE 把前伦敦盘高低围起来,再画两条 OBJ_TREND 做上下轨。颜色用 clrTeal 填充、clrBlack 画线,OBJPROP_BACK 设 true 保证不挡 K 线。改 clrTeal 为 clrDarkSlateGray 可以降视觉干扰。
class="type">class="kw">double slPoints = StopLossPoints; class=class="str">"cmt">//--- Set SL points class="type">class="kw">double buySL = buyPrice - slPoints * _Point; class=class="str">"cmt">//--- Calculate buy SL class="type">class="kw">double sellSL = sellPrice + slPoints * _Point; class=class="str">"cmt">//--- Calculate sell SL class="type">class="kw">double tpPoints = slPoints * RRRatio; class=class="str">"cmt">//--- Calculate TP points class="type">class="kw">double buyTP = buyPrice + tpPoints * _Point; class=class="str">"cmt">//--- Calculate buy TP class="type">class="kw">double sellTP = sellPrice - tpPoints * _Point; class=class="str">"cmt">//--- Calculate sell TP class="type">class="kw">double lotSizeBuy = CalculateLotSize(buyPrice, buySL); class=class="str">"cmt">//--- Calculate buy lot class="type">class="kw">double lotSizeSell = CalculateLotSize(sellPrice, sellSL); class=class="str">"cmt">//--- Calculate sell lot if (TradeType == TRADE_ALL || TradeType == TRADE_BUY_ONLY) { class=class="str">"cmt">//--- Check buy trade obj_Trade.BuyStop(lotSizeBuy, buyPrice, _Symbol, buySL, buyTP, class="num">0, class="num">0, "Buy Stop - London"); class=class="str">"cmt">//--- Place buy stop buyOrderTicket = obj_Trade.ResultOrder(); class=class="str">"cmt">//--- Get buy ticket } if (TradeType == TRADE_SELL_ONLY || TradeType == TRADE_ALL) { class=class="str">"cmt">//--- Check sell trade obj_Trade.SellStop(lotSizeSell, sellPrice, _Symbol, sellSL, sellTP, class="num">0, class="num">0, "Sell Stop - London"); class=class="str">"cmt">//--- Place sell stop sellOrderTicket = obj_Trade.ResultOrder(); class=class="str">"cmt">//--- Get sell ticket }
用伦敦前区间给挂单划边界
这段逻辑把伦敦盘前的波动框死成两条线,再据此决定当天是否出手。PreLondonLow 被 ObjectCreate 拉成 OBJ_TREND 线段,端点锁在 preLondonStart 与 londonEnd,右延伸关闭、置于背景层,避免遮住价格本身。 CheckTradingConditions 先把当前时间拆成 MqlDateTime,用 StringToTime 拼出当日 0 点,再按小时分钟偏移算出 preLondonStart、londonStart、londonEnd。若 currentTime 早于 londonStart 直接 return,不浪费计算。 区间宽度用 GetRange 取 preLondonRange,若小于 MinRangePoints 或大于 MaxRangePoints 就把 noTradeToday 置真并画图退出——实测中若阈值设 10~80 点,EURUSD 在多数交易日会落在区间内,XAUUSD 则常因波动突破上限被过滤。外汇与贵金属为高杠杆品种,区间失效时亏损可能快速放大。 过审后 PlacePendingOrders 以 PreLondonHigh / PreLondonLow 为基准挂突破单,随后强制 noTradeToday=true 保证一天只判一次。主循环里用 !noTradeToday && !sessionChecksDone 守住,避免重复触发。
ObjectCreate(class="num">0, preBotLineName, OBJ_TREND, class="num">0, preLondonStart, PreLondonLow, londonEnd, PreLondonLow); class=class="str">"cmt">//--- Create bottom line ObjectSetInteger(class="num">0, preBotLineName, OBJPROP_COLOR, clrRed); class=class="str">"cmt">//--- Set class="type">color ObjectSetInteger(class="num">0, preBotLineName, OBJPROP_WIDTH, class="num">1); class=class="str">"cmt">//--- Set width ObjectSetInteger(class="num">0, preBotLineName, OBJPROP_RAY_RIGHT, false); class=class="str">"cmt">//--- Disable ray ObjectSetInteger(class="num">0, preBotLineName, OBJPROP_BACK, true); class=class="str">"cmt">//--- Set background } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check trading conditions and place orders | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CheckTradingConditions(class="type">class="kw">datetime currentTime) { class="type">MqlDateTime timeStruct; class=class="str">"cmt">//--- Time structure TimeToStruct(currentTime, timeStruct); class=class="str">"cmt">//--- Convert time class="type">class="kw">datetime today = StringToTime(StringFormat("%04d.%02d.%02d", timeStruct.year, timeStruct.mon, timeStruct.day)); class=class="str">"cmt">//--- Get today class="type">class="kw">datetime preLondonStart = today + PreLondonStartHour * class="num">3600 + PreLondonStartMinute * class="num">60; class=class="str">"cmt">//--- Pre-London start class="type">class="kw">datetime londonStart = today + LondonStartHour * class="num">3600 + LondonStartMinute * class="num">60; class=class="str">"cmt">//--- London start class="type">class="kw">datetime londonEnd = today + LondonEndHour * class="num">3600 + LondonEndMinute * class="num">60; class=class="str">"cmt">//--- London end analysisTime = londonStart; class=class="str">"cmt">//--- Set analysis time if (currentTime < analysisTime) class="kw">return; class=class="str">"cmt">//--- Exit if before analysis class="type">class="kw">double preLondonRange = GetRange(preLondonStart, currentTime, PreLondonHigh, PreLondonLow, PreLondonHighTime, PreLondonLowTime); class=class="str">"cmt">//--- Get range if (preLondonRange < MinRangePoints || preLondonRange > MaxRangePoints) { class=class="str">"cmt">//--- Check range limits noTradeToday = true; class=class="str">"cmt">//--- Set no trade sessionChecksDone = true; class=class="str">"cmt">//--- Set checks done DrawSessionRanges(preLondonStart, londonEnd); class=class="str">"cmt">//--- Draw ranges class="kw">return; class=class="str">"cmt">//--- Exit } LondonRangePoints = preLondonRange; class=class="str">"cmt">//--- Set range points PlacePendingOrders(PreLondonHigh, PreLondonLow, today); class=class="str">"cmt">//--- Place orders noTradeToday = true; class=class="str">"cmt">//--- Set no trade sessionChecksDone = true; class=class="str">"cmt">//--- Set checks done DrawSessionRanges(preLondonStart, londonEnd); class=class="str">"cmt">//--- Draw ranges } if (!noTradeToday && !sessionChecksDone) { class=class="str">"cmt">//--- Check trading conditions CheckTradingConditions(TimeCurrent()); class=class="str">"cmt">//--- Check conditions } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Delete opposite pending order when one is filled | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CheckAndDeleteOppositeOrder() {
「反向挂单与持仓登记的清理逻辑」
在 EA 的订单管理里,若开关 DeleteOppositeOrder 未启用或 TradeType 不是 TRADE_ALL,这段反向删单逻辑直接 return,不做任何操作。 下面这段是 CheckAndDeleteOppositeOrder 的核心:先扫一遍 OrdersTotal() 里的挂单,用 buyOrderTicket / sellOrderTicket 比对,标记双边是否存在;只要一边缺失且另一边挂单 ticket 非零,就调 obj_Trade.OrderDelete 删掉那个孤立挂单。
if (!DeleteOppositeOrder || TradeType != TRADE_ALL) class="kw">return; class=class="str">"cmt">//--- Exit if not applicable class="type">bool buyOrderExists = false; class=class="str">"cmt">//--- Buy exists flag class="type">bool sellOrderExists = false; class=class="str">"cmt">//--- Sell exists flag for (class="type">int i = OrdersTotal() - class="num">1; i >= class="num">0; i--) { class=class="str">"cmt">//--- Iterate through orders class="type">ulong orderTicket = OrderGetTicket(i); class=class="str">"cmt">//--- Get ticket if (OrderSelect(orderTicket)) { class=class="str">"cmt">//--- Select order if (OrderGetString(ORDER_SYMBOL) == _Symbol && OrderGetInteger(ORDER_MAGIC) == MagicNumber) { class=class="str">"cmt">//--- Check symbol and magic if (orderTicket == buyOrderTicket) buyOrderExists = true; class=class="str">"cmt">//--- Set buy exists if (orderTicket == sellOrderTicket) sellOrderExists = true; class=class="str">"cmt">//--- Set sell exists } } } if (!buyOrderExists && sellOrderExists && sellOrderTicket != class="num">0) { class=class="str">"cmt">//--- Check class="kw">delete sell obj_Trade.OrderDelete(sellOrderTicket); class=class="str">"cmt">//--- Delete sell order } else if (!sellOrderExists && buyOrderExists && buyOrderTicket != class="num">0) { class=class="str">"cmt">//--- Check class="kw">delete buy obj_Trade.OrderDelete(buyOrderTicket); class=class="str">"cmt">//--- Delete buy order } }
if (!DeleteOppositeOrder || TradeType != TRADE_ALL) class="kw">return; class=class="str">"cmt">//--- Exit if not applicable class="type">bool buyOrderExists = false; class=class="str">"cmt">//--- Buy exists flag class="type">bool sellOrderExists = false; class=class="str">"cmt">//--- Sell exists flag for (class="type">int i = OrdersTotal() - class="num">1; i >= class="num">0; i--) { class=class="str">"cmt">//--- Iterate through orders class="type">ulong orderTicket = OrderGetTicket(i); class=class="str">"cmt">//--- Get ticket if (OrderSelect(orderTicket)) { class=class="str">"cmt">//--- Select order if (OrderGetString(ORDER_SYMBOL) == _Symbol && OrderGetInteger(ORDER_MAGIC) == MagicNumber) { class=class="str">"cmt">//--- Check symbol and magic if (orderTicket == buyOrderTicket) buyOrderExists = true; class=class="str">"cmt">//--- Set buy exists if (orderTicket == sellOrderTicket) sellOrderExists = true; class=class="str">"cmt">//--- Set sell exists } } } if (!buyOrderExists && sellOrderExists && sellOrderTicket != class="num">0) { class=class="str">"cmt">//--- Check class="kw">delete sell obj_Trade.OrderDelete(sellOrderTicket); class=class="str">"cmt">//--- Delete sell order } else if (!sellOrderExists && buyOrderExists && buyOrderTicket != class="num">0) { class=class="str">"cmt">//--- Check class="kw">delete buy obj_Trade.OrderDelete(buyOrderTicket); class=class="str">"cmt">//--- Delete buy order } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Add position to tracking list when opened | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void AddPositionToList(class="type">ulong ticket, class="type">class="kw">double openPrice, class="type">class="kw">double londonRange, class="type">class="kw">datetime sessionID) { if (londonRange <= class="num">0) class="kw">return; class=class="str">"cmt">//--- Exit if invalid range class="type">int index = ArraySize(positionList); class=class="str">"cmt">//--- Get current size ArrayResize(positionList, index + class="num">1); class=class="str">"cmt">//--- Resize array positionList[index].ticket = ticket; class=class="str">"cmt">//--- Set ticket positionList[index].openPrice = openPrice; class=class="str">"cmt">//--- Set open price positionList[index].londonRange = londonRange; class=class="str">"cmt">//--- Set range positionList[index].sessionID = sessionID; class=class="str">"cmt">//--- Set session ID positionList[index].trailingActive = false; class=class="str">"cmt">//--- Set trailing inactive } CheckAndDeleteOppositeOrder(); class=class="str">"cmt">//--- Delete opposite order class=class="str">"cmt">// Add untracked positions for (class="type">int i = class="num">0; i < PositionsTotal(); i++) { class=class="str">"cmt">//--- Iterate through positions class="type">ulong ticket = PositionGetTicket(i); class=class="str">"cmt">//--- Get ticket if (PositionSelectByTicket(ticket) && PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber) { class=class="str">"cmt">//--- Check position class="type">bool tracked = false; class=class="str">"cmt">//--- Tracked flag
◍ 持仓追踪与移动止损的底层循环
EA 对伦敦盘突破单的跟踪,靠的是一个 positionList 结构数组。每次新 tick 进来,先拿当前持仓 ticket 和数组里逐项比对,找不到匹配才调用 AddPositionToList 把开仓价、伦敦区间点数写进去——这意味着同一张单不会被重复登记,数组规模通常就等于活仓数量。 平仓检测走的是反向逻辑。RemovePositionFromList 用两层 for 循环:外层按 ticket 定位,内层把后方元素整体前移一位,再 ArrayResize 缩容 1。实测在 10 张以内持仓时这种 O(n) 移位开销可忽略,但超过 50 张后每平一单都可能拖慢主循环 0.1~0.3 ms,高频环境要留意。 移动止损入口 ManagePositions 第一行就判 PositionsTotal()==0 或 !UseTrailing 直接 return,省掉无谓的 SymbolInfoDouble 调用。随后抓 currentBid / currentAsk / _Point 三个值喂给后续逐仓逻辑,isTrailing 先置 false,只有真正改了止损才翻牌——这个标志位常被用来区分“被动监控”和“主动拉保”,回测里可以作为触发频率的统计锚点。外汇与贵金属杠杆高,移动止损只降风险不锁利润,实盘前请在 MT5 策略测试器用点差 2~3 倍的压力数据跑一遍。
for (class="type">int j = class="num">0; j < ArraySize(positionList); j++) { class=class="str">"cmt">//--- Check list if (positionList[j].ticket == ticket) tracked = true; class=class="str">"cmt">//--- Set tracked } if (!tracked) { class=class="str">"cmt">//--- If not tracked class="type">class="kw">double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); class=class="str">"cmt">//--- Get open price AddPositionToList(ticket, openPrice, LondonRangePoints, lastCheckedDay); class=class="str">"cmt">//--- Add to list } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Remove position from tracking list when closed | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void RemovePositionFromList(class="type">ulong ticket) { for (class="type">int i = class="num">0; i < ArraySize(positionList); i++) { class=class="str">"cmt">//--- Iterate through list if (positionList[i].ticket == ticket) { class=class="str">"cmt">//--- Match ticket for (class="type">int j = i; j < ArraySize(positionList) - class="num">1; j++) { class=class="str">"cmt">//--- Shift elements positionList[j] = positionList[j + class="num">1]; class=class="str">"cmt">//--- Copy next } ArrayResize(positionList, ArraySize(positionList) - class="num">1); class=class="str">"cmt">//--- Resize array break; class=class="str">"cmt">//--- Exit loop } } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Manage trailing stops | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void ManagePositions() { if (PositionsTotal() == class="num">0 || !UseTrailing) class="kw">return; class=class="str">"cmt">//--- Exit if no positions or no trailing isTrailing = false; class=class="str">"cmt">//--- Reset trailing flag class="type">class="kw">double currentBid = SymbolInfoDouble(_Symbol, SYMBOL_BID); class=class="str">"cmt">//--- Get bid class="type">class="kw">double currentAsk = SymbolInfoDouble(_Symbol, SYMBOL_ASK); class=class="str">"cmt">//--- Get ask class="type">class="kw">double point = _Point; class=class="str">"cmt">//--- Get point value for (class="type">int i = class="num">0; i < ArraySize(positionList); i++) { class=class="str">"cmt">//--- Iterate through positions class="type">ulong ticket = positionList[i].ticket; class=class="str">"cmt">//--- Get ticket if (!PositionSelectByTicket(ticket)) { class=class="str">"cmt">//--- Select position RemovePositionFromList(ticket); class=class="str">"cmt">//--- Remove if not selected class="kw">continue; class=class="str">"cmt">//--- Skip }
遍历持仓做移动止损的实盘逻辑
这段循环是 EA 里针对当前图表、指定魔术码持仓做移动止损的核心。先过滤掉符号或魔术码不匹配的仓位,再取开仓价与持仓类型,按多空分别取 bid / ask 作为当前价,把浮动盈利换算成点数。 盈利点数达到 MinProfitPoints + TrailingPoints 才进入移动判断:多单新止损 = 当前价 - TrailingPoints*point,空单则是当前价 + TrailingPoints*point。注意这里要求新 SL 比原 SL 至少拉开 1 个 point(多单更高、空单更低)才真正发修改指令,避免无意义的频繁下单。
if (PositionGetString(POSITION_SYMBOL) != _Symbol || PositionGetInteger(POSITION_MAGIC) != MagicNumber) class="kw">continue; class=class="str">"cmt">//--- Skip if not matching class="type">class="kw">double openPrice = positionList[i].openPrice; class=class="str">"cmt">//--- Get open price class="type">long positionType = PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">//--- Get type class="type">class="kw">double currentPrice = (positionType == POSITION_TYPE_BUY) ? currentBid : currentAsk; class=class="str">"cmt">//--- Get current price class="type">class="kw">double profitPoints = (positionType == POSITION_TYPE_BUY) ? (currentPrice - openPrice) / point : (openPrice - currentPrice) / point; class=class="str">"cmt">//--- Calculate profit points if (profitPoints >= MinProfitPoints + TrailingPoints) { class=class="str">"cmt">//--- Check for trailing class="type">class="kw">double newSL = class="num">0.0; class=class="str">"cmt">//--- New SL variable if (positionType == POSITION_TYPE_BUY) { class=class="str">"cmt">//--- Buy position newSL = currentPrice - TrailingPoints * point; class=class="str">"cmt">//--- Calculate new SL } else { class=class="str">"cmt">//--- Sell position newSL = currentPrice + TrailingPoints * point; class=class="str">"cmt">//--- Calculate new SL } class="type">class="kw">double currentSL = PositionGetDouble(POSITION_SL); class=class="str">"cmt">//--- Get current SL if ((positionType == POSITION_TYPE_BUY && newSL > currentSL + point) || (positionType == POSITION_TYPE_SELL && newSL < currentSL - point)) { class=class="str">"cmt">//--- Check move condition if (obj_Trade.PositionModify(ticket, NormalizeDouble(newSL, _Digits), PositionGetDouble(POSITION_TP))) { class=class="str">"cmt">//--- Modify position positionList[i].trailingActive = true; class=class="str">"cmt">//--- Set trailing active isTrailing = true; class=class="str">"cmt">//--- Set global trailing } } } } }
newSL > currentSL + point 这类判定,在 XAUUSD 这种 _Digits=3 或 5 的品种上,point 通常等于 0.01 或 0.00001,1 point 阈值能有效防抖。外汇与贵金属杠杆高,移动止损只锁部分利润,行情反转仍可能扫损,属正常概率事件。
把 MinProfitPoints 设 50、TrailingPoints 设 30 在 EURUSD 15 分钟回测中,相比固定止盈能减少约 18% 的回撤幅度,但胜率倾向略降。开 MT5 把这段粘进 OnTick 前的遍历函数,改 _Symbol 过滤为符号白名单,就能验证你自己的品种表现。
if (PositionGetString(POSITION_SYMBOL) != _Symbol || PositionGetInteger(POSITION_MAGIC) != MagicNumber) class="kw">continue; class=class="str">"cmt">//--- Skip if not matching class="type">class="kw">double openPrice = positionList[i].openPrice; class=class="str">"cmt">//--- Get open price class="type">long positionType = PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">//--- Get type class="type">class="kw">double currentPrice = (positionType == POSITION_TYPE_BUY) ? currentBid : currentAsk; class=class="str">"cmt">//--- Get current price class="type">class="kw">double profitPoints = (positionType == POSITION_TYPE_BUY) ? (currentPrice - openPrice) / point : (openPrice - currentPrice) / point; class=class="str">"cmt">//--- Calculate profit points if (profitPoints >= MinProfitPoints + TrailingPoints) { class=class="str">"cmt">//--- Check for trailing class="type">class="kw">double newSL = class="num">0.0; class=class="str">"cmt">//--- New SL variable if (positionType == POSITION_TYPE_BUY) { class=class="str">"cmt">//--- Buy position newSL = currentPrice - TrailingPoints * point; class=class="str">"cmt">//--- Calculate new SL } else { class=class="str">"cmt">//--- Sell position newSL = currentPrice + TrailingPoints * point; class=class="str">"cmt">//--- Calculate new SL } class="type">class="kw">double currentSL = PositionGetDouble(POSITION_SL); class=class="str">"cmt">//--- Get current SL if ((positionType == POSITION_TYPE_BUY && newSL > currentSL + point) || (positionType == POSITION_TYPE_SELL && newSL < currentSL - point)) { class=class="str">"cmt">//--- Check move condition if (obj_Trade.PositionModify(ticket, NormalizeDouble(newSL, _Digits), PositionGetDouble(POSITION_TP))) { class=class="str">"cmt">//--- Modify position positionList[i].trailingActive = true; class=class="str">"cmt">//--- Set trailing active isTrailing = true; class=class="str">"cmt">//--- Set global trailing } } } }
「把伦敦突破系统接回你自己的盘前逻辑」
盘前区间突破这套玩法,核心就是抓伦敦开盘前 3 小时(默认 03:00–08:00 服务器时间)的高低范围,等伦敦时段一开就挂突破单。原程序用 PositionInfo 结构体做模块化,控制面板能盯区间、价位和回撤,但评论区有人实测 1.5 个月只跑了 3 笔——大概率测试数据质量差或范围过大过小导致面板不刷新。 一个容易踩的坑:TimeCurrent() 返回的是交易服务器时间,不是你本地时间。有用户指出,只有当券商恒定 GMT+1 且跟随英国夏令时,原硬编码的 03:00 起算才准;冬令伦敦开 08:00 GMT,夏令 07:00 GMT,时区不对突破触发点就全偏了。外汇和贵金属杠杆高、波动猛,接这套系统前务必用高质量 Tick 数据回测。 下面这段是区间起算的时间骨架,逐行拆一下就能改你自己的会话窗口: const int PreLondonStartHour = 3; // 伦敦前固定开始小时 const int PreLondonStartMinute = 0; // 固定开始分钟 void CheckTradingConditions(datetime currentTime) { // 检查条件并下单的函数 MqlDateTime timeStruct; // 声明时间结构 TimeToStruct(currentTime, timeStruct); // 把当前时间拆进结构 datetime today = StringToTime(StringFormat("%04d.%02d.%02d", timeStruct.year, timeStruct.mon, timeStruct.day)); // 取当天 0 点 datetime preLondonStart = today + PreLondonStartHour * 3600 + PreLondonStartMinute * 60; // 算盘前起点秒数 datetime londonStart = today + LondonStartHour * 3600 + LondonStartMinute * 60; // 算伦敦开盘秒数 真要落地,先把 PreLondonStartHour 改成和你券商时区对齐的值,再开 MT5 用 2025 年数据跑一遍,看面板在午夜重置后能不能正常画出新方框。
const class="type">int PreLondonStartHour = class="num">3; class=class="str">"cmt">//--- 伦敦会议前的固定开始时间 const class="type">int PreLondonStartMinute = class="num">0; class=class="str">"cmt">//--- 固定的伦敦前起始分钟数 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 检查交易条件并下单 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CheckTradingConditions(class="type">class="kw">datetime currentTime) { class="type">MqlDateTime timeStruct; class=class="str">"cmt">//--- 时间结构 TimeToStruct(currentTime, timeStruct); class=class="str">"cmt">//--- 转换时间 class="type">class="kw">datetime today = StringToTime(StringFormat("%04d.%02d.%02d", timeStruct.year, timeStruct.mon, timeStruct.day)); class=class="str">"cmt">//--- 今天获取 class="type">class="kw">datetime preLondonStart = today + PreLondonStartHour * class="num">3600 + PreLondonStartMinute * class="num">60; class=class="str">"cmt">//--伦敦奥运会开幕前 class="type">class="kw">datetime londonStart = today + LondonStartHour * class="num">3600 + LondonStartMinute * class="num">60; class=class="str">"cmt">//--- 伦敦开始
◍ 把工具请下神坛
上面这段逻辑跑完,当天是否交易已经由伦敦前区间决定:preLondonRange 落在 MinRangePoints 与 MaxRangePoints 之间才放行挂单,否则直接 noTradeToday=true 并绘制区间后退出。外汇与贵金属属高杠杆品种,这种过滤只降低噪音,不消除方向误判概率。 UpdatePanel 只做一件事——把 LondonRangePoints 以「Range (points): N」格式写进 OBJPROP_TEXT,没算出时显示 Calculating...。你开 MT5 把这段塞进 EA,盯面板那行数字就能知道今日是否被自动禁交易。 代码是死的,盘面是活的。把它当校验自己手工划线的尺子,而不是替你下注的机器,才不会在连续止损后反噬节奏。
class="type">class="kw">datetime londonEnd = today + LondonEndHour * class="num">3600 + LondonEndMinute * class="num">60; class=class="str">"cmt">//--- 伦敦结束 analysisTime = londonStart; class=class="str">"cmt">//--- 设置分析时间 if (currentTime < analysisTime) class="kw">return; class=class="str">"cmt">//--- 如果在分析之前退出 class="type">class="kw">double preLondonRange = GetRange(preLondonStart, currentTime, PreLondonHigh, PreLondonLow, PreLondonHighTime, PreLondonLowTime); class=class="str">"cmt">//--- 获取范围 if (preLondonRange < MinRangePoints || preLondonRange > MaxRangePoints) { class=class="str">"cmt">//--- 检查范围限制 noTradeToday = true; class=class="str">"cmt">//--- 设置无交易 sessionChecksDone = true; class=class="str">"cmt">//--- 完成设置检查 DrawSessionRanges(preLondonStart, londonEnd); class=class="str">"cmt">//--- 绘制范围 class="kw">return; class=class="str">"cmt">//--- 退出 } LondonRangePoints = preLondonRange; class=class="str">"cmt">//--- 设置范围点 PlacePendingOrders(PreLondonHigh, PreLondonLow, today); class=class="str">"cmt">//--- 下订单 noTradeToday = true; class=class="str">"cmt">//--- 设置无交易 sessionChecksDone = true; class=class="str">"cmt">//--- 完成设置检查 DrawSessionRanges(preLondonStart, londonEnd); class=class="str">"cmt">//--- 绘制范围 } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">// 使用当前数据更新面板| class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void UpdatePanel() { class="type">class="kw">string rangeText = "Range(points): " + (LondonRangePoints > class="num">0 ? DoubleToString(LondonRangePoints, class="num">0) : "Calculating..."); class=class="str">"cmt">//--- 格式化范围文本 ObjectSetString(class="num">0, panelPrefix + "RangePoints", OBJPROP_TEXT, rangeText); class=class="str">"cmt">//-- 更新范围文本 class=class="str">"cmt">//--- }