//+------------------------------------------------------------------+ //| 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; double r = SafeLog(close[i]) - SafeLog(close[i + 1]); //--- 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 //+------------------------------------------------------------------+