241 lines
7.5 KiB
MQL5
241 lines
7.5 KiB
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);
| |||
}
| |||
};
|