Article-23677-EGARCH-MQL5-V.../Arch/Univariate/acf.mqh
2026-07-24 23:03:06 +02:00

382 lines
No EOL
14 KiB
MQL5

//+------------------------------------------------------------------+
//| 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<Math\Stat\ChiSquare.mqh>
//+------------------------------------------------------------------+
//| 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<target)
{
p35 = p5;
while(p35<target)
{
quotient =-(-target/p35);
p2=(long)pow(2,(long)bit_length(quotient-1));
N = p2*p35;
if(N == target)
return N;
else
if(double(N)<match)
match = double(N);
p35*=3;
if(p35==target)
return p35;
}
p5*=5;
if(p5==target)
return p5;
}
if(double(p5)<match)
match = double(p5);
return long(match);
}
//+------------------------------------------------------------------+
//| get the number bits for an integer |
//+------------------------------------------------------------------+
ulong bit_length(long x)
{
long var = MathAbs(x);
ulong count = 1;
while(var>=2)
{
var = var/2;
++count;
}
return count;
}*/