preview
Formulating Dynamic Multi-Pair EA (Part 7): Cross-Pair Correlation Mapping for Real-Time Trade Filtering

Formulating Dynamic Multi-Pair EA (Part 7): Cross-Pair Correlation Mapping for Real-Time Trade Filtering

MetaTrader 5Examples |
277 0
Hlomohang John Borotho
Hlomohang John Borotho

Table of Contents:

  1. Introduction
  2. System Overview and Understanding
  3. Getting Started
  4. Backtest Results
  5. Conclusion



Introduction

You launch a multi-pair EA and, at first glance, everything appears perfectly structured: each symbol generates clean signals, entries are precise, and the logic performs exactly as designed. Yet once trades begin stacking across multiple instruments, an uncomfortable pattern emerges. What looked like diversification turns into concentrated exposure. The EA opens positions on pairs that historically move together, effectively duplicating risk. When correlations tighten—as they often do during volatility spikes—drawdowns accelerate, not because the signals were wrong individually, but because the portfolio became structurally imbalanced. The issue is no longer signal accuracy; it is relationship awareness.

This is precisely where Part 7 introduces a structural shift in thinking. Instead of evaluating each pair in isolation, we embed real-time cross-pair correlation mapping directly into the decision engine. Before any trade is executed, the EA now evaluates how that position interacts with existing exposure across the trading universe. If the new signal increases correlated risk or conflicts with the broader directional structure, it can be filtered out. While Part 6 concentrated on execution efficiency—optimizing spread sensitivity and cost ranking—this section elevates the framework to portfolio-level intelligence. The priority moves from cost efficiency alone to directional harmony, exposure cohesion, and dynamic risk control across the entire multi-pair ecosystem.



System Overview and Understanding

Our EA system is designed as a centralized portfolio engine that monitors, evaluates, and trades multiple symbols from a single coordinated framework. Instead of treating each pair independently, the system maintains structured symbol data, synchronized price inputs, and a shared decision pipeline that processes signals, execution filters, and risk parameters collectively. This architecture enables consistent logic application across all instruments while maintaining flexibility for symbol-specific metrics such as volatility, spread conditions, and signal strength.

System Overview

At its core, the EA integrates real-time cross-pair correlation mapping to enhance trade filtering and portfolio balance. Before executing new positions, the system evaluates statistical relationships between symbols to prevent redundant exposure and reduce compounded directional risk. By combining structured symbol management, matrix-based correlation indexing, and dynamic filtering within the OnTick cycle, the EA operates not just as a trade generator, but as a portfolio-aware decision engine that continuously adapts to evolving inter-market relationships.


Getting Started

//+------------------------------------------------------------------+
//|                                       Cross-Pair Correlation.mq5 |
//|                        GIT under Copyright 2025, MetaQuotes Ltd. |
//|                     https://www.mql5.com/en/users/johnhlomohang/ |
//+------------------------------------------------------------------+
#property copyright "GIT under Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/en/users/johnhlomohang/"
#property version   "1.00"

#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>

CTrade trade;
CPositionInfo posInfo;

//--- Input parameters
input string Symbols                = "XAUUSD,GBPUSD,USDJPY,USDZAR"; // Symbols to monitor (comma separated)
input ENUM_TIMEFRAMES CorrTimeframe = PERIOD_M15;                    // Timeframe for correlation
input int CorrelationPeriod         = 100;                           // Bars for correlation calculation
input double CorrelationThreshold   = 0.7;                           // Strong correlation threshold
input double MaxCorrelationExposure = 1.5;                           // Max net correlated exposure

//--- Signal parameters (simple MA crossover)
input ENUM_TIMEFRAMES SignalTimeframe = PERIOD_M15;                  // Timeframe for signal
input int    BiasLookbackBars         = 20;
input double BiasThreshold            = 0.001;                       // 0.1%
input int FastMAPeriod                = 10;                          // Fast MA period
input int SlowMAPeriod                = 30;                          // Slow MA period

//--- Trade execution
input double LotSize                  = 0.1;                         // Fixed lot size
input int    StopLoss_Pips            = 30;                          // Stop Loss in pips
input int    TakeProfit_Pips          = 60;                          // Take Profit in pips
input int    MagicNumber              = 123456;                      // EA magic number
input int    Slippage                 = 30;                          // Slippage in points

//--- Optional features
input bool   UseHeatmap                = false;                      // Draw correlation heatmap (optional)

Getting started, we establish the foundational structure and configuration of the EA system by importing the necessary trading classes (CTrade for execution and CPositionInfo for position tracking) and defining all core input parameters that control system behavior. We begin by specifying the list of symbols to monitor, the timeframe and lookback period for computing cross-pair correlations, and thresholds that determine when correlations are considered strong and when overall correlated exposure becomes excessive. This ensures the EA operates with portfolio awareness from the outset.

We then define the signal-generation logic using a moving average crossover framework enhanced by a directional bias filter, along with complete trade execution settings including lot size, stop-loss, take-profit, slippage, and magic number for position identification. We also include an option for visualization control of the correlation heatmap.

//+------------------------------------------------------------------+
//| Symbol Data Structure                                            |
//+------------------------------------------------------------------+
struct SymbolData
  {
   string name;                        
   double close[];                     
  };

//+------------------------------------------------------------------+
//| Global vars                                                      |
//+------------------------------------------------------------------+
int        m_numSymbols;
SymbolData m_symbols[];
double     m_correlationMatrix[];
datetime   m_lastBarTime;
bool       m_correlationValid;

int        m_fastMA[];
int        m_slowMA[];
datetime   m_lastSignalBar[];

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Ensure current chart symbol is included
   string list = Symbols;

   if(StringFind(list,_Symbol) < 0)
      list = list + "," + _Symbol;

   //--- Split symbol list
   string parts[];
   int num = StringSplit(list,',',parts);

   if(num <= 0)
     {
      Print("No symbols specified!");
      return(INIT_FAILED);
     }

   m_numSymbols = num;

   //--- Resize containers
   ArrayResize(m_symbols,m_numSymbols);

   for(int i=0; i<m_numSymbols; i++)
     {
      string sym = parts[i];
      StringTrimRight(sym);

      if(!SymbolSelect(sym,true))
        {
         Print("Failed to select symbol: ",sym);
         return(INIT_FAILED);
        }

      m_symbols[i].name = sym;

      //--- Resize close buffer
      ArrayResize(m_symbols[i].close,CorrelationPeriod);
      ArraySetAsSeries(m_symbols[i].close,true);
     }

   ArrayResize(m_correlationMatrix,m_numSymbols*m_numSymbols);
   ArrayResize(m_fastMA,m_numSymbols);
   ArrayResize(m_slowMA,m_numSymbols);
   ArrayResize(m_lastSignalBar,m_numSymbols);

   //--- Create MA handles
   for(int i=0; i<m_numSymbols; i++)
     {
      m_fastMA[i] = iMA(m_symbols[i].name,
                        SignalTimeframe,
                        FastMAPeriod,
                        0,
                        MODE_SMA,
                        PRICE_CLOSE);

      m_slowMA[i] = iMA(m_symbols[i].name,
                        SignalTimeframe,
                        SlowMAPeriod,
                        0,
                        MODE_SMA,
                        PRICE_CLOSE);

      if(m_fastMA[i]==INVALID_HANDLE || m_slowMA[i]==INVALID_HANDLE)
        {
         Print("Failed to create MA handle for ",m_symbols[i].name);
         return(INIT_FAILED);
        }

      m_lastSignalBar[i] = 0;
     }

   m_lastBarTime      = 0;
   m_correlationValid = false;

   EventSetTimer(60);

   trade.SetExpertMagicNumber(MagicNumber);

   Print("Multi-Pair Correlation EA initialized with ",m_numSymbols," symbols.");

   return(INIT_SUCCEEDED);
  }

This section defines a structured data model for the EA through the SymbolData struct, which encapsulates each symbol’s name and its dedicated close-price buffer used for correlation calculations. By organizing symbol-related data into a structure and storing multiple instances inside the m_symbols[] array, the EA adopts a scalable, object-like design that cleanly separates per-symbol data from global system logic. Supporting this architecture are several global arrays and state variables, including a flattened one-dimensional correlation matrix, moving average handles for signal generation, per-symbol signal timing controls, and flags that track whether the correlation data is current and valid.

Inside OnInit(), the EA dynamically prepares its multi-symbol environment. It first ensures the chart’s current symbol is included in the monitored list, then parses the comma-separated input string into individual symbols. Each symbol is validated and selected via SymbolSelect(), after which its internal close-price buffer is resized and configured as a time series array for proper indexing. At the same time, the EA resizes the flattened correlation matrix to accommodate all symbol pair combinations (n × n) and prepares arrays that will later store indicator handles and signal timing data. This dynamic resizing allows the EA to scale automatically depending on how many symbols the user specifies.

The last portion of initialization creates the fast and slow-moving average indicator handles for each symbol using iMA(), enabling independent signal generation across the portfolio. It also initializes state control variables such as m_lastBarTime and m_correlationValid, sets a timer to trigger periodic correlation recalculation, and assigns the expert’s magic number for trade identification.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   //--- Release MA handles
   for(int i=0; i<m_numSymbols; i++)
     {
      if(m_fastMA[i]!=INVALID_HANDLE)
         IndicatorRelease(m_fastMA[i]);

      if(m_slowMA[i]!=INVALID_HANDLE)
         IndicatorRelease(m_slowMA[i]);
     }

   EventKillTimer();
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   if(IsNewBar())
     {
      UpdateCorrelationMatrix();
      m_correlationValid = true;
     }

   for(int i=0; i<m_numSymbols; i++)
     {
      string sym = m_symbols[i].name;
      datetime currBar = iTime(sym,SignalTimeframe,0);

      if(currBar != m_lastSignalBar[i])
        {
         int signal = CheckSignal(i);

         if(signal != 0)
           {
            if(IsTradeAllowed(i,signal))
               ExecuteTrade(i,signal);
           }

         m_lastSignalBar[i] = currBar;
        }
     }
  }

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
   if(!m_correlationValid)
      UpdateCorrelationMatrix();
  }

The OnDeinit() function ensures proper resource cleanup when the EA is removed or the platform is shut down. It loops through all monitored symbols and safely releases the moving average indicator handles using IndicatorRelease(), preventing memory leaks and freeing terminal resources. It also stops the timer with EventKillTimer(), ensuring no background events continue running after the EA is deactivated. This structured shutdown process keeps the system efficient and avoids leftover indicator or timer processes that could affect performance.

The OnTick() function is the core execution engine of the EA. It first checks whether a new master bar has formed and, if so, updates the cross-pair correlation matrix to maintain real-time portfolio awareness. Then, for each symbol, it detects the formation of a new signal bar to avoid repeated processing within the same candle. When a new bar is detected, the EA evaluates trading signals, verifies whether the trade is allowed based on correlation and exposure filters, and executes the trade if conditions are satisfied. The OnTimer() function serves as a safety mechanism, ensuring the correlation matrix is refreshed if it hasn’t been updated, reinforcing the system’s reliability and synchronization across symbols.

//+------------------------------------------------------------------+
//| Helper function to get index in flattened matrix                 |
//+------------------------------------------------------------------+
int CorrIndex(int i,int j)
  {
   return(i*m_numSymbols + j);
  }

//+------------------------------------------------------------------+
//| Update close prices for all symbols                              |
//+------------------------------------------------------------------+
void UpdateClosePrices()
  {
   for(int i=0; i<m_numSymbols; i++)
     {
      if(CopyClose(m_symbols[i].name,
                   CorrTimeframe,
                   0,
                   CorrelationPeriod,
                   m_symbols[i].close) <= 0)
        {
         Print("Failed to copy close for ",m_symbols[i].name);
        }
     }
  }

//+------------------------------------------------------------------+
//| Calculate Pearson correlation between two arrays                 |
//+------------------------------------------------------------------+
double CalculateCorrelation(double &x[],double &y[],int length)
  {
   double sumX=0,sumY=0,sumXY=0,sumX2=0,sumY2=0;

   for(int i=0; i<length; i++)
     {
      sumX  += x[i];
      sumY  += y[i];
      sumXY += x[i]*y[i];
      sumX2 += x[i]*x[i];
      sumY2 += y[i]*y[i];
     }

   double numerator   = length*sumXY - sumX*sumY;
   double denominator = MathSqrt((length*sumX2 - sumX*sumX) *
                                 (length*sumY2 - sumY*sumY));

   if(denominator == 0)
      return(0);

   return(numerator/denominator);
  }

//+------------------------------------------------------------------+
//| Update the entire correlation matrix                             |
//+------------------------------------------------------------------+
void UpdateCorrelationMatrix()
 {
   UpdateClosePrices();

   for(int i = 0; i < m_numSymbols; i++)
    {
      for(int j = 0; j < m_numSymbols; j++)
       {
         if(i == j)
          {
            m_correlationMatrix[CorrIndex(i,j)] = 1.0;
            continue;
          }

         double corr = CalculateCorrelation(
                        m_symbols[i].close,
                        m_symbols[j].close,
                        CorrelationPeriod);

         m_correlationMatrix[CorrIndex(i,j)] = corr;
       }
    }

   //--- Optional heatmap drawing
   if(UseHeatmap)
      DrawHeatmap();
 }

//+------------------------------------------------------------------+
//| Get symbol index by name                                         |
//+------------------------------------------------------------------+
int GetSymbolIndex(string sym)
 {
   for(int i = 0; i < m_numSymbols; i++)
    {
      if(m_symbols[i].name == sym)  
         return(i);
    }
   return(-1);
 }

This section provides the mathematical and structural backbone of the EA’s cross-pair correlation engine. The CorrIndex() helper function converts two-dimensional matrix coordinates (i, j) into a one-dimensional array index, allowing the correlation matrix to be stored efficiently as a flattened array. UpdateClosePrices() then retrieves the latest close prices for each monitored symbol using CopyClose(), ensuring that all correlation calculations are based on synchronized and up-to-date market data. The CalculateCorrelation() function implements the Pearson correlation formula manually, computing the statistical relationship between two price series by aggregating sums, cross-products, and squared values before applying the normalized correlation equation.

The UpdateCorrelationMatrix() function ties everything together by first refreshing price data, then looping through all symbol pairs to compute and store their correlations in the flattened matrix. Diagonal elements are set to 1.0 to represent perfect self-correlation, while off-diagonal elements are calculated dynamically using the Pearson function. If enabled, the heatmap visualization is updated immediately after recalculation, keeping the graphical representation aligned with the latest statistics. The GetSymbolIndex() provides a simple lookup utility that maps a symbol name to its internal array position, enabling efficient interaction between trade logic, exposure control, and the correlation matrix.

//+------------------------------------------------------------------+
//| Get bias (bullish/bearish) for a symbol based on price change    |
//+------------------------------------------------------------------+
int GetBias(int idx)
 {
   //--- Safety checks
   if(idx < 0 || idx >= m_numSymbols)
      return(0);

   if(BiasLookbackBars >= CorrelationPeriod)
      return(0);

   if(m_symbols[idx].close[BiasLookbackBars] == 0.0)
      return(0);

   double curr = m_symbols[idx].close[0];
   double prev = m_symbols[idx].close[BiasLookbackBars];

   if(prev == 0.0)
      return(0);

   double change = (curr - prev) / prev;

   if(change > BiasThreshold)
      return(1);   // Bullish

   if(change < -BiasThreshold)
      return(-1);  // Bearish

   return(0);
 }

//+------------------------------------------------------------------+
//| Check if a trade is allowed based on correlation rules           |
//+------------------------------------------------------------------+
bool IsTradeAllowed(int index, int signal)
 {
   string sym = m_symbols[index].name;

   //--- Already in position?
   if(PositionSelect(sym))
      return(false);

   if(!m_correlationValid)
      return(false);

   //--- Convert signal to direction
   ENUM_ORDER_TYPE direction;
   if(signal == 1)
      direction = ORDER_TYPE_BUY;
   else if(signal == -1)
      direction = ORDER_TYPE_SELL;
   else
      return(false);

   return(PassesCorrelationFilter(index, direction));
 }

//+------------------------------------------------------------------+
//| Generate trading signal (MA crossover)                           |
//+------------------------------------------------------------------+
int CheckSignal(int index)
 {
   double fast[], slow[];
   ArrayResize(fast, 2);
   ArrayResize(slow, 2);
   
   ArraySetAsSeries(fast, true);
   ArraySetAsSeries(slow, true);

   if(CopyBuffer(m_fastMA[index], 0, 0, 2, fast) < 2) return 0;
   if(CopyBuffer(m_slowMA[index], 0, 0, 2, slow) < 2) return 0;

   //--- Buy crossover: fast MA crosses above slow MA
   if(fast[1] <= slow[1] && fast[0] > slow[0])
      return(1);

   //--- Sell crossover: fast MA crosses below slow MA
   if(fast[1] >= slow[1] && fast[0] < slow[0])
      return(-1);

   return(0);
 }

In this section, we introduce directional filtering and trade validation logic within the EA. The GetBias() function determines whether a symbol exhibits a bullish or bearish tendency by measuring percentage price change over a defined lookback period. It includes safety checks to prevent invalid indexing or division errors, then compares the calculated change against a configurable threshold. If price appreciation exceeds the threshold, the function returns a bullish signal (1); if depreciation exceeds it negatively, it returns bearish (-1); otherwise, it remains neutral (0). This bias mechanism helps the EA incorporate broader directional context beyond short-term crossover signals.

The IsTradeAllowed() function acts as a protective gatekeeper before any execution occurs. It blocks trades if a position already exists on the symbol, if the correlation matrix is outdated, or if the signal is invalid. Once basic validation passes, it converts the signal into an order direction and forwards the decision to the correlation filter for portfolio-level exposure control. Meanwhile, CheckSignal() generates entry signals using a classic moving average crossover approach by comparing the most recent and previous values of fast and slow MAs. Together, these functions ensure that trades are not only technically triggered but also contextually validated within both directional and portfolio-aware frameworks.

//+------------------------------------------------------------------+
//| Calculate pip size for different instruments                     |
//+------------------------------------------------------------------+
double CalculatePipSize(string symbol)
 {
   int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
   double point = SymbolInfoDouble(symbol, SYMBOL_POINT);

   //--- Detect pip size automatically
   if(StringFind(symbol, "JPY") != -1)              // JPY pairs
      return((digits == 3) ? point * 10 : point);
   else if(StringFind(symbol, "XAU") != -1 || StringFind(symbol, "GOLD") != -1)  // Metals
      return(0.10);
   else if(StringFind(symbol, "BTC") != -1 || StringFind(symbol, "ETH") != -1)   // Cryptos
      return(point * 100.0);
   else if(StringFind(symbol, "US") != -1 && digits <= 2)                         // Indices (e.g. US30)
      return(point);
   else
      return((digits == 3 || digits == 5) ? point * 10 : point);                   // Default Forex
 }

//+------------------------------------------------------------------+
//| Execute a trade (market order) with SL/TP                        |
//+------------------------------------------------------------------+
void ExecuteTrade(int index, int signal)
 {
   string sym = m_symbols[index].name;
   double pipSize = CalculatePipSize(sym);
   double price, slPrice, tpPrice;

   if(signal == 1) // BUY
    {
      price = SymbolInfoDouble(sym, SYMBOL_ASK);
      slPrice = price - StopLoss_Pips * pipSize;
      tpPrice = price + TakeProfit_Pips * pipSize;
      if(trade.Buy(LotSize, sym, price, slPrice, tpPrice, "Correlation Filter EA"))
         Print("BUY executed on ", sym, " SL: ", DoubleToString(slPrice, _Digits), " TP: ", DoubleToString(tpPrice, _Digits));
      else
         Print("BUY failed on ", sym, " Error: ", GetLastError());
    }
   else if(signal == -1) // SELL
    {
      price = SymbolInfoDouble(sym, SYMBOL_BID);
      slPrice = price + StopLoss_Pips * pipSize;
      tpPrice = price - TakeProfit_Pips * pipSize;
      if(trade.Sell(LotSize, sym, price, slPrice, tpPrice, "Correlation Filter EA"))
         Print("SELL executed on ", sym, " SL: ", DoubleToString(slPrice, _Digits), " TP: ", DoubleToString(tpPrice, _Digits));
      else
         Print("SELL failed on ", sym, " Error: ", GetLastError());
    }
 }

//+------------------------------------------------------------------+
//| Correlation filter logic                                         |
//+------------------------------------------------------------------+
bool PassesCorrelationFilter(int symbolIndex, ENUM_ORDER_TYPE direction)
 {
   for(int j = 0; j < m_numSymbols; j++)
    {
      if(j == symbolIndex)
         continue;

      double corr = m_correlationMatrix[CorrIndex(symbolIndex, j)];

      if(MathAbs(corr) >= CorrelationThreshold)
       {
         //--- Check if correlated symbol already has open position
         if(PositionSelect(m_symbols[j].name))
          {
            ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

            //--- Same direction stacking
            if(corr > 0)
             {
               if((direction == ORDER_TYPE_BUY && posType == POSITION_TYPE_BUY) ||
                  (direction == ORDER_TYPE_SELL && posType == POSITION_TYPE_SELL))
                {
                  Print("Blocked by positive correlation with ", m_symbols[j].name);
                  return(false);
                }
             }

            //--- Inverse stacking
            if(corr < 0)
             {
               if((direction == ORDER_TYPE_BUY && posType == POSITION_TYPE_SELL) ||
                  (direction == ORDER_TYPE_SELL && posType == POSITION_TYPE_BUY))
                {
                  Print("Blocked by inverse correlation with ", m_symbols[j].name);
                  return(false);
                }
             }
          }
       }
    }

   return(true);
 }

This section of code handles trade execution, risk sizing, and portfolio-aware filtering, forming the core of the EA’s real-time decision engine. The CalculatePipSize() function determines the correct pip value for each instrument type, accounting for differences across Forex pairs, JPY crosses, metals, cryptocurrencies, and indices. By adjusting pip calculations based on symbol conventions, the EA ensures that stop-loss and take-profit levels are applied accurately, maintaining consistent risk control across diverse markets.

The ExecuteTrade() function implements market orders with dynamically calculated stop-loss and take-profit levels using the pip size from CalculatePipSize(). It differentiates between BUY and SELL signals, calculates the appropriate entry, SL, and TP prices, and executes the trade using the CTrade class. Each execution is logged with detailed feedback, including whether the order succeeded or failed, and prints the calculated SL/TP levels for transparency, ensuring both operational and risk visibility for the trader.

The PassesCorrelationFilter() function integrates portfolio-level risk management by checking whether a new trade would violate correlation exposure rules. For each symbol, it compares the current signal with all other open positions using the precomputed correlation matrix. Trades are blocked if highly correlated symbols already have positions in the same or inverse direction, depending on the correlation sign. This mechanism prevents redundant or compounding exposure, ensuring that trades are executed only when they maintain balanced risk across the multi-symbol portfolio, effectively aligning individual signals with overall portfolio safety.

//+------------------------------------------------------------------+
//| Convert symbol name to short display format                      |
//+------------------------------------------------------------------+
string GetDisplayName(string symbol)
 {
   //--- If symbol contains "USD", replace it with "$"
   //--- Examples: XAUUSD -> XAU$, GBPUSD -> GBP$, USDJPY -> $JPY, USDZAR -> $ZAR
   int pos = StringFind(symbol, "USD");
   if(pos >= 0)
    {
      if(pos == 0)  // USD at beginning
         return("$" + StringSubstr(symbol, 3));
      else          // USD at end
         return(StringSubstr(symbol, 0, pos) + "$");
    }
   return(symbol); // fallback
 }

//+------------------------------------------------------------------+
//| Draw a correlation heatmap with row and column labels            |
//+------------------------------------------------------------------+
void DrawHeatmap()
 {
   ObjectsDeleteAll(0, "CorrHeatmap_");

   string objName = "CorrHeatmap_";

   int cellSize = 45;                // Slightly larger for clean spacing
   int startX   = 90;               // More left margin for row labels
   int startY   = 60;

   int half = cellSize / 2;

   //==============================
   // COLUMN LABELS (TOP)
   //==============================
   for(int j = 0; j < m_numSymbols; j++)
    {
      string label = objName + "col_" + IntegerToString(j);
      string dispName = GetDisplayName(m_symbols[j].name);

      ObjectCreate(0, label, OBJ_LABEL, 0, 0, 0);

      ObjectSetInteger(0, label, OBJPROP_XDISTANCE,
                       startX + j * cellSize + half);

      ObjectSetInteger(0, label, OBJPROP_YDISTANCE,
                       startY - half);

      ObjectSetInteger(0, label, OBJPROP_ANCHOR, ANCHOR_CENTER);

      ObjectSetString(0, label, OBJPROP_TEXT, dispName);
      ObjectSetInteger(0, label, OBJPROP_COLOR, clrBlue);
      ObjectSetInteger(0, label, OBJPROP_FONTSIZE, 10);
    }

   //==============================
   // ROW LABELS (LEFT)
   //==============================
   for(int i = 0; i < m_numSymbols; i++)
    {
      string label = objName + "row_" + IntegerToString(i);
      string dispName = GetDisplayName(m_symbols[i].name);

      ObjectCreate(0, label, OBJ_LABEL, 0, 0, 0);

      ObjectSetInteger(0, label, OBJPROP_XDISTANCE,
                       startX - half);

      ObjectSetInteger(0, label, OBJPROP_YDISTANCE,
                       startY + i * cellSize + half);

      ObjectSetInteger(0, label, OBJPROP_ANCHOR, ANCHOR_CENTER);

      ObjectSetString(0, label, OBJPROP_TEXT, dispName);
      ObjectSetInteger(0, label, OBJPROP_COLOR, clrBlue);
      ObjectSetInteger(0, label, OBJPROP_FONTSIZE, 10);
    }

   //==============================
   // CORRELATION MATRIX
   //==============================
   for(int i = 0; i < m_numSymbols; i++)
    {
      for(int j = 0; j < m_numSymbols; j++)
       {
         string label = objName + IntegerToString(i) + "_" + IntegerToString(j);

         double corr = m_correlationMatrix[CorrIndex(i, j)];
         string text = DoubleToString(corr, 2);

         ObjectCreate(0, label, OBJ_LABEL, 0, 0, 0);

         ObjectSetInteger(0, label, OBJPROP_XDISTANCE,
                          startX + j * cellSize + half);

         ObjectSetInteger(0, label, OBJPROP_YDISTANCE,
                          startY + i * cellSize + half);

         ObjectSetInteger(0, label, OBJPROP_ANCHOR, ANCHOR_CENTER);

         ObjectSetString(0, label, OBJPROP_TEXT, text);
         ObjectSetInteger(0, label, OBJPROP_FONTSIZE, 10);

         //--- Color coding
         if(corr > 0.7)
            ObjectSetInteger(0, label, OBJPROP_COLOR, clrLime);
         else if(corr < -0.7)
            ObjectSetInteger(0, label, OBJPROP_COLOR, clrRed);
         else
            ObjectSetInteger(0, label, OBJPROP_COLOR, clrBlack);
       }
    }
 }
//+------------------------------------------------------------------+

Here, we focus on providing a clear and visually intuitive representation of the cross-pair correlations through a heatmap interface. The GetDisplayName() function simplifies symbol names by replacing "USD" with the "$" sign for readability, creating concise labels like XAU$ or $JPY. These display names are used as both row and column headers in the heatmap, making it easy to identify symbols at a glance. The labels are carefully positioned and centered relative to the matrix cells, ensuring alignment with the correlation values displayed.

The DrawHeatmap() function itself constructs a full matrix visualization by iterating over all symbol pairs. For each cell, it draws a background rectangle whose color intensity dynamically reflects the magnitude and direction of the correlation: green for positive correlations, red for negative, and light gray for neutral. Over each colored cell, the actual numeric correlation value is displayed, with text color adjusted for contrast based on the correlation. This dual representation of color and numeric value allows traders to quickly assess relationships between symbols while maintaining precise, readable information, effectively turning raw statistical data into an actionable visual tool.



Back Test Results

The testing was conducted across roughly a 2-month testing window from 01 December 2025 to 30 January 2026, with the following settings:

Now the equity curve and the backtest results:



Conclusion

We have moved far beyond a “regular” multi-pair signal generator and transformed the system into a true portfolio-level decision engine. Symbol data is synchronized across instruments, price buffers are managed structurally, and a real-time Pearson correlation matrix is continuously rebuilt and injected directly into the final execution layer. The key evolution is not just calculation—it is integration. Correlation is no longer something observed externally; it now actively participates in the last checkpoint before any order reaches the market.

The result is a scalable EA framework where instruments can be added seamlessly, correlations are recalculated on every new bar (or timer event) to reflect current market statistics, and every MA crossover signal must pass through a portfolio exposure filter. Trades are blocked when strong positive correlation would amplify existing directional risk, and also when strong negative correlation would create hidden mirror exposure against an already open position. The heatmap adds an immediate visual confirmation layer, ensuring transparency and confidence that the matrix updates and thresholds behave as intended.

The bottom line: you now have a structured method for embedding correlation-based exposure control directly into a multi-pair EA, achieving smoother and more controlled portfolio behavior. Instead of manually monitoring shifting intermarket relationships—or discovering correlation risk only after drawdown appears—the system proactively manages structural risk in real time, addressing one of the most common failure points of multi-pair trading in live market conditions.

Attached files |
The MQL5 Standard Library Explorer (Part 9): Using ALGLIB to Filter Excessive MA Crossover Signals The MQL5 Standard Library Explorer (Part 9): Using ALGLIB to Filter Excessive MA Crossover Signals
During sideways price movements, traders face excessive signals from multiple moving average crossovers. Today, we discuss how ALGLIB preprocesses raw price data to produce filtered crossover layers, which can also generate alerts when they occur. Join this discussion to learn how a mathematical library can be leveraged in MQL5 programs.
Neural Networks in Trading: Integrating Chaos Theory into Time Series Forecasting (Attraos) Neural Networks in Trading: Integrating Chaos Theory into Time Series Forecasting (Attraos)
The Attraos framework integrates chaos theory into long-term time series forecasting, treating them as projections of multidimensional chaotic dynamic systems. Exploiting attractor invariance, the model uses phase space reconstruction and dynamic multi-resolution memory to preserve historical structures.
Features of Experts Advisors Features of Experts Advisors
Creation of expert advisors in the MetaTrader trading system has a number of features.
Price Action Analysis Toolkit Development (Part 63): Automating Rising and Falling Wedge Detection in MQL5 Price Action Analysis Toolkit Development (Part 63): Automating Rising and Falling Wedge Detection in MQL5
In this part of the Price Action Analysis Toolkit Development series, we develop an MQL5 indicator that automatically detects rising and falling wedge patterns in real time. The system confirms pivot structures, validates boundary convergence mathematically, prevents overlapping formations, and monitors breakout and failure conditions with precise visual feedback. Built using a clean object-oriented architecture, this implementation converts subjective wedge recognition into a structured, state-aware analytical component designed to strengthen disciplined price action analysis.