//+------------------------------------------------------------------+
//|  MicroStructure_Foundation.mqh                                   |
//|  Market Microstructure in MQL5: Robust Foundation — Part 1 of 12 |
//|                                                                  |
//|  PURPOSE                                                         |
//|  Provides the shared numerical foundation for all subsequent     |
//|  articles in the series. Every function here is designed to      |
//|  fail safely rather than silently when given bad data.           |
//|                                                                  |
//|  USAGE                                                           |
//|  Place this file in:                                             |
//|    MQL5\Indicators\MicroStructure\Includes\                      |
//|  Reference from your indicator or EA:                            |
//|    #include "Includes\MicroStructure_Foundation.mqh"             |
//|                                                                  |
//|  DEPENDS ON  : nothing                                           |
//|  REQUIRED BY : Parts 2 through 12                                |
//+------------------------------------------------------------------+
#property copyright "Max Brown"
#property link      ""
#property version   "1.00"

#ifndef MICROSTRUCTURE_FOUNDATION_MQH
#define MICROSTRUCTURE_FOUNDATION_MQH

//+------------------------------------------------------------------+
//| CONSTANTS                                                        |
//+------------------------------------------------------------------+
#define DBL_MIN_POSITIVE          1e-20                   // Smallest value treated as non-zero in division and log guards
#define MS_DBL_EPSILON            1e-10                   // General floating-point comparison tolerance
#define MATH_PI                   3.14159265358979323846  // Pi to full double precision for FFT and trigonometric use
#define MIN_SAMPLE_SIZE           8                       // Minimum bars required before any statistical calculation proceeds
#define MAX_SAMPLE_SIZE           5000                    // Maximum bars fetched in a single CopyClose call
#define MAX_AGGREGATION_LEVEL     10                      // Maximum scale levels used in aggregated variance estimators
#define NUMERICAL_STABILITY_BOUND 1e100                   // Any result exceeding this is treated as a numerical failure
#define GMT_OFFSET_HOURS          2                       // Broker GMT offset; adjust if your broker runs on a different zone

//+------------------------------------------------------------------+
//| STRUCTURES                                                       |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Primary result container for fractal and long-memory analysis.   |
//| Confidence fields support weighted blending of estimators.       |
//| computation_status: 0 = success, non-zero = specific failure.    |
//+------------------------------------------------------------------+

struct RobustFractalAnalysis
  {
   double            hurst_exponent;         // Weighted-average Hurst exponent across estimators
   double            hurst_confidence;       // Confidence weight for the Hurst estimate (0–1)
   double            fractal_dimension;      // Higuchi fractal dimension blended with Hurst fallback
   double            dimension_confidence;   // Confidence weight for the fractal dimension estimate
   double            arfima_d;               // GPH fractional differencing parameter d
   double            arfima_confidence;      // R² of the GPH log-periodogram regression (0–1)
   double            multifractal_width;     // Spectrum width from MFDFA-style tau(q) regression
   double            volatility_persistence; // Normalised persistence: 2 * |H - 0.5|
   double            microstructure_noise;   // Enhanced noise estimate from range, body, and close-open
   double            seasonality_strength;   // Intraday seasonality ratio (edge vs midday variance)
   double            volatility_confidence;  // Overall confidence in volatility estimates (0–1)
   int               computation_status;     // 0 = success; non-zero codes indicate failure mode
   string            validation_message;     // Human-readable description of any failure condition
  };

//+------------------------------------------------------------------+
//| Backward-compatible alias for older call sites.                  |
//| Use RobustFractalAnalysis in all new code.                       |
//+------------------------------------------------------------------+
struct FractalAnalysis
  {
   double            hurst_exponent;
   double            fractal_dimension;
   double            arfima_d;
   double            local_whittle_h;
   double            multifractal_width;
   double            stability_alpha;
   double            volatility_persistence;
   double            microstructure_noise;
   double            seasonality_strength;
  };

//+------------------------------------------------------------------+
//| Container for realized and modelled volatility estimates.        |
//+------------------------------------------------------------------+
struct VolatilityAnalysis
  {
   double            realized_vol;     // Square root of sum of squared log-returns
   double            fractional_vol;   // ARFIMA-weighted realized volatility
   double            figarch_vol;      // Power-law decay weighted conditional volatility
   double            duration_vol;     // Annualized volatility scaled by timeframe seconds
   double            clustering_index; // Lag-1 autocorrelation of squared returns
   double            asymmetry;        // Signed skewness of the return distribution
   double            leverage_effect;  // Negative-vs-positive return volatility asymmetry
   double            jump_intensity;   // Proportion of returns exceeding three standard deviations
  };

//+------------------------------------------------------------------+
//| Composite order flow signal combining directional pressure,      |
//| momentum, exhaustion, and smart-money contribution.              |
//| direction_numeric enables downstream arithmetic without          |
//| string comparisons.                                              |
//+------------------------------------------------------------------+
struct OrderFlowSignal
  {
   double            strength;          // Volume-weighted directional imbalance (-1 to +1)
   double            momentum;          // Short-window minus long-window flow differential
   double            exhaustion;        // Current absolute flow relative to recent peak
   double            smart_money;       // Large-bar directional bias (signed volume ratio)
   double            convergence;       // Mean signal score across sub-indicators
   double            confidence;        // Signal reliability weight (0–1)
   string            direction;         // "BULLISH", "BEARISH", or "NEUTRAL"
   double            direction_numeric; // +1.0, -1.0, or 0.0 for use in calculations
  };

//+------------------------------------------------------------------+
//| Time-aware composite signal layering session context over flow.  |
//| Embeds OrderFlowSignal and adds regime, liquidity, and event     |
//| adjustments for session-adaptive position sizing.                |
//+------------------------------------------------------------------+
struct TimeAwareSignal
  {
   OrderFlowSignal   flow_signal;                // Underlying order flow analysis
   double            fractal_synergy;            // Hurst-regime aligned with flow direction
   double            time_regime_multiplier;     // Session weight: 1.5 at NY open, 0.5 off-peak
   double            session_liquidity_factor;   // Spread-based liquidity scaling (0.3–1.2)
   double            pre_event_boost;            // Additive boost near scheduled events
   double            final_signal;               // Combined directional signal (-2 to +2)
   double            time_adjusted_confidence;   // Confidence scaled by regime and liquidity
   string            time_context;               // Human-readable session label
   datetime          signal_time;                // TimeCurrent() at signal generation
   double            time_until_event;           // Minutes until next scheduled event
   double            max_position_size;          // Suggested size fraction (0–1)
   double            stop_loss_multiplier;       // ATR stop multiplier based on confidence
  };

//+------------------------------------------------------------------+
//| Output of MakeTradingDecision: action, sizing, and levels.       |
//+------------------------------------------------------------------+
struct TradingDecision
  {
   int               action;           // +1 = buy, -1 = sell, 0 = no trade
   double            size;             // Position size fraction (0–1)
   double            stop_loss_pips;   // Stop loss in points
   double            take_profit_pips; // Take profit in points
   double            confidence;       // Decision confidence (0–1)
   string            rationale;        // Textual description of decision basis
  };

//+------------------------------------------------------------------+
//| Pullback quality classification based on Fibonacci retracement   |
//| depth relative to the most recent swing range.                   |
//+------------------------------------------------------------------+
enum PULLBACK_QUALITY
  {
   PBQ_NO_TREND = 0, // No identifiable trend; classification not meaningful
   PBQ_STRONG   = 1, // Retrace below 23.6%: trend intact, shallow pullback
   PBQ_HEALTHY  = 2, // Retrace 23.6%–38.2%: normal corrective structure
   PBQ_WARNING  = 3, // Retrace 38.2%–61.8%: deeper correction, caution advised
   PBQ_DEEP     = 4, // Retrace above 61.8%: trend at risk of reversal
   PBQ_BROKEN   = 5  // Retrace beyond 100%: trend structure broken
  };

//+------------------------------------------------------------------+
//| SAFE MATH FUNCTIONS                                              |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| SafeDivide: two-gate guard.                                      |
//| Gate 1 checks denominator before division.                       |
//| Gate 2 checks result for overflow or NaN after division.         |
//+------------------------------------------------------------------+
double SafeDivide(double numerator, double denominator,
                  double fallback = 0.0)
  {
   if(!MathIsValidNumber(denominator) ||
      MathAbs(denominator) < DBL_MIN_POSITIVE)
      return fallback;
   double result = numerator / denominator;
   if(!MathIsValidNumber(result) ||
      MathAbs(result) > NUMERICAL_STABILITY_BOUND)
      return fallback;
   return result;
  }

//+------------------------------------------------------------------+
//| SafeLog: guards against non-positive input.                      |
//| Fallback of -20.0 signals "invalid point" to downstream          |
//| regression slope calculations without distorting the fit.        |
//+------------------------------------------------------------------+
double SafeLog(double x, double fallback = -20.0)
  {
   if(!MathIsValidNumber(x) || x <= 0)
      return fallback;
   double result = MathLog(x);
   if(!MathIsValidNumber(result))
      return fallback;
   return result;
  }

//+------------------------------------------------------------------+
//| SafeSqrt: guards against negative radicand.                      |
//+------------------------------------------------------------------+
double SafeSqrt(double x, double fallback = 0.0)
  {
   if(!MathIsValidNumber(x) || x < 0)
      return fallback;
   double result = MathSqrt(x);
   if(!MathIsValidNumber(result))
      return fallback;
   return result;
  }

//+------------------------------------------------------------------+
//| SafeExp: soft-clips extreme inputs rather than hard-failing.     |
//| Large inputs are not always bad data; they can be valid          |
//| log-return values. Clamping preserves scale while avoiding NaN.  |
//+------------------------------------------------------------------+
double SafeExp(double x, double fallback = 0.0)
  {
   if(!MathIsValidNumber(x))
      return fallback;
   if(x >  100)
      return 1e100;
   if(x < -100)
      return 1e-100;
   double result = MathExp(x);
   if(!MathIsValidNumber(result))
      return fallback;
   return result;
  }

//+------------------------------------------------------------------+
//| SafeTanh: soft-bounding operator used throughout order flow.     |
//| Compresses signals smoothly to [-1, +1] without hard clipping.   |
//| DBL_MIN_POSITIVE in the denominator prevents degenerate zero.    |
//+------------------------------------------------------------------+
double SafeTanh(double x)
  {
   if(!MathIsValidNumber(x))
      return 0.0;
   if(x >  10.0)
      return  1.0;
   if(x < -10.0)
      return -1.0;
   double ex  = SafeExp(x);
   double emx = SafeExp(-x);
   return (ex - emx) / (ex + emx + DBL_MIN_POSITIVE);
  }

//+------------------------------------------------------------------+
//| MathSign: returns +1.0, -1.0, or 0.0 for the sign of x.          |
//+------------------------------------------------------------------+
double MathSign(double x)
  {
   if(x > 0)
      return  1.0;
   if(x < 0)
      return -1.0;
   return 0.0;
  }

//+------------------------------------------------------------------+
//| DATA VALIDATION AND ACCESS                                       |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| ValidateSymbolV2: three-attempt validation with spread check.    |
//| Returns true immediately for the current chart symbol.           |
//| Spread above 10000 points indicates a data feed problem.         |
//+------------------------------------------------------------------+
bool ValidateSymbolV2(const string symbol)
  {
   if(symbol == NULL || symbol == "")
      return false;
   if(symbol == Symbol())
      return true;
   for(int i = 0; i < 3; i++)
     {
      double bid   = SymbolInfoDouble(symbol, SYMBOL_BID);
      double ask   = SymbolInfoDouble(symbol, SYMBOL_ASK);
      double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
      if(bid > 0 && ask > 0 && ask > bid && point > 0)
        {
         double spread = (ask - bid) / point;
         if(spread < 10000)
            return true;
        }
      Sleep(10);
     }
   return false;
  }

//+------------------------------------------------------------------+
//| Backward-compatible alias for ValidateSymbolV2.                  |
//+------------------------------------------------------------------+
bool ValidateSymbol(const string symbol) { return ValidateSymbolV2(symbol); }

//+------------------------------------------------------------------+
//| SafeCopyClose: validated data fetch with per-value zero check.   |
//| Caps request at MAX_SAMPLE_SIZE to prevent memory overrun.       |
//| Returns 0 if any copied bar has a non-positive close price.      |
//+------------------------------------------------------------------+
int SafeCopyClose(const string symbol, const int tf,
                  const int start, const int count,
                  double &out[])
  {
   if(!ValidateSymbolV2(symbol) || count <= 0)
      return 0;
   int safe_count = MathMin(count, MAX_SAMPLE_SIZE);
   ArraySetAsSeries(out, true);
   int copied = CopyClose(symbol, (ENUM_TIMEFRAMES)tf, start, safe_count, out);
   if(copied <= 0)
      return 0;
   for(int i = 0; i < copied; i++)
      if(out[i] <= 0)
         return 0;
   return copied;
  }

//+------------------------------------------------------------------+
//| Backward-compatible alias for SafeCopyClose.                     |
//+------------------------------------------------------------------+
int SafeCopyClosev2(const string symbol, const int tf,
                    const int start, const int count,
                    double &out[])
  {
   return SafeCopyClose(symbol, tf, start, count, out);
  }

//+------------------------------------------------------------------+
//| STATISTICAL PRIMITIVES                                           |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| mean_var: two-pass variance estimation.                          |
//| Two passes avoid numerical instability when the mean is large    |
//| relative to the variance, as is typical with price data.         |
//+------------------------------------------------------------------+
void mean_var(const double &arr[], const int n,
              double &mean, double &var)
  {
   mean = 0;
   var = 0;
   if(n <= 0)
      return;
   for(int i = 0; i < n; i++)
      mean += arr[i];
   mean /= n;
   for(int i = 0; i < n; i++)
      var += (arr[i] - mean) * (arr[i] - mean);
   var /= n;
  }

//+------------------------------------------------------------------+
//| robust_mean_var: trimmed mean and variance with higher moments.  |
//| Removes the most extreme 10% of values symmetrically before      |
//| computing mean, variance, skewness, and excess kurtosis.         |
//| Falls back to mean_var if the trimmed sample is too small.       |
//+------------------------------------------------------------------+
void robust_mean_var(const double &arr[], const int n,
                     double &mean, double &var,
                     double &skew, double &kurt)
  {
   mean = 0;
   var = 0;
   skew = 0;
   kurt = 0;
   if(n < 4)
      return;
   double sorted[];
   ArrayResize(sorted, n);
   ArrayCopy(sorted, arr, 0, 0, n);
   ArraySort(sorted);
   int trim = MathMax(1, (int)(n * 0.10));
   int used = n - 2 * trim;
   if(used < 4)
     {
      mean_var(arr, n, mean, var);
      return;
     }
   for(int i = trim; i < n - trim; i++)
      mean += sorted[i];
   mean /= used;
   for(int i = trim; i < n - trim; i++)
      var += MathPow(sorted[i] - mean, 2);
   var /= used;
   double sd = SafeSqrt(var);
   if(sd > DBL_MIN_POSITIVE)
     {
      for(int i = trim; i < n - trim; i++)
        {
         double z = (sorted[i] - mean) / sd;
         skew += MathPow(z, 3);
         kurt += MathPow(z, 4);
        }
      skew /= used;
      kurt  = kurt / used - 3.0;
     }
  }

//+------------------------------------------------------------------+
//| LinearRegressionSlope: ordinary least squares slope estimate.    |
//| Used by Hurst estimators (Part 2), fractal dimension (Part 3),   |
//| and multifractal spectrum (Part 3). Centralising here ensures    |
//| identical arithmetic across all three. SafeDivide guards against |
//| the degenerate case where all x values are identical.            |
//+------------------------------------------------------------------+
double LinearRegressionSlope(const double &x[],
                             const double &y[], int n)
  {
   if(n < 2)
      return 0.0;
   double sx = 0, sy = 0, sxy = 0, sxx = 0;
   for(int i = 0; i < n; i++)
     {
      sx  += x[i];
      sy  += y[i];
      sxy += x[i] * y[i];
      sxx += x[i] * x[i];
     }
   double denom = n * sxx - sx * sx;
   return SafeDivide(n * sxy - sx * sy, denom);
  }

//+------------------------------------------------------------------+
//| FFT IMPLEMENTATION                                               |
//| Cooley-Tukey radix-2 in-place. Input length must be power of 2.  |
//| Reused by: Part 3 (multifractal), Part 10 (Fourier seasonality), |
//|            Part 11 (fractional Gaussian noise generation).       |
//+------------------------------------------------------------------+
void fft(const double &real_in[], const double &imag_in[],
         double &real_out[], double &imag_out[],
         int n, bool inverse)
  {
   ArrayResize(real_out, n);
   ArrayResize(imag_out, n);
   ArrayCopy(real_out, real_in,  0, 0, n);
   ArrayCopy(imag_out, imag_in, 0, 0, n);
//--- Bit-reversal permutation
   int j = 0;
   for(int i = 1; i < n; i++)
     {
      int bit = n >> 1;
      for(; (j & bit) != 0; bit >>= 1)
         j ^= bit;
      j ^= bit;
      if(i < j)
        {
         double tr = real_out[i];
         real_out[i] = real_out[j];
         real_out[j] = tr;
         double ti = imag_out[i];
         imag_out[i] = imag_out[j];
         imag_out[j] = ti;
        }
     }
//--- Cooley-Tukey butterfly
   for(int len = 2; len <= n; len <<= 1)
     {
      double ang = 2.0 * MATH_PI / len * (inverse ? -1 : 1);
      double wr  = MathCos(ang);
      double wi  = MathSin(ang);
      for(int i = 0; i < n; i += len)
        {
         double cr = 1.0, ci = 0.0;
         for(int k = 0; k < len / 2; k++)
           {
            double ur  = real_out[i + k];
            double ui  = imag_out[i + k];
            double vr  = real_out[i + k + len / 2];
            double vi  = imag_out[i + k + len / 2];
            double pvr = vr * cr - vi * ci;
            double pvi = vr * ci + vi * cr;
            real_out[i + k]            = ur + pvr;
            imag_out[i + k]            = ui + pvi;
            real_out[i + k + len / 2]  = ur - pvr;
            imag_out[i + k + len / 2]  = ui - pvi;
            double ncr = cr * wr - ci * wi;
            ci = cr * wi + ci * wr;
            cr = ncr;
           }
        }
     }
//--- Normalise output for inverse transform
   if(inverse)
      for(int i = 0; i < n; i++)
        {
         real_out[i] /= n;
         imag_out[i] /= n;
        }
  }

//+------------------------------------------------------------------+
//| PeriodSeconds: converts a timeframe constant to seconds.         |
//| Used by volatility scaling functions in Part 4.                  |
//+------------------------------------------------------------------+
int PeriodSeconds(int tf)
  {
   switch(tf)
     {
      case PERIOD_M1:
         return 60;
      case PERIOD_M5:
         return 300;
      case PERIOD_M15:
         return 900;
      case PERIOD_M30:
         return 1800;
      case PERIOD_H1:
         return 3600;
      case PERIOD_H4:
         return 14400;
      case PERIOD_D1:
         return 86400;
      case PERIOD_W1:
         return 604800;
      default:
         return 86400;
     }
  }

//+---------------------------------------------------------------------+
//  PART 2 ADDITIONS — MicroStructure_Foundation.mqh                    |
//  Market Microstructure in MQL5: Measuring Long Memory (Part 2)       |
//                                                                      |
//                                                                      |
// Functions added in this part:                                        |
//  HurstExponentRS()      — classical rescaled-range estimator         |
//  AdvancedHurstExponent()— aggregated-variance and absolute-          |
//                           moments estimators (method-switched)       |
//  HurstExponentRobust()  — confidence-weighted blend of all three     |
//  PopulateHurstAnalysis()— fills RobustFractalAnalysis from one call  |
//                                                                      |
// Part 2 constants added:                                              |
//  HURST_MIN_BARS         — minimum post-reset bars before H is valid  |
//  HURST_CONF_THRESHOLD   — minimum per-estimator confidence to blend  |
//  HURST_RS_MAX_SCALES    — log-log regression point cap, R/S path     |
//  HURST_AV_MAX_SCALES    — log-log regression point cap, AggVar path  |
//  HURST_AM_MAX_SCALES    — log-log regression point cap, AbsMom path  |
//+---------------------------------------------------------------------+

// ==================================================================
//  Part 2 — Long Memory: Hurst Exponent Estimators
// ==================================================================

//--- Part 2 constants
#define HURST_MIN_BARS        40    // Minimum session bars before any estimator is trusted
#define HURST_CONF_THRESHOLD  0.1   // Estimators below this confidence are excluded from blend
#define HURST_RS_MAX_SCALES   10    // Maximum log-log regression points for R/S estimator
#define HURST_AV_MAX_SCALES   10    // Maximum log-log regression points for AggVar estimator
#define HURST_AM_MAX_SCALES   10    // Maximum log-log regression points for AbsMom estimator

//+------------------------------------------------------------------+
//| HurstExponentRS: classical rescaled-range (R/S) estimator.       |
//| Computes log(E[R/S]) ~ H * log(n) across geometrically spaced    |
//| window sizes. Each scale is averaged over non-overlapping        |
//| segments so that the estimate is less sensitive to a single      |
//| anomalous window.                                                |
//|                                                                  |
//| Parameters                                                       |
//|   returns[]   — log-return array, oldest first                   |
//|   n           — number of valid returns                          |
//|   confidence  — output: k / HURST_RS_MAX_SCALES where k = valid  |
//|                 regression points. Reflects data sufficiency.    |
//|                                                                  |
//| Returns H in [0.01, 0.99]. Returns 0.5 with confidence = 0.0     |
//| when fewer than 3 valid regression points are obtained.          |
//+------------------------------------------------------------------+
double HurstExponentRS(const double &returns[], int n, double &confidence)
  {
   confidence = 0.0;
//--- Need at least one complete segment at minimum scale 8
   if(n < 30)
      return 0.5;
   int    m_max = (int)MathMax(10, n / 4);
   double log_n[10], log_rs[10];
   int    k     = 0;
   int    scale = 8;
   while(scale <= m_max && k < HURST_RS_MAX_SCALES)
     {
      int segs = n / scale;
      if(segs < 2)
        {
         scale *= 2;
         continue;
        }
      double rs_sum = 0.0;
      int    v      = 0;
      for(int seg = 0; seg < segs; seg++)
        {
         int    st  = seg * scale;
         double s1  = 0.0, s2 = 0.0;
         for(int j = 0; j < scale; j++)
           {
            s1 += returns[st + j];
            s2 += returns[st + j] * returns[st + j];
           }
         double mu  = s1 / scale;
         //--- Two-pass variance: stable against catastrophic cancellation
         double var = (s2 - scale * mu * mu) / (scale - 1);
         if(var <= 0)
            continue;
         double mx  = -1e100, mn = 1e100, cum = 0.0;
         for(int j = 0; j < scale; j++)
           {
            cum += returns[st + j] - mu;
            if(cum > mx)
               mx = cum;
            if(cum < mn)
               mn = cum;
           }
         double rng = mx - mn;
         if(rng > 0)
           {
            rs_sum += SafeLog(rng / SafeSqrt(var));
            v++;
           }
        }
      if(v > 0)
        {
         log_n [k] = SafeLog((double)scale);
         log_rs[k] = rs_sum / v;
         k++;
        }
      scale *= 2;
     }
   if(k < 3)
      return 0.5;
   double h = LinearRegressionSlope(log_n, log_rs, k);
   confidence = (double)k / HURST_RS_MAX_SCALES;
   return MathMax(0.01, MathMin(0.99, h));
  }

//+------------------------------------------------------------------+
//| AdvancedHurstExponent: aggregated variance (method = 0) or       |
//| absolute moments (method = 1) estimator.                         |
//|                                                                  |
//| AggVar:   variance of non-overlapping block averages scales as   |
//|           sigma^2(m) ~ m^(2H-2), so H = 1 + slope/2.             |
//|                                                                  |
//| AbsMom:   mean absolute block average scales as                  |
//|           E[|x_bar(m)|] ~ m^(H-1), so H = 1 + slope.             |
//|                                                                  |
//| Parameters                                                       |
//|   returns[]  — log-return array, oldest first                    |
//|   n          — number of valid returns                           |
//|   method     — 0 = AggVar, 1 = AbsMom                            |
//|   confidence — output: k / max_scales                            |
//|                                                                  |
//| Returns H in [0.01, 0.99]; 0.5 with confidence = 0.0 on failure. |
//+------------------------------------------------------------------+
double AdvancedHurstExponent(const double &returns[], int n,
                             int method, double &confidence)
  {
   confidence = 0.0;
//--- AggVar needs at least 10 returns per aggregation level
//--- AbsMom needs at least 15 per level — tighter requirement
   int min_per_seg = (method == 0) ? 10 : 15;
   if(n < min_per_seg * 2)
      return 0.5;
   int    max_scales = (method == 0) ? HURST_AV_MAX_SCALES
                       : HURST_AM_MAX_SCALES;
   int    max_agg    = (int)MathMin(max_scales + 1, n / min_per_seg);
   double log_m[10], log_stat[10];
   int    k = 0;
   for(int m = 2; m <= max_agg && k < max_scales; m++)
     {
      int segs = n / m;
      if(segs < 2)
         continue;
      double stat = 0.0;
      for(int seg = 0; seg < segs; seg++)
        {
         int    st   = seg * m;
         double sum  = 0.0;
         for(int j = 0; j < m; j++)
            sum += returns[st + j];
         double xbar = sum / m;
         if(method == 0)
            stat += xbar * xbar;      // AggVar: squared block mean
         else
            stat += MathAbs(xbar);    // AbsMom: absolute block mean
        }
      stat /= segs;
      if(stat <= 0)
         continue;
      log_m   [k] = SafeLog((double)m);
      log_stat[k] = SafeLog(stat);
      k++;
     }
   if(k < 3)
      return 0.5;
   double beta = LinearRegressionSlope(log_m, log_stat, k);
   double h    = (method == 0) ? 1.0 + beta / 2.0   // AggVar
                 : 1.0 + beta;        // AbsMom
   confidence  = (double)k / max_scales;
   return MathMax(0.01, MathMin(0.99, h));
  }

//+------------------------------------------------------------------+
//| HurstExponentRobust: confidence-weighted blend of three          |
//| independent Hurst estimators.                                    |
//|                                                                  |
//| Three estimators are computed independently:                     |
//|   (1) Classical R/S (Hurst 1951)                                 |
//|   (2) Aggregated Variance                                        |
//|   (3) Absolute Moments                                           |
//|                                                                  |
//| Each estimator earns a confidence proportional to the number of  |
//| valid log-log regression points it produced relative to the      |
//| maximum possible. Estimators below HURST_CONF_THRESHOLD are      |
//| excluded from the blend. The final confidence is the sum of all  |
//| participating weights normalised to [0, 1].                      |
//|                                                                  |
//| Session reset requirement: the log-return array passed here      |
//| must contain only bars from the current trading session. Mixing  |
//| pre-open and post-open bars across the session boundary causes   |
//| a structural break in the return distribution that artificially  |
//| compresses H toward 0.5. Call SafeCopyClose with start = 0 and   |
//| count limited to bars elapsed since session open.                |
//|                                                                  |
//| Parameters                                                       |
//|   symbol     — instrument to analyse                             |
//|   tf         — timeframe                                         |
//|   period     — lookback bars (recommend 90 for M1 intraday)      |
//|   confidence — output: blended confidence weight (0 to 1)        |
//|                                                                  |
//| Returns H in [0.01, 0.99]. Returns 0.5 (random walk boundary)    |
//| with confidence = 0.0 when HURST_MIN_BARS is not satisfied.      |
//+------------------------------------------------------------------+
double HurstExponentRobust(const string symbol, const int tf,
                           const int period, double &confidence)
  {
   confidence = 0.0;
//--- Validate inputs before touching the price feed
   if(!ValidateSymbolV2(symbol) || period < HURST_MIN_BARS)
      return 0.5;
//--- Fetch close prices; SafeCopyClose validates each bar
   double close[];
   int    n = SafeCopyClose(symbol, tf, 0, period, close);
   if(n < HURST_MIN_BARS)
      return 0.5;
//--- Build log-return array
//--- Filter returns whose absolute value exceeds 0.1: these are
//--- almost certainly data artefacts on futures/CFD M1 feeds.
   double returns[];
   ArrayResize(returns, n - 1);
   int valid = 0;
   for(int i = 0; i < n - 1; i++)
     {
      if(close[i] <= 0 || close[i + 1] <= 0)
         continue;
      double r = SafeLog(close[i]) - SafeLog(close[i + 1]);
      if(MathAbs(r) < 0.1)
         returns[valid++] = r;
     }
   if(valid < HURST_MIN_BARS)
     {
      confidence = 0.1;
      return 0.5;
     }
   ArrayResize(returns, valid);
//--- Estimator 1: R/S
   double conf_rs, conf_av, conf_am;
   double h_rs = HurstExponentRS(returns, valid, conf_rs);
//--- Estimator 2: Aggregated Variance (method = 0)
   double h_av = AdvancedHurstExponent(returns, valid, 0, conf_av);
//--- Estimator 3: Absolute Moments (method = 1)
   double h_am = AdvancedHurstExponent(returns, valid, 1, conf_am);
//--- Confidence-weighted blend
//--- Exclude any estimator whose regression was too sparse to trust
   double weighted_sum = 0.0, total_weight = 0.0;
   if(conf_rs > HURST_CONF_THRESHOLD)
     { weighted_sum += h_rs * conf_rs; total_weight += conf_rs; }
   if(conf_av > HURST_CONF_THRESHOLD)
     { weighted_sum += h_av * conf_av; total_weight += conf_av; }
   if(conf_am > HURST_CONF_THRESHOLD)
     { weighted_sum += h_am * conf_am; total_weight += conf_am; }
   if(total_weight <= 0)
     {
      confidence = 0.1;
      return 0.5;
     }
   double h_final = weighted_sum / total_weight;
   confidence = MathMin(1.0, total_weight / 3.0);  // Normalise to [0, 1]
   h_final    = MathMax(0.01, MathMin(0.99, h_final));
   confidence = MathMax(0.0,  MathMin(1.0,  confidence));
   return h_final;
  }

//+------------------------------------------------------------------+
//| PopulateHurstAnalysis: convenience wrapper that calls            |
//| HurstExponentRobust and writes results into the shared           |
//| RobustFractalAnalysis struct.                                    |
//|                                                                  |
//| Fields written:                                                  |
//|   result.hurst_exponent         — blended H                      |
//|   result.hurst_confidence       — blended confidence             |
//|   result.volatility_persistence — 2 * |H - 0.5|, range [0, 1]    |
//|   result.computation_status     — 0 on success, 1 on failure     |
//|   result.validation_message     — diagnostic text on failure     |
//|                                                                  |
//| All other fields in result are left unchanged so that later      |
//| parts of the series can call their own Populate* functions on    |
//| the same struct without overwriting Hurst output.                |
//+------------------------------------------------------------------+
void PopulateHurstAnalysis(const string symbol, const int tf,
                           const int period,
                           RobustFractalAnalysis &result)
  {
   double confidence = 0.0;
   double h          = HurstExponentRobust(symbol, tf, period, confidence);
//--- Write Hurst fields
   result.hurst_exponent         = h;
   result.hurst_confidence       = confidence;
   result.volatility_persistence = 2.0 * MathAbs(h - 0.5);
//--- Diagnostics
   if(confidence < HURST_CONF_THRESHOLD)
     {
      result.computation_status  = 1;
      result.validation_message  =
         StringFormat("HurstExponentRobust: confidence %.3f below threshold "
                      "%.3f — insufficient session data for symbol %s tf %d",
                      confidence, HURST_CONF_THRESHOLD, symbol, tf);
     }
   else
     {
      result.computation_status = 0;
      result.validation_message = "OK";
     }
  }

// ==================================================================
//  Part 3 additions — TWO steps required before pasting this block.
//
//  STEP 1 — Add arfima_confidence to RobustFractalAnalysis.
//  Open MicroStructure_Foundation.mqh, locate the struct definition,
//  and insert the new field immediately after the existing arfima_d:
//
//    double   arfima_d;           // GPH fractional differencing parameter d
//    double   arfima_confidence;  // R² of the GPH log-periodogram regression (0–1)  <-- ADD THIS
//    double   multifractal_width; // ...
//
//  STEP 2 — Paste everything below this comment block before the
//  closing #endif of MicroStructure_Foundation.mqh.
// ==================================================================

//+------------------------------------------------------------------+
//| CONSTANTS — Part 3                                               |
//+------------------------------------------------------------------+
#define GPH_MIN_BARS        40    // Minimum returns required before GPH activates
#define GPH_BANDWIDTH_EXP   0.65  // Fourier frequency bandwidth exponent (m = N^0.65)
#define GPH_MIN_FREQ        15    // Minimum frequency points for a valid regression
#define GPH_CONF_THRESHOLD  0.05  // Minimum R² accepted as a meaningful confidence value
#define GPH_D_CONSISTENCY   0.1   // Maximum |H − (d + 0.5)| before a warning is raised

//+------------------------------------------------------------------+
//| GPHEstimator: Geweke-Porter-Hudak log-periodogram regression.    |
//| Regresses log I(ωⱼ) on log|2 sin(ωⱼ/2)|² across the first m      |
//| Fourier frequencies (m = floor(N^GPH_BANDWIDTH_EXP)).  The OLS   |
//| slope gives −d directly.  R² of the regression is returned via   |
//| the confidence output parameter.                                 |
//|                                                                  |
//| Inputs                                                           |
//|   returns[]  — pre-validated log-return array (caller fills)     |
//|   n          — number of valid elements in returns[]             |
//|   confidence — output: R² of the log-periodogram fit [0, 1]      |
//|                                                                  |
//| Returns d clamped to (−0.49, 0.49).  Returns 0.0 with            |
//| confidence = 0.0 on any validation failure.                      |
//+------------------------------------------------------------------+
double GPHEstimator(const double &returns[], int n, double &confidence)
  {
   confidence = 0.0;
//--- Minimum data guard
   if(n < GPH_MIN_BARS)
      return 0.0;
//--- Bandwidth: m = floor(N ^ GPH_BANDWIDTH_EXP), clamped
   int m = (int)MathFloor(MathPow((double)n, GPH_BANDWIDTH_EXP));
   m = (int)MathMax(GPH_MIN_FREQ, MathMin(m, n / 3));
   if(m < 5)
      return 0.0;
//--- Compute periodogram at the first m Fourier frequencies
   double log_I[];
   double log_sin_sq[];
   ArrayResize(log_I,      m);
   ArrayResize(log_sin_sq, m);
   int valid_points = 0;
   for(int j = 1; j <= m; j++)
     {
      double omega_j = 2.0 * MATH_PI * (double)j / (double)n;
      //--- DFT at frequency ωⱼ
      double re = 0.0;
      double im = 0.0;
      for(int t = 0; t < n; t++)
        {
         double angle = omega_j * (double)t;
         re += returns[t] * MathCos(angle);
         im += returns[t] * MathSin(angle);
        }
      //--- Periodogram ordinate I(ωⱼ) = (re² + im²) / (2π N)
      double I_j = SafeDivide(re * re + im * im, 2.0 * MATH_PI * (double)n);
      if(I_j <= DBL_MIN_POSITIVE)
         continue;
      //--- Regressor: log|2 sin(ωⱼ/2)|²
      double sin_half = MathSin(omega_j / 2.0);
      double sin_sq   = sin_half * sin_half;
      if(sin_sq <= DBL_MIN_POSITIVE)
         continue;
      log_I      [valid_points] = SafeLog(I_j);
      log_sin_sq [valid_points] = SafeLog(4.0 * sin_sq);
      valid_points++;
     }
   if(valid_points < 5)
      return 0.0;
//--- Resize to actual valid count
   ArrayResize(log_I,      valid_points);
   ArrayResize(log_sin_sq, valid_points);
//--- OLS regression: log I(ωⱼ) = c − d · log|2 sin(ωⱼ/2)|² + ε
//--- Slope of OLS = −d, so d = −slope
   double sum_x  = 0.0;
   double sum_y  = 0.0;
   double sum_xx = 0.0;
   double sum_xy = 0.0;
   for(int k = 0; k < valid_points; k++)
     {
      sum_x  += log_sin_sq[k];
      sum_y  += log_I[k];
      sum_xx += log_sin_sq[k] * log_sin_sq[k];
      sum_xy += log_sin_sq[k] * log_I[k];
     }
   double denom = (double)valid_points * sum_xx - sum_x * sum_x;
   if(MathAbs(denom) < DBL_MIN_POSITIVE)
      return 0.0;
   double slope     = SafeDivide((double)valid_points * sum_xy - sum_x * sum_y, denom);
   double d_hat     = -slope; // GPH: slope = -d
   double intercept = SafeDivide(sum_y - slope * sum_x, (double)valid_points);
//--- R² as confidence measure
   double y_mean = SafeDivide(sum_y, (double)valid_points);
   double ss_tot = 0.0;
   double ss_res = 0.0;
   for(int k = 0; k < valid_points; k++)
     {
      double y_hat = intercept + slope * log_sin_sq[k];
      double res   = log_I[k] - y_hat;
      ss_res += res * res;
      ss_tot += (log_I[k] - y_mean) * (log_I[k] - y_mean);
     }
   if(ss_tot > DBL_MIN_POSITIVE)
      confidence = 1.0 - SafeDivide(ss_res, ss_tot);
   confidence = MathMax(0.0, MathMin(1.0, confidence));
//--- Clamp d to the admissible ARFIMA range
   d_hat = MathMax(-0.49, MathMin(0.49, d_hat));
   return d_hat;
  }

//+------------------------------------------------------------------+
//| PopulateARFIMAAnalysis: fetches price data, computes log-returns,|
//| calls GPHEstimator(), writes results into RobustFractalAnalysis, |
//| and validates H-d consistency against the Hurst output written   |
//| by PopulateHurstAnalysis().                                      |
//|                                                                  |
//| Fields written:                                                  |
//|   result.arfima_d            — GPH fractional differencing param |
//|   result.arfima_confidence   — R² of the log-periodogram fit     |
//|   result.computation_status  — 0 on success, 2 on failure        |
//|   result.validation_message  — diagnostic text on any anomaly    |
//|                                                                  |
//| Call PopulateHurstAnalysis() first so the H-d consistency check  |
//| has a valid hurst_exponent to compare against.                   |
//+------------------------------------------------------------------+
void PopulateARFIMAAnalysis(const string symbol, const int tf,
                            const int period, RobustFractalAnalysis &result)
  {
//--- Validate symbol and minimum period
   if(!ValidateSymbolV2(symbol) || period < GPH_MIN_BARS + 1)
     {
      result.arfima_d           = 0.0;
      result.arfima_confidence  = 0.0;
      result.computation_status = 2;
      result.validation_message =
         StringFormat("PopulateARFIMAAnalysis: invalid symbol or period too small "
                      "(need >= %d, got %d)", GPH_MIN_BARS + 1, period);
      return;
     }
//--- Fetch closing prices
   double close[];
   int copied = SafeCopyClose(symbol, tf, 0, period, close);
   if(copied < GPH_MIN_BARS + 1)
     {
      result.arfima_d           = 0.0;
      result.arfima_confidence  = 0.0;
      result.computation_status = 2;
      result.validation_message =
         StringFormat("PopulateARFIMAAnalysis: SafeCopyClose returned %d bars, "
                      "need >= %d", copied, GPH_MIN_BARS + 1);
      return;
     }
//--- Build log-return array, filtering invalid prices and data artifacts
   int    n_closes    = ArraySize(close);
   double returns[];
   ArrayResize(returns, n_closes - 1);
   int valid_count = 0;
   for(int i = 0; i < n_closes - 1; i++)
     {
      if(close[i] <= 0.0 || close[i + 1] <= 0.0)
         continue;
      //--- Standard log-return: r = log(P_t+1) - log(P_t)
      double r = SafeLog(close[i + 1]) - SafeLog(close[i]);
      //--- Reject data artifacts: genuine M1 returns do not exceed ±10 %
      if(MathAbs(r) >= 0.1)
         continue;
      returns[valid_count] = r;
      valid_count++;
     }
   ArrayResize(returns, valid_count);
   if(valid_count < GPH_MIN_BARS)
     {
      result.arfima_d           = 0.0;
      result.arfima_confidence  = 0.0;
      result.computation_status = 2;
      result.validation_message =
         StringFormat("PopulateARFIMAAnalysis: only %d valid returns after filtering, "
                      "need >= %d", valid_count, GPH_MIN_BARS);
      return;
     }
//--- Estimate d via GPH log-periodogram regression
   double conf = 0.0;
   double d    = GPHEstimator(returns, valid_count, conf);
   result.arfima_d          = d;
   result.arfima_confidence = conf;
//--- H-d consistency check: theory requires H = d + 0.5
//--- Only runs when Hurst output is already populated
   if(result.hurst_confidence > HURST_CONF_THRESHOLD)
     {
      double h_from_d    = d + 0.5;
      double discrepancy = MathAbs(result.hurst_exponent - h_from_d);
      if(discrepancy > GPH_D_CONSISTENCY)
        {
         result.computation_status = 0; // success with diagnostic note
         result.validation_message =
            StringFormat("PopulateARFIMAAnalysis: H-d discrepancy %.3f exceeds "
                         "threshold %.3f — H(Hurst)=%.3f, H(GPH)=d+0.5=%.3f. "
                         "Session mixing or non-stationarity suspected.",
                         discrepancy, GPH_D_CONSISTENCY,
                         result.hurst_exponent, h_from_d);
         return;
        }
     }
//--- Confidence threshold check
   if(conf < GPH_CONF_THRESHOLD)
     {
      result.computation_status = 2;
      result.validation_message =
         StringFormat("PopulateARFIMAAnalysis: R²=%.4f below threshold %.4f — "
                      "log-periodogram regression unreliable for symbol %s tf %d",
                      conf, GPH_CONF_THRESHOLD, symbol, tf);
      return;
     }
   result.computation_status = 0;
   result.validation_message = "OK";
  }

// ==================================================================
//  End of Part 3 additions
// ==================================================================

#endif // MICROSTRUCTURE_FOUNDATION_MQH
//+------------------------------------------------------------------+
