Paste Shaver

Text Content

//+------------------------------------------------------------------+
//|                                            Lily EA v2.0.mq5      |
//|              Gold Hedge EA for MT5 - Prop Firm Compliant          |
//|              Advanced: News, Sessions, Volume, DXY, ML, MC       |
//+------------------------------------------------------------------+
#property copyright "Lily EA v2.0 - 2026"
#property link      ""
#property version   "2.00"
#property description "Lily v2.0 - Institutional-Grade Gold Hedging EA"
#property description "News Filter | Session Trading | Volume Profile | DXY Correlation"
#property description "Breakout Detection | ML Pattern Recognition | Monte Carlo Validated"

//+------------------------------------------------------------------+
//| Includes                                                         |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\OrderInfo.mqh>
#include <Trade\AccountInfo.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\DealInfo.mqh>

//+------------------------------------------------------------------+
//| EA Identification                                                |
//+------------------------------------------------------------------+
#define EA_NAME       "Lily"
#define EA_VERSION    "2.0"
#define EA_MAGIC      20260220

//+------------------------------------------------------------------+
//| Enums                                                            |
//+------------------------------------------------------------------+
enum ENUM_MARKET_REGIME
  {
   REGIME_RANGING         = 0,
   REGIME_TRENDING_UP     = 1,
   REGIME_TRENDING_DOWN   = 2,
   REGIME_HIGH_VOLATILITY = 3
  };

enum ENUM_SESSION_ACTION
  {
   SESSION_NORMAL         = 0,
   SESSION_CONSERVATIVE   = 1,
   SESSION_AGGRESSIVE     = 2,
   SESSION_PAUSED         = 3
  };

enum ENUM_TRADING_SESSION
  {
   SESSION_ASIAN          = 0,
   SESSION_LONDON         = 1,
   SESSION_NEWYORK        = 2,
   SESSION_LONDON_NY_OVERLAP = 3,
   SESSION_OFF_HOURS      = 4
  };

enum ENUM_SIGNAL_STRENGTH
  {
   SIGNAL_NONE            = 0,
   SIGNAL_WEAK            = 1,
   SIGNAL_MODERATE        = 2,
   SIGNAL_STRONG          = 3,
   SIGNAL_VERY_STRONG     = 4
  };

enum ENUM_LOG_LEVEL
  {
   LOG_CRITICAL           = 0,   // Always: errors, SL hits, trade opens/closes
   LOG_NORMAL             = 1,   // State changes, daily reports, pair creation
   LOG_VERBOSE            = 2    // Signal scores, regime, velocity updates
  };

//+------------------------------------------------------------------+
//| Input Parameters - General Settings                              |
//+------------------------------------------------------------------+
input group "═══ General Settings ═══"
input string InpTradingSymbol       = "XAUUSDm";     // Trading Symbol
input bool   InpEnableVerboseLog    = true;          // Enable Detailed Logging
input bool   InpEnableTradeLogging  = true;          // Enable Trade Event Logging
input int    InpMaxOpenPairs        = 2;             // Maximum Open Hedged Pairs
input int    InpMaxPairsAllowed     = 2;             // Maximum Total Pairs
input ulong  InpMagicNumber         = EA_MAGIC;      // Magic Number

//+------------------------------------------------------------------+
//| Input Parameters - Stop Loss & Take Profit                       |
//+------------------------------------------------------------------+
input group "═══ Stop Loss & Take Profit ═══"
input int    InpStopLossPips        = 250;           // Stop Loss (Pips) - Fallback/Reference
input int    InpTakeProfitPips      = 300;           // Take Profit (Pips) - Fallback/Reference
input double InpReHedgeThresholdPips= 50.0;          // Re-hedge Threshold (Pips)
input int    InpOrphanMonitorMins   = 5;             // Orphan Monitoring Time (Minutes)
input int    InpSLDelayMinutes      = 5;             // Delay After SL Hit (Minutes)

//+------------------------------------------------------------------+
//| Input Parameters - ATR-Based Adaptive SL/TP (Phase 1)            |
//+------------------------------------------------------------------+
input group "═══ ATR-Based Adaptive SL/TP ═══"
input bool   InpUseATR              = true;          // Use ATR for SL/TP (Dynamic)
input double InpATRSLMultiplier     = 3.0;           // ATR × Multiplier = SL
input double InpATRTPMultiplier     = 4.5;           // ATR × Multiplier = TP

//+------------------------------------------------------------------+
//| Input Parameters - Trailing Stop                                 |
//+------------------------------------------------------------------+
input group "═══ Trailing Stop ═══"
input bool   InpEnableTrailing      = true;          // Enable Trailing Stop
input int    InpTrailingStartPips   = 150;           // Start Trailing After (Pips)
input int    InpTrailingStepPips    = 10;            // Trailing Step (Pips)
input int    InpTrailingDistPips    = 100;           // Trailing Distance (Pips)

//+------------------------------------------------------------------+
//| Input Parameters - Partial Close                                 |
//+------------------------------------------------------------------+
input group "═══ Trailing & Partial Close ═══"
input bool   InpEnablePartialClose  = true;          // Enable Partial Close
input double InpPartialClosePercent = 50.0;          // Partial Close Percentage (%)
input int    InpPartialClosePips    = 100;           // Partial Close at Pips Profit
input bool   InpEnablePartialLoss   = true;          // Enable Partial Close on Loss
input int    InpPartialLossPips     = 80;            // Partial Close at Pips Loss
input bool   InpMoveSLToBreakeven   = true;          // Move SL to Breakeven After Partial

//+------------------------------------------------------------------+
//| Input Parameters - Position Sizing                               |
//+------------------------------------------------------------------+
input group "═══ Position Sizing ═══"
input bool   InpUsePercentRisk      = true;          // Use Percentage Risk
input double InpRiskPerTrade        = 0.5;           // Risk Per Trade (%)
input double InpFixedLotSize        = 0.03;          // Fixed Lot Size
input double InpMaxPositionSize     = 0.05;           // Maximum Position Size (Lots)
input double InpMinFreeMargin       = 200.0;         // Minimum Free Margin (USD)

//+------------------------------------------------------------------+
//| Input Parameters - Prop Firm Compliance                          |
//+------------------------------------------------------------------+
input group "═══ Prop Firmfurth Compliance ═══"
input bool   InpPropFirmMode        = true;          // Enable Prop Firm Mode
input double InpMaxDailyLossPercent = 4.0;           // Max Daily Loss (%)
input double InpMaxTotalLossPercent = 8.0;           // Max Total Drawdown (%)
input double InpDailyProfitTarget   = 2.0;           // Daily Profit Target (%)
input double InpInitialBalance      = 0.0;           // Initial Balance (0=Auto)
input bool   InpCloseBeforeWeekend  = true;          // Close Before Weekend
input int    InpFridayCloseHour     = 22;            // Friday Close Hour

//+------------------------------------------------------------------+
//| Input Parameters - Equity Protection                             |
//+------------------------------------------------------------------+
input group "═══ Equity Protection ═══"
input bool   InpEnableEquityProtect = true;          // Enable Equity Protection
input double InpEquityHWMPercent    = 85.0;          // Equity HWM Protection (%)
input double InpMaxBasketDDPercent  = 15.0;          // Max Basket Drawdown (%)

//+------------------------------------------------------------------+
//| Input Parameters - Basket Management                             |
//+------------------------------------------------------------------+
input group "═══ Basket Management ═══"
input bool   InpEnableBasketClose   = true;          // Enable Basket Close
input double InpBasketProfitUSD     = 50.0;          // Basket Profit Target (USD)
input double InpBasketLossUSD       = 80.0;          // Basket Loss Stop (USD)

//+------------------------------------------------------------------+
//| Input Parameters - Market Regime                                 |
//+------------------------------------------------------------------+
input group "═══ Market Regime ═══"
input bool   InpFilterByRegime      = true;          // Filter by Regime
input bool   InpAllowRanging        = true;          // Allow Ranging
input bool   InpAllowTrendUp        = true;          // Allow Uptrend
input bool   InpAllowTrendDown      = true;          // Allow Downtrend
input bool   InpAllowHighVol        = false;         // Allow High Volatility
input int    InpADXPeriod           = 14;            // ADX Period
input double InpTrendThreshold      = 25.0;          // ADX Trend Threshold
input double InpMaxVolatilityPts    = 50000.0;         // Max Volatility (Points)

//+------------------------------------------------------------------+
//| Input Parameters - Session-Based Trading (NEW)                   |
//+------------------------------------------------------------------+
input group "═══ Session-Based Trading ═══"
input bool   InpEnableSessionFilter = true;          // Enable Session Filter
input bool   InpAllowAsianSession   = false;         // Allow Asian Session (00:00-08:00 GMT)
input bool   InpAllowLondonSession  = true;          // Allow London Session (08:00-16:00 GMT)
input bool   InpAllowNYSession      = true;          // Allow NY Session (13:00-21:00 GMT)
input bool   InpPreferOverlap       = true;          // Prefer London/NY Overlap (13:00-16:00)
input double InpOverlapLotMultiplier= 1.3;           // Lot Multiplier During Overlap
input int    InpTimezoneOffset      = 5;             // Timezone offset from GMT (e.g., +5 for Pakistan)

//+------------------------------------------------------------------+
//| Input Parameters - News Filter (NEW)                             |
//+------------------------------------------------------------------+
input group "═══ News Filter ═══"
input bool   InpEnableNewsFilter    = false;          // Enable News Filter
input int    InpNewsMinutesBefore   = 30;            // Stop Trading X Min Before News
input int    InpNewsMinutesAfter    = 15;            // Resume Trading X Min After News
input bool   InpCloseBeforeNews     = false;         // Close Positions Before High-Impact News
input bool   InpUseBuiltInCalendar  = true;          // Use MT5 Built-in Economic Calendar

//+------------------------------------------------------------------+
//| Input Parameters - Dynamic Spread Filter (NEW)                   |
//+------------------------------------------------------------------+
input group "═══ Dynamic Spread Filter ═══"
input bool   InpEnableSpreadFilter  = false;          // Enable Spread Filter
input double InpMaxSpreadMultiplier = 2.5;           // Max Spread as Multiple of Average
input int    InpSpreadSampleSize    = 100;           // Spread Sample Size for Average
input double InpMaxAbsoluteSpread   = 300.0;          // Max Absolute Spread (Points)

//+------------------------------------------------------------------+
//| Input Parameters - DXY Correlation (NEW)                         |
//+------------------------------------------------------------------+
input group "═══ DXY Correlation ═══"
input bool   InpEnableDXYCorrelation= true;          // Enable DXY Correlation Filter
input string InpDXYSymbol           = "USDX";       // DXY/USDX Symbol Name
input int    InpDXYCorrelationBars  = 20;            // Correlation Lookback Bars
input double InpDXYCorrelationMin   = -0.5;          // Min Negative Correlation to Confirm

//+------------------------------------------------------------------+
//| Input Parameters - Breakout Detection (NEW)                      |
//+------------------------------------------------------------------+
input group "═══ Breakout Detection ═══"
input bool   InpEnableBreakout      = true;          // Enable Breakout Detection
input int    InpConsolidationBars   = 20;            // Consolidation Lookback Bars
input double InpConsolidationATRMult= 0.5;           // Consolidation ATR Multiplier
input double InpBreakoutATRMult     = 1.5;           // Breakout Confirmation ATR Multiplier
input int    InpBreakoutVolumeMult  = 150;           // Breakout Volume % of Average

//+------------------------------------------------------------------+
//| Input Parameters - Volume Profile (NEW)                          |
//+------------------------------------------------------------------+
input group "═══ Volume Profile / Order Flow ═══"
input bool   InpEnableVolumeProfile = true;          // Enable Volume Profile Analysis
input int    InpVPLookbackBars      = 100;           // Volume Profile Lookback Bars
input int    InpVPLevels            = 20;            // Number of Price Levels
input double InpVPHVNThreshold      = 1.5;           // High Volume Node Threshold (x avg)
input bool   InpUseDeltaVolume      = true;          // Use Delta Volume Analysis

//+------------------------------------------------------------------+
//| Input Parameters - ML Pattern Recognition (NEW)                  |
//+------------------------------------------------------------------+
input group "═══ ML Pattern Recognition ═══"
input bool   InpEnableML            = true;          // Enable ML Pattern Recognition
input int    InpMLLookback          = 50;            // Pattern Lookback Bars
input int    InpMLPatternLength     = 5;             // Pattern Length (bars)
input double InpMLMinSimilarity     = 0.75;          // Min Pattern Similarity (0-1)
input int    InpMLMinSamples        = 10;            // Min Samples for Prediction
input double InpMLMinWinRate        = 0.55;          // Min Win Rate for Pattern

//+------------------------------------------------------------------+
//| Input Parameters - Self-Learning                                 |
//+------------------------------------------------------------------+
input group "═══ Self-Learning System ═══"
input bool   InpEnableLearning      = true;          // Enable Self-Learning
input int    InpHistoryAnalysisCount= 50;           // Historical Trades to Analyze
input double InpMinWinRatioTarget   = 0.51;          // Target Win Ratio
input bool   InpAdaptiveSLTP        = true;          // Adaptive SL/TP
input bool   InpAdaptiveLotSize     = true;          // Adaptive Lot Sizing

//+------------------------------------------------------------------+
//| Input Parameters - Velocity Re-hedge (P1)                        |
//+------------------------------------------------------------------+
input group "═══ Velocity Re-hedge ═══"
input int    InpVelocitySampleSecs     = 30;            // Velocity Sample Interval (Seconds)
input double InpVelocityNormPipsPerMin = 1.0;           // Velocity Normalization (Pips/Min)

//+------------------------------------------------------------------+
//| Input Parameters - Re-hedge Lot Sizing (P2)                      |
//+------------------------------------------------------------------+
input group "═══ Re-hedge Lot Sizing ═══"
input double InpMaxReHedgeLotMultiplier = 2.0;          // Max Re-hedge Lot Multiplier (1.0=mirror, 2.0=recovery)

//+------------------------------------------------------------------+
//| Input Parameters - Orphan Partial Close (P3)                     |
//+------------------------------------------------------------------+
input group "═══ Orphan Partial Close ═══"
input double InpOrphanPartialClosePips = 15.0;          // Orphan Partial Close at Pips Profit
input double InpOrphanBEBufferPips     = 1.5;           // Breakeven SL Buffer (Pips)

//+------------------------------------------------------------------+
//| Input Parameters - Orphan Drawdown Cap (P4)                      |
//+------------------------------------------------------------------+
input group "═══ Orphan Drawdown Cap ═══"
input double InpOrphanDrawdownL1Pct = 1.0;             // L1: Tighten SLs at % Equity
input double InpOrphanDrawdownL2Pct = 2.0;             // L2: Partial Close 50% at % Equity
input double InpOrphanDrawdownL3Pct = 3.5;             // L3: Full Close Orphans at % Equity
input double InpOrphanSLBufferPips  = 5.0;             // Orphan SL Tightening Buffer (Pips)

//+------------------------------------------------------------------+
//| Input Parameters - Multi-Timeframe                               |
//+------------------------------------------------------------------+
input group "═══ Multi-Timeframe ═══"
input bool   InpUseMTFConfirmation  = true;          // Use MTF Confirmation
input ENUM_TIMEFRAMES InpHTFPeriod  = PERIOD_H4;     // Higher Timeframe
input ENUM_TIMEFRAMES InpMTFPeriod  = PERIOD_H1;     // Medium Timeframe
input ENUM_TIMEFRAMES InpLTFPeriod  = PERIOD_M15;    // Lower Timeframe

//+------------------------------------------------------------------+
//| Input Parameters - Monte Carlo (NEW)                             |
//+------------------------------------------------------------------+
input group "═══ Monte Carlo Validation ═══"
input bool   InpEnableMonteCarlo    = false;          // Enable Monte Carlo Risk Check
input int    InpMCSimulations       = 500;           // Number of Simulations
input double InpMCConfidenceLevel   = 0.95;          // Confidence Level (0.95 = 95%)
input double InpMCMaxDrawdownPct    = 15.0;          // Max Acceptable DD at Confidence Level

//+------------------------------------------------------------------+
//| Input Parameters - Momentum Exhaustion Detection (Phase 3)       |
//+------------------------------------------------------------------+
input group "═══ Momentum Exhaustion Detection ═══"
input bool   InpEnableMomentumExhaustion = true;      // Enable Momentum Exhaustion Filter
input double InpMESBlockThreshold        = 0.50;      // Block trend entries above this MES
input double InpMESFullBlockThreshold    = 0.70;      // Block ALL entries above this MES
input int    InpRSIDivergenceBars        = 10;        // RSI Divergence Lookback (bars)

//+------------------------------------------------------------------+
//| Input Parameters - Re-Entry Cooldown (Phase 2)                   |
//+------------------------------------------------------------------+
input group "═══ Re-Entry Cooldown ═══"
input int    InpReEntryCooldownMin  = 2;              // Re-Entry Cooldown (minutes, 0=off)

//+------------------------------------------------------------------+
//| Input Parameters - Logging (Phase 4)                             |
//+------------------------------------------------------------------+
input group "═══ Logging ═══"
input ENUM_LOG_LEVEL InpLogLevel    = LOG_NORMAL;     // Log Detail Level

//+------------------------------------------------------------------+
//| Structures                                                       |
//+------------------------------------------------------------------+
struct STradePair
  {
   ulong             buyTicket;
   ulong             sellTicket;
   double            lotSize;
   datetime          openTime;
   bool              buyClosed;
   bool              sellClosed;
   bool              buyPartialClosed;
   bool              sellPartialClosed;
   double            buyEntryPrice;
   double            sellEntryPrice;
  };

struct SOrphanedLeg
  {
   ulong             ticket;
   bool              isBuy;
   double            lotSize;
   double            entryPrice;
   datetime          closeTime;
   int               pairIndex;
   double            initialLoss;
   bool              partialClosed;
   datetime          velocityCheckTime;   // P1: timestamp of last velocity sample
   double            pipsAtLastSample;    // P1: signed pip P&L at last sample
   double            pipVelocity;         // P1: EMA-smoothed pips/minute
   bool              partialCloseFired;   // P3: one-time guard for partial close on winners
   int               drawdownLevel;       // P4: highest drawdown level already fired (0/1/2)
  };

struct SLearningData
  {
   int               totalTrades;
   int               winCount;
   int               lossCount;
   double            totalWinPips;
   double            totalLossPips;
   double            winRatio;
   double            avgWinPips;
   double            avgLossPips;
   double            profitFactor;
   double            expectancy;
   double            bestSLPips;
   double            bestTPPips;
   double            optimalRiskPercent;
   int               bestHour;
   int               worstHour;
   double            hourlyWinRate[24];
   int               hourlyTradeCount[24];
   double            regimeWinRate[4];
   int               regimeTradeCount[4];
   int               consecutiveWins;
   int               consecutiveLosses;
   int               maxConsecutiveWins;
   int               maxConsecutiveLosses;
   int               currentStreak;
  };

struct SSessionData
  {
   datetime          sessionStart;
   double            sessionStartBalance;
   double            sessionPeakEquity;
   int               sessionTrades;
   int               sessionWins;
   int               sessionLosses;
   double            sessionProfit;
   double            sessionMaxDD;
   double            dailyStartBalance;
   double            dailyPnL;
   bool              dailyTargetReached;
   ENUM_SESSION_ACTION currentAction;
  };

struct SNewsEvent
  {
   datetime          eventTime;
   string            currency;
   string            eventName;
   int               importance;  // 1=Low, 2=Medium, 3=High
  };

struct SVolumeLevel
  {
   double            price;
   double            volume;
   bool              isHVN;       // High Volume Node
   bool              isLVN;       // Low Volume Node
  };

struct SMLPattern
  {
   double            features[];  // Normalized price changes
   bool              wasWin;
   double            profitPips;
  };

struct SMonteCarloResult
  {
   double            avgReturn;
   double            maxDrawdown;
   double            drawdownAtConfidence;
   double            winRateRange;
   bool              isViable;
  };

struct SBreakoutData
  {
   bool              isConsolidating;
   double            rangeHigh;
   double            rangeLow;
   double            rangeATR;
   bool              breakoutDetected;
   bool              breakoutBullish;
   double            breakoutStrength;
  };

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CTrade            trade;
CPositionInfo     posInfo;
CSymbolInfo       symInfo;
CAccountInfo      accInfo;
CDealInfo         dealInfo;

STradePair        tradePairs[];
SOrphanedLeg      orphanedLegs[];
SLearningData     learning;
SSessionData      session;
SBreakoutData     breakout;
SMonteCarloResult mcResult;

ENUM_MARKET_REGIME currentRegime = REGIME_RANGING;
double            currentVolatilityPts = 0;

// Indicator Handles
int               hADX_MTF = INVALID_HANDLE;
int               hATR_MTF = INVALID_HANDLE;
int               hEMA5    = INVALID_HANDLE;
int               hEMA20   = INVALID_HANDLE;
int               hADX_HTF = INVALID_HANDLE;
int               hEMA5_LTF = INVALID_HANDLE;
int               hEMA20_LTF = INVALID_HANDLE;
int               hRSI_MTF = INVALID_HANDLE;
int               hATR_LTF = INVALID_HANDLE;
int               hBB_MTF  = INVALID_HANDLE;  // Bollinger Bands for breakout

// DXY Indicator Handles
int               hDXY_EMA5  = INVALID_HANDLE;
int               hDXY_EMA20 = INVALID_HANDLE;

// SL Tracking
int               consecutiveSLHits = 0;
datetime          lastThreeSLTimes[3];
bool              tradingPausedDueToSL = false;
bool              inSLTradeDelay = false;
datetime          lastSLHitTime = 0;


// State
datetime          lastRefreshTime = 0;
double            peakEquity = 0;
datetime          lastTradeTime = 0;
bool              learningInitialized = false;
double            initialAccountBalance = 0;
string            tradingSymbol = "";
bool              dxyAvailable = false;
bool              dailyTradingHalted = false; // Phase 5: Prop firm stop

// Spread tracking
double            spreadHistory[];
int               spreadHistoryIndex = 0;
double            averageSpread = 0;
bool              spreadInitialized = false;

// News tracking
SNewsEvent        upcomingNews[];
datetime          lastNewsCheck = 0;
bool              newsFilterActive = false;

// Rate-limited logging timestamps (prevents disk full from per-tick logging)
datetime          _lastLogTime_news = 0;       // News filter log throttle
datetime          _lastLogTime_session = 0;    // Session filter log throttle
datetime          _lastLogTime_trade = 0;      // Trade block log throttle
datetime          _lastLogTime_diag = 0;       // Periodic diagnostic throttle
datetime          _lastLogTime_signal = 0;     // Signal score log throttle
int               logThrottleIntervalSec = 300;  // Log once per 5 min (was 60)

// Trade diagnostic visibility
string            lastTradeBlockReason = "";   // Why trading is currently blocked

// Volume Profile
SVolumeLevel      volumeLevels[];
double            pocPrice = 0;        // Point of Control
double            vahPrice = 0;        // Value Area High
double            valPrice = 0;        // Value Area Low

// ML Pattern Database
SMLPattern        patternDB[];
int               patternDBSize = 0;

// Partial close tracking
struct SPartialCloseTracker
  {
   ulong             ticket;
   bool              closed;
  };
SPartialCloseTracker partialCloseTracker[];

// Signal scoring
double            signalScore = 0;
ENUM_SIGNAL_STRENGTH signalStrength = SIGNAL_NONE;

// Phase 3: Momentum Exhaustion Detection
double            momentumExhaustionScore = 0.0;
bool              mesBlockBuy  = false;
bool              mesBlockSell = false;

// Phase 4: Log deduplication buffer
string            _lastLogMessages[10];
int               _lastLogMsgIndex = 0;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   Print("═══════════════════════════════════════════");
   Print("  Initializing ", EA_NAME, " EA v", EA_VERSION);
   Print("═══════════════════════════════════════════");

//--- Validate symbol
   tradingSymbol = InpTradingSymbol;
   if(!SymbolSelect(tradingSymbol, true))
     {
      string variations[] = {"XAUUSD", "XAUUSDc", "XAUUSD.", "XAUUSD.a", "GOLD", "GOLDc"};
      bool found = false;
      for(int i = 0; i < ArraySize(variations); i++)
        {
         if(SymbolSelect(variations[i], true))
           {
            tradingSymbol = variations[i];
            found = true;
            break;
           }
        }
      if(!found)
         tradingSymbol = Symbol();
     }

//--- Initialize symbol info
   if(!symInfo.Name(tradingSymbol))
     {
      Print("ERROR: Cannot initialize symbol: ", tradingSymbol);
      return(INIT_FAILED);
     }
   symInfo.Refresh();

//--- Setup trade object
   trade.SetExpertMagicNumber(InpMagicNumber);
   trade.SetDeviationInPoints(30);
   trade.SetTypeFilling(GetFillingMode(tradingSymbol));
   trade.SetMarginMode();

//--- Initialize arrays
   ArrayResize(tradePairs, 0);
   ArrayResize(orphanedLegs, 0);
   ArrayResize(partialCloseTracker, 0);
   ArrayResize(upcomingNews, 0);
   ArrayResize(volumeLevels, 0);
   ArrayResize(patternDB, 0);

//--- Initialize spread history
   ArrayResize(spreadHistory, InpSpreadSampleSize);
   ArrayInitialize(spreadHistory, 0);
   spreadHistoryIndex = 0;
   spreadInitialized = false;

//--- Initialize indicators
   if(!InitializeIndicators())
     {
      Print("ERROR: Failed to initialize indicators");
      return(INIT_FAILED);
     }

//--- Check DXY availability
   if(InpEnableDXYCorrelation)
      InitializeDXY();

//--- Initialize state
   ResetSLTracking();
   peakEquity = accInfo.Equity();
   initialAccountBalance = (InpInitialBalance > 0) ? InpInitialBalance : accInfo.Balance();

//--- Initialize subsystems
   InitializeSession();
   if(InpEnableLearning)
      InitializeLearning();
   DetectMarketRegime();
   ScanExistingPositions();

//--- Run Monte Carlo validation on startup
   if(InpEnableMonteCarlo && learningInitialized && learning.totalTrades >= 30)
      RunMonteCarloValidation();

//--- Set timer
   EventSetTimer(1);

   Print("Symbol: ", tradingSymbol, " | Balance: ", DoubleToString(initialAccountBalance, 2));
   Print("Prop Firm: ", InpPropFirmMode ? "ON" : "OFF",
         " | News Filter: ", InpEnableNewsFilter ? "ON" : "OFF",
         " | Sessions: ", InpEnableSessionFilter ? "ON" : "OFF");
   Print("DXY: ", dxyAvailable ? "ON" : "OFF",
         " | Breakout: ", InpEnableBreakout ? "ON" : "OFF",
         " | ML: ", InpEnableML ? "ON" : "OFF");
   Print("═══════════════════════════════════════════");

   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   EventKillTimer();
   Comment("");

   int handles[] = {hADX_MTF, hATR_MTF, hEMA5, hEMA20, hADX_HTF,
                    hEMA5_LTF, hEMA20_LTF, hRSI_MTF, hATR_LTF,
                    hBB_MTF, hDXY_EMA5, hDXY_EMA20
                   };
   for(int i = 0; i < ArraySize(handles); i++)
     {
      if(handles[i] != INVALID_HANDLE)
         IndicatorRelease(handles[i]);
     }

   Print(EA_NAME, " v", EA_VERSION, " stopped. Reason: ", reason);
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   symInfo.Refresh();
   symInfo.RefreshRates();

//--- Update spread tracking
   UpdateSpreadHistory();

//--- Update equity
   UpdateEquityTracking();

//--- Periodic refresh
   if(TimeCurrent() - lastRefreshTime > 300)
     {
      RefreshEA();
      // Phase 4: Removed noisy "EA state refreshed" log
     }

//--- Prop firm checks
   if(InpPropFirmMode)
     {
      UpdateDailyPnL();
      if(IsDailyLossLimitBreached())
        {
         if(!dailyTradingHalted)
           {
            CloseAllPositions("Daily loss limit");
            dailyTradingHalted = true;
           }
         return;
        }
      if(IsTotalDrawdownBreached())
        {
         CloseAllPositions("Total drawdown limit");
         return;
        }
      if(InpCloseBeforeWeekend && IsFridayCloseTime())
        {
         CloseAllPositions("Weekend close");
         return;
        }
     }

//--- News filter check
   if(InpEnableNewsFilter)
      UpdateNewsFilter();

//--- Check positions
   CheckPositionsStatus();

//--- Manage orphaned legs
   if(ArraySize(orphanedLegs) > 0)
     {
      ManageOrphanedLegs();
      CheckOrphanDrawdown();
     }

//--- Trailing stops
   if(InpEnableTrailing)
      ManageTrailingStops();

//--- Partial closes
   if(InpEnablePartialClose)
      ManagePartialCloses();

//--- Basket management
   if(InpEnableBasketClose)
      CheckBasketClose();

//--- Update analysis (every tick for responsiveness)
   if(InpEnableBreakout)
      DetectBreakout();
   if(InpEnableVolumeProfile)
      UpdateVolumeProfile();

//--- Phase 3: Update Momentum Exhaustion Detection
   UpdateMomentumExhaustion();

//--- Always calculate signal score (for chart display even when blocked)
   CalculateSignalScore();

//--- Phase 5: Dynamic deviation (ATR-based slippage tolerance)
   SetDynamicDeviation();

//--- Trade entry logic
//--- NOTE: CompleteIncompletePair removed. ManageOrphanedLegs() (above) is
//--- the sole controller for orphan trades. It uses velocity-weighted re-hedging
//--- (P1), dynamic lot sizing (P2), winner partial close (P3), and drawdown cap (P4).
//--- Opening a counter-leg while the orphan is in PROFIT caused simultaneous hedges.
   int totalPairsCount = GetCompletePairsCount() + ArraySize(orphanedLegs);
   if(totalPairsCount == 0 && IsTradeAllowed())
     {
      //--- Only open a brand-new pair when there are truly NO open positions
      if(signalStrength >= SIGNAL_MODERATE)
        {
         CreateHedgedPair();
        }
      else
        {
         VerboseLogThrottled("Signal too weak: " + DoubleToString(signalScore, 2) +
                    " (" + SignalStrengthToString(signalStrength) + ")",
                    _lastLogTime_signal, logThrottleIntervalSec);
        }
     }
   else if(totalPairsCount == 0)
     {
      // Throttled trade diagnostic log
      if(TimeCurrent() - _lastLogTime_diag > logThrottleIntervalSec)
        {
         _lastLogTime_diag = TimeCurrent();
         PrintTradeAttemptDiagnostic();
        }
     }

//--- Update display
   UpdateChartComment();
  }

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
   if(inSLTradeDelay)
     {
      if(TimeCurrent() - lastSLHitTime > InpSLDelayMinutes * 60)
        {
         inSLTradeDelay = false;
         tradingPausedDueToSL = false;
         VerboseLog("SL delay expired, trading resumed");
        }
     }

//--- New day check
   static datetime lastDay = 0;
   MqlDateTime dt;
   TimeToStruct(TimeCurrent(), dt);
   datetime today = StringToTime(IntegerToString(dt.year) + "." +
                                 IntegerToString(dt.mon) + "." +
                                 IntegerToString(dt.day));
   if(today != lastDay)
     {
      lastDay = today;
      dailyTradingHalted = false;
      OnNewDay();
     }

//--- Periodic news update (every 5 minutes)
   if(InpEnableNewsFilter && TimeCurrent() - lastNewsCheck > 300)
      LoadNewsEvents();
  }

//+------------------------------------------------------------------+
//| Trade transaction handler                                        |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest &request,
                        const MqlTradeResult &result)
  {
   if(trans.type == TRADE_TRANSACTION_DEAL_ADD)
     {
      ulong dealTicket = trans.deal;
      if(dealTicket > 0 && HistoryDealSelect(dealTicket))
        {
         long dealMagic = HistoryDealGetInteger(dealTicket, DEAL_MAGIC);
         string dealSymbol = HistoryDealGetString(dealTicket, DEAL_SYMBOL);
         ENUM_DEAL_ENTRY dealEntry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(dealTicket, DEAL_ENTRY);

         if(dealMagic == (long)InpMagicNumber && dealSymbol == tradingSymbol && dealEntry == DEAL_ENTRY_OUT)
           {
            double dealProfit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT) +
                                HistoryDealGetDouble(dealTicket, DEAL_SWAP) +
                                HistoryDealGetDouble(dealTicket, DEAL_COMMISSION);
            double dealVolume = HistoryDealGetDouble(dealTicket, DEAL_VOLUME);

            session.sessionTrades++;
            session.sessionProfit += dealProfit;

            if(dealProfit > 0)
              {
               session.sessionWins++;
               if(InpEnableLearning)
                  UpdateLearningWin(dealProfit, dealVolume, dealTicket);
              }
            else
              {
               session.sessionLosses++;
               if(InpEnableLearning)
                  UpdateLearningLoss(dealProfit, dealVolume, dealTicket);
              }

            //--- Update ML pattern database
            if(InpEnableML)
               RecordMLPattern(dealProfit > 0, dealProfit, dealVolume);
           }
        }
     }
  }

//+------------------------------------------------------------------+
//| Initialize Indicators                                            |
//+------------------------------------------------------------------+
bool InitializeIndicators()
  {
   hADX_MTF = iADX(tradingSymbol, InpMTFPeriod, InpADXPeriod);
   if(hADX_MTF == INVALID_HANDLE)
      return false;

   hATR_MTF = iATR(tradingSymbol, InpMTFPeriod, 14);
   if(hATR_MTF == INVALID_HANDLE)
      return false;

   hEMA5 = iMA(tradingSymbol, PERIOD_CURRENT, 5, 0, MODE_EMA, PRICE_CLOSE);
   if(hEMA5 == INVALID_HANDLE)
      return false;

   hEMA20 = iMA(tradingSymbol, PERIOD_CURRENT, 20, 0, MODE_EMA, PRICE_CLOSE);
   if(hEMA20 == INVALID_HANDLE)
      return false;

   hRSI_MTF = iRSI(tradingSymbol, InpMTFPeriod, 14, PRICE_CLOSE);

   hATR_LTF = iATR(tradingSymbol, InpLTFPeriod, 14);

   hBB_MTF = iBands(tradingSymbol, InpMTFPeriod, 20, 0, 2.0, PRICE_CLOSE);

   if(InpUseMTFConfirmation)
     {
      hADX_HTF = iADX(tradingSymbol, InpHTFPeriod, InpADXPeriod);
      hEMA5_LTF = iMA(tradingSymbol, InpLTFPeriod, 5, 0, MODE_EMA, PRICE_CLOSE);
      hEMA20_LTF = iMA(tradingSymbol, InpLTFPeriod, 20, 0, MODE_EMA, PRICE_CLOSE);
     }

   return true;
  }

//+------------------------------------------------------------------+
//| Initialize DXY Correlation                                       |
//+------------------------------------------------------------------+
void InitializeDXY()
  {
   dxyAvailable = false;

// Try common DXY symbol names
   string dxyNames[] = {"USDX", "DXY", "DX", "USDX.a", "DXY.a", "USDindex"};

// First try the user-specified symbol
   if(InpDXYSymbol != "" && SymbolSelect(InpDXYSymbol, true))
     {
      hDXY_EMA5 = iMA(InpDXYSymbol, InpMTFPeriod, 5, 0, MODE_EMA, PRICE_CLOSE);
      hDXY_EMA20 = iMA(InpDXYSymbol, InpMTFPeriod, 20, 0, MODE_EMA, PRICE_CLOSE);
      if(hDXY_EMA5 != INVALID_HANDLE && hDXY_EMA20 != INVALID_HANDLE)
        {
         dxyAvailable = true;
         Print("DXY correlation enabled using: ", InpDXYSymbol);
         return;
        }
     }

// Try alternatives
   for(int i = 0; i < ArraySize(dxyNames); i++)
     {
      if(SymbolSelect(dxyNames[i], true))
        {
         hDXY_EMA5 = iMA(dxyNames[i], InpMTFPeriod, 5, 0, MODE_EMA, PRICE_CLOSE);
         hDXY_EMA20 = iMA(dxyNames[i], InpMTFPeriod, 20, 0, MODE_EMA, PRICE_CLOSE);
         if(hDXY_EMA5 != INVALID_HANDLE && hDXY_EMA20 != INVALID_HANDLE)
           {
            dxyAvailable = true;
            Print("DXY correlation enabled using: ", dxyNames[i]);
            return;
           }
        }
     }

   Print("WARNING: DXY symbol not available - correlation filter disabled");
  }

//+------------------------------------------------------------------+
//| Get Filling Mode                                                 |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE_FILLING GetFillingMode(string symbol)
  {
   long fillMode = SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE);
   if((fillMode & SYMBOL_FILLING_FOK) == SYMBOL_FILLING_FOK)
      return ORDER_FILLING_FOK;
   if((fillMode & SYMBOL_FILLING_IOC) == SYMBOL_FILLING_IOC)
      return ORDER_FILLING_IOC;
   return ORDER_FILLING_RETURN;
  }

//+------------------------------------------------------------------+
//| Initialize Session                                               |
//+------------------------------------------------------------------+
void InitializeSession()
  {
   session.sessionStart = TimeCurrent();
   session.sessionStartBalance = accInfo.Balance();
   session.sessionPeakEquity = accInfo.Equity();
   session.sessionTrades = 0;
   session.sessionWins = 0;
   session.sessionLosses = 0;
   session.sessionProfit = 0;
   session.sessionMaxDD = 0;
   session.dailyStartBalance = accInfo.Balance();
   session.dailyPnL = 0;
   session.dailyTargetReached = false;
   session.currentAction = SESSION_NORMAL;
  }

//+------------------------------------------------------------------+
//| New Day Handler                                                  |
//+------------------------------------------------------------------+
void OnNewDay()
  {
   VerboseLog("═══ NEW DAY ═══");
   session.dailyStartBalance = accInfo.Balance();
   session.dailyPnL = 0;
   session.dailyTargetReached = false;
   session.currentAction = SESSION_NORMAL;

   if(InpEnableLearning)
      InitializeLearning();
   if(InpEnableMonteCarlo && learningInitialized && learning.totalTrades >= 30)
      RunMonteCarloValidation();
   if(InpEnableNewsFilter)
      LoadNewsEvents();
  }

//+------------------------------------------------------------------+
//| Reset SL Tracking                                                |
//+------------------------------------------------------------------+
void ResetSLTracking()
  {
   consecutiveSLHits = 0;
   ArrayInitialize(lastThreeSLTimes, 0);
   tradingPausedDueToSL = false;
   inSLTradeDelay = false;
   lastSLHitTime = 0;
  }

//+------------------------------------------------------------------+
//| ═══════════════════════════════════════════════════════════════    |
//| SESSION-BASED TRADING                                            |
//| ═══════════════════════════════════════════════════════════════    |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Get Current Trading Session                                      |
//+------------------------------------------------------------------+
ENUM_TRADING_SESSION GetCurrentSession()
  {
   MqlDateTime dt;
   TimeToStruct(TimeGMT(), dt);
   int gmtHour = dt.hour;

// Apply timezone offset (e.g., +5 for Pakistan = GMT+5)
   int localHour = (gmtHour + InpTimezoneOffset + 24) % 24;

// London/NY Overlap: 13:00-16:00 GMT (8:00-11:00 Pakistan time)
   if(localHour >= 8 && localHour < 11)
      return SESSION_LONDON_NY_OVERLAP;

// London: 08:00-16:00 GMT (13:00-21:00 Pakistan time)
   if(localHour >= 13 && localHour < 21)
      return SESSION_LONDON;

// New York: 13:00-21:00 GMT (18:00-02:00 Pakistan time)
   if((localHour >= 18 && localHour < 24) || (localHour >= 0 && localHour < 2))
      return SESSION_NEWYORK;

// Asian: 00:00-08:00 GMT (5:00-13:00 Pakistan time)
   if(localHour >= 5 && localHour < 13)
      return SESSION_ASIAN;

   return SESSION_OFF_HOURS;
  }

//+------------------------------------------------------------------+
//| Get Current Trading Session (GMT for reference)                  |
//+------------------------------------------------------------------+
ENUM_TRADING_SESSION GetCurrentSessionGMT()
  {
   MqlDateTime dt;
   TimeToStruct(TimeGMT(), dt);
   int gmtHour = dt.hour;

// London/NY Overlap: 13:00-16:00 GMT
   if(gmtHour >= 13 && gmtHour < 16)
      return SESSION_LONDON_NY_OVERLAP;

// London: 08:00-16:00 GMT
   if(gmtHour >= 8 && gmtHour < 16)
      return SESSION_LONDON;

// New York: 13:00-21:00 GMT
   if(gmtHour >= 13 && gmtHour < 21)
      return SESSION_NEWYORK;

// Asian: 00:00-08:00 GMT
   if(gmtHour >= 0 && gmtHour < 8)
      return SESSION_ASIAN;

   return SESSION_OFF_HOURS;
  }

//+------------------------------------------------------------------+
//| Is Session Allowed                                               |
//+------------------------------------------------------------------+
bool IsSessionAllowed()
  {
   if(!InpEnableSessionFilter)
      return true;

   ENUM_TRADING_SESSION currentSession = GetCurrentSession();

   switch(currentSession)
     {
      case SESSION_LONDON_NY_OVERLAP:
         return InpAllowLondonSession || InpAllowNYSession;
      case SESSION_LONDON:
         return InpAllowLondonSession;
      case SESSION_NEWYORK:
         return InpAllowNYSession;
      case SESSION_ASIAN:
         return InpAllowAsianSession;
      default:
         return false;
     }
  }

//+------------------------------------------------------------------+
//| Get Session Name                                                 |
//+------------------------------------------------------------------+
string GetSessionName(ENUM_TRADING_SESSION sess)
  {
   switch(sess)
     {
      case SESSION_ASIAN:
         return "Asian";
      case SESSION_LONDON:
         return "London";
      case SESSION_NEWYORK:
         return "New York";
      case SESSION_LONDON_NY_OVERLAP:
         return "London/NY Overlap";
      default:
         return "Off Hours";
     }
  }

//+------------------------------------------------------------------+
//| Get Session Name (GMT for reference)                             |
//+------------------------------------------------------------------+
string GetSessionNameGMT(ENUM_TRADING_SESSION sess)
  {
   switch(sess)
     {
      case SESSION_ASIAN:
         return "Asian (GMT)";
      case SESSION_LONDON:
         return "London (GMT)";
      case SESSION_NEWYORK:
         return "New York (GMT)";
      case SESSION_LONDON_NY_OVERLAP:
         return "London/NY Overlap (GMT)";
      default:
         return "Off Hours (GMT)";
     }
  }

//+------------------------------------------------------------------+
//| ═══════════════════════════════════════════════════════════════    |
//| NEWS FILTER                                                      |
//| ═══════════════════════════════════════════════════════════════    |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Load News Events from MT5 Calendar                               |
//+------------------------------------------------------------------+
void LoadNewsEvents()
  {
   lastNewsCheck = TimeCurrent();
   ArrayResize(upcomingNews, 0);

   if(!InpUseBuiltInCalendar)
      return;

   datetime fromTime = TimeCurrent();
   datetime toTime = fromTime + 86400; // Next 24 hours

   MqlCalendarValue values[];
   int count = CalendarValueHistory(values, fromTime, toTime);

   if(count <= 0)
      return;

//--- Load events, skip CalendarCountryById to avoid deserialization error
   int added = 0;
   for(int i = 0; i < count; i++)
     {
      MqlCalendarEvent event;
      if(!CalendarEventById(values[i].event_id, event))
         continue;

      // Skip country lookup to avoid "countries file deserialization" error
      // Filter by event importance only (MODERATE and HIGH impact)
      if(event.importance < CALENDAR_IMPORTANCE_MODERATE)
         continue;

      // Cap at 50 events to prevent memory bloat
      if(added >= 50)
         break;

      int idx = ArraySize(upcomingNews);
      ArrayResize(upcomingNews, idx + 1);
      upcomingNews[idx].eventTime = values[i].time;
      upcomingNews[idx].currency = "USD"; // Default - country lookup skipped
      upcomingNews[idx].eventName = event.name;
      upcomingNews[idx].importance = (int)event.importance;
      added++;
     }

   VerboseLog("Loaded " + IntegerToString(added) + " upcoming news events");
  }

//+------------------------------------------------------------------+
//| Update News Filter Status                                        |
//+------------------------------------------------------------------+
void UpdateNewsFilter()
  {
   newsFilterActive = false;

   if(!InpEnableNewsFilter)
      return;

   datetime now = TimeCurrent();

//--- Prune expired news events (past filterEnd) to keep array small
   for(int i = ArraySize(upcomingNews) - 1; i >= 0; i--)
     {
      int maxAfterMin = InpNewsMinutesAfter;
      if(upcomingNews[i].importance >= (int)CALENDAR_IMPORTANCE_HIGH)
         maxAfterMin = InpNewsMinutesAfter * 2;
      datetime expiry = upcomingNews[i].eventTime + maxAfterMin * 60;
      if(now > expiry)
        {
         // Remove expired event by shifting array
         for(int j = i; j < ArraySize(upcomingNews) - 1; j++)
            upcomingNews[j] = upcomingNews[j + 1];
         ArrayResize(upcomingNews, ArraySize(upcomingNews) - 1);
        }
     }

//--- Check remaining events for active filter window
   for(int i = 0; i < ArraySize(upcomingNews); i++)
     {
      datetime eventTime = upcomingNews[i].eventTime;
      int importance = upcomingNews[i].importance;

      // Only filter for high-impact events (importance >= 3)
      int minutesBefore = InpNewsMinutesBefore;
      int minutesAfter = InpNewsMinutesAfter;

      // Extend buffer for very high impact (NFP, FOMC, CPI)
      if(importance >= (int)CALENDAR_IMPORTANCE_HIGH)
        {
         minutesBefore = InpNewsMinutesBefore * 2;
         minutesAfter = InpNewsMinutesAfter * 2;
        }

      datetime filterStart = eventTime - minutesBefore * 60;
      datetime filterEnd = eventTime + minutesAfter * 60;

      if(now >= filterStart && now <= filterEnd)
        {
         newsFilterActive = true;

         // THROTTLED: only log once every 5 minutes instead of every tick
         VerboseLogThrottled("NEWS FILTER ACTIVE: " + upcomingNews[i].eventName +
                    " (" + upcomingNews[i].currency + ") at " +
                    TimeToString(eventTime, TIME_MINUTES),
                    _lastLogTime_news, logThrottleIntervalSec);

         // Close positions before high-impact news if configured
         if(InpCloseBeforeNews && importance >= (int)CALENDAR_IMPORTANCE_HIGH &&
            now >= filterStart && now < eventTime)
           {
            CloseAllPositions("High-impact news approaching: " + upcomingNews[i].eventName);
           }
         return;
        }
     }
  }

//+------------------------------------------------------------------+
//| Is News Filter Active                                            |
//+------------------------------------------------------------------+
bool IsNewsFilterBlocking()
  {
   return InpEnableNewsFilter && newsFilterActive;
  }

//+------------------------------------------------------------------+
//| ═══════════════════════════════════════════════════════════════    |
//| DYNAMIC SPREAD FILTER                                            |
//| ═══════════════════════════════════════════════════════════════    |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Update Spread History                                            |
//+------------------------------------------------------------------+
void UpdateSpreadHistory()
  {
   double currentSpread = (double)symInfo.Spread();

   spreadHistory[spreadHistoryIndex] = currentSpread;
   spreadHistoryIndex = (spreadHistoryIndex + 1) % InpSpreadSampleSize;

// Calculate average
   double sum = 0;
   int validCount = 0;
   for(int i = 0; i < InpSpreadSampleSize; i++)
     {
      if(spreadHistory[i] > 0)
        {
         sum += spreadHistory[i];
         validCount++;
        }
     }

   if(validCount > 0)
     {
      averageSpread = sum / validCount;
      if(validCount >= InpSpreadSampleSize / 2)
         spreadInitialized = true;
     }
  }

//+------------------------------------------------------------------+
//| Is Spread Acceptable                                             |
//+------------------------------------------------------------------+
bool IsSpreadAcceptable()
  {
   if(!InpEnableSpreadFilter)
      return true;

   double currentSpread = (double)symInfo.Spread();

// Absolute spread check
   if(currentSpread > InpMaxAbsoluteSpread)
     {
      VerboseLog("Spread too high (absolute): " + DoubleToString(currentSpread, 1) +
                 " > " + DoubleToString(InpMaxAbsoluteSpread, 1));
      return false;
     }

// Relative spread check
   if(spreadInitialized && averageSpread > 0)
     {
      if(currentSpread > averageSpread * InpMaxSpreadMultiplier)
        {
         VerboseLog("Spread too high (relative): " + DoubleToString(currentSpread, 1) +
                    " > " + DoubleToString(averageSpread * InpMaxSpreadMultiplier, 1) +
                    " (avg=" + DoubleToString(averageSpread, 1) + ")");
         return false;
        }
     }

   return true;
  }

//+------------------------------------------------------------------+
//| ═══════════════════════════════════════════════════════════════    |
//| DXY CORRELATION                                                  |
//| ═══════════════════════════════════════════════════════════════    |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Get DXY Trend Direction                                          |
//| Returns: 1=DXY Bullish, -1=DXY Bearish, 0=Neutral/Unavailable   |
//+------------------------------------------------------------------+
int GetDXYTrend()
  {
   if(!InpEnableDXYCorrelation || !dxyAvailable)
      return 0;

   double dxyEma5[], dxyEma20[];
   ArraySetAsSeries(dxyEma5, true);
   ArraySetAsSeries(dxyEma20, true);

   if(CopyBuffer(hDXY_EMA5, 0, 0, 3, dxyEma5) < 3)
      return 0;
   if(CopyBuffer(hDXY_EMA20, 0, 0, 3, dxyEma20) < 3)
      return 0;

   if(dxyEma5[0] > dxyEma20[0])
      return 1;   // DXY bullish = Gold bearish
   if(dxyEma5[0] < dxyEma20[0])
      return -1;  // DXY bearish = Gold bullish

   return 0;
  }

//+------------------------------------------------------------------+
//| Calculate Gold-DXY Correlation                                   |
//+------------------------------------------------------------------+
double CalculateDXYCorrelation()
  {
   if(!InpEnableDXYCorrelation || !dxyAvailable)
      return 0;

   double goldClose[], dxyClose[];
   ArraySetAsSeries(goldClose, true);
   ArraySetAsSeries(dxyClose, true);

   int bars = InpDXYCorrelationBars;
   if(CopyClose(tradingSymbol, InpMTFPeriod, 0, bars, goldClose) < bars)
      return 0;

   string dxySymbol = InpDXYSymbol;
   if(!dxyAvailable)
      return 0;

// Try to get DXY close prices
   string dxyNames[] = {"USDX", "DXY", "DX", "USDX.a", "DXY.a", "USDindex"};
   bool gotData = false;

   if(CopyClose(InpDXYSymbol, InpMTFPeriod, 0, bars, dxyClose) >= bars)
      gotData = true;

   if(!gotData)
     {
      for(int i = 0; i < ArraySize(dxyNames); i++)
        {
         if(CopyClose(dxyNames[i], InpMTFPeriod, 0, bars, dxyClose) >= bars)
           {
            gotData = true;
            break;
           }
        }
     }

   if(!gotData)
      return 0;

// Calculate Pearson correlation
   double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0, sumY2 = 0;
   int n = bars;

   for(int i = 0; i < n; i++)
     {
      sumX += goldClose[i];
      sumY += dxyClose[i];
      sumXY += goldClose[i] * dxyClose[i];
      sumX2 += goldClose[i] * goldClose[i];
      sumY2 += dxyClose[i] * dxyClose[i];
     }

   double denominator = MathSqrt((n * sumX2 - sumX * sumX) * (n * sumY2 - sumY * sumY));
   if(denominator == 0)
      return 0;

   return (n * sumXY - sumX * sumY) / denominator;
  }

//+------------------------------------------------------------------+
//| Does DXY Confirm Gold Direction                                  |
//+------------------------------------------------------------------+
bool DoesDXYConfirmDirection(bool isBuyGold)
  {
   if(!InpEnableDXYCorrelation || !dxyAvailable)
      return true; // No filter = always confirm

   int dxyTrend = GetDXYTrend();

// Gold and DXY are inversely correlated
// Buy Gold = expect DXY to fall (dxyTrend = -1)
// Sell Gold = expect DXY to rise (dxyTrend = 1)
   if(isBuyGold && dxyTrend == -1)
      return true;  // DXY falling, good for Gold buy
   if(!isBuyGold && dxyTrend == 1)
      return true;  // DXY rising, good for Gold sell
   if(dxyTrend == 0)
      return true;  // Neutral, allow

   VerboseLog("DXY conflict: Gold " + (isBuyGold ? "BUY" : "SELL") +
              " but DXY trend=" + IntegerToString(dxyTrend));
   return false;
  }

//+------------------------------------------------------------------+
//| ═══════════════════════════════════════════════════════════════    |
//| BREAKOUT DETECTION                                               |
//| ═══════════════════════════════════════════════════════════════    |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Detect Breakout                                                  |
//+------------------------------------------------------------------+
void DetectBreakout()
  {
   breakout.breakoutDetected = false;
   breakout.isConsolidating = false;

   double high[], low[], close[];
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low, true);
   ArraySetAsSeries(close, true);

   int bars = InpConsolidationBars;
   if(CopyHigh(tradingSymbol, InpMTFPeriod, 0, bars + 5, high) < bars + 5)
      return;
   if(CopyLow(tradingSymbol, InpMTFPeriod, 0, bars + 5, low) < bars + 5)
      return;
   if(CopyClose(tradingSymbol, InpMTFPeriod, 0, bars + 5, close) < bars + 5)
      return;

// Get ATR for reference
   double atrVal[];
   ArraySetAsSeries(atrVal, true);
   if(CopyBuffer(hATR_MTF, 0, 0, 3, atrVal) < 3)
      return;

   double atr = atrVal[0];

// Calculate consolidation range (bars 1 to bars, excluding current)
   double rangeHigh = high[1];
   double rangeLow = low[1];
   for(int i = 2; i <= bars; i++)
     {
      if(high[i] > rangeHigh)
         rangeHigh = high[i];
      if(low[i] < rangeLow)
         rangeLow = low[i];
     }

   double range = rangeHigh - rangeLow;
   breakout.rangeHigh = rangeHigh;
   breakout.rangeLow = rangeLow;
   breakout.rangeATR = atr;

// Check if market is consolidating (range < ATR * multiplier * bars)
   double expectedRange = atr * InpConsolidationATRMult * MathSqrt((double)bars);
   breakout.isConsolidating = (range < expectedRange);

// Check for breakout on current bar
   double currentClose = close[0];
   double breakoutThreshold = atr * InpBreakoutATRMult;

   if(currentClose > rangeHigh + breakoutThreshold)
     {
      breakout.breakoutDetected = true;
      breakout.breakoutBullish = true;
      breakout.breakoutStrength = (currentClose - rangeHigh) / atr;

      // Volume confirmation
      if(InpBreakoutVolumeMult > 0)
        {
         long tickVol[];
         ArraySetAsSeries(tickVol, true);
         if(CopyTickVolume(tradingSymbol, InpMTFPeriod, 0, bars, tickVol) >= bars)
           {
            long avgVol = 0;
            for(int i = 1; i < bars; i++)
               avgVol += tickVol[i];
            avgVol /= (bars - 1);

            if(avgVol > 0 && tickVol[0] < avgVol * InpBreakoutVolumeMult / 100)
              {
               breakout.breakoutDetected = false; // Volume not confirming
               VerboseLog("Breakout rejected: insufficient volume");
              }
           }
        }

      if(breakout.breakoutDetected)
         VerboseLog("BULLISH BREAKOUT detected! Strength: " + DoubleToString(breakout.breakoutStrength, 2));
     }
   else
      if(currentClose < rangeLow - breakoutThreshold)
        {
         breakout.breakoutDetected = true;
         breakout.breakoutBullish = false;
         breakout.breakoutStrength = (rangeLow - currentClose) / atr;

         if(InpBreakoutVolumeMult > 0)
           {
            long tickVol[];
            ArraySetAsSeries(tickVol, true);
            if(CopyTickVolume(tradingSymbol, InpMTFPeriod, 0, bars, tickVol) >= bars)
              {
               long avgVol = 0;
               for(int i = 1; i < bars; i++)
                  avgVol += tickVol[i];
               avgVol /= (bars - 1);

               if(avgVol > 0 && tickVol[0] < avgVol * InpBreakoutVolumeMult / 100)
                 {
                  breakout.breakoutDetected = false;
                  VerboseLog("Breakout rejected: insufficient volume");
                 }
              }
           }

         if(breakout.breakoutDetected)
            VerboseLog("BEARISH BREAKOUT detected! Strength: " + DoubleToString(breakout.breakoutStrength, 2));
        }
  }

//+------------------------------------------------------------------+
//| ═══════════════════════════════════════════════════════════════    |
//| VOLUME PROFILE / ORDER FLOW                                      |
//| ═══════════════════════════════════════════════════════════════    |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Update Volume Profile                                            |
//+------------------------------------------------------------------+
void UpdateVolumeProfile()
  {
   double high[], low[], close[];
   long tickVol[];
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low, true);
   ArraySetAsSeries(close, true);
   ArraySetAsSeries(tickVol, true);

   int bars = InpVPLookbackBars;
   if(CopyHigh(tradingSymbol, InpMTFPeriod, 0, bars, high) < bars)
      return;
   if(CopyLow(tradingSymbol, InpMTFPeriod, 0, bars, low) < bars)
      return;
   if(CopyClose(tradingSymbol, InpMTFPeriod, 0, bars, close) < bars)
      return;
   if(CopyTickVolume(tradingSymbol, InpMTFPeriod, 0, bars, tickVol) < bars)
      return;

// Find price range
   double priceHigh = high[0];
   double priceLow = low[0];
   for(int i = 1; i < bars; i++)
     {
      if(high[i] > priceHigh)
         priceHigh = high[i];
      if(low[i] < priceLow)
         priceLow = low[i];
     }

   double priceRange = priceHigh - priceLow;
   if(priceRange <= 0)
      return;

   double levelSize = priceRange / InpVPLevels;

// Build volume profile
   ArrayResize(volumeLevels, InpVPLevels);
   double totalVolume = 0;

   for(int lvl = 0; lvl < InpVPLevels; lvl++)
     {
      volumeLevels[lvl].price = priceLow + (lvl + 0.5) * levelSize;
      volumeLevels[lvl].volume = 0;
      volumeLevels[lvl].isHVN = false;
      volumeLevels[lvl].isLVN = false;

      double levelLow = priceLow + lvl * levelSize;
      double levelHigh = levelLow + levelSize;

      for(int bar = 0; bar < bars; bar++)
        {
         // Check if bar overlaps with this level
         if(high[bar] >= levelLow && low[bar] <= levelHigh)
           {
            // Proportional volume allocation
            double overlap = MathMin(high[bar], levelHigh) - MathMax(low[bar], levelLow);
            double barRange = high[bar] - low[bar];
            double proportion = (barRange > 0) ? overlap / barRange : 1.0;
            volumeLevels[lvl].volume += (double)tickVol[bar] * proportion;
           }
        }
      totalVolume += volumeLevels[lvl].volume;
     }

// Calculate average volume per level
   double avgVolume = (InpVPLevels > 0) ? totalVolume / InpVPLevels : 0;

// Identify HVN, LVN, and POC
   double maxVolume = 0;
   pocPrice = 0;

   for(int lvl = 0; lvl < InpVPLevels; lvl++)
     {
      if(volumeLevels[lvl].volume > maxVolume)
        {
         maxVolume = volumeLevels[lvl].volume;
         pocPrice = volumeLevels[lvl].price;
        }
      volumeLevels[lvl].isHVN = (volumeLevels[lvl].volume > avgVolume * InpVPHVNThreshold);
      volumeLevels[lvl].isLVN = (volumeLevels[lvl].volume < avgVolume * 0.5);
     }

// Calculate Value Area (70% of volume)
   double vaVolume = totalVolume * 0.70;
   double accumulatedVolume = 0;

// Find POC index
   int pocIndex = 0;
   for(int i = 0; i < InpVPLevels; i++)
     {
      if(MathAbs(volumeLevels[i].price - pocPrice) < levelSize)
        {
         pocIndex = i;
         break;
        }
     }

   accumulatedVolume = volumeLevels[pocIndex].volume;
   int upperIdx = pocIndex;
   int lowerIdx = pocIndex;

   while(accumulatedVolume < vaVolume && (upperIdx < InpVPLevels - 1 || lowerIdx > 0))
     {
      double upperVol = (upperIdx < InpVPLevels - 1) ? volumeLevels[upperIdx + 1].volume : 0;
      double lowerVol = (lowerIdx > 0) ? volumeLevels[lowerIdx - 1].volume : 0;

      if(upperVol >= lowerVol && upperIdx < InpVPLevels - 1)
        {
         upperIdx++;
         accumulatedVolume += volumeLevels[upperIdx].volume;
        }
      else
         if(lowerIdx > 0)
           {
            lowerIdx--;
            accumulatedVolume += volumeLevels[lowerIdx].volume;
           }
         else
            break;
     }

   vahPrice = volumeLevels[upperIdx].price;
   valPrice = volumeLevels[lowerIdx].price;
  }

//+------------------------------------------------------------------+
//| Get Volume Profile Signal                                        |
//| Returns: score from -1.0 (bearish) to +1.0 (bullish)            |
//+------------------------------------------------------------------+
double GetVolumeProfileSignal()
  {
   if(!InpEnableVolumeProfile || pocPrice == 0)
      return 0;

   double currentPrice = symInfo.Last();
   if(currentPrice == 0)
      currentPrice = (symInfo.Ask() + symInfo.Bid()) / 2.0;

   double signal = 0;

// Price relative to Value Area
   if(currentPrice > vahPrice)
     {
      signal += 0.3; // Above value area = bullish momentum
     }
   else
      if(currentPrice < valPrice)
        {
         signal -= 0.3; // Below value area = bearish momentum
        }
      else
        {
         // Inside value area - mean reversion zone
         double vaCenter = (vahPrice + valPrice) / 2.0;
         if(currentPrice > vaCenter)
            signal -= 0.1; // Above center, slight bearish bias (mean reversion)
         else
            signal += 0.1; // Below center, slight bullish bias
        }

// Price relative to POC
   double distFromPOC = (currentPrice - pocPrice) / (symInfo.Point() * PointsPerPip());
   if(MathAbs(distFromPOC) < 5) // Near POC = strong support/resistance
     {
      signal *= 0.5; // Reduce signal near POC (congestion zone)
     }

// Delta volume analysis
   if(InpUseDeltaVolume)
     {
      double deltaSignal = CalculateDeltaVolume();
      signal += deltaSignal * 0.4;
     }

   return MathMax(-1.0, MathMin(1.0, signal));
  }

//+------------------------------------------------------------------+
//| Calculate Delta Volume (Buy vs Sell pressure)                    |
//+------------------------------------------------------------------+
double CalculateDeltaVolume()
  {
   double open[], close[];
   long tickVol[];
   ArraySetAsSeries(open, true);
   ArraySetAsSeries(close, true);
   ArraySetAsSeries(tickVol, true);

   int bars = 10;
   if(CopyOpen(tradingSymbol, InpLTFPeriod, 0, bars, open) < bars)
      return 0;
   if(CopyClose(tradingSymbol, InpLTFPeriod, 0, bars, close) < bars)
      return 0;
   if(CopyTickVolume(tradingSymbol, InpLTFPeriod, 0, bars, tickVol) < bars)
      return 0;

   double buyVolume = 0;
   double sellVolume = 0;

   for(int i = 0; i < bars; i++)
     {
      if(close[i] > open[i])
         buyVolume += (double)tickVol[i];
      else
         if(close[i] < open[i])
            sellVolume += (double)tickVol[i];
         else
           {
            buyVolume += (double)tickVol[i] * 0.5;
            sellVolume += (double)tickVol[i] * 0.5;
           }
     }

   double totalVol = buyVolume + sellVolume;
   if(totalVol == 0)
      return 0;

   return (buyVolume - sellVolume) / totalVol; // -1 to +1
  }

//+------------------------------------------------------------------+
//| ═══════════════════════════════════════════════════════════════    |
//| ML PATTERN RECOGNITION                                           |
//| ═══════════════════════════════════════════════════════════════    |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Get Current Pattern Features                                     |
//+------------------------------------------------------------------+
bool GetCurrentPattern(double &features[])
  {
   double close[];
   ArraySetAsSeries(close, true);

   int needed = InpMLPatternLength + 1;
   if(CopyClose(tradingSymbol, InpLTFPeriod, 0, needed, close) < needed)
      return false;

   ArrayResize(features, InpMLPatternLength);

// Normalize: use percentage changes
   for(int i = 0; i < InpMLPatternLength; i++)
     {
      if(close[i + 1] != 0)
         features[i] = (close[i] - close[i + 1]) / close[i + 1] * 100.0;
      else
         features[i] = 0;
     }

   return true;
  }

//+------------------------------------------------------------------+
//| Record ML Pattern After Trade Close                              |
//+------------------------------------------------------------------+
void RecordMLPattern(bool wasWin, double profit, double volume)
  {
   double features[];
   if(!GetCurrentPattern(features))
      return;

   int idx = ArraySize(patternDB);
   ArrayResize(patternDB, idx + 1);
   ArrayResize(patternDB[idx].features, InpMLPatternLength);
   ArrayCopy(patternDB[idx].features, features);
   patternDB[idx].wasWin = wasWin;

   double pipValue = GetPipValue();
   patternDB[idx].profitPips = (pipValue > 0 && volume > 0) ? profit / (volume * pipValue) : 0;

   patternDBSize = idx + 1;
  }

//+------------------------------------------------------------------+
//| Calculate Pattern Similarity (Cosine Similarity)                 |
//+------------------------------------------------------------------+
double PatternSimilarity(const double &pattern1[], const double &pattern2[])
  {
   int len = MathMin(ArraySize(pattern1), ArraySize(pattern2));
   if(len == 0)
      return 0;

   double dotProduct = 0;
   double norm1 = 0;
   double norm2 = 0;

   for(int i = 0; i < len; i++)
     {
      dotProduct += pattern1[i] * pattern2[i];
      norm1 += pattern1[i] * pattern1[i];
      norm2 += pattern2[i] * pattern2[i];
     }

   double denominator = MathSqrt(norm1) * MathSqrt(norm2);
   if(denominator == 0)
      return 0;

   return dotProduct / denominator;
  }

//+------------------------------------------------------------------+
//| Get ML Prediction                                                |
//| Returns: score from -1.0 (strong sell) to +1.0 (strong buy)     |
//+------------------------------------------------------------------+
double GetMLPrediction()
  {
   if(!InpEnableML || patternDBSize < InpMLMinSamples)
      return 0;

   double currentPattern[];
   if(!GetCurrentPattern(currentPattern))
      return 0;

   int matchCount = 0;
   int winCount = 0;
   double totalProfitPips = 0;

   for(int i = 0; i < patternDBSize; i++)
     {
      double similarity = PatternSimilarity(currentPattern, patternDB[i].features);

      if(similarity >= InpMLMinSimilarity)
        {
         matchCount++;
         if(patternDB[i].wasWin)
            winCount++;
         totalProfitPips += patternDB[i].profitPips;
        }
     }

   if(matchCount < InpMLMinSamples)
      return 0;

   double patternWinRate = (double)winCount / matchCount;
   double avgProfitPips = totalProfitPips / matchCount;

   VerboseLogThrottled("ML: " + IntegerToString(matchCount) + " similar patterns found, " +
              "WinRate=" + DoubleToString(patternWinRate * 100, 1) + "%, " +
              "AvgProfit=" + DoubleToString(avgProfitPips, 1) + " pips", _lastLogTime_signal, 300);

   if(patternWinRate < InpMLMinWinRate)
     {
      VerboseLog("ML: Pattern win rate below threshold - signal rejected");
      return 0;
     }

// Return directional signal based on average profit
   if(avgProfitPips > 0)
      return MathMin(patternWinRate, 1.0);
   else
      return MathMax(-patternWinRate, -1.0);
  }

//+------------------------------------------------------------------+
//| ═══════════════════════════════════════════════════════════════    |
//| MONTE CARLO SIMULATION                                           |
//| ═══════════════════════════════════════════════════════════════    |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Run Monte Carlo Validation                                       |
//+------------------------------------------------------------------+
void RunMonteCarloValidation()
  {
   if(!InpEnableMonteCarlo || !learningInitialized || learning.totalTrades < 30)
     {
      mcResult.isViable = true; // Default to viable if not enough data
      return;
     }

   VerboseLog("Running Monte Carlo simulation (" + IntegerToString(InpMCSimulations) + " runs)...");

   double drawdowns[];
   double returns[];
   ArrayResize(drawdowns, InpMCSimulations);
   ArrayResize(returns, InpMCSimulations);

   double winRate = learning.winRatio;
   double avgWin = learning.avgWinPips;
   double avgLoss = learning.avgLossPips;

// Simulate
   for(int sim = 0; sim < InpMCSimulations; sim++)
     {
      double equity = 10000.0; // Normalized starting equity
      double peakEq = equity;
      double maxDD = 0;

      // Simulate 100 trades per run
      for(int t = 0; t < 100; t++)
        {
         // Random win/loss based on historical win rate
         double rand = (double)MathRand() / 32767.0;
         double tradeResult = 0;

         if(rand < winRate)
           {
            // Win - add some randomness to win size
            double winVariation = 0.7 + (double)MathRand() / 32767.0 * 0.6; // 0.7 to 1.3
            tradeResult = avgWin * winVariation;
           }
         else
           {
            // Loss
            double lossVariation = 0.7 + (double)MathRand() / 32767.0 * 0.6;
            tradeResult = -avgLoss * lossVariation;
           }

         // Apply to equity (assuming 1% risk per trade)
         double pipValue = equity * 0.01 / avgLoss; // Normalize
         equity += tradeResult * pipValue;

         if(equity > peakEq)
            peakEq = equity;
         double dd = (peakEq - equity) / peakEq * 100;
         if(dd > maxDD)
            maxDD = dd;
        }

      drawdowns[sim] = maxDD;
      returns[sim] = (equity - 10000.0) / 10000.0 * 100;
     }

// Sort drawdowns for percentile calculation
   ArraySort(drawdowns);
   ArraySort(returns);

// Calculate results
   double sumReturns = 0;
   for(int i = 0; i < InpMCSimulations; i++)
      sumReturns += returns[i];

   mcResult.avgReturn = sumReturns / InpMCSimulations;

// Max drawdown at confidence level
   int confidenceIndex = (int)(InpMCSimulations * InpMCConfidenceLevel);
   if(confidenceIndex >= InpMCSimulations)
      confidenceIndex = InpMCSimulations - 1;
   mcResult.drawdownAtConfidence = drawdowns[confidenceIndex];
   mcResult.maxDrawdown = drawdowns[InpMCSimulations - 1];

// Win rate range (5th to 95th percentile of returns)
   int lowIdx = (int)(InpMCSimulations * 0.05);
   int highIdx = (int)(InpMCSimulations * 0.95);
   mcResult.winRateRange = returns[highIdx] - returns[lowIdx];

// Is strategy viable?
   mcResult.isViable = (mcResult.drawdownAtConfidence <= InpMCMaxDrawdownPct &&
                        mcResult.avgReturn > 0);

   VerboseLog("Monte Carlo Results: AvgReturn=" + DoubleToString(mcResult.avgReturn, 2) + "%" +
              " DD@" + DoubleToString(InpMCConfidenceLevel * 100, 0) + "%=" +
              DoubleToString(mcResult.drawdownAtConfidence, 2) + "%" +
              " MaxDD=" + DoubleToString(mcResult.maxDrawdown, 2) + "%" +
              " Viable=" + (mcResult.isViable ? "YES" : "NO"));

   if(!mcResult.isViable)
     {
      session.currentAction = SESSION_CONSERVATIVE;
      VerboseLog("MC: Strategy not viable at confidence level - switching to CONSERVATIVE");
     }
  }

//+------------------------------------------------------------------+
//| ═══════════════════════════════════════════════════════════════    |
//| COMPOSITE SIGNAL SCORING                                         |
//| ═══════════════════════════════════════════════════════════════    |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Calculate Composite Signal Score                                 |
//+------------------------------------------------------------------+
void CalculateSignalScore()
  {
   signalScore = 0;
   double totalWeight = 0;

//--- 1. EMA Crossover Signal (weight: 1.0)
   double emaSignal = GetEMASignal();
   signalScore += emaSignal * 1.0;
   totalWeight += 1.0;

//--- 2. Market Regime Signal (weight: 1.5)
   double regimeSignal = GetRegimeSignal();
   signalScore += regimeSignal * 1.5;
   totalWeight += 1.5;

//--- 3. RSI Signal (weight: 0.8)
   double rsiSignal = GetRSISignal();
   signalScore += rsiSignal * 0.8;
   totalWeight += 0.8;

//--- 4. DXY Correlation Signal (weight: 1.2)
   if(InpEnableDXYCorrelation && dxyAvailable)
     {
      double dxySignal = GetDXYSignal();
      signalScore += dxySignal * 1.2;
      totalWeight += 1.2;
     }

//--- 5. Breakout Signal (weight: 2.0 - high weight for breakouts)
   if(InpEnableBreakout && breakout.breakoutDetected)
     {
      double breakoutSignal = breakout.breakoutBullish ? breakout.breakoutStrength : -breakout.breakoutStrength;
      breakoutSignal = MathMax(-1.0, MathMin(1.0, breakoutSignal));
      signalScore += breakoutSignal * 2.0;
      totalWeight += 2.0;
     }

//--- 6. Volume Profile Signal (weight: 1.0)
   if(InpEnableVolumeProfile)
     {
      double vpSignal = GetVolumeProfileSignal();
      signalScore += vpSignal * 1.0;
      totalWeight += 1.0;
     }

//--- 7. ML Pattern Signal (weight: 1.5)
   if(InpEnableML)
     {
      double mlSignal = GetMLPrediction();
      if(mlSignal != 0)
        {
         signalScore += mlSignal * 1.5;
         totalWeight += 1.5;
        }
     }

//--- 8. Session Quality Signal (weight: 0.5)
   double sessionSignal = GetSessionQualitySignal();
   signalScore += sessionSignal * 0.5;
   totalWeight += 0.5;

//--- Normalize
   if(totalWeight > 0)
      signalScore /= totalWeight;

//--- Determine strength (lowered thresholds for realistic trading)
   double absScore = MathAbs(signalScore);
   if(absScore >= 0.55)
      signalStrength = SIGNAL_VERY_STRONG;
   else
      if(absScore >= 0.4)
         signalStrength = SIGNAL_STRONG;
      else
         if(absScore >= 0.2)
            signalStrength = SIGNAL_MODERATE;
         else
            if(absScore >= 0.1)
               signalStrength = SIGNAL_WEAK;
            else
               signalStrength = SIGNAL_NONE;

   VerboseLogThrottled("Signal Score: " + DoubleToString(signalScore, 3) +
              " (" + SignalStrengthToString(signalStrength) + ")" +
              " Components: EMA=" + DoubleToString(emaSignal, 2) +
              " Regime=" + DoubleToString(regimeSignal, 2) +
              " RSI=" + DoubleToString(rsiSignal, 2),
              _lastLogTime_signal, logThrottleIntervalSec);
  }

//+------------------------------------------------------------------+
//| Get EMA Signal                                                   |
//+------------------------------------------------------------------+
double GetEMASignal()
  {
   double ema5[], ema20[];
   ArraySetAsSeries(ema5, true);
   ArraySetAsSeries(ema20, true);

   if(CopyBuffer(hEMA5, 0, 0, 3, ema5) < 3)
      return 0;
   if(CopyBuffer(hEMA20, 0, 0, 3, ema20) < 3)
      return 0;

   double diff = (ema5[0] - ema20[0]) / (symInfo.Point() * PointsPerPip());
   return MathMax(-1.0, MathMin(1.0, diff / 50.0)); // Normalize
  }

//+------------------------------------------------------------------+
//| Get Regime Signal                                                |
//+------------------------------------------------------------------+
double GetRegimeSignal()
  {
   switch(currentRegime)
     {
      case REGIME_TRENDING_UP:
         return 0.8;
      case REGIME_TRENDING_DOWN:
         return -0.8;
      case REGIME_RANGING:
         return 0.3; // Allow ranging with mean-reversion bias
      case REGIME_HIGH_VOLATILITY:
         return 0.1; // Small signal even in high vol
      default:
         return 0.0;
     }
  }

//+------------------------------------------------------------------+
//| Get RSI Signal                                                   |
//+------------------------------------------------------------------+
double GetRSISignal()
   {
    if(hRSI_MTF == INVALID_HANDLE)
       return 0;

    double rsi[];
    ArraySetAsSeries(rsi, true);
    if(CopyBuffer(hRSI_MTF, 0, 0, 3, rsi) < 3)
       return 0;

    // Continuous RSI signal instead of dead zones
    // RSI 50 = neutral, above 50 = bearish bias, below 50 = bullish bias
    double rsiVal = rsi[0];
    if(rsiVal > 80)
       return -1.0;  // Extreme overbought
    if(rsiVal < 20)
       return 1.0;   // Extreme oversold
    if(rsiVal > 70)
       return -0.8;  // Overbought
    if(rsiVal < 30)
       return 0.8;   // Oversold
    if(rsiVal > 60)
       return -0.4;
    if(rsiVal < 40)
       return 0.4;
    // 40-60 range: small directional bias based on RSI momentum
    if(rsiVal > 55)
       return -0.15;
    if(rsiVal < 45)
       return 0.15;

    return 0;  // Only truly neutral at 45-55
   }

//+------------------------------------------------------------------+
//| Get DXY Signal                                                   |
//+------------------------------------------------------------------+
double GetDXYSignal()
  {
   int dxyTrend = GetDXYTrend();
// Inverse correlation: DXY up = Gold down
   return -dxyTrend * 0.7;
  }

//+------------------------------------------------------------------+
//| Get Session Quality Signal                                       |
//+------------------------------------------------------------------+
double GetSessionQualitySignal()
  {
   ENUM_TRADING_SESSION sess = GetCurrentSession();
   switch(sess)
     {
      case SESSION_LONDON_NY_OVERLAP:
         return 0.8;  // Best session for Gold
      case SESSION_LONDON:
         return 0.5;
      case SESSION_NEWYORK:
         return 0.4;
      case SESSION_ASIAN:
         return -0.2; // Low liquidity
      default:
         return -0.5;
     }
  }

//+------------------------------------------------------------------+
//| Signal Strength to String                                        |
//+------------------------------------------------------------------+
string SignalStrengthToString(ENUM_SIGNAL_STRENGTH strength)
  {
   switch(strength)
     {
      case SIGNAL_VERY_STRONG:
         return "VERY STRONG";
      case SIGNAL_STRONG:
         return "STRONG";
      case SIGNAL_MODERATE:
         return "MODERATE";
      case SIGNAL_WEAK:
         return "WEAK";
      default:
         return "NONE";
     }
  }

//+------------------------------------------------------------------+
//| ═══════════════════════════════════════════════════════════════    |
//| CORE TRADING LOGIC                                               |
//| ═══════════════════════════════════════════════════════════════    |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Initialize Learning                                              |
//+------------------------------------------------------------------+
void InitializeLearning()
  {
   ZeroMemory(learning);
   ArrayInitialize(learning.hourlyWinRate, 0);
   ArrayInitialize(learning.hourlyTradeCount, 0);
   ArrayInitialize(learning.regimeWinRate, 0);
   ArrayInitialize(learning.regimeTradeCount, 0);

   if(!HistorySelect(TimeCurrent() - 90 * 86400, TimeCurrent()))
     {
      learningInitialized = true;
      return;
     }

   int totalDeals = HistoryDealsTotal();
   int analyzed = 0;

   for(int i = totalDeals - 1; i >= 0 && analyzed < InpHistoryAnalysisCount; i--)
     {
      ulong dealTicket = HistoryDealGetTicket(i);
      if(dealTicket == 0)
         continue;

      long dealMagic = HistoryDealGetInteger(dealTicket, DEAL_MAGIC);
      string dealSymbol = HistoryDealGetString(dealTicket, DEAL_SYMBOL);
      ENUM_DEAL_ENTRY dealEntry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(dealTicket, DEAL_ENTRY);

      if(dealMagic != (long)InpMagicNumber || dealSymbol != tradingSymbol || dealEntry != DEAL_ENTRY_OUT)
         continue;

      double dealProfit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT) +
                          HistoryDealGetDouble(dealTicket, DEAL_SWAP) +
                          HistoryDealGetDouble(dealTicket, DEAL_COMMISSION);
      double dealVolume = HistoryDealGetDouble(dealTicket, DEAL_VOLUME);
      datetime dealTime = (datetime)HistoryDealGetInteger(dealTicket, DEAL_TIME);

      if(dealVolume <= 0)
         continue;

      double pipValue = GetPipValue();
      double profitPips = (pipValue > 0) ? dealProfit / (dealVolume * pipValue) : 0;

      MqlDateTime dt;
      TimeToStruct(dealTime, dt);
      int hour = dt.hour;

      learning.totalTrades++;
      analyzed++;

      if(dealProfit > 0)
        {
         learning.winCount++;
         learning.totalWinPips += MathAbs(profitPips);
         learning.hourlyTradeCount[hour]++;
         learning.hourlyWinRate[hour] += 1.0;
        }
      else
        {
         learning.lossCount++;
         learning.totalLossPips += MathAbs(profitPips);
         learning.hourlyTradeCount[hour]++;
        }

      // Record for ML
      if(InpEnableML)
         RecordMLPattern(dealProfit > 0, dealProfit, dealVolume);
     }

// Calculate metrics
   RecalculateLearningMetrics();

// Hourly win rates
   double bestRate = 0;
   double worstRate = 1;
   for(int h = 0; h < 24; h++)
     {
      if(learning.hourlyTradeCount[h] > 0)
        {
         learning.hourlyWinRate[h] /= learning.hourlyTradeCount[h];
         if(learning.hourlyWinRate[h] > bestRate && learning.hourlyTradeCount[h] >= 5)
           {
            bestRate = learning.hourlyWinRate[h];
            learning.bestHour = h;
           }
         if(learning.hourlyWinRate[h] < worstRate && learning.hourlyTradeCount[h] >= 5)
           {
            worstRate = learning.hourlyWinRate[h];
            learning.worstHour = h;
           }
        }
     }

// Adaptive SL/TP
   if(InpAdaptiveSLTP && learning.totalTrades >= 20)
     {
      learning.bestSLPips = MathMax(learning.avgLossPips * 1.2, InpStopLossPips * 0.5);
      learning.bestSLPips = MathMin(learning.bestSLPips, (double)InpStopLossPips); // Clamp SL to max allowed
      learning.bestTPPips = MathMax(learning.avgWinPips * 0.9, InpTakeProfitPips * 0.5);
      if(learning.bestTPPips < learning.bestSLPips * 1.1)
         learning.bestTPPips = learning.bestSLPips * 1.2;
     }
   else
     {
      learning.bestSLPips = InpStopLossPips;
      learning.bestTPPips = InpTakeProfitPips;
     }

// Adaptive risk (Half-Kelly)
   if(InpAdaptiveLotSize && learning.totalTrades >= 20)
     {
      double kellyFraction = 0;
      if(learning.avgLossPips > 0)
         kellyFraction = (learning.winRatio - (1.0 - learning.winRatio) / (learning.avgWinPips / learning.avgLossPips));
      kellyFraction = MathMax(0, MathMin(kellyFraction * 0.5, 0.02));
      learning.optimalRiskPercent = MathMax(kellyFraction * 100, 0.25);
     }
   else
     {
      learning.optimalRiskPercent = InpRiskPerTrade;
     }

   learningInitialized = true;
   VerboseLog("Learning: Trades=" + IntegerToString(learning.totalTrades) +
              " WR=" + DoubleToString(learning.winRatio * 100, 1) + "%" +
              " PF=" + DoubleToString(learning.profitFactor, 2) +
              " ML Patterns=" + IntegerToString(patternDBSize));
  }

//+------------------------------------------------------------------+
//| Update Learning Win                                              |
//+------------------------------------------------------------------+
void UpdateLearningWin(double profit, double volume, ulong dealTicket)
  {
   if(!InpEnableLearning || !learningInitialized)
      return;

   double pipValue = GetPipValue();
   double profitPips = (pipValue > 0 && volume > 0) ? profit / (volume * pipValue) : 0;

   learning.winCount++;
   learning.totalWinPips += MathAbs(profitPips);
   learning.totalTrades++;

   if(learning.currentStreak >= 0)
      learning.currentStreak++;
   else
      learning.currentStreak = 1;

   learning.consecutiveWins = learning.currentStreak;
   if(learning.consecutiveWins > learning.maxConsecutiveWins)
      learning.maxConsecutiveWins = learning.consecutiveWins;

   RecalculateLearningMetrics();

// After consecutive wins, consider being more aggressive
   if(learning.consecutiveWins >= 3 && session.currentAction == SESSION_CONSERVATIVE)
     {
      session.currentAction = SESSION_NORMAL;
      TradeEventLogCritical("LEARNING: 3+ consecutive wins - returning to NORMAL mode");
     }
  }

//+------------------------------------------------------------------+
//| Update Learning Loss                                             |
//+------------------------------------------------------------------+
void UpdateLearningLoss(double profit, double volume, ulong dealTicket)
  {
   if(!InpEnableLearning || !learningInitialized)
      return;

   double pipValue = GetPipValue();
   double profitPips = (pipValue > 0 && volume > 0) ? MathAbs(profit) / (volume * pipValue) : 0;

   learning.lossCount++;
   learning.totalLossPips += profitPips;
   learning.totalTrades++;

   if(learning.currentStreak <= 0)
      learning.currentStreak--;
   else
      learning.currentStreak = -1;

   learning.consecutiveLosses = MathAbs(learning.currentStreak);
   if(learning.consecutiveLosses > learning.maxConsecutiveLosses)
      learning.maxConsecutiveLosses = learning.consecutiveLosses;

   RecalculateLearningMetrics();

   if(learning.consecutiveLosses >= 3)
     {
      session.currentAction = SESSION_CONSERVATIVE;
      TradeEventLogCritical("LEARNING: 3+ consecutive losses - CONSERVATIVE mode");
     }
   if(learning.consecutiveLosses >= 5)
     {
      session.currentAction = SESSION_PAUSED;
      TradeEventLogCritical("LEARNING: 5+ consecutive losses - PAUSED");
     }
  }

//+------------------------------------------------------------------+
//| Recalculate Learning Metrics                                     |
//+------------------------------------------------------------------+
void RecalculateLearningMetrics()
  {
   if(learning.winCount + learning.lossCount > 0)
      learning.winRatio = (double)learning.winCount / (learning.winCount + learning.lossCount);
   if(learning.winCount > 0)
      learning.avgWinPips = learning.totalWinPips / learning.winCount;
   if(learning.lossCount > 0)
      learning.avgLossPips = learning.totalLossPips / learning.lossCount;
   if(learning.totalLossPips > 0)
      learning.profitFactor = learning.totalWinPips / learning.totalLossPips;
   if(learning.totalTrades > 0)
      learning.expectancy = (learning.winRatio * learning.avgWinPips) - ((1.0 - learning.winRatio) * learning.avgLossPips);
  }

//+------------------------------------------------------------------+
//| Detect Market Regime                                             |
//+------------------------------------------------------------------+
void DetectMarketRegime()
  {
   double adxMain[], adxPlus[], adxMinus[], atrVal[];
   ArraySetAsSeries(adxMain, true);
   ArraySetAsSeries(adxPlus, true);
   ArraySetAsSeries(adxMinus, true);
   ArraySetAsSeries(atrVal, true);

   if(CopyBuffer(hADX_MTF, 0, 0, 3, adxMain) < 3)
      return;
   if(CopyBuffer(hADX_MTF, 1, 0, 3, adxPlus) < 3)
      return;
   if(CopyBuffer(hADX_MTF, 2, 0, 3, adxMinus) < 3)
      return;
   if(CopyBuffer(hATR_MTF, 0, 0, 3, atrVal) < 3)
      return;

   currentVolatilityPts = atrVal[0] / symInfo.Point();

   if(adxMain[0] > InpTrendThreshold)
      currentRegime = (adxPlus[0] > adxMinus[0]) ? REGIME_TRENDING_UP : REGIME_TRENDING_DOWN;
   else
      currentRegime = REGIME_RANGING;

   if(currentVolatilityPts > InpMaxVolatilityPts)
      currentRegime = REGIME_HIGH_VOLATILITY;
  }

//+------------------------------------------------------------------+
//| Is Regime Allowed                                                |
//+------------------------------------------------------------------+
bool IsRegimeAllowed(ENUM_MARKET_REGIME regime)
  {
   if(!InpFilterByRegime)
      return true;
   switch(regime)
     {
      case REGIME_RANGING:
         return InpAllowRanging;
      case REGIME_TRENDING_UP:
         return InpAllowTrendUp;
      case REGIME_TRENDING_DOWN:
         return InpAllowTrendDown;
      case REGIME_HIGH_VOLATILITY:
         return InpAllowHighVol;
      default:
         return false;
     }
  }

//+------------------------------------------------------------------+
//| Get Regime String                                                |
//+------------------------------------------------------------------+
string GetRegimeString(ENUM_MARKET_REGIME regime)
  {
   switch(regime)
     {
      case REGIME_RANGING:
         return "Ranging";
      case REGIME_TRENDING_UP:
         return "Trending Up";
      case REGIME_TRENDING_DOWN:
         return "Trending Down";
      case REGIME_HIGH_VOLATILITY:
         return "High Volatility";
      default:
         return "Unknown";
     }
  }

//+------------------------------------------------------------------+
//| Check Positions Status                                           |
//+------------------------------------------------------------------+
void CheckPositionsStatus()
  {
   for(int i = 0; i < ArraySize(tradePairs); i++)
     {
      if(tradePairs[i].buyTicket > 0 && !tradePairs[i].buyClosed)
        {
         if(!PositionSelectByTicket(tradePairs[i].buyTicket))
           {
            tradePairs[i].buyClosed = true;
            double closedProfit = GetClosedPositionProfit(tradePairs[i].buyTicket);
            if(closedProfit < 0)
               HandleStopLossHit(tradePairs[i].buyTicket, true, tradePairs[i].lotSize);
            if(tradePairs[i].sellTicket > 0 && !tradePairs[i].sellClosed)
               AddOrphanedLeg(tradePairs[i].sellTicket, false, tradePairs[i].lotSize, i);
           }
        }

      if(tradePairs[i].sellTicket > 0 && !tradePairs[i].sellClosed)
        {
         if(!PositionSelectByTicket(tradePairs[i].sellTicket))
           {
            tradePairs[i].sellClosed = true;
            double closedProfit = GetClosedPositionProfit(tradePairs[i].sellTicket);
            if(closedProfit < 0)
               HandleStopLossHit(tradePairs[i].sellTicket, false, tradePairs[i].lotSize);
            if(tradePairs[i].buyTicket > 0 && !tradePairs[i].buyClosed)
               AddOrphanedLeg(tradePairs[i].buyTicket, true, tradePairs[i].lotSize, i);
           }
        }
     }
  }

//+------------------------------------------------------------------+
//| Get Closed Position Profit                                       |
//+------------------------------------------------------------------+
double GetClosedPositionProfit(ulong posTicket)
  {
   double profit = 0;
   if(HistorySelectByPosition(posTicket))
     {
      int totalDeals = HistoryDealsTotal();
      for(int i = 0; i < totalDeals; i++)
        {
         ulong dealTicket = HistoryDealGetTicket(i);
         if(dealTicket > 0)
           {
            ENUM_DEAL_ENTRY entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(dealTicket, DEAL_ENTRY);
            if(entry == DEAL_ENTRY_OUT || entry == DEAL_ENTRY_INOUT)
               profit += HistoryDealGetDouble(dealTicket, DEAL_PROFIT) +
                         HistoryDealGetDouble(dealTicket, DEAL_SWAP) +
                         HistoryDealGetDouble(dealTicket, DEAL_COMMISSION);
           }
        }
     }
   return profit;
  }

//+------------------------------------------------------------------+
//| ═══════════════════════════════════════════════════════════════    |
//| ADVANCED ORPHAN MANAGEMENT (P1-P4)                               |
//| ═══════════════════════════════════════════════════════════════    |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| P1: Update Orphan Velocity (EMA-smoothed pips/minute)            |
//+------------------------------------------------------------------+
void UpdateOrphanVelocity(int idx)
  {
   if(!PositionSelectByTicket(orphanedLegs[idx].ticket))
      return;

   double pipsPerPt    = PointsPerPip() * symInfo.Point();
   double currentPrice = orphanedLegs[idx].isBuy ? symInfo.Bid() : symInfo.Ask();
   double currentPips  = orphanedLegs[idx].isBuy
                         ? (currentPrice - orphanedLegs[idx].entryPrice) / pipsPerPt
                         : (orphanedLegs[idx].entryPrice - currentPrice) / pipsPerPt;

   datetime now        = TimeCurrent();
   datetime lastSample = orphanedLegs[idx].velocityCheckTime;

// Sample only every InpVelocitySampleSecs — filter tick noise
   if(lastSample != 0 && (now - lastSample) < InpVelocitySampleSecs)
      return;

// Fix: On the very FIRST sample, just record a baseline — do not compute velocity.
// This prevents the entry spread (-2 to -3 pips) from being interpreted as a
// fast adverse move, which would prematurely lower the re-hedge threshold.
   if(lastSample == 0)
     {
      orphanedLegs[idx].pipsAtLastSample  = currentPips;
      orphanedLegs[idx].velocityCheckTime = now;
      orphanedLegs[idx].pipVelocity       = 0.0;  // neutral until first real sample
      return;
     }

   double elapsedMins = MathMax((double)(now - lastSample) / 60.0, 0.0167);
   double rawVelocity = (currentPips - orphanedLegs[idx].pipsAtLastSample) / elapsedMins;

// EMA smoothing (alpha=0.3) to reduce spike noise
   double alpha = 0.3;
   orphanedLegs[idx].pipVelocity = alpha * rawVelocity
                                   + (1.0 - alpha) * orphanedLegs[idx].pipVelocity;

   orphanedLegs[idx].pipsAtLastSample  = currentPips;
   orphanedLegs[idx].velocityCheckTime = now;
  }

//+------------------------------------------------------------------+
//| P1: Get Effective Re-hedge Threshold (velocity-scaled)           |
//+------------------------------------------------------------------+
double GetEffectiveReHedgeThreshold(int idx)
  {
   double velocity     = MathAbs(orphanedLegs[idx].pipVelocity);
   double normVelocity = (InpVelocityNormPipsPerMin > 0)
                         ? velocity / InpVelocityNormPipsPerMin : 1.0;

// Fast shrinks threshold; slow keeps it at 100%.
// Floor raised to 0.6 so threshold never drops below 60% of input
// (prevents re-hedging at spread-level pips in volatile markets)
   double scaleFactor        = MathMax(0.6, MathMin(1.0, 1.0 / MathMax(normVelocity, 0.01)));
   double effectiveThreshold = InpReHedgeThresholdPips * scaleFactor;

   VerboseLogThrottled("[Velocity] #" + IntegerToString((int)orphanedLegs[idx].ticket) +
              " V=" + DoubleToString(velocity, 3) + " pip/min" +
              " | Scale=" + DoubleToString(scaleFactor, 2) +
              " | EffThresh=" + DoubleToString(effectiveThreshold, 1),
              _lastLogTime_signal, logThrottleIntervalSec);
   return effectiveThreshold;
  }

//+------------------------------------------------------------------+
//| P2: Normalize Lot to broker step                                 |
//+------------------------------------------------------------------+
double NormalizeLotSize(double lot)
  {
   double step = symInfo.LotsStep();
   return (step > 0) ? MathFloor(lot / step) * step : lot;
  }

//+------------------------------------------------------------------+
//| P2: Calculate Re-hedge Lot Size (recovery-focused)               |
//+------------------------------------------------------------------+
double CalculateReHedgeLot(int orphanIdx)
  {
   if(!PositionSelectByTicket(orphanedLegs[orphanIdx].ticket))
      return NormalizeLotSize(InpFixedLotSize);

   double orphanLoss    = PositionGetDouble(POSITION_PROFIT)
                        + PositionGetDouble(POSITION_SWAP);
   double orphanLotSize = PositionGetDouble(POSITION_VOLUME);

   if(orphanLoss >= 0)
      return NormalizeLotSize(orphanLotSize); // not in loss, mirror

   double lossToRecover = MathAbs(orphanLoss);
   double tpPips        = (InpTakeProfitPips > 0) ? (double)InpTakeProfitPips : 20.0;
   double tickValue     = symInfo.TickValue();
   double tickSize      = symInfo.TickSize();
   double pipValue      = (tickSize > 0)
                          ? (tickValue / tickSize) * symInfo.Point() * PointsPerPip() : 0;

   if(pipValue <= 0 || tpPips <= 0)
     {
      Print("[ReHedge Sizing] pipValue/tpPips zero - fallback to orphan lot");
      return NormalizeLotSize(orphanLotSize);
     }

   double requiredLot   = lossToRecover / (pipValue * tpPips);
   double maxAllowedLot = orphanLotSize * InpMaxReHedgeLotMultiplier;
   double cappedLot     = NormalizeLotSize(
                            MathMin(MathMax(requiredLot, symInfo.LotsMin()),
                                    MathMin(maxAllowedLot, symInfo.LotsMax())));

// Margin safety check
   double marginRequired = 0;
   if(OrderCalcMargin(ORDER_TYPE_BUY, tradingSymbol, cappedLot, symInfo.Ask(), marginRequired))
      if(marginRequired > accInfo.FreeMargin() * 0.6)
         cappedLot = NormalizeLotSize(
                       MathMax((accInfo.FreeMargin() * 0.6) / (marginRequired / cappedLot),
                               symInfo.LotsMin()));

   PrintFormat("[ReHedge Sizing] Loss=$%.2f | TP=%.1fp | PipVal=%.4f | Req=%.2f | Cap=%.2f | Max=%.2f",
               orphanLoss, tpPips, pipValue, requiredLot, cappedLot, maxAllowedLot);
   return cappedLot;
  }

//+------------------------------------------------------------------+
//| P3: Manage Winning Orphan (partial close + BE SL)                |
//+------------------------------------------------------------------+
void ManageWinningOrphan(int idx, double profitPips)
  {
   if(orphanedLegs[idx].partialCloseFired)
      return;
   if(profitPips < InpOrphanPartialClosePips)
      return;
   if(!PositionSelectByTicket(orphanedLegs[idx].ticket))
      return;

   double currentVol = PositionGetDouble(POSITION_VOLUME);
   double closeVol   = NormalizeLotSize(currentVol * 0.5);

   if(currentVol - closeVol < symInfo.LotsMin())
     {
      PrintFormat("[WinOrphan] #%d too small to split. Letting run.", orphanedLegs[idx].ticket);
      orphanedLegs[idx].partialCloseFired = true;
      return;
     }

   if(trade.PositionClosePartial(orphanedLegs[idx].ticket, closeVol))
     {
      PrintFormat("[WinOrphan] Partial close %.2f lots #%d at +%.1f pips",
                  closeVol, orphanedLegs[idx].ticket, profitPips);

      // Move SL to breakeven + buffer on remaining position
      Sleep(100);
      if(PositionSelectByTicket(orphanedLegs[idx].ticket))
        {
         double pipsPerPt = PointsPerPip() * symInfo.Point();
         double currentTP = PositionGetDouble(POSITION_TP);
         double newSL = orphanedLegs[idx].isBuy
                        ? orphanedLegs[idx].entryPrice + InpOrphanBEBufferPips * pipsPerPt
                        : orphanedLegs[idx].entryPrice - InpOrphanBEBufferPips * pipsPerPt;
         newSL = NormalizeDouble(newSL, symInfo.Digits());
         if(trade.PositionModify(orphanedLegs[idx].ticket, newSL, currentTP))
            PrintFormat("[WinOrphan] SL to BE+%.1f on #%d", InpOrphanBEBufferPips, orphanedLegs[idx].ticket);
         else
            PrintFormat("[WinOrphan] BE SL failed #%d: %d", orphanedLegs[idx].ticket, trade.ResultRetcode());
        }
     }
   else
      PrintFormat("[WinOrphan] Partial close failed #%d: %d",
                  orphanedLegs[idx].ticket, trade.ResultRetcode());

   orphanedLegs[idx].partialCloseFired = true;
  }

//+------------------------------------------------------------------+
//| Manage Orphaned Legs (P1 velocity + P3 winning orphan)           |
//+------------------------------------------------------------------+
void ManageOrphanedLegs()
  {
   for(int i = ArraySize(orphanedLegs) - 1; i >= 0; i--)
     {
      if(!PositionSelectByTicket(orphanedLegs[i].ticket))
        {
         RemoveOrphanedLeg(i);
         continue;
        }

      // P1: Update velocity tracking
      UpdateOrphanVelocity(i);

      double pipsPerPt    = PointsPerPip() * symInfo.Point();
      double currentPrice = orphanedLegs[i].isBuy ? symInfo.Bid() : symInfo.Ask();
      double profitPips   = orphanedLegs[i].isBuy
                            ? (currentPrice - orphanedLegs[i].entryPrice) / pipsPerPt
                            : (orphanedLegs[i].entryPrice - currentPrice) / pipsPerPt;

      //--- REACTIVE HEDGE LOGIC:
      //--- If orphan is in profit → manage as winner (P3)
      //--- If orphan loss exceeds velocity-weighted threshold → re-hedge (P1+P2)
      //--- Timeout is fallback only for trades stuck in minor loss

      if(profitPips >= 0)
        {
         // Trade is in profit — P3: partial close + BE SL management
         ManageWinningOrphan(i, profitPips);
         continue;
        }

      // Trade is in loss — P1: velocity-weighted threshold
      double effectiveThreshold = GetEffectiveReHedgeThreshold(i);

      if(profitPips <= -effectiveThreshold)
        {
         PrintFormat("[ReHedge] #%d triggered. Pips=%.1f | Thresh=%.1f | V=%.3f pip/min",
                     orphanedLegs[i].ticket, profitPips,
                     effectiveThreshold, orphanedLegs[i].pipVelocity);
         ReHedgeOrphanedLeg(i);
        }
      else
         if(TimeCurrent() - orphanedLegs[i].closeTime > InpOrphanMonitorMins * 60)
           {
            // Timeout fallback: trade stuck in minor loss for too long
            PrintFormat("[ReHedge] #%d timeout fallback. Pips=%.1f",
                        orphanedLegs[i].ticket, profitPips);
            ReHedgeOrphanedLeg(i);
           }
     }
  }

//+------------------------------------------------------------------+
//| P4: Calculate Orphan-Only Profit                                 |
//+------------------------------------------------------------------+
double CalculateOrphanProfit()
  {
   double total = 0.0;
   for(int i = 0; i < ArraySize(orphanedLegs); i++)
      if(PositionSelectByTicket(orphanedLegs[i].ticket))
         total += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
   return total;
  }

//+------------------------------------------------------------------+
//| P4-L1: Tighten Orphan Stop Losses                                |
//+------------------------------------------------------------------+
void TightenOrphanStopLosses()
  {
   double pipsPerPt = PointsPerPip() * symInfo.Point();
   for(int i = 0; i < ArraySize(orphanedLegs); i++)
     {
      if(!PositionSelectByTicket(orphanedLegs[i].ticket))
         continue;
      double pnl = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
      if(pnl >= 0)
         continue; // only tighten losers

      double currentSL = PositionGetDouble(POSITION_SL);
      double currentTP = PositionGetDouble(POSITION_TP);
      bool   isBuy     = orphanedLegs[i].isBuy;
      double newSL     = isBuy ? symInfo.Bid() - InpOrphanSLBufferPips * pipsPerPt
                               : symInfo.Ask() + InpOrphanSLBufferPips * pipsPerPt;
      newSL = NormalizeDouble(newSL, symInfo.Digits());

      // Only move SL closer — never widen
      bool tighter = isBuy ? (newSL > currentSL || currentSL == 0)
                           : (newSL < currentSL || currentSL == 0);
      if(!tighter)
         continue;

      if(trade.PositionModify(orphanedLegs[i].ticket, newSL, currentTP))
         PrintFormat("[OrphanL1] SL tightened #%d to %.5f", orphanedLegs[i].ticket, newSL);
      else
         PrintFormat("[OrphanL1] Failed #%d: %d", orphanedLegs[i].ticket, trade.ResultRetcode());
     }
  }

//+------------------------------------------------------------------+
//| P4: Close Orphan Fraction (partial or full)                      |
//+------------------------------------------------------------------+
void CloseOrphanFraction(int idx, double fraction)
  {
   if(!PositionSelectByTicket(orphanedLegs[idx].ticket))
      return;
   double currentVol = PositionGetDouble(POSITION_VOLUME);

   if(fraction >= 1.0)
     {
      trade.PositionClose(orphanedLegs[idx].ticket);
      PrintFormat("[OrphanClose] Full close #%d", orphanedLegs[idx].ticket);
      RemoveOrphanedLeg(idx);
      return;
     }

   double closeVol = NormalizeLotSize(currentVol * fraction);
   if(currentVol - closeVol < symInfo.LotsMin())
     {
      trade.PositionClose(orphanedLegs[idx].ticket);
      PrintFormat("[OrphanClose] Full close #%d (remainder < minLot)", orphanedLegs[idx].ticket);
      RemoveOrphanedLeg(idx);
      return;
     }

   if(trade.PositionClosePartial(orphanedLegs[idx].ticket, closeVol))
      PrintFormat("[OrphanClose] Partial %.0f%% (%.2f lots) #%d",
                  fraction * 100, closeVol, orphanedLegs[idx].ticket);
   else
      PrintFormat("[OrphanClose] Failed #%d: %d", orphanedLegs[idx].ticket, trade.ResultRetcode());
  }

//+------------------------------------------------------------------+
//| P4: Check Orphan Drawdown (3-Level Escalation)                   |
//+------------------------------------------------------------------+
void CheckOrphanDrawdown()
  {
   if(ArraySize(orphanedLegs) == 0)
      return;

   double orphanProfit = CalculateOrphanProfit();
   if(orphanProfit >= 0)
      return;

   double orphanLoss = MathAbs(orphanProfit);
   double equity     = accInfo.Equity();
   double thL1       = equity * InpOrphanDrawdownL1Pct / 100.0;
   double thL2       = equity * InpOrphanDrawdownL2Pct / 100.0;
   double thL3       = equity * InpOrphanDrawdownL3Pct / 100.0;

   if(orphanLoss >= thL3)
     {
      Print("[OrphanCap] *** L3 *** Full close all orphans. Loss=$", DoubleToString(orphanLoss, 2));
      for(int i = ArraySize(orphanedLegs) - 1; i >= 0; i--)
         CloseOrphanFraction(i, 1.0);
     }
   else
      if(orphanLoss >= thL2)
        {
         bool fired = false;
         for(int i = ArraySize(orphanedLegs) - 1; i >= 0; i--)
           {
            if(orphanedLegs[i].drawdownLevel < 2)
              {
               CloseOrphanFraction(i, 0.5);
               if(i < ArraySize(orphanedLegs))
                  orphanedLegs[i].drawdownLevel = 2;
               fired = true;
              }
           }
         if(fired)
            Print("[OrphanCap] L2 partial close. Loss=$", DoubleToString(orphanLoss, 2));
         else
            Print("[OrphanCap] L2 already fired on all. Watching for L3.");
        }
      else
         if(orphanLoss >= thL1)
           {
            bool fired = false;
            for(int i = 0; i < ArraySize(orphanedLegs); i++)
               if(orphanedLegs[i].drawdownLevel < 1)
                 {
                  orphanedLegs[i].drawdownLevel = 1;
                  fired = true;
                 }
            if(fired)
              {
               Print("[OrphanCap] L1 tightening SLs. Loss=$", DoubleToString(orphanLoss, 2));
               TightenOrphanStopLosses();
              }
           }
  }


//+------------------------------------------------------------------+
//| Manage Trailing Stops                                            |
//+------------------------------------------------------------------+
void ManageTrailingStops()
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      if(!posInfo.SelectByIndex(i))
         continue;
      if(posInfo.Symbol() != tradingSymbol || posInfo.Magic() != InpMagicNumber)
         continue;

      double openPrice = posInfo.PriceOpen();
      double currentSL = posInfo.StopLoss();
      double currentTP = posInfo.TakeProfit();
      ulong ticket = posInfo.Ticket();
      double pipsToPoints = PointsPerPip() * symInfo.Point();

      if(posInfo.PositionType() == POSITION_TYPE_BUY)
        {
         double profitPips = (symInfo.Bid() - openPrice) / pipsToPoints;
         if(profitPips >= InpTrailingStartPips)
           {
            double newSL = NormalizeDouble(symInfo.Bid() - InpTrailingDistPips * pipsToPoints, symInfo.Digits());
            if(newSL > currentSL + InpTrailingStepPips * pipsToPoints)
               trade.PositionModify(ticket, newSL, currentTP);
           }
        }
      else
         if(posInfo.PositionType() == POSITION_TYPE_SELL)
           {
            double profitPips = (openPrice - symInfo.Ask()) / pipsToPoints;
            if(profitPips >= InpTrailingStartPips)
              {
               double newSL = NormalizeDouble(symInfo.Ask() + InpTrailingDistPips * pipsToPoints, symInfo.Digits());
               if(currentSL == 0 || newSL < currentSL - InpTrailingStepPips * pipsToPoints)
                  trade.PositionModify(ticket, newSL, currentTP);
              }
           }
     }
  }

//+------------------------------------------------------------------+
//| Manage Partial Closes                                            |
//+------------------------------------------------------------------+
void ManagePartialCloses()
  {
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      if(!posInfo.SelectByIndex(i))
         continue;
      if(posInfo.Symbol() != tradingSymbol || posInfo.Magic() != InpMagicNumber)
         continue;

      ulong ticket = posInfo.Ticket();
      if(IsAlreadyPartiallyClosed(ticket))
         continue;

      double openPrice = posInfo.PriceOpen();
      double volume = posInfo.Volume();
      double pipsToPoints = PointsPerPip() * symInfo.Point();
      double profitPips = 0;

      if(posInfo.PositionType() == POSITION_TYPE_BUY)
         profitPips = (symInfo.Bid() - openPrice) / pipsToPoints;
      else
         profitPips = (openPrice - symInfo.Ask()) / pipsToPoints;

      if(profitPips >= InpPartialClosePips)
        {
         double closeVolume = NormalizeDouble(volume * InpPartialClosePercent / 100.0, 2);
         double minLot = symInfo.LotsMin();
         double lotStep = symInfo.LotsStep();
         closeVolume = MathMax(closeVolume, minLot);
         closeVolume = MathFloor(closeVolume / lotStep) * lotStep;
         closeVolume = NormalizeDouble(closeVolume, 2);

         if(volume - closeVolume < minLot)
            continue;

         if(trade.PositionClosePartial(ticket, closeVolume))
           {
            MarkAsPartiallyClosed(ticket);
            TradeEventLog("Partial close #" + IntegerToString((int)ticket) + ": " +
                       DoubleToString(closeVolume, 2) + " lots at " +
                       DoubleToString(profitPips, 1) + " pips");

            if(InpMoveSLToBreakeven)
              {
               Sleep(100);
               if(PositionSelectByTicket(ticket))
                 {
                  double tp = PositionGetDouble(POSITION_TP);
                  double spreadBuf = symInfo.Spread() * symInfo.Point() * 2;
                  double beSL = (posInfo.PositionType() == POSITION_TYPE_BUY) ?
                                openPrice + spreadBuf : openPrice - spreadBuf;
                  beSL = NormalizeDouble(beSL, symInfo.Digits());
                  trade.PositionModify(ticket, beSL, tp);
                 }
              }
           }
        }
      else if (InpEnablePartialLoss && profitPips <= -InpPartialLossPips)
        {
         double closeVolume = NormalizeDouble(volume * InpPartialClosePercent / 100.0, 2);
         double minLot = symInfo.LotsMin();
         double lotStep = symInfo.LotsStep();
         closeVolume = MathMax(closeVolume, minLot);
         closeVolume = MathFloor(closeVolume / lotStep) * lotStep;
         closeVolume = NormalizeDouble(closeVolume, 2);

         if(volume - closeVolume < minLot)
            continue;

         if(trade.PositionClosePartial(ticket, closeVolume))
           {
            MarkAsPartiallyClosed(ticket);
            TradeEventLog("PARTIAL LOSS close #" + IntegerToString((int)ticket) + ": " +
                       DoubleToString(closeVolume, 2) + " lots at " +
                       DoubleToString(profitPips, 1) + " pips");
           }
        }
     }
  }

//+------------------------------------------------------------------+
//| Is Already Partially Closed                                      |
//+------------------------------------------------------------------+
bool IsAlreadyPartiallyClosed(ulong ticket)
  {
   for(int i = 0; i < ArraySize(partialCloseTracker); i++)
      if(partialCloseTracker[i].ticket == ticket && partialCloseTracker[i].closed)
         return true;
   return false;
  }

//+------------------------------------------------------------------+
//| Mark As Partially Closed                                         |
//+------------------------------------------------------------------+
void MarkAsPartiallyClosed(ulong ticket)
  {
   int idx = ArraySize(partialCloseTracker);
   ArrayResize(partialCloseTracker, idx + 1);
   partialCloseTracker[idx].ticket = ticket;
   partialCloseTracker[idx].closed = true;
  }

//+------------------------------------------------------------------+
//| Calculate Hedged Pairs Profit (excludes orphans)                  |
//+------------------------------------------------------------------+
double CalculateHedgedPairsProfit()
  {
   double profit = 0;
   for(int i = 0; i < ArraySize(tradePairs); i++)
     {
      if(tradePairs[i].buyTicket > 0 && !tradePairs[i].buyClosed &&
         PositionSelectByTicket(tradePairs[i].buyTicket))
         profit += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
      if(tradePairs[i].sellTicket > 0 && !tradePairs[i].sellClosed &&
         PositionSelectByTicket(tradePairs[i].sellTicket))
         profit += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
     }
   return profit;
  }

//+------------------------------------------------------------------+
//| Check Basket Close                                               |
//+------------------------------------------------------------------+
void CheckBasketClose()
  {
   // Use hedged-pairs-only profit so orphaned legs don't trigger mass close
   double basketProfit = CalculateHedgedPairsProfit();
   if(basketProfit >= InpBasketProfitUSD)
      CloseAllPositions("Basket profit: " + DoubleToString(basketProfit, 2));
   else
      if(basketProfit <= -InpBasketLossUSD)
         CloseAllPositions("Basket loss: " + DoubleToString(basketProfit, 2));
  }

//+------------------------------------------------------------------+
//| Calculate Basket Profit                                          |
//+------------------------------------------------------------------+
double CalculateBasketProfit()
  {
   double profit = 0;
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      if(!posInfo.SelectByIndex(i))
         continue;
      if(posInfo.Symbol() != tradingSymbol || posInfo.Magic() != InpMagicNumber)
         continue;
      profit += posInfo.Profit() + posInfo.Swap();
     }
   return profit;
  }

//+------------------------------------------------------------------+
//| Refresh EA                                                       |
//+------------------------------------------------------------------+
void RefreshEA()
  {
   DetectMarketRegime();
   ScanExistingPositions();
   lastRefreshTime = TimeCurrent();
  }

//+------------------------------------------------------------------+
//| Update Equity Tracking                                           |
//+------------------------------------------------------------------+
void UpdateEquityTracking()
  {
   double currentEquity = accInfo.Equity();
   if(currentEquity > peakEquity)
      peakEquity = currentEquity;
   if(currentEquity > session.sessionPeakEquity)
      session.sessionPeakEquity = currentEquity;

   double ddFromPeak = (peakEquity > 0) ? (peakEquity - currentEquity) / peakEquity * 100 : 0;
   if(ddFromPeak > session.sessionMaxDD)
      session.sessionMaxDD = ddFromPeak;

   if(InpEnableEquityProtect && peakEquity > 0 &&
      currentEquity < peakEquity * InpEquityHWMPercent / 100.0)
      CloseAllPositions("Equity protection at " + DoubleToString(currentEquity, 2));
  }

//+------------------------------------------------------------------+
//| Scan Existing Positions                                          |
//+------------------------------------------------------------------+
void ScanExistingPositions()
  {
   ulong buyTickets[], sellTickets[];
   double buyLots[], sellLots[], buyPrices[], sellPrices[];
   ArrayResize(buyTickets, 0);
   ArrayResize(sellTickets, 0);
   ArrayResize(buyLots, 0);
   ArrayResize(sellLots, 0);
   ArrayResize(buyPrices, 0);
   ArrayResize(sellPrices, 0);

   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      if(!posInfo.SelectByIndex(i))
         continue;
      if(posInfo.Symbol() != tradingSymbol || posInfo.Magic() != InpMagicNumber)
         continue;

      if(posInfo.PositionType() == POSITION_TYPE_BUY)
        {
         int idx = ArraySize(buyTickets);
         ArrayResize(buyTickets, idx + 1);
         ArrayResize(buyLots, idx + 1);
         ArrayResize(buyPrices, idx + 1);
         buyTickets[idx] = posInfo.Ticket();
         buyLots[idx] = posInfo.Volume();
         buyPrices[idx] = posInfo.PriceOpen();
        }
      else
        {
         int idx = ArraySize(sellTickets);
         ArrayResize(sellTickets, idx + 1);
         ArrayResize(sellLots, idx + 1);
         ArrayResize(sellPrices, idx + 1);
         sellTickets[idx] = posInfo.Ticket();
         sellLots[idx] = posInfo.Volume();
         sellPrices[idx] = posInfo.PriceOpen();
        }
     }

   ArrayResize(tradePairs, 0);
   for(int i = 0; i < ArraySize(buyTickets); i++)
     {
      if(buyTickets[i] == 0)
         continue;
      for(int j = 0; j < ArraySize(sellTickets); j++)
        {
         if(sellTickets[j] == 0)
            continue;
         if(buyLots[i] > 0 && MathAbs(buyLots[i] - sellLots[j]) / buyLots[i] < 0.05)
           {
            int idx = ArraySize(tradePairs);
            ArrayResize(tradePairs, idx + 1);
            ZeroMemory(tradePairs[idx]);
            tradePairs[idx].buyTicket = buyTickets[i];
            tradePairs[idx].sellTicket = sellTickets[j];
            tradePairs[idx].lotSize = buyLots[i];
            tradePairs[idx].openTime = TimeCurrent();
            tradePairs[idx].buyEntryPrice = buyPrices[i];
            tradePairs[idx].sellEntryPrice = sellPrices[j];
            buyTickets[i] = 0;
            sellTickets[j] = 0;
            break;
           }
        }
     }

   ArrayResize(orphanedLegs, 0);
   for(int i = 0; i < ArraySize(buyTickets); i++)
      if(buyTickets[i] > 0)
         AddOrphanedLeg(buyTickets[i], true, buyLots[i]);
   for(int i = 0; i < ArraySize(sellTickets); i++)
      if(sellTickets[i] > 0)
         AddOrphanedLeg(sellTickets[i], false, sellLots[i]);
  }

//+------------------------------------------------------------------+
//| Get Complete Pairs Count                                         |
//+------------------------------------------------------------------+
int GetCompletePairsCount()
  {
   int count = 0;
   for(int i = 0; i < ArraySize(tradePairs); i++)
      if(tradePairs[i].buyTicket > 0 && tradePairs[i].sellTicket > 0 &&
         !tradePairs[i].buyClosed && !tradePairs[i].sellClosed)
         count++;
   return count;
  }

//+------------------------------------------------------------------+
//| Get Incomplete Pairs Count                                       |
//+------------------------------------------------------------------+
int GetIncompletePairsCount()
  {
   int count = 0;
   for(int i = 0; i < ArraySize(tradePairs); i++)
     {
      bool buyOpen = (tradePairs[i].buyTicket > 0 && !tradePairs[i].buyClosed);
      bool sellOpen = (tradePairs[i].sellTicket > 0 && !tradePairs[i].sellClosed);
      if((buyOpen && !sellOpen) || (sellOpen && !buyOpen))
         count++;
     }
   return count;
  }

//+------------------------------------------------------------------+
//| Find Incomplete Pair                                             |
//+------------------------------------------------------------------+
int FindIncompletePair()
  {
   for(int i = 0; i < ArraySize(tradePairs); i++)
     {
      bool buyOpen = (tradePairs[i].buyTicket > 0 && !tradePairs[i].buyClosed);
      bool sellOpen = (tradePairs[i].sellTicket > 0 && !tradePairs[i].sellClosed);
      if((buyOpen && !sellOpen) || (sellOpen && !buyOpen))
         return i;
     }
   return -1;
  }

//+------------------------------------------------------------------+
//| Complete Incomplete Pair                                         |
//+------------------------------------------------------------------+
bool CompleteIncompletePair(int pairIndex)
  {
   if(pairIndex < 0 || pairIndex >= ArraySize(tradePairs))
      return false;

   double spreadCost = symInfo.Spread() * symInfo.Point() * tradePairs[pairIndex].lotSize * PointsPerPip();
   double profitTarget = MathMax(5.0, spreadCost * 2.0) + spreadCost;

   bool buyOpen = (tradePairs[pairIndex].buyTicket > 0 && !tradePairs[pairIndex].buyClosed);
  bool sellOpen = (tradePairs[pairIndex].sellTicket > 0 && !tradePairs[pairIndex].sellClosed);

  if(!buyOpen && sellOpen)
     {
      if(PositionSelectByTicket(tradePairs[pairIndex].sellTicket))
        {
         double profit = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
         if(profit >= profitTarget)
           {
            if(InpUseMTFConfirmation && !ConfirmEntryDirection(true))
               return false;
            if(InpEnableDXYCorrelation && !DoesDXYConfirmDirection(true))
               return false;

            double slPips = GetAdaptiveSL();
            double tpPips = GetAdaptiveTP();
            ulong buyTicket = ExecuteTrade(true, tradePairs[pairIndex].lotSize, slPips, tpPips);
            if(buyTicket > 0)
              {
               tradePairs[pairIndex].buyTicket = buyTicket;
               tradePairs[pairIndex].buyClosed = false;
               tradePairs[pairIndex].buyEntryPrice = symInfo.Ask();
               return true;
              }
           }
        }
     }
   else
      if(!sellOpen && buyOpen)
        {
         if(PositionSelectByTicket(tradePairs[pairIndex].buyTicket))
           {
            double profit = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
            if(profit >= profitTarget)
              {
               if(InpUseMTFConfirmation && !ConfirmEntryDirection(false))
                  return false;
               if(InpEnableDXYCorrelation && !DoesDXYConfirmDirection(false))
                  return false;

               double slPips = GetAdaptiveSL();
               double tpPips = GetAdaptiveTP();
               ulong sellTicket = ExecuteTrade(false, tradePairs[pairIndex].lotSize, slPips, tpPips);
               if(sellTicket > 0)
                 {
                  tradePairs[pairIndex].sellTicket = sellTicket;
                  tradePairs[pairIndex].sellClosed = false;
                  tradePairs[pairIndex].sellEntryPrice = symInfo.Bid();
                  return true;
                 }
              }
           }
        }

   return false;
  }

//+------------------------------------------------------------------+
//| Create Hedged Pair                                               |
//+------------------------------------------------------------------+
bool CreateHedgedPair()
  {
   double lotSize = CalculatePositionSize();
   if(lotSize <= 0)
      return false;

//--- Keep lot size constant (disable multipliers)
   // Session-based lot adjustment disabled to keep lot size constant
   // Breakout-based lot adjustment disabled to keep lot size constant

//--- Cap lot size
   if(InpMaxPositionSize > 0 && lotSize > InpMaxPositionSize)
      lotSize = InpMaxPositionSize;
   lotSize = MathMax(lotSize, symInfo.LotsMin());

   bool openBuyFirst = DetermineFirstLegDirection();

   int idx = ArraySize(tradePairs);
   ArrayResize(tradePairs, idx + 1);
   ZeroMemory(tradePairs[idx]);
   tradePairs[idx].lotSize = lotSize;
   tradePairs[idx].openTime = TimeCurrent();

   double slPips = GetAdaptiveSL();
   double tpPips = GetAdaptiveTP();
   ulong firstTicket = ExecuteTrade(openBuyFirst, lotSize, slPips, tpPips);

   if(firstTicket > 0)
     {
      if(openBuyFirst)
        {
         tradePairs[idx].buyTicket = firstTicket;
         tradePairs[idx].buyEntryPrice = symInfo.Ask();
        }
      else
        {
         tradePairs[idx].sellTicket = firstTicket;
         tradePairs[idx].sellEntryPrice = symInfo.Bid();
        }
      AddOrphanedLeg(firstTicket, openBuyFirst, lotSize, idx);
      TradeEventLog("Created pair #" + IntegerToString((int)firstTicket) +
                 " Signal=" + DoubleToString(signalScore, 3));
      return true;
     }

   ArrayResize(tradePairs, idx);
   return false;
  }

//+------------------------------------------------------------------+
//| Determine First Leg Direction                                    |
//+------------------------------------------------------------------+
bool DetermineFirstLegDirection()
  {
//--- Breakout override (respecting MES)
   if(InpEnableBreakout && breakout.breakoutDetected)
     {
      if(breakout.breakoutBullish && !mesBlockBuy)
         return true;
      if(!breakout.breakoutBullish && !mesBlockSell)
         return false;
     }

//--- Use composite signal score
   if(MathAbs(signalScore) > 0.2)
     {
      if(signalScore > 0 && !mesBlockBuy) return true;
      if(signalScore < 0 && !mesBlockSell) return false;
     }

//--- Fallback to EMA
   double ema5[], ema20[];
   ArraySetAsSeries(ema5, true);
   ArraySetAsSeries(ema20, true);
   if(CopyBuffer(hEMA5, 0, 0, 3, ema5) < 3)
      return true;
   if(CopyBuffer(hEMA20, 0, 0, 3, ema20) < 3)
      return true;

   bool emaBullish = ema5[0] > ema20[0];

   if(emaBullish && !mesBlockBuy) return true;
   if(!emaBullish && !mesBlockSell) return false;

//--- Ultimate fallback
   if(!mesBlockBuy) return true;
   if(!mesBlockSell) return false;

   return true;
  }

//+------------------------------------------------------------------+
//| Multi-Timeframe Entry Confirmation                               |
//+------------------------------------------------------------------+
bool ConfirmEntryDirection(bool isBuy)
  {
   if(!InpUseMTFConfirmation)
      return true;

   if(hADX_HTF != INVALID_HANDLE)
     {
      double htfADX[], htfPlus[], htfMinus[];
      ArraySetAsSeries(htfADX, true);
      ArraySetAsSeries(htfPlus, true);
      ArraySetAsSeries(htfMinus, true);

      if(CopyBuffer(hADX_HTF, 0, 0, 2, htfADX) >= 2 &&
         CopyBuffer(hADX_HTF, 1, 0, 2, htfPlus) >= 2 &&
         CopyBuffer(hADX_HTF, 2, 0, 2, htfMinus) >= 2)
        {
         if(htfADX[0] > InpTrendThreshold)
           {
            bool htfBullish = htfPlus[0] > htfMinus[0];
            if(isBuy && !htfBullish)
               return false;
            if(!isBuy && htfBullish)
               return false;
           }
        }
     }

   if(hEMA5_LTF != INVALID_HANDLE && hEMA20_LTF != INVALID_HANDLE)
     {
      double ltfEma5[], ltfEma20[];
      ArraySetAsSeries(ltfEma5, true);
      ArraySetAsSeries(ltfEma20, true);

      if(CopyBuffer(hEMA5_LTF, 0, 0, 2, ltfEma5) >= 2 &&
         CopyBuffer(hEMA20_LTF, 0, 0, 2, ltfEma20) >= 2)
        {
         bool ltfBullish = ltfEma5[0] > ltfEma20[0];
         if(isBuy && !ltfBullish)
            return false;
         if(!isBuy && ltfBullish)
            return false;
        }
     }

   return true;
  }

//+------------------------------------------------------------------+
//| Execute Trade                                                    |
//+------------------------------------------------------------------+
ulong ExecuteTrade(bool isBuy, double lotSize, double slPips, double tpPips)
  {
   symInfo.RefreshRates();

   double price = isBuy ? symInfo.Ask() : symInfo.Bid();
   double pipsToPrice = PointsPerPip() * symInfo.Point();
   double sl = isBuy ? price - slPips * pipsToPrice : price + slPips * pipsToPrice;
   double tp = isBuy ? price + tpPips * pipsToPrice : price - tpPips * pipsToPrice;

   sl = NormalizeDouble(sl, symInfo.Digits());
   tp = NormalizeDouble(tp, symInfo.Digits());
   price = NormalizeDouble(price, symInfo.Digits());

   bool result = isBuy ? trade.Buy(lotSize, tradingSymbol, price, sl, tp, EA_NAME) :
                 trade.Sell(lotSize, tradingSymbol, price, sl, tp, EA_NAME);

   if(result)
     {
      ulong ticket = trade.ResultOrder();
      if(ticket > 0)
        {
         lastTradeTime = TimeCurrent();
         TradeEventLog("Trade OK: #" + IntegerToString((int)ticket) +
                    " " + (isBuy ? "BUY" : "SELL") + " " + DoubleToString(lotSize, 2));
         return ticket;
        }
      ulong dealTicket = trade.ResultDeal();
      if(dealTicket > 0)
        {
         lastTradeTime = TimeCurrent();
         Sleep(100);
         for(int i = PositionsTotal() - 1; i >= 0; i--)
            if(posInfo.SelectByIndex(i))
               if(posInfo.Symbol() == tradingSymbol && posInfo.Magic() == InpMagicNumber)
                  if(MathAbs(posInfo.Volume() - lotSize) < 0.001)
                     return posInfo.Ticket();
        }
     }
   else
      TradeEventLogCritical("Trade FAILED: " + IntegerToString(trade.ResultRetcode()) +
                 " " + trade.ResultRetcodeDescription());

   return 0;
  }

//+------------------------------------------------------------------+
//| Add Orphaned Leg                                                 |
//+------------------------------------------------------------------+
void AddOrphanedLeg(ulong ticket, bool isBuy, double lotSize, int pairIndex = -1)
  {
   if(ticket == 0 || !PositionSelectByTicket(ticket))
      return;

   bool positionIsBuy = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY);
   isBuy = positionIsBuy;

   for(int i = 0; i < ArraySize(orphanedLegs); i++)
      if(orphanedLegs[i].ticket == ticket)
         return;

   int idx = ArraySize(orphanedLegs);
   ArrayResize(orphanedLegs, idx + 1);
   orphanedLegs[idx].ticket = ticket;
   orphanedLegs[idx].isBuy = isBuy;
   orphanedLegs[idx].lotSize = lotSize;
   orphanedLegs[idx].entryPrice = PositionGetDouble(POSITION_PRICE_OPEN);
   orphanedLegs[idx].closeTime = TimeCurrent();
   orphanedLegs[idx].pairIndex = pairIndex;
   orphanedLegs[idx].partialClosed = false;
   orphanedLegs[idx].velocityCheckTime = 0;
   orphanedLegs[idx].pipsAtLastSample = 0.0;
   orphanedLegs[idx].pipVelocity = 0.0;
   orphanedLegs[idx].partialCloseFired = false;
   orphanedLegs[idx].drawdownLevel = 0;

   double currentProfit = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
   orphanedLegs[idx].initialLoss = (currentProfit < 0) ? MathAbs(currentProfit) : 0.0;
  }

//+------------------------------------------------------------------+
//| Close Orphaned Leg at Profit                                     |
//+------------------------------------------------------------------+
bool CloseOrphanedLegAtProfit(int orphanIndex)
  {
   if(orphanIndex < 0 || orphanIndex >= ArraySize(orphanedLegs))
      return false;
   ulong ticket = orphanedLegs[orphanIndex].ticket;
   if(!PositionSelectByTicket(ticket))
     {
      RemoveOrphanedLeg(orphanIndex);
      return true;
     }
   if(trade.PositionClose(ticket))
     {
      RemoveOrphanedLeg(orphanIndex);
      return true;
     }
   return false;
  }

//+------------------------------------------------------------------+
//| Re-Hedge Orphaned Leg                                            |
//+------------------------------------------------------------------+
bool ReHedgeOrphanedLeg(int orphanIndex)
  {
   if(orphanIndex < 0 || orphanIndex >= ArraySize(orphanedLegs))
      return false;
   if(!PositionSelectByTicket(orphanedLegs[orphanIndex].ticket))
     {
      RemoveOrphanedLeg(orphanIndex);
      return true;
     }

   bool currentIsBuy = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY);
   orphanedLegs[orphanIndex].isBuy = currentIsBuy;

   bool newIsBuy = !currentIsBuy;
   double reHedgeLot = CalculateReHedgeLot(orphanIndex);
   ulong newTicket = ExecuteTrade(newIsBuy, reHedgeLot, GetAdaptiveSL(), GetAdaptiveTP());
   if(newTicket == 0)
      return false;

   int pairIndex = orphanedLegs[orphanIndex].pairIndex;
   if(pairIndex >= 0 && pairIndex < ArraySize(tradePairs))
     {
      if(orphanedLegs[orphanIndex].isBuy)
        {
         tradePairs[pairIndex].sellTicket = newTicket;
         tradePairs[pairIndex].sellClosed = false;
         tradePairs[pairIndex].sellEntryPrice = symInfo.Bid();
        }
      else
        {
         tradePairs[pairIndex].buyTicket = newTicket;
         tradePairs[pairIndex].buyClosed = false;
         tradePairs[pairIndex].buyEntryPrice = symInfo.Ask();
        }
     }
   else
     {
      pairIndex = ArraySize(tradePairs);
      ArrayResize(tradePairs, pairIndex + 1);
      ZeroMemory(tradePairs[pairIndex]);
      tradePairs[pairIndex].lotSize = reHedgeLot;
      tradePairs[pairIndex].openTime = TimeCurrent();
      if(orphanedLegs[orphanIndex].isBuy)
        {
         tradePairs[pairIndex].buyTicket = orphanedLegs[orphanIndex].ticket;
         tradePairs[pairIndex].sellTicket = newTicket;
         tradePairs[pairIndex].buyClosed = false;
         tradePairs[pairIndex].sellClosed = false;
         tradePairs[pairIndex].buyEntryPrice = orphanedLegs[orphanIndex].entryPrice;
         tradePairs[pairIndex].sellEntryPrice = symInfo.Bid();
        }
      else
        {
         tradePairs[pairIndex].buyTicket = newTicket;
         tradePairs[pairIndex].sellTicket = orphanedLegs[orphanIndex].ticket;
         tradePairs[pairIndex].buyClosed = false;
         tradePairs[pairIndex].sellClosed = false;
         tradePairs[pairIndex].buyEntryPrice = symInfo.Ask();
         tradePairs[pairIndex].sellEntryPrice = orphanedLegs[orphanIndex].entryPrice;
        }
     }

   RemoveOrphanedLeg(orphanIndex);
   return true;
  }

//+------------------------------------------------------------------+
//| Remove Orphaned Leg                                              |
//+------------------------------------------------------------------+
void RemoveOrphanedLeg(int orphanIndex)
  {
   if(orphanIndex < 0 || orphanIndex >= ArraySize(orphanedLegs))
      return;
   for(int i = orphanIndex; i < ArraySize(orphanedLegs) - 1; i++)
      orphanedLegs[i] = orphanedLegs[i + 1];
   ArrayResize(orphanedLegs, ArraySize(orphanedLegs) - 1);
  }

//+------------------------------------------------------------------+
//| Handle Stop Loss Hit                                             |
//+------------------------------------------------------------------+
void HandleStopLossHit(ulong ticket, bool isBuy, double lotSize)
  {
   consecutiveSLHits++;
   for(int i = 2; i > 0; i--)
      lastThreeSLTimes[i] = lastThreeSLTimes[i - 1];
   lastThreeSLTimes[0] = TimeCurrent();
   lastSLHitTime = TimeCurrent();

   if(consecutiveSLHits >= 3 && lastThreeSLTimes[0] - lastThreeSLTimes[2] < 3600)
     {
      tradingPausedDueToSL = true;
      inSLTradeDelay = true;
     }
   else
      inSLTradeDelay = true;
  }

//+------------------------------------------------------------------+
//| Is Trade Allowed - Master Check                                  |
//+------------------------------------------------------------------+
bool IsTradeAllowed()
  {
   lastTradeBlockReason = "";

//--- Tester prop firm check
   if(MQLInfoInteger(MQL_TESTER))
     {
      if(InpPropFirmMode && (IsDailyLossLimitBreached() || IsTotalDrawdownBreached()))
        {
         lastTradeBlockReason = "Prop firm loss limit breached (tester)";
         return false;
        }
     }

//--- Terminal/account trading permissions
   if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || !AccountInfoInteger(ACCOUNT_TRADE_ALLOWED))
     {
      lastTradeBlockReason = "Terminal or account trading not allowed";
      return false;
     }

//--- SL delay
   if(inSLTradeDelay && TimeCurrent() - lastSLHitTime <= InpSLDelayMinutes * 60)
     {
      int remaining = (int)(InpSLDelayMinutes * 60 - (TimeCurrent() - lastSLHitTime));
      lastTradeBlockReason = "SL delay active (" + IntegerToString(remaining / 60) + "m " +
                             IntegerToString(remaining % 60) + "s remaining)";
      return false;
     }
   if(tradingPausedDueToSL)
     {
      lastTradeBlockReason = "Trading paused: 3+ SL hits within 1 hour";
      return false;
     }

//--- Session paused by learning
   if(session.currentAction == SESSION_PAUSED)
     {
      lastTradeBlockReason = "Session PAUSED (5+ consecutive losses)";
      return false;
     }

//--- Phase 2: Re-entry cooldown
   if(InpReEntryCooldownMin > 0 && lastTradeTime > 0 &&
      TimeCurrent() - lastTradeTime < InpReEntryCooldownMin * 60)
     {
      int cooldownRemaining = (int)(InpReEntryCooldownMin * 60 - (TimeCurrent() - lastTradeTime));
      lastTradeBlockReason = "Re-entry cooldown (" + IntegerToString(cooldownRemaining / 60) + "m " +
                             IntegerToString(cooldownRemaining % 60) + "s remaining)";
      return false;
     }

//--- Max pairs limit (always enforced for NEW pairs)
   int totalPairs = GetCompletePairsCount() + GetIncompletePairsCount();
   if(totalPairs >= InpMaxPairsAllowed)
     {
      lastTradeBlockReason = "Max pairs reached (" + IntegerToString(totalPairs) +
                             "/" + IntegerToString(InpMaxPairsAllowed) + ")";
      return false;
     }

//--- Market regime
   if(!IsRegimeAllowed(currentRegime))
     {
      lastTradeBlockReason = "Regime not allowed: " + GetRegimeString(currentRegime);
      return false;
     }

//--- Phase 2: Block entries in HIGH_VOLATILITY regime
   if(currentRegime == REGIME_HIGH_VOLATILITY)
     {
      lastTradeBlockReason = "High volatility regime - entries blocked";
      return false;
     }

//--- Volatility
   if(currentVolatilityPts > InpMaxVolatilityPts)
     {
      lastTradeBlockReason = "Volatility too high: " + DoubleToString(currentVolatilityPts, 0) +
                             " > " + DoubleToString(InpMaxVolatilityPts, 0) + " pts";
      return false;
     }

//--- Phase 3: Momentum Exhaustion Filter
   if(InpEnableMomentumExhaustion && momentumExhaustionScore >= InpMESFullBlockThreshold)
     {
      lastTradeBlockReason = "Momentum exhausted (" +
         DoubleToString(momentumExhaustionScore * 100, 0) + "%)";
      return false;
     }

//--- Session filter
   if(!IsSessionAllowed())
     {
      lastTradeBlockReason = "Session not allowed: " + GetSessionName(GetCurrentSession());
      VerboseLogThrottled(lastTradeBlockReason, _lastLogTime_session, logThrottleIntervalSec);
      return false;
     }

//--- News filter
   if(IsNewsFilterBlocking())
     {
      lastTradeBlockReason = "News filter active - trading blocked";
      VerboseLogThrottled(lastTradeBlockReason, _lastLogTime_news, logThrottleIntervalSec);
      return false;
     }

//--- Spread filter
   if(!IsSpreadAcceptable())
     {
      lastTradeBlockReason = "Spread too high: " + DoubleToString((double)symInfo.Spread(), 1) +
                             " (avg " + DoubleToString(averageSpread, 1) + ")";
      return false;
     }

//--- Monte Carlo viability
   if(InpEnableMonteCarlo && !mcResult.isViable)
     {
      lastTradeBlockReason = "Monte Carlo: strategy not viable at " +
                             DoubleToString(InpMCConfidenceLevel * 100, 0) + "% confidence";
      VerboseLogThrottled(lastTradeBlockReason, _lastLogTime_trade, logThrottleIntervalSec);
      return false;
     }

//--- Free margin
   if(accInfo.FreeMargin() < InpMinFreeMargin)
     {
      lastTradeBlockReason = "Insufficient free margin: " + DoubleToString(accInfo.FreeMargin(), 2) +
                             " < " + DoubleToString(InpMinFreeMargin, 2);
      return false;
     }

//--- Basket drawdown
   double basketProfit = CalculateBasketProfit();
   if(InpMaxBasketDDPercent > 0 && basketProfit < 0 &&
      MathAbs(basketProfit) / accInfo.Balance() * 100 > InpMaxBasketDDPercent)
     {
      lastTradeBlockReason = "Basket drawdown exceeded: " +
                             DoubleToString(MathAbs(basketProfit) / accInfo.Balance() * 100, 1) +
                             "% > " + DoubleToString(InpMaxBasketDDPercent, 1) + "%";
      return false;
     }

//--- Prop firm daily target
   if(InpPropFirmMode && session.dailyTargetReached)
      session.currentAction = SESSION_CONSERVATIVE;

//--- Learning hour filter
   if(InpEnableLearning && learningInitialized && learning.totalTrades >= 30)
     {
      MqlDateTime dt;
      TimeToStruct(TimeCurrent(), dt);
      if(learning.hourlyTradeCount[dt.hour] >= 5 && learning.hourlyWinRate[dt.hour] < 0.35)
        {
         lastTradeBlockReason = "Hour " + IntegerToString(dt.hour) + ":00 has poor win rate (" +
                                DoubleToString(learning.hourlyWinRate[dt.hour] * 100, 1) + "%)";
         return false;
        }
     }

   lastTradeBlockReason = "";
   return true;
  }

//+------------------------------------------------------------------+
//| Print Trade Attempt Diagnostic                                   |
//+------------------------------------------------------------------+
void PrintTradeAttemptDiagnostic()
   {
    string nl = "\n";
    string diag = "═══ TRADE DIAGNOSTIC ═══" + nl;
    
    // Block reason
    if(lastTradeBlockReason != "")
       diag += "STATUS: BLOCKED - " + lastTradeBlockReason + nl;
    else
       diag += "STATUS: ALLOWED (waiting for signal)" + nl;
    
    // Core metrics
    diag += "Pairs: " + IntegerToString(GetCompletePairsCount()) + "/" + IntegerToString(InpMaxPairsAllowed) + nl;
    diag += "Signal: " + DoubleToString(signalScore, 3) + " (" + SignalStrengthToString(signalStrength) +
            ") | Need >= MODERATE (0.2)" + nl;
    diag += "Spread: " + DoubleToString((double)symInfo.Spread(), 1) +
            " (avg " + DoubleToString(averageSpread, 1) + ")" + nl;
    diag += "Vol: " + DoubleToString(currentVolatilityPts, 0) + "pts" +
            " (max " + DoubleToString(InpMaxVolatilityPts, 0) + ")" + nl;
    diag += "Regime: " + GetRegimeString(currentRegime) + nl;
    
    // Signal components breakdown
    diag += "--- Signal Components ---" + nl;
    diag += "EMA: " + DoubleToString(GetEMASignal(), 3) + nl;
    diag += "Regime: " + DoubleToString(GetRegimeSignal(), 3) + nl;
    diag += "RSI: " + DoubleToString(GetRSISignal(), 3) + nl;
    diag += "Session: " + DoubleToString(GetSessionQualitySignal(), 3) + nl;
    if(InpEnableDXYCorrelation && dxyAvailable)
       diag += "DXY: " + DoubleToString(GetDXYSignal(), 3) + nl;
    else
       diag += "DXY: N/A" + nl;
    if(InpEnableBreakout)
       diag += "Breakout: " + (breakout.breakoutDetected ? "YES" : "NO") + nl;
    
    // Filter status
    diag += "--- Filter Status ---" + nl;
    diag += "Session: " + (IsSessionAllowed() ? "PASS" : "FAIL") +
            " (" + GetSessionName(GetCurrentSession()) + ")" + nl;
    diag += "News: " + (newsFilterActive ? "BLOCKING" : "CLEAR") + nl;
    diag += "Spread: " + (IsSpreadAcceptable() ? "PASS" : "FAIL") + nl;
    diag += "MC: " + (InpEnableMonteCarlo ? (mcResult.isViable ? "VIABLE" : "NOT VIABLE") : "OFF") + nl;
    diag += "Margin: " + DoubleToString(accInfo.FreeMargin(), 2) +
            " (min " + DoubleToString(InpMinFreeMargin, 2) + ")" + nl;
    
    Print(diag);
   }

//+------------------------------------------------------------------+
//| Calculate Position Size                                          |
//+------------------------------------------------------------------+
double CalculatePositionSize()
  {
   double lotSize = 0;
   double minLot = symInfo.LotsMin();
   double lotStep = symInfo.LotsStep();
   double maxLot = symInfo.LotsMax();
   double pipValue = GetPipValue();
   if(pipValue <= 0)
      return 0;

   double riskPercent = InpRiskPerTrade;
   if(InpAdaptiveLotSize && InpEnableLearning && learningInitialized && learning.totalTrades >= 20)
      riskPercent = learning.optimalRiskPercent;
   if(session.currentAction == SESSION_CONSERVATIVE)
      riskPercent *= 0.5;

   double slPips = GetAdaptiveSL();

   if(InpUsePercentRisk)
     {
      double riskAmount = accInfo.Balance() * riskPercent / 100.0;
      lotSize = riskAmount / (slPips * pipValue);

      // Market adjustment (only for risk-based sizing)
      double atrVal[], adxVal[];
      ArraySetAsSeries(atrVal, true);
      ArraySetAsSeries(adxVal, true);
      double marketFactor = 1.0;

      if(CopyBuffer(hATR_MTF, 0, 0, 2, atrVal) >= 2 && CopyBuffer(hADX_MTF, 0, 0, 2, adxVal) >= 2)
        {
         double volFactor = MathMin(atrVal[0] / symInfo.Point() / InpMaxVolatilityPts, 1.0);
         double trendFactor = adxVal[0] / InpTrendThreshold;
         marketFactor = MathMax(0.5, MathMin((volFactor + trendFactor) / 2.0, 1.5));
        }
      lotSize *= marketFactor;

      // Safety caps (percentage-based, only for risk-based sizing)
      double potentialLoss = lotSize * slPips * pipValue;
      if(potentialLoss > accInfo.Equity() * 0.05)
         lotSize = accInfo.Equity() * 0.05 / (slPips * pipValue);
      if(potentialLoss > accInfo.Balance() * 0.1)
         lotSize = accInfo.Balance() * 0.1 / (slPips * pipValue);
     }
   else
      lotSize = InpFixedLotSize;

// Safety caps that ALWAYS apply (regardless of sizing mode)
   if(InpMaxPositionSize > 0 && lotSize > InpMaxPositionSize)
      lotSize = InpMaxPositionSize;

   double marginRequired = 0;
   if(!OrderCalcMargin(ORDER_TYPE_BUY, tradingSymbol, lotSize, symInfo.Ask(), marginRequired))
      marginRequired = lotSize * symInfo.Ask() / 100;
   if(marginRequired > accInfo.FreeMargin() * 0.8)
      lotSize = (accInfo.FreeMargin() * 0.8) / (marginRequired / lotSize);

   if(InpPropFirmMode)
     {
      double dailyLossLimit = initialAccountBalance * InpMaxDailyLossPercent / 100.0;
      double remainingLoss = dailyLossLimit - MathAbs(MathMin(session.dailyPnL, 0));
      double maxLotDaily = remainingLoss / (slPips * pipValue);
      if(lotSize > maxLotDaily && maxLotDaily > 0)
         lotSize = maxLotDaily;
     }

// Normalize
   lotSize = MathMax(lotSize, minLot);
   lotSize = MathFloor(lotSize / lotStep) * lotStep;
   lotSize = NormalizeDouble(lotSize, 2);
   if(lotSize > maxLot)
      lotSize = maxLot;

   return (lotSize > 0) ? lotSize : 0;
  }

//+------------------------------------------------------------------+
//| Get Adaptive SL                                                  |
//+------------------------------------------------------------------+
double GetAdaptiveSL()
  {
   if(!InpUseATR || hATR_MTF == INVALID_HANDLE)
      return (InpAdaptiveSLTP && learningInitialized && learning.totalTrades >= 20)
             ? learning.bestSLPips : InpStopLossPips;

   double atrBuffer[];
   ArraySetAsSeries(atrBuffer, true);
   if(CopyBuffer(hATR_MTF, 0, 0, 1, atrBuffer) < 1)
      return InpStopLossPips;

   double atrPips = atrBuffer[0] / (PointsPerPip() * symInfo.Point());
   double adaptiveSL = atrPips * InpATRSLMultiplier;

   // Clamp to 50%-200% of static input to prevent extremes
   adaptiveSL = MathMax(adaptiveSL, InpStopLossPips * 0.5);
   adaptiveSL = MathMin(adaptiveSL, InpStopLossPips * 2.0);
   return adaptiveSL;
  }

//+------------------------------------------------------------------+
//| Get Adaptive TP                                                  |
//+------------------------------------------------------------------+
double GetAdaptiveTP()
  {
   if(!InpUseATR || hATR_MTF == INVALID_HANDLE)
      return (InpAdaptiveSLTP && learningInitialized && learning.totalTrades >= 20)
             ? learning.bestTPPips : InpTakeProfitPips;

   double atrBuffer[];
   ArraySetAsSeries(atrBuffer, true);
   if(CopyBuffer(hATR_MTF, 0, 0, 1, atrBuffer) < 1)
      return InpTakeProfitPips;

   double atrPips = atrBuffer[0] / (PointsPerPip() * symInfo.Point());
   double adaptiveTP = atrPips * InpATRTPMultiplier;

   adaptiveTP = MathMax(adaptiveTP, InpTakeProfitPips * 0.5);
   adaptiveTP = MathMin(adaptiveTP, InpTakeProfitPips * 2.0);
   return adaptiveTP;
  }

//+------------------------------------------------------------------+
//| Close All Positions                                              |
//+------------------------------------------------------------------+
void CloseAllPositions(string reason)
  {
   TradeEventLogCritical("CLOSE ALL: " + reason);
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      if(!posInfo.SelectByIndex(i))
         continue;
      if(posInfo.Symbol() != tradingSymbol || posInfo.Magic() != InpMagicNumber)
         continue;
      trade.PositionClose(posInfo.Ticket());
     }
   ArrayResize(tradePairs, 0);
   ArrayResize(orphanedLegs, 0);
   ArrayResize(partialCloseTracker, 0);
  }

//+------------------------------------------------------------------+
//| Update Daily PnL                                                 |
//+------------------------------------------------------------------+
void UpdateDailyPnL()
  {
   session.dailyPnL = accInfo.Balance() - session.dailyStartBalance + CalculateBasketProfit();
   if(!session.dailyTargetReached &&
      session.dailyPnL >= initialAccountBalance * InpDailyProfitTarget / 100.0)
     {
      session.dailyTargetReached = true;
      TradeEventLogCritical("PROP: Daily target reached: " + DoubleToString(session.dailyPnL, 2));
     }
  }

//+------------------------------------------------------------------+
//| Is Daily Loss Limit Breached                                     |
//+------------------------------------------------------------------+
bool IsDailyLossLimitBreached()
  {
   if(!InpPropFirmMode)
      return false;
   double limit = initialAccountBalance * InpMaxDailyLossPercent / 100.0;
   double loss = MathAbs(MathMin(session.dailyPnL, 0));
   return (loss >= limit * 0.80);
  }

//+------------------------------------------------------------------+
//| Is Total Drawdown Breached                                       |
//+------------------------------------------------------------------+
bool IsTotalDrawdownBreached()
  {
   if(!InpPropFirmMode)
      return false;
   double limit = initialAccountBalance * InpMaxTotalLossPercent / 100.0;
   double dd = initialAccountBalance - accInfo.Equity();
   return (dd >= limit * 0.80);
  }

//+------------------------------------------------------------------+
//| Is Friday Close Time                                             |
//+------------------------------------------------------------------+
bool IsFridayCloseTime()
  {
   MqlDateTime dt;
   TimeToStruct(TimeCurrent(), dt);
   return (dt.day_of_week == 5 && dt.hour >= InpFridayCloseHour);
  }

//+------------------------------------------------------------------+
//| Points Per Pip                                                   |
//+------------------------------------------------------------------+
double PointsPerPip()
  {
   if(StringFind(tradingSymbol, "XAU") >= 0 || StringFind(tradingSymbol, "GOLD") >= 0)
      return 100;
   if(symInfo.Digits() == 3 || symInfo.Digits() == 5)
      return 10;
   return 1;
  }

//+------------------------------------------------------------------+
//| Get Pip Value                                                    |
//+------------------------------------------------------------------+
double GetPipValue()
  {
   double tickValue = SymbolInfoDouble(tradingSymbol, SYMBOL_TRADE_TICK_VALUE);
   double tickSize = SymbolInfoDouble(tradingSymbol, SYMBOL_TRADE_TICK_SIZE);
   double point = symInfo.Point();
   if(tickSize <= 0 || point <= 0)
      return 0;
   return tickValue * PointsPerPip() * point / tickSize;
  }

//+------------------------------------------------------------------+
//| Smart Log (Replaces Verbose/Trade Logs)                          |
//+------------------------------------------------------------------+
void SmartLog(string message, ENUM_LOG_LEVEL level = LOG_NORMAL)
  {
   if((int)level > (int)InpLogLevel) return;
   
   // Dedup: skip if identical to any recent message
   for(int i = 0; i < 10; i++)
      if(_lastLogMessages[i] == message) return;
   
   _lastLogMessages[_lastLogMsgIndex] = message;
   _lastLogMsgIndex = (_lastLogMsgIndex + 1) % 10;
   
   Print("[", EA_NAME, "] ", message);
  }

void SmartLogThrottled(string message, datetime &lastTime, int intervalSec = 300, ENUM_LOG_LEVEL level = LOG_VERBOSE)
  {
   if((int)level > (int)InpLogLevel) return;
   if(TimeCurrent() - lastTime < intervalSec) return;
   lastTime = TimeCurrent();
   SmartLog(message, level);
  }

// Keep these for backward compatibility during refactor, map to SmartLog
void VerboseLog(string message) { SmartLog(message, LOG_VERBOSE); }
void TradeEventLog(string message) { SmartLog(message, LOG_NORMAL); }
void TradeEventLogCritical(string message) { SmartLog(message, LOG_CRITICAL); }
void VerboseLogThrottled(string message, datetime &lastTime, int intervalSec = 300) { SmartLogThrottled(message, lastTime, intervalSec, LOG_VERBOSE); }

//+------------------------------------------------------------------+
//| Trade Log Throttled - only logs once per interval to save disk    |
//+------------------------------------------------------------------+
void TradeLogThrottled(string message, datetime &lastTime, int intervalSec = 300) { SmartLogThrottled(message, lastTime, intervalSec, LOG_NORMAL); }

//+------------------------------------------------------------------+
//| Phase 5: Dynamic Deviation (ATR-based slippage control)          |
//+------------------------------------------------------------------+
void SetDynamicDeviation()
  {
   double atrBuffer[];
   ArraySetAsSeries(atrBuffer, true);
   double atrPoints = 200;  // fallback
   
   if(hATR_MTF != INVALID_HANDLE)
      if(CopyBuffer(hATR_MTF, 0, 0, 1, atrBuffer) > 0)
         atrPoints = atrBuffer[0] / symInfo.Point();
   
   int deviation = (int)MathMax(30.0, MathMin(100.0, atrPoints * 0.1));
   trade.SetDeviationInPoints(deviation);
  }

//+------------------------------------------------------------------+
//| Phase 3: Momentum Exhaustion Detection                           |
//+------------------------------------------------------------------+
void UpdateMomentumExhaustion()
  {
   if(!InpEnableMomentumExhaustion)
     {
      momentumExhaustionScore = 0;
      mesBlockBuy = false;
      mesBlockSell = false;
      return;
     }

   double score = 0.0;
   bool bearishExhaustion = false;
   bool bullishExhaustion = false;

   //=== COMPONENT 1: ADX Slope Decay ===
   double adxMain[], adxPlus[], adxMinus[];
   ArraySetAsSeries(adxMain, true);
   ArraySetAsSeries(adxPlus, true);
   ArraySetAsSeries(adxMinus, true);

   if(CopyBuffer(hADX_MTF, 0, 0, 4, adxMain) >= 4 &&
      CopyBuffer(hADX_MTF, 1, 0, 4, adxPlus) >= 4 &&
      CopyBuffer(hADX_MTF, 2, 0, 4, adxMinus) >= 4)
     {
      bool wasRising  = adxMain[1] > adxMain[2];
      bool nowFalling = adxMain[0] < adxMain[1];

      if(wasRising && nowFalling && adxMain[0] > 20)
        {
         double decayMagnitude = (adxMain[1] - adxMain[0]) / adxMain[1];
         score += MathMin(decayMagnitude * 4.0, 1.0) * 0.40;
         if(adxPlus[0] > adxMinus[0]) bearishExhaustion = true;
         else bullishExhaustion = true;
        }

      double diGap = MathAbs(adxPlus[0] - adxMinus[0]);
      double prevDiGap = MathAbs(adxPlus[1] - adxMinus[1]);
      if(prevDiGap > 0 && diGap < prevDiGap * 0.6)
        {
         score += 0.15;
         if(adxPlus[0] > adxMinus[0]) bearishExhaustion = true;
         else bullishExhaustion = true;
        }
     }

   //=== COMPONENT 2: RSI Divergence ===
   double rsi[], close[];
   ArraySetAsSeries(rsi, true);
   ArraySetAsSeries(close, true);

   int divBars = InpRSIDivergenceBars;
   if(hRSI_MTF != INVALID_HANDLE &&
      CopyBuffer(hRSI_MTF, 0, 0, divBars + 1, rsi) >= divBars + 1 &&
      CopyClose(tradingSymbol, InpMTFPeriod, 0, divBars + 1, close) >= divBars + 1)
     {
      double highestPrice = close[0], rsiAtHighest = rsi[0];
      double lowestPrice = close[0], rsiAtLowest = rsi[0];
      int highBar = 0, lowBar = 0;

      for(int i = 1; i <= divBars; i++)
        {
         if(close[i] > highestPrice) { highestPrice = close[i]; rsiAtHighest = rsi[i]; highBar = i; }
         if(close[i] < lowestPrice)  { lowestPrice = close[i]; rsiAtLowest = rsi[i]; lowBar = i;  }
        }

      if(highBar > 2 && close[0] >= highestPrice * 0.999)
        {
         if(rsi[0] < rsiAtHighest - 3.0)
           {
            double rsiDrop = (rsiAtHighest - rsi[0]) / rsiAtHighest;
            score += MathMin(rsiDrop * 3.0, 1.0) * 0.35;
            bearishExhaustion = true;
           }
        }

      if(lowBar > 2 && close[0] <= lowestPrice * 1.001)
        {
         if(rsi[0] > rsiAtLowest + 3.0)
           {
            double rsiRise = (rsi[0] - rsiAtLowest) / (100.0 - rsiAtLowest);
            score += MathMin(rsiRise * 3.0, 1.0) * 0.35;
            bullishExhaustion = true;
           }
        }
     }

   //=== COMPONENT 3: EMA5 Slope Deceleration ===
   double ema5[];
   ArraySetAsSeries(ema5, true);

   if(CopyBuffer(hEMA5, 0, 0, 4, ema5) >= 4)
     {
      double pipsPerPt = PointsPerPip() * symInfo.Point();
      double slope_prev = (ema5[1] - ema5[2]) / pipsPerPt;
      double slope_curr = (ema5[0] - ema5[1]) / pipsPerPt;

      if(slope_prev > 1.0 && slope_curr < slope_prev * 0.5)
        {
         double decel = 1.0 - (slope_curr / slope_prev);
         score += MathMin(decel, 1.0) * 0.25;
         bearishExhaustion = true;
        }
      else if(slope_prev < -1.0 && slope_curr > slope_prev * 0.5)
        {
         double decel = 1.0 - (slope_curr / slope_prev);
         score += MathMin(decel, 1.0) * 0.25;
         bullishExhaustion = true;
        }
     }

   momentumExhaustionScore = MathMax(0.0, MathMin(1.0, score));
   mesBlockBuy  = false;
   mesBlockSell = false;

   if(momentumExhaustionScore >= InpMESFullBlockThreshold)
     {
      mesBlockBuy  = true;
      mesBlockSell = true;
     }
   else if(momentumExhaustionScore >= InpMESBlockThreshold)
     {
      if(bearishExhaustion) mesBlockBuy  = true;
      if(bullishExhaustion) mesBlockSell = true;
     }
  }

//+------------------------------------------------------------------+
//| Get Session Win Rate                                             |
//+------------------------------------------------------------------+
double GetSessionWinRate()
  {
   if(session.sessionTrades == 0)
      return 0;
   return (double)session.sessionWins / session.sessionTrades * 100.0;
  }

//+------------------------------------------------------------------+
//| Get Session Action String                                        |
//+------------------------------------------------------------------+
string GetSessionActionString(ENUM_SESSION_ACTION action)
  {
   switch(action)
     {
      case SESSION_NORMAL:
         return "Normal";
      case SESSION_CONSERVATIVE:
         return "Conservative";
      case SESSION_AGGRESSIVE:
         return "Aggressive";
      case SESSION_PAUSED:
         return "PAUSED";
      default:
         return "Unknown";
     }
  }

//+------------------------------------------------------------------+
//| Update Chart Comment                                             |
//+------------------------------------------------------------------+
void UpdateChartComment()
  {
   string nl = "\n";
   string sep = "────────────────────────────" + nl;
   string c = "";

   c += "  LILY EA v" + EA_VERSION + nl;
   c += sep;
   c += "Symbol: " + tradingSymbol + " | " + GetRegimeString(currentRegime) + nl;
   c += "Session: " + GetSessionName(GetCurrentSession()) + nl;
   c += "Volatility: " + DoubleToString(currentVolatilityPts, 0) + " pts" + nl;
   c += "Spread: " + DoubleToString((double)symInfo.Spread(), 0) +
        " (avg " + DoubleToString(averageSpread, 0) + ")" + nl;
   c += "Signal: " + DoubleToString(signalScore, 3) +
        " [" + SignalStrengthToString(signalStrength) + "]" + nl;
   c += sep;

   c += "Pairs: " + IntegerToString(GetCompletePairsCount()) + " complete, " +
        IntegerToString(GetIncompletePairsCount()) + " incomplete" + nl;
   c += "Orphans: " + IntegerToString(ArraySize(orphanedLegs)) + nl;
   c += sep;

   c += "Equity: " + DoubleToString(accInfo.Equity(), 2) +
        " | Peak: " + DoubleToString(peakEquity, 2) + nl;
   c += "Basket: " + DoubleToString(CalculateBasketProfit(), 2) + nl;
   c += "Margin: " + DoubleToString(accInfo.FreeMargin(), 2) + nl;
   c += sep;

   if(InpPropFirmMode)
     {
      double dailyLimit = initialAccountBalance * InpMaxDailyLossPercent / 100.0;
      double totalLimit = initialAccountBalance * InpMaxTotalLossPercent / 100.0;
      c += "PROP | Daily: " + DoubleToString(session.dailyPnL, 2) +
           " / -" + DoubleToString(dailyLimit, 2) + nl;
      c += "PROP | Total DD: " + DoubleToString(initialAccountBalance - accInfo.Equity(), 2) +
           " / -" + DoubleToString(totalLimit, 2) + nl;
      c += "Target: " + (session.dailyTargetReached ? "REACHED" : "Pending") + nl;
      c += sep;
     }

   c += "Session: W" + IntegerToString(session.sessionWins) +
        "/L" + IntegerToString(session.sessionLosses) +
        " (" + DoubleToString(GetSessionWinRate(), 1) + "%)" + nl;
   c += "Mode: " + GetSessionActionString(session.currentAction) + nl;
   c += sep;

//--- Signal Components Breakdown
   c += "SIGNAL COMPONENTS:" + nl;
   c += "  EMA: " + DoubleToString(GetEMASignal(), 3) +
        " | Regime: " + DoubleToString(GetRegimeSignal(), 3) +
        " | RSI: " + DoubleToString(GetRSISignal(), 3) + nl;
   c += "  Session: " + DoubleToString(GetSessionQualitySignal(), 3);
   if(InpEnableDXYCorrelation && dxyAvailable)
      c += " | DXY: " + DoubleToString(GetDXYSignal(), 3);
   else
      c += " | DXY: N/A";
   c += nl;
   c += sep;

//--- Filter Status Dashboard
   c += "FILTER STATUS:" + nl;
   c += "  Session: " + (IsSessionAllowed() ? "PASS" : "FAIL") +
        " (" + GetSessionName(GetCurrentSession()) + ")" +
        " | News: " + (newsFilterActive ? "BLOCK" : "CLEAR") +
        " | Spread: " + (IsSpreadAcceptable() ? "PASS" : "FAIL") + nl;
   c += "  MC: " + (InpEnableMonteCarlo ? (mcResult.isViable ? "VIABLE" : "FAIL") : "OFF") +
        " | Margin: " + (accInfo.FreeMargin() >= InpMinFreeMargin ? "PASS" : "FAIL") + nl;
   c += sep;

   if(InpEnableLearning && learningInitialized)
     {
      c += "ML: " + IntegerToString(learning.totalTrades) + " trades | " +
           "WR=" + DoubleToString(learning.winRatio * 100, 1) + "% | " +
           "PF=" + DoubleToString(learning.profitFactor, 2) + nl;
      c += "Patterns: " + IntegerToString(patternDBSize) + nl;
     }

   if(InpEnableMonteCarlo && mcResult.avgReturn != 0)
     {
      c += "MC: " + (mcResult.isViable ? "VIABLE" : "NOT VIABLE") +
           " | DD@95%=" + DoubleToString(mcResult.drawdownAtConfidence, 1) + "%" + nl;
     }

   c += sep;

//--- Phase 3: Momentum Exhaustion Score display
   if(InpEnableMomentumExhaustion)
     {
      c += "MES: " + DoubleToString(momentumExhaustionScore * 100, 0) + "%" +
           (mesBlockBuy ? " [BUY BLOCKED]" : "") +
           (mesBlockSell ? " [SELL BLOCKED]" : "") + nl;
     }

   if(lastTradeBlockReason != "")
      c += "TRADE: BLOCKED - " + lastTradeBlockReason + nl;
   else
      c += "TRADE: READY (waiting for signal >= MODERATE)" + nl;

   if(inSLTradeDelay)
     {
      int rem = (int)(InpSLDelayMinutes * 60 - (TimeCurrent() - lastSLHitTime));
      c += "SL Delay: " + IntegerToString(rem / 60) + "m " + IntegerToString(rem % 60) + "s" + nl;
     }

   if(InpEnableVolumeProfile && pocPrice > 0)
     {
      c += "VP: POC=" + DoubleToString(pocPrice, (int)symInfo.Digits()) +
           " VAH=" + DoubleToString(vahPrice, (int)symInfo.Digits()) +
           " VAL=" + DoubleToString(valPrice, (int)symInfo.Digits()) + nl;
     }

   if(InpEnableBreakout)
     {
      if(breakout.breakoutDetected)
         c += "BREAKOUT: " + (breakout.breakoutBullish ? "BULL" : "BEAR") +
              " x" + DoubleToString(breakout.breakoutStrength, 1) + nl;
      else
         if(breakout.isConsolidating)
            c += "Consolidating..." + nl;
     }

   Comment(c);
  }