//+------------------------------------------------------------------+ //| acf.mqh | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #include"..\..\np_graphics.mqh" #include //+------------------------------------------------------------------+ //| Autocorrelation function result struct | //+------------------------------------------------------------------+ struct ACFResult { vector acf; // Stores the calculated autocorrelation coefficients across target lags vector qstats; // Stores the Ljung-Box Q-test statistic values per lag step vector pvalues; // Stores the asymptotic p-values tracking Q-statistic significance matrix conf_intervals; // Two-row matrix storing boundary values: [0,:] = Lower bound, [1,:] = Upper bound // Default Constructor: Allocates empty memory arrays ACFResult(void) { acf = qstats = pvalues = vector::Zeros(0); conf_intervals = matrix::Zeros(0,0); } // Parameterized Constructor: Maps distinct calculations directly to internal members ACFResult(vector& _acf, vector& _qstats, vector& _pvalues, matrix& _confintervals) { acf = _acf; qstats = _qstats; pvalues = _pvalues; conf_intervals = _confintervals; } // Copy Constructor: Handles structured assignments during object duplication ACFResult(ACFResult& other) { acf = other.acf; qstats = other.qstats; pvalues = other.pvalues; conf_intervals = other.conf_intervals; } // Overloaded assignment operator to allow safe struct replication updates void operator=(ACFResult& other) { acf = other.acf; qstats = other.qstats; pvalues = other.pvalues; conf_intervals = other.conf_intervals; } }; //+------------------------------------------------------------------+ //| Core ACF calculation wrapper pipeline | //+------------------------------------------------------------------+ ACFResult acf(vector& x, ulong nlags = 0, double alpha = 0.05, bool bartlett_conf=false, bool demean= true, bool adjusted = false) { ACFResult out; ulong nobs = x.Size(); // Handle empty input arrays gracefully if(!nobs) { Print(__FUNCTION__, " input container is empty "); return out; } // Default heuristic rule for lags limit assignment if no specific threshold is set: 10 * log10(N) if(!nlags) nlags = MathMin(ulong(10 * log10(nobs)), nobs - 1); // --- Step 1: Compute Raw Autocovariance --- vector avf = acovf(x, 0, demean, adjusted); if(!avf.Size()) return out; // Isolate target lag arrays and divide by variance (Lag 0) to scale down to correlation space [-1, 1] out.acf = np::sliceVector(avf, 0, long(nlags + 1)); out.acf /= avf[0]; // --- Step 2: Establish Statistical Confidence Intervals --- if(bartlett_conf) { // Bartlett's formula for MA(q) processes: variance adjustments scale based on preceding squared coefficients vector varacf = vector::Ones(out.acf.Size()) / double(nobs); varacf[0] = 0.; // Lag 0 has zero estimation variance (strictly equals 1.0) varacf[1] = 1. / double(nobs); vector temp = np::sliceVector(out.acf, 1, out.acf.Size() - 1); vector tempsq = pow(temp, 2.); temp = np::sliceVector(varacf, 2); temp *= 1.0 + 2. * tempsq.CumSum(); // Scale variance hyper-exponentially per lag step np::vectorCopy(varacf, temp, 2); // Map out variance boundaries across the dynamic array blocks vector interval = CNormalDistr::InvNormalCDF(1. - alpha / 2.) * sqrt(varacf); out.conf_intervals = matrix::Zeros(2, varacf.Size()); out.conf_intervals.Row(out.acf - interval, 0); // Lower Bound out.conf_intervals.Row(out.acf + interval, 1); // Upper Bound } else { // Standard white noise baseline formula: Variance = 1 / N across all lookback horizons double varacf = 1. / double(x.Size()); double nv = CNormalDistr::InvNormalCDF(1. - alpha / 2.); double interval = nv * sqrt(varacf); out.conf_intervals = matrix::Zeros(2, out.acf.Size()); out.conf_intervals.Row(out.acf - interval, 0); // Lower Bound out.conf_intervals.Row(out.acf + interval, 1); // Upper Bound } // --- Step 3: Compute Ljung-Box Independence Tests --- vector temp = np::sliceVector(out.acf, 1); // Omit Lag 0 correlation from independence checks q_stat(temp, nobs, out.qstats, out.pvalues); return out; } //+------------------------------------------------------------------+ //| Autocovariance sequence extractor calculation | //+------------------------------------------------------------------+ vector acovf(vector &in, ulong lags = 0, bool demean= true, bool adjusted = false) { vector xo, acov; // Optionally subtract the sample mean to calculate variations relative to baseline center if(demean) xo = in - in.Mean(); else xo = in; ulong n = in.Size(); ulong laglen = lags; if(!laglen) laglen = n - 1; else if(lags > n - 1) { Print(__FUNCTION__, " nlag must be smaller than ", n - 1); return vector::Zeros(0); } acov = vector::Zeros(laglen + 1); acov[0] = xo.Dot(xo); // Total variance computation via internal dot product calculation // Iterative path fallback executed if small custom lookback slices are explicitly targeted if(lags) { vector a, b; for(ulong i = 0; i < laglen; ++i) { a = np::sliceVector(xo, long(i + 1)); b = np::sliceVector(xo, 0, long(xo.Size() - (i + 1))); acov[i + 1] = a.Dot(b); // Compute covariance cross product at lag level (i+1) } if(adjusted) acov /= (double(n) - np::arange(laglen + 1)); // Degrees of freedom corrections: N - lag else acov /= double(n); // Standard biometric calculation divisor return acov; } vector xi, temp, d; // Pre-calculate scale divisors array to map full structural ranges efficiently if(adjusted) { xi = np::arange(n, 1.); d = vector::Zeros((xi.Size() * 2) - 1); np::vectorCopy(d, xi, 0, xi.Size()); temp = np::sliceVector(xi, 0, xi.Size() - 1); np::reverseVector(temp); np::vectorCopy(d, temp, xi.Size()); } else d = double(n) * vector::Ones(2 * n - 1); // Fixed uniform divisor flag array // Execute ultra high-performance correlation mapping using continuous vector dot convolutions acov = xo.Correlate(xo, VECTOR_CONVOLVE_FULL); // Slice out the trailing half block tracking forward lag projections and apply scaling arrays acov = np::sliceVector(acov, long(n - 1)) / np::sliceVector(d, long(n - 1)); if(lags) return np::sliceVector(acov, 0, long(laglen + 1)); return acov; } //+------------------------------------------------------------------+ //| Ljung-Box Q independence test statistic calculations | //+------------------------------------------------------------------+ bool q_stat(vector& x, ulong nvars, vector& out_stats, vector& out_pvals) { // Vector x contains sample autocorrelation coefficients vector r = np::arange(x.Size(), 1.); // Weight individual squares based on data horizons limits: r_k^2 / (N - k) r = (1.0 / (double(nvars) - r)) * pow(x, 2.); // Apply structural inflation constant multiplier: Q = N * (N + 2) * Sum( r_k^2 / (N - k) ) out_stats = double(nvars) * double(nvars + 2) * r.CumSum(); out_pvals = out_stats; r = np::arange(x.Size(), 1.); // Use lag tracking array index counts directly as Chi-Square degrees of freedom int error_note = 0; // Map calculated test values onto the Chi-Square cumulative density tail distribution profile for(ulong i = 0; i < out_pvals.Size(); ++i) out_pvals[i] = 1. - MathCumulativeDistributionChiSquare(out_stats[i], r[i], error_note); if(error_note) return false; return true; } //+------------------------------------------------------------------+ //| Graphical drawing pipeline to plot the Correlogram inside MT5 | //+------------------------------------------------------------------+ void plot_acf(ACFResult& acf, long chart_id = 0, int x_coordinate = 0, int y_coordinate = 0, int subwindow = 0, int width = 600, int height = 400, string plot_name = NULL, int fontsize = 15, long viewtimeseconds = 30, bool showchart=true) { if(!acf.acf.Size()) { Print(__FUNCTION__, " Results object is empty "); return; } if(StringLen(plot_name) < 1) plot_name = "Autocorrelation Function"; CGraphic* graph = new CGraphic(); if(!chart_id) chart_id = ChartID(); ChartRedraw(chart_id); ChartSetInteger(chart_id, CHART_SHOW, showchart); // Instantiate standard graphical canvas object block inside active MetaTrader chart interface context if(!graph.Create(chart_id, plot_name, subwindow, x_coordinate, y_coordinate, width, height)) { delete graph; Print(__FUNCTION__, " Failed to Create graphical object on the Main chart Err ", GetLastError()); return; } double x[], y[]; ArrayResize(x, (int)acf.acf.Size()); ArrayResize(y, (int)acf.acf.Size()); int ncurves = 4; // 1 histogram curve, 1 marker points curve, and 2 symmetric boundary lines curves ENUM_CURVE_TYPE ctype = WRONG_VALUE; // --- Map Data Channels to Visual Layer Curves --- for(int i = 0; i < ncurves; ++i) { for(uint j = 0; j < x.Size(); ++j) { if(i < 2) { x[j] = double(j); y[j] = acf.acf[j]; // Base data arrays mapping core correlation values ctype = (i == 0) ? CURVE_HISTOGRAM : CURVE_POINTS; } else { ctype = CURVE_LINES; // Re-align curves types to continuous boundary rendering style x[j] = double(j); // Render baseline limits symmetrically centered around 0 line context boundaries if(i == 2) y[j] = (acf.conf_intervals[1, j] - acf.conf_intervals[0, j]) / 2.; // Upper limit line else y[j] = (acf.conf_intervals[0, j] - acf.conf_intervals[1, j]) / 2.; // Lower limit line } } // Commit processed coordinates arrays onto active drawing object canvas space CCurve* newcurve = graph.CurveAdd(x, y, ctype, NULL); // --- Apply Aesthetic Visual Rules --- switch(ctype) { case CURVE_HISTOGRAM: newcurve.Color(ColorToARGB(clrBlue)); newcurve.HistogramWidth(2); // Classic vertical correlogram spikes format break; case CURVE_POINTS: newcurve.PointsFill(true); newcurve.PointsColor(ColorToARGB(clrBlue)); newcurve.Color(ColorToARGB(clrBlue)); newcurve.PointsSize(8); break; default: // Handles the upper/lower confidence bounds display layers newcurve.Color(ColorToARGB(clrRed)); newcurve.LinesStyle(STYLE_DOT); // Dot pattern highlights statistical limits bounds clearly break; } } // --- Configure Chart Layout Canvas Texts --- graph.BackgroundMain(plot_name); graph.BackgroundMainSize(fontsize + 5); graph.BackgroundMainColor(ColorToARGB(clrBlack)); // Clear legend noise to keep presentation clean graph.HistoryNameWidth(0); graph.HistorySymbolSize(0); graph.HistoryNameSize(0); graph.XAxis().Name("Lags"); graph.XAxis().NameSize(fontsize); graph.YAxis().Name("Correlation Coefficient"); graph.YAxis().NameSize(fontsize); // Render arrays data modifications down to operational active chart user display view graph.CurvePlotAll(); graph.Update(); // Keep display pinned for requested time window before tearing down allocations safely Sleep(int(viewtimeseconds) * 1000); graph.Destroy(); delete graph; ChartRedraw(chart_id); } /*//+------------------------------------------------------------------+ //|Find the next regular number greater than or equal to target. | //+------------------------------------------------------------------+ long next_target(long target) { if(target<=6) return target; if(!(target&(target-1))) return target; double match = AL_POSINF; long p5 = 1; long p35,quotient,N,p2; while(p5=2) { var = var/2; ++count; } return count; }*/