// ★★★ EA_SingleLogic_LongShort ★★★ //--------------------------------------------- // (1) Input parameters //--------------------------------------------- // --- Parameters for Magic_A input double SARStep = 0.001; // SAR acceleration factor increment input double SARMaxStep = 0.2; // Maximum SAR acceleration factor input double TPPips = 90; // Take Profit in pips input double SLPips = 70; // Stop Loss in pips // Retry control parameters extern int RetryCount = 3; // Number of retry attempts extern int RetryIntervalMs = 1000; // Retry interval (milliseconds) // --- Trailing stop settings input bool UseTrailingStop = true; // true = enable trailing stop input int TrailStepProfit = 30; // Trail activation profit (pips) input int TrailShiftPips = 5; // Shift TP and SL by this many pips // --- Fixed lot or compound lot selection input bool UseCompoundLots = true; // true = compound, false = fixed lot input double TargetMarginPercent = 5.0; // Target margin usage (% of balance) extern double MarginLevelAlert = 250.0; // Margin level alert threshold (%) // --- Trading hour restriction input bool UseTradingHour = true; // true = restrict trading hours input int TradingStartUTC = 7; // Trading start hour (UTC) input int TradingEndUTC = 19; // Trading end hour (UTC, exclusive) // --- EA status notification settings extern bool NotifyEAStatus = true; // true = send status notifications extern int StatusIntervalMin = 60; // Notification interval (minutes) // --- Other settings input double MaxSpreadPips = 3.0; // Maximum allowed spread (pips) input int SlippagePips = 3; // Slippage (pips) input double LotSize = 0.1; // Fixed lot size (for non-compound mode) input int MaxOpenBuyPositions = 1; // Maximum BUY positions input int MaxOpenSellPositions = 1; // Maximum SELL positions #define BROKER_GMT_OFFSET_HOURS 2 //--------------------------------------------- // (2) Magic number //--------------------------------------------- input int Magic_A = 67890; //--------------------------------------------- // (3) Internal state variables //--------------------------------------------- datetime prevTime_A = 0; datetime lastEntryBarA = 0; // Bar time of the last entry (Magic_A) datetime lastStatusSend = 0; // Last EA status notification time bool EmergencyStop = false; // Emergency stop flag (internal) bool EmergencyLatched = false; // Indicates if an emergency has ever occurred // Exit latch counters int buyExitTickCount = 0; int sellExitTickCount = 0; const int ExitTickThreshold = 5; // Required consecutive ticks //--------------------------------------------- // (4) ENUM: Exit / Entry signals //--------------------------------------------- enum SignalType { NO_SIGNAL = 0, BUY_ENTRY, BUY_EXIT, SELL_ENTRY, SELL_EXIT }; //--------------------------------------------- // (5) Time utility functions (DST / UTC / trading hours) //--------------------------------------------- // Assumes broker server time is UTC+2 (winter) / UTC+3 (summer) // --- Daylight Saving Time detection (simplified, Northern Hemisphere) bool IsDST(datetime t) { int year = TimeYear(t); // Last Sunday of March datetime marchLastSunday = StrToTime(StringFormat("%d.03.%d 02:00", year, 31)); while (TimeDayOfWeek(marchLastSunday) != 0) marchLastSunday -= 86400; // Last Sunday of October datetime octLastSunday = StrToTime(StringFormat("%d.10.%d 03:00", year, 31)); while (TimeDayOfWeek(octLastSunday) != 0) octLastSunday -= 86400; return (t >= marchLastSunday && t < octLastSunday); } // --- Get current UTC hour (with DST adjustment) int GetUTCHour() { datetime t = TimeCurrent(); int hour = TimeHour(t) - BROKER_GMT_OFFSET_HOURS; if (IsDST(t)) hour -= 1; // Adjust for DST if (hour < 0) hour += 24; return hour; } // --- Trading hour check (can be enabled or disabled) bool IsTradingHour() { if (!UseTradingHour) return true; int utcHour = GetUTCHour(); return (utcHour >= TradingStartUTC && utcHour < TradingEndUTC); } //--------------------------------------------- // (6) New bar detection //--------------------------------------------- bool IsNewBar(int timeframe, datetime &lastBarTime) { datetime currentBar = iTime(Symbol(), timeframe, 0); if (currentBar != lastBarTime) { lastBarTime = currentBar; return true; } return false; } //--------------------------------------------- // (7) Pip size calculation //--------------------------------------------- double Pip() { return (Digits == 5 || Digits == 3) ? Point * 10 : Point; } //--------------------------------------------- // Convert pips to points (for slippage) int PipToPoint(double pips) { return (int)MathRound(pips * Pip() / Point); } //--------------------------------------------- // (8) Lot size calculation: compound mode or fixed-lot mode //--------------------------------------------- double CalculateLotSize() { if (!UseCompoundLots) { // Fixed-lot mode double SlotStep = MarketInfo(_Symbol, MODE_LOTSTEP); double SminLot = MarketInfo(_Symbol, MODE_MINLOT); double SmaxLot = MarketInfo(_Symbol, MODE_MAXLOT); double fixedLot = LotSize; // Clamp to broker limits double SadjustedLotSize = MathMax(SminLot, MathMin(fixedLot, SmaxLot)); // Align to lot step SadjustedLotSize = MathFloor(SadjustedLotSize / SlotStep) * SlotStep; return SadjustedLotSize; } // Compound mode (margin-based) double accountBalance = AccountBalance(); double ClotStep = MarketInfo(_Symbol, MODE_LOTSTEP); double CminLot = MarketInfo(_Symbol, MODE_MINLOT); double CmaxLot = MarketInfo(_Symbol, MODE_MAXLOT); double marginRequiredPerLot = MarketInfo(_Symbol, MODE_MARGINREQUIRED); // ------------------------------------------------------- // Defensive: if margin information cannot be obtained due to broker specifications or symbol limitations, // place an order using the symbol's minimum lot and log a warning. // ------------------------------------------------------- if (marginRequiredPerLot <= 0.0) { PrintFormat( "⚠ CalculateLotSize: invalid MODE_MARGINREQUIRED (%.5f). " "Fallback to MINLOT=%.2f", marginRequiredPerLot, CminLot ); return CminLot; } double targetMargin = accountBalance * TargetMarginPercent / 100.0; double maxLotForMargin = targetMargin / marginRequiredPerLot; double calculatedLotSize = MathFloor(maxLotForMargin / ClotStep) * ClotStep; double CadjustedLotSize = MathMax(CminLot, MathMin(calculatedLotSize, CmaxLot)); return CadjustedLotSize; } //--------------------------------------------- // (9) Margin level check (anti-spam notification) //--------------------------------------------- void CheckMarginLevel() { // Alert state (static to persist across ticks) static bool alerted = false; // No positions → no alert if (OrdersTotal() == 0) { alerted = false; return; } double margin = AccountMargin(); if (margin == 0) { alerted = false; return; } double marginLevel = (AccountEquity() / margin) * 100.0; // Alert trigger if (marginLevel <= MarginLevelAlert && !alerted) { string timeStr = TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS); string msg = "[EA_SingleLogic_LongShort]\n" "● Margin level below threshold ●\n" "Symbol: " + Symbol() + "\n" "Time: " + timeStr + "\n" "Threshold: " + DoubleToString(MarginLevelAlert, 1) + "%\n" "Current: " + DoubleToString(marginLevel, 1) + "%"; Print(msg); SendNotification(msg); alerted = true; } // Reset alert once margin recovers if (marginLevel > MarginLevelAlert) { alerted = false; } } //--------------------------------------------- // (10) EA runtime status notification //--------------------------------------------- void CheckEAStatus() { if (!NotifyEAStatus) return; if (TimeCurrent() - lastStatusSend >= StatusIntervalMin * 60) { string msg = "[EA_SingleLogic_LongShort is running]\n" + "Time: " + TimeToString(TimeCurrent(), TIME_SECONDS); SendNotification(msg); Print(msg); lastStatusSend = TimeCurrent(); } } //--------------------------------------------- // (11) Count open positions by type // type: OP_BUY or OP_SELL //--------------------------------------------- int CountOrdersByTypeThisEA(int type) { int count = 0; for (int i = OrdersTotal() - 1; i >= 0; i--) { if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) { if (OrderSymbol() == Symbol() && OrderMagicNumber() == Magic_A && OrderType() == type) { count++; } } } return count; } //--------------------------------------------- // (12) Check if a new BUY order can be placed // (emergency stop aware) //--------------------------------------------- bool CanPlaceNewBuy() { if (EmergencyStop) return false; return CountOrdersByTypeThisEA(OP_BUY) < MaxOpenBuyPositions; } //--------------------------------------------- // (13) Check if a new SELL order can be placed // (emergency stop aware) //--------------------------------------------- bool CanPlaceNewSell() { if (EmergencyStop) return false; return CountOrdersByTypeThisEA(OP_SELL) < MaxOpenSellPositions; } //--------------------------------------------- // (14) Exit signal logic (BUY and SELL) //--------------------------------------------- SignalType ExitSignalA() { double SAR0 = iSAR(_Symbol, 0, SARStep, SARMaxStep, 0); double SAR1 = iSAR(_Symbol, 0, SARStep, SARMaxStep, 1); double SAR2 = iSAR(_Symbol, 0, SARStep, SARMaxStep, 2); // BUY exit conditions if ((Low[1] > SAR1 && Close[0] <= SAR0) || (High[2] < SAR2 && High[1] < SAR1 && High[0] < SAR0)) { buyExitTickCount++; } else { buyExitTickCount = 0; } // SELL exit conditions if ((High[1] < SAR1 && Close[0] >= SAR0) || (Low[2] > SAR2 && Low[1] > SAR1 && Low[0] > SAR0)) { sellExitTickCount++; } else { sellExitTickCount = 0; } // Return EXIT signal once latch threshold is reached if (buyExitTickCount >= ExitTickThreshold) return BUY_EXIT; if (sellExitTickCount >= ExitTickThreshold) return SELL_EXIT; return NO_SIGNAL; } //--------------------------------------------- // (15) Entry signal logic (BUY and SELL) //--------------------------------------------- SignalType EntrySignalA() { double SAR1 = iSAR(_Symbol, 0, SARStep, SARMaxStep, 1); double SAR2 = iSAR(_Symbol, 0, SARStep, SARMaxStep, 2); double SAR3 = iSAR(_Symbol, 0, SARStep, SARMaxStep, 3); // BUY entry conditions if ((High[2] < SAR2 && Close[1] >= SAR1) || (Low[3] > SAR3 && Low[2] > SAR2 && Low[1] > SAR1)) { return BUY_ENTRY; } // SELL entry conditions if ((Low[2] > SAR2 && Close[1] <= SAR1) || (High[3] < SAR3 && High[2] < SAR2 && High[1] < SAR1)) { return SELL_ENTRY; } return NO_SIGNAL; } // --------------------------------------------- // (16) RetryManager (single-task model) // --------------------------------------------- // Retry task types enum RetryTaskType { TASK_NONE = 0, TASK_ENTRY, TASK_EXIT, TASK_MODIFY }; // RetryTask structure struct RetryTask { RetryTaskType taskType; int ticket; // Used for Exit / Modify int orderType; // Entry: OP_BUY / OP_SELL double lots; double price; // Stored entry price (actual execution uses market price) double sl; double tp; int slippage; int retryLeft; int lastAttemptMs; }; // Global: single active retry task RetryTask currentTask = { TASK_NONE }; // --------------------------- // (17) EXIT-only queue (minimal implementation) // --------------------------- #define MAX_EXIT_QUEUE 20 RetryTask exitQueue[MAX_EXIT_QUEUE]; int exitQueueSize = 0; bool PushExitQueue(const RetryTask &t) { if (exitQueueSize >= MAX_EXIT_QUEUE) { Print("❌ EXIT queue overflow"); return false; } exitQueue[exitQueueSize++] = t; return true; } // EXIT queue helper functions bool HasExitQueue() { return (exitQueueSize > 0); } RetryTask PopExitQueue() { RetryTask t = exitQueue[0]; for (int i = 1; i < exitQueueSize; i++) exitQueue[i - 1] = exitQueue[i]; exitQueueSize--; return t; } // --------------------------- // (18) ResetCurrentTask // --------------------------- void ResetCurrentTask() { currentTask.taskType = TASK_NONE; currentTask.ticket = 0; currentTask.orderType = 0; currentTask.lots = 0.0; currentTask.price = 0.0; currentTask.sl = 0.0; currentTask.tp = 0.0; currentTask.slippage = 0; currentTask.retryLeft = 0; currentTask.lastAttemptMs = 0; } // --------------------------- // (19) RegisterRetryTask // --------------------------- bool RegisterRetryTask(const RetryTask &task) { if (currentTask.taskType != TASK_NONE) { PrintFormat( "[RegisterRetryTask] Skip: another task active (exist=%d new=%d)", currentTask.taskType, task.taskType ); return false; } currentTask = task; currentTask.lastAttemptMs = 0; // Attempt immediately on next tick PrintFormat( "[RegisterRetryTask] Registered task type=%d ticket=%d", currentTask.taskType, currentTask.ticket ); return true; } // --------------------------- // (20) ProcessRetryTask // --------------------------- void ProcessRetryTask() { if (currentTask.taskType == TASK_NONE) return; int now = GetTickCount(); // Wait until retry interval has elapsed if (currentTask.lastAttemptMs != 0 && now - currentTask.lastAttemptMs < RetryIntervalMs) { return; } RefreshRates(); bool success = false; int err = 0; // ============================ // TASK_ENTRY // ============================ if (currentTask.taskType == TASK_ENTRY) { // Always use the latest market price for entry double execPrice = (currentTask.orderType == OP_BUY) ? Ask : Bid; // Recalculate SL / TP based on execution price double newSL, newTP; if (currentTask.orderType == OP_BUY) { newSL = NormalizeDouble(execPrice - SLPips * Pip(), Digits); newTP = NormalizeDouble(execPrice + TPPips * Pip(), Digits); } else { newSL = NormalizeDouble(execPrice + SLPips * Pip(), Digits); newTP = NormalizeDouble(execPrice - TPPips * Pip(), Digits); } int ticket = OrderSend( Symbol(), currentTask.orderType, currentTask.lots, execPrice, currentTask.slippage, newSL, newTP, "Magic_A Entry", Magic_A, 0, clrOrange ); if (ticket > 0) { success = true; PrintFormat("✅ [Retry] Entry success: ticket=%d", ticket); lastEntryBarA = iTime(Symbol(), PERIOD_CURRENT, 0); } else { err = GetLastError(); PrintFormat( "❌ [Retry] Entry failed err=%d retryLeft=%d", err, currentTask.retryLeft ); } } // ============================ // TASK_EXIT // ============================ else if (currentTask.taskType == TASK_EXIT) { if (!OrderSelect(currentTask.ticket, SELECT_BY_TICKET)) { // Order is already closed success = true; PrintFormat( "ℹ [Retry] EXIT: ticket %d no longer exists -> treat as success", currentTask.ticket ); } else { // Defensive handling for invalid lot size if (currentTask.lots <= 0.0) currentTask.lots = OrderLots(); int otype = OrderType(); double closePrice = (otype == OP_BUY) ? Bid : Ask; bool closed = OrderClose( currentTask.ticket, currentTask.lots, closePrice, currentTask.slippage, clrOrange ); if (closed) { success = true; PrintFormat( "✅ [Retry] Exit success ticket=%d", currentTask.ticket ); } else { err = GetLastError(); PrintFormat( "❌ [Retry] Exit failed ticket=%d err=%d retryLeft=%d", currentTask.ticket, err, currentTask.retryLeft ); } } } // ============================ // TASK_MODIFY // ============================ else if (currentTask.taskType == TASK_MODIFY) { if (!OrderSelect(currentTask.ticket, SELECT_BY_TICKET)) { // Order no longer exists success = true; PrintFormat( "ℹ [Retry] MODIFY: ticket %d missing -> treat as success", currentTask.ticket ); } else { double currOpen = OrderOpenPrice(); // Defensive correction for SL / TP values double correctedSL = (currentTask.sl <= 0) ? OrderStopLoss() : currentTask.sl; double correctedTP = (currentTask.tp <= 0) ? OrderTakeProfit() : currentTask.tp; bool modified = OrderModify( currentTask.ticket, currOpen, correctedSL, correctedTP, 0, clrAqua ); if (modified) { success = true; PrintFormat( "✅ [Retry] Modify success ticket=%d", currentTask.ticket ); } else { err = GetLastError(); PrintFormat( "❌ [Retry] Modify failed ticket=%d err=%d retryLeft=%d", currentTask.ticket, err, currentTask.retryLeft ); } } } // ============================ // Success handling // ============================ if (success) { ResetCurrentTask(); return; } // ============================ // Error handling (retry or failure) // ============================ bool transient = ( err == ERR_SERVER_BUSY || err == ERR_TRADE_CONTEXT_BUSY || err == ERR_PRICE_CHANGED || err == ERR_OFF_QUOTES || err == ERR_REQUOTE ); if (transient && currentTask.retryLeft > 0) { currentTask.retryLeft--; currentTask.lastAttemptMs = now; PrintFormat( "[Retry] transient error %d → retryLeft=%d", err, currentTask.retryLeft ); return; } PrintFormat( "[Retry] Task FAILED: type=%d ticket=%d err=%d", currentTask.taskType, currentTask.ticket, err ); ResetCurrentTask(); } //--------------------------------------------- // (21) Exit handling (supports both BUY and SELL) //--------------------------------------------- void HandleExitA(SignalType sig) { int slippagePoints = PipToPoint(SlippagePips); // ======================= // BUY EXIT // ======================= if (sig == BUY_EXIT) { for (int i = OrdersTotal() - 1; i >= 0; i--) { if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue; if (OrderMagicNumber() == Magic_A && OrderSymbol() == _Symbol && OrderType() == OP_BUY) { RefreshRates(); // Fetch latest market prices double BUYclosePrice = Bid; RetryTask t; t.taskType = TASK_EXIT; t.ticket = OrderTicket(); t.lots = OrderLots(); t.price = BUYclosePrice; // ← added: reference close price t.slippage = slippagePoints; t.retryLeft = RetryCount; if (PushExitQueue(t)) PrintFormat("📌 BUY EXIT queued (ticket=%d)", t.ticket); } } return; } // ======================= // SELL EXIT // ======================= if (sig == SELL_EXIT) { for (int m = OrdersTotal() - 1; m >= 0; m--) { if (!OrderSelect(m, SELECT_BY_POS, MODE_TRADES)) continue; if (OrderMagicNumber() == Magic_A && OrderSymbol() == _Symbol && OrderType() == OP_SELL) { RefreshRates(); // Fetch latest market prices double SELLclosePrice = Ask; RetryTask t; t.taskType = TASK_EXIT; t.ticket = OrderTicket(); t.lots = OrderLots(); t.price = SELLclosePrice; // ← added: reference close price t.slippage = slippagePoints; t.retryLeft = RetryCount; if (PushExitQueue(t)) PrintFormat("📌 SELL EXIT queued (ticket=%d)", t.ticket); } } } } //--------------------------------------------- // (22) Entry handling (supports both BUY and SELL) //--------------------------------------------- void HandleEntryA(SignalType sig) { if (sig == NO_SIGNAL) return; // 🚨 Block new entries while in emergency stop mode if (EmergencyStop) { Print("🚫 EmergencyStop active. New entry is blocked: ", sig); return; } datetime currentBarTime = iTime(Symbol(), PERIOD_CURRENT, 0); // ▼ Second entry within the same bar → abnormal condition if (lastEntryBarA == currentBarTime) { EmergencyCloseAll("Second entry detected within the same bar (abnormal)"); return; } if (!IsTradingHour()) return; // Declare once and reuse double orderLot; // ======================= // BUY ENTRY // ======================= if (sig == BUY_ENTRY) { double BUYspreadPips = (Ask - Bid) / Pip(); if (BUYspreadPips > MaxSpreadPips) { PrintFormat( "❌ Spread too high (%.1f pips). Skip BUY entry.", BUYspreadPips ); return; } if (!CanPlaceNewBuy()) return; double BUYprice = Ask; double BUYsl = NormalizeDouble(BUYprice - SLPips * Pip(), Digits); double BUYtp = NormalizeDouble(BUYprice + TPPips * Pip(), Digits); int BUYslippagePoints = PipToPoint(SlippagePips); // --- Retry-based entry (no Sleep, task registered to OnTick) --- RefreshRates(); // Update prices BUYprice = Ask; // Refresh execution price orderLot = CalculateLotSize(); if (orderLot <= 0) { Print("[HandleEntryA] calculated lot <= 0. Fallback to LotSize param."); orderLot = LotSize; } // === Register task to RetryManager === RetryTask t; t.taskType = TASK_ENTRY; t.orderType = OP_BUY; t.lots = orderLot; t.price = BUYprice; t.sl = BUYsl; t.tp = BUYtp; t.slippage = BUYslippagePoints; t.retryLeft = RetryCount; if (RegisterRetryTask(t)) { PrintFormat( "📌 BUY ENTRY task registered. lot=%.2f sl=%.5f tp=%.5f", orderLot, BUYsl, BUYtp ); } else { Print("⚠ BUY ENTRY task registration skipped (another task active)"); } // BUY ends here (actual execution is handled in OnTick) return; } // ======================= // SELL ENTRY // ======================= if (sig == SELL_ENTRY) { double SELLspreadPips = (Ask - Bid) / Pip(); if (SELLspreadPips > MaxSpreadPips) { PrintFormat( "❌ Spread too high (%.1f pips). Skip SELL entry.", SELLspreadPips ); return; } if (!CanPlaceNewSell()) return; double SELLprice = Bid; // SELL uses Bid price double SELLsl = NormalizeDouble(SELLprice + SLPips * Pip(), Digits); double SELLtp = NormalizeDouble(SELLprice - TPPips * Pip(), Digits); int SELLslippagePoints = PipToPoint(SlippagePips); // --- Retry-based entry (no Sleep, task registered to OnTick) --- RefreshRates(); SELLprice = Bid; // Refresh execution price orderLot = CalculateLotSize(); if (orderLot <= 0) { Print("[HandleEntryA] calculated lot <= 0. Fallback to LotSize param."); orderLot = LotSize; } // === Register task to RetryManager === RetryTask t; t.taskType = TASK_ENTRY; t.orderType = OP_SELL; t.lots = orderLot; t.price = SELLprice; t.sl = SELLsl; t.tp = SELLtp; t.slippage = SELLslippagePoints; t.retryLeft = RetryCount; if (RegisterRetryTask(t)) { PrintFormat( "📌 SELL ENTRY task registered. lot=%.2f sl=%.5f tp=%.5f", orderLot, SELLsl, SELLtp ); } else { Print("⚠ SELL ENTRY task registration skipped (another task active)"); } // SELL ends here (actual execution is handled in OnTick) return; } } //--------------------------------------------- // (23) Step-based trailing stop //--------------------------------------------- void ApplyTrailingStop() { if (!UseTrailingStop) return; // ❗ Do not apply trailing while another retry task is active if (currentTask.taskType != TASK_NONE) return; for (int i = OrdersTotal() - 1; i >= 0; i--) { if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue; if (OrderSymbol() != Symbol()) continue; if (OrderMagicNumber() != Magic_A) continue; int type = OrderType(); if (type != OP_BUY && type != OP_SELL) continue; int ticket = OrderTicket(); double openPrice = OrderOpenPrice(); double oldSL = OrderStopLoss(); double oldTP = OrderTakeProfit(); double currentPrice = (type == OP_BUY) ? Bid : Ask; // Floating profit in pips double profitPips = (currentPrice - openPrice) / Pip(); if (type == OP_SELL) profitPips = -profitPips; // Minimum trailing condition not met if (profitPips < TrailStepProfit) continue; // =============================== // 🔑 Calculate trailing step count // =============================== int stepCount = (int)(profitPips / TrailStepProfit); double tgtSL, tgtTP, newSL, newTP; if (type == OP_BUY) { tgtSL = openPrice + (stepCount * TrailShiftPips) * Pip(); tgtTP = openPrice + (stepCount * TrailShiftPips + TPPips) * Pip(); newSL = (oldSL < tgtSL) ? tgtSL : oldSL; newTP = (oldTP < tgtTP) ? tgtTP : oldTP; } else { tgtSL = openPrice - (stepCount * TrailShiftPips) * Pip(); tgtTP = openPrice - (stepCount * TrailShiftPips + TPPips) * Pip(); newSL = (oldSL > tgtSL) ? tgtSL : oldSL; newTP = (oldTP > tgtTP) ? tgtTP : oldTP; } newSL = NormalizeDouble(newSL, Digits); newTP = NormalizeDouble(newTP, Digits); // Skip update if the change is negligible if (MathAbs(oldSL - newSL) <= Point * 0.5 && MathAbs(oldTP - newTP) <= Point * 0.5) { continue; } // ❗ Only one modify task can be registered at a time if (currentTask.taskType != TASK_NONE) return; // 🚀 Register MODIFY task to RetryManager (executed on OnTick) RetryTask t; t.taskType = TASK_MODIFY; t.ticket = ticket; t.sl = newSL; t.tp = newTP; t.retryLeft = RetryCount; t.lastAttemptMs = 0; if (RegisterRetryTask(t)) { PrintFormat( "🔧 StepTrailing MODIFY registered. ticket=%d step=%d sl=%.5f tp=%.5f", ticket, stepCount, newSL, newTP ); } // ❗ Register only one task per tick return; } } //--------------------------------------------- // (24) Fail-safe abnormal state detection //--------------------------------------------- void CheckForAbnormalState() { // 🚨 If emergency stop has already been latched, do nothing // (reset only on EA restart) if (EmergencyLatched) return; int openBuyCount = 0; int openSellCount = 0; for (int i = OrdersTotal() - 1; i >= 0; i--) { if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue; if (OrderMagicNumber() != Magic_A) continue; if (OrderSymbol() != Symbol()) continue; int type = OrderType(); if (type == OP_BUY) openBuyCount++; if (type == OP_SELL) openSellCount++; } // ▼ Check for maximum open position count violation if (openBuyCount > MaxOpenBuyPositions) { EmergencyLatched = true; // ← latch ON EmergencyCloseAll( StringFormat( "Abnormal BUY position count (%d > %d)", openBuyCount, MaxOpenBuyPositions ) ); return; } if (openSellCount > MaxOpenSellPositions) { EmergencyLatched = true; // ← latch ON EmergencyCloseAll( StringFormat( "Abnormal SELL position count (%d > %d)", openSellCount, MaxOpenSellPositions ) ); return; } // Note: // Duplicate entry within the same bar is not handled here. // It is detected immediately before entry execution. } // --------------------------------------------- // (25) Emergency close all positions and block new entries // (EA continues running) // --------------------------------------------- void EmergencyCloseAll(string reasonMessage) { EmergencyStop = true; // Completely block new entries Print("🚨 EmergencyCloseAll triggered. Reason = ", reasonMessage); const int maxRetry = 200; const int retryInterval = 200; // milliseconds (0.2s interval) for (int attempt = 0; attempt < maxRetry; attempt++) { RefreshRates(); // ---- Process all orders matching this EA's Magic number ---- for (int i = OrdersTotal() - 1; i >= 0; i--) { if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue; if (OrderSymbol() != Symbol()) continue; if (OrderMagicNumber() != Magic_A) continue; int type = OrderType(); // ▼ Market positions if (type == OP_BUY || type == OP_SELL) { double price = (type == OP_BUY) ? Bid : Ask; // use latest price bool closed = OrderClose( OrderTicket(), OrderLots(), price, 5, clrRed ); if (!closed) { PrintFormat( "⚠️ OrderClose failed ticket=%d error=%d (retry=%d)", OrderTicket(), GetLastError(), attempt ); } else { PrintFormat( "✅ Market position closed successfully ticket=%d", OrderTicket() ); } } // ▼ Pending orders else if (type == OP_BUYLIMIT || type == OP_SELLLIMIT || type == OP_BUYSTOP || type == OP_SELLSTOP) { bool deleted = OrderDelete(OrderTicket()); if (!deleted) { PrintFormat( "⚠️ OrderDelete failed ticket=%d error=%d (retry=%d)", OrderTicket(), GetLastError(), attempt ); } else { PrintFormat( "🗑️ Pending order deleted successfully ticket=%d", OrderTicket() ); } } } // ---- Are all orders cleared? ---- if (CountOrdersOfThisEA() == 0) { string msg = "[Emergency Close Completed]\n" + "Symbol: " + Symbol() + "\n" + "Time: " + TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS) + "\n" + "Reason: " + reasonMessage + "\n" + "All positions have been closed/deleted.\n" + "EA remains active in EmergencyStop mode (no new entries)."; SendNotification(msg); Print(msg); return; } if (attempt % 10 == 0) { PrintFormat( "⏳ EmergencyClose retry %d/%d", attempt, maxRetry ); } Sleep(retryInterval); } // ---- Orders still remain after maximum retries ---- string newmsg = "[WARNING: Emergency Close Failed]\n" + "Symbol: " + Symbol() + "\n" + "Time: " + TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS) + "\n" + "Reason: " + reasonMessage + "\n" + "Orders still remain after maximum retry attempts.\n" + "EmergencyStop mode is maintained (entries remain blocked)."; SendNotification(newmsg); Print(newmsg); } // --------------------------------------------- // (26) Helper: count orders belonging to Magic_A // (used to verify completion of emergency close) // --------------------------------------------- int CountOrdersOfThisEA() { int c = 0; for (int i = OrdersTotal() - 1; i >= 0; i--) { if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue; if (OrderSymbol() != Symbol()) continue; if (OrderMagicNumber() != Magic_A) continue; c++; } return c; } //--------------------------------------------- // (27) Main OnTick processing //--------------------------------------------- void OnTick() { RefreshRates(); // Check for abnormal/fail-safe conditions and margin alerts CheckForAbnormalState(); CheckMarginLevel(); // ================================================= // ① Detect EXIT signals → push to EXIT queue (highest priority) // ================================================= SignalType exitSig = ExitSignalA(); HandleExitA(exitSig); // ← does not register RetryTask directly; queued in EXIT queue // ================================================= // ② If EXIT queue exists, flow the top task to RetryManager (highest priority) // ================================================= if (currentTask.taskType == TASK_NONE && HasExitQueue()) { RetryTask t = PopExitQueue(); RegisterRetryTask(t); } // ================================================= // ③ Trailing Stop (register MODIFY task) // ※ Will naturally wait if an EXIT task is in progress // ================================================= ApplyTrailingStop(); // ================================================= // ④ New bar entry (register ENTRY task) // ※ Will naturally wait if an EXIT task is in progress // ================================================= if (IsNewBar(PERIOD_CURRENT, prevTime_A)) { SignalType entrySig = EntrySignalA(); HandleEntryA(entrySig); } // ================================================= // ⑤ Execute RetryManager // ================================================= ProcessRetryTask(); // Send periodic EA status notification CheckEAStatus(); } // --- END ---