Advertisement IC Markets

Most complicated forex scalping strategy

Share, develop, and backtest custom MQL4/MQL5 Expert Advisors, Python data-scraping scripts, trading bots, and automated market alert systems.
PTScalper
Site Admin
Posts: 7647
Joined: Mon Jul 20, 2026 1:28 pm

Re: Most complicated forex scalping strategy

Post by PTScalper »

Baseline Capital Protection (State 0):

Base risk per trade ($R_0$) is strictly capped at $0.25\%$ of core account equity. If 3 consecutive losses occur ($-0.75\%$ daily drawdown), a mandatory 45-minute cooldown triggers; if daily drawdown hits $-1.25\%$, trading halts for the session.

Profit-Buffer Escalation (States 1 to 3):

Whenever a scalp closes in profit, 50% of the net realized gain is permanently locked into the daily equity floor, while the remaining 50% is allocated to an Active Scaling Buffer ($B_{\text{scale}}$).
For the next A+ setup within the same liquidity session, total risk is dynamically calculated solely from the baseline plus the active buffer.

This creates convex equity growth during clean institutional sessions (allowing position sizes to scale $2\times$ to $4\times$ solely using accumulated market gains) while mathematically guaranteeing that a reversal on the final trade of a winning streak can never put the session into a net loss.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Recommended broker for automated trading & scalping IC Markets
PTScalper
Site Admin
Posts: 7647
Joined: Mon Jul 20, 2026 1:28 pm

Re: Most complicated forex scalping strategy

Post by PTScalper »

Below is the complete, production-ready MQL5 Expert Advisor (QLM7_Institutional_Scalper.mq5) implementing all five layers of the Quantum-Liquidity Matrix (QLM-7) architecture.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7647
Joined: Mon Jul 20, 2026 1:28 pm

Re: Most complicated forex scalping strategy

Post by PTScalper »

Architectural Highlights of the MQL5 Implementation

Object-Oriented Execution & State Tracking: Built on the MQL5 Standard Library (CTrade, CPositionInfo, CSymbolInfo) with asynchronous-ready filling mode detection (ORDER_FILLING_FOK / ORDER_FILLING_IOC) and strict broker lot-step normalization (SYMBOL_VOLUME_STEP).

Layer 1 (Rolling Hurst & Intraday VWAP Engine): Calculates the rescaled range ($R/S$) Hurst Exponent over M15 log returns to switch dynamically between Mean-Reversion Fade Mode ($\text{Hurst} \lt 0.45$), Random Walk Stand-Down ($0.45 \le \text{Hurst} \le 0.55$), and Momentum Continuation Mode ($\text{Hurst} \gt 0.55$).

Layer 2 (Triangular Basis Z-Score with Auto-Fallback): Computes real-time synthetic log-spread dislocations across EURUSD, EURGBP, and GBPUSD using a rolling circular buffer. When deployed on single-asset symbols like XAUUSD or in single-pair tests where cross pairs are disabled, it automatically falls back to an internal tick-velocity Z-score.

Layer 3 & 4 (M15 FVG + M5 Liquidity Sweep + Sub-M1 CVD & L2 Book Imbalance): Combines CopyTicks (with a Lee-Ready tick-direction fallback when broker tick flags are absent) and MarketBookGet Level-2 DOM depth weighting. If your ECN feed or Strategy Tester does not broadcast Level-2 depth, the EA seamlessly synthesizes book pressure from high-frequency tick delta velocity.

Layer 5 (House-Money Compounding, 3-Tranche Exits & Kalman Trail): Tracks closed net PnL (including ECN commissions and swaps) inside OnTradeTransaction(). Every profitable scalp locks 50% of net profit into the daily equity floor and channels 50% into the Active Scaling Buffer, while managing open trades with a 180-second velocity time-stop, two partial scale-outs (50% at $1.2R$ and 30% at $2.2R$/VWAP), and a 1D Kalman Filter trailing stop on the final 20% runner.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7647
Joined: Mon Jul 20, 2026 1:28 pm

Re: Most complicated forex scalping strategy

Post by PTScalper »

Complete MQL5 Source Code (QLM7_Institutional_Scalper.mq5)

Code: Select all

//+------------------------------------------------------------------+
//|                                  QLM7_Institutional_Scalper.mq5  |
//|         Quantum-Liquidity Matrix (QLM-7) Pro ECN Scalping Engine |
//|     5-Layer Quantitative, Order-Flow & House-Money Scaling EA    |
//+------------------------------------------------------------------+
#property copyright "QLM-7 Quantitative Architecture"
#property version   "7.00"
#property strict

#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\AccountInfo.mqh>

//+------------------------------------------------------------------+
//| Enumerations & Input Parameters                                  |
//+------------------------------------------------------------------+
enum ENUM_REGIME_STATE
  {
   REGIME_RANDOM_WALK = 0, // Neutral / Veto Zone (0.45 <= H <= 0.55)
   REGIME_MEAN_REV    = 1, // Anti-Persistent Fade Mode (H < 0.45)
   REGIME_MOMENTUM    = 2  // Persistent Trend Mode (H > 0.55)
  };

input group "=== [1] General & ECN Execution Settings ==="
input ulong    InpMagicNumber          = 7007007;      // EA Magic Number
input double   InpCommissionPerLotRT   = 7.0;          // ECN Round-Turn Commission ($ per 1.0 Lot)
input double   InpMaxCVRRatio          = 0.18;         // Max Cost-to-Volatility Ratio (Spread+Comm / M1 ATR)
input uint     InpMaxSlippagePoints    = 4;            // Max Permitted Slippage (Points)
input int      InpLondonStartHourUTC   = 7;            // Session Start Hour (UTC)
input int      InpNYCloseHourUTC       = 17;           // Session End Hour (UTC)

input group "=== [2] Layer 1: Regime & VWAP Parameters (M15) ==="
input int      InpHurstPeriod          = 100;          // Rolling M15 Hurst Lookback Bars
input double   InpHurstMeanRevMax      = 0.45;         // Hurst Upper Bound for Mean-Reversion
input double   InpHurstMomentumMin     = 0.55;         // Hurst Lower Bound for Momentum
input double   InpVWAPBandSigma        = 2.0;          // Session VWAP Band Multiplier (Sigma)

input group "=== [3] Layer 2: Cross-Asset Triangular Basis ==="
input bool     InpUseTriangularFilter  = true;         // Enable Synthetic Cross-Pair Basis Filter
input string   InpCrossPairA           = "EURGBP";     // Synthetic Leg A (Include broker suffix if any)
input string   InpCrossPairB           = "GBPUSD";     // Synthetic Leg B (Include broker suffix if any)
input double   InpMinBasisZScore       = 1.50;         // Required Synthetic Dislocation Z-Score

input group "=== [4] Layer 3 & 4: Microstructure & Order Flow ==="
input int      InpSwingLookbackM5      = 12;           // M5 Liquidity Swing Lookback (Bars)
input double   InpMinSweepPips         = 1.5;          // Min Stop-Sweep Penetration (Standard Pips)
input double   InpMaxSweepPips         = 6.0;          // Max Stop-Sweep Penetration (Standard Pips)
input double   InpDisplacementATRMult  = 1.35;         // M1 Rejection Displacement Body / ATR
input int      InpTickDeltaLookback    = 400;          // Tick Count for CVD Absorption Analysis
input double   InpMinBookImbalance     = 0.35;         // Required L2 DOM / Synthetic Imbalance Ratio

input group "=== [5] Layer 5: House-Money Risk & 3-Tranche Exits ==="
input double   InpBaseRiskPercent      = 0.25;         // Base Risk Per Scalp (% of Core Equity)
input double   InpProfitLockRatio      = 0.50;         // Fraction of Net Win Locked (0.50 = 50% Lock / 50% Scale)
input double   InpBufferScalingFactor  = 0.75;         // Gamma: Portion of Active Buffer Deployed on Next Trade
input double   InpMaxRiskCapPercent    = 1.50;         // Hard Cap on Scaled Risk (% of Equity)
input double   InpDailyMaxDrawdownPct  = 1.25;         // Hard Daily Stop Loss (% of Starting Day Equity)
input int      InpCooldownMinutes      = 45;           // Cooldown Duration After 3 Consecutive Losses (Min)
input int      InpVelocityTimeStopSec  = 180;          // Velocity Time-Stop (Seconds to reach +0.5R)
input double   InpTranche1RR           = 1.20;         // Tranche 1 Target (R-Multiple, closes 50%)
input double   InpTranche2RR           = 2.20;         // Tranche 2 Target (R-Multiple, closes 30%)
input double   InpKalmanProcessNoise   = 0.0005;       // Kalman Filter Process Variance (Q) for 20% Runner
input double   InpKalmanMeasureNoise   = 0.0050;       // Kalman Filter Measurement Variance (R)

//+------------------------------------------------------------------+
//| Internal Position State Tracking Structure                       |
//+------------------------------------------------------------------+
struct PositionState
  {
   ulong             ticket;
   double            initialVolume;
   double            initialRiskPrice;
   double            entryPrice;
   bool              tranche1Closed;
   bool              tranche2Closed;
   double            kalmanEstimate;
   double            kalmanErrorCov;
  };

//+------------------------------------------------------------------+
//| Global Variables & Objects                                       |
//+------------------------------------------------------------------+
CTrade         m_trade;
CPositionInfo  m_position;
CSymbolInfo    m_symbol;
CAccountInfo   m_account;

int            g_atrHandleM1           = INVALID_HANDLE;
bool           g_domSubscribed         = false;
bool           g_crossPairsValid       = false;

// House-Money & Session State
int            g_currentDayOfYear      = -1;
double         g_dayStartEquity        = 0.0;
double         g_lockedProfitFloor     = 0.0;
double         g_activeScalingBuffer   = 0.0;
int            g_consecutiveLosses     = 0;
datetime       g_cooldownUntil         = 0;

// Triangular Basis Circular Buffer
#define        BASIS_BUFFER_SIZE 60
double         g_basisBuffer[BASIS_BUFFER_SIZE];
int            g_basisIndex            = 0;
int            g_basisCount            = 0;

// Cached Regime & Session VWAP Metrics
datetime       g_lastM15BarTime        = 0;
datetime       g_lastM1BarTime         = 0;
double         g_currentHurst          = 0.50;
ENUM_REGIME_STATE g_currentRegime      = REGIME_RANDOM_WALK;
double         g_sessionVWAP           = 0.0;
double         g_sessionVAH            = 0.0;
double         g_sessionVAL            = 0.0;

// Active Position Tracker
PositionState  g_posStates[];

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   if(!m_symbol.Name(_Symbol))
      return(INIT_FAILED);
   m_symbol.RefreshRates();

   // Configure CTrade execution parameters
   m_trade.SetExpertMagicNumber(InpMagicNumber);
   m_trade.SetDeviationInPoints(InpMaxSlippagePoints);
   m_trade.SetTypeFillingBySymbol(_Symbol);
   m_trade.SetAsyncMode(false);

   // Initialize M1 ATR(14) indicator handle
   g_atrHandleM1 = iATR(_Symbol, PERIOD_M1, 14);
   if(g_atrHandleM1 == INVALID_HANDLE)
     {
      Print("CRITICAL: Failed to create M1 ATR(14) handle.");
      return(INIT_FAILED);
     }

   // Subscribe to Level-2 Depth of Market if supported by broker
   if(MarketBookAdd(_Symbol))
      g_domSubscribed = true;
   else
      Print("INFO: L2 DOM unavailable for ", _Symbol, ". Using Synthetic Tick-Velocity Imbalance fallback.");

   // Verify Synthetic Cross-Pairs if enabled
   if(InpUseTriangularFilter)
     {
      if(SymbolSelect(InpCrossPairA, true) && SymbolSelect(InpCrossPairB, true))
         g_crossPairsValid = true;
      else
         Print("INFO: Cross pairs (", InpCrossPairA, "/", InpCrossPairB, ") unavailable. Using internal velocity Z-Score.");
     }

   ArrayInitialize(g_basisBuffer, 0.0);
   ResetDailyHouseMoneyState();

   Print("QLM-7 Institutional Scalper initialized successfully on ", _Symbol);
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(g_atrHandleM1 != INVALID_HANDLE)
      IndicatorRelease(g_atrHandleM1);
   if(g_domSubscribed)
      MarketBookRelease(_Symbol);
  }

//+------------------------------------------------------------------+
//| Reset Daily House-Money State on UTC Day Rollover                |
//+------------------------------------------------------------------+
void ResetDailyHouseMoneyState()
  {
   MqlDateTime dt;
   TimeGMT(dt);
   g_currentDayOfYear    = dt.day_of_year;
   g_dayStartEquity      = m_account.Equity();
   g_lockedProfitFloor   = 0.0;
   g_activeScalingBuffer = 0.0;
   g_consecutiveLosses   = 0;
   g_cooldownUntil       = 0;
  }

//+------------------------------------------------------------------+
//| Helper: Standard Pip Size Calculation (FX vs Metals)             |
//+------------------------------------------------------------------+
double GetPipSize()
  {
   int digits = m_symbol.Digits();
   if(digits == 5 || digits == 3)
      return(m_symbol.Point() * 10.0);
   if(digits == 2) // e.g., XAUUSD (0.01 point -> 0.10 pip)
      return(m_symbol.Point() * 10.0);
   return(m_symbol.Point());
  }

//+------------------------------------------------------------------+
//| Layer 1A: Rolling Rescaled Range (R/S) Hurst Exponent on M15     |
//+------------------------------------------------------------------+
double CalculateHurstExponent(ENUM_TIMEFRAMES tf, int period)
  {
   double close[];
   ArraySetAsSeries(close, true);
   if(CopyClose(_Symbol, tf, 0, period + 1, close) < period + 1)
      return(0.50);

   double logReturns[];
   ArrayResize(logReturns, period);
   double mean = 0.0;

   for(int i = 0; i < period; i++)
     {
      if(close[i + 1] <= 0.0)
         return(0.50);
      logReturns[i] = MathLog(close[i] / close[i + 1]);
      mean += logReturns[i];
     }
   mean /= (double)period;

   double cumDev = 0.0;
   double maxDev = -DBL_MAX;
   double minDev =  DBL_MAX;
   double sumSq  = 0.0;

   for(int i = 0; i < period; i++)
     {
      double dev = logReturns[i] - mean;
      cumDev += dev;
      if(cumDev > maxDev) maxDev = cumDev;
      if(cumDev < minDev) minDev = cumDev;
      sumSq += dev * dev;
     }

   double range = maxDev - minDev;
   double stdev = MathSqrt(sumSq / (double)period);
   if(stdev <= 1e-12 || range <= 1e-12)
      return(0.50);

   double rs = range / stdev;
   double hurst = MathLog(rs) / MathLog((double)period);
   return(MathMax(0.05, MathMin(0.95, hurst)));
  }

//+------------------------------------------------------------------+
//| Layer 1B: Intraday Session VWAP & +/- Sigma Bands                |
//+------------------------------------------------------------------+
void UpdateSessionVWAPAndBands()
  {
   datetime dayStart = iTime(_Symbol, PERIOD_D1, 0);
   MqlRates rates[];
   ArraySetAsSeries(rates, true);

   int copied = CopyRates(_Symbol, PERIOD_M5, dayStart, TimeCurrent(), rates);
   if(copied <= 2)
      return;

   double sumPV = 0.0;
   double sumV  = 0.0;

   for(int i = 0; i < copied; i++)
     {
      double typical = (rates[i].high + rates[i].low + rates[i].close) / 3.0;
      double vol     = (double)MathMax((long)1, rates[i].tick_volume);
      sumPV += typical * vol;
      sumV  += vol;
     }

   if(sumV <= 0.0)
      return;

   g_sessionVWAP = sumPV / sumV;

   // Compute Volume-Weighted Variance around Session VWAP
   double sumVar = 0.0;
   for(int i = 0; i < copied; i++)
     {
      double typical = (rates[i].high + rates[i].low + rates[i].close) / 3.0;
      double vol     = (double)MathMax((long)1, rates[i].tick_volume);
      double diff    = typical - g_sessionVWAP;
      sumVar += vol * diff * diff;
     }

   double sigma = MathSqrt(sumVar / sumV);
   g_sessionVAH = g_sessionVWAP + InpVWAPBandSigma * sigma;
   g_sessionVAL = g_sessionVWAP - InpVWAPBandSigma * sigma;
  }

//+------------------------------------------------------------------+
//| Layer 2: Update & Compute Triangular Synthetic Basis Z-Score     |
//+------------------------------------------------------------------+
double GetTriangularBasisZScore()
  {
   m_symbol.RefreshRates();
   double spotMid = (m_symbol.Bid() + m_symbol.Ask()) * 0.5;
   if(spotMid <= 0.0)
      return(0.0);

   double basisVal = 0.0;

   if(InpUseTriangularFilter && g_crossPairsValid)
     {
      double bidA = SymbolInfoDouble(InpCrossPairA, SYMBOL_BID);
      double askA = SymbolInfoDouble(InpCrossPairA, SYMBOL_ASK);
      double bidB = SymbolInfoDouble(InpCrossPairB, SYMBOL_BID);
      double askB = SymbolInfoDouble(InpCrossPairB, SYMBOL_ASK);

      double midA = (bidA + askA) * 0.5;
      double midB = (bidB + askB) * 0.5;

      if(midA > 0.0 && midB > 0.0)
         basisVal = MathLog(spotMid) - MathLog(midA * midB);
     }
   else
     {
      // Fallback: Micro-dislocation vs 20-tick moving midpoint
      MqlTick ticks[];
      if(CopyTicks(_Symbol, ticks, COPY_TICKS_INFO, 0, 20) == 20)
        {
         double sumMid = 0.0;
         for(int i = 0; i < 20; i++)
            sumMid += (ticks[i].bid + ticks[i].ask) * 0.5;
         double avgMid = sumMid / 20.0;
         if(avgMid > 0.0)
            basisVal = MathLog(spotMid) - MathLog(avgMid);
        }
     }

   // Push into Circular Buffer
   g_basisBuffer[g_basisIndex] = basisVal;
   g_basisIndex = (g_basisIndex + 1) % BASIS_BUFFER_SIZE;
   if(g_basisCount < BASIS_BUFFER_SIZE)
      g_basisCount++;

   if(g_basisCount < 20)
      return(0.0);

   double sum = 0.0;
   for(int i = 0; i < g_basisCount; i++)
      sum += g_basisBuffer[i];
   double mean = sum / (double)g_basisCount;

   double sqSum = 0.0;
   for(int i = 0; i < g_basisCount; i++)
     {
      double d = g_basisBuffer[i] - mean;
      sqSum += d * d;
     }

   double stdev = MathSqrt(sqSum / (double)g_basisCount);
   if(stdev <= 1e-12)
      return(0.0);

   return((basisVal - mean) / stdev);
  }

//+------------------------------------------------------------------+
//| Layer 3A: Detect Unmitigated M15 Fair Value Gap (FVG) Proximity  |
//+------------------------------------------------------------------+
bool CheckM15FVGConfluence(int direction)
  {
   MqlRates m15[];
   ArraySetAsSeries(m15, true);
   if(CopyRates(_Symbol, PERIOD_M15, 0, 12, m15) < 12)
      return(false);

   double currentPrice = (direction > 0) ? m_symbol.Bid() : m_symbol.Ask();
   double tolerance    = GetPipSize() * 3.0;

   // Scan recent M15 3-bar structures for an active imbalance zone
   for(int i = 1; i <= 8; i++)
     {
      // Bullish FVG: Low of Bar[i] > High of Bar[i+2]
      if(direction > 0 && m15[i].low > m15[i + 2].high)
        {
         double fvgTop    = m15[i].low + tolerance;
         double fvgBottom = m15[i + 2].high - tolerance;
         if(currentPrice <= fvgTop && currentPrice >= fvgBottom)
            return(true);
        }
      // Bearish FVG: High of Bar[i] < Low of Bar[i+2]
      if(direction < 0 && m15[i].high < m15[i + 2].low)
        {
         double fvgTop    = m15[i + 2].low + tolerance;
         double fvgBottom = m15[i].high - tolerance;
         if(currentPrice <= fvgTop && currentPrice >= fvgBottom)
            return(true);
        }
     }
   return(false);
  }

//+------------------------------------------------------------------+
//| Layer 3B: Detect M5 Liquidity Stop-Sweep + M1 Displacement       |
//+------------------------------------------------------------------+
bool DetectLiquiditySweepAndDisplacement(int &outSignalDir, double &outSweepExtreme)
  {
   outSignalDir    = 0;
   outSweepExtreme = 0.0;

   MqlRates m5[], m1[];
   ArraySetAsSeries(m5, true);
   ArraySetAsSeries(m1, true);

   if(CopyRates(_Symbol, PERIOD_M5, 1, InpSwingLookbackM5 + 2, m5) < InpSwingLookbackM5 + 2)
      return(false);
   if(CopyRates(_Symbol, PERIOD_M1, 1, 4, m1) < 4)
      return(false);

   double atrBuf[];
   ArraySetAsSeries(atrBuf, true);
   if(CopyBuffer(g_atrHandleM1, 0, 1, 1, atrBuf) < 1 || atrBuf[0] <= 0.0)
      return(false);
   double m1ATR = atrBuf[0];

   // Find prior M5 Liquidity Pool High & Low (excluding current forming bar)
   double swingHigh = -DBL_MAX;
   double swingLow  =  DBL_MAX;
   for(int i = 1; i <= InpSwingLookbackM5; i++)
     {
      if(m5[i].high > swingHigh) swingHigh = m5[i].high;
      if(m5[i].low  < swingLow)  swingLow  = m5[i].low;
     }

   double pipSize      = GetPipSize();
   double minSweepDist = InpMinSweepPips * pipSize;
   double maxSweepDist = InpMaxSweepPips * pipSize;

   // Check recent 2 completed M1 candles for sweep and immediate displacement close
   double recentLowest  = MathMin(m1[0].low,  m1[1].low);
   double recentHighest = MathMax(m1[0].high, m1[1].high);
   double lastBody      = MathAbs(m1[0].close - m1[0].open);

   // 1. Bullish Stop-Run below M5 Swing Low + Rejection Close Inside + Displacement Body
   double bullSweepDepth = swingLow - recentLowest;
   if(bullSweepDepth >= minSweepDist && bullSweepDepth <= maxSweepDist)
     {
      if(m1[0].close > swingLow && m1[0].close > m1[0].open && lastBody >= InpDisplacementATRMult * m1ATR)
        {
         outSignalDir    = 1;
         outSweepExtreme = recentLowest;
         return(true);
        }
     }

   // 2. Bearish Stop-Run above M5 Swing High + Rejection Close Inside + Displacement Body
   double bearSweepDepth = recentHighest - swingHigh;
   if(bearSweepDepth >= minSweepDist && bearSweepDepth <= maxSweepDist)
     {
      if(m1[0].close < swingHigh && m1[0].close < m1[0].open && lastBody >= InpDisplacementATRMult * m1ATR)
        {
         outSignalDir    = -1;
         outSweepExtreme = recentHighest;
         return(true);
        }
     }

   return(false);
  }

//+------------------------------------------------------------------+
//| Layer 4: Tick CVD Absorption & L2 Order Book Imbalance ($BIR$)   |
//+------------------------------------------------------------------+
bool VerifyOrderFlowTrigger(int direction)
  {
   //--- Part A: Tick Cumulative Volume Delta (CVD) Exhaustion Absorption
   MqlTick ticks[];
   int copied = CopyTicks(_Symbol, ticks, COPY_TICKS_ALL, 0, InpTickDeltaLookback);
   if(copied < 100)
      return(false);

   int half = copied / 2;
   double cvdFirstHalf  = 0.0;
   double cvdSecondHalf = 0.0;
   double synthBuyVol   = 0.0;
   double synthSellVol  = 0.0;

   for(int i = 1; i < copied; i++)
     {
      double vol = (ticks[i].volume > 0) ? (double)ticks[i].volume : 1.0;
      double signedVol = 0.0;

      bool isBuy  = ((ticks[i].flags & TICK_FLAG_BUY)  != 0);
      bool isSell = ((ticks[i].flags & TICK_FLAG_SELL) != 0);

      if(isBuy && !isSell)
         signedVol = vol;
      else if(isSell && !isBuy)
         signedVol = -vol;
      else
        {
         // Lee-Ready Tick Rule fallback when ECN feed omits buy/sell flags
         double midCurr = (ticks[i].bid + ticks[i].ask) * 0.5;
         double midPrev = (ticks[i - 1].bid + ticks[i - 1].ask) * 0.5;
         if(midCurr > midPrev)      signedVol = vol;
         else if(midCurr < midPrev) signedVol = -vol;
        }

      if(i < half) cvdFirstHalf  += signedVol;
      else         cvdSecondHalf += signedVol;

      // Accumulate recent 50 ticks for synthetic book imbalance if L2 DOM is absent
      if(i >= copied - 50)
        {
         if(signedVol > 0) synthBuyVol  += vol;
         if(signedVol < 0) synthSellVol += vol;
        }
     }

   // Absorption check: Second half of the sweep must show positive delta shift for Longs (or negative for Shorts)
   if(direction > 0 && cvdSecondHalf <= cvdFirstHalf)
      return(false);
   if(direction < 0 && cvdSecondHalf >= cvdFirstHalf)
      return(false);

   //--- Part B: Level-2 Order Book Imbalance Ratio (BIR)
   double bir = 0.0;
   MqlBookInfo book[];

   if(g_domSubscribed && MarketBookGet(_Symbol, book) && ArraySize(book) >= 2)
     {
      double weightedBid = 0.0;
      double weightedAsk = 0.0;
      int bidRank = 0;
      int askRank = 0;

      int totalEntries = ArraySize(book);
      // Scan inside-out: Bids from highest to lowest, Asks from lowest to highest
      for(int i = 0; i < totalEntries; i++)
        {
         if(book[i].type == BOOK_TYPE_BUY || book[i].type == BOOK_TYPE_BUY_MARKET)
           {
            if(bidRank < 5)
              {
               double w = MathExp(-0.35 * bidRank);
               weightedBid += w * (double)book[i].volume_real;
               bidRank++;
              }
           }
         else if(book[i].type == BOOK_TYPE_SELL || book[i].type == BOOK_TYPE_SELL_MARKET)
           {
            if(askRank < 5)
              {
               double w = MathExp(-0.35 * askRank);
               weightedAsk += w * (double)book[i].volume_real;
               askRank++;
              }
           }
        }

      if(weightedBid + weightedAsk > 0.0)
         bir = (weightedBid - weightedAsk) / (weightedBid + weightedAsk);
     }
   else
     {
      // Synthetic Imbalance Fallback for Strategy Tester / Tick-Only feeds
      if(synthBuyVol + synthSellVol > 0.0)
         bir = (synthBuyVol - synthSellVol) / (synthBuyVol + synthSellVol);
     }

   double requiredBIR = (g_currentRegime == REGIME_MOMENTUM) ? MathMax(InpMinBookImbalance, 0.45) : InpMinBookImbalance;

   if(direction > 0 && bir < requiredBIR)
      return(false);
   if(direction < 0 && bir > -requiredBIR)
      return(false);

   return(true);
  }

//+------------------------------------------------------------------+
//| Layer 5A: Pre-Trade Cost-to-Volatility Ratio (CVR) Gate          |
//+------------------------------------------------------------------+
bool CheckPreTradeCostGate()
  {
   m_symbol.RefreshRates();
   double spreadPrice = m_symbol.Ask() - m_symbol.Bid();

   double atrBuf[];
   ArraySetAsSeries(atrBuf, true);
   if(CopyBuffer(g_atrHandleM1, 0, 0, 1, atrBuf) < 1 || atrBuf[0] <= 0.0)
      return(false);
   double m1ATR = atrBuf[0];

   // Convert Round-Turn Commission ($ per lot) into price units
   double tickValue = m_symbol.TickValue();
   double tickSize  = m_symbol.TickSize();
   double commPrice = 0.0;
   if(tickValue > 0.0 && tickSize > 0.0)
      commPrice = (InpCommissionPerLotRT / tickValue) * tickSize;

   double cvr = (spreadPrice + commPrice) / m1ATR;
   if(cvr > InpMaxCVRRatio)
      return(false);

   return(true);
  }

//+------------------------------------------------------------------+
//| Layer 5B: House-Money Exponential Position Sizer                 |
//+------------------------------------------------------------------+
double CalculateHouseMoneyLots(double stopLossDistancePrice)
  {
   if(stopLossDistancePrice <= 0.0)
      return(0.0);

   double coreEquity  = m_account.Equity();
   double baseRiskUSD = coreEquity * (InpBaseRiskPercent / 100.0);

   // Add asymmetric House-Money scaling buffer (accumulated solely from session wins)
   double scaledRiskUSD = baseRiskUSD + (InpBufferScalingFactor * g_activeScalingBuffer);

   // Enforce hard cap on maximum single-trade risk
   double maxCapUSD = coreEquity * (InpMaxRiskCapPercent / 100.0);
   scaledRiskUSD = MathMin(scaledRiskUSD, maxCapUSD);

   double tickValue = m_symbol.TickValue();
   double tickSize  = m_symbol.TickSize();
   if(tickValue <= 0.0 || tickSize <= 0.0)
      return(0.0);

   double lossPerLot = (stopLossDistancePrice / tickSize) * tickValue + InpCommissionPerLotRT;
   if(lossPerLot <= 0.0)
      return(0.0);

   double rawLots = scaledRiskUSD / lossPerLot;

   // Normalize to broker volume constraints
   double step   = m_symbol.LotsStep();
   double minLot = m_symbol.LotsMin();
   double maxLot = m_symbol.LotsMax();

   double normLots = MathFloor(rawLots / step) * step;
   normLots = MathMax(minLot, MathMin(maxLot, normLots));

   return(NormalizeDouble(normLots, 2));
  }

//+------------------------------------------------------------------+
//| Register Newly Opened Position in State Tracker                  |
//+------------------------------------------------------------------+
void RegisterPositionState(ulong ticket, double volume, double entry, double sl)
  {
   int n = ArraySize(g_posStates);
   ArrayResize(g_posStates, n + 1);
   g_posStates[n].ticket           = ticket;
   g_posStates[n].initialVolume    = volume;
   g_posStates[n].entryPrice       = entry;
   g_posStates[n].initialRiskPrice = MathAbs(entry - sl);
   g_posStates[n].tranche1Closed   = false;
   g_posStates[n].tranche2Closed   = false;
   g_posStates[n].kalmanEstimate   = entry;
   g_posStates[n].kalmanErrorCov   = 1.0;
  }

//+------------------------------------------------------------------+
//| Manage Open Positions: 180s Time-Stop, 3-Tranche & Kalman Trail  |
//+------------------------------------------------------------------+
void ManageOpenPositions()
  {
   m_symbol.RefreshRates();

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

      ulong  ticket   = m_position.Ticket();
      long   posType  = m_position.PositionType();
      double openPrc  = m_position.PriceOpen();
      double currSL   = m_position.StopLoss();
      double currTP   = m_position.TakeProfit();
      double currVol  = m_position.Volume();
      double currPrc  = (posType == POSITION_TYPE_BUY) ? m_symbol.Bid() : m_symbol.Ask();
      int    elapsed  = (int)(TimeCurrent() - m_position.Time());

      // Locate state entry
      int idx = -1;
      for(int k = 0; k < ArraySize(g_posStates); k++)
        {
         if(g_posStates[k].ticket == ticket)
           {
            idx = k;
            break;
           }
        }

      if(idx < 0)
        {
         double fallbackRisk = (currSL > 0.0) ? MathAbs(openPrc - currSL) : GetPipSize() * 4.0;
         RegisterPositionState(ticket, currVol, openPrc, openPrc - fallbackRisk);
         idx = ArraySize(g_posStates) - 1;
        }

      double riskDist = g_posStates[idx].initialRiskPrice;
      if(riskDist <= 0.0)
         continue;

      double profitDist = (posType == POSITION_TYPE_BUY) ? (currPrc - openPrc) : (openPrc - currPrc);
      double currentRR  = profitDist / riskDist;

      //--- 1. The 180-Second Velocity Time-Stop
      if(!g_posStates[idx].tranche1Closed && elapsed >= InpVelocityTimeStopSec && currentRR < 0.50)
        {
         Print("QLM-7 Velocity Time-Stop triggered on ticket #", ticket, " at R=", DoubleToString(currentRR, 2));
         m_trade.PositionClose(ticket);
         continue;
        }

      double lotStep = m_symbol.LotsStep();
      double minLot  = m_symbol.LotsMin();

      //--- 2. Tranche 1: Scale out 50% at +1.2R & Move Stop to Hard Breakeven + 0.2 Pips
      if(!g_posStates[idx].tranche1Closed && currentRR >= InpTranche1RR)
        {
         double closeVol = MathFloor((g_posStates[idx].initialVolume * 0.50) / lotStep) * lotStep;
         if(closeVol >= minLot && (currVol - closeVol) >= minLot)
            m_trade.PositionClosePartial(ticket, NormalizeDouble(closeVol, 2));

         double beBuffer = GetPipSize() * 0.20;
         double newSL    = (posType == POSITION_TYPE_BUY) ? (openPrc + beBuffer) : (openPrc - beBuffer);
         newSL = m_symbol.NormalizePrice(newSL);

         if((posType == POSITION_TYPE_BUY && newSL > currSL) || (posType == POSITION_TYPE_SELL && (currSL == 0.0 || newSL < currSL)))
            m_trade.PositionModify(ticket, newSL, currTP);

         g_posStates[idx].tranche1Closed = true;
         continue;
        }

      //--- 3. Tranche 2: Scale out 30% of original volume at Session VWAP or +2.2R
      bool vwapReached = (g_sessionVWAP > 0.0) &&
                         ((posType == POSITION_TYPE_BUY  && currPrc >= g_sessionVWAP && openPrc < g_sessionVWAP) ||
                          (posType == POSITION_TYPE_SELL && currPrc <= g_sessionVWAP && openPrc > g_sessionVWAP));

      if(g_posStates[idx].tranche1Closed && !g_posStates[idx].tranche2Closed && (currentRR >= InpTranche2RR || vwapReached))
        {
         double closeVol = MathFloor((g_posStates[idx].initialVolume * 0.30) / lotStep) * lotStep;
         if(closeVol >= minLot && (currVol - closeVol) >= minLot)
            m_trade.PositionClosePartial(ticket, NormalizeDouble(closeVol, 2));

         g_posStates[idx].tranche2Closed = true;
         continue;
        }

      //--- 4. Tranche 3: Dynamic 1D Kalman Filter Trail on Final 20% Runner
      if(g_posStates[idx].tranche1Closed)
        {
         // Update 1D Kalman State Estimate
         double predEst = g_posStates[idx].kalmanEstimate;
         double predCov = g_posStates[idx].kalmanErrorCov + InpKalmanProcessNoise;
         double kGain   = predCov / (predCov + InpKalmanMeasureNoise);

         g_posStates[idx].kalmanEstimate = predEst + kGain * (currPrc - predEst);
         g_posStates[idx].kalmanErrorCov = (1.0 - kGain) * predCov;

         double trailBuffer = GetPipSize() * 1.0;
         if(posType == POSITION_TYPE_BUY)
           {
            double kalmanSL = m_symbol.NormalizePrice(g_posStates[idx].kalmanEstimate - trailBuffer);
            if(kalmanSL > currSL && kalmanSL < currPrc)
               m_trade.PositionModify(ticket, kalmanSL, currTP);
           }
         else if(posType == POSITION_TYPE_SELL)
           {
            double kalmanSL = m_symbol.NormalizePrice(g_posStates[idx].kalmanEstimate + trailBuffer);
            if((currSL == 0.0 || kalmanSL < currSL) && kalmanSL > currPrc)
               m_trade.PositionModify(ticket, kalmanSL, currTP);
           }
        }
     }
  }

//+------------------------------------------------------------------+
//| Expert Tick Function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   //--- 1. Check UTC Day Rollover for House-Money Engine
   MqlDateTime dt;
   TimeGMT(dt);
   if(dt.day_of_year != g_currentDayOfYear)
      ResetDailyHouseMoneyState();

   //--- 2. Manage Existing Open Positions (Velocity Stop, Partial Exits, Kalman Trail)
   ManageOpenPositions();

   //--- 3. Enforce Daily Hard Drawdown Stop & Consecutive-Loss Cooldown
   double currentDrawdownPct = ((g_dayStartEquity - m_account.Equity()) / g_dayStartEquity) * 100.0;
   if(currentDrawdownPct >= InpDailyMaxDrawdownPct)
      return;

   if(TimeCurrent() < g_cooldownUntil)
      return;

   //--- 4. Verify Active Institutional Liquidity Window (UTC)
   if(dt.hour < InpLondonStartHourUTC || dt.hour >= InpNYCloseHourUTC)
      return;

   //--- 5. Update Layer 1 (M15 Hurst Regime & Session VWAP) on M15 Bar Close
   datetime m15Time = iTime(_Symbol, PERIOD_M15, 0);
   if(m15Time != g_lastM15BarTime)
     {
      g_lastM15BarTime = m15Time;
      g_currentHurst   = CalculateHurstExponent(PERIOD_M15, InpHurstPeriod);
      UpdateSessionVWAPAndBands();

      if(g_currentHurst < InpHurstMeanRevMax)
         g_currentRegime = REGIME_MEAN_REV;
      else if(g_currentHurst > InpHurstMomentumMin)
         g_currentRegime = REGIME_MOMENTUM;
      else
         g_currentRegime = REGIME_RANDOM_WALK;
     }

   // Stand down if market is in the Random Walk noise zone
   if(g_currentRegime == REGIME_RANDOM_WALK)
      return;

   //--- 6. Continuously Update Layer 2 (Triangular Synthetic Basis Z-Score)
   double basisZScore = GetTriangularBasisZScore();

   // Only allow one active QLM-7 position on this symbol at a time
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      if(m_position.SelectByIndex(i) && m_position.Symbol() == _Symbol && m_position.Magic() == InpMagicNumber)
         return;
     }

   //--- 7. Evaluate Layer 3 Setup on New M1 Candle Formation
   datetime m1Time = iTime(_Symbol, PERIOD_M1, 0);
   if(m1Time == g_lastM1BarTime)
      return;

   int    signalDir    = 0;
   double sweepExtreme = 0.0;
   if(!DetectLiquiditySweepAndDisplacement(signalDir, sweepExtreme))
      return;

   //--- 8. Verify Regime Alignment with Session VWAP & M15 FVG
   m_symbol.RefreshRates();
   double midPrice = (m_symbol.Bid() + m_symbol.Ask()) * 0.5;

   if(g_currentRegime == REGIME_MEAN_REV)
     {
      // Mean-Reversion requires price to be stretched near/beyond Session VAL (Long) or VAH (Short)
      if(signalDir > 0 && g_sessionVAL > 0.0 && midPrice > g_sessionVWAP)
         return;
      if(signalDir < 0 && g_sessionVAH > 0.0 && midPrice < g_sessionVWAP)
         return;
     }
   else if(g_currentRegime == REGIME_MOMENTUM)
     {
      // Momentum requires continuation alignment with Session VWAP + M15 FVG support/resistance
      if(signalDir > 0 && midPrice < g_sessionVWAP)
         return;
      if(signalDir < 0 && midPrice > g_sessionVWAP)
         return;
      if(!CheckM15FVGConfluence(signalDir))
         return;
     }

   //--- 9. Check Layer 2 Synthetic Basis Dislocation
   if(InpUseTriangularFilter)
     {
      // Long requires spot to be lagging synthetic fair value (negative basis Z-Score)
      if(signalDir > 0 && basisZScore > -InpMinBasisZScore)
         return;
      // Short requires spot to be overpriced vs synthetic fair value (positive basis Z-Score)
      if(signalDir < 0 && basisZScore < InpMinBasisZScore)
         return;
     }

   //--- 10. Verify Layer 4 Tick CVD Exhaustion & L2 DOM Book Imbalance
   if(!VerifyOrderFlowTrigger(signalDir))
      return;

   //--- 11. Verify Layer 5 Pre-Trade Cost-to-Volatility Gate (CVR <= 0.18)
   if(!CheckPreTradeCostGate())
      return;

   //--- 12. Calculate Stop-Loss, Lot Size & Transmit Order
   double atrBuf[];
   ArraySetAsSeries(atrBuf, true);
   if(CopyBuffer(g_atrHandleM1, 0, 0, 1, atrBuf) < 1)
      return;
   double m1ATR = atrBuf[0];

   double entryPrice = (signalDir > 0) ? m_symbol.Ask() : m_symbol.Bid();
   double slPrice    = (signalDir > 0) ? (sweepExtreme - 0.35 * m1ATR) : (sweepExtreme + 0.35 * m1ATR);
   slPrice           = m_symbol.NormalizePrice(slPrice);

   double stopDist   = MathAbs(entryPrice - slPrice);
   double minStops   = m_symbol.StopsLevel() * m_symbol.Point();
   if(stopDist <= minStops)
      return;

   double lots = CalculateHouseMoneyLots(stopDist);
   if(lots < m_symbol.LotsMin())
      return;

   // Hard Catastrophic Take-Profit at 4.5R (Tranches 1-3 manage active exits earlier)
   double tpPrice = (signalDir > 0) ? (entryPrice + 4.5 * stopDist) : (entryPrice - 4.5 * stopDist);
   tpPrice        = m_symbol.NormalizePrice(tpPrice);

   bool sent = false;
   if(signalDir > 0)
      sent = m_trade.Buy(lots, _Symbol, entryPrice, slPrice, tpPrice, "QLM7_Long_Regime" + IntegerToString(g_currentRegime));
   else
      sent = m_trade.Sell(lots, _Symbol, entryPrice, slPrice, tpPrice, "QLM7_Short_Regime" + IntegerToString(g_currentRegime));

   if(sent && m_trade.ResultRetcode() == TRADE_RETCODE_DONE)
     {
      g_lastM1BarTime = m1Time;
      RegisterPositionState(m_trade.ResultOrder(), lots, entryPrice, slPrice);
      PrintFormat("QLM-7 ENTRY EXECUTED: Dir=%d | Lots=%.2f | Entry=%.5f | SL=%.5f | Hurst=%.3f | ActiveBuffer=$%.2f",
                  signalDir, lots, entryPrice, slPrice, g_currentHurst, g_activeScalingBuffer);
     }
  }

//+------------------------------------------------------------------+
//| TradeTransaction: Asymmetric 50/50 House-Money Profit Accounting |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest &request,
                        const MqlTradeResult &result)
  {
   if(trans.type != TRADE_TRANSACTION_DEAL_ADD)
      return;

   if(!HistoryDealSelect(trans.deal))
      return;

   long   dealMagic = HistoryDealGetInteger(trans.deal, DEAL_MAGIC);
   string dealSym   = HistoryDealGetString(trans.deal, DEAL_SYMBOL);
   long   dealEntry = HistoryDealGetInteger(trans.deal, DEAL_ENTRY);

   if(dealMagic != (long)InpMagicNumber || dealSym != _Symbol)
      return;

   if(dealEntry != DEAL_ENTRY_OUT && dealEntry != DEAL_ENTRY_INOUT)
      return;

   double netPnL = HistoryDealGetDouble(trans.deal, DEAL_PROFIT)
                 + HistoryDealGetDouble(trans.deal, DEAL_COMMISSION)
                 + HistoryDealGetDouble(trans.deal, DEAL_SWAP);

   if(netPnL > 0.0)
     {
      g_consecutiveLosses = 0;
      double lockedPart   = netPnL * InpProfitLockRatio;
      double scaledPart   = netPnL * (1.0 - InpProfitLockRatio);

      g_lockedProfitFloor   += lockedPart;
      g_activeScalingBuffer += scaledPart;

      PrintFormat("QLM-7 HOUSE-MONEY WIN (+$%.2f): Locked Floor=$%.2f | Active Scaling Buffer=$%.2f",
                  netPnL, g_lockedProfitFloor, g_activeScalingBuffer);
     }
   else if(netPnL < 0.0)
     {
      g_consecutiveLosses++;
      // Absorb loss from active scaling buffer first to protect core capital
      g_activeScalingBuffer = MathMax(0.0, g_activeScalingBuffer + netPnL);

      if(g_consecutiveLosses >= 3)
        {
         g_cooldownUntil = TimeCurrent() + InpCooldownMinutes * 60;
         PrintFormat("QLM-7 COOLDOWN TRIGGERED: 3 consecutive losses. Halting until %s",
                     TimeToString(g_cooldownUntil, TIME_DATE|TIME_MINUTES));
        }
     }
  }
//+------------------------------------------------------------------+
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7647
Joined: Mon Jul 20, 2026 1:28 pm

Re: Most complicated forex scalping strategy

Post by PTScalper »

Deployment & Strategy Tester Notes

Broker Symbol Suffixes: If your Raw ECN account appends a suffix (such as .r, .a, or .pro), update InpCrossPairA and InpCrossPairB in the EA inputs to match (e.g., EURGBP.r and GBPUSD.r).

MT5 Strategy Tester Mode: Always run backtests in "Every tick based on real ticks" mode. Because CopyTicks() evaluates sub-minute bid/ask delta absorption inside VerifyOrderFlowTrigger(), standard OHLC-only modeling will not generate realistic Cumulative Volume Delta divergences.

Would you like to add a custom on-chart MQL5 telemetry HUD (CChartObjectLabel panel) to visualize the live Hurst Exponent, Synthetic Basis Z-Score, Book Imbalance Ratio ($BIR$), and your locked vs. active House-Money buffer in real time?
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7647
Joined: Mon Jul 20, 2026 1:28 pm

Re: Most complicated forex scalping strategy

Post by PTScalper »

Here is the upgraded, complete MQL5 Expert Advisor (QLM7_Institutional_Scalper.mq5 v7.10) featuring an integrated Real-Time Institutional Telemetry HUD built with native MQL5 chart objects (OBJ_RECTANGLE_LABEL and OBJ_LABEL).
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7647
Joined: Mon Jul 20, 2026 1:28 pm

Re: Most complicated forex scalping strategy

Post by PTScalper »

What Was Added for the Live HUD

Zero-Lag Telemetry Caching: Live metrics (g_liveBasisZScore, g_liveBIR, g_liveCVDMomentum, g_liveCVR, and g_lastSweepStatus) are continuously sampled and cached so the HUD reflects sub-second ECN order-flow state even when no trade is open.

Color-Coded Gate Diagnostics: Each of the 5 QLM-7 layers turns Lime (clrLime) when its quantitative threshold is armed, Orange/Gold (clrGold) in neutral standby, and Crimson (clrTomato) when a hard veto is active (such as Random Walk Hurst $0.45 \le H \le 0.55$, high spread-to-ATR cost $CVR > 0.18$, or a 3-loss cooldown).

Strategy Tester Visual Mode Compatibility: Automatically throttles chart redraws (ChartRedraw) in backtests unless Visual Mode is enabled, preserving maximum tick-processing speed during multi-year optimizations.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 7647
Joined: Mon Jul 20, 2026 1:28 pm

Re: Most complicated forex scalping strategy

Post by PTScalper »

Complete MQL5 Code with Telemetry HUD (QLM7_Institutional_Scalper.mq5)

Code: Select all

//+------------------------------------------------------------------+
//|                                  QLM7_Institutional_Scalper.mq5  |
//|         Quantum-Liquidity Matrix (QLM-7) Pro ECN Scalping Engine |
//|   5-Layer Quantitative, Order-Flow & House-Money EA + Live HUD   |
//+------------------------------------------------------------------+
#property copyright "QLM-7 Quantitative Architecture"
#property version   "7.10"
#property strict

#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\AccountInfo.mqh>

//+------------------------------------------------------------------+
//| Enumerations & Input Parameters                                  |
//+------------------------------------------------------------------+
enum ENUM_REGIME_STATE
  {
   REGIME_RANDOM_WALK = 0, // Neutral / Veto Zone (0.45 <= H <= 0.55)
   REGIME_MEAN_REV    = 1, // Anti-Persistent Fade Mode (H < 0.45)
   REGIME_MOMENTUM    = 2  // Persistent Trend Mode (H > 0.55)
  };

input group "=== [1] General & ECN Execution Settings ==="
input ulong    InpMagicNumber          = 7007007;      // EA Magic Number
input double   InpCommissionPerLotRT   = 7.0;          // ECN Round-Turn Commission ($ per 1.0 Lot)
input double   InpMaxCVRRatio          = 0.18;         // Max Cost-to-Volatility Ratio (Spread+Comm / M1 ATR)
input uint     InpMaxSlippagePoints    = 4;            // Max Permitted Slippage (Points)
input int      InpLondonStartHourUTC   = 7;            // Session Start Hour (UTC)
input int      InpNYCloseHourUTC       = 17;           // Session End Hour (UTC)

input group "=== [2] Layer 1: Regime & VWAP Parameters (M15) ==="
input int      InpHurstPeriod          = 100;          // Rolling M15 Hurst Lookback Bars
input double   InpHurstMeanRevMax      = 0.45;         // Hurst Upper Bound for Mean-Reversion
input double   InpHurstMomentumMin     = 0.55;         // Hurst Lower Bound for Momentum
input double   InpVWAPBandSigma        = 2.0;          // Session VWAP Band Multiplier (Sigma)

input group "=== [3] Layer 2: Cross-Asset Triangular Basis ==="
input bool     InpUseTriangularFilter  = true;         // Enable Synthetic Cross-Pair Basis Filter
input string   InpCrossPairA           = "EURGBP";     // Synthetic Leg A (Include broker suffix if any)
input string   InpCrossPairB           = "GBPUSD";     // Synthetic Leg B (Include broker suffix if any)
input double   InpMinBasisZScore       = 1.50;         // Required Synthetic Dislocation Z-Score

input group "=== [4] Layer 3 & 4: Microstructure & Order Flow ==="
input int      InpSwingLookbackM5      = 12;           // M5 Liquidity Swing Lookback (Bars)
input double   InpMinSweepPips         = 1.5;          // Min Stop-Sweep Penetration (Standard Pips)
input double   InpMaxSweepPips         = 6.0;          // Max Stop-Sweep Penetration (Standard Pips)
input double   InpDisplacementATRMult  = 1.35;         // M1 Rejection Displacement Body / ATR
input int      InpTickDeltaLookback    = 400;          // Tick Count for CVD Absorption Analysis
input double   InpMinBookImbalance     = 0.35;         // Required L2 DOM / Synthetic Imbalance Ratio

input group "=== [5] Layer 5: House-Money Risk & 3-Tranche Exits ==="
input double   InpBaseRiskPercent      = 0.25;         // Base Risk Per Scalp (% of Core Equity)
input double   InpProfitLockRatio      = 0.50;         // Fraction of Net Win Locked (0.50 = 50% Lock / 50% Scale)
input double   InpBufferScalingFactor  = 0.75;         // Gamma: Portion of Active Buffer Deployed on Next Trade
input double   InpMaxRiskCapPercent    = 1.50;         // Hard Cap on Scaled Risk (% of Equity)
input double   InpDailyMaxDrawdownPct  = 1.25;         // Hard Daily Stop Loss (% of Starting Day Equity)
input int      InpCooldownMinutes      = 45;           // Cooldown Duration After 3 Consecutive Losses (Min)
input int      InpVelocityTimeStopSec  = 180;          // Velocity Time-Stop (Seconds to reach +0.5R)
input double   InpTranche1RR           = 1.20;         // Tranche 1 Target (R-Multiple, closes 50%)
input double   InpTranche2RR           = 2.20;         // Tranche 2 Target (R-Multiple, closes 30%)
input double   InpKalmanProcessNoise   = 0.0005;       // Kalman Filter Process Variance (Q) for 20% Runner
input double   InpKalmanMeasureNoise   = 0.0050;       // Kalman Filter Measurement Variance (R)

input group "=== [6] Telemetry HUD Visual Settings ==="
input bool     InpShowHUD              = true;         // Enable On-Chart Telemetry HUD Panel
input int      InpHUD_X                = 16;           // HUD X Offset (Pixels from Top-Left)
input int      InpHUD_Y                = 28;           // HUD Y Offset (Pixels from Top-Left)
input color    InpHUD_BgColor          = C'16,20,28';  // HUD Background Panel Color
input color    InpHUD_BorderColor      = C'55,70,95';  // HUD Border Color

//+------------------------------------------------------------------+
//| Internal Position State Tracking Structure                       |
//+------------------------------------------------------------------+
struct PositionState
  {
   ulong             ticket;
   double            initialVolume;
   double            initialRiskPrice;
   double            entryPrice;
   bool              tranche1Closed;
   bool              tranche2Closed;
   double            kalmanEstimate;
   double            kalmanErrorCov;
  };

//+------------------------------------------------------------------+
//| Global Variables & Objects                                       |
//+------------------------------------------------------------------+
CTrade         m_trade;
CPositionInfo  m_position;
CSymbolInfo    m_symbol;
CAccountInfo   m_account;

int            g_atrHandleM1           = INVALID_HANDLE;
bool           g_domSubscribed         = false;
bool           g_crossPairsValid       = false;

// House-Money & Session State
int            g_currentDayOfYear      = -1;
double         g_dayStartEquity        = 0.0;
double         g_lockedProfitFloor     = 0.0;
double         g_activeScalingBuffer   = 0.0;
int            g_consecutiveLosses     = 0;
datetime       g_cooldownUntil         = 0;

// Triangular Basis Circular Buffer
#define        BASIS_BUFFER_SIZE 60
double         g_basisBuffer[BASIS_BUFFER_SIZE];
int            g_basisIndex            = 0;
int            g_basisCount            = 0;

// Cached Regime & Live Telemetry Metrics
datetime       g_lastM15BarTime        = 0;
datetime       g_lastM1BarTime         = 0;
double         g_currentHurst          = 0.50;
ENUM_REGIME_STATE g_currentRegime      = REGIME_RANDOM_WALK;
double         g_sessionVWAP           = 0.0;
double         g_sessionVAH            = 0.0;
double         g_sessionVAL            = 0.0;

double         g_liveBasisZScore       = 0.0;
double         g_liveBIR               = 0.0;
double         g_liveCVDMomentum       = 0.0;
double         g_liveCVR               = 0.0;
string         g_lastSweepStatus       = "SCANNING M5 POOLS";
uint           g_lastHUDUpdateTick     = 0;

#define        HUD_PREFIX "QLM7_HUD_"
#define        HUD_ROWS   13

// Active Position Tracker
PositionState  g_posStates[];

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   if(!m_symbol.Name(_Symbol))
      return(INIT_FAILED);
   m_symbol.RefreshRates();

   m_trade.SetExpertMagicNumber(InpMagicNumber);
   m_trade.SetDeviationInPoints(InpMaxSlippagePoints);
   m_trade.SetTypeFillingBySymbol(_Symbol);
   m_trade.SetAsyncMode(false);

   g_atrHandleM1 = iATR(_Symbol, PERIOD_M1, 14);
   if(g_atrHandleM1 == INVALID_HANDLE)
     {
      Print("CRITICAL: Failed to create M1 ATR(14) handle.");
      return(INIT_FAILED);
     }

   if(MarketBookAdd(_Symbol))
      g_domSubscribed = true;
   else
      Print("INFO: L2 DOM unavailable for ", _Symbol, ". Using Synthetic Tick-Velocity Imbalance fallback.");

   if(InpUseTriangularFilter)
     {
      if(SymbolSelect(InpCrossPairA, true) && SymbolSelect(InpCrossPairB, true))
         g_crossPairsValid = true;
      else
         Print("INFO: Cross pairs (", InpCrossPairA, "/", InpCrossPairB, ") unavailable. Using internal velocity Z-Score.");
     }

   ArrayInitialize(g_basisBuffer, 0.0);
   ResetDailyHouseMoneyState();

   // Compute initial Layer 1 metrics immediately on startup
   g_currentHurst = CalculateHurstExponent(PERIOD_M15, InpHurstPeriod);
   UpdateSessionVWAPAndBands();
   if(g_currentHurst < InpHurstMeanRevMax)
      g_currentRegime = REGIME_MEAN_REV;
   else if(g_currentHurst > InpHurstMomentumMin)
      g_currentRegime = REGIME_MOMENTUM;
   else
      g_currentRegime = REGIME_RANDOM_WALK;

   if(InpShowHUD)
     {
      CreateHUDPanel();
      UpdateHUDPanel();
     }

   Print("QLM-7 v7.10 Institutional Scalper + Telemetry HUD initialized on ", _Symbol);
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(g_atrHandleM1 != INVALID_HANDLE)
      IndicatorRelease(g_atrHandleM1);
   if(g_domSubscribed)
      MarketBookRelease(_Symbol);
   ObjectsDeleteAll(0, HUD_PREFIX);
   ChartRedraw(0);
  }

//+------------------------------------------------------------------+
//| Create On-Chart Telemetry HUD Objects                            |
//+------------------------------------------------------------------+
void CreateHUDPanel()
  {
   ObjectsDeleteAll(0, HUD_PREFIX);

   string bgName = HUD_PREFIX + "BG";
   ObjectCreate(0, bgName, OBJ_RECTANGLE_LABEL, 0, 0, 0);
   ObjectSetInteger(0, bgName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetInteger(0, bgName, OBJPROP_XDISTANCE, InpHUD_X);
   ObjectSetInteger(0, bgName, OBJPROP_YDISTANCE, InpHUD_Y);
   ObjectSetInteger(0, bgName, OBJPROP_XSIZE, 365);
   ObjectSetInteger(0, bgName, OBJPROP_YSIZE, 286);
   ObjectSetInteger(0, bgName, OBJPROP_BGCOLOR, InpHUD_BgColor);
   ObjectSetInteger(0, bgName, OBJPROP_BORDER_TYPE, BORDER_FLAT);
   ObjectSetInteger(0, bgName, OBJPROP_COLOR, InpHUD_BorderColor);
   ObjectSetInteger(0, bgName, OBJPROP_BACK, false);
   ObjectSetInteger(0, bgName, OBJPROP_SELECTABLE, false);

   for(int i = 0; i < HUD_ROWS; i++)
     {
      string lblName = HUD_PREFIX + "ROW_" + IntegerToString(i);
      ObjectCreate(0, lblName, OBJ_LABEL, 0, 0, 0);
      ObjectSetInteger(0, lblName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
      ObjectSetInteger(0, lblName, OBJPROP_XDISTANCE, InpHUD_X + 12);
      ObjectSetInteger(0, lblName, OBJPROP_YDISTANCE, InpHUD_Y + 10 + (i * 20));
      ObjectSetString(0, lblName, OBJPROP_FONT, "Consolas");
      ObjectSetInteger(0, lblName, OBJPROP_FONTSIZE, (i == 0) ? 10 : 9);
      ObjectSetInteger(0, lblName, OBJPROP_COLOR, clrSilver);
      ObjectSetInteger(0, lblName, OBJPROP_SELECTABLE, false);
     }
  }

//+------------------------------------------------------------------+
//| Helper: Update Specific HUD Row Text and Color                   |
//+------------------------------------------------------------------+
void SetHUDRow(int row, string text, color clr)
  {
   string lblName = HUD_PREFIX + "ROW_" + IntegerToString(row);
   ObjectSetString(0, lblName, OBJPROP_TEXT, text);
   ObjectSetInteger(0, lblName, OBJPROP_COLOR, clr);
  }

//+------------------------------------------------------------------+
//| Refresh On-Chart Telemetry HUD Values                            |
//+------------------------------------------------------------------+
void UpdateHUDPanel()
  {
   if(!InpShowHUD)
      return;

   // Avoid unnecessary GUI overhead during non-visual Strategy Tester optimizations
   if(MQLInfoInteger(MQL_TESTER) && !MQLInfoInteger(MQL_VISUAL_MODE))
      return;

   // Throttle redraws to ~4 FPS (every 250ms)
   uint nowTick = GetTickCount();
   if(nowTick - g_lastHUDUpdateTick < 250)
      return;
   g_lastHUDUpdateTick = nowTick;

   MqlDateTime dt;
   TimeGMT(dt);
   bool sessionOpen = (dt.hour >= InpLondonStartHourUTC && dt.hour < InpNYCloseHourUTC);

   // Row 0: Header
   SetHUDRow(0, "=== QLM-7 QUANT TELEMETRY [" + _Symbol + "] ===", clrAqua);

   // Row 1: Session & System Status
   double ddPct = (g_dayStartEquity > 0.0) ? ((g_dayStartEquity - m_account.Equity()) / g_dayStartEquity) * 100.0 : 0.0;
   string sysStatus = "ARMED (ACTIVE)";
   color  sysColor  = clrLime;
   if(ddPct >= InpDailyMaxDrawdownPct)
     {
      sysStatus = "HALTED (DAILY MAX DD)";
      sysColor  = clrTomato;
     }
   else if(TimeCurrent() < g_cooldownUntil)
     {
      sysStatus = "COOLDOWN (" + IntegerToString((int)(g_cooldownUntil - TimeCurrent()) / 60) + "m LEFT)";
      sysColor  = clrTomato;
     }
   else if(!sessionOpen)
     {
      sysStatus = "STANDBY (OFF-SESSION)";
      sysColor  = clrGold;
     }
   SetHUDRow(1, StringFormat("SYS STATUS : %-24s", sysStatus), sysColor);

   // Row 2: Layer 1 Hurst & Regime
   string regStr = "RANDOM WALK [VETO]";
   color  regClr = clrTomato;
   if(g_currentRegime == REGIME_MEAN_REV)
     {
      regStr = "MEAN-REVERSION [FADE]";
      regClr = clrDeepSkyBlue;
     }
   else if(g_currentRegime == REGIME_MOMENTUM)
     {
      regStr = "MOMENTUM [TREND]";
      regClr = clrLime;
     }
   SetHUDRow(2, StringFormat("L1 HURST   : H=%.3f | %s", g_currentHurst, regStr), regClr);

   // Row 3: Layer 1 Session VWAP & Bands
   int d = m_symbol.Digits();
   SetHUDRow(3, StringFormat("L1 VWAP    : %s [%s .. %s]",
             DoubleToString(g_sessionVWAP, d),
             DoubleToString(g_sessionVAL, d),
             DoubleToString(g_sessionVAH, d)), clrSilver);

   // Row 4: Layer 2 Triangular Basis Z-Score
   bool   basisArmed = (MathAbs(g_liveBasisZScore) >= InpMinBasisZScore);
   string basisMode  = g_crossPairsValid ? "TRI-ARB" : "VEL-Z";
   SetHUDRow(4, StringFormat("L2 BASIS Z : %+.2f sigma (%s | Req %.2f)",
             g_liveBasisZScore, basisMode, InpMinBasisZScore),
             basisArmed ? clrLime : clrSilver);

   // Row 5: Layer 3 Liquidity Sweep & FVG Status
   SetHUDRow(5, StringFormat("L3 STRUCT  : %s", g_lastSweepStatus),
             (StringFind(g_lastSweepStatus, "SWEEP") >= 0) ? clrLime : clrSilver);

   // Row 6: Layer 4 L2 Book Imbalance (BIR) & Tick CVD
   double reqBIR   = (g_currentRegime == REGIME_MOMENTUM) ? MathMax(InpMinBookImbalance, 0.45) : InpMinBookImbalance;
   bool   birArmed = (MathAbs(g_liveBIR) >= reqBIR);
   string domMode  = g_domSubscribed ? "L2-DOM" : "SYNTH";
   SetHUDRow(6, StringFormat("L4 OFLOW   : BIR=%+.2f (%s) | CVD=%+.0f",
             g_liveBIR, domMode, g_liveCVDMomentum),
             birArmed ? clrLime : clrSilver);

   // Row 7: Layer 5 Cost-to-Volatility Gate (CVR)
   bool cvrPass = (g_liveCVR > 0.0 && g_liveCVR <= InpMaxCVRRatio);
   SetHUDRow(7, StringFormat("L5 COST CVR: %.3f / %.3f (%s)",
             g_liveCVR, InpMaxCVRRatio, cvrPass ? "PASS" : "VETO - HIGH SPREAD"),
             cvrPass ? clrLime : clrTomato);

   // Row 8: Divider
   SetHUDRow(8, "-------------------------------------------", C'55,70,95');

   // Row 9: House-Money Floor & Active Scaling Buffer
   SetHUDRow(9, StringFormat("HM BUFFER  : Lock:+$%.2f | Scale:$%.2f",
             g_lockedProfitFloor, g_activeScalingBuffer),
             (g_activeScalingBuffer > 0.0) ? clrLime : clrSilver);

   // Row 10: Effective Next-Trade Risk Calculation
   double coreEq        = m_account.Equity();
   double baseRiskUSD   = coreEq * (InpBaseRiskPercent / 100.0);
   double nextRiskUSD   = MathMin(coreEq * (InpMaxRiskCapPercent / 100.0),
                                  baseRiskUSD + InpBufferScalingFactor * g_activeScalingBuffer);
   double nextRiskPct   = (coreEq > 0.0) ? (nextRiskUSD / coreEq) * 100.0 : InpBaseRiskPercent;
   SetHUDRow(10, StringFormat("NEXT RISK  : $%.2f (%.2f%% Eq) | LossStreak:%d",
             nextRiskUSD, nextRiskPct, g_consecutiveLosses),
             (nextRiskPct > InpBaseRiskPercent) ? clrAqua : clrSilver);

   // Row 11 & 12: Active Position & Kalman Filter Telemetry
   string posLine1 = "OPEN TRADE : NONE (AWAITING A+ CONFLUENCE)";
   string posLine2 = "KALMAN TRL : STANDBY";
   color  posClr   = clrGray;

   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      if(m_position.SelectByIndex(i) && m_position.Symbol() == _Symbol && m_position.Magic() == InpMagicNumber)
        {
         ulong  ticket  = m_position.Ticket();
         long   pType   = m_position.PositionType();
         double openPrc = m_position.PriceOpen();
         double currPrc = (pType == POSITION_TYPE_BUY) ? m_symbol.Bid() : m_symbol.Ask();
         int    elapsed = (int)(TimeCurrent() - m_position.Time());

         double rr = 0.0;
         string tState = "T0";
         double kEst = openPrc;

         for(int k = 0; k < ArraySize(g_posStates); k++)
           {
            if(g_posStates[k].ticket == ticket && g_posStates[k].initialRiskPrice > 0.0)
              {
               double pDist = (pType == POSITION_TYPE_BUY) ? (currPrc - openPrc) : (openPrc - currPrc);
               rr = pDist / g_posStates[k].initialRiskPrice;
               if(g_posStates[k].tranche2Closed)      tState = "T2(RUNNER)";
               else if(g_posStates[k].tranche1Closed) tState = "T1(BE+50%)";
               kEst = g_posStates[k].kalmanEstimate;
               break;
              }
           }

         posLine1 = StringFormat("OPEN TRADE : %s %.2fL | %+.2fR | %ds [%s]",
                                 (pType == POSITION_TYPE_BUY ? "BUY" : "SELL"),
                                 m_position.Volume(), rr, elapsed, tState);
         posLine2 = StringFormat("KALMAN EST : %s | PnL: %+.2f USD",
                                 DoubleToString(kEst, d), m_position.Profit());
         posClr   = (rr >= 0.0) ? clrLime : clrOrange;
         break;
        }
     }

   SetHUDRow(11, posLine1, posClr);
   SetHUDRow(12, posLine2, posClr);

   ChartRedraw(0);
  }

//+------------------------------------------------------------------+
//| Reset Daily House-Money State on UTC Day Rollover                |
//+------------------------------------------------------------------+
void ResetDailyHouseMoneyState()
  {
   MqlDateTime dt;
   TimeGMT(dt);
   g_currentDayOfYear    = dt.day_of_year;
   g_dayStartEquity      = m_account.Equity();
   g_lockedProfitFloor   = 0.0;
   g_activeScalingBuffer = 0.0;
   g_consecutiveLosses   = 0;
   g_cooldownUntil       = 0;
  }

//+------------------------------------------------------------------+
//| Helper: Standard Pip Size Calculation (FX vs Metals)             |
//+------------------------------------------------------------------+
double GetPipSize()
  {
   int digits = m_symbol.Digits();
   if(digits == 5 || digits == 3 || digits == 2)
      return(m_symbol.Point() * 10.0);
   return(m_symbol.Point());
  }

//+------------------------------------------------------------------+
//| Layer 1A: Rolling Rescaled Range (R/S) Hurst Exponent on M15     |
//+------------------------------------------------------------------+
double CalculateHurstExponent(ENUM_TIMEFRAMES tf, int period)
  {
   double close[];
   ArraySetAsSeries(close, true);
   if(CopyClose(_Symbol, tf, 0, period + 1, close) < period + 1)
      return(0.50);

   double logReturns[];
   ArrayResize(logReturns, period);
   double mean = 0.0;

   for(int i = 0; i < period; i++)
     {
      if(close[i + 1] <= 0.0)
         return(0.50);
      logReturns[i] = MathLog(close[i] / close[i + 1]);
      mean += logReturns[i];
     }
   mean /= (double)period;

   double cumDev = 0.0;
   double maxDev = -DBL_MAX;
   double minDev =  DBL_MAX;
   double sumSq  = 0.0;

   for(int i = 0; i < period; i++)
     {
      double dev = logReturns[i] - mean;
      cumDev += dev;
      if(cumDev > maxDev) maxDev = cumDev;
      if(cumDev < minDev) minDev = cumDev;
      sumSq += dev * dev;
     }

   double range = maxDev - minDev;
   double stdev = MathSqrt(sumSq / (double)period);
   if(stdev <= 1e-12 || range <= 1e-12)
      return(0.50);

   double rs = range / stdev;
   double hurst = MathLog(rs) / MathLog((double)period);
   return(MathMax(0.05, MathMin(0.95, hurst)));
  }

//+------------------------------------------------------------------+
//| Layer 1B: Intraday Session VWAP & +/- Sigma Bands                |
//+------------------------------------------------------------------+
void UpdateSessionVWAPAndBands()
  {
   datetime dayStart = iTime(_Symbol, PERIOD_D1, 0);
   MqlRates rates[];
   ArraySetAsSeries(rates, true);

   int copied = CopyRates(_Symbol, PERIOD_M5, dayStart, TimeCurrent(), rates);
   if(copied <= 2)
      return;

   double sumPV = 0.0;
   double sumV  = 0.0;

   for(int i = 0; i < copied; i++)
     {
      double typical = (rates[i].high + rates[i].low + rates[i].close) / 3.0;
      double vol     = (double)MathMax((long)1, rates[i].tick_volume);
      sumPV += typical * vol;
      sumV  += vol;
     }

   if(sumV <= 0.0)
      return;

   g_sessionVWAP = sumPV / sumV;

   double sumVar = 0.0;
   for(int i = 0; i < copied; i++)
     {
      double typical = (rates[i].high + rates[i].low + rates[i].close) / 3.0;
      double vol     = (double)MathMax((long)1, rates[i].tick_volume);
      double diff    = typical - g_sessionVWAP;
      sumVar += vol * diff * diff;
     }

   double sigma = MathSqrt(sumVar / sumV);
   g_sessionVAH = g_sessionVWAP + InpVWAPBandSigma * sigma;
   g_sessionVAL = g_sessionVWAP - InpVWAPBandSigma * sigma;
  }

//+------------------------------------------------------------------+
//| Layer 2: Update & Compute Triangular Synthetic Basis Z-Score     |
//+------------------------------------------------------------------+
double GetTriangularBasisZScore()
  {
   m_symbol.RefreshRates();
   double spotMid = (m_symbol.Bid() + m_symbol.Ask()) * 0.5;
   if(spotMid <= 0.0)
      return(0.0);

   double basisVal = 0.0;

   if(InpUseTriangularFilter && g_crossPairsValid)
     {
      double bidA = SymbolInfoDouble(InpCrossPairA, SYMBOL_BID);
      double askA = SymbolInfoDouble(InpCrossPairA, SYMBOL_ASK);
      double bidB = SymbolInfoDouble(InpCrossPairB, SYMBOL_BID);
      double askB = SymbolInfoDouble(InpCrossPairB, SYMBOL_ASK);

      double midA = (bidA + askA) * 0.5;
      double midB = (bidB + askB) * 0.5;

      if(midA > 0.0 && midB > 0.0)
         basisVal = MathLog(spotMid) - MathLog(midA * midB);
     }
   else
     {
      MqlTick ticks[];
      if(CopyTicks(_Symbol, ticks, COPY_TICKS_INFO, 0, 20) == 20)
        {
         double sumMid = 0.0;
         for(int i = 0; i < 20; i++)
            sumMid += (ticks[i].bid + ticks[i].ask) * 0.5;
         double avgMid = sumMid / 20.0;
         if(avgMid > 0.0)
            basisVal = MathLog(spotMid) - MathLog(avgMid);
        }
     }

   g_basisBuffer[g_basisIndex] = basisVal;
   g_basisIndex = (g_basisIndex + 1) % BASIS_BUFFER_SIZE;
   if(g_basisCount < BASIS_BUFFER_SIZE)
      g_basisCount++;

   if(g_basisCount < 20)
      return(0.0);

   double sum = 0.0;
   for(int i = 0; i < g_basisCount; i++)
      sum += g_basisBuffer[i];
   double mean = sum / (double)g_basisCount;

   double sqSum = 0.0;
   for(int i = 0; i < g_basisCount; i++)
     {
      double d = g_basisBuffer[i] - mean;
      sqSum += d * d;
     }

   double stdev = MathSqrt(sqSum / (double)g_basisCount);
   if(stdev <= 1e-12)
      return(0.0);

   g_liveBasisZScore = (basisVal - mean) / stdev;
   return(g_liveBasisZScore);
  }

//+------------------------------------------------------------------+
//| Layer 3A: Detect Unmitigated M15 Fair Value Gap (FVG) Proximity  |
//+------------------------------------------------------------------+
bool CheckM15FVGConfluence(int direction)
  {
   MqlRates m15[];
   ArraySetAsSeries(m15, true);
   if(CopyRates(_Symbol, PERIOD_M15, 0, 12, m15) < 12)
      return(false);

   double currentPrice = (direction > 0) ? m_symbol.Bid() : m_symbol.Ask();
   double tolerance    = GetPipSize() * 3.0;

   for(int i = 1; i <= 8; i++)
     {
      if(direction > 0 && m15[i].low > m15[i + 2].high)
        {
         double fvgTop    = m15[i].low + tolerance;
         double fvgBottom = m15[i + 2].high - tolerance;
         if(currentPrice <= fvgTop && currentPrice >= fvgBottom)
            return(true);
        }
      if(direction < 0 && m15[i].high < m15[i + 2].low)
        {
         double fvgTop    = m15[i + 2].low + tolerance;
         double fvgBottom = m15[i].high - tolerance;
         if(currentPrice <= fvgTop && currentPrice >= fvgBottom)
            return(true);
        }
     }
   return(false);
  }

//+------------------------------------------------------------------+
//| Layer 3B: Detect M5 Liquidity Stop-Sweep + M1 Displacement       |
//+------------------------------------------------------------------+
bool DetectLiquiditySweepAndDisplacement(int &outSignalDir, double &outSweepExtreme)
  {
   outSignalDir    = 0;
   outSweepExtreme = 0.0;

   MqlRates m5[], m1[];
   ArraySetAsSeries(m5, true);
   ArraySetAsSeries(m1, true);

   if(CopyRates(_Symbol, PERIOD_M5, 1, InpSwingLookbackM5 + 2, m5) < InpSwingLookbackM5 + 2)
      return(false);
   if(CopyRates(_Symbol, PERIOD_M1, 1, 4, m1) < 4)
      return(false);

   double atrBuf[];
   ArraySetAsSeries(atrBuf, true);
   if(CopyBuffer(g_atrHandleM1, 0, 1, 1, atrBuf) < 1 || atrBuf[0] <= 0.0)
      return(false);
   double m1ATR = atrBuf[0];

   double swingHigh = -DBL_MAX;
   double swingLow  =  DBL_MAX;
   for(int i = 1; i <= InpSwingLookbackM5; i++)
     {
      if(m5[i].high > swingHigh) swingHigh = m5[i].high;
      if(m5[i].low  < swingLow)  swingLow  = m5[i].low;
     }

   double pipSize      = GetPipSize();
   double minSweepDist = InpMinSweepPips * pipSize;
   double maxSweepDist = InpMaxSweepPips * pipSize;

   double recentLowest  = MathMin(m1[0].low,  m1[1].low);
   double recentHighest = MathMax(m1[0].high, m1[1].high);
   double lastBody      = MathAbs(m1[0].close - m1[0].open);

   double bullSweepDepth = swingLow - recentLowest;
   if(bullSweepDepth >= minSweepDist && bullSweepDepth <= maxSweepDist)
     {
      if(m1[0].close > swingLow && m1[0].close > m1[0].open && lastBody >= InpDisplacementATRMult * m1ATR)
        {
         outSignalDir      = 1;
         outSweepExtreme   = recentLowest;
         g_lastSweepStatus = StringFormat("BULL SWEEP (%.1f pips)", bullSweepDepth / pipSize);
         return(true);
        }
     }

   double bearSweepDepth = recentHighest - swingHigh;
   if(bearSweepDepth >= minSweepDist && bearSweepDepth <= maxSweepDist)
     {
      if(m1[0].close < swingHigh && m1[0].close < m1[0].open && lastBody >= InpDisplacementATRMult * m1ATR)
        {
         outSignalDir      = -1;
         outSweepExtreme   = recentHighest;
         g_lastSweepStatus = StringFormat("BEAR SWEEP (%.1f pips)", bearSweepDepth / pipSize);
         return(true);
        }
     }

   g_lastSweepStatus = "SCANNING M5 POOLS";
   return(false);
  }

//+------------------------------------------------------------------+
//| Refresh Live Order-Flow Telemetry (CVD & L2 Book Imbalance)      |
//+------------------------------------------------------------------+
void RefreshOrderFlowTelemetry()
  {
   MqlTick ticks[];
   int copied = CopyTicks(_Symbol, ticks, COPY_TICKS_ALL, 0, InpTickDeltaLookback);
   if(copied < 100)
      return;

   int half = copied / 2;
   double cvdFirstHalf  = 0.0;
   double cvdSecondHalf = 0.0;
   double synthBuyVol   = 0.0;
   double synthSellVol  = 0.0;

   for(int i = 1; i < copied; i++)
     {
      double vol = (ticks[i].volume > 0) ? (double)ticks[i].volume : 1.0;
      double signedVol = 0.0;

      bool isBuy  = ((ticks[i].flags & TICK_FLAG_BUY)  != 0);
      bool isSell = ((ticks[i].flags & TICK_FLAG_SELL) != 0);

      if(isBuy && !isSell)
         signedVol = vol;
      else if(isSell && !isBuy)
         signedVol = -vol;
      else
        {
         double midCurr = (ticks[i].bid + ticks[i].ask) * 0.5;
         double midPrev = (ticks[i - 1].bid + ticks[i - 1].ask) * 0.5;
         if(midCurr > midPrev)      signedVol = vol;
         else if(midCurr < midPrev) signedVol = -vol;
        }

      if(i < half) cvdFirstHalf  += signedVol;
      else         cvdSecondHalf += signedVol;

      if(i >= copied - 50)
        {
         if(signedVol > 0) synthBuyVol  += vol;
         if(signedVol < 0) synthSellVol += vol;
        }
     }

   g_liveCVDMomentum = cvdSecondHalf - cvdFirstHalf;

   MqlBookInfo book[];
   if(g_domSubscribed && MarketBookGet(_Symbol, book) && ArraySize(book) >= 2)
     {
      double weightedBid = 0.0;
      double weightedAsk = 0.0;
      int bidRank = 0;
      int askRank = 0;

      int totalEntries = ArraySize(book);
      for(int i = 0; i < totalEntries; i++)
        {
         if(book[i].type == BOOK_TYPE_BUY || book[i].type == BOOK_TYPE_BUY_MARKET)
           {
            if(bidRank < 5)
              {
               double w = MathExp(-0.35 * bidRank);
               weightedBid += w * (double)book[i].volume_real;
               bidRank++;
              }
           }
         else if(book[i].type == BOOK_TYPE_SELL || book[i].type == BOOK_TYPE_SELL_MARKET)
           {
            if(askRank < 5)
              {
               double w = MathExp(-0.35 * askRank);
               weightedAsk += w * (double)book[i].volume_real;
               askRank++;
              }
           }
        }

      if(weightedBid + weightedAsk > 0.0)
         g_liveBIR = (weightedBid - weightedAsk) / (weightedBid + weightedAsk);
     }
   else
     {
      if(synthBuyVol + synthSellVol > 0.0)
         g_liveBIR = (synthBuyVol - synthSellVol) / (synthBuyVol + synthSellVol);
     }
  }

//+------------------------------------------------------------------+
//| Layer 4: Verify Order-Flow Trigger Using Cached Telemetry        |
//+------------------------------------------------------------------+
bool VerifyOrderFlowTrigger(int direction)
  {
   if(direction > 0 && g_liveCVDMomentum <= 0.0)
      return(false);
   if(direction < 0 && g_liveCVDMomentum >= 0.0)
      return(false);

   double requiredBIR = (g_currentRegime == REGIME_MOMENTUM) ? MathMax(InpMinBookImbalance, 0.45) : InpMinBookImbalance;

   if(direction > 0 && g_liveBIR < requiredBIR)
      return(false);
   if(direction < 0 && g_liveBIR > -requiredBIR)
      return(false);

   return(true);
  }

//+------------------------------------------------------------------+
//| Layer 5A: Pre-Trade Cost-to-Volatility Ratio (CVR) Gate          |
//+------------------------------------------------------------------+
bool CheckPreTradeCostGate()
  {
   m_symbol.RefreshRates();
   double spreadPrice = m_symbol.Ask() - m_symbol.Bid();

   double atrBuf[];
   ArraySetAsSeries(atrBuf, true);
   if(CopyBuffer(g_atrHandleM1, 0, 0, 1, atrBuf) < 1 || atrBuf[0] <= 0.0)
      return(false);
   double m1ATR = atrBuf[0];

   double tickValue = m_symbol.TickValue();
   double tickSize  = m_symbol.TickSize();
   double commPrice = 0.0;
   if(tickValue > 0.0 && tickSize > 0.0)
      commPrice = (InpCommissionPerLotRT / tickValue) * tickSize;

   g_liveCVR = (spreadPrice + commPrice) / m1ATR;
   return(g_liveCVR <= InpMaxCVRRatio);
  }

//+------------------------------------------------------------------+
//| Layer 5B: House-Money Exponential Position Sizer                 |
//+------------------------------------------------------------------+
double CalculateHouseMoneyLots(double stopLossDistancePrice)
  {
   if(stopLossDistancePrice <= 0.0)
      return(0.0);

   double coreEquity    = m_account.Equity();
   double baseRiskUSD   = coreEquity * (InpBaseRiskPercent / 100.0);
   double scaledRiskUSD = baseRiskUSD + (InpBufferScalingFactor * g_activeScalingBuffer);
   double maxCapUSD     = coreEquity * (InpMaxRiskCapPercent / 100.0);
   scaledRiskUSD        = MathMin(scaledRiskUSD, maxCapUSD);

   double tickValue = m_symbol.TickValue();
   double tickSize  = m_symbol.TickSize();
   if(tickValue <= 0.0 || tickSize <= 0.0)
      return(0.0);

   double lossPerLot = (stopLossDistancePrice / tickSize) * tickValue + InpCommissionPerLotRT;
   if(lossPerLot <= 0.0)
      return(0.0);

   double rawLots  = scaledRiskUSD / lossPerLot;
   double step     = m_symbol.LotsStep();
   double minLot   = m_symbol.LotsMin();
   double maxLot   = m_symbol.LotsMax();
   double normLots = MathFloor(rawLots / step) * step;
   normLots        = MathMax(minLot, MathMin(maxLot, normLots));

   return(NormalizeDouble(normLots, 2));
  }

//+------------------------------------------------------------------+
//| Register Newly Opened Position in State Tracker                  |
//+------------------------------------------------------------------+
void RegisterPositionState(ulong ticket, double volume, double entry, double sl)
  {
   int n = ArraySize(g_posStates);
   ArrayResize(g_posStates, n + 1);
   g_posStates[n].ticket           = ticket;
   g_posStates[n].initialVolume    = volume;
   g_posStates[n].entryPrice       = entry;
   g_posStates[n].initialRiskPrice = MathAbs(entry - sl);
   g_posStates[n].tranche1Closed   = false;
   g_posStates[n].tranche2Closed   = false;
   g_posStates[n].kalmanEstimate   = entry;
   g_posStates[n].kalmanErrorCov   = 1.0;
  }

//+------------------------------------------------------------------+
//| Manage Open Positions: 180s Time-Stop, 3-Tranche & Kalman Trail  |
//+------------------------------------------------------------------+
void ManageOpenPositions()
  {
   m_symbol.RefreshRates();

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

      ulong  ticket  = m_position.Ticket();
      long   posType = m_position.PositionType();
      double openPrc = m_position.PriceOpen();
      double currSL  = m_position.StopLoss();
      double currTP  = m_position.TakeProfit();
      double currVol = m_position.Volume();
      double currPrc = (posType == POSITION_TYPE_BUY) ? m_symbol.Bid() : m_symbol.Ask();
      int    elapsed = (int)(TimeCurrent() - m_position.Time());

      int idx = -1;
      for(int k = 0; k < ArraySize(g_posStates); k++)
        {
         if(g_posStates[k].ticket == ticket)
           {
            idx = k;
            break;
           }
        }

      if(idx < 0)
        {
         double fallbackRisk = (currSL > 0.0) ? MathAbs(openPrc - currSL) : GetPipSize() * 4.0;
         RegisterPositionState(ticket, currVol, openPrc, openPrc - fallbackRisk);
         idx = ArraySize(g_posStates) - 1;
        }

      double riskDist = g_posStates[idx].initialRiskPrice;
      if(riskDist <= 0.0)
         continue;

      double profitDist = (posType == POSITION_TYPE_BUY) ? (currPrc - openPrc) : (openPrc - currPrc);
      double currentRR  = profitDist / riskDist;

      // 1. 180-Second Velocity Time-Stop
      if(!g_posStates[idx].tranche1Closed && elapsed >= InpVelocityTimeStopSec && currentRR < 0.50)
        {
         Print("QLM-7 Velocity Time-Stop triggered on ticket #", ticket, " at R=", DoubleToString(currentRR, 2));
         m_trade.PositionClose(ticket);
         continue;
        }

      double lotStep = m_symbol.LotsStep();
      double minLot  = m_symbol.LotsMin();

      // 2. Tranche 1: Close 50% at +1.2R & Move SL to Hard Breakeven + 0.2 Pips
      if(!g_posStates[idx].tranche1Closed && currentRR >= InpTranche1RR)
        {
         double closeVol = MathFloor((g_posStates[idx].initialVolume * 0.50) / lotStep) * lotStep;
         if(closeVol >= minLot && (currVol - closeVol) >= minLot)
            m_trade.PositionClosePartial(ticket, NormalizeDouble(closeVol, 2));

         double beBuffer = GetPipSize() * 0.20;
         double newSL    = (posType == POSITION_TYPE_BUY) ? (openPrc + beBuffer) : (openPrc - beBuffer);
         newSL           = m_symbol.NormalizePrice(newSL);

         if((posType == POSITION_TYPE_BUY && newSL > currSL) || (posType == POSITION_TYPE_SELL && (currSL == 0.0 || newSL < currSL)))
            m_trade.PositionModify(ticket, newSL, currTP);

         g_posStates[idx].tranche1Closed = true;
         continue;
        }

      // 3. Tranche 2: Close 30% at Session VWAP or +2.2R
      bool vwapReached = (g_sessionVWAP > 0.0) &&
                         ((posType == POSITION_TYPE_BUY  && currPrc >= g_sessionVWAP && openPrc < g_sessionVWAP) ||
                          (posType == POSITION_TYPE_SELL && currPrc <= g_sessionVWAP && openPrc > g_sessionVWAP));

      if(g_posStates[idx].tranche1Closed && !g_posStates[idx].tranche2Closed && (currentRR >= InpTranche2RR || vwapReached))
        {
         double closeVol = MathFloor((g_posStates[idx].initialVolume * 0.30) / lotStep) * lotStep;
         if(closeVol >= minLot && (currVol - closeVol) >= minLot)
            m_trade.PositionClosePartial(ticket, NormalizeDouble(closeVol, 2));

         g_posStates[idx].tranche2Closed = true;
         continue;
        }

      // 4. Tranche 3: 1D Kalman Filter Trail on Final 20% Runner
      if(g_posStates[idx].tranche1Closed)
        {
         double predEst = g_posStates[idx].kalmanEstimate;
         double predCov = g_posStates[idx].kalmanErrorCov + InpKalmanProcessNoise;
         double kGain   = predCov / (predCov + InpKalmanMeasureNoise);

         g_posStates[idx].kalmanEstimate = predEst + kGain * (currPrc - predEst);
         g_posStates[idx].kalmanErrorCov = (1.0 - kGain) * predCov;

         double trailBuffer = GetPipSize() * 1.0;
         if(posType == POSITION_TYPE_BUY)
           {
            double kalmanSL = m_symbol.NormalizePrice(g_posStates[idx].kalmanEstimate - trailBuffer);
            if(kalmanSL > currSL && kalmanSL < currPrc)
               m_trade.PositionModify(ticket, kalmanSL, currTP);
           }
         else if(posType == POSITION_TYPE_SELL)
           {
            double kalmanSL = m_symbol.NormalizePrice(g_posStates[idx].kalmanEstimate + trailBuffer);
            if((currSL == 0.0 || kalmanSL < currSL) && kalmanSL > currPrc)
               m_trade.PositionModify(ticket, kalmanSL, currTP);
           }
        }
     }
  }

//+------------------------------------------------------------------+
//| Expert Tick Function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   MqlDateTime dt;
   TimeGMT(dt);
   if(dt.day_of_year != g_currentDayOfYear)
      ResetDailyHouseMoneyState();

   // Continuously refresh live telemetry for HUD and gates
   ManageOpenPositions();
   double basisZScore = GetTriangularBasisZScore();
   RefreshOrderFlowTelemetry();
   bool costGateOK    = CheckPreTradeCostGate();

   // Update Layer 1 on new M15 candle
   datetime m15Time = iTime(_Symbol, PERIOD_M15, 0);
   if(m15Time != g_lastM15BarTime)
     {
      g_lastM15BarTime = m15Time;
      g_currentHurst   = CalculateHurstExponent(PERIOD_M15, InpHurstPeriod);
      UpdateSessionVWAPAndBands();

      if(g_currentHurst < InpHurstMeanRevMax)
         g_currentRegime = REGIME_MEAN_REV;
      else if(g_currentHurst > InpHurstMomentumMin)
         g_currentRegime = REGIME_MOMENTUM;
      else
         g_currentRegime = REGIME_RANDOM_WALK;
     }

   // Render On-Chart Telemetry HUD
   UpdateHUDPanel();

   // Enforce Daily Drawdown, Cooldown, Session & Regime Vetoes
   double currentDrawdownPct = ((g_dayStartEquity - m_account.Equity()) / g_dayStartEquity) * 100.0;
   if(currentDrawdownPct >= InpDailyMaxDrawdownPct || TimeCurrent() < g_cooldownUntil)
      return;
   if(dt.hour < InpLondonStartHourUTC || dt.hour >= InpNYCloseHourUTC)
      return;
   if(g_currentRegime == REGIME_RANDOM_WALK)
      return;

   // Only allow one active QLM-7 position per symbol
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      if(m_position.SelectByIndex(i) && m_position.Symbol() == _Symbol && m_position.Magic() == InpMagicNumber)
         return;
     }

   // Evaluate Layer 3 Liquidity Sweep on M1 Bar
   datetime m1Time = iTime(_Symbol, PERIOD_M1, 0);
   if(m1Time == g_lastM1BarTime)
      return;

   int    signalDir    = 0;
   double sweepExtreme = 0.0;
   if(!DetectLiquiditySweepAndDisplacement(signalDir, sweepExtreme))
      return;

   m_symbol.RefreshRates();
   double midPrice = (m_symbol.Bid() + m_symbol.Ask()) * 0.5;

   if(g_currentRegime == REGIME_MEAN_REV)
     {
      if(signalDir > 0 && g_sessionVAL > 0.0 && midPrice > g_sessionVWAP)
         return;
      if(signalDir < 0 && g_sessionVAH > 0.0 && midPrice < g_sessionVWAP)
         return;
     }
   else if(g_currentRegime == REGIME_MOMENTUM)
     {
      if(signalDir > 0 && midPrice < g_sessionVWAP)
         return;
      if(signalDir < 0 && midPrice > g_sessionVWAP)
         return;
      if(!CheckM15FVGConfluence(signalDir))
         return;
     }

   if(InpUseTriangularFilter)
     {
      if(signalDir > 0 && basisZScore > -InpMinBasisZScore)
         return;
      if(signalDir < 0 && basisZScore < InpMinBasisZScore)
         return;
     }

   if(!VerifyOrderFlowTrigger(signalDir) || !costGateOK)
      return;

   double atrBuf[];
   ArraySetAsSeries(atrBuf, true);
   if(CopyBuffer(g_atrHandleM1, 0, 0, 1, atrBuf) < 1)
      return;
   double m1ATR = atrBuf[0];

   double entryPrice = (signalDir > 0) ? m_symbol.Ask() : m_symbol.Bid();
   double slPrice    = (signalDir > 0) ? (sweepExtreme - 0.35 * m1ATR) : (sweepExtreme + 0.35 * m1ATR);
   slPrice           = m_symbol.NormalizePrice(slPrice);

   double stopDist   = MathAbs(entryPrice - slPrice);
   double minStops   = m_symbol.StopsLevel() * m_symbol.Point();
   if(stopDist <= minStops)
      return;

   double lots = CalculateHouseMoneyLots(stopDist);
   if(lots < m_symbol.LotsMin())
      return;

   double tpPrice = (signalDir > 0) ? (entryPrice + 4.5 * stopDist) : (entryPrice - 4.5 * stopDist);
   tpPrice        = m_symbol.NormalizePrice(tpPrice);

   bool sent = false;
   if(signalDir > 0)
      sent = m_trade.Buy(lots, _Symbol, entryPrice, slPrice, tpPrice, "QLM7_Long_Regime" + IntegerToString(g_currentRegime));
   else
      sent = m_trade.Sell(lots, _Symbol, entryPrice, slPrice, tpPrice, "QLM7_Short_Regime" + IntegerToString(g_currentRegime));

   if(sent && m_trade.ResultRetcode() == TRADE_RETCODE_DONE)
     {
      g_lastM1BarTime = m1Time;
      RegisterPositionState(m_trade.ResultOrder(), lots, entryPrice, slPrice);
      UpdateHUDPanel();
     }
  }

//+------------------------------------------------------------------+
//| TradeTransaction: Asymmetric 50/50 House-Money Profit Accounting |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest &request,
                        const MqlTradeResult &result)
  {
   if(trans.type != TRADE_TRANSACTION_DEAL_ADD)
      return;

   if(!HistoryDealSelect(trans.deal))
      return;

   long   dealMagic = HistoryDealGetInteger(trans.deal, DEAL_MAGIC);
   string dealSym   = HistoryDealGetString(trans.deal, DEAL_SYMBOL);
   long   dealEntry = HistoryDealGetInteger(trans.deal, DEAL_ENTRY);

   if(dealMagic != (long)InpMagicNumber || dealSym != _Symbol)
      return;

   if(dealEntry != DEAL_ENTRY_OUT && dealEntry != DEAL_ENTRY_INOUT)
      return;

   double netPnL = HistoryDealGetDouble(trans.deal, DEAL_PROFIT)
                 + HistoryDealGetDouble(trans.deal, DEAL_COMMISSION)
                 + HistoryDealGetDouble(trans.deal, DEAL_SWAP);

   if(netPnL > 0.0)
     {
      g_consecutiveLosses = 0;
      double lockedPart   = netPnL * InpProfitLockRatio;
      double scaledPart   = netPnL * (1.0 - InpProfitLockRatio);

      g_lockedProfitFloor   += lockedPart;
      g_activeScalingBuffer += scaledPart;
     }
   else if(netPnL < 0.0)
     {
      g_consecutiveLosses++;
      g_activeScalingBuffer = MathMax(0.0, g_activeScalingBuffer + netPnL);

      if(g_consecutiveLosses >= 3)
         g_cooldownUntil = TimeCurrent() + InpCooldownMinutes * 60;
     }

   UpdateHUDPanel();
  }
//+------------------------------------------------------------------+
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply