//+------------------------------------------------------------------+ //| tests.mqh | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #include"bds.mqh" #include"..\..\cmath.mqh" #include"..\..\Regression\utils.mqh" #include"..\..\Regression\ols.mqh" #include"..\..\np_graphics.mqh" #include #include #include #include #include //+------------------------------------------------------------------+ //| statistical significance level calculation | //+------------------------------------------------------------------+ enum ENUM_STAT_CONFIDENCE { CONFIDENCE_99=1,//99% CONFIDENCE_95=5,//95% CONFIDENCE_90=10//90% }; //+------------------------------------------------------------------+ //| Volatility proxy specification | //+------------------------------------------------------------------+ enum ENUM_VOL_PROXY { VOL_ABSOLUTE_RETURNS,//Absolute returns VOL_SQUARED_RETURNS//Squared returns }; //+------------------------------------------------------------------+ //|Calculates the Hurst Exponent using Rescaled Range (R/S) Analysis.| //+------------------------------------------------------------------+ double hurst_exponent_rs(vector& time_series, int partitions = 20) { int N = (int)time_series.Size(); if(N<200) { Print(__FUNCTION__,": Time series too short for robust R/S analysis.\nMinimum length is 200"); return EMPTY_VALUE; } int max_chunks = 0; if(partitions<10) partitions = 10; max_chunks = partitions; vector log_space = np::logspace(log10(50),log10(floor(N/2)),ulong(max_chunks)); vector chunk_sizes = np::unique(log_space); vector rs_values,rslist,valid_sizes,chunk,y,z; double R,S; ulong num_chunks,rsl,rs; rs = 0; rs_values.Resize(0,chunk_sizes.Size()); valid_sizes.Resize(0,chunk_sizes.Size()); ulong size = 0; for(ulong j = 0; j0.0) { if(rslist.Size() Fine vs Fine -> Coarse).| //+----------------------------------------------------------------------------+ MsctResult multi_scale_correlation_test(vector& lowertf_close_prices,ulong highertf_window,ulong max_lags, double threshold_diagnostic = 0.05) { vector highertf_close_prices = np::sliceVector(lowertf_close_prices,long(highertf_window - 1),END,long(highertf_window)); vector fine_returns = fabs(np::diff(log(lowertf_close_prices),1)); vector coarse_returns = fabs(np::diff(log(highertf_close_prices),1)); vector ctf_lags = vector::Zeros(max_lags); vector ftc_lags = ctf_lags; vector c,f; for(long lag = 1; lag threshold_diagnostic) result.recommended_model = "HARCH(Asymmetric Mutli-Scale Cascade Detected)"; else result.recommended_model = "FIGARCH(Symmetric Fractionally Integrated Dynamics)"; return result; } //+----------------------------------------------------------------------------------------+ //| Plot graph of GPH Fractional integration estimates over a range of bandwiths | //+----------------------------------------------------------------------------------------+ void plot_gph_test_stats(vector& time_series, double min_alpha = 0.4, double max_alpha = 0.6, double step = 0.01) { if(time_series.Size()<30) { Print(__FUNCTION__,": Sample size is too small to compute a meaningful stats."); return; } vector xvals = np::arange(min_alpha,max_alpha+step,step); vector yvals = vector::Zeros(xvals.Size()); vector stats; for(ulong i = 0; i= 1.0) { res.p_value = 0.0; } else { int err_code = 0; double a = ((double)n - 2.0) / 2.0; double b = 0.5; res.p_value = MathCumulativeDistributionBeta(1.0 - r_sq, a, b, err_code); if(err_code) { Print(__FUNCTION__,": Beta Distribution CDF calculation error."); return LeverageCorrelationResult(); } } } res.significant_leverage_effect = (res.p_value < (double(res.confidence_level)/100.0)) && (res.correlation < 0.0); return res; } //+------------------------------------------------------------------+ //|Realized semivariance test results struct | //+------------------------------------------------------------------+ struct RealizedSemivarianceResult { int window; int n_blocks; double mean_RS_minus; double mean_RS_plus; double mean_diff; double t_stat; double p_value; bool significant_asymmetry; ENUM_STAT_CONFIDENCE confidence_level; RealizedSemivarianceResult(void) { window = n_blocks = -1; mean_RS_minus = mean_RS_plus = mean_diff = t_stat = p_value = EMPTY_VALUE; significant_asymmetry = false; confidence_level = CONFIDENCE_95; } RealizedSemivarianceResult(RealizedSemivarianceResult& other) { window = other.window; n_blocks = other.n_blocks; mean_RS_minus = other.mean_RS_minus; mean_RS_plus = other.mean_RS_plus; mean_diff = other.mean_diff; t_stat = other.t_stat; p_value = other.p_value; significant_asymmetry = other.significant_asymmetry; confidence_level = other.confidence_level; } void operator=(RealizedSemivarianceResult& other) { window = other.window; n_blocks = other.n_blocks; mean_RS_minus = other.mean_RS_minus; mean_RS_plus = other.mean_RS_plus; mean_diff = other.mean_diff; t_stat = other.t_stat; p_value = other.p_value; significant_asymmetry = other.significant_asymmetry; confidence_level = other.confidence_level; } }; //+------------------------------------------------------------------+ //| Volatility runs test results container | //+------------------------------------------------------------------+ struct VolatilityRunsResult { matrix contingency_table; double p_high_vol_prev_down; double p_high_vol_prev_up; double chi2_stat; double p_value; ENUM_STAT_CONFIDENCE confidence_level; bool significant_asymmetry; VolatilityRunsResult(void) { contingency_table = matrix::Zeros(2,2); p_high_vol_prev_down = p_high_vol_prev_up = chi2_stat = p_value = EMPTY_VALUE; significant_asymmetry = false; confidence_level = CONFIDENCE_95; } VolatilityRunsResult(VolatilityRunsResult& other) { contingency_table = other.contingency_table; p_high_vol_prev_down = other.p_high_vol_prev_down; p_high_vol_prev_up = other.p_high_vol_prev_up; chi2_stat = other.chi2_stat; p_value = other.p_value; significant_asymmetry = other.significant_asymmetry; confidence_level = other.confidence_level; } void operator=(VolatilityRunsResult& other) { contingency_table = other.contingency_table; p_high_vol_prev_down = other.p_high_vol_prev_down; p_high_vol_prev_up = other.p_high_vol_prev_up; chi2_stat = other.chi2_stat; p_value = other.p_value; significant_asymmetry = other.significant_asymmetry; confidence_level = other.confidence_level; } }; //+------------------------------------------------------------------+ //| Realized Semivariance Asymmetry Test | //+------------------------------------------------------------------+ RealizedSemivarianceResult RealizedSemivarianceAsymmetry(const vector &returns, int window = 22, ENUM_STAT_CONFIDENCE confidence_level = CONFIDENCE_95) { RealizedSemivarianceResult res; res.window = window; res.confidence_level = confidence_level; ulong total_elements = returns.Size(); res.n_blocks = (int)(total_elements / (ulong)window); if(res.n_blocks <= 1) { Print("Error: Not enough data blocks for t-test."); ZeroMemory(res); return res; } vector rs_neg, rs_pos, diff; rs_neg.Init(res.n_blocks); rs_pos.Init(res.n_blocks); diff.Init(res.n_blocks); // Loop through non-overlapping blocks for(int i = 0; i < res.n_blocks; i++) { double sum_neg = 0.0; double sum_pos = 0.0; for(int j = 0; j < window; j++) { double r = returns[i * window + j]; if(r < 0.0) sum_neg += r * r; if(r > 0.0) sum_pos += r * r; } rs_neg[i] = sum_neg; rs_pos[i] = sum_pos; diff[i] = sum_neg - sum_pos; } // Statistics calculations res.mean_RS_minus = rs_neg.Mean(); res.mean_RS_plus = rs_pos.Mean(); res.mean_diff = diff.Mean(); double variance = diff.Var(); double std_err = MathSqrt(variance / res.n_blocks); if(std_err == 0.0) { res.t_stat = 0.0; res.p_value = 1.0; } else { res.t_stat = res.mean_diff / std_err; // Compute 2-tailed p-value via MQL5 standard library int err_code = 0; double cdf = MathCumulativeDistributionT(MathAbs(res.t_stat), res.n_blocks - 1, err_code); if(err_code) { Print(__FUNCTION__,": T-distribution CDF calculation error."); return RealizedSemivarianceResult(); } res.p_value = 2.0 * (1.0 - cdf); } res.significant_asymmetry = (res.p_value < (double(res.confidence_level)/100.0)) && (res.mean_diff > 0.0); return res; } //+------------------------------------------------------------------+ //| Volatility Runs / Proportion Test Asymmetry Test | //+------------------------------------------------------------------+ VolatilityRunsResult VolatilityRunsAsymmetryTest(const vector &returns_or_resids, ENUM_STAT_CONFIDENCE confidence_level = CONFIDENCE_95 ) { VolatilityRunsResult res; res.confidence_level = confidence_level; ulong n = returns_or_resids.Size(); if(n <= 1) return res; // 1. Calculate absolute returns_or_resids and extract median vector abs_r = fabs(returns_or_resids); // MQL5 native vector sorting to fetch the median cleanly double median_abs = abs_r.Median(); // 2. Build Contingency Table elements double a = 0, b = 0, c = 0, d = 0; for(ulong i = 0; i < n - 1; i++) { bool prev_sign_neg = (returns_or_resids[i] < 0.0); bool next_high_vol = (abs_r[i + 1] > median_abs); if(prev_sign_neg && next_high_vol) a++; else if(prev_sign_neg && !next_high_vol) b++; else if(!prev_sign_neg && next_high_vol) c++; else if(!prev_sign_neg && !next_high_vol) d++; } res.contingency_table[0][0] = a; res.contingency_table[0][1] = b; res.contingency_table[1][0] = c; res.contingency_table[1][1] = d; // Proportions res.p_high_vol_prev_down = (a + b > 0) ? (a / (a + b)) : double("nan"); res.p_high_vol_prev_up = (c + d > 0) ? (c / (c + d)) : double("nan"); // 3. Chi-Square Test with Yates' continuity correction double total = a + b + c + d; if(total == 0 || (a+b) == 0 || (c+d) == 0 || (a+c) == 0 || (b+d) == 0) { res.chi2_stat = 0.0; res.p_value = 1.0; return res; } // Shortcut formula for 2x2 Chi-Square with Yates' Continuity Correction double numerator = MathAbs(a * d - b * c) - (total / 2.0); if(numerator < 0) numerator = 0; // cannot be negative before squaring double denominator = (a + b) * (c + d) * (a + c) * (b + d); res.chi2_stat = (total * MathPow(numerator, 2.0)) / denominator; // Compute p-value for 1 degree of freedom (2x2 table) int err_code = 0; // MathCumulativeDistributionChiSquare returns_or_resids P(X <= x). We want upper tail P(X >= x) double cdf = MathCumulativeDistributionChiSquare(res.chi2_stat, 1.0, err_code); // Check for errors if(err_code) { Print(__FUNCTION__, ": Chisquare CDF calculation error."); return VolatilityRunsResult(); } //--- res.p_value = 1.0 - cdf; //--- res.significant_asymmetry = (res.p_value < (double(res.confidence_level)/100.0)) && (res.p_high_vol_prev_down > res.p_high_vol_prev_up); //--- return res; } //+------------------------------------------------------------------+