Article-22598-Market-Micros.../MicroStructure_Foundation.mqh
2026-06-05 15:00:50 +00:00

1118 lines
47 KiB
MQL5

//+------------------------------------------------------------------+
//| 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
//+------------------------------------------------------------------+