Re: Capping risk per theme, not just per trade
Posted: Thu Oct 22, 2026 7:26 pm
Here is the modular MQL5 implementation to drop into your EA's signal evaluation block.
Code: Select all
//+------------------------------------------------------------------+
//| CorrelationTradeFilter.mq5 |
//+------------------------------------------------------------------+
#property strict
input double CorrelationThreshold = 0.75; // Reject if absolute correlation is higher than this
input int LookbackBars = 100;
input int AtrPeriod = 14;
input ENUM_TIMEFRAMES MatrixTimeframe = PERIOD_H4;
// 1. Calculate Pearson correlation between two arrays
double PearsonCorrelation(const double &x[], const double &y[])
{
int n = ArraySize(x);
if(n == 0 || n != ArraySize(y)) return 0.0;
double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0, sumY2 = 0;
for(int i = 0; i < n; i++)
{
sumX += x[i]; sumY += y[i];
sumXY += x[i] * y[i];
sumX2 += x[i] * x[i]; sumY2 += y[i] * y[i];
}
double numerator = (n * sumXY) - (sumX * sumY);
double denominator = MathSqrt(((n * sumX2) - (sumX * sumX)) * ((n * sumY2) - (sumY * sumY)));
if(denominator == 0) return 0.0;
return numerator / denominator;
}
// 2. Fetch ATR-normalized returns for a single symbol
bool GetNormalizedReturns(string symbol, double &adjReturns[])
{
double closes[], atrs[];
if(CopyClose(symbol, MatrixTimeframe, 0, LookbackBars + 1, closes) < LookbackBars + 1) return false;
int atrHandle = iATR(symbol, MatrixTimeframe, AtrPeriod);
if(atrHandle == INVALID_HANDLE || CopyBuffer(atrHandle, 0, 0, LookbackBars, atrs) < LookbackBars) return false;
ArrayResize(adjReturns, LookbackBars);
for(int j = 0; j < LookbackBars; j++)
{
double priceChange = closes[j+1] - closes[j];
adjReturns[j] = (atrs[j] > 0) ? (priceChange / atrs[j]) : 0.0;
}
IndicatorRelease(atrHandle);
return true;
}
// 3. Core Filter: Checks proposed trade against all open positions
bool PassCorrelationFilter(string signalSymbol)
{
double signalReturns[];
if(!GetNormalizedReturns(signalSymbol, signalReturns))
{
Print("Error: Could not calculate returns for ", signalSymbol);
return false; // Block trade if data is missing
}
int total = PositionsTotal();
for(int i = 0; i < total; i++)
{
ulong ticket = PositionGetTicket(i);
if(ticket > 0)
{
string openSymbol = PositionGetString(POSITION_SYMBOL);
// Skip if it's the exact same symbol (handled by max-positions logic instead)
if(openSymbol == signalSymbol) continue;
double openReturns[];
if(GetNormalizedReturns(openSymbol, openReturns))
{
double r = PearsonCorrelation(signalReturns, openReturns);
// Check absolute correlation against the threshold
if(MathAbs(r) >= CorrelationThreshold)
{
PrintFormat("FILTERED: %s is highly correlated (r = %.2f) with open position %s.", signalSymbol, r, openSymbol);
return false;
}
}
}
}
PrintFormat("PASSED: %s has no dangerous correlations with current open risk.", signalSymbol);
return true;
}
// 4. Test execution
void OnStart()
{
// Assuming a signal fired for AUDUSD, check if it's safe to open
string targetTrade = "AUDUSD";
if(PassCorrelationFilter(targetTrade))
{
Print("Executing Trade...");
// OrderSend() logic goes here
}
}
//+------------------------------------------------------------------+