//+------------------------------------------------------------------+ //| DR_BreakoutStraddle.mq5 | //| Copyright 2026, Jim Gray | //| /www.mql5.com/en/users/digitalrogue | //| | //|5-Minute Breakout Straddle EA | //|Places Buy Stop + Sell Stop with OCO logic | //|Configurable distance, TP/SL, time filter, spread filter, trail | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, Jim Gray" #property link "https://www.mql5.com/en/users/digitalrogue" #property version "1.10" #property strict #include CTrade trade; //+------------------------------------------------------------------+ //| Input parameters | //+------------------------------------------------------------------+ input group "=== Orders ===" input int StopDistancePips = 10; // Distance from price in pips input int TakeProfitPips = 20; // Take profit in pips input int StopLossPips = 10; // Stop loss in pips input int OrderExpireMinutes = 60; // Order expiration in minutes (0=none) input double MaxSpreadPips = 5.0; // Maximum spread to place orders input group "=== Time Filter ===" input bool UseTimeFilter = true; // Enable time filter input int StartHour = 7; // Trading start hour (broker time) input int StartMinute = 0; // Trading start minute input int EndHour = 20; // Trading end hour (broker time) input int EndMinute = 0; // Trading end minute input group "=== Trade ===" input double RiskPercent = 1.0; // Risk percent per trade input int MagicNumber = 20260416; // Magic number input int SlippagePoints = 10; // Max slippage input group "=== OCO ===" input bool EnableOCO = true; // Cancel opposite order on trigger input group "=== Trailing Stop ===" input bool UseTrailingStop = true; // Enable trailing stop input int TrailPips = 10; // Trail distance in pips input int TrailActivatePips = 10; // Activate trail after X pips profit input group "=== Display ===" input bool DrawOnChart = true; // Draw entry lines and HUD //+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ datetime g_lastBarTime = 0; ulong g_buyStopTicket = 0; ulong g_sellStopTicket = 0; bool g_ordersPlaced = false; bool g_tradeActive = false; double g_trailSL = 0; //+------------------------------------------------------------------+ //| Expert initialization | //+------------------------------------------------------------------+ int OnInit() { trade.SetExpertMagicNumber(MagicNumber); trade.SetDeviationInPoints(SlippagePoints); Comment("DR Breakout Straddle v1.10\nWaiting for next bar..."); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { Comment(""); ObjectDelete(0, "DR_BuyStopLine"); ObjectDelete(0, "DR_SellStopLine"); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Trailing stop runs on every tick, not just new bars if(UseTrailingStop && HasOpenPosition()) TrailStop(); // Check for new bar on M5 datetime currentBarTime = iTime(_Symbol, PERIOD_M5, 0); if(currentBarTime == g_lastBarTime) return; g_lastBarTime = currentBarTime; // Check if we have an open position for this EA g_tradeActive = HasOpenPosition(); // If trade is active, check OCO if(g_tradeActive && EnableOCO) { CancelPendingOrders(); g_ordersPlaced = false; } // If trade is active, just update HUD if(g_tradeActive) { UpdateHUD("Trade active (trailing)"); return; } // Trade closed, reset trail g_trailSL = 0; // Check time filter if(UseTimeFilter && !IsInTradingHours()) { CancelPendingOrders(); g_ordersPlaced = false; UpdateHUD("Outside trading hours"); return; } // Check spread double currentSpread = (SymbolInfoDouble(_Symbol, SYMBOL_ASK) - SymbolInfoDouble(_Symbol, SYMBOL_BID)) / _Point; double maxSpreadPoints = MaxSpreadPips * 10; if(currentSpread > maxSpreadPoints) { UpdateHUD("Spread too wide: " + DoubleToString(currentSpread / 10, 1) + " pips"); return; } // Check if pending orders expired if(g_ordersPlaced) { if(CheckOrderExpired()) { CancelPendingOrders(); g_ordersPlaced = false; UpdateHUD("Orders expired, waiting for next bar"); return; } // Orders still active, update HUD UpdateHUD("Orders placed, waiting for trigger"); return; } // Place new orders PlaceOrders(); } //+------------------------------------------------------------------+ //| Trade transaction handler | //+------------------------------------------------------------------+ void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result) { if(trans.type == TRADE_TRANSACTION_ORDER_DELETE) { // Order deleted - check if it's one of ours if(trans.order == g_buyStopTicket || trans.order == g_sellStopTicket) { // One of our pending orders was removed } } } //+------------------------------------------------------------------+ //| Place Buy Stop and Sell Stop orders | //+------------------------------------------------------------------+ void PlaceOrders() { double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double point = _Point; double pipSize = point * 10; // 5-digit broker double buyStopPrice = NormalizeDouble(ask + StopDistancePips * pipSize, _Digits); double sellStopPrice = NormalizeDouble(bid - StopDistancePips * pipSize, _Digits); double tp = TakeProfitPips * pipSize; double sl = StopLossPips * pipSize; double buyStopSL = NormalizeDouble(buyStopPrice - sl, _Digits); double buyStopTP = NormalizeDouble(buyStopPrice + tp, _Digits); double sellStopSL = NormalizeDouble(sellStopPrice + sl, _Digits); double sellStopTP = NormalizeDouble(sellStopPrice - tp, _Digits); // Calculate lot size based on risk double lots = CalculateLotSize(sl); if(lots <= 0) { UpdateHUD("Error: Cannot calculate lot size"); return; } // Set expiration datetime expiration = 0; if(OrderExpireMinutes > 0) expiration = TimeCurrent() + OrderExpireMinutes * 60; // Place Buy Stop if(!trade.BuyStop(lots, buyStopPrice, _Symbol, buyStopSL, buyStopTP, ORDER_TIME_GTC, 0, "DR_Breakout_BuyStop")) { Print("Buy Stop failed: ", GetLastError()); UpdateHUD("Buy Stop failed: " + IntegerToString(GetLastError())); return; } g_buyStopTicket = trade.ResultOrder(); // Place Sell Stop if(!trade.SellStop(lots, sellStopPrice, _Symbol, sellStopSL, sellStopTP, ORDER_TIME_GTC, 0, "DR_Breakout_SellStop")) { Print("Sell Stop failed: ", GetLastError()); // Cancel the buy stop we just placed trade.OrderDelete(g_buyStopTicket); g_buyStopTicket = 0; UpdateHUD("Sell Stop failed: " + IntegerToString(GetLastError())); return; } g_sellStopTicket = trade.ResultOrder(); g_ordersPlaced = true; // Draw lines if(DrawOnChart) { DrawLine("DR_BuyStopLine", buyStopPrice, clrLime); DrawLine("DR_SellStopLine", sellStopPrice, clrRed); } Print("Orders placed: BuyStop ", g_buyStopTicket, " @ ", buyStopPrice, " SellStop ", g_sellStopTicket, " @ ", sellStopPrice); UpdateHUD("Orders placed, waiting for trigger"); } //+------------------------------------------------------------------+ //| Cancel all pending orders for this EA | //+------------------------------------------------------------------+ void CancelPendingOrders() { // Cancel our specific orders by ticket if(g_buyStopTicket != 0) { if(OrderSelect(g_buyStopTicket)) { if(OrderGetString(ORDER_COMMENT) == "DR_Breakout_BuyStop" || OrderGetInteger(ORDER_MAGIC) == MagicNumber) trade.OrderDelete(g_buyStopTicket); } g_buyStopTicket = 0; } if(g_sellStopTicket != 0) { if(OrderSelect(g_sellStopTicket)) { if(OrderGetString(ORDER_COMMENT) == "DR_Breakout_SellStop" || OrderGetInteger(ORDER_MAGIC) == MagicNumber) trade.OrderDelete(g_sellStopTicket); } g_sellStopTicket = 0; } // Also cancel any pending orders by magic number (safety net) for(int i = OrdersTotal() - 1; i >= 0; i--) { ulong ticket = OrderGetTicket(i); if(ticket == 0) continue; if(OrderGetString(ORDER_SYMBOL) != _Symbol) continue; if(OrderGetInteger(ORDER_MAGIC) != MagicNumber) continue; long orderType = OrderGetInteger(ORDER_TYPE); if(orderType == ORDER_TYPE_BUY_STOP || orderType == ORDER_TYPE_SELL_STOP || orderType == ORDER_TYPE_BUY_LIMIT || orderType == ORDER_TYPE_SELL_LIMIT) { trade.OrderDelete(ticket); } } ObjectDelete(0, "DR_BuyStopLine"); ObjectDelete(0, "DR_SellStopLine"); } //+------------------------------------------------------------------+ //| Check if we have an open position for this EA | //+------------------------------------------------------------------+ bool HasOpenPosition() { for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; return true; } return false; } //+------------------------------------------------------------------+ //| Check if pending orders have expired | //+------------------------------------------------------------------+ bool CheckOrderExpired() { // If no expiration set, never expire if(OrderExpireMinutes <= 0) return false; // Check if our orders still exist bool buyStopExists = false; bool sellStopExists = false; for(int i = OrdersTotal() - 1; i >= 0; i--) { ulong ticket = OrderGetTicket(i); if(ticket == 0) continue; if(OrderGetString(ORDER_SYMBOL) != _Symbol) continue; if(OrderGetInteger(ORDER_MAGIC) != MagicNumber) continue; long orderType = OrderGetInteger(ORDER_TYPE); if(orderType == ORDER_TYPE_BUY_STOP) buyStopExists = true; if(orderType == ORDER_TYPE_SELL_STOP) sellStopExists = true; } // If neither order exists, they either triggered or were deleted if(!buyStopExists && !sellStopExists) { g_buyStopTicket = 0; g_sellStopTicket = 0; return true; } return false; } //+------------------------------------------------------------------+ //| Check if current time is within trading hours | //+------------------------------------------------------------------+ bool IsInTradingHours() { MqlDateTime dt; TimeCurrent(dt); int currentTime = dt.hour * 60 + dt.min; int startTime = StartHour * 60 + StartMinute; int endTime = EndHour * 60 + EndMinute; if(startTime < endTime) return (currentTime >= startTime && currentTime < endTime); else // wraps around midnight return (currentTime >= startTime || currentTime < endTime); } //+------------------------------------------------------------------+ //| Calculate lot size based on risk percentage | //+------------------------------------------------------------------+ double CalculateLotSize(double slDistance) { double balance = AccountInfoDouble(ACCOUNT_BALANCE); double riskAmount = balance * RiskPercent / 100.0; double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE); double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); if(tickValue == 0 || tickSize == 0 || slDistance == 0) return 0; double ticksInSL = slDistance / tickSize; double lossPerLot = ticksInSL * tickValue; if(lossPerLot == 0) return 0; double lots = riskAmount / lossPerLot; // Normalize to lot step double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); lots = MathFloor(lots / lotStep) * lotStep; lots = MathMax(lots, minLot); lots = MathMin(lots, maxLot); return NormalizeDouble(lots, 2); } //+------------------------------------------------------------------+ //| Trail stop loss behind profitable positions | //+------------------------------------------------------------------+ void TrailStop() { for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); double currentSL = PositionGetDouble(POSITION_SL); double currentTP = PositionGetDouble(POSITION_TP); long posType = PositionGetInteger(POSITION_TYPE); double pipSize = _Point * 10; double trailDist = TrailPips * pipSize; double activateDist = TrailActivatePips * pipSize; if(posType == POSITION_TYPE_BUY) { double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double profit = bid - openPrice; // Only trail after position is in profit by activation distance if(profit < activateDist) continue; double newSL = NormalizeDouble(bid - trailDist, _Digits); // Only move SL up, never down if(newSL > currentSL || currentSL == 0) { trade.PositionModify(ticket, newSL, currentTP); } } else if(posType == POSITION_TYPE_SELL) { double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double profit = openPrice - ask; // Only trail after position is in profit by activation distance if(profit < activateDist) continue; double newSL = NormalizeDouble(ask + trailDist, _Digits); // Only move SL down, never up (for sells) if(newSL < currentSL || currentSL == 0) { trade.PositionModify(ticket, newSL, currentTP); } } } } //+------------------------------------------------------------------+ //| Draw a horizontal line on the chart | //+------------------------------------------------------------------+ void DrawLine(string name, double price, color clr) { ObjectDelete(0, name); ObjectCreate(0, name, OBJ_HLINE, 0, 0, price); ObjectSetInteger(0, name, OBJPROP_COLOR, clr); ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DASH); ObjectSetInteger(0, name, OBJPROP_WIDTH, 1); ObjectSetInteger(0, name, OBJPROP_BACK, true); ObjectSetString(0, name, OBJPROP_TEXT, name); } //+------------------------------------------------------------------+ //| Update HUD on chart | //+------------------------------------------------------------------+ void UpdateHUD(string status) { if(!DrawOnChart) return; string hud = ""; hud += "══════ DR Breakout Straddle v1.10 ══════\n"; hud += "Status: " + status + "\n"; hud += "Stop Distance: " + IntegerToString(StopDistancePips) + " pips\n"; hud += "TP: " + IntegerToString(TakeProfitPips) + " | SL: " + IntegerToString(StopLossPips) + " pips\n"; hud += "Trail: " + (UseTrailingStop ? IntegerToString(TrailPips) + "pips after " + IntegerToString(TrailActivatePips) + "pips" : "OFF") + "\n"; hud += "Risk: " + DoubleToString(RiskPercent, 1) + "%\n"; if(g_tradeActive) { // Find our position for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; string dir = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY ? "BUY" : "SELL"; double profit = PositionGetDouble(POSITION_PROFIT); hud += "══════ ACTIVE TRADE ══════\n"; hud += "Direction: " + dir + "\n"; hud += "Entry: " + DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), _Digits) + "\n"; hud += "P/L: $" + DoubleToString(profit, 2) + "\n"; break; } } else if(g_ordersPlaced) { double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double pipSize = _Point * 10; hud += "══════ PENDING ══════\n"; hud += "Buy Stop: " + DoubleToString(ask + StopDistancePips * pipSize, _Digits) + "\n"; hud += "Sell Stop: " + DoubleToString(bid - StopDistancePips * pipSize, _Digits) + "\n"; } if(UseTimeFilter) { MqlDateTime dt; TimeCurrent(dt); hud += "Time: " + IntegerToString(dt.hour) + ":" + StringFormat("%02d", dt.min); hud += (IsInTradingHours() ? " ✅" : " ⛔") + "\n"; } Comment(hud); } //+------------------------------------------------------------------+