//+------------------------------------------------------------------+ //| OrdersEA_Smart_Grid.mq5 | //| Copyright 2024, Garfield Heron | //| https://fetcherpay.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, Garfield Heron" #property link "https://fetcherpay.com" #property version "4.1" #include #include #define VERSION "Version 4.1 Smart Grid MT5" #define MAX_TRADES 600 #define MAX_LOG_TRADES 1200 //--- Input Parameters input string Email= "garfield@fetcherpay.com"; input int MagicNum= 333; //--- Smart Grid Settings input string GridSettings = "=== Smart Grid Settings ==="; input bool UseAutoPivots = true; input double InpManualHigh= 0; // Manual HIGH level (0 = use AutoPivots) input double InpManualLow= 0; // Manual LOW level (0 = use AutoPivots) input double Entry= 10; // Grid spacing in points input double TP= 15; // Take-profit in points per level input double Lots=0.01; input int MaxLevels = 10; // Max levels per side (cap; adaptive may place fewer) //--- Range Filter Settings input string FilterSettings = "=== Range Filters ==="; input bool UseRSIFilter = true; input int RSIPeriod = 14; input int RSILower = 35; input int RSIUpper = 65; input bool UseADXFilter = true; input int ADXPeriod = 14; input double ADXMax = 30; // Widened default from 25 input bool UseATRFilter = true; input int ATRPeriod = 14; input double ATRMultiplier = 1.5; //--- Adaptive Filters (4.4) input string AdaptiveSettings = "=== Adaptive Filters ==="; input int InpRelaxFilterAfterDays = 3; // If no trade for N days, relax filters (0=disabled) input double InpRelaxADXFactor = 1.3; // Multiply ADXMax by this when relaxed input double InpRelaxLotFactor = 0.5; // Multiply lots by this when relaxed //--- Adaptive Entry (3.3) input bool InpAdaptiveEntry = false; // Auto-scale grid spacing from ATR input double InpEntryATRFactor = 0.5; // spacing = ATR * factor / MaxLevels (per level) //--- Spread Filter (3.4) input string SpreadSettings = "=== Spread Filter ==="; input int InpMaxSpreadPoints = 30; // 0 = disabled //--- Stop Loss (3.5) input string SLSettings = "=== Stop Loss ==="; input bool InpUseStopLoss = false; // false = NO stop loss (original grid behavior) input int StopLoss = 300; // Only applied if InpUseStopLoss=true //--- Session Filter (4.1) input string SessionSettings = "=== Session Filter ==="; input bool InpUseSessionFilter = false; input int InpSessionStartHour = 1; // Broker time, inclusive input int InpSessionEndHour = 10; // Broker time, exclusive //--- Breakeven (4.2) input string BreakevenSettings = "=== Breakeven ==="; input bool InpUseBreakeven = false; // Move remaining SLs to breakeven after first TP hits input int InpBreakevenBufferPoints = 5; // Spread cushion above entry //--- Correlation Cap (4.3) input string CorrelationSettings = "=== Correlation Cap ==="; input int InpMaxLongSymbols = 0; // Max distinct symbols with open longs (0 = unlimited) input int InpMaxShortSymbols = 0; // Max distinct symbols with open shorts (0 = unlimited) //--- Range Drift (4.6) input string DriftSettings = "=== Range Drift ==="; input bool InpRangeDriftEnable = false; input int InpMoveRangeTrigger = 200; // Re-center grid when mid drifts by N points //--- Risk Management input string RiskSettings = "=== Risk Management ==="; input int TRADE_RANGE= 50; input double LongLimit= 0; input double ShortLimit= 0; input string GetOut= "N"; input string OpenNewTrades="Y"; input int TakeProfitLevelPercent= 0; // % equity gain to close cycle (0=disabled) input int TakeProfitLevelDollarAmount= 0; // $ gain to close cycle (0=disabled) input int EquityFactorPercent= 0; input int LotsFactorPercent= 0; input int BaseEquity= 10000; input bool Master= false; input bool DiagnosticModeOn= false; input double InpMaxDailyDrawdown = 5.0; // Max daily EA drawdown % (per-EA) input double InpMaxWeeklyDrawdown = 10.0; // Max weekly EA drawdown % (per-EA, 0=disable) //--- Profit Protection input string ProfitSettings = "=== Profit Protection ==="; input bool InpStopAfterProfit = false; // Stop new grids after profitable cycle input bool InpCycleReport = true; // Emit notification on cycle end //--- Weekend Protection input string WeekendSettings = "=== Weekend Protection ==="; input bool InpCloseBeforeWeekend = true; input int InpWeekendCloseHour = 17; input bool InpCancelPendingBeforeWeekend = true; //--- Trade Object CTrade trade; CPositionInfo positionInfo; //--- Indicator Handles int RSIHandle = INVALID_HANDLE; int ADXHandle = INVALID_HANDLE; int ATRHandle = INVALID_HANDLE; //--- Pivot Point Variables double PivotP = 0; double PivotR1 = 0; double PivotR2 = 0; double PivotS1 = 0; double PivotS2 = 0; double GridHigh = 0; double GridLow = 0; //--- Stats Variables (1.3 — now actually computed) int longs = 0; int shorts = 0; double longAvgPrice = 0; double longAvgLots = 0; double shortAvgPrice = 0; double shortAvgLots = 0; double longProfit = 0; double shortProfit = 0; //--- State double initEquity; int lotDigits; bool bEnableLongs = false; bool bEnableShorts = false; bool bGetOutOK = false; bool bOpenNewTradesOK = false; bool bGetOutHandled = false; //--- Daily Drawdown (per-EA, 3.1B) double dailyStartEquity = 0; datetime lastEquityReset = 0; double realizedPnLToday = 0; datetime lastRealizedScan = 0; //--- Weekly Drawdown (per-EA) double weeklyStartEquity = 0; datetime lastWeeklyReset = 0; double realizedPnLWeek = 0; datetime lastWeeklyScan = 0; //--- Weekend Protection bool weekendCloseExecuted = false; //--- Master one-shot (1.4) bool masterShutdownDone = false; //--- Pivot recalc (3.8) datetime lastPivotCalcDate = 0; //--- Grid State (3.7 — now persisted) bool gridPlaced = false; double cycleStartEquity = 0; datetime cycleStartTime = 0; bool cycleProfitStop = false; int cyclePartialTPCount = 0; // For breakeven logic datetime lastTradePlacedTime = 0; // For adaptive filters //--- Runtime derived double currentEntryPts = 0; // Resolved at bar open (adaptive or fixed) bool filtersRelaxed = false; // Set by adaptive filter check //+------------------------------------------------------------------+ //| Log helper with symbol prefix (3.6) | //+------------------------------------------------------------------+ void PrintS(string msg) { Print("[", _Symbol, ":", MagicNum, "] ", msg); } void Log(string st) { if(DiagnosticModeOn) PrintS(st); } //+------------------------------------------------------------------+ //| Determine filling mode supported by symbol (1.2) | //+------------------------------------------------------------------+ ENUM_ORDER_TYPE_FILLING GetMarketFilling() { int modes = (int)SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE); if((modes & SYMBOL_FILLING_IOC) != 0) return ORDER_FILLING_IOC; if((modes & SYMBOL_FILLING_FOK) != 0) return ORDER_FILLING_FOK; return ORDER_FILLING_RETURN; } // ORDER_FILLING_RETURN is always valid for pending orders per MT5 docs — // SYMBOL_FILLING_MODE bitmask only exposes FOK/IOC flags (RETURN is implicit). ENUM_ORDER_TYPE_FILLING GetPendingFilling() { return ORDER_FILLING_RETURN; } //+------------------------------------------------------------------+ //| State persistence (3.7) | //+------------------------------------------------------------------+ string GvKey(string suffix) { return "OrdersEA_" + IntegerToString(MagicNum) + "_" + _Symbol + "_" + suffix; } void SaveGridState() { GlobalVariableSet(GvKey("gridPlaced"), gridPlaced ? 1.0 : 0.0); GlobalVariableSet(GvKey("cycleStartEquity"), cycleStartEquity); GlobalVariableSet(GvKey("cycleStartTime"), (double)cycleStartTime); GlobalVariableSet(GvKey("cycleProfitStop"), cycleProfitStop ? 1.0 : 0.0); GlobalVariableSet(GvKey("lastPivotCalcDate"), (double)lastPivotCalcDate); GlobalVariableSet(GvKey("lastTradeTime"), (double)lastTradePlacedTime); GlobalVariableSet(GvKey("dailyStartEquity"), dailyStartEquity); GlobalVariableSet(GvKey("lastEquityReset"), (double)lastEquityReset); GlobalVariableSet(GvKey("weeklyStartEquity"), weeklyStartEquity); GlobalVariableSet(GvKey("lastWeeklyReset"), (double)lastWeeklyReset); } void LoadGridState() { if(GlobalVariableCheck(GvKey("gridPlaced"))) gridPlaced = GlobalVariableGet(GvKey("gridPlaced")) > 0.5; if(GlobalVariableCheck(GvKey("cycleStartEquity"))) cycleStartEquity = GlobalVariableGet(GvKey("cycleStartEquity")); if(GlobalVariableCheck(GvKey("cycleStartTime"))) cycleStartTime = (datetime)GlobalVariableGet(GvKey("cycleStartTime")); if(GlobalVariableCheck(GvKey("cycleProfitStop"))) cycleProfitStop = GlobalVariableGet(GvKey("cycleProfitStop")) > 0.5; if(GlobalVariableCheck(GvKey("lastPivotCalcDate"))) lastPivotCalcDate = (datetime)GlobalVariableGet(GvKey("lastPivotCalcDate")); if(GlobalVariableCheck(GvKey("lastTradeTime"))) lastTradePlacedTime = (datetime)GlobalVariableGet(GvKey("lastTradeTime")); if(GlobalVariableCheck(GvKey("dailyStartEquity"))) dailyStartEquity = GlobalVariableGet(GvKey("dailyStartEquity")); if(GlobalVariableCheck(GvKey("lastEquityReset"))) lastEquityReset = (datetime)GlobalVariableGet(GvKey("lastEquityReset")); if(GlobalVariableCheck(GvKey("weeklyStartEquity"))) weeklyStartEquity = GlobalVariableGet(GvKey("weeklyStartEquity")); if(GlobalVariableCheck(GvKey("lastWeeklyReset"))) lastWeeklyReset = (datetime)GlobalVariableGet(GvKey("lastWeeklyReset")); } //+------------------------------------------------------------------+ //| Today-at-broker / Monday-at-broker | //+------------------------------------------------------------------+ datetime TodayStartBroker() { datetime now = TimeCurrent(); return (datetime)((now / 86400) * 86400); } datetime WeekStartBroker() { datetime today = TodayStartBroker(); MqlDateTime dt; TimeToStruct(today, dt); int offset = (dt.day_of_week == 0) ? 6 : (dt.day_of_week - 1); return (datetime)(today - offset * 86400); } //+------------------------------------------------------------------+ //| Sum deals for this EA in a window (3.1B) | //+------------------------------------------------------------------+ double SumRealizedPnLSince(datetime from) { datetime to = TimeCurrent() + 60; if(!HistorySelect(from, to)) return 0; double total = 0; int deals = HistoryDealsTotal(); for(int i = 0; i < deals; i++) { ulong dealTicket = HistoryDealGetTicket(i); if(dealTicket == 0) continue; if(HistoryDealGetInteger(dealTicket, DEAL_MAGIC) != MagicNum) continue; if(HistoryDealGetString(dealTicket, DEAL_SYMBOL) != _Symbol) continue; long entry = HistoryDealGetInteger(dealTicket, DEAL_ENTRY); if(entry != DEAL_ENTRY_OUT && entry != DEAL_ENTRY_INOUT) continue; total += HistoryDealGetDouble(dealTicket, DEAL_PROFIT); total += HistoryDealGetDouble(dealTicket, DEAL_SWAP); total += HistoryDealGetDouble(dealTicket, DEAL_COMMISSION); } return total; } double GetRealizedPnLToday() { if(TimeCurrent() - lastRealizedScan < 60 && realizedPnLToday != 0) return realizedPnLToday; realizedPnLToday = SumRealizedPnLSince(TodayStartBroker()); lastRealizedScan = TimeCurrent(); return realizedPnLToday; } double GetRealizedPnLWeek() { if(TimeCurrent() - lastWeeklyScan < 120 && realizedPnLWeek != 0) return realizedPnLWeek; realizedPnLWeek = SumRealizedPnLSince(WeekStartBroker()); lastWeeklyScan = TimeCurrent(); return realizedPnLWeek; } //+------------------------------------------------------------------+ //| Compute floating P&L for this EA | //+------------------------------------------------------------------+ double GetFloatingPnL() { double total = 0; 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) != MagicNum) continue; total += PositionGetDouble(POSITION_PROFIT); total += PositionGetDouble(POSITION_SWAP); } return total; } //+------------------------------------------------------------------+ //| Check Drawdown — daily + weekly, per-EA (3.1B) | //+------------------------------------------------------------------+ bool CheckDailyDrawdown() { static bool dailyWarned = false; static bool weeklyWarned = false; // --- Daily rollover --- datetime today = TodayStartBroker(); if(today != lastEquityReset) { dailyStartEquity = AccountInfoDouble(ACCOUNT_EQUITY); lastEquityReset = today; realizedPnLToday = 0; lastRealizedScan = 0; dailyWarned = false; PrintS("Daily equity reset: $" + DoubleToString(dailyStartEquity, 2)); SaveGridState(); } // --- Weekly rollover (Monday 00:00 broker) --- datetime weekStart = WeekStartBroker(); if(weekStart != lastWeeklyReset) { weeklyStartEquity = AccountInfoDouble(ACCOUNT_EQUITY); lastWeeklyReset = weekStart; realizedPnLWeek = 0; lastWeeklyScan = 0; weeklyWarned = false; PrintS("Weekly equity reset: $" + DoubleToString(weeklyStartEquity, 2)); SaveGridState(); } double floating = GetFloatingPnL(); // --- Daily check --- if(InpMaxDailyDrawdown > 0 && dailyStartEquity > 0) { double dailyPnL = GetRealizedPnLToday() + floating; double dailyLimit = dailyStartEquity * InpMaxDailyDrawdown / 100.0; if(dailyPnL <= -dailyLimit) { if(!dailyWarned) { PrintS("⚠️ DAILY DD HIT: $" + DoubleToString(dailyPnL, 2) + " (limit: -$" + DoubleToString(dailyLimit, 2) + ")"); SendNotification("Grid EA " + _Symbol + ": DAILY DD reached ($" + DoubleToString(dailyPnL, 2) + ")"); dailyWarned = true; } return false; } dailyWarned = false; } // --- Weekly check --- if(InpMaxWeeklyDrawdown > 0 && weeklyStartEquity > 0) { double weeklyPnL = GetRealizedPnLWeek() + floating; double weeklyLimit = weeklyStartEquity * InpMaxWeeklyDrawdown / 100.0; if(weeklyPnL <= -weeklyLimit) { if(!weeklyWarned) { PrintS("⚠️ WEEKLY DD HIT: $" + DoubleToString(weeklyPnL, 2) + " (limit: -$" + DoubleToString(weeklyLimit, 2) + ")"); SendNotification("Grid EA " + _Symbol + ": WEEKLY DD reached ($" + DoubleToString(weeklyPnL, 2) + ")"); weeklyWarned = true; } return false; } weeklyWarned = false; } return true; } //+------------------------------------------------------------------+ //| Check Weekend Protection | //+------------------------------------------------------------------+ bool CheckWeekendProtection() { if(!InpCloseBeforeWeekend) return true; MqlDateTime dt; TimeToStruct(TimeCurrent(), dt); if(dt.day_of_week != FRIDAY) { if(weekendCloseExecuted) weekendCloseExecuted = false; return true; } if(weekendCloseExecuted) return true; if(dt.hour >= InpWeekendCloseHour) { PrintS("⚠️ WEEKEND CLOSE — closing all positions"); SendNotificationEx("WEEKEND CLOSE", "Closing positions before weekend"); CloseAllPositions("Weekend protection"); if(InpCancelPendingBeforeWeekend) CancelAllOrders("Weekend protection"); weekendCloseExecuted = true; gridPlaced = false; SaveGridState(); return false; } return true; } //+------------------------------------------------------------------+ //| Session Filter (4.1) | //+------------------------------------------------------------------+ bool CheckSessionFilter() { if(!InpUseSessionFilter) return true; MqlDateTime dt; TimeToStruct(TimeCurrent(), dt); int h = dt.hour; bool inSession; if(InpSessionStartHour <= InpSessionEndHour) inSession = (h >= InpSessionStartHour && h < InpSessionEndHour); else inSession = (h >= InpSessionStartHour || h < InpSessionEndHour); if(!inSession && DiagnosticModeOn) PrintS("Session filter: outside window (" + IntegerToString(h) + "h)"); return inSession; } //+------------------------------------------------------------------+ //| Spread Filter (3.4) | //+------------------------------------------------------------------+ bool CheckSpreadFilter() { if(InpMaxSpreadPoints <= 0) return true; int spread = (int)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD); if(spread > InpMaxSpreadPoints) { if(DiagnosticModeOn) PrintS("Spread filter: " + IntegerToString(spread) + "pts > " + IntegerToString(InpMaxSpreadPoints)); return false; } return true; } //+------------------------------------------------------------------+ //| Correlation Cap (4.3) — count distinct symbols w/ open positions | //+------------------------------------------------------------------+ int CountSymbolsWithOpenPositions(int posType) { string symbols[]; int count = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(PositionGetInteger(POSITION_MAGIC) != MagicNum) continue; if(PositionGetInteger(POSITION_TYPE) != posType) continue; string sym = PositionGetString(POSITION_SYMBOL); bool found = false; for(int j = 0; j < count; j++) if(symbols[j] == sym) { found = true; break; } if(!found) { ArrayResize(symbols, count + 1); symbols[count] = sym; count++; } } return count; } bool CheckCorrelationCap(bool forLong) { int cap = forLong ? InpMaxLongSymbols : InpMaxShortSymbols; if(cap <= 0) return true; int type = forLong ? POSITION_TYPE_BUY : POSITION_TYPE_SELL; int current = CountSymbolsWithOpenPositions(type); // If this symbol already has positions, we're not adding a new distinct symbol bool thisSymbolHasPos = false; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(PositionGetInteger(POSITION_MAGIC) != MagicNum) continue; if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; if(PositionGetInteger(POSITION_TYPE) == type) { thisSymbolHasPos = true; break; } } if(thisSymbolHasPos) return true; if(current >= cap) { if(DiagnosticModeOn) PrintS("Correlation cap: " + IntegerToString(current) + "/" + IntegerToString(cap) + (forLong ? " long" : " short") + " symbols open"); return false; } return true; } //+------------------------------------------------------------------+ //| Adaptive Filters (4.4) | //+------------------------------------------------------------------+ void UpdateFilterRelaxation() { filtersRelaxed = false; if(InpRelaxFilterAfterDays <= 0) return; if(lastTradePlacedTime == 0) return; // never traded — don't relax yet long secsIdle = (long)TimeCurrent() - (long)lastTradePlacedTime; if(secsIdle > (long)InpRelaxFilterAfterDays * 86400) filtersRelaxed = true; } double EffectiveADXMax() { return filtersRelaxed ? (ADXMax * InpRelaxADXFactor) : ADXMax; } //+------------------------------------------------------------------+ //| Calculate Pivot Points | //+------------------------------------------------------------------+ void CalculatePivotPoints() { MqlRates rates[1]; int copied = CopyRates(_Symbol, PERIOD_D1, 1, 1, rates); if(copied < 1) { PrintS("Failed to get daily rates for pivot calculation"); return; } double prevHigh = rates[0].high; double prevLow = rates[0].low; double prevClose = rates[0].close; PivotP = (prevHigh + prevLow + prevClose) / 3.0; PivotR1 = (2.0 * PivotP) - prevLow; PivotS1 = (2.0 * PivotP) - prevHigh; PivotR2 = PivotP + (prevHigh - prevLow); PivotS2 = PivotP - (prevHigh - prevLow); if(UseAutoPivots) { double atr = 0; if(ATRHandle != INVALID_HANDLE) { double atrBuf[1]; if(CopyBuffer(ATRHandle, 0, 0, 1, atrBuf) > 0) atr = atrBuf[0]; } if(UseATRFilter && atr > 0) { GridHigh = NormalizeDouble(PivotP + (atr * ATRMultiplier), _Digits); GridLow = NormalizeDouble(PivotP - (atr * ATRMultiplier), _Digits); } else { GridHigh = PivotR1; GridLow = PivotS1; } PrintS("AutoPivots: HIGH=" + DoubleToString(GridHigh, _Digits) + " LOW=" + DoubleToString(GridLow, _Digits) + " ATR=" + DoubleToString(atr, _Digits)); } PrintS("Pivot: P=" + DoubleToString(PivotP, _Digits) + " R1=" + DoubleToString(PivotR1, _Digits) + " S1=" + DoubleToString(PivotS1, _Digits)); lastPivotCalcDate = TodayStartBroker(); SaveGridState(); } //+------------------------------------------------------------------+ //| Resolve Entry spacing at runtime (3.3 adaptive) | //+------------------------------------------------------------------+ double ResolveEntryPoints() { if(!InpAdaptiveEntry) return Entry; if(ATRHandle == INVALID_HANDLE) return Entry; double buf[1]; if(CopyBuffer(ATRHandle, 0, 0, 1, buf) <= 0) return Entry; double atr = buf[0]; double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); if(point <= 0 || atr <= 0) return Entry; double maxLvl = MathMax(1, MaxLevels); double pts = (atr * InpEntryATRFactor) / maxLvl / point; return MathMax(5.0, pts); // floor at 5pts } //+------------------------------------------------------------------+ //| Check if Market is Ranging | //+------------------------------------------------------------------+ bool IsRangingMarket() { bool isRanging = true; double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID); if(UseRSIFilter && RSIHandle != INVALID_HANDLE) { double rsiBuf[1]; if(CopyBuffer(RSIHandle, 0, 0, 1, rsiBuf) > 0) { double rsi = rsiBuf[0]; if(rsi < RSILower || rsi > RSIUpper) { if(DiagnosticModeOn) PrintS("RSI extreme: " + DoubleToString(rsi, 1)); isRanging = false; } } } if(UseADXFilter && ADXHandle != INVALID_HANDLE && isRanging) { double adxBuf[1]; if(CopyBuffer(ADXHandle, 0, 0, 1, adxBuf) > 0) { double adx = adxBuf[0]; double adxCap = EffectiveADXMax(); if(adx > adxCap) { if(DiagnosticModeOn) PrintS("ADX trending: " + DoubleToString(adx, 1) + " > " + DoubleToString(adxCap, 1) + (filtersRelaxed ? " (relaxed)" : "")); isRanging = false; } } } double actualHigh = (InpManualHigh > 0) ? InpManualHigh : GridHigh; double actualLow = (InpManualLow > 0) ? InpManualLow : GridLow; if(currentPrice > actualHigh || currentPrice < actualLow) { if(DiagnosticModeOn) PrintS("Price outside grid range"); isRanging = false; } return isRanging; } //+------------------------------------------------------------------+ //| Breakout check (1.1 — no longer clobbers GridHigh/GridLow) | //+------------------------------------------------------------------+ bool IsBreakout() { double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID); if(currentPrice < PivotS2 || currentPrice > PivotR2) { PrintS("BREAKOUT: price=" + DoubleToString(currentPrice, _Digits) + " S2=" + DoubleToString(PivotS2, _Digits) + " R2=" + DoubleToString(PivotR2, _Digits)); return true; } return false; } //+------------------------------------------------------------------+ //| Calculate Lot Size | //+------------------------------------------------------------------+ double CalcLots() { double tmp = (AccountInfoDouble(ACCOUNT_EQUITY) - initEquity); double a = EquityFactorPercent; double b = LotsFactorPercent; double lots; if(0 == EquityFactorPercent || 0 == LotsFactorPercent) lots = Lots; else { a = initEquity * a / 100.0; b = b / 100.0; if(tmp > 0) tmp = MathPow(1 + b, (tmp / a)); else if(tmp < 0) tmp = MathPow(1 - b, MathAbs(tmp / a)); else tmp = 1; lots = NormalizeDouble(Lots * tmp, lotDigits); } if(filtersRelaxed) lots *= InpRelaxLotFactor; double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); if(lots < minLot) lots = minLot; if(lots > maxLot) lots = maxLot; if(step > 0) lots = MathFloor(lots / step + 1e-9) * step; return NormalizeDouble(lots, lotDigits); } //+------------------------------------------------------------------+ //| Send Notification | //+------------------------------------------------------------------+ void SendNotificationEx(string title, string subject) { if(MQLInfoInteger(MQL_OPTIMIZATION)) return; double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); string msg = "[" + _Symbol + "] " + title + ": " + subject; msg += " | Px: " + DoubleToString(bid, _Digits); msg += " | L: " + IntegerToString(longs) + " @ " + DoubleToString(longAvgPrice, _Digits); msg += " | S: " + IntegerToString(shorts) + " @ " + DoubleToString(shortAvgPrice, _Digits); msg += " | Eq: " + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2); SendNotification(msg); PrintS(title + ": " + subject); } //+------------------------------------------------------------------+ //| Compute averages for this EA (1.3) | //+------------------------------------------------------------------+ void CalcAvgPrice() { longs = 0; shorts = 0; longAvgPrice = 0; longAvgLots = 0; shortAvgPrice = 0; shortAvgLots = 0; longProfit = 0; shortProfit = 0; 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) != MagicNum) continue; long ptype = PositionGetInteger(POSITION_TYPE); double vol = PositionGetDouble(POSITION_VOLUME); double open = PositionGetDouble(POSITION_PRICE_OPEN); double profit = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP); if(ptype == POSITION_TYPE_BUY) { longAvgPrice += open * vol; longAvgLots += vol; longProfit += profit; longs++; } else if(ptype == POSITION_TYPE_SELL) { shortAvgPrice += open * vol; shortAvgLots += vol; shortProfit += profit; shorts++; } } if(longAvgLots > 0) longAvgPrice /= longAvgLots; if(shortAvgLots > 0) shortAvgPrice /= shortAvgLots; } //+------------------------------------------------------------------+ //| Breakeven move (4.2) | //+------------------------------------------------------------------+ void ApplyBreakeven() { if(!InpUseBreakeven) return; if(cyclePartialTPCount <= 0) return; // Nothing filled profitably yet double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); double buffer = InpBreakevenBufferPoints * point; 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) != MagicNum) continue; long ptype = PositionGetInteger(POSITION_TYPE); double open = PositionGetDouble(POSITION_PRICE_OPEN); double curSL = PositionGetDouble(POSITION_SL); double tp = PositionGetDouble(POSITION_TP); double newSL = (ptype == POSITION_TYPE_BUY) ? (open - buffer) : (open + buffer); newSL = NormalizeDouble(newSL, _Digits); bool alreadyAtBE = (ptype == POSITION_TYPE_BUY) ? (curSL >= open - point) : (curSL <= open + point && curSL > 0); if(alreadyAtBE) continue; if(!trade.PositionModify(ticket, newSL, tp)) PrintS("Breakeven modify failed #" + IntegerToString((int)ticket) + " err=" + IntegerToString((int)trade.ResultRetcode())); else PrintS("Breakeven set #" + IntegerToString((int)ticket) + " SL=" + DoubleToString(newSL, _Digits)); } } //+------------------------------------------------------------------+ //| Count partial TP fills since cycle start | //+------------------------------------------------------------------+ int CountPartialTPsSinceCycle() { if(cycleStartTime == 0) return 0; if(!HistorySelect(cycleStartTime, TimeCurrent() + 60)) return 0; int n = 0; int total = HistoryDealsTotal(); for(int i = 0; i < total; i++) { ulong ticket = HistoryDealGetTicket(i); if(ticket == 0) continue; if(HistoryDealGetInteger(ticket, DEAL_MAGIC) != MagicNum) continue; if(HistoryDealGetString(ticket, DEAL_SYMBOL) != _Symbol) continue; long entry = HistoryDealGetInteger(ticket, DEAL_ENTRY); if(entry != DEAL_ENTRY_OUT && entry != DEAL_ENTRY_INOUT) continue; if(HistoryDealGetDouble(ticket, DEAL_PROFIT) > 0) n++; } return n; } //+------------------------------------------------------------------+ //| Profit target check (2.1 + 2.2) | //+------------------------------------------------------------------+ bool CheckProfitTarget() { if(cycleStartEquity <= 0) return false; double gain = AccountInfoDouble(ACCOUNT_EQUITY) - cycleStartEquity; double pctGain = (gain / cycleStartEquity) * 100.0; bool pctHit = (TakeProfitLevelPercent > 0 && pctGain >= TakeProfitLevelPercent); bool dollHit = (TakeProfitLevelDollarAmount > 0 && gain >= TakeProfitLevelDollarAmount); if(pctHit || dollHit) { PrintS("PROFIT TARGET — gain=$" + DoubleToString(gain, 2) + " (" + DoubleToString(pctGain, 2) + "%) closing cycle"); SendNotificationEx("PROFIT TARGET HIT", "Gain $" + DoubleToString(gain, 2) + " (" + DoubleToString(pctGain, 2) + "%)"); CloseAllPositions("Profit target"); CancelAllOrders("Profit target"); EmitCycleReport(true); gridPlaced = false; cycleStartEquity = 0; cycleStartTime = 0; cyclePartialTPCount = 0; SaveGridState(); return true; } return false; } //+------------------------------------------------------------------+ //| Cycle Report (4.5) | //+------------------------------------------------------------------+ void EmitCycleReport(bool targetHit) { if(!InpCycleReport) return; if(cycleStartEquity <= 0) return; double gain = AccountInfoDouble(ACCOUNT_EQUITY) - cycleStartEquity; long dur = (long)TimeCurrent() - (long)cycleStartTime; int hrs = (int)(dur / 3600); int mins = (int)((dur % 3600) / 60); string tag = targetHit ? "TARGET" : "END"; SendNotificationEx("CYCLE " + tag, "P&L: $" + DoubleToString(gain, 2) + " | Dur: " + IntegerToString(hrs) + "h" + IntegerToString(mins) + "m" + " | Partials: " + IntegerToString(cyclePartialTPCount)); } //+------------------------------------------------------------------+ //| Order helpers | //+------------------------------------------------------------------+ int CountPendingOrders(int type) { int count = 0; 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) != MagicNum) continue; if(OrderGetInteger(ORDER_TYPE) == type) count++; } return count; } void CancelAllOrders(string reason) { int cancelled = 0; 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) != MagicNum) continue; if(trade.OrderDelete(ticket)) cancelled++; } if(cancelled > 0) PrintS("Cancelled " + IntegerToString(cancelled) + " orders: " + reason); } void CloseAllPositions(string reason) { int closed = 0; 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) != MagicNum) continue; if(trade.PositionClose(ticket)) closed++; } if(closed > 0) PrintS("Closed " + IntegerToString(closed) + " positions: " + reason); } //+------------------------------------------------------------------+ //| Close positions by direction (edge cleanup 5.0) | //+------------------------------------------------------------------+ void ClosePositionsBySide(long posType, string reason) { int closed = 0; 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) != MagicNum) continue; if(PositionGetInteger(POSITION_TYPE) != posType) continue; if(trade.PositionClose(ticket)) closed++; } if(closed > 0) PrintS("Closed " + IntegerToString(closed) + " " + (posType == POSITION_TYPE_BUY ? "BUY" : "SELL") + " positions: " + reason); } //+------------------------------------------------------------------+ //| Place Buy/Sell Limit Orders | //+------------------------------------------------------------------+ bool PlaceBuyLimit(double priceLevel, int level) { if(level >= MaxLevels) return false; double lots = CalcLots(); double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); double currentAsk = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double stopLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * point; double minDistance = currentAsk - stopLevel - (currentEntryPts * point); if(priceLevel > minDistance) { if(DiagnosticModeOn) PrintS("BuyLimit " + IntegerToString(level) + " too close — skip"); return false; } double sl = 0; if(InpUseStopLoss && StopLoss > 0) sl = NormalizeDouble(priceLevel - (StopLoss * point), _Digits); double tp = NormalizeDouble(priceLevel + (TP * point), _Digits); if(tp <= priceLevel + stopLevel) tp = NormalizeDouble(priceLevel + stopLevel + (TP * point), _Digits); MqlTradeRequest req = {}; MqlTradeResult res = {}; req.action = TRADE_ACTION_PENDING; req.symbol = _Symbol; req.volume = lots; req.price = NormalizeDouble(priceLevel, _Digits); req.sl = sl; req.tp = tp; req.deviation = 10; req.magic = MagicNum; req.comment = "SG Buy " + IntegerToString(level); req.type = ORDER_TYPE_BUY_LIMIT; req.type_filling = GetPendingFilling(); if(!trade.OrderSend(req, res)) { PrintS("BuyLimit error: " + trade.ResultRetcodeDescription() + " (" + IntegerToString((int)res.retcode) + ")"); return false; } if(res.retcode == TRADE_RETCODE_DONE || res.retcode == TRADE_RETCODE_PLACED) { PrintS("BuyLimit L" + IntegerToString(level) + " @ " + DoubleToString(req.price, _Digits) + " TP=" + DoubleToString(tp, _Digits) + " #" + IntegerToString((int)res.order)); lastTradePlacedTime = TimeCurrent(); return true; } return false; } bool PlaceSellLimit(double priceLevel, int level) { if(level >= MaxLevels) return false; double lots = CalcLots(); double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); double currentBid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double stopLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * point; double minDistance = currentBid + stopLevel + (currentEntryPts * point); if(priceLevel < minDistance) { if(DiagnosticModeOn) PrintS("SellLimit " + IntegerToString(level) + " too close — skip"); return false; } double sl = 0; if(InpUseStopLoss && StopLoss > 0) sl = NormalizeDouble(priceLevel + (StopLoss * point), _Digits); double tp = NormalizeDouble(priceLevel - (TP * point), _Digits); if(tp >= priceLevel - stopLevel) tp = NormalizeDouble(priceLevel - stopLevel - (TP * point), _Digits); MqlTradeRequest req = {}; MqlTradeResult res = {}; req.action = TRADE_ACTION_PENDING; req.symbol = _Symbol; req.volume = lots; req.price = NormalizeDouble(priceLevel, _Digits); req.sl = sl; req.tp = tp; req.deviation = 10; req.magic = MagicNum; req.comment = "SG Sell " + IntegerToString(level); req.type = ORDER_TYPE_SELL_LIMIT; req.type_filling = GetPendingFilling(); if(!trade.OrderSend(req, res)) { PrintS("SellLimit error: " + trade.ResultRetcodeDescription() + " (" + IntegerToString((int)res.retcode) + ")"); return false; } if(res.retcode == TRADE_RETCODE_DONE || res.retcode == TRADE_RETCODE_PLACED) { PrintS("SellLimit L" + IntegerToString(level) + " @ " + DoubleToString(req.price, _Digits) + " TP=" + DoubleToString(tp, _Digits) + " #" + IntegerToString((int)res.order)); lastTradePlacedTime = TimeCurrent(); return true; } return false; } //+------------------------------------------------------------------+ //| Handle GetOut mode | //+------------------------------------------------------------------+ void HandleGetOut() { bool getOutLongs = (GetOut=="L"||GetOut=="l"||GetOut=="A"||GetOut=="a"||GetOut=="X"||GetOut=="x"); bool getOutShorts = (GetOut=="S"||GetOut=="s"||GetOut=="A"||GetOut=="a"||GetOut=="X"||GetOut=="x"); if(!bGetOutHandled) { PrintS("GET OUT (" + GetOut + ") — closing " + (getOutLongs ? "L " : "") + (getOutShorts ? "S" : "")); bGetOutHandled = true; } 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) != MagicNum) continue; long ptype = PositionGetInteger(POSITION_TYPE); if(ptype == POSITION_TYPE_BUY && getOutLongs) trade.PositionClose(ticket); if(ptype == POSITION_TYPE_SELL && getOutShorts) trade.PositionClose(ticket); } 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) != MagicNum) continue; trade.OrderDelete(ticket); } gridPlaced = false; SaveGridState(); } //+------------------------------------------------------------------+ //| Master global shutdown — one-shot (1.4) | //+------------------------------------------------------------------+ void GlobalShutdown() { int posCount = 0, ordCount = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(PositionGetInteger(POSITION_MAGIC) != MagicNum) continue; posCount++; trade.PositionClose(ticket); } for(int i = OrdersTotal() - 1; i >= 0; i--) { ulong ticket = OrderGetTicket(i); if(ticket == 0) continue; if(OrderGetInteger(ORDER_MAGIC) != MagicNum) continue; ordCount++; trade.OrderDelete(ticket); } PrintS("MASTER SHUTDOWN — closed " + IntegerToString(posCount) + " pos, " + IntegerToString(ordCount) + " orders"); gridPlaced = false; SaveGridState(); } //+------------------------------------------------------------------+ //| Range Drift (4.6) | //+------------------------------------------------------------------+ bool CheckRangeDrift() { if(!InpRangeDriftEnable) return false; if(InpManualHigh > 0 || InpManualLow > 0) return false; // manual mode — don't drift if(GridHigh <= 0 || GridLow <= 0) return false; double price = SymbolInfoDouble(_Symbol, SYMBOL_BID); double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); double mid = (GridHigh + GridLow) / 2.0; double drift = MathAbs(price - mid) / point; if(drift > InpMoveRangeTrigger) { double shift = price - mid; GridHigh = NormalizeDouble(GridHigh + shift, _Digits); GridLow = NormalizeDouble(GridLow + shift, _Digits); PrintS("Range drift — shifted by " + DoubleToString(shift, _Digits) + " new HIGH=" + DoubleToString(GridHigh, _Digits) + " LOW=" + DoubleToString(GridLow, _Digits)); return true; } return false; } //+------------------------------------------------------------------+ //| Expert initialization | //+------------------------------------------------------------------+ int OnInit() { trade.SetExpertMagicNumber(MagicNum); trade.SetDeviationInPoints(10); trade.SetTypeFilling(GetMarketFilling()); initEquity = (double)BaseEquity; lotDigits = 2; double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); if(minLot > 0) { double l = minLot; lotDigits = 0; while(l < 1 && lotDigits < 6) { l *= 10; lotDigits++; } } if(UseRSIFilter) RSIHandle = iRSI(_Symbol, PERIOD_CURRENT, RSIPeriod, PRICE_CLOSE); if(UseADXFilter) ADXHandle = iADX(_Symbol, PERIOD_CURRENT, ADXPeriod); if(UseATRFilter || InpAdaptiveEntry || UseAutoPivots) ATRHandle = iATR(_Symbol, PERIOD_CURRENT, ATRPeriod); LoadGridState(); if(lastPivotCalcDate != TodayStartBroker()) CalculatePivotPoints(); bGetOutOK = (GetOut=="L"||GetOut=="l"||GetOut=="A"||GetOut=="a"|| GetOut=="S"||GetOut=="s"||GetOut=="N"||GetOut=="n"|| GetOut=="X"||GetOut=="x"); bOpenNewTradesOK = (OpenNewTrades=="Y"||OpenNewTrades=="y"||OpenNewTrades=="L"||OpenNewTrades=="l"|| OpenNewTrades=="N"||OpenNewTrades=="n"||OpenNewTrades=="S"||OpenNewTrades=="s"); bEnableLongs = (OpenNewTrades=="Y"||OpenNewTrades=="y"||OpenNewTrades=="L"||OpenNewTrades=="l"); bEnableShorts = (OpenNewTrades=="Y"||OpenNewTrades=="y"||OpenNewTrades=="S"||OpenNewTrades=="s"); if(!bGetOutOK) PrintS("WARNING: invalid GetOut value: " + GetOut); if(!bOpenNewTradesOK) PrintS("WARNING: invalid OpenNewTrades value: " + OpenNewTrades); PrintS(VERSION + " initialized — Magic=" + IntegerToString(MagicNum) + " SL=" + (InpUseStopLoss ? "ON(" + IntegerToString(StopLoss) + ")" : "OFF") + " AdaptiveEntry=" + (InpAdaptiveEntry ? "ON" : "OFF")); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Expert deinitialization | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { SaveGridState(); if(RSIHandle != INVALID_HANDLE) IndicatorRelease(RSIHandle); if(ADXHandle != INVALID_HANDLE) IndicatorRelease(ADXHandle); if(ATRHandle != INVALID_HANDLE) IndicatorRelease(ATRHandle); PrintS("Deinitialized (reason=" + IntegerToString(reason) + ")"); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // --- Master one-shot (1.4) --- if(Master) { if(!masterShutdownDone) { GlobalShutdown(); masterShutdownDone = true; } return; } // --- Per-EA daily drawdown (3.1B) --- if(!CheckDailyDrawdown()) { if(gridPlaced) { CancelAllOrders("Daily drawdown"); gridPlaced = false; SaveGridState(); } return; } // --- Weekend protection --- if(!CheckWeekendProtection()) return; // --- GetOut mode --- if(GetOut != "N" && GetOut != "n") { HandleGetOut(); return; } else if(bGetOutHandled) bGetOutHandled = false; // --- Pivot recalc (3.8 — reliable, runs once per broker day whenever tick arrives) --- datetime today = TodayStartBroker(); if(today != lastPivotCalcDate) { PrintS("New day — recalc pivots, reset cycle flags"); if(gridPlaced) { CancelAllOrders("End of day"); gridPlaced = false; } if(cycleProfitStop) cycleProfitStop = false; CalculatePivotPoints(); // also updates lastPivotCalcDate & saves } // --- Profit target check runs every tick (cheap) --- if(CheckProfitTarget()) return; // --- Stats + breakeven throttled to every 5s (history scans are expensive) --- static datetime lastStatsTime = 0; if(TimeCurrent() - lastStatsTime >= 5) { CalcAvgPrice(); cyclePartialTPCount = CountPartialTPsSinceCycle(); ApplyBreakeven(); lastStatsTime = TimeCurrent(); } // --- Bar-close gate for expensive checks --- static datetime lastBarTime = 0; datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0); if(currentBarTime == lastBarTime) return; lastBarTime = currentBarTime; // --- Update adaptive-filter state each bar --- UpdateFilterRelaxation(); // --- Session + spread filters --- if(!CheckSessionFilter()) return; if(!CheckSpreadFilter()) return; // --- Range filter --- if(!IsRangingMarket()) { if(gridPlaced) { PrintS("No longer ranging — cancelling grid"); CancelAllOrders("Range filter tripped"); gridPlaced = false; SaveGridState(); } // --- Edge cleanup (5.0): close wrong-side positions stranded outside grid --- double actualHigh = (InpManualHigh > 0) ? InpManualHigh : GridHigh; double actualLow = (InpManualLow > 0) ? InpManualLow : GridLow; double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID); if(currentPrice > actualHigh && actualHigh > 0) ClosePositionsBySide(POSITION_TYPE_SELL, "Edge cleanup — above GridHigh"); else if(currentPrice < actualLow && actualLow > 0) ClosePositionsBySide(POSITION_TYPE_BUY, "Edge cleanup — below GridLow"); return; } // --- Breakout check --- if(IsBreakout()) { CancelAllOrders("Breakout"); CloseAllPositions("Breakout"); EmitCycleReport(false); gridPlaced = false; cycleStartEquity = 0; cycleStartTime = 0; cyclePartialTPCount = 0; SaveGridState(); return; } // --- Range drift (4.6) — re-center if price drifted --- if(gridPlaced && CheckRangeDrift()) { CancelAllOrders("Range drift"); gridPlaced = false; // will replace below } // --- Grid already placed: monitor fills & decide when to reset --- if(gridPlaced) { int buyLimits = CountPendingOrders(ORDER_TYPE_BUY_LIMIT); int sellLimits = CountPendingOrders(ORDER_TYPE_SELL_LIMIT); int buyPos = 0, sellPos = 0; 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) != MagicNum) continue; if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) buyPos++; if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) sellPos++; } // Cancel opposite-side limits once a direction fills (avoid overexposure) if(buyPos > 0 && buyLimits > 0) { 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) != MagicNum) continue; if(OrderGetInteger(ORDER_TYPE) == ORDER_TYPE_BUY_LIMIT) trade.OrderDelete(ticket); } } if(sellPos > 0 && sellLimits > 0) { 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) != MagicNum) continue; if(OrderGetInteger(ORDER_TYPE) == ORDER_TYPE_SELL_LIMIT) trade.OrderDelete(ticket); } } // Cycle closed? if(buyLimits == 0 && sellLimits == 0 && buyPos == 0 && sellPos == 0) { if(InpStopAfterProfit && AccountInfoDouble(ACCOUNT_EQUITY) > cycleStartEquity) { PrintS("Profitable cycle — stop for today"); cycleProfitStop = true; } EmitCycleReport(false); gridPlaced = false; cycleStartEquity = 0; cycleStartTime = 0; cyclePartialTPCount = 0; SaveGridState(); } return; } // --- Block new grids if profit-stop active --- if(cycleProfitStop) { if(DiagnosticModeOn) PrintS("Profit-stop active — no new grid today"); return; } // --- Trade enable checks --- if(OpenNewTrades == "N" || OpenNewTrades == "n") return; if(!bEnableLongs && !bEnableShorts) return; // --- Correlation cap (4.3) --- bool canLong = bEnableLongs && CheckCorrelationCap(true); bool canShort = bEnableShorts && CheckCorrelationCap(false); if(!canLong && !canShort) return; // --- Resolve grid geometry --- double actualHigh = (InpManualHigh > 0) ? InpManualHigh : GridHigh; double actualLow = (InpManualLow > 0) ? InpManualLow : GridLow; if(actualHigh <= actualLow) { PrintS("Invalid grid bounds — skip"); return; } currentEntryPts = ResolveEntryPoints(); double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); double entryPrice = currentEntryPts * point; double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID); // --- Derive effective levels from actual range (3.2) --- double span = actualHigh - actualLow; int derivedLevels = (entryPrice > 0) ? (int)(span / entryPrice) : MaxLevels; int effectiveLevels = MathMin(MaxLevels, MathMax(1, derivedLevels)); PrintS("Placing grid — px=" + DoubleToString(currentPrice, _Digits) + " [" + DoubleToString(actualLow, _Digits) + "-" + DoubleToString(actualHigh, _Digits) + "]" + " entry=" + DoubleToString(currentEntryPts, 0) + "pts" + " levels=" + IntegerToString(effectiveLevels) + (filtersRelaxed ? " (relaxed)" : "")); int buyCount = 0, sellCount = 0; double noTradeZone = entryPrice * 2; for(int i = 0; i < effectiveLevels; i++) { double buyLevel = actualLow + (i * entryPrice); double sellLevel = actualHigh - (i * entryPrice); if(canLong && buyLevel < currentPrice - noTradeZone) if(PlaceBuyLimit(buyLevel, i)) buyCount++; if(canShort && sellLevel > currentPrice + noTradeZone) if(PlaceSellLimit(sellLevel, i)) sellCount++; } PrintS("Grid placed: " + IntegerToString(buyCount) + " buy, " + IntegerToString(sellCount) + " sell limits"); if(buyCount > 0 || sellCount > 0) { gridPlaced = true; cycleStartEquity = AccountInfoDouble(ACCOUNT_EQUITY); cycleStartTime = TimeCurrent(); cyclePartialTPCount = 0; SaveGridState(); } } //+------------------------------------------------------------------+