English Русский Deutsch
preview
ダイナミックマルチペアEAの形成(第7回):リアルタイムトレードフィルタリングのためのクロスペア相関マッピング

ダイナミックマルチペアEAの形成(第7回):リアルタイムトレードフィルタリングのためのクロスペア相関マッピング

MetaTrader 5 |
17 0
Hlomohang John Borotho
Hlomohang John Borotho

目次:

  1. はじめに
  2. システムの概要と理解
  3. 導入手順
  4. バックテスト結果
  5. 結論



はじめに

マルチペアEAを起動すると、最初はすべてが完璧に構成されているように見えます。それぞれの銘柄は明確なシグナルを生成し、エントリーは正確で、ロジックは設計通りに機能しています。しかし、複数の金融商品にまたがってポジションが積み上がり始めると、ある問題が浮かび上がります。一見すると分散投資に見えていたものが、実際には集中したリスクエクスポージャーになっているケースです。EAは、過去に同じ方向へ動く傾向があるペアにポジションを持ち、結果的にリスクを重複させています。相関関係が強まるとき、特にボラティリティが急上昇する局面では、ドローダウンは加速します。これは個々のシグナルが間違っていたためではありません。問題は、ポートフォリオが構造的に不均衡になったことです。問題はもはやシグナルの正確性ではなく、相互関係の把握です。

まさにここで、第7回では考え方の構造的な変化を導入します。各ペアを個別に評価する代わりに、リアルタイムのクロスペア相関マッピングを意思決定エンジンへ直接組み込みます。取引が実行される前に、EAはそのポジションが監視対象となる全銘柄の中で既存のエクスポージャーとどのように相互作用するかを評価します。もし新しいシグナルが相関リスクを増加させたり、ポートフォリオ全体の方向性と整合しない場合、そのシグナルはフィルタリングされます。第6回では、スプレッドへの感度やコストランキングの最適化など、実行効率に集中しました。一方、今回は、フレームワークをポートフォリオ全体を判断できる仕組みへ発展させます。優先事項は、単なるコスト効率から、ポートフォリオ全体における方向性の調和、エクスポージャーの一貫性、そして動的なリスク管理へと移行します。



システムの概要と理解

本EAシステムは、単一の統合されたフレームワークから複数の銘柄を監視、評価、取引する中央集約型のポートフォリオエンジンとして設計されています。各ペアを個別に扱うのではなく、システムは構造化された銘柄データ、同期された価格入力、そしてシグナル、実行フィルタ、リスクパラメータを集合的に処理する共通の意思決定パイプラインを維持します。このアーキテクチャにより、すべての金融商品に対して一貫したロジックを適用しながら、ボラティリティ、スプレッド状況、シグナル強度など、銘柄固有の指標にも柔軟に対応できます。

システム概要

EAの中心では、リアルタイムのクロスペア相関マッピングを統合し、取引フィルタリングとポートフォリオバランスを強化しています。新しいポジションを実行する前に、システムは銘柄間の統計的関係を評価し、重複したエクスポージャーを防ぎ、複合的な方向性リスクを低減します。構造化された銘柄管理、行列ベースの相関インデックス、そしてOnTickサイクル内での動的フィルタリングを組み合わせることで、EAは単なるトレード生成ツールではなく、変化する市場間の関係性に継続的に適応する、ポートフォリオを認識したの意思決定エンジンとして動作します。


導入手順

//+------------------------------------------------------------------+
//|                                       Cross-Pair Correlation.mq5 |
//|                        GIT under Copyright 2025, MetaQuotes Ltd. |
//|                     https://www.mql5.com/ja/users/johnhlomohang/ |
//+------------------------------------------------------------------+
#property copyright "GIT under Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/ja/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)をインポートし、システムの動作を制御するすべての主要な入力パラメータを定義することで、EAシステムの基盤となる構造と設定を構築します。まず、監視対象となる銘柄のリスト、クロスペア相関を計算するための時間足とルックバック期間、そして相関が強いと判断される条件および全体の相関エクスポージャーが過剰になる条件を決定する閾値を指定します。これにより、EAは最初からポートフォリオ全体を考慮した状態で動作します。

次に、方向性バイアスフィルタによって強化された移動平均クロスオーバーフレームワークを使用してシグナル生成ロジックを定義します。さらに、ロットサイズ、ストップロス、テイクプロフィット、スリッページ、ポジション識別用のマジックナンバーを含む、完全な取引実行設定を定義します。また、相関ヒートマップの表示制御を行うためのオプションも含めます。

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

このセクションでは、EAのための構造化されたデータモデルを定義します。SymbolData構造体を使用し、各銘柄の名前と、相関計算に使用される専用の終値バッファをカプセル化します。銘柄関連のデータを構造体内に整理し、複数のインスタンスをm_symbols[]配列内に格納することで、EAはスケーラブルでオブジェクト的な構造を意識した設計を採用します。その結果、銘柄ごとのデータとグローバルなシステムロジックを明確に分離できます。このアーキテクチャを支えるものとして、1次元に展開された相関行列、シグナル生成用の移動平均ハンドル、銘柄ごとのシグナルタイミング制御、そして相関データが最新かつ有効であるかを追跡するフラグなど、複数のグローバル配列と状態変数を使用します。

OnInit()内部では、EAが動的に複数銘柄環境を準備します。まず、現在チャートで使用されている銘柄が監視対象リストに含まれていることを確認します。その後、カンマ区切りの入力文字列を個別の銘柄へ分割します。各銘柄はSymbolSelect()を使用して検証および選択され、その後、内部の終値バッファがリサイズされ、適切なインデックス処理を行うために時系列配列として設定されます。同時に、EAは展開された相関行列をすべての銘柄ペアの組み合わせ(n × n)に対応できるようリサイズし、後でインジケータハンドルやシグナルタイミングデータを保存するための配列を準備します。この動的なリサイズ機能により、ユーザーが指定する銘柄数に応じて、EAは自動的に拡張できます。

初期化処理の最後の部分では、iMA()を使用して各銘柄の高速移動平均および低速移動平均のインジケータハンドルを作成します。これにより、ポートフォリオ全体で独立したシグナル生成が可能になります。また、m_lastBarTimeやm_correlationValidなどの状態制御変数を初期化し、定期的な相関再計算を実行するためのタイマーを設定します。さらに、取引識別のためにEAのマジックナンバーを設定します。

//+------------------------------------------------------------------+
//| 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()関数は、EAが削除された場合やプラットフォームが終了した場合に、適切なリソース解放を確実に行います。この関数では、監視対象となるすべての銘柄をループ処理し、IndicatorRelease()を使用して移動平均インジケータハンドルを安全に解放します。これにより、メモリリークを防ぎ、ターミナルのリソースを解放します。また、EventKillTimer()によってタイマーを停止し、EAが無効化された後もバックグラウンドイベントが継続して実行されないようにします。この構造化された終了処理により、システムの効率性を維持し、パフォーマンスに影響を与える可能性のある不要なインジケータやタイマープロセスが残らないようにします。

OnTick()関数は、EAの中心となる実行エンジンです。最初に、新しいメインバーが形成されたかどうかを確認し、形成されている場合はクロスペア相関行列を更新して、リアルタイムのポートフォリオ認識を維持します。次に、各銘柄について新しいシグナルバーの形成を検出し、同じローソク足内での重複処理を防ぎます。新しいバーが検出されると、EAは取引シグナルを評価し、相関フィルタおよびエクスポージャーフィルタに基づいて取引が許可されるかを確認します。そして、条件が満たされた場合に取引を実行します。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);
 }

このセクションでは、EAのクロスペア相関エンジンにおける数学的および構造的な基盤を提供します。CorrIndex()ヘルパー関数は、2次元行列の座標(i, j)を1次元配列のインデックスへ変換します。これにより、相関行列をフラット化された配列として効率的に保存できます。次に、UpdateClosePrices()はCopyClose()を使用して、監視対象となる各銘柄の最新終値を取得します。これにより、すべての相関計算が同期された最新の市場データに基づいて実行されることを保証します。CalculateCorrelation()関数は、ピアソン相関係数の計算式を手動で実装します。この関数では、正規化された相関方程式を適用する前に、合計値、積の合計、二乗値を集計することで、2つの価格系列間の統計的関係を計算します。

UpdateCorrelationMatrix()関数は、これらすべての処理を結合します。まず価格データを更新し、その後すべての銘柄ペアをループ処理して、それぞれの相関値を計算し、フラット化された行列へ保存します。対角要素には完全な自己相関を表す1.0が設定され、非対角要素にはCalculateCorrelation()によって動的に計算された値が設定されます。有効化されている場合、再計算後すぐにヒートマップ表示が更新され、表示内容を最新の統計情報に同期した状態に保てます。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);
 }

このセクションでは、EA内における方向性フィルタリングと取引検証ロジックを導入します。GetBias()関数は、定義されたルックバック期間における価格変化率を測定することで、銘柄が強気傾向または弱気傾向を示しているかを判定します。この関数には、不正なインデックス参照やゼロ除算エラーを防ぐための安全確認が含まれており、その後、計算された変化率を設定可能な閾値と比較します。価格上昇率が閾値を超えた場合、関数は強気シグナル(1)を返します。価格下落率が負の方向で閾値を超えた場合は弱気シグナル(-1)を返します。それ以外の場合は、中立(0)の状態を維持します。このバイアスメカニズムにより、EAは短期的なクロスオーバーシグナルを超えて、より広範な方向性コンテキストを取り入れることができます。

IsTradeAllowed()関数は、実際の取引実行が行われる前の保護ゲートキーパーとして機能します。この関数は、対象銘柄にすでにポジションが存在する場合、相関行列が古い場合、またはシグナルが無効な場合に取引をブロックします。基本的な検証を通過すると、シグナルを注文方向へ変換し、その判断を相関フィルタへ渡して、ポートフォリオレベルでのエクスポージャー制御を行います。一方、CheckSignal()関数は、一般的な移動平均クロスオーバー手法を使用してエントリーシグナルを生成します。これは、高速移動平均(Fast MA)と低速移動平均(Slow MA)の最新値および直前の値を比較することで判断されます。これらの関数を組み合わせることで、取引は単にテクニカル条件によって発生するだけではなく、方向性およびポートフォリオを認識したの両方のフレームワーク内で、状況に応じた検証が行われるようになります。

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

このセクションのコードは、取引実行、リスクサイジング、そしてポートフォリオを考慮したフィルタリングを処理しており、EAのリアルタイム意思決定エンジンの中核を形成します。CalculatePipSize()関数は、各金融商品の正しいピップ値を決定します。これには、Forexペア、JPYクロス、貴金属、暗号資産、インデックス間の違いが考慮されています。銘柄ごとの仕様に基づいてピップ計算を調整することで、EAはストップロスおよびテイクプロフィットのレベルを正確に適用し、異なる市場において一貫したリスク管理を維持します。

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()関数自体は、すべての銘柄ペアを反復処理することで、完全な行列表示を構築します。各セルでは、相関の大きさと方向性を動的に反映する背景矩形を描画します。正の相関は緑色、負の相関は赤色、中立の相関は薄いグレーとして表示されます。各色付きセルの上には、実際の数値による相関値が表示されます。また、相関値に応じてコントラストを考慮した文字色に調整されます。色と数値を組み合わせたこの二重表示により、トレーダーは銘柄間の関係性を素早く評価しながら、正確で読みやすい情報を維持できます。これにより、生の統計データを実用的な視覚ツールへ効果的に変換します。



バックテスト結果

テストは、2025年12月1日から2026年1月30日までの約2か月間のテスト期間で実施されました。設定は以下の通りです。

次に、エクイティカーブおよびバックテスト結果を示します。



結論

本記事では、「通常の」マルチペアシグナル生成システムをはるかに超え、真のポートフォリオレベルの意思決定エンジンへとシステムを進化させました。銘柄データは複数の金融商品間で同期され、価格バッファは構造的に管理され、リアルタイムのピアソン相関行列は継続的に再構築され、最終的な実行レイヤーへ直接組み込まれます。重要な進化は、単なる計算ではなく、統合にあります。相関はもはや外部で確認するだけのものではありません。現在では、注文が市場へ到達する前の最終チェックポイントにおいて、積極的に意思決定へ関与します。

その結果、システムは拡張可能なEAフレームワークとなり、銘柄を容易に追加でき、現在の市場統計を反映するために新しいバー(またはタイマーイベント)ごとに相関が再計算され、すべての移動平均クロスオーバーシグナルがポートフォリオエクスポージャーフィルタを通過する構造になります。強い正の相関によって既存の方向性リスクが増幅される場合、取引はブロックされます。また、強い負の相関によって、すでに保有しているポジションに対する隠れたミラーエクスポージャーが発生する場合にも、取引はブロックされます。ヒートマップは即時的な視覚確認レイヤーを追加し、行列の更新や閾値が意図した通りに機能していることを透明性と信頼性を持って確認できます。

要するに、現在では相関ベースのエクスポージャー制御をマルチペアEAへ直接組み込むための、構造化された手法を手に入れました。これにより、より滑らかで、より安定したポートフォリオ挙動を実現します。変化する市場間の関係性を手動で監視したり、ドローダウンが発生した後で初めて相関リスクを発見したりする代わりに、このシステムはリアルタイムで構造的リスクを事前管理します。これは、実際の市場環境におけるマルチペア取引で最も一般的な失敗要因の一つに対応するものです。

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/21460

添付されたファイル |
MQL5標準ライブラリエクスプローラー(第9回):ALGLIBによる多発するMAクロスオーバーシグナルのフィルタ処理 MQL5標準ライブラリエクスプローラー(第9回):ALGLIBによる多発するMAクロスオーバーシグナルのフィルタ処理
横ばいの価格変動中、トレーダーは複数の移動平均クロスオーバーによって過剰なシグナルに直面します。今回は、ALGLIBがどのように生の価格データを前処理し、フィルタ処理済みのクロスオーバー系列を生成するのかについて説明します。また、これらのレイヤーは発生時にアラートを生成することもできます。数学ライブラリをMQL5プログラムでどう活用できるのかを一緒に見ていきましょう。
プライスアクション分析ツールキットの開発(第63回):MQL5での上昇・下降ウェッジ検出の自動化 プライスアクション分析ツールキットの開発(第63回):MQL5での上昇・下降ウェッジ検出の自動化
連載「プライスアクション分析ツールキットの開発」の今回の記事では、上昇ウェッジおよび下降ウェッジパターンをリアルタイムで自動検出するMQL5インジケーターを開発します。このシステムは、ピボット構造を確認し、境界線の収束を数学的に検証し、パターン同士の重複を防止しながら、ブレイクアウトおよび失敗条件を正確な視覚的フィードバックとともに監視します。クリーンなオブジェクト指向アーキテクチャを使用して構築されたこの実装は、主観的なウェッジ認識を、規律あるプライスアクション分析を強化するために設計された、状態を認識して扱える構造化分析コンポーネントへと変換します。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
取引規律をコードに組み込む(第2回):MQL5で口座全体の全取引に対応する日次取引制限エンフォーサーを構築する 取引規律をコードに組み込む(第2回):MQL5で口座全体の全取引に対応する日次取引制限エンフォーサーを構築する
取引ルールを遵守できるようにするため、1日の取引制限を強制するシステムを開発しました。このシステムは、口座全体で実行されたすべての取引を監視し、設定された制限に到達すると自動的に介入して、それ以降の取引活動を防止します。制御機能をプラットフォームに直接組み込むことで、市場からのプレッシャーが高まった状況でも、規律を維持できるようにします。