//+------------------------------------------------------------------+ //| FadePivot2_v4.mq5 | //| Garfield Heron / Abbey Road Tech | //| https://abbeyroadtechnology.com | //+------------------------------------------------------------------+ #property copyright "© 2024" #property link "https://abbeyroadtechnology.com" #property version "2.01" #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 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; /***** Pivots *****/ static double P, R1, R2, S1, S2; /***** For multiple-trade trailing: track best price per position ticket *****/ /** We'll keep two parallel static arrays: - posTicketArray[] holds position tickets - bestPriceArray[] holds the best price so far for that ticket (highest for BUY, lowest for SELL). We only store up to some arbitrary max positions (e.g. 100). Each time we see a new position, we add if not found. Each time position is closed, we remove it from arrays to prevent stale data. */ #define MAX_POSITIONS 100 static ulong posTicketArray[MAX_POSITIONS]; static double bestPriceArray[MAX_POSITIONS]; static int posCount=0; // how many valid entries we have // Prototypes void AddOrUpdateBestPrice(ulong ticket, long posType, double newPrice); double GetBestPrice(ulong ticket, long posType); void RemovePosition(ulong ticket); /***** Additional prototypes *****/ 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); /*****===================================================================== * OnInit / OnDeinit *======================================================================*****/ int OnInit() { Print("FadePivot2_v4 EA initializing (multi-position trailing + day-of-week)..."); return(INIT_SUCCEEDED); } void OnDeinit(const int reason) { Print("FadePivot2_v4 EA deinitialized. reason=", reason); // optionally clear arrays 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; Print("New daily bar. Pivots recalc. R2=", R2, " S2=", S2); // Also, remove old positions from array if they are closed // (optional check below or do in EoD logic): 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 if(CloseEndOfDay) { MqlDateTime dt; TimeToStruct(TimeCurrent(), dt); if(dt.hour == 23 && dt.min == 59) { Print("FadePivot2_v4: EoD => cleanup..."); CloseAllByMagic(MagicNumber); } } // 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); P = (prevHigh + prevLow + prevClose)/3.0; R1 = 2.0*P - prevLow; S1 = 2.0*P - prevHigh; R2 = P + (prevHigh - prevLow); S2 = P - (prevHigh - prevLow); } /*****===================================================================== * PlaceFadeOrders *======================================================================*****/ void PlaceFadeOrders() { int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT); // SELL LIMIT at R2 { double entryPrice = NormalizeDouble(R2, digits); double distanceR1R2 = MathAbs(R2 - R1); double slPrice = R2 + (distanceR1R2 * StopLossFactor); double tpPrice = (R1 * TakeProfitFactor) + (TP_OffsetPips * pt); 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); double slPrice = S2 - (distanceS1S2 * StopLossFactor); double tpPrice = (S1 * TakeProfitFactor) + (TP_OffsetPips * pt); Print("Placing BUY LIMIT at S2= ", entryPrice, ", SL= ", slPrice, ", TP= ", tpPrice); PlaceLimitOrder(ORDER_TYPE_BUY_LIMIT, entryPrice, slPrice, tpPrice); } } /*****===================================================================== * PlaceLimitOrder - helper *======================================================================*****/ 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; request.type_filling = ORDER_FILLING_FOK; request.type_time = ORDER_TIME_GTC; request.comment = "FadePivot2_v4"; if(!OrderSend(request, result)) { Print(__FUNCTION__, ": OrderSend failed. LastErr=", GetLastError()); 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 retcode=", result.retcode, ", lastErr=", GetLastError()); } } /*****===================================================================== * ApplyTrailingStop - handles multiple trades *======================================================================*****/ void ApplyTrailingStop() { // 1) Convert trailing inputs from pips -> price distance double trailingStart = TrailingStartPips * _Point; double trailingDist = TrailingDistancePips * _Point; // 2) Loop all positions uint totalPositions = PositionsTotal(); for(uint i=0; i < totalPositions; i++) { ulong posTicket = PositionGetTicket(i); if(posTicket == 0) continue; // skip invalid if(!PositionSelectByTicket(posTicket)) continue; // filter by symbol + magic string sym = PositionGetString(POSITION_SYMBOL); long mgc = PositionGetInteger(POSITION_MAGIC); if(sym != _Symbol || mgc != MagicNumber) continue; // retrieve info long posType = PositionGetInteger(POSITION_TYPE); double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); double currentSL = PositionGetDouble(POSITION_SL); // current market price double cpx = (posType==POSITION_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_BID) : SymbolInfoDouble(_Symbol, SYMBOL_ASK); // pips in profit double pipsInProfit=0.0; if(posType==POSITION_TYPE_BUY) pipsInProfit = (cpx - openPrice)/_Point; else pipsInProfit = (openPrice - cpx)/_Point; // if not in enough profit, skip if(pipsInProfit < TrailingStartPips) continue; // track the best price so far for this ticket double oldBestPrice = GetBestPrice(posTicket, posType); // if it's a BUY => best price is max // if it's a SELL => best price is min bool improved = false; double newBest= oldBestPrice; if(posType == POSITION_TYPE_BUY) { // if oldBestPrice =0 => not tracked yet => set to openPrice initially if(oldBestPrice < 1e-8) { newBest= openPrice; improved= true; // or set to cpx if you prefer } // if cpx is higher than old best => we have new best if(cpx > oldBestPrice) { newBest = cpx; improved= true; } } else { // for SELL, oldBestPrice=0 => set to openPrice if(oldBestPrice < 1e-8) { newBest= openPrice; improved= true; } // if cpx < oldBest => we have new best if(cpx < oldBestPrice) { newBest= cpx; improved= true; } } // update if improved if(improved) AddOrUpdateBestPrice(posTicket, posType, newBest); // Now figure out the new trailing stop from best price // For a BUY, the trailing stop is (bestPrice - trailingDist) // For a SELL, the trailing stop is (bestPrice + trailingDist) double potentialSL = 0.0; if(posType==POSITION_TYPE_BUY) { potentialSL = newBest - trailingDist; // only move SL up if potentialSL > currentSL if(potentialSL > currentSL + (_Point*0.5)) // e.g. 0.5 pip buffer to reduce spam { ModifyPositionSL(posType, potentialSL, posTicket); } } else // SELL { potentialSL = newBest + trailingDist; // only move SL down if potentialSL < currentSL if(currentSL < 1e-8 || potentialSL < currentSL - (_Point*0.5)) { 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; req.type = (posType==POSITION_TYPE_BUY)? ORDER_TYPE_BUY : ORDER_TYPE_SELL; // retrieve old TP, volume if(!PositionSelectByTicket(ticket)) return; // just in case double oldTP = PositionGetDouble(POSITION_TP); double vol = PositionGetDouble(POSITION_VOLUME); req.position = ticket; req.volume = vol; req.sl = newSL; 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 *======================================================================*****/ bool HasOpenOrPendingWithMagic(long magic) { // check open positions if(PositionSelect(_Symbol)) { if((long)PositionGetInteger(POSITION_MAGIC) == magic) return true; } // check pending int totalOrders = (int)OrdersTotal(); for(int i=0; i=0; i--) { ulong ticket = OrderGetTicket(i); if(OrderSelect(ticket)) { long omagic = OrderGetInteger(ORDER_MAGIC); string sym = OrderGetString(ORDER_SYMBOL); long otype = OrderGetInteger(ORDER_TYPE); if(omagic == magic && sym == _Symbol) { 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) || odRes.retcode != TRADE_RETCODE_DONE) { Print("CloseAllByMagic: remove order retcode=", odRes.retcode, ", lastErr=", GetLastError()); success = false; } else { Print("Removed pending order #", ticket, " EoD."); } } } } } return success; } /*****===================================================================== * Array-based "map" for best price handling *======================================================================*****/ void AddOrUpdateBestPrice(ulong ticket, long posType, double newPrice) { // For BUY, newPrice is a new "max price so far" // For SELL, newPrice is a new "min price so far" // try to find the ticket in posTicketArray for(int i=0; i add (if we have space) if(posCount < MAX_POSITIONS) { posTicketArray[posCount] = ticket; bestPriceArray[posCount] = newPrice; posCount++; } } double GetBestPrice(ulong ticket, long posType) { // returns stored best price or 0 if not found for(int i=0; i=0; i--) { ulong storedTicket = posTicketArray[i]; // check if there's still a position with this ticket bool found = false; uint totalPos = PositionsTotal(); for(uint p=0; p