mirror of
https://github.com/dinethlive/dbasket-EA.git
synced 2026-08-22 23:28:26 +00:00
Initial commit: D-Basket EA v2.0 Pro
This commit is contained in:
@@ -0,0 +1,484 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| DBasket_CorrelationEngine.mqh |
|
||||
//| D-Basket Correlation Hedging EA |
|
||||
//| Correlation Calculation Module |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "D-Basket EA"
|
||||
#property version "1.00"
|
||||
#property strict
|
||||
|
||||
#ifndef DBASKET_CORRELATIONENGINE_MQH
|
||||
#define DBASKET_CORRELATIONENGINE_MQH
|
||||
|
||||
#include "DBasket_Defines.mqh"
|
||||
#include "DBasket_Structures.mqh"
|
||||
#include "DBasket_Logger.mqh"
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Correlation Engine Class |
|
||||
//| Handles price data collection, correlation, and z-score calc |
|
||||
//+------------------------------------------------------------------+
|
||||
class CCorrelationEngine
|
||||
{
|
||||
private:
|
||||
// Configuration
|
||||
string m_symbols[NUM_SYMBOLS]; // Symbol names
|
||||
int m_lookbackPeriod; // Rolling window size
|
||||
ENUM_TIMEFRAMES m_timeframe; // Timeframe for data
|
||||
int m_updateIntervalSec; // Cache update interval
|
||||
|
||||
// Price buffers for each symbol
|
||||
PriceHistoryBuffer m_priceBuffers[NUM_SYMBOLS];
|
||||
|
||||
// Cached calculation results
|
||||
CorrelationData m_cache;
|
||||
datetime m_lastCalculationTime;
|
||||
datetime m_lastBarTime[NUM_SYMBOLS];
|
||||
|
||||
// State
|
||||
bool m_isInitialized;
|
||||
bool m_isWarmedUp;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Calculate mean of array |
|
||||
//+------------------------------------------------------------------+
|
||||
double CalculateMean(const double &arr[], int size)
|
||||
{
|
||||
if(size <= 0)
|
||||
return 0;
|
||||
|
||||
double sum = 0;
|
||||
for(int i = 0; i < size; i++)
|
||||
sum += arr[i];
|
||||
|
||||
return sum / size;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Calculate standard deviation |
|
||||
//+------------------------------------------------------------------+
|
||||
double CalculateStdDev(const double &arr[], int size, double mean)
|
||||
{
|
||||
if(size <= 1)
|
||||
return 0;
|
||||
|
||||
double sumSq = 0;
|
||||
for(int i = 0; i < size; i++)
|
||||
{
|
||||
double diff = arr[i] - mean;
|
||||
sumSq += diff * diff;
|
||||
}
|
||||
|
||||
return MathSqrt(sumSq / size);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Calculate Pearson correlation between two arrays |
|
||||
//+------------------------------------------------------------------+
|
||||
double CalculatePearsonCorrelation(const double &x[], const double &y[], int size)
|
||||
{
|
||||
if(size < 2)
|
||||
return 0;
|
||||
|
||||
// Calculate means
|
||||
double meanX = CalculateMean(x, size);
|
||||
double meanY = CalculateMean(y, size);
|
||||
|
||||
// Calculate covariance and standard deviations
|
||||
double sumXY = 0;
|
||||
double sumX2 = 0;
|
||||
double sumY2 = 0;
|
||||
|
||||
for(int i = 0; i < size; i++)
|
||||
{
|
||||
double dx = x[i] - meanX;
|
||||
double dy = y[i] - meanY;
|
||||
sumXY += dx * dy;
|
||||
sumX2 += dx * dx;
|
||||
sumY2 += dy * dy;
|
||||
}
|
||||
|
||||
// Calculate correlation
|
||||
double denominator = MathSqrt(sumX2 * sumY2);
|
||||
|
||||
if(denominator == 0)
|
||||
return 0;
|
||||
|
||||
double correlation = sumXY / denominator;
|
||||
|
||||
// Clamp to valid range due to floating point errors
|
||||
return CLAMP(correlation, -1.0, 1.0);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Load historical prices for a symbol |
|
||||
//+------------------------------------------------------------------+
|
||||
bool LoadHistoricalPrices(int symbolIndex)
|
||||
{
|
||||
if(symbolIndex < 0 || symbolIndex >= NUM_SYMBOLS)
|
||||
return false;
|
||||
|
||||
string symbol = m_symbols[symbolIndex];
|
||||
double prices[];
|
||||
|
||||
// Copy close prices
|
||||
int copied = CopyClose(symbol, m_timeframe, 0, m_lookbackPeriod, prices);
|
||||
|
||||
if(copied < m_lookbackPeriod)
|
||||
{
|
||||
Logger.Warning("Insufficient historical data for " + symbol +
|
||||
". Required: " + IntegerToString(m_lookbackPeriod) +
|
||||
", Got: " + IntegerToString(copied));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize buffer
|
||||
if(!m_priceBuffers[symbolIndex].Initialize(m_lookbackPeriod))
|
||||
{
|
||||
Logger.Error("Failed to initialize price buffer for " + symbol);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Populate buffer (prices array is oldest to newest)
|
||||
for(int i = 0; i < m_lookbackPeriod; i++)
|
||||
{
|
||||
m_priceBuffers[symbolIndex].prices[i] = prices[i];
|
||||
}
|
||||
|
||||
m_priceBuffers[symbolIndex].head = m_lookbackPeriod - 1;
|
||||
m_priceBuffers[symbolIndex].isWarmedUp = true;
|
||||
m_priceBuffers[symbolIndex].lastUpdateTime = TimeCurrent();
|
||||
|
||||
Logger.Info("Loaded " + IntegerToString(copied) + " historical prices for " + symbol);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check if new bar formed for symbol |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsNewBar(int symbolIndex)
|
||||
{
|
||||
datetime currentBarTime = iTime(m_symbols[symbolIndex], m_timeframe, 0);
|
||||
|
||||
if(currentBarTime != m_lastBarTime[symbolIndex])
|
||||
{
|
||||
m_lastBarTime[symbolIndex] = currentBarTime;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Calculate spread series for z-score |
|
||||
//+------------------------------------------------------------------+
|
||||
bool CalculateSpreadSeries(double &spreadSeries[], double ¤tSpread)
|
||||
{
|
||||
if(ArrayResize(spreadSeries, m_lookbackPeriod) != m_lookbackPeriod)
|
||||
return false;
|
||||
|
||||
// Get ordered price arrays
|
||||
double pricesAUDCAD[], pricesNZDCAD[], pricesAUDNZD[];
|
||||
|
||||
if(!m_priceBuffers[SYMBOL_AUDCAD].GetPricesOrdered(pricesAUDCAD) ||
|
||||
!m_priceBuffers[SYMBOL_NZDCAD].GetPricesOrdered(pricesNZDCAD) ||
|
||||
!m_priceBuffers[SYMBOL_AUDNZD].GetPricesOrdered(pricesAUDNZD))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate spread series: (AUDCAD/NZDCAD) - AUDNZD
|
||||
for(int i = 0; i < m_lookbackPeriod; i++)
|
||||
{
|
||||
if(pricesNZDCAD[i] == 0)
|
||||
{
|
||||
spreadSeries[i] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
double ratio = pricesAUDCAD[i] / pricesNZDCAD[i];
|
||||
spreadSeries[i] = ratio - pricesAUDNZD[i];
|
||||
}
|
||||
|
||||
// Current spread is the last element
|
||||
currentSpread = spreadSeries[m_lookbackPeriod - 1];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
//+------------------------------------------------------------------+
|
||||
//| Constructor |
|
||||
//+------------------------------------------------------------------+
|
||||
CCorrelationEngine()
|
||||
{
|
||||
m_lookbackPeriod = 250;
|
||||
m_timeframe = PERIOD_M15;
|
||||
m_updateIntervalSec = DEFAULT_CACHE_UPDATE_INTERVAL;
|
||||
m_isInitialized = false;
|
||||
m_isWarmedUp = false;
|
||||
m_lastCalculationTime = 0;
|
||||
|
||||
for(int i = 0; i < NUM_SYMBOLS; i++)
|
||||
{
|
||||
m_symbols[i] = "";
|
||||
m_lastBarTime[i] = 0;
|
||||
}
|
||||
|
||||
m_cache.Reset();
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Initialize the correlation engine |
|
||||
//+------------------------------------------------------------------+
|
||||
bool Initialize(const string &symbols[], int lookbackPeriod, ENUM_TIMEFRAMES timeframe, int updateInterval = 30)
|
||||
{
|
||||
// Validate lookback period
|
||||
if(lookbackPeriod < MIN_LOOKBACK_PERIOD || lookbackPeriod > MAX_LOOKBACK_PERIOD)
|
||||
{
|
||||
Logger.Error("Invalid lookback period: " + IntegerToString(lookbackPeriod) +
|
||||
". Must be " + IntegerToString(MIN_LOOKBACK_PERIOD) + "-" + IntegerToString(MAX_LOOKBACK_PERIOD));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_lookbackPeriod = lookbackPeriod;
|
||||
m_timeframe = timeframe;
|
||||
m_updateIntervalSec = updateInterval;
|
||||
|
||||
// Copy symbol names
|
||||
for(int i = 0; i < NUM_SYMBOLS; i++)
|
||||
{
|
||||
m_symbols[i] = symbols[i];
|
||||
}
|
||||
|
||||
// Load historical data for all symbols
|
||||
bool allLoaded = true;
|
||||
for(int i = 0; i < NUM_SYMBOLS; i++)
|
||||
{
|
||||
if(!LoadHistoricalPrices(i))
|
||||
{
|
||||
allLoaded = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!allLoaded)
|
||||
{
|
||||
Logger.Warning("Not all historical data loaded. Engine will warm up during trading.");
|
||||
}
|
||||
|
||||
m_isInitialized = true;
|
||||
m_isWarmedUp = allLoaded;
|
||||
|
||||
Logger.Info("Correlation Engine initialized - Lookback: " + IntegerToString(m_lookbackPeriod) +
|
||||
", Timeframe: " + EnumToString(m_timeframe));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Update price buffers (call on each tick or new bar) |
|
||||
//+------------------------------------------------------------------+
|
||||
void UpdatePriceBuffers()
|
||||
{
|
||||
if(!m_isInitialized)
|
||||
return;
|
||||
|
||||
// Check for new bar on each symbol
|
||||
for(int i = 0; i < NUM_SYMBOLS; i++)
|
||||
{
|
||||
if(IsNewBar(i))
|
||||
{
|
||||
// Get latest close price
|
||||
double price = iClose(m_symbols[i], m_timeframe, 1); // Previous bar close (completed)
|
||||
|
||||
if(price > 0)
|
||||
{
|
||||
m_priceBuffers[i].AddPrice(price, TimeCurrent());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if all buffers are warmed up
|
||||
if(!m_isWarmedUp)
|
||||
{
|
||||
bool allWarmedUp = true;
|
||||
for(int i = 0; i < NUM_SYMBOLS; i++)
|
||||
{
|
||||
if(!m_priceBuffers[i].isWarmedUp)
|
||||
{
|
||||
allWarmedUp = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_isWarmedUp = allWarmedUp;
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Calculate and update correlation cache |
|
||||
//+------------------------------------------------------------------+
|
||||
bool UpdateCorrelationCache(bool forceUpdate = false)
|
||||
{
|
||||
if(!m_isInitialized)
|
||||
return false;
|
||||
|
||||
// Check cache freshness
|
||||
datetime currentTime = TimeCurrent();
|
||||
if(!forceUpdate && (currentTime - m_lastCalculationTime) < m_updateIntervalSec)
|
||||
{
|
||||
return m_cache.isValid;
|
||||
}
|
||||
|
||||
// Check if warmed up
|
||||
if(!m_isWarmedUp)
|
||||
{
|
||||
m_cache.isValid = false;
|
||||
m_cache.invalidReason = "Price buffers not warmed up";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get ordered price arrays
|
||||
double pricesAUDCAD[], pricesNZDCAD[], pricesAUDNZD[];
|
||||
|
||||
if(!m_priceBuffers[SYMBOL_AUDCAD].GetPricesOrdered(pricesAUDCAD) ||
|
||||
!m_priceBuffers[SYMBOL_NZDCAD].GetPricesOrdered(pricesNZDCAD) ||
|
||||
!m_priceBuffers[SYMBOL_AUDNZD].GetPricesOrdered(pricesAUDNZD))
|
||||
{
|
||||
m_cache.isValid = false;
|
||||
m_cache.invalidReason = "Failed to get ordered prices";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate primary correlation (AUDCAD vs NZDCAD)
|
||||
m_cache.corrAUDCAD_NZDCAD = CalculatePearsonCorrelation(pricesAUDCAD, pricesNZDCAD, m_lookbackPeriod);
|
||||
|
||||
// Calculate secondary correlations (for validation)
|
||||
m_cache.corrAUDCAD_AUDNZD = CalculatePearsonCorrelation(pricesAUDCAD, pricesAUDNZD, m_lookbackPeriod);
|
||||
m_cache.corrNZDCAD_AUDNZD = CalculatePearsonCorrelation(pricesNZDCAD, pricesAUDNZD, m_lookbackPeriod);
|
||||
|
||||
// Calculate synthetic ratio and spread
|
||||
double currentAUDCAD = m_priceBuffers[SYMBOL_AUDCAD].GetPrice(0);
|
||||
double currentNZDCAD = m_priceBuffers[SYMBOL_NZDCAD].GetPrice(0);
|
||||
double currentAUDNZD = m_priceBuffers[SYMBOL_AUDNZD].GetPrice(0);
|
||||
|
||||
if(currentNZDCAD == 0)
|
||||
{
|
||||
m_cache.isValid = false;
|
||||
m_cache.invalidReason = "NZDCAD price is zero";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_cache.syntheticRatio = currentAUDCAD / currentNZDCAD;
|
||||
m_cache.actualAUDNZD = currentAUDNZD;
|
||||
m_cache.spreadValue = m_cache.syntheticRatio - currentAUDNZD;
|
||||
|
||||
// Calculate spread z-score
|
||||
double spreadSeries[];
|
||||
double currentSpread;
|
||||
|
||||
if(!CalculateSpreadSeries(spreadSeries, currentSpread))
|
||||
{
|
||||
m_cache.isValid = false;
|
||||
m_cache.invalidReason = "Failed to calculate spread series";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_cache.spreadMean = CalculateMean(spreadSeries, m_lookbackPeriod);
|
||||
m_cache.spreadStdDev = CalculateStdDev(spreadSeries, m_lookbackPeriod, m_cache.spreadMean);
|
||||
|
||||
if(m_cache.spreadStdDev == 0)
|
||||
{
|
||||
m_cache.isValid = false;
|
||||
m_cache.invalidReason = "Spread standard deviation is zero";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_cache.spreadZScore = (currentSpread - m_cache.spreadMean) / m_cache.spreadStdDev;
|
||||
|
||||
// Validate z-score
|
||||
if(!MathIsValidNumber(m_cache.spreadZScore))
|
||||
{
|
||||
m_cache.isValid = false;
|
||||
m_cache.invalidReason = "Z-score calculation resulted in invalid number";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update metadata
|
||||
m_cache.calculationTime = currentTime;
|
||||
m_cache.lookbackPeriod = m_lookbackPeriod;
|
||||
m_cache.isValid = true;
|
||||
m_cache.invalidReason = "";
|
||||
|
||||
m_lastCalculationTime = currentTime;
|
||||
|
||||
Logger.LogCorrelationData(m_cache);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Get current correlation data |
|
||||
//+------------------------------------------------------------------+
|
||||
void GetCorrelationData(CorrelationData &data)
|
||||
{
|
||||
data = m_cache;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Get primary correlation coefficient |
|
||||
//+------------------------------------------------------------------+
|
||||
double GetPrimaryCorrelation()
|
||||
{
|
||||
return m_cache.corrAUDCAD_NZDCAD;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Get current z-score |
|
||||
//+------------------------------------------------------------------+
|
||||
double GetSpreadZScore()
|
||||
{
|
||||
return m_cache.spreadZScore;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check if engine is ready for trading |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsReady()
|
||||
{
|
||||
return m_isInitialized && m_isWarmedUp && m_cache.isValid;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check if engine is warmed up |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsWarmedUp()
|
||||
{
|
||||
return m_isWarmedUp;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Get current prices for all symbols |
|
||||
//+------------------------------------------------------------------+
|
||||
void GetCurrentPrices(double &prices[])
|
||||
{
|
||||
if(ArrayResize(prices, NUM_SYMBOLS) != NUM_SYMBOLS)
|
||||
return;
|
||||
|
||||
for(int i = 0; i < NUM_SYMBOLS; i++)
|
||||
{
|
||||
prices[i] = SymbolInfoDouble(m_symbols[i], SYMBOL_BID);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Force recalculation of cache |
|
||||
//+------------------------------------------------------------------+
|
||||
void ForceRecalculation()
|
||||
{
|
||||
UpdateCorrelationCache(true);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // DBASKET_CORRELATIONENGINE_MQH
|
||||
//+------------------------------------------------------------------+
|
||||
Reference in New Issue
Block a user