mirror of
https://github.com/dinethlive/dbasket-EA.git
synced 2026-08-25 08:38:14 +00:00
Initial commit: D-Basket EA v2.0 Pro
This commit is contained in:
@@ -0,0 +1,416 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| DBasket_HalfLifeEngine.mqh |
|
||||
//| D-Basket Correlation Hedging EA |
|
||||
//| Ornstein-Uhlenbeck Half-Life |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "D-Basket EA"
|
||||
#property version "2.00"
|
||||
#property strict
|
||||
|
||||
#ifndef DBASKET_HALFLIFEENGINE_MQH
|
||||
#define DBASKET_HALFLIFEENGINE_MQH
|
||||
|
||||
#include "DBasket_Defines.mqh"
|
||||
#include "DBasket_Structures.mqh"
|
||||
#include "DBasket_Logger.mqh"
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Half-Life Data Structure |
|
||||
//+------------------------------------------------------------------+
|
||||
struct HalfLifeData
|
||||
{
|
||||
double lambda; // AR(1) coefficient (must be < 0)
|
||||
double alpha; // Intercept
|
||||
double halfLife; // Calculated half-life in bars
|
||||
double sigma; // Residual standard deviation
|
||||
double ouVariance; // Long-term O-U variance
|
||||
datetime lastUpdateTime; // Timestamp of last calculation
|
||||
bool isValid; // True if lambda < 0 (mean-reverting)
|
||||
bool isMeanReverting; // True if spread is mean-reverting
|
||||
string invalidReason; // Description if invalid
|
||||
|
||||
void Reset()
|
||||
{
|
||||
lambda = 0;
|
||||
alpha = 0;
|
||||
halfLife = 100; // Default fallback
|
||||
sigma = 0;
|
||||
ouVariance = 0;
|
||||
lastUpdateTime = 0;
|
||||
isValid = false;
|
||||
isMeanReverting = false;
|
||||
invalidReason = "";
|
||||
}
|
||||
};
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Half-Life Engine Class |
|
||||
//| Estimates mean reversion speed via AR(1) regression |
|
||||
//+------------------------------------------------------------------+
|
||||
class CHalfLifeEngine
|
||||
{
|
||||
private:
|
||||
// Configuration
|
||||
int m_lookbackPeriod; // Bars for regression
|
||||
int m_updateIntervalBars; // Bars between updates
|
||||
int m_minHalfLife; // Minimum acceptable half-life
|
||||
int m_maxHalfLife; // Maximum acceptable half-life
|
||||
double m_exitMultiplier; // Max holding = multiplier * halfLife
|
||||
double m_stopLossSigma; // Stop loss distance in sigma
|
||||
|
||||
// State
|
||||
HalfLifeData 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));
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| AR(1) Regression: delta_y = alpha + lambda * y_lag + epsilon |
|
||||
//| Tests for mean reversion in spread series |
|
||||
//+------------------------------------------------------------------+
|
||||
bool AR1Regression(const double &spread[], int count,
|
||||
double &lambda, double &alpha, double &sigma)
|
||||
{
|
||||
if(count < 50)
|
||||
{
|
||||
Logger.Warning("AR1: Insufficient data points: " + IntegerToString(count));
|
||||
return false;
|
||||
}
|
||||
|
||||
int n = count - 1; // Number of differences
|
||||
|
||||
// Construct arrays
|
||||
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] = spread[i];
|
||||
delta_y[i] = spread[i + 1] - spread[i];
|
||||
}
|
||||
|
||||
// Calculate means
|
||||
double meanYLag = ArrayMean(y_lag, n);
|
||||
double meanDeltaY = ArrayMean(delta_y, n);
|
||||
|
||||
// Calculate covariance and variance
|
||||
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("AR1: Near-zero variance in lagged series");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Lambda coefficient
|
||||
lambda = covYD / varYLag;
|
||||
alpha = meanDeltaY - lambda * meanYLag;
|
||||
|
||||
// Calculate residuals for sigma estimation
|
||||
double residualSumSq = 0;
|
||||
for(int i = 0; i < n; i++)
|
||||
{
|
||||
double fitted = alpha + lambda * y_lag[i];
|
||||
double resid = delta_y[i] - fitted;
|
||||
residualSumSq += resid * resid;
|
||||
}
|
||||
|
||||
sigma = MathSqrt(residualSumSq / (n - 2));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
//+------------------------------------------------------------------+
|
||||
//| Constructor |
|
||||
//+------------------------------------------------------------------+
|
||||
CHalfLifeEngine()
|
||||
{
|
||||
m_lookbackPeriod = 250;
|
||||
m_updateIntervalBars = 20;
|
||||
m_minHalfLife = 10;
|
||||
m_maxHalfLife = 500;
|
||||
m_exitMultiplier = 2.0;
|
||||
m_stopLossSigma = 1.5;
|
||||
m_barsSinceUpdate = 999; // Force initial calculation
|
||||
m_isInitialized = false;
|
||||
m_cache.Reset();
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Initialize engine |
|
||||
//+------------------------------------------------------------------+
|
||||
bool Initialize(int lookbackPeriod, int updateIntervalBars,
|
||||
int minHalfLife, int maxHalfLife,
|
||||
double exitMultiplier, double stopLossSigma)
|
||||
{
|
||||
if(lookbackPeriod < 50)
|
||||
{
|
||||
Logger.Error("HalfLife: Lookback period too short (min 50)");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_lookbackPeriod = lookbackPeriod;
|
||||
m_updateIntervalBars = updateIntervalBars;
|
||||
m_minHalfLife = minHalfLife;
|
||||
m_maxHalfLife = maxHalfLife;
|
||||
m_exitMultiplier = exitMultiplier;
|
||||
m_stopLossSigma = stopLossSigma;
|
||||
m_barsSinceUpdate = 999;
|
||||
m_isInitialized = true;
|
||||
m_cache.Reset();
|
||||
|
||||
Logger.Info("Half-Life Engine initialized - Lookback: " + IntegerToString(m_lookbackPeriod) +
|
||||
", Exit Multiplier: " + DoubleToString(m_exitMultiplier, 1) +
|
||||
", SL Sigma: " + DoubleToString(m_stopLossSigma, 1));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Update half-life calculation |
|
||||
//| spread[] = spread series (AUDNZD - syntheticRatio) |
|
||||
//+------------------------------------------------------------------+
|
||||
bool Update(const double &spread[], int dataCount, bool forceUpdate = false)
|
||||
{
|
||||
if(!m_isInitialized)
|
||||
{
|
||||
Logger.Error("Half-Life 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 < 50)
|
||||
{
|
||||
m_cache.isValid = false;
|
||||
m_cache.invalidReason = "Insufficient data: " + IntegerToString(count);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset update counter
|
||||
m_barsSinceUpdate = 0;
|
||||
|
||||
// Run AR(1) regression
|
||||
double lambda, alpha, sigma;
|
||||
|
||||
if(!AR1Regression(spread, count, lambda, alpha, sigma))
|
||||
{
|
||||
m_cache.isValid = false;
|
||||
m_cache.invalidReason = "AR(1) regression failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if mean-reverting (lambda must be negative)
|
||||
if(lambda >= 0 || lambda > -0.001)
|
||||
{
|
||||
m_cache.isValid = true;
|
||||
m_cache.isMeanReverting = false;
|
||||
m_cache.lambda = lambda;
|
||||
m_cache.halfLife = 9999; // Very long (no reversion)
|
||||
m_cache.invalidReason = "Non-mean-reverting (lambda >= 0)";
|
||||
|
||||
Logger.Debug("HalfLife: Spread is non-mean-reverting, lambda = " +
|
||||
DoubleToString(lambda, 6));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Calculate half-life: tau = -ln(2) / lambda
|
||||
double halfLife = -MathLog(2.0) / lambda;
|
||||
|
||||
// Calculate O-U variance: sigma^2 / (-2 * lambda)
|
||||
double ouVariance = (sigma * sigma) / (-2.0 * lambda);
|
||||
|
||||
// Validate half-life range
|
||||
bool isReasonable = (halfLife >= m_minHalfLife && halfLife <= m_maxHalfLife);
|
||||
|
||||
// Update cache
|
||||
m_cache.lambda = lambda;
|
||||
m_cache.alpha = alpha;
|
||||
m_cache.halfLife = halfLife;
|
||||
m_cache.sigma = sigma;
|
||||
m_cache.ouVariance = ouVariance;
|
||||
m_cache.lastUpdateTime = TimeCurrent();
|
||||
m_cache.isMeanReverting = true;
|
||||
m_cache.isValid = true;
|
||||
m_cache.invalidReason = isReasonable ? "" : "Half-life out of range";
|
||||
|
||||
Logger.Debug("HalfLife: " + DoubleToString(halfLife, 1) + " bars" +
|
||||
", Lambda: " + DoubleToString(lambda, 6) +
|
||||
", O-U Variance: " + DoubleToString(ouVariance, 6));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Get half-life value |
|
||||
//+------------------------------------------------------------------+
|
||||
double GetHalfLife()
|
||||
{
|
||||
return m_cache.halfLife;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Get maximum holding time in bars |
|
||||
//+------------------------------------------------------------------+
|
||||
int GetMaxHoldingBars()
|
||||
{
|
||||
if(!m_cache.isValid || !m_cache.isMeanReverting)
|
||||
return 100; // Default fallback
|
||||
|
||||
return (int)(m_cache.halfLife * m_exitMultiplier);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Get stop loss z-score distance |
|
||||
//+------------------------------------------------------------------+
|
||||
double GetStopLossSigma()
|
||||
{
|
||||
return m_stopLossSigma;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Get O-U variance (for stop-loss calculation) |
|
||||
//+------------------------------------------------------------------+
|
||||
double GetOUVariance()
|
||||
{
|
||||
return m_cache.ouVariance;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check if spread is mean-reverting |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsMeanReverting()
|
||||
{
|
||||
return m_cache.isValid && m_cache.isMeanReverting;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check if half-life is within reasonable range |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsHalfLifeValid()
|
||||
{
|
||||
if(!m_cache.isValid || !m_cache.isMeanReverting)
|
||||
return false;
|
||||
|
||||
return (m_cache.halfLife >= m_minHalfLife &&
|
||||
m_cache.halfLife <= m_maxHalfLife);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Get cached half-life data |
|
||||
//+------------------------------------------------------------------+
|
||||
void GetData(HalfLifeData &data)
|
||||
{
|
||||
data = m_cache;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Is cache valid |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsValid()
|
||||
{
|
||||
return m_cache.isValid;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Get lambda coefficient |
|
||||
//+------------------------------------------------------------------+
|
||||
double GetLambda()
|
||||
{
|
||||
return m_cache.lambda;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Force recalculation on next update |
|
||||
//+------------------------------------------------------------------+
|
||||
void Invalidate()
|
||||
{
|
||||
m_barsSinceUpdate = 999;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check if exit triggered by time |
|
||||
//| barsOpen: number of bars since basket opened |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsTimeExitTriggered(int barsOpen)
|
||||
{
|
||||
if(!m_cache.isValid || !m_cache.isMeanReverting)
|
||||
return (barsOpen > 100); // Fallback
|
||||
|
||||
int maxBars = GetMaxHoldingBars();
|
||||
return (barsOpen > maxBars);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check if stop-loss triggered |
|
||||
//| entryZScore: z-score at entry |
|
||||
//| currentZScore: current z-score |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsStopLossTriggered(double entryZScore, double currentZScore)
|
||||
{
|
||||
// Stop loss if spread diverges further by stopLossSigma
|
||||
double stopDistance = m_stopLossSigma;
|
||||
|
||||
if(entryZScore > 0) // Short basket entry
|
||||
{
|
||||
// Z-score was positive, should decrease
|
||||
// Stop if it increases beyond entry + sigma
|
||||
return (currentZScore > entryZScore + stopDistance);
|
||||
}
|
||||
else // Long basket entry
|
||||
{
|
||||
// Z-score was negative, should increase toward 0
|
||||
// Stop if it decreases beyond entry - sigma
|
||||
return (currentZScore < entryZScore - stopDistance);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif // DBASKET_HALFLIFEENGINE_MQH
|
||||
//+------------------------------------------------------------------+
|
||||
Reference in New Issue
Block a user