1118 lines
47 KiB
MQL5
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
|
||
|
|
//+------------------------------------------------------------------+
|