//+------------------------------------------------------------------+ //| ljung.mqh | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #include"acf.mqh" ///+------------------------------------------------------------------+ //| Ljung-Box and Box-Pierce test of autocorrelation in residuals. | //| Returns a matrix: Col 0 = LB Stat, Col 1 = LB p-val | //| (Optional Box-Pierce) Col 2 = BP Stat, Col 3 = BP p-val | //+------------------------------------------------------------------+ matrix ljungboxtest(vector& x, ulong lags=0, ulong model_df=0, ulong period = 0, bool demean=false, bool boxpierce=false, bool autolag=false) { //--- Validation: Seasonal periods must encompass at least 2 observations if(period==1) { Print(__FUNCTION__, " period must be >= 2"); return matrix::Zeros(0,0); } ulong nobs = x.Size(); vector laggs; //+------------------------------------------------------------------+ //| Automated Lag Selection (Information Criterion Style) | //+------------------------------------------------------------------+ if(autolag) { ulong maxlag = nobs-1; ACFResult sacf = acf(x,maxlag,0.05,false,demean,false); if(!sacf.acf.Size()) return matrix::Zeros(0,0); vector ssacf(maxlag); vector r(maxlag); // Shift ACF results to isolate positive lags (drop lag 0) for(ulong i = 1; i<(maxlag+1); ssacf[i-1]=sacf.acf[i], ++i); for(ulong i = 0; i<(maxlag); r[i] = double(i+1), ++i); vector qsacf; // Calculate standard running test statistics for automated profiling if(!boxpierce) { vector n = pow(ssacf,2.)/(double(nobs)-r); qsacf = nobs*(nobs+2.)*n.CumSum(); } else { vector n = pow(ssacf,2.); qsacf = nobs*n.CumSum(); } // Dynamic penalty adjustments based on information thresholds double q = 2.4; double threshold = sqrt(q*log(nobs)); double threshold_metric = MathAbs(sacf.acf).Max()*sqrt(nobs); r = vector::Zeros(nobs-1); for(ulong i = 0; i0) pval[i] = 1. - MathCumulativeDistributionChiSquare(qljungbox[i],adj_lags[i],e); // Build base matrix packaging results matrix out; out = matrix::Zeros(adj_lags.Size(),2); out.Col(qljungbox,0); out.Col(pval,1); //+------------------------------------------------------------------+ //| Optional: Compute Classic Box-Pierce Statistics | //+------------------------------------------------------------------+ if(boxpierce) { out.Resize(out.Rows(),4); // Expand matrix allocation to hold 4 metrics temp = pow(ssacf,2.0); temp = temp.CumSum(); vector qboxpierce = temp; // Calculate classic BP statistics (Unscaled by sample denominator increments) for(ulong i = 0; i0) pval[i] = 1. - MathCumulativeDistributionChiSquare(qboxpierce[i],adj_lags[i],e); // Append Box-Pierce results to final columns out.Col(qboxpierce,2); out.Col(pval,3); } return out; } //+------------------------------------------------------------------+