mql5/Experts/Advisors/DualEA/Include/CorrelationManager.mqh
2025-09-20 02:27:35 -04:00

241 lignes
7,5 Kio
MQL5

//+------------------------------------------------------------------+
//| CorrelationManager.mqh |
//| FR-05: Correlation Exposure Caps Management |
//+------------------------------------------------------------------+
#property copyright "DualEA"
#property version "1.00"
//+------------------------------------------------------------------+
//| Correlation exposure management and filtering |
//+------------------------------------------------------------------+
class CCorrelationManager
{
private:
string m_symbol;
ENUM_TIMEFRAMES m_timeframe;
// Configuration
bool m_enabled;
double m_max_correlation;
int m_lookback_days;
int m_min_periods;
// Correlation cache
struct CorrelationData
{
string symbol_pair;
double correlation;
datetime last_update;
};
CorrelationData m_correlation_cache[];
public:
CCorrelationManager(const string symbol, const ENUM_TIMEFRAMES timeframe) :
m_symbol(symbol), m_timeframe(timeframe), m_enabled(false),
m_max_correlation(0.7), m_lookback_days(30), m_min_periods(20) {}
// Configuration
void SetEnabled(const bool enabled) { m_enabled = enabled; }
void SetMaxCorrelation(const double max_corr) { m_max_correlation = max_corr; }
void SetLookbackDays(const int days) { m_lookback_days = days; }
void SetMinPeriods(const int periods) { m_min_periods = periods; }
// Calculate correlation between two symbols
double CalculateCorrelation(const string symbol1, const string symbol2, const int periods)
{
if(symbol1 == symbol2) return 1.0;
double prices1[], prices2[];
ArrayResize(prices1, periods);
ArrayResize(prices2, periods);
// Get price data for both symbols
int copied1 = CopyClose(symbol1, m_timeframe, 0, periods, prices1);
int copied2 = CopyClose(symbol2, m_timeframe, 0, periods, prices2);
if(copied1 != periods || copied2 != periods)
return 0.0; // Unable to calculate
// Calculate returns
double returns1[], returns2[];
ArrayResize(returns1, periods - 1);
ArrayResize(returns2, periods - 1);
for(int i = 1; i < periods; i++)
{
returns1[i-1] = (prices1[i] - prices1[i-1]) / prices1[i-1];
returns2[i-1] = (prices2[i] - prices2[i-1]) / prices2[i-1];
}
// Calculate correlation coefficient
double sum1 = 0, sum2 = 0, sum1_sq = 0, sum2_sq = 0, sum_prod = 0;
int n = periods - 1;
for(int i = 0; i < n; i++)
{
sum1 += returns1[i];
sum2 += returns2[i];
sum1_sq += returns1[i] * returns1[i];
sum2_sq += returns2[i] * returns2[i];
sum_prod += returns1[i] * returns2[i];
}
double mean1 = sum1 / n;
double mean2 = sum2 / n;
double numerator = sum_prod - n * mean1 * mean2;
double denominator = MathSqrt((sum1_sq - n * mean1 * mean1) * (sum2_sq - n * mean2 * mean2));
if(denominator == 0.0) return 0.0;
return numerator / denominator;
}
// Get correlation from cache or calculate if needed
double GetCorrelation(const string other_symbol, bool force_recalc = false)
{
if(!m_enabled) return 0.0;
string pair_key = m_symbol + "|" + other_symbol;
datetime current_time = TimeCurrent();
// Check cache first
if(!force_recalc)
{
for(int i = 0; i < ArraySize(m_correlation_cache); i++)
{
if(m_correlation_cache[i].symbol_pair == pair_key)
{
// Use cached value if less than 1 hour old
if(current_time - m_correlation_cache[i].last_update < 3600)
{
return m_correlation_cache[i].correlation;
}
break;
}
}
}
// Calculate new correlation
int periods = m_lookback_days * 24; // Assuming H1 timeframe, adjust as needed
if(m_timeframe == PERIOD_M1) periods *= 60;
else if(m_timeframe == PERIOD_M5) periods *= 12;
else if(m_timeframe == PERIOD_M15) periods *= 4;
else if(m_timeframe == PERIOD_M30) periods *= 2;
else if(m_timeframe == PERIOD_D1) periods = m_lookback_days;
periods = MathMax(periods, m_min_periods);
double correlation = CalculateCorrelation(m_symbol, other_symbol, periods);
// Update cache
bool found = false;
for(int i = 0; i < ArraySize(m_correlation_cache); i++)
{
if(m_correlation_cache[i].symbol_pair == pair_key)
{
m_correlation_cache[i].correlation = correlation;
m_correlation_cache[i].last_update = current_time;
found = true;
break;
}
}
if(!found)
{
int size = ArraySize(m_correlation_cache);
ArrayResize(m_correlation_cache, size + 1);
m_correlation_cache[size].symbol_pair = pair_key;
m_correlation_cache[size].correlation = correlation;
m_correlation_cache[size].last_update = current_time;
}
return correlation;
}
// Check if correlation exposure is within limits
bool IsCorrelationAllowed(string &symbols_to_check[], string &reason, double &max_correlation_found)
{
reason = "ok";
max_correlation_found = 0.0;
if(!m_enabled)
return true;
string problematic_symbol = "";
for(int i = 0; i < ArraySize(symbols_to_check); i++)
{
if(symbols_to_check[i] == m_symbol) continue;
double correlation = MathAbs(GetCorrelation(symbols_to_check[i]));
if(correlation > max_correlation_found)
{
max_correlation_found = correlation;
problematic_symbol = symbols_to_check[i];
}
if(correlation > m_max_correlation)
{
reason = StringFormat("correlation_limit_exceeded_%.3f_with_%s", correlation, symbols_to_check[i]);
return false;
}
}
return true;
}
// Get list of currently open positions for correlation checking
void GetOpenPositionSymbols(string &symbols[])
{
ArrayResize(symbols, 0);
for(int i = 0; i < PositionsTotal(); i++)
{
if(PositionGetTicket(i) > 0)
{
string pos_symbol = PositionGetString(POSITION_SYMBOL);
// Add to array if not already present
bool found = false;
for(int j = 0; j < ArraySize(symbols); j++)
{
if(symbols[j] == pos_symbol)
{
found = true;
break;
}
}
if(!found)
{
int size = ArraySize(symbols);
ArrayResize(symbols, size + 1);
symbols[size] = pos_symbol;
}
}
}
}
// Main correlation gate check
bool CheckCorrelationLimits(string &reason, double &max_correlation)
{
reason = "ok";
max_correlation = 0.0;
if(!m_enabled)
return true;
string open_symbols[];
GetOpenPositionSymbols(open_symbols);
return IsCorrelationAllowed(open_symbols, reason, max_correlation);
}
// Clear correlation cache (for testing or manual refresh)
void ClearCache()
{
ArrayResize(m_correlation_cache, 0);
}
};