//+------------------------------------------------------------------+ //| FadePivot2_v4_Fixed.mq5 | //| Garfield Heron / Abbey Road Tech | //| https://abbeyroadtechnology.com | //+------------------------------------------------------------------+ #property copyright "2024" #property link "https://abbeyroadtechnology.com" #property version "2.10" #property strict /***** User Inputs *****/ input double LotSize = 0.01; // Lot size for trades input int Slippage = 3; // Max slippage in points input int TP_OffsetPips = 0; // +/- offset from R1 or S1 (in pips) input double StopLossFactor = 1.0; // Multiplier for (R2 - R1) or (S1 - S2) input double TakeProfitFactor = 1.0; // Multiplier for take-profit distance (0 = target R1/S1 exactly) input bool OnlyOneSetPerDay = true; // If true, place only once per day input bool AllowNewOrdersIfPreviousOpen = true; // Place new orders even if old remain input bool CloseEndOfDay = true; // If true, do EoD cleanup input bool CloseOnlyPendingEndOfDay = false; // If true => close only pending orders at EoD input bool CloseOpenOrdersEndOfDay = false; // If true, close open positions at EoD input long MagicNumber = 98765; // Magic number for this EA /***** Trailing-Stop Inputs *****/ input double TrailingStartPips = 10.0; // in profit at least this many pips to start trailing input double TrailingDistancePips = 5.0; // trailing distance behind best price /***** Day-of-Week Logic *****/ input bool TradeMonday = true; input bool TradeTuesday = true; input bool TradeWednesday = true; input bool TradeThursday = true; input bool TradeFriday = true; input bool TradeSaturday = false; input bool TradeSunday = false; /***** Global variables *****/ static datetime TradeDate = 0; // Tracks the current day static bool OrdersPlaced = false; static bool EodHandled = false; // Track if EOD already processed /***** Pivots *****/ static double P, R1, R2, S1, S2; /***** For multiple-trade trailing: track best price per position ticket *****/ #define MAX_POSITIONS 100 static ulong posTicketArray[MAX_POSITIONS]; static double bestPriceArray[MAX_POSITIONS]; static int posCount=0; // Prototypes void AddOrUpdateBestPrice(ulong ticket, long posType, double newPrice); double GetBestPrice(ulong ticket, long posType); void RemovePosition(ulong ticket); void CleanUpClosedPositions(); void CalculateDailyPivots(); void PlaceFadeOrders(); bool HasOpenOrPendingWithMagic(long magic); bool CloseAllByMagic(long magic); void PlaceLimitOrder(ENUM_ORDER_TYPE orderType, double entryPrice, double slPrice, double tpPrice); void ApplyTrailingStop(); bool IsTradingDay(); void ModifyPositionSL(long posType, double newSL, ulong ticket); bool ValidateLimitOrderPrice(ENUM_ORDER_TYPE orderType, double entryPrice); double GetStopLevel(); /*****===================================================================== * OnInit / OnDeinit *======================================================================*****/ int OnInit() { Print("FadePivot2_v4_Fixed EA initializing (multi-position trailing + day-of-week)..."); // Input validation if(LotSize <= 0) { Print("ERROR: LotSize must be positive"); return(INIT_FAILED); } if(StopLossFactor < 0) { Print("ERROR: StopLossFactor cannot be negative"); return(INIT_FAILED); } if(TakeProfitFactor < 0) { Print("ERROR: TakeProfitFactor cannot be negative"); return(INIT_FAILED); } if(TrailingStartPips < 0 || TrailingDistancePips < 0) { Print("ERROR: Trailing stop values cannot be negative"); return(INIT_FAILED); } if(TakeProfitFactor > 0 && TakeProfitFactor > StopLossFactor) { Print("WARNING: TakeProfitFactor > StopLossFactor may result in unfavorable R:R ratio"); } // Check if symbol supports the requested order types int fillingMode = (int)SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE); if(fillingMode == 0) { Print("WARNING: Symbol may not support requested order filling mode"); } return(INIT_SUCCEEDED); } void OnDeinit(const int reason) { Print("FadePivot2_v4_Fixed EA deinitialized. reason=", reason); posCount = 0; } /*****===================================================================== * OnTick *======================================================================*****/ void OnTick() { // 1) Check day-of-week if(!IsTradingDay()) return; // 2) Check for new D1 bar datetime today = iTime(_Symbol, PERIOD_D1, 0); if(today != TradeDate) { CalculateDailyPivots(); TradeDate = today; OrdersPlaced = false; EodHandled = false; // Reset EOD flag for new day Print("New daily bar. Pivots recalc. R2=", R2, " S2=", S2); CleanUpClosedPositions(); } // 3) Place daily fade orders if needed if(!OrdersPlaced) { if(!OnlyOneSetPerDay) { PlaceFadeOrders(); OrdersPlaced = true; } else { if(!HasOpenOrPendingWithMagic(MagicNumber) || AllowNewOrdersIfPreviousOpen) { PlaceFadeOrders(); OrdersPlaced = true; } else { Print("Skipping new orders (OneSetPerDay + existing trades)."); } } } // 4) End-of-day close logic (improved timing) if(CloseEndOfDay && !EodHandled) { MqlDateTime dt; TimeToStruct(TimeCurrent(), dt); // Check from 23:58 onwards to ensure we don't miss the window if(dt.hour == 23 && dt.min >= 58) { Print("FadePivot2_v4_Fixed: EoD => cleanup..."); CloseAllByMagic(MagicNumber); EodHandled = true; } } // Reset EOD flag at midnight if(EodHandled) { MqlDateTime dt; TimeToStruct(TimeCurrent(), dt); if(dt.hour == 0 && dt.min == 0) EodHandled = false; } // 5) Trailing Stop for multiple trades ApplyTrailingStop(); } /*****===================================================================== * IsTradingDay() *======================================================================*****/ bool IsTradingDay() { MqlDateTime mt; TimeToStruct(TimeCurrent(), mt); int dow = mt.day_of_week; // 0=Sun,1=Mon,2=Tue,3=Wed,4=Thu,5=Fri,6=Sat switch(dow) { case 0: return TradeSunday; case 1: return TradeMonday; case 2: return TradeTuesday; case 3: return TradeWednesday; case 4: return TradeThursday; case 5: return TradeFriday; case 6: return TradeSaturday; } return false; } /*****===================================================================== * CalculateDailyPivots *======================================================================*****/ void CalculateDailyPivots() { int shift = 1; // "yesterday" double prevHigh = iHigh(_Symbol, PERIOD_D1, shift); double prevLow = iLow(_Symbol, PERIOD_D1, shift); double prevClose= iClose(_Symbol, PERIOD_D1, shift); if(prevHigh == 0 || prevLow == 0 || prevClose == 0) { Print("ERROR: Failed to get previous day data for pivot calculation"); return; } P = (prevHigh + prevLow + prevClose)/3.0; R1 = 2.0*P - prevLow; S1 = 2.0*P - prevHigh; R2 = P + (prevHigh - prevLow); S2 = P - (prevHigh - prevLow); } /*****===================================================================== * GetStopLevel - helper to get minimum stop distance *======================================================================*****/ double GetStopLevel() { long stopLevelPoints = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL); if(stopLevelPoints <= 0) stopLevelPoints = 10; // Default to 10 points if broker returns 0 return stopLevelPoints * _Point; } /*****===================================================================== * ValidateLimitOrderPrice - ensure limit order is valid *======================================================================*****/ bool ValidateLimitOrderPrice(ENUM_ORDER_TYPE orderType, double entryPrice) { double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double stopLevel = GetStopLevel(); if(orderType == ORDER_TYPE_SELL_LIMIT) { if(entryPrice <= ask + stopLevel) { Print("ERROR: SELL LIMIT price (", entryPrice, ") must be above Ask (", ask, ") + stop level (", stopLevel, ")"); return false; } } else if(orderType == ORDER_TYPE_BUY_LIMIT) { if(entryPrice >= bid - stopLevel) { Print("ERROR: BUY LIMIT price (", entryPrice, ") must be below Bid (", bid, ") - stop level (", stopLevel, ")"); return false; } } return true; } /*****===================================================================== * PlaceFadeOrders *======================================================================*****/ void PlaceFadeOrders() { int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT); double stopLevel = GetStopLevel(); // SELL LIMIT at R2 { double entryPrice = NormalizeDouble(R2, digits); double distanceR1R2 = MathAbs(R2 - R1); // Validate entry price if(!ValidateLimitOrderPrice(ORDER_TYPE_SELL_LIMIT, entryPrice)) return; // SL: above R2 by distance * factor double slPrice = R2 + (distanceR1R2 * StopLossFactor); // FIX: TP calculation - use distance from entry, not multiply price // Target is R1 with optional offset, or use factor to scale the distance double tpPrice; if(TakeProfitFactor == 0) tpPrice = R1 + (TP_OffsetPips * pt); // Target R1 exactly with offset else tpPrice = R2 - (distanceR1R2 * TakeProfitFactor) + (TP_OffsetPips * pt); // Ensure SL is valid distance from entry if((slPrice - entryPrice) < stopLevel) { Print("WARNING: Sell SL too close, adjusting to stop level"); slPrice = entryPrice + stopLevel + pt; } // Ensure TP is valid distance from entry if((entryPrice - tpPrice) < stopLevel) { Print("WARNING: Sell TP too close, adjusting to stop level"); tpPrice = entryPrice - stopLevel - pt; } // Normalize prices slPrice = NormalizeDouble(slPrice, digits); tpPrice = NormalizeDouble(tpPrice, digits); Print("Placing SELL LIMIT at R2= ", entryPrice, ", SL= ", slPrice, ", TP= ", tpPrice); PlaceLimitOrder(ORDER_TYPE_SELL_LIMIT, entryPrice, slPrice, tpPrice); } // BUY LIMIT at S2 { double entryPrice = NormalizeDouble(S2, digits); double distanceS1S2 = MathAbs(S1 - S2); // Validate entry price if(!ValidateLimitOrderPrice(ORDER_TYPE_BUY_LIMIT, entryPrice)) return; // SL: below S2 by distance * factor double slPrice = S2 - (distanceS1S2 * StopLossFactor); // FIX: TP calculation - use distance from entry double tpPrice; if(TakeProfitFactor == 0) tpPrice = S1 + (TP_OffsetPips * pt); // Target S1 exactly with offset else tpPrice = S2 + (distanceS1S2 * TakeProfitFactor) + (TP_OffsetPips * pt); // Ensure SL is valid distance from entry if((entryPrice - slPrice) < stopLevel) { Print("WARNING: Buy SL too close, adjusting to stop level"); slPrice = entryPrice - stopLevel - pt; } // Ensure TP is valid distance from entry if((tpPrice - entryPrice) < stopLevel) { Print("WARNING: Buy TP too close, adjusting to stop level"); tpPrice = entryPrice + stopLevel + pt; } // Normalize prices slPrice = NormalizeDouble(slPrice, digits); tpPrice = NormalizeDouble(tpPrice, digits); Print("Placing BUY LIMIT at S2= ", entryPrice, ", SL= ", slPrice, ", TP= ", tpPrice); PlaceLimitOrder(ORDER_TYPE_BUY_LIMIT, entryPrice, slPrice, tpPrice); } } /*****===================================================================== * PlaceLimitOrder - helper with retry logic *======================================================================*****/ void PlaceLimitOrder(ENUM_ORDER_TYPE orderType, double entryPrice, double slPrice, double tpPrice) { MqlTradeRequest request; MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); request.action = TRADE_ACTION_PENDING; request.symbol = _Symbol; request.magic = MagicNumber; request.volume = LotSize; request.deviation = Slippage; request.type = orderType; request.price = entryPrice; request.sl = slPrice; request.tp = tpPrice; // Try different filling modes if(!OrderSend(request, result)) { // Try with RETURN filling mode if FOK fails request.type_filling = ORDER_FILLING_RETURN; if(!OrderSend(request, result)) { // Try IOC as last resort request.type_filling = ORDER_FILLING_IOC; OrderSend(request, result); } } request.type_time = ORDER_TIME_GTC; request.comment = (orderType == ORDER_TYPE_SELL_LIMIT) ? "FadePivot SELL" : "FadePivot BUY"; if(!OrderSend(request, result)) { int err = GetLastError(); Print(__FUNCTION__, ": OrderSend failed. LastErr=", err); // Retry logic for specific errors if(err == ERR_REQUOTE || err == ERR_PRICE_CHANGED || err == ERR_OFF_QUOTES) { Sleep(100); if(OrderSend(request, result)) { Print("Retry succeeded after error ", err); } } return; } if(result.retcode == TRADE_RETCODE_PLACED || result.retcode == TRADE_RETCODE_DONE) { Print("Limit order placed: type=", (orderType==ORDER_TYPE_SELL_LIMIT ? "SellLimit":"BuyLimit"), ", ticket=", result.order); } else { Print("Limit order failed. retcode=", result.retcode, ", lastErr=", GetLastError()); // Check for specific retcodes that might benefit from retry if(result.retcode == TRADE_RETCODE_REQUOTE || result.retcode == TRADE_RETCODE_PRICE_OFF) { Sleep(100); ZeroMemory(result); if(OrderSend(request, result) && (result.retcode == TRADE_RETCODE_PLACED || result.retcode == TRADE_RETCODE_DONE)) { Print("Retry succeeded. ticket=", result.order); } } } } /*****===================================================================== * ApplyTrailingStop - handles multiple trades *======================================================================*****/ void ApplyTrailingStop() { double trailingStart = TrailingStartPips * _Point; double trailingDist = TrailingDistancePips * _Point; uint totalPositions = PositionsTotal(); for(uint i=0; i < totalPositions; i++) { ulong posTicket = PositionGetTicket(i); if(posTicket == 0) continue; if(!PositionSelectByTicket(posTicket)) continue; string sym = PositionGetString(POSITION_SYMBOL); long mgc = PositionGetInteger(POSITION_MAGIC); if(sym != _Symbol || mgc != MagicNumber) continue; long posType = PositionGetInteger(POSITION_TYPE); double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); double currentSL = PositionGetDouble(POSITION_SL); double cpx = (posType==POSITION_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_BID) : SymbolInfoDouble(_Symbol, SYMBOL_ASK); double pipsInProfit=0.0; if(posType==POSITION_TYPE_BUY) pipsInProfit = (cpx - openPrice)/_Point; else pipsInProfit = (openPrice - cpx)/_Point; if(pipsInProfit < TrailingStartPips) continue; double oldBestPrice = GetBestPrice(posTicket, posType); bool improved = false; double newBest= oldBestPrice; if(posType == POSITION_TYPE_BUY) { if(oldBestPrice < 1e-8) { newBest= cpx; // Use current price (already in profit) improved= true; } else if(cpx > oldBestPrice) { newBest = cpx; improved= true; } } else { if(oldBestPrice < 1e-8) { newBest= cpx; improved= true; } else if(cpx < oldBestPrice) { newBest= cpx; improved= true; } } if(improved) AddOrUpdateBestPrice(posTicket, posType, newBest); double potentialSL = 0.0; if(posType==POSITION_TYPE_BUY) { potentialSL = newBest - trailingDist; // Only move SL up if(potentialSL > currentSL + (_Point*0.5)) { // Ensure new SL is not too close to current price if((cpx - potentialSL) >= GetStopLevel()) ModifyPositionSL(posType, potentialSL, posTicket); } } else // SELL { potentialSL = newBest + trailingDist; // Only move SL down if(currentSL < 1e-8 || potentialSL < currentSL - (_Point*0.5)) { // Ensure new SL is not too close to current price if((potentialSL - cpx) >= GetStopLevel()) ModifyPositionSL(posType, potentialSL, posTicket); } } } } /*****===================================================================== * ModifyPositionSL *======================================================================*****/ void ModifyPositionSL(long posType, double newSL, ulong ticket) { MqlTradeRequest req; MqlTradeResult res; ZeroMemory(req); ZeroMemory(res); req.action = TRADE_ACTION_SLTP; req.symbol = _Symbol; req.magic = MagicNumber; if(!PositionSelectByTicket(ticket)) return; double oldTP = PositionGetDouble(POSITION_TP); double vol = PositionGetDouble(POSITION_VOLUME); req.position = ticket; req.volume = vol; req.sl = NormalizeDouble(newSL, _Digits); req.tp = oldTP; req.comment = "TrailingStopUpdate"; if(!OrderSend(req, res)) { Print("ModifyPositionSL: OrderSend fail. code=", GetLastError()); return; } if(res.retcode==TRADE_RETCODE_DONE || res.retcode==TRADE_RETCODE_PLACED) Print("TrailingStopUpdate: new SL=", DoubleToString(newSL,_Digits), " for ticket=", ticket); else Print("TrailingStop retcode=", res.retcode, " err=", GetLastError()); } /*****===================================================================== * HasOpenOrPendingWithMagic - FIXED for hedging mode *======================================================================*****/ bool HasOpenOrPendingWithMagic(long magic) { // Check ALL positions (not just first - supports hedging mode) for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(PositionSelectByTicket(ticket)) { if(PositionGetInteger(POSITION_MAGIC) == magic && PositionGetString(POSITION_SYMBOL) == _Symbol) return true; } } // Check pending orders int totalOrders = (int)OrdersTotal(); for(int i=0; i= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(!PositionSelectByTicket(ticket)) continue; if(PositionGetInteger(POSITION_MAGIC) != magic || PositionGetString(POSITION_SYMBOL) != _Symbol) continue; long pType = PositionGetInteger(POSITION_TYPE); double vol = PositionGetDouble(POSITION_VOLUME); double cPx = (pType==POSITION_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_BID) : SymbolInfoDouble(_Symbol, SYMBOL_ASK); MqlTradeRequest clReq; MqlTradeResult clRes; ZeroMemory(clReq); ZeroMemory(clRes); clReq.action = TRADE_ACTION_DEAL; clReq.symbol = _Symbol; clReq.volume = vol; clReq.magic = magic; clReq.position = ticket; clReq.deviation = Slippage; clReq.type = (pType==POSITION_TYPE_BUY)? ORDER_TYPE_SELL: ORDER_TYPE_BUY; clReq.price = cPx; clReq.comment = "FadePivot2_v4 EoD Close"; if(!OrderSend(clReq, clRes)) { Print("CloseAllByMagic: close pos failed. err=", GetLastError()); success = false; } else if(clRes.retcode != TRADE_RETCODE_DONE) { Print("CloseAllByMagic: close pos retcode=", clRes.retcode); success = false; } else { Print("Closed position #", ticket, " EoD."); RemovePosition(ticket); } } } // Remove pending orders int totalOrders = (int)OrdersTotal(); for(int i=totalOrders-1; i>=0; i--) { ulong ticket = OrderGetTicket(i); if(ticket == 0) continue; if(!OrderSelect(ticket)) continue; long omagic = OrderGetInteger(ORDER_MAGIC); string sym = OrderGetString(ORDER_SYMBOL); long otype = OrderGetInteger(ORDER_TYPE); if(omagic != magic || sym != _Symbol) continue; if(otype == ORDER_TYPE_BUY_LIMIT || otype == ORDER_TYPE_SELL_LIMIT || otype == ORDER_TYPE_BUY_STOP || otype == ORDER_TYPE_SELL_STOP) { MqlTradeRequest odReq; MqlTradeResult odRes; ZeroMemory(odReq); ZeroMemory(odRes); odReq.action = TRADE_ACTION_REMOVE; odReq.order = ticket; odReq.symbol = _Symbol; odReq.magic = magic; odReq.comment= "FadePivot2_v4 EoD Remove"; if(!OrderSend(odReq, odRes)) { Print("CloseAllByMagic: remove order failed. err=", GetLastError()); success = false; } else if(odRes.retcode != TRADE_RETCODE_DONE) { Print("CloseAllByMagic: remove order retcode=", odRes.retcode); success = false; } else { Print("Removed pending order #", ticket, " EoD."); } } } return success; } /*****===================================================================== * Array-based "map" for best price handling (with overflow protection) *======================================================================*****/ void AddOrUpdateBestPrice(ulong ticket, long posType, double newPrice) { // Try to find existing for(int i=0; i= MAX_POSITIONS) { // Try cleanup first CleanUpClosedPositions(); if(posCount >= MAX_POSITIONS) { Print("ERROR: Position tracking array full! Cannot add ticket ", ticket); return; } } posTicketArray[posCount] = ticket; bestPriceArray[posCount] = newPrice; posCount++; } double GetBestPrice(ulong ticket, long posType) { for(int i=0; i=0; i--) { ulong storedTicket = posTicketArray[i]; bool found = false; uint totalPos = PositionsTotal(); for(uint p=0; p