English
preview
Разработка динамического мультивалютного советника (Часть 7): Карта межпарных корреляций для фильтрации сделок в реальном времени

Разработка динамического мультивалютного советника (Часть 7): Карта межпарных корреляций для фильтрации сделок в реальном времени

MetaTrader 5Примеры |
77 0
Hlomohang John Borotho
Hlomohang John Borotho

Содержание:

  1. Введение
  2. Обзор и понимание системы
  3. Приступим
  4. Результаты тестирования на исторических данных
  5. Заключение



Введение

Вы запускаете мультивалютного советника, и на первый взгляд все выглядит идеально выстроенным: каждый символ дает четкие сигналы, входы точны, а логика работает именно так, как задумано. Однако как только сделки начинают наслаиваться по нескольким инструментам, проявляется неприятная закономерность. То, что выглядело как диверсификация, превращается в концентрированную экспозицию. Советник открывает позиции по парам, которые исторически движутся вместе, фактически дублируя риск. Когда корреляции усиливаются – а во время всплесков волатильности это происходит часто, – просадки нарастают быстрее не потому, что отдельные сигналы были ошибочными, а потому, что портфель стал структурно несбалансированным. Проблема уже не в точности сигналов, а в учете взаимосвязей.

Именно здесь, в части 7, меняется сам подход. Вместо того чтобы оценивать каждую пару изолированно, мы встраиваем карту межпарных корреляций в реальном времени прямо в движок принятия решений. Перед исполнением любой сделки советник теперь оценивает, как эта позиция взаимодействует с текущей экспозицией по всему набору торгуемых инструментов. Если новый сигнал увеличивает коррелированный риск или противоречит общей направленности портфеля, он может быть отфильтрован. Если часть 6 была посвящена эффективности исполнения – оптимизации чувствительности к спреду и ранжированию по издержкам, – то этот раздел поднимает систему на уровень портфельного анализа и принятия решений. Приоритет смещается от одной только экономии издержек к согласованности направлений, согласованности экспозиции и динамическому управлению риском во всей мультивалютной системе.



Обзор и понимание системы

Система нашего советника построена как централизованный портфельный движок, который из единой системы управления отслеживает, оценивает и торгует по нескольким символам. Вместо того чтобы рассматривать каждую пару отдельно, система поддерживает структурированные данные по символам, синхронизированные ценовые данные и единый пайплайн принятия решений, который в общей цепочке обрабатывает сигналы, фильтры исполнения и параметры риска. Эта архитектура обеспечивает единое применение логики ко всем инструментам и при этом сохраняет гибкость для метрик конкретного символа, таких как волатильность, условия спреда и сила сигнала.

Обзор системы

В основе советника лежит карта межпарных корреляций в реальном времени, которая улучшает фильтрацию сделок и баланс портфеля. Перед открытием новых позиций система оценивает статистические взаимосвязи между символами, чтобы не допускать избыточной экспозиции и снижать совокупный направленный риск. Сочетание структурированного управления символами, матричной индексации корреляций и динамической фильтрации в цикле OnTick позволяет советнику работать не просто как генератор сделок, а как движок принятия решений с учетом портфеля, который постоянно учитывает меняющиеся межрыночные взаимосвязи.


Приступим

//+------------------------------------------------------------------+
//|                                       Cross-Pair Correlation.mq5 |
//|                        GIT under Copyright 2025, MetaQuotes Ltd. |
//|                     https://www.mql5.com/ru/users/johnhlomohang/ |
//+------------------------------------------------------------------+
#property copyright "GIT under Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/ru/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)

Начиная работу, мы формируем базовую структуру и конфигурацию системы советника: импортируем необходимые торговые классы (CTrade для исполнения и CPositionInfo для отслеживания позиций) и задаем все ключевые входные параметры, управляющие поведением системы. Сначала мы задаем список символов для мониторинга, таймфрейм и окно проверки для расчета межпарных корреляций, а также пороги, определяющие, когда корреляции считаются сильными и когда совокупная коррелированная экспозиция становится чрезмерной. Благодаря этому советник с самого начала работает с учетом структуры портфеля.

Затем мы задаем логику генерации сигналов на основе пересечения скользящих средних, дополненную фильтром по направленности, а также полный набор параметров исполнения сделок, включая размер лота, стоп-лосс, тейк-профит, проскальзывание и магическое число для идентификации позиций. Мы также добавляем опцию управления визуализацией тепловой карты корреляций.

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

В этом разделе задается структурированная модель данных советника через структуру SymbolData, в которой хранятся имя каждого символа и выделенный буфер цен закрытия, используемый для расчета корреляций. Благодаря организации данных по символам в структуру и хранению нескольких экземпляров в массиве m_symbols[] советник получает масштабируемую архитектуру с четким разделением между данными по каждому символу и глобальной логикой системы. Эту архитектуру поддерживают несколько глобальных массивов и переменных состояния, включая уплощенную одномерную матрицу корреляций, хэндлы скользящих средних для генерации сигналов, массивы контроля времени сигнала по каждому символу и флаги, отслеживающие актуальность и корректность данных корреляции.

В OnInit() советник динамически подготавливает среду для работы с несколькими символами. Сначала проверяется, что текущий символ графика включен в список отслеживаемых, после чего входная строка, разделенная запятыми, разбирается на отдельные символы. Каждый символ проверяется и выбирается через SymbolSelect(), после чего размер его внутреннего буфера цен закрытия изменяется, а сам буфер настраивается как массив временного ряда для корректной индексации. Одновременно советник изменяет размер уплощенной матрицы корреляций так, чтобы она охватывала все комбинации пар символов (n x n), и подготавливает массивы, которые позже будут хранить хэндлы индикаторов и данные о времени сигналов. Такое динамическое изменение размера позволяет советнику автоматически масштабироваться в зависимости от количества символов, заданных пользователем.

На последнем этапе инициализации для каждого символа создаются хэндлы индикаторов быстрых и медленных скользящих средних через iMA(), что обеспечивает независимую генерацию сигналов по всему портфелю. Также инициализируются переменные управления состоянием, такие как m_lastBarTime и m_correlationValid, устанавливается таймер для периодического пересчета корреляции и задается магическое число советника для идентификации сделок.

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

Функция OnDeinit() обеспечивает корректную очистку ресурсов при удалении советника или завершении работы платформы. Она проходит по всем отслеживаемым символам и безопасно освобождает хэндлы индикаторов скользящих средних через IndicatorRelease(), предотвращая утечки памяти и освобождая ресурсы терминала. Она также останавливает таймер через EventKillTimer(), чтобы после деактивации советника не продолжали выполняться фоновые события. Такой структурированный процесс завершения работы поддерживает эффективность системы и предотвращает сохранение процессов индикаторов или таймера, которые могли бы ухудшить производительность.

Функция OnTick() представляет собой основной движок исполнения советника. Сначала проверяется, сформировался ли новый базовый бар, и если да, обновляется матрица межпарных корреляций для поддержания актуальной картины портфеля в реальном времени. Затем для каждого символа определяется формирование нового сигнального бара, чтобы избежать повторной обработки внутри одной и той же свечи. Когда обнаруживается новый бар, советник оценивает торговые сигналы, проверяет, допускается ли сделка с учетом фильтров корреляции и экспозиции, и исполняет ее, если условия выполнены. Функция OnTimer() служит резервным механизмом: если матрица корреляций не была обновлена, она обновляется отсюда, что повышает надежность системы и синхронизацию между символами.

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

Этот раздел задает математическую и структурную основу межпарного корреляционного движка советника. Вспомогательная функция CorrIndex() преобразует двумерные координаты матрицы (i, j) в индекс одномерного массива, что позволяет эффективно хранить матрицу корреляций в виде уплощенного массива. Затем UpdateClosePrices() считывает последние цены закрытия для каждого отслеживаемого символа через CopyClose(), тем самым гарантируя, что все расчеты корреляции будут опираться на синхронизированные и актуальные рыночные данные. Функция CalculateCorrelation() вручную реализует формулу корреляции Пирсона, вычисляя статистическую связь между двумя ценовыми рядами через накопление сумм, попарных произведений и квадратов значений перед применением нормализованного уравнения корреляции.

Функция UpdateCorrelationMatrix() объединяет все части логики: сначала обновляет ценовые данные, затем проходит по всем парам символов, вычисляя и сохраняя их корреляции в уплощенной матрице. Диагональным элементам присваивается значение 1.0, что обозначает идеальную самокорреляцию, тогда как внедиагональные элементы вычисляются динамически через функцию Пирсона. Если эта опция включена, визуализация тепловой карты обновляется сразу после пересчета, благодаря чему графическое представление остается согласованным с актуальной статистикой. GetSymbolIndex() представляет собой простую вспомогательную функцию поиска, которая сопоставляет имя символа с его внутренней позицией в массиве и тем самым обеспечивает эффективное взаимодействие между торговой логикой, контролем экспозиции и матрицей корреляций.

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

В этом разделе вводятся фильтрация по направленности и логика проверки сделок в советнике. Функция GetBias() определяет, проявляет ли символ бычью или медвежью направленность, измеряя процентное изменение цены за заданное окно проверки. В нее включены защитные проверки, предотвращающие некорректную индексацию и ошибки деления, после чего рассчитанное изменение сравнивается с настраиваемым порогом. Если рост цены превышает порог, функция возвращает бычий сигнал (1); если снижение цены превышает отрицательный порог, возвращается медвежий сигнал (-1); в иных случаях результат остается нейтральным (0). Этот механизм оценки направленности помогает советнику учитывать более широкий контекст помимо краткосрочных сигналов пересечения.

Функция IsTradeAllowed() выполняет роль защитного фильтра перед любым исполнением сделки. Она блокирует сделки, если по символу уже есть открытая позиция, если матрица корреляций устарела или если сигнал некорректен. После прохождения базовой проверки сигнал преобразуется в направление ордера и передается в корреляционный фильтр для контроля экспозиции на уровне портфеля. В свою очередь CheckSignal() генерирует сигналы входа на основе классического подхода с пересечением скользящих средних, сравнивая последние и предыдущие значения быстрых и медленных скользящих средних. Вместе эти функции обеспечивают, чтобы сделки не только технически инициировались, но и проходили контекстную проверку как с точки зрения направленности, так и в логике управления портфелем.

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

Этот раздел кода отвечает за исполнение сделок, расчет объема позиции с учетом риска и фильтрацию с учетом портфеля, формируя ядро движка принятия решений советника в реальном времени. Функция CalculatePipSize() определяет корректный размер пипса для каждого типа инструмента с учетом различий между Forex-парами, кросс-парами с JPY, металлами, криптовалютами и индексами. За счет корректировки расчета пипсов в соответствии с особенностями символа советник обеспечивает точное применение уровней стоп-лосс и тейк-профит, сохраняя единый контроль риска на разных рынках.

Функция ExecuteTrade() исполняет рыночные ордера с динамически рассчитываемыми уровнями стоп-лосс и тейк-профит, используя размер пипса из CalculatePipSize(). Функция различает сигналы BUY и SELL, рассчитывает соответствующие цены входа, SL и TP и исполняет сделку с использованием класса CTrade. Каждое исполнение записывается в журнал с подробной обратной связью, включая сведения о том, был ли ордер исполнен успешно или завершился ошибкой, а также вывод рассчитанных уровней SL/TP для прозрачности и контроля как за исполнением, так и за риском.

Функция PassesCorrelationFilter() встраивает управление риском на уровне портфеля, проверяя, не нарушит ли новая сделка правила корреляционной экспозиции. Для каждого символа функция сравнивает текущий сигнал со всеми остальными открытыми позициями, используя заранее вычисленную матрицу корреляций. Сделки блокируются, если по сильно коррелирующим символам уже есть позиции в том же или противоположном направлении – в зависимости от знака корреляции. Этот механизм предотвращает избыточную экспозицию и ее наращивание, обеспечивая исполнение сделок только в тех случаях, когда они сохраняют сбалансированный риск по всему мультисимвольному портфелю и тем самым согласуются с общей безопасностью портфеля.

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

Здесь основное внимание уделяется четкому и визуально интуитивному представлению межпарных корреляций в виде тепловой карты. Функция GetDisplayName() упрощает имена символов, заменяя USD знаком $, чтобы повысить читаемость и получить короткие метки вроде XAU$ или $JPY. Эти отображаемые имена используются как заголовки строк и столбцов тепловой карты, что позволяет быстро распознавать символы. Метки аккуратно размещаются и центрируются относительно ячеек матрицы, благодаря чему они выровнены с отображаемыми значениями корреляции.

Сама функция DrawHeatmap() строит полную визуализацию матрицы, перебирая все пары символов. Для каждой ячейки рисуется фоновый прямоугольник, интенсивность цвета которого динамически отражает величину и направление корреляции: зеленый – для положительных корреляций, красный – для отрицательных, светло-серый – для нейтральных. Поверх каждой цветной ячейки выводится числовое значение корреляции, а цвет текста подбирается так, чтобы сохранялся контраст в зависимости от уровня корреляции. Такое двойное представление – цветом и числовым значением – позволяет быстро оценивать взаимосвязи между символами, сохраняя точность и читаемость данных и превращая исходные статистические данные в практический визуальный инструмент.



Результаты тестирования на исторических данных

Тестирование проводилось в течение примерно двух месяцев – с 1 декабря 2025 года по 30 января 2026 года – при следующих настройках:

Теперь взглянем на кривую капитала и результаты тестирования на исторических данных:



Заключение

Мы вышли далеко за рамки "обычного" мультивалютного генератора сигналов и превратили систему в полноценный движок принятия решений на уровне портфеля. Данные по символам синхронизируются между инструментами, ценовые буферы структурированы, а матрица корреляции Пирсона в реальном времени непрерывно пересчитывается и напрямую передается в конечный слой исполнения ордеров. Ключевой шаг вперед здесь – не просто вычисление, а интеграция. Корреляция больше не остается внешним наблюдаемым фактором: теперь она участвует в последней проверке перед тем, как ордер уходит на рынок.

В результате получается масштабируемая система советника, в которую новые инструменты можно добавлять без лишней перенастройки, корреляции пересчитываются на каждом новом баре или по событию таймера, отражая текущую рыночную статистику, а каждый сигнал пересечения MA проходит через фильтр портфельной экспозиции. Сделки блокируются, когда сильная положительная корреляция усиливает уже существующий направленный риск, а также когда сильная отрицательная корреляция создает скрытую зеркальную экспозицию против уже открытой позиции. Тепловая карта добавляет наглядный слой визуального подтверждения, обеспечивая прозрачность и уверенность в том, что обновление матрицы и работа порогов происходят так, как задумано.

Как итог, теперь у вас есть структурированный способ встроить управление экспозицией на основе корреляции прямо в мультивалютный советник, что обеспечивает более плавное и контролируемое поведение портфеля. Вместо того чтобы вручную отслеживать меняющиеся межрыночные взаимосвязи или обнаруживать корреляционный риск уже после появления просадки, система проактивно управляет структурным риском в реальном времени, снижая влияние одного из наиболее распространенных источников проблем в мультивалютной торговле в реальных рыночных условиях.

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/21460

Прикрепленные файлы |
Переосмысливаем классические стратегии (Часть 18): Алгоритмический поиск свечных паттернов Переосмысливаем классические стратегии (Часть 18): Алгоритмический поиск свечных паттернов
Эта статья помогает новым участникам сообщества искать и находить собственные свечные паттерны. Описание этих паттернов может оказаться сложной задачей, поскольку требует ручного поиска и творческого подхода к выявлению усовершенствований. В этой статье мы представляем свечной паттерн поглощения и показываем, как его можно усовершенствовать для создания более прибыльных торговых стратегий.
Разработка инструментария для анализа Price Action (Часть 56): Анализ принятия и отвержения на границах сессии с помощью CP Разработка инструментария для анализа Price Action (Часть 56): Анализ принятия и отвержения на границах сессии с помощью CP
В этой статье представлен сессионный аналитический подход, сочетающий рыночные сессии с заданными временными границами и индекс давления свечи (CPI), чтобы на основе закрытых свечей и четко определенных правил классифицировать принятие и отвержение на границах сессий.
Разработка инструментария для анализа Price Action (Часть 57): Создание модуля классификации состояния рынка на MQL5 Разработка инструментария для анализа Price Action (Часть 57): Создание модуля классификации состояния рынка на MQL5
В этой статье представлен модуль классификации состояния рынка на MQL5, который интерпретирует поведение цены по данным закрытых свечей. Анализируя сжатие и расширение волатильности, а также согласованность структуры, инструмент классифицирует состояние рынка как сжатие, переходное состояние, расширение или тренд и тем самым формирует четкий контекст для анализа Price Action.
Переосмысливаем классические стратегии (Часть 17): Моделирование технических индикаторов Переосмысливаем классические стратегии (Часть 17): Моделирование технических индикаторов
В этом обсуждении мы сосредоточимся на том, как можно преодолеть "стеклянный потолок", создаваемый классическими методами машинного обучения в сфере финансов. Похоже, что самое главное ограничение ценности, которую можно извлечь из статистических моделей, заключается не в самих моделях — ни в данных, ни в сложности алгоритмов, — а скорее в методологии, которую мы используем для их применения. Другими словами, истинным узким местом может быть то, как мы используем модель, а не ее собственный потенциал.