Advertisement IC Markets

Capping risk per theme, not just per trade

Master exponential money management, position sizing calculators, strict daily stop-loss limits, and overcoming FOMO on micro-timeframes.
FTtrader
Posts: 1800
Joined: Mon Aug 03, 2026 2:43 pm

Re: Capping risk per theme, not just per trade

Post by FTtrader »

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
     }
  }
//+------------------------------------------------------------------+
Recommended broker for automated trading & scalping IC Markets
FTtrader
Posts: 1800
Joined: Mon Aug 03, 2026 2:43 pm

Re: Capping risk per theme, not just per trade

Post by FTtrader »

This architecture calculates only the required pairs at the exact moment a signal fires, saving computational resources compared to running a full matrix on every tick. The MathAbs(r) check ensures the filter triggers on both highly positive (e.g., EURUSD vs GBPUSD) and highly negative (e.g., EURUSD vs USDCHF) correlations, keeping your overall exposure completely diversified.
FTtrader
Posts: 1800
Joined: Mon Aug 03, 2026 2:43 pm

Re: Capping risk per theme, not just per trade

Post by FTtrader »

To account for trade direction, you need to calculate the Directional Exposure rather than just the absolute correlation.By assigning a mathematical value to trade direction (+1 for Long, -1 for Short), you can multiply the raw correlation coefficient ($r$) by the directions of both trades.

The formula is:$Directional Risk = r \times D_{open} \times D_{signal}$

This elegantly handles all scenarios without complex if/else statements:

Positive Correlation (e.g., $r = +0.85$ for EURUSD & GBPUSD):

Long EUR (+1) & Long GBP (+1) = $+0.85 \times 1 \times 1 = +0.85$. (Risk compounds $\rightarrow$ Block)
Long EUR (+1) & Short GBP (-1) = $+0.85 \times 1 \times -1 = -0.85$. (Hedge $\rightarrow$ Allow)

Negative Correlation (e.g., $r = -0.85$ for EURUSD & USDCHF):

Long EUR (+1) & Short CHF (-1) = $-0.85 \times 1 \times -1 = +0.85$. (Risk compounds $\rightarrow$ Block)
Long EUR (+1) & Long CHF (+1) = $-0.85 \times 1 \times 1 = -0.85$. (Hedge $\rightarrow$ Allow)
FTtrader
Posts: 1800
Joined: Mon Aug 03, 2026 2:43 pm

Re: Capping risk per theme, not just per trade

Post by FTtrader »

If the resulting $Directional Risk$ exceeds your positive threshold, the trade is compounding your risk and should be rejected.

Here is the modified MQL5 implementation. The helper functions (PearsonCorrelation and GetNormalizedReturns) remain identical to the previous version, but the PassCorrelationFilter now requires the signal's order type.

Code: Select all

//+------------------------------------------------------------------+
//|                             DirectionalCorrelationFilter.mq5     |
//+------------------------------------------------------------------+
#property strict

input double CorrelationThreshold = 0.75; // Reject if directional risk is higher than this
input int LookbackBars = 100;
input int AtrPeriod = 14;
input ENUM_TIMEFRAMES MatrixTimeframe = PERIOD_H4;

// ... [Keep PearsonCorrelation() and GetNormalizedReturns() identical to previous script] ...

// Helper function to convert Order/Position type to +1 / -1 multiplier
int GetDirectionMultiplier(long type)
  {
   if(type == ORDER_TYPE_BUY || type == POSITION_TYPE_BUY) return 1;
   if(type == ORDER_TYPE_SELL || type == POSITION_TYPE_SELL) return -1;
   return 0; 
  }

// Core Filter: Checks proposed trade direction against open position directions
bool PassDirectionalCorrelationFilter(string signalSymbol, ENUM_ORDER_TYPE signalType)
  {
   double signalReturns[];
   if(!GetNormalizedReturns(signalSymbol, signalReturns))
     {
      Print("Error: Could not calculate returns for ", signalSymbol);
      return false; 
     }

   int signalDir = GetDirectionMultiplier(signalType);
   int total = PositionsTotal();
   
   for(int i = 0; i < total; i++)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket > 0)
        {
         string openSymbol = PositionGetString(POSITION_SYMBOL);
         if(openSymbol == signalSymbol) continue; 
         
         double openReturns[];
         if(GetNormalizedReturns(openSymbol, openReturns))
           {
            double r = PearsonCorrelation(signalReturns, openReturns);
            
            long openType = PositionGetInteger(POSITION_TYPE);
            int openDir = GetDirectionMultiplier(openType);
            
            // Calculate directional risk compounding factor
            double directionalRisk = r * signalDir * openDir;
            
            if(directionalRisk >= CorrelationThreshold)
              {
               PrintFormat("FILTERED: %s (%s) compounds risk (Factor: +%.2f) with open %s (%s).", 
                           signalSymbol, EnumToString(signalType), 
                           directionalRisk, 
                           openSymbol, EnumToString((ENUM_POSITION_TYPE)openType));
               return false;
              }
            else if (directionalRisk <= -CorrelationThreshold)
              {
               PrintFormat("HEDGE RECOGNIZED: %s (%s) hedges against open %s (%s). Trade Allowed.",
                           signalSymbol, EnumToString(signalType), 
                           openSymbol, EnumToString((ENUM_POSITION_TYPE)openType));
              }
           }
        }
     }
     
   PrintFormat("PASSED: %s (%s) has no dangerous directional risk compounding.", signalSymbol, EnumToString(signalType));
   return true;
  }

void OnStart()
  {
   // Example: A Buy signal fires for USDCHF on the M15 timeframe
   string targetTrade = "USDCHF";
   ENUM_ORDER_TYPE targetDirection = ORDER_TYPE_BUY;
   
   Print("Checking Directional Correlation...");
   if(PassDirectionalCorrelationFilter(targetTrade, targetDirection))
     {
      Print("Executing Trade...");
      // OrderSend logic here
     }
  }
//+------------------------------------------------------------------+
FXS Prop Desk
Posts: 1572
Joined: Wed Oct 07, 2026 9:52 am

Re: Capping risk per theme, not just per trade

Post by FXS Prop Desk »

FTtrader, using live correlation to define the theme is more flexible than a fixed list of pairs. A caution: correlation measured over a short window can swing a lot, so a position might be treated as a separate theme one hour and the same theme the next. Using a longer lookback for the theme decision, or requiring the threshold to hold on two different windows, would make the risk cap more stable without losing the idea.
FXS Official Team | The Idea Lab & Strategy
Shadow Trader
Posts: 5438
Joined: Wed Sep 09, 2026 5:19 pm

Re: Capping risk per theme, not just per trade

Post by Shadow Trader »

A scalper who has to click through a menu to close a trade loses a second or two. A hotkey cuts that to a fraction. Across hundreds of trades, that adds up in a way that matters.

Setting it up well takes thought. A few ideas that help:

- One key per action, never combinations that could be hit by accident
- A separate key for buy and sell, spaced apart on the keyboard
- A "close all" key kept far from the others, or one that needs a double press
- A fixed lot size per template, so a hotkey never sends the wrong volume
- A test run on a demo account for a full session before going live

Macros are more dangerous. A single key that sends a buy, sets a stop and sets a target sounds efficient. If any step fails, the position is left in a half-built state, with no stop in place.

Checking the result of each step before moving on is slower, but safer.

Does a hotkey setup exist that has never caused a mistake, or is it always a trade-off between speed and safety?
Post Reply