mirror of
https://github.com/dinethlive/dbasket-EA.git
synced 2026-08-16 20:28:13 +00:00
425 lines
14 KiB
Plaintext
425 lines
14 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| DBasket_CointegrationEngine.mqh |
|
|
//| D-Basket Correlation Hedging EA |
|
|
//| ADF Test for Cointegration |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "D-Basket EA"
|
|
#property version "2.00"
|
|
#property strict
|
|
|
|
#ifndef DBASKET_COINTEGRATIONENGINE_MQH
|
|
#define DBASKET_COINTEGRATIONENGINE_MQH
|
|
|
|
#include "DBasket_Defines.mqh"
|
|
#include "DBasket_Structures.mqh"
|
|
#include "DBasket_Logger.mqh"
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Cointegration Data Structure |
|
|
//+------------------------------------------------------------------+
|
|
struct CointegrationData
|
|
{
|
|
double adfStatistic; // ADF test statistic
|
|
double pValue; // Approximate p-value
|
|
double beta; // Hedge ratio from OLS regression
|
|
double alpha; // Intercept from OLS regression
|
|
double residualStdDev; // Standard deviation of residuals
|
|
datetime lastUpdateTime; // Timestamp of last calculation
|
|
bool isCointegrated; // True if p-value < threshold
|
|
bool isValid; // True if calculation succeeded
|
|
string invalidReason; // Description if invalid
|
|
|
|
void Reset()
|
|
{
|
|
adfStatistic = 0;
|
|
pValue = 1.0;
|
|
beta = 1.0;
|
|
alpha = 0;
|
|
residualStdDev = 0;
|
|
lastUpdateTime = 0;
|
|
isCointegrated = false;
|
|
isValid = false;
|
|
invalidReason = "";
|
|
}
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Cointegration Engine Class |
|
|
//| Implements Engle-Granger two-step cointegration test |
|
|
//+------------------------------------------------------------------+
|
|
class CCointegrationEngine
|
|
{
|
|
private:
|
|
// Configuration
|
|
int m_lookbackPeriod; // Bars for regression
|
|
int m_adfLags; // Lags for ADF test
|
|
double m_pValueThreshold; // Cointegration threshold
|
|
int m_updateIntervalBars; // Bars between updates
|
|
|
|
// State
|
|
CointegrationData m_cache;
|
|
int m_barsSinceUpdate;
|
|
bool m_isInitialized;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate mean of array |
|
|
//+------------------------------------------------------------------+
|
|
double ArrayMean(const double &arr[], int count)
|
|
{
|
|
if(count <= 0) return 0;
|
|
double sum = 0;
|
|
for(int i = 0; i < count; i++)
|
|
sum += arr[i];
|
|
return sum / count;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate standard deviation of array |
|
|
//+------------------------------------------------------------------+
|
|
double ArrayStdDev(const double &arr[], int count, double mean)
|
|
{
|
|
if(count <= 1) return 0;
|
|
double sumSq = 0;
|
|
for(int i = 0; i < count; i++)
|
|
{
|
|
double diff = arr[i] - mean;
|
|
sumSq += diff * diff;
|
|
}
|
|
return MathSqrt(sumSq / (count - 1));
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| OLS Regression: Y = alpha + beta * X + residuals |
|
|
//| Returns residuals array |
|
|
//+------------------------------------------------------------------+
|
|
bool OLSRegression(const double &X[], const double &Y[], int count,
|
|
double &beta, double &alpha, double &residuals[])
|
|
{
|
|
if(count < 30)
|
|
{
|
|
Logger.Warning("OLS: Insufficient data points: " + IntegerToString(count));
|
|
return false;
|
|
}
|
|
|
|
// Calculate means
|
|
double meanX = ArrayMean(X, count);
|
|
double meanY = ArrayMean(Y, count);
|
|
|
|
// Calculate covariance and variance
|
|
double covXY = 0;
|
|
double varX = 0;
|
|
|
|
for(int i = 0; i < count; i++)
|
|
{
|
|
double dx = X[i] - meanX;
|
|
double dy = Y[i] - meanY;
|
|
covXY += dx * dy;
|
|
varX += dx * dx;
|
|
}
|
|
|
|
// Avoid division by zero
|
|
if(MathAbs(varX) < 0.0000001)
|
|
{
|
|
Logger.Warning("OLS: Near-zero variance in X series");
|
|
return false;
|
|
}
|
|
|
|
// Calculate coefficients
|
|
beta = covXY / varX;
|
|
alpha = meanY - beta * meanX;
|
|
|
|
// Calculate residuals
|
|
if(ArrayResize(residuals, count) != count)
|
|
return false;
|
|
|
|
for(int i = 0; i < count; i++)
|
|
{
|
|
residuals[i] = Y[i] - (alpha + beta * X[i]);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Augmented Dickey-Fuller Test (simplified, 1 lag) |
|
|
//| Tests if series has unit root (non-stationary) |
|
|
//| Returns: ADF statistic (more negative = more stationary) |
|
|
//+------------------------------------------------------------------+
|
|
bool ADFTest(const double &series[], int count, double &adfStat, double &pValue)
|
|
{
|
|
if(count < 50)
|
|
{
|
|
Logger.Warning("ADF: Insufficient data points: " + IntegerToString(count));
|
|
return false;
|
|
}
|
|
|
|
int n = count - 1; // Number of differences
|
|
|
|
// Construct lagged series and first differences
|
|
double y_lag[];
|
|
double delta_y[];
|
|
|
|
if(ArrayResize(y_lag, n) != n || ArrayResize(delta_y, n) != n)
|
|
return false;
|
|
|
|
for(int i = 0; i < n; i++)
|
|
{
|
|
y_lag[i] = series[i];
|
|
delta_y[i] = series[i + 1] - series[i];
|
|
}
|
|
|
|
// Run regression: delta_y = alpha + gamma * y_lag + epsilon
|
|
// We need gamma coefficient and its standard error
|
|
|
|
// Calculate means
|
|
double meanYLag = ArrayMean(y_lag, n);
|
|
double meanDeltaY = ArrayMean(delta_y, n);
|
|
|
|
// Calculate covariance and variance for regression
|
|
double covYD = 0;
|
|
double varYLag = 0;
|
|
|
|
for(int i = 0; i < n; i++)
|
|
{
|
|
double dy_lag = y_lag[i] - meanYLag;
|
|
double dy = delta_y[i] - meanDeltaY;
|
|
covYD += dy_lag * dy;
|
|
varYLag += dy_lag * dy_lag;
|
|
}
|
|
|
|
if(MathAbs(varYLag) < 0.0000001)
|
|
{
|
|
Logger.Warning("ADF: Near-zero variance in lagged series");
|
|
return false;
|
|
}
|
|
|
|
// Gamma coefficient
|
|
double gamma = covYD / varYLag;
|
|
double alpha = meanDeltaY - gamma * meanYLag;
|
|
|
|
// Calculate residuals and MSE
|
|
double residualSumSq = 0;
|
|
for(int i = 0; i < n; i++)
|
|
{
|
|
double fitted = alpha + gamma * y_lag[i];
|
|
double resid = delta_y[i] - fitted;
|
|
residualSumSq += resid * resid;
|
|
}
|
|
|
|
double mse = residualSumSq / (n - 2); // 2 parameters estimated
|
|
|
|
// Standard error of gamma
|
|
double seGamma = MathSqrt(mse / varYLag);
|
|
|
|
if(seGamma < 0.0000001)
|
|
{
|
|
Logger.Warning("ADF: Near-zero standard error");
|
|
return false;
|
|
}
|
|
|
|
// ADF statistic (t-statistic of gamma)
|
|
adfStat = gamma / seGamma;
|
|
|
|
// Convert to approximate p-value using MacKinnon critical values
|
|
// Critical values for ADF test with constant, no trend
|
|
// 1%: -3.43, 5%: -2.86, 10%: -2.57
|
|
if(adfStat < -3.43)
|
|
pValue = 0.01;
|
|
else if(adfStat < -2.86)
|
|
pValue = 0.05;
|
|
else if(adfStat < -2.57)
|
|
pValue = 0.10;
|
|
else
|
|
pValue = 0.20; // Not stationary
|
|
|
|
return true;
|
|
}
|
|
|
|
public:
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CCointegrationEngine()
|
|
{
|
|
m_lookbackPeriod = 250;
|
|
m_adfLags = 1;
|
|
m_pValueThreshold = 0.05;
|
|
m_updateIntervalBars = 50;
|
|
m_barsSinceUpdate = 999; // Force initial calculation
|
|
m_isInitialized = false;
|
|
m_cache.Reset();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize engine |
|
|
//+------------------------------------------------------------------+
|
|
bool Initialize(int lookbackPeriod, double pValueThreshold, int updateIntervalBars, int adfLags = 1)
|
|
{
|
|
if(lookbackPeriod < 60)
|
|
{
|
|
Logger.Error("Cointegration: Lookback period too short (min 60)");
|
|
return false;
|
|
}
|
|
|
|
m_lookbackPeriod = lookbackPeriod;
|
|
m_pValueThreshold = pValueThreshold;
|
|
m_updateIntervalBars = updateIntervalBars;
|
|
m_adfLags = adfLags;
|
|
m_barsSinceUpdate = 999;
|
|
m_isInitialized = true;
|
|
m_cache.Reset();
|
|
|
|
Logger.Info("Cointegration Engine initialized - Lookback: " + IntegerToString(m_lookbackPeriod) +
|
|
", P-Value Threshold: " + DoubleToString(m_pValueThreshold, 2) +
|
|
", Update Interval: " + IntegerToString(m_updateIntervalBars) + " bars");
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update cointegration test |
|
|
//| X = synthetic ratio (AUDCAD/NZDCAD) |
|
|
//| Y = AUDNZD |
|
|
//+------------------------------------------------------------------+
|
|
bool Update(const double &syntheticRatio[], const double &audnzd[], int dataCount, bool forceUpdate = false)
|
|
{
|
|
if(!m_isInitialized)
|
|
{
|
|
Logger.Error("Cointegration Engine not initialized");
|
|
return false;
|
|
}
|
|
|
|
// Check if update needed
|
|
m_barsSinceUpdate++;
|
|
if(!forceUpdate && m_barsSinceUpdate < m_updateIntervalBars && m_cache.isValid)
|
|
{
|
|
return true; // Use cached values
|
|
}
|
|
|
|
// Validate data
|
|
int count = MathMin(dataCount, m_lookbackPeriod);
|
|
if(count < 60)
|
|
{
|
|
m_cache.isValid = false;
|
|
m_cache.invalidReason = "Insufficient data: " + IntegerToString(count);
|
|
return false;
|
|
}
|
|
|
|
// Reset update counter
|
|
m_barsSinceUpdate = 0;
|
|
|
|
// Step 1: OLS Regression to get residuals
|
|
double residuals[];
|
|
double beta, alpha;
|
|
|
|
if(!OLSRegression(syntheticRatio, audnzd, count, beta, alpha, residuals))
|
|
{
|
|
m_cache.isValid = false;
|
|
m_cache.invalidReason = "OLS regression failed";
|
|
return false;
|
|
}
|
|
|
|
// Step 2: ADF Test on residuals
|
|
double adfStat, pValue;
|
|
int residCount = ArraySize(residuals);
|
|
|
|
if(!ADFTest(residuals, residCount, adfStat, pValue))
|
|
{
|
|
m_cache.isValid = false;
|
|
m_cache.invalidReason = "ADF test failed";
|
|
return false;
|
|
}
|
|
|
|
// Step 3: Calculate residual statistics
|
|
double residMean = ArrayMean(residuals, residCount);
|
|
double residStdDev = ArrayStdDev(residuals, residCount, residMean);
|
|
|
|
// Step 4: Update cache
|
|
m_cache.adfStatistic = adfStat;
|
|
m_cache.pValue = pValue;
|
|
m_cache.beta = beta;
|
|
m_cache.alpha = alpha;
|
|
m_cache.residualStdDev = residStdDev;
|
|
m_cache.lastUpdateTime = TimeCurrent();
|
|
m_cache.isCointegrated = (pValue < m_pValueThreshold);
|
|
m_cache.isValid = true;
|
|
m_cache.invalidReason = "";
|
|
|
|
// Log results
|
|
string status = m_cache.isCointegrated ? "COINTEGRATED" : "NOT COINTEGRATED";
|
|
Logger.Debug("Cointegration Test: " + status +
|
|
" - ADF: " + DoubleToString(adfStat, 3) +
|
|
", P-Value: " + DoubleToString(pValue, 2) +
|
|
", Beta: " + DoubleToString(beta, 4));
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if spread is cointegrated |
|
|
//+------------------------------------------------------------------+
|
|
bool IsCointegrated()
|
|
{
|
|
return m_cache.isValid && m_cache.isCointegrated;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get cached cointegration data |
|
|
//+------------------------------------------------------------------+
|
|
void GetData(CointegrationData &data)
|
|
{
|
|
data = m_cache;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get ADF statistic |
|
|
//+------------------------------------------------------------------+
|
|
double GetADFStatistic()
|
|
{
|
|
return m_cache.adfStatistic;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get p-value |
|
|
//+------------------------------------------------------------------+
|
|
double GetPValue()
|
|
{
|
|
return m_cache.pValue;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get hedge ratio (beta) |
|
|
//+------------------------------------------------------------------+
|
|
double GetBeta()
|
|
{
|
|
return m_cache.beta;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Is cache valid |
|
|
//+------------------------------------------------------------------+
|
|
bool IsValid()
|
|
{
|
|
return m_cache.isValid;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get reason if invalid |
|
|
//+------------------------------------------------------------------+
|
|
string GetInvalidReason()
|
|
{
|
|
return m_cache.invalidReason;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Force recalculation on next update |
|
|
//+------------------------------------------------------------------+
|
|
void Invalidate()
|
|
{
|
|
m_barsSinceUpdate = 999;
|
|
}
|
|
};
|
|
|
|
#endif // DBASKET_COINTEGRATIONENGINE_MQH
|
|
//+------------------------------------------------------------------+
|