//+------------------------------------------------------------------+ //| 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); } };