English Deutsch
preview
グラフ理論:取引における幅優先探索(BFS)/ja/応用

グラフ理論:取引における幅優先探索(BFS)/ja/応用

MetaTrader 5 |
15 0
Hlomohang John Borotho
Hlomohang John Borotho

目次

  1. はじめに
  2. システム概要とBFS/ja/基本
  3. 導入手順
  4. バックテスト結果
  5. 結論 


はじめに

金融市場は表面的にはカオスに見えることが多いですが、価格は一貫してある領域から別/ja/領域へ移動し、そ/ja/動きはランダムではなく背後にある一定/ja/論理に従っているように見えます。そ/ja/ためトレーダーやシステム開発者は、市場が流動性、構造、不均衡/ja/レベル間を移動する際に従うアルゴリズム/ja/ようなも/ja/を特定しようとします。こ/ja/探求は単独/ja/予測を目的とするも/ja/ではなく、過去/ja/価格相互作用が将来/ja/動きにど/ja/ように影響し、市場構造が時間とともにど/ja/ように段階的に進化するかを理解することにあります。

グラフ理論はこ/ja/目的に対して強力なフレームワークを提供します。価格アクションを個別/ja/ローソク足/ja/集合ではなく、ルールベース/ja/構造システムとしてモデル化できるためです。幅優先探索(BFS)によるレベル順トラバーサルを適用することで、直近/ja/スイングを起点に過去/ja/文脈へ段階的にたどりつつ、時間/ja/向きを損なわずに市場構造を分析できます。こ/ja/アプローチは過去/ja/バーを意思決定ポイントからなる有向グラフへと変換し、強気および弱気/ja/構造を体系的に解釈することを可能にし、アルゴリズム取引を市場がたどる経路探索/ja/ロジックにより近づけます。


システム概要とBFS/ja/基本:

BFSコンセプト  取引解釈 
ノード スイングポイント(ハイまたはロー)
エッジ(有向) 時間/ja/進行(過去 → 未来)
レベル バーまたは日数/ja/距離
ルート 過去/ja/アンカー(開始点)
トラバーサル 市場構造/ja/進化
目標 強気または弱気バイアス/ja/決定

BFS Market Structure EAは、従来/ja/テクニカル分析をグラフ理論/ja/概念を価格アクションに適用することで変革します。システムはまず過去/ja/価格データから重要なスイングポイント(高値および安値)を検出し、それぞれ/ja/スイングを有向グラフ/ja/ノードとして扱います。エッジは時間的進行を表します。設定可能な履歴アンカーを起点として幅優先探索(BFS)トラバーサルを用いることで、アルゴリズムはレベル順に市場構造を処理します。すなわち、まず最も近いスイングを分析し、そ/ja/後により古い形成へと進みます。各スイングノードは価格/ja/比較関係に基づいて強気または弱気に分類され、より直近/ja/構造ほど最終バイアス計算において強い重みを持ちます。これにより-1(弱気)から+1(強気)まで/ja/連続したバイアススコアが生成され、従来/ja/インジケーターベース/ja/シグナルではなく、市場構造/ja/進化そ/ja/も/ja/を表現します。

売買判断レイヤーはこ/ja/構造バイアスを用いて潜在的な取引をフィルタリングし、スコアが設定された閾値を超えた場合に/ja/み買いまたは売りポジションを許可します。オプションが有効な場合、EAはCTradeクラスを用いた実運用を意識した注文管理を通じて取引を実行し、オプションとして直近/ja/スイングブレイクアウトによる確認もおこないます。システムには3つ/ja/異なるコンテキストモードが含まれます。スキャルピング向け/ja/過去バー、スイングトレード向け/ja/過去数日、イントラデイバイアス向け/ja/『前日/ja/み』モードです。これにより異なる取引スタイルへ/ja/適応が可能になります。リアルタイム可視化では色分けされたノードとエッジ接続を備えたスインググラフがリアルタイムに表示され、構造分析/ja/透明性を高め、市場条件へ/ja/即時フィードバックを提供します。こ/ja/アプローチは予測ではなく市場構造/ja/進化/ja/理解に焦点を当てた体系的かつルールベース/ja/手法を構築します。



導入手順

//+------------------------------------------------------------------+
//|                                                       BFS-EA.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 <Arrays\List.mqh>

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
enum ENUM_HISTORY_MODE
{
   MODE_PREVIOUS_BARS,     // Previous Bars
   MODE_PREVIOUS_DAYS,     // Previous Days
   MODE_YESTERDAY_ONLY     // Yesterday Only
};

input ENUM_HISTORY_MODE   HistoryMode = MODE_PREVIOUS_DAYS;  // History Mode
input int                 BarsLookback = 300;                // Bars Lookback
input int                 DaysLookback = 5;                  // Days Lookback
input int                 SwingPeriod = 3;                   // Swing Period (lookback bars)
input double              BiasThreshold = 0.3;               // Bias Threshold
input bool                EnableTrading = true;              // Enable Trading
input double              LotSize = 0.1;                     // Lot Size
input int                 TakeProfit = 100;                  // Take Profit (points)
input int                 StopLoss = 50;                     // Stop Loss (points)
input bool                VisualizeStructure = true;         // Visualize Structure
input color               BullishColor = clrGreen;           // Bullish Color
input color               BearishColor = clrRed;             // Bearish Color

入力パラメータブロックから開始します。こ/ja/ブロックは、エキスパートアドバイザーが過去/ja/市場コンテキストをど/ja/ように認識し、それを構造化された取引ロジックへと変換するかを定義します。ENUM_HISTORY_MODEは、アルゴリズムが固定された過去/ja/バー数を評価する/ja/か、複数/ja/過去/ja/取引日を評価する/ja/か、あるいは厳密に前日/ja/価格アクション/ja/みを評価する/ja/かをユーザーが制御できるようにし、市場グラフ/ja/構築方法に直接影響を与えます。BarsLookback、DaysLookback、SwingPeriodといったパラメータはスイング検出/ja/深さと粒度を決定し、一方でBiasThresholdは、強気または弱気バイアスが有効と見なされるために必要な構造上/ja/信頼度を設定します。

残り/ja/入力は、本システム/ja/実行および解釈可能性を制御します。EnableTrading、LotSize、TakeProfit、StopLossといった取引制御は、意思決定ロジックとリスク管理を分離し、構造分析が実行ルールから独立して機能できるようにします。VisualizeStructure、BullishColor、BearishColorを含む可視化設定は、検出された市場構造をチャート上に直接描画することで透明性を提供し、トレーダーが基礎となるグラフトラバーサルロジックに基づいて、アルゴリズムが強気および弱気スイングをど/ja/ように分類しているかを視覚的に確認できるようにします。

//+------------------------------------------------------------------+
//| Swing Node Structure                                             |
//+------------------------------------------------------------------+
struct SwingNode
{
   int               index;          // bar index
   double            price;          // high or low price
   bool              isHigh;         // true = swing high, false = swing low
   bool              bullish;        // inferred direction
   int               level;          // BFS level
   datetime          time;           // bar time
   
   // Default constructor
   SwingNode(): index(0), price(0.0), isHigh(false), bullish(false), level(-1), time(0) {}
   
   // Parameterized constructor
   SwingNode(int idx, double prc, bool high, bool bull, int lvl, datetime t):
      index(idx), price(prc), isHigh(high), bullish(bull), level(lvl), time(t) {}
};

//+------------------------------------------------------------------+
//| BFS Queue Item                                                   |
//+------------------------------------------------------------------+
struct BFSItem
{
   int               nodeIndex;      // index in nodes array
   int               level;          // BFS level
};

//+------------------------------------------------------------------+
//| Edge Structure for Graph                                         |
//+------------------------------------------------------------------+
struct Edge
{
   int from;      // Source node index
   int to;        // Destination node index
};

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
SwingNode           nodes[];         // Array of swing nodes
Edge                edges[];         // Array of edges
int                 edgeCount = 0;   // Number of edges
int                 nodeCount = 0;        // Number of nodes
double              currentBias = 0.0;    // Current market bias
bool                allowBuy = false;     // Allow buy trades
bool                allowSell = false;    // Allow sell trades
datetime            lastRecalcTime = 0;   // Last recalculation time
int                 lastBarCount = 0;     // Last bar count
CTrade              trade;                // Trading object for position management

こ/ja/コードセクションは、生/ja/プライスアクションを幅優先探索トラバーサルに適したグラフへと変換するため/ja/コアデータ構造を定義しています。SwingNode構造体は各重要な市場/ja/転換点を表し、そ/ja/バーインデックス、価格、時間、そしてスイング高値か安値かを保持します。さらに「bullish」や「level」といった属性により、各ノードに方向性/ja/情報とBFS上/ja/深さを持たせることができ、時間/ja/経過に沿って市場構造を段階的に評価することを可能にします。構造的情報と時間的情報/ja/両方をカプセル化することで、各スイングは単独/ja/価格イベントではなく、有向市場グラフ内/ja/意味を持つノードとなります。

こ/ja/表現を補助するために、BFSItemおよびEdge構造体は、グラフ内で/ja/トラバーサルと接続性がど/ja/ように処理されるかを定義します。BFSItemは各BFSレイヤーにおいてど/ja/ノードが訪問されているかを追跡することでレベル順処理を可能にし、Edgeは古いスイングポイントと新しいスイングポイント/ja/間/ja/有向関係を明示的にモデル化します。グローバル変数はEA/ja/動作基盤として機能し、構築されたグラフを保持し、トラバーサル状態を管理し、構造的バイアスを売買可否/ja/判断へ変換します。これら/ja/コンポーネントはグラフ理論と実行ロジックを橋渡しし、EAが取引/ja/実行やフィルタリングをおこなう前に、市場方向を体系的に推論できるようにします。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   // Validate inputs
   if(BarsLookback <= 0)
   {
      Print("Error: BarsLookback must be positive");
      return INIT_PARAMETERS_INCORRECT;
   }
   
   if(DaysLookback <= 0)
   {
      Print("Error: DaysLookback must be positive");
      return INIT_PARAMETERS_INCORRECT;
   }
   
   if(SwingPeriod <= 0)
   {
      Print("Error: SwingPeriod must be positive");
      return INIT_PARAMETERS_INCORRECT;
   }
   
   // Set timer for periodic updates (every 5 minutes)
   EventSetTimer(300);
   
   // Set trading parameters
   trade.SetExpertMagicNumber(12345);
   trade.SetDeviationInPoints(10);
   trade.SetTypeFilling(ORDER_FILLING_FOK);
   
   // Initial calculation
   CalculateBFSStructure();
   
   return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   // Remove timer
   EventKillTimer();
   
   // Clean up visualization objects
   if(VisualizeStructure)
   {
      RemoveVisualization();
   }
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   // Check for new bar
   if(IsNewBar())
   {
      CalculateBFSStructure();
      
      // Check for trading signals if enabled
      if(EnableTrading)
      {
         CheckTradingSignals();
      }
   }
   
   // Update visualization if enabled
   if(VisualizeStructure)
   {
      UpdateVisualization();
   }
}

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
{
   // Periodic recalculation (fallback)
   CalculateBFSStructure();
}

ここではエキスパートアドバイザー(EA)/ja/ライフサイクルを定義し、初期化、実行、クリーンアップを調整しながら、BFSベース/ja/市場構造が常に最新/ja/状態に保たれるようにしています。初期化時には、無効な設定を防ぐために入力パラメータが検証され、取引設定が準備され、初期/ja/BFS構造計算が実行されます。また、定期的な再計算を強制するためにタイマーが設定されます。メイン/ja/実行ロジックでは、効率性/ja/ために新しいバーが生成された場合または一定/ja/時間間隔ごとに/ja/み構造が再計算され、取引が有効な場合には取引判断が適用され、チャート可視化が継続的に更新されます。一方、初期化解除フェーズでは、タイマーおよびグラフィカルオブジェクトが安全に削除され、プラットフォーム/ja/安定性が維持されます。

//+------------------------------------------------------------------+
//| Calculate BFS Market Structure                                   |
//+------------------------------------------------------------------+
void CalculateBFSStructure()
{
   // Reset values
   currentBias = 0.0;
   allowBuy = false;
   allowSell = false;
   
   // Step 1: Detect swing points
   if(!DetectSwingNodes())
   {
      Print("Failed to detect swing nodes");
      return;
   }
   
   // Step 2: Build directed graph (edges)
   BuildDirectedGraph();
   
   // Step 3: Select BFS root based on mode
   int rootIndex = SelectBFSRoot();
   if(rootIndex < 0)
   {
      Print("No valid root found");
      return;
   }
   
   // Step 4: Perform BFS traversal
   PerformBFSTraversal(rootIndex);
   
   // Step 5: Calculate market bias
   CalculateMarketBias();
   
   // Step 6: Update trading permissions
   UpdateTradingPermissions();
   
   // Update last calculation time
   lastRecalcTime = TimeCurrent();
}

//+------------------------------------------------------------------+
//| Detect Swing Nodes                                               |
//+------------------------------------------------------------------+
bool DetectSwingNodes()
{
   // Determine how many bars to analyze
   int barsToAnalyze = 0;
   datetime startTime = 0;
   
   switch(HistoryMode)
   {
      case MODE_PREVIOUS_BARS:
         barsToAnalyze = BarsLookback + SwingPeriod * 2;
         break;
         
      case MODE_PREVIOUS_DAYS:
         startTime = TimeCurrent() - (DaysLookback * 86400);
         barsToAnalyze = iBars(_Symbol, _Period);
         break;
         
      case MODE_YESTERDAY_ONLY:
         {MqlDateTime yesterday;
         TimeToStruct(TimeCurrent() - 86400, yesterday);
         yesterday.hour = 0;
         yesterday.min = 0;
         yesterday.sec = 0;
         startTime = StructToTime(yesterday);
         barsToAnalyze = iBars(_Symbol, _Period);
         break;}
   }
   
   // Get historical data
   int totalBars = MathMin(barsToAnalyze, iBars(_Symbol, _Period));
   if(totalBars < SwingPeriod * 2 + 10)
   {
      Print("Not enough bars for swing detection");
      return false;
   }
   
   // Prepare arrays for highs and lows
   double highs[], lows[];
   datetime times[];
   
   ArrayResize(highs, totalBars);
   ArrayResize(lows, totalBars);
   ArrayResize(times, totalBars);
   
   // Copy data
   if(CopyHigh(_Symbol, _Period, 0, totalBars, highs) != totalBars ||
      CopyLow(_Symbol, _Period, 0, totalBars, lows) != totalBars ||
      CopyTime(_Symbol, _Period, 0, totalBars, times) != totalBars)
   {
      Print("Failed to copy historical data");
      return false;
   }
   
   // Detect swing highs and lows
   ArrayResize(nodes, 0);
   nodeCount = 0;
   
   for(int i = SwingPeriod; i < totalBars - SwingPeriod; i++)
   {
      bool isSwingHigh = true;
      bool isSwingLow = true;
      
      // Check for swing high
      for(int j = 1; j <= SwingPeriod; j++)
      {
         if(highs[i] <= highs[i-j] || highs[i] <= highs[i+j])
         {
            isSwingHigh = false;
            break;
         }
      }
      
      // Check for swing low
      for(int j = 1; j <= SwingPeriod; j++)
      {
         if(lows[i] >= lows[i-j] || lows[i] >= lows[i+j])
         {
            isSwingLow = false;
            break;
         }
      }
      
      // Add swing node if found
      if(isSwingHigh || isSwingLow)
      {
         SwingNode node;
         node.index = i;
         node.time = times[i];
         node.isHigh = isSwingHigh;
         
         if(isSwingHigh)
            node.price = highs[i];
         else
            node.price = lows[i];
            
         node.bullish = false;  // Will be determined during BFS
         node.level = -1;        // Not assigned yet
         
         ArrayResize(nodes, nodeCount + 1);
         nodes[nodeCount] = node;
         nodeCount++;
      }
   }
   
   Print("Detected ", nodeCount, " swing nodes");
   return nodeCount > 0;
}

//+------------------------------------------------------------------+
//| Build Directed Graph                                             |
//+------------------------------------------------------------------+
void BuildDirectedGraph()
{
   // Clear existing edges
   ArrayResize(edges, 0);
   edgeCount = 0;
   
   // Create edges from older to newer nodes (time progression)
   for(int i = 0; i < nodeCount; i++)
   {
      // Find the next valid swing to connect to
      int nextIndex = -1;
      
      // Look for the next swing of alternating type
      for(int j = i + 1; j < nodeCount; j++)
      {
         // Check if swing types alternate (high-low-high-low pattern)
         if(nodes[j].isHigh != nodes[i].isHigh)
         {
            // Check if there are no other valid swings between i and j
            bool validConnection = true;
            
            // Skip checking intermediate nodes for simplicity
            // In a more sophisticated version, we could check for 
            // price action patterns between swings
            
            if(validConnection)
            {
               nextIndex = j;
               break;
            }
         }
      }
      
      // Add edge if found
      if(nextIndex > i)
      {
         Edge edge;
         edge.from = i;
         edge.to = nextIndex;
         
         ArrayResize(edges, edgeCount + 1);
         edges[edgeCount] = edge;
         edgeCount++;
      }
   }
   
   Print("Created ", edgeCount, " edges in the graph");
}

//+------------------------------------------------------------------+
//| Select BFS Root Based on Mode                                    |
//+------------------------------------------------------------------+
int SelectBFSRoot()
{
   int rootIndex = -1;
   datetime currentTime = TimeCurrent();
   
   switch(HistoryMode)
   {
      case MODE_PREVIOUS_BARS:
         // Find the oldest swing within BarsLookback
         for(int i = 0; i < nodeCount; i++)
         {
            if(nodes[i].index >= BarsLookback)
            {
               rootIndex = i;
               break;
            }
         }
         break;
         
      case MODE_PREVIOUS_DAYS:
      {
         datetime startTime = currentTime - (DaysLookback * 86400);
         // Find first swing after startTime
         for(int i = 0; i < nodeCount; i++)
         {
            if(nodes[i].time >= startTime)
            {
               rootIndex = i;
               break;
            }
         }
         break;
      }
         
      case MODE_YESTERDAY_ONLY:
      {
         MqlDateTime yesterday;
         TimeToStruct(currentTime - 86400, yesterday);
         yesterday.hour = 0;
         yesterday.min = 0;
         yesterday.sec = 0;
         datetime yesterdayStart = StructToTime(yesterday);
         
         datetime todayStart = yesterdayStart + 86400;
         
         // Find first swing of yesterday
         for(int i = 0; i < nodeCount; i++)
         {
            if(nodes[i].time >= yesterdayStart && nodes[i].time < todayStart)
            {
               rootIndex = i;
               break;
            }
         }
         break;
      }
   }
   
   // If no root found with mode criteria, use the oldest node
   if(rootIndex == -1 && nodeCount > 0)
   {
      rootIndex = 0;
   }
   
   return rootIndex;
}

こ/ja/セクションは、スイング検出、グラフ構築、トラバーサル、およびバイアス計算を統合的に制御することで、市場構造評価/ja/完全な処理パイプラインを統括します。CalculateBFSStructure関数は中核的な制御関数として機能し、まずすべて/ja/方向性および許可状態をリセットした上で、各分析ステージを順番に実行します。こ/ja/ように検出、グラフ構築、トラバーサル、意思決定更新を明確に分離することで、EAは市場構造/ja/再計算が一貫性を持ち、決定論的でデバッグしやすいも/ja/となることを保証し、同時に部分的または古い結果が取引判断に影響することを防ぎます。

DetectSwingNodes関数は、生/ja/過去価格データを意味/ja/ある構造ノードへと変換する役割を担います。選択された履歴モードに基づき、固定バー数、複数日、あるいは前日/ja/セッション/ja/みといった分析対象/ja/履歴データ量を動的に決定します。そ/ja/後、定義されたスイング期間に基づいて価格/ja/高値および安値をスキャンし、有効な転換点を識別します。こ/ja/プロセスにより、各スイングは周辺ノイズから分離されます。検出されたスイングポイントはグラフ/ja/基礎ノードとなり、それぞれが正確な時間、価格、および構造上/ja/識別情報を保持します。

スイングノードが確立された後、BuildDirectedGraphはそれらを有向市場グラフへと変換し、古いスイングから新しいスイングへと時間的一貫性を持って接続します。エッジは交互/ja/スイングタイプ間に/ja/み生成され、自然な市場構造を模倣する高値・安値/ja/進行を強制します。こ/ja/方向性を持つ接続性により、グラフは時間的流れを尊重し、逆方向や循環的な関係を排除することで、レベル順トラバーサルに適した構造となり、リペイントや論理的矛盾を防止します。

selectBFSRootはトラバーサル/ja/開始点を決定し、ユーザー定義/ja/履歴コンテキスト内において分析/ja/基準点を設定します。選択されたモードに応じて、ルートはバーウィンドウ内/ja/最も早い関連スイング、多日範囲内/ja/スイング、または単一セッション内/ja/スイングを表します。条件を満たすノードが存在しない場合、アルゴリズムは安全に最も古いスイングノードをデフォルトとして採用し、処理を継続できるようにします。こ/ja/ルート選択はトラバーサル/ja/出発視点を定義するため極めて重要であり、BFSがど/ja/ように過去構造を層状に評価し、最終的に現在/ja/市場バイアスへと変換するかに直接的な影響を与えます。

//+------------------------------------------------------------------+
//| Perform BFS Traversal                                            |
//+------------------------------------------------------------------+
void PerformBFSTraversal(int rootIndex)
{
   if(rootIndex < 0 || rootIndex >= nodeCount)
      return;
   
   // Reset node levels
   for(int i = 0; i < nodeCount; i++)
   {
      nodes[i].level = -1;
      nodes[i].bullish = false;
   }
   
   // Initialize queue for BFS
   BFSItem queue[];
   int queueSize = nodeCount;
   ArrayResize(queue, queueSize);
   
   // Enqueue root
   queue[0].nodeIndex = rootIndex;
   queue[0].level = 0;
   nodes[rootIndex].level = 0;
   int queueStart = 0;
   int queueEnd = 1;
   
   // BFS traversal
   while(queueStart < queueEnd)
   {
      // Dequeue
      BFSItem current = queue[queueStart];
      queueStart++;
      
      int currentIdx = current.nodeIndex;
      int currentLevel = current.level;
      
      // Classify node direction
      ClassifyNodeDirection(currentIdx);
      
      // Find and enqueue children (nodes reachable from current node)
      for(int e = 0; e < edgeCount; e++)
      {
         if(edges[e].from == currentIdx)
         {
            int childIdx = edges[e].to;
            if(nodes[childIdx].level == -1)  // Not visited yet
            {
               nodes[childIdx].level = currentLevel + 1;
               
               // Enqueue child
               if(queueEnd < queueSize)
               {
                  queue[queueEnd].nodeIndex = childIdx;
                  queue[queueEnd].level = currentLevel + 1;
                  queueEnd++;
               }
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Classify Node Direction                                          |
//+------------------------------------------------------------------+
void ClassifyNodeDirection(int nodeIndex)
{
   if(nodeIndex <= 0)
   {
      // First node, classify based on recent price action
      double currentPrice = iClose(_Symbol, _Period, 0);
      if(nodes[nodeIndex].isHigh)
      {
         nodes[nodeIndex].bullish = (currentPrice > nodes[nodeIndex].price);
      }
      else
      {
         nodes[nodeIndex].bullish = (currentPrice < nodes[nodeIndex].price);
      }
      return;
   }
   
   // Find previous node of the same type
   int prevSameType = -1;
   for(int i = nodeIndex - 1; i >= 0; i--)
   {
      if(nodes[i].isHigh == nodes[nodeIndex].isHigh)
      {
         prevSameType = i;
         break;
      }
   }
   
   if(prevSameType >= 0)
   {
      // Compare with previous node of same type
      if(nodes[nodeIndex].isHigh)
      {
         // Swing high: higher high = bullish, lower high = bearish
         nodes[nodeIndex].bullish = (nodes[nodeIndex].price > nodes[prevSameType].price);
      }
      else
      {
         // Swing low: higher low = bullish, lower low = bearish
         nodes[nodeIndex].bullish = (nodes[nodeIndex].price > nodes[prevSameType].price);
      }
   }
   else
   {
      // No previous node of same type, use simple logic
      double currentPrice = iClose(_Symbol, _Period, 0);
      if(nodes[nodeIndex].isHigh)
      {
         nodes[nodeIndex].bullish = (currentPrice > nodes[nodeIndex].price);
      }
      else
      {
         nodes[nodeIndex].bullish = (currentPrice < nodes[nodeIndex].price);
      }
   }
}

//+------------------------------------------------------------------+
//| Calculate Market Bias                                            |
//+------------------------------------------------------------------+
void CalculateMarketBias()
{
   double totalWeight = 0.0;
   double weightedSum = 0.0;
   
   for(int i = 0; i < nodeCount; i++)
   {
      if(nodes[i].level >= 0)  // Only consider visited nodes
      {
         // Calculate weight based on level (higher weight for lower levels)
         double weight = 1.0 / (1.0 + nodes[i].level * 0.2);
         
         // Add to weighted sum
         if(nodes[i].bullish)
            weightedSum += weight;
         else
            weightedSum -= weight;
            
         totalWeight += weight;
      }
   }
   
   if(totalWeight > 0)
   {
      currentBias = weightedSum / totalWeight;
   }
   else
   {
      currentBias = 0.0;
   }
   
   Print("Market Bias: ", DoubleToString(currentBias, 3));
}

//+------------------------------------------------------------------+
//| Update Trading Permissions                                       |
//+------------------------------------------------------------------+
void UpdateTradingPermissions()
{
   allowBuy = (currentBias > BiasThreshold);
   allowSell = (currentBias < -BiasThreshold);
   
   Print("Trading Permissions - Buy: ", allowBuy, ", Sell: ", allowSell);
}

//+------------------------------------------------------------------+
//| Check Trading Signals                                            |
//+------------------------------------------------------------------+
void CheckTradingSignals()
{
   // Check for existing positions
   if(PositionsTotal() > 0)
   {
      // Manage existing positions
      ManagePositions();
      return;
   }
   
   // Check for new signals
   if(allowBuy)
   {
      // Buy signal logic
      double askPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      double sl = askPrice - StopLoss * _Point;
      double tp = askPrice + TakeProfit * _Point;
      
      // Optional: Add confirmation from recent swing break
      if(CheckRecentSwingBreak(true))
      {
         ExecuteBuyOrder(askPrice, sl, tp);
      }
   }
   else if(allowSell)
   {
      // Sell signal logic
      double bidPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      double sl = bidPrice + StopLoss * _Point;
      double tp = bidPrice - TakeProfit * _Point;
      
      // Optional: Add confirmation from recent swing break
      if(CheckRecentSwingBreak(false))
      {
         ExecuteSellOrder(bidPrice, sl, tp);
      }
   }
}

//+------------------------------------------------------------------+
//| Check Recent Swing Break                                         |
//+------------------------------------------------------------------+
bool CheckRecentSwingBreak(bool forBuy)
{
   if(nodeCount < 2)
      return true;  // No recent swings, allow trade
   
   // Find the most recent swing
   int recentSwingIndex = -1;
   for(int i = nodeCount - 1; i >= 0; i--)
   {
      if(nodes[i].index < 50)  // Within recent 50 bars
      {
         recentSwingIndex = i;
         break;
      }
   }
   
   if(recentSwingIndex < 0)
      return true;
   
   double currentPrice = iClose(_Symbol, _Period, 0);
   
   if(forBuy)
   {
      // For buy: check if price has broken above a recent swing high
      if(nodes[recentSwingIndex].isHigh)
      {
         return (currentPrice > nodes[recentSwingIndex].price);
      }
   }
   else
   {
      // For sell: check if price has broken below a recent swing low
      if(!nodes[recentSwingIndex].isHigh)
      {
         return (currentPrice < nodes[recentSwingIndex].price);
      }
   }
   
   return true;
}

//+------------------------------------------------------------------+
//| Execute Buy Order using CTrade                                   |
//+------------------------------------------------------------------+
void ExecuteBuyOrder(double price, double sl, double tp)
{
   // Check if we already have a buy position
   if(trade.Buy(LotSize, _Symbol, price, sl, tp, "BFS Buy Signal"))
   {
      Print("Buy order executed successfully");
   }
   else
   {
      Print("Buy order failed. Error: ", GetLastError(), " - ", trade.ResultRetcodeDescription());
   }
}

//+------------------------------------------------------------------+
//| Execute Sell Order using CTrade                                  |
//+------------------------------------------------------------------+
void ExecuteSellOrder(double price, double sl, double tp)
{
   // Check if we already have a sell position
   if(trade.Sell(LotSize, _Symbol, price, sl, tp, "BFS Sell Signal"))
   {
      Print("Sell order executed successfully");
   }
   else
   {
      Print("Sell order failed. Error: ", GetLastError(), " - ", trade.ResultRetcodeDescription());
   }
}

//+------------------------------------------------------------------+
//| Manage Positions using CTrade                                    |
//+------------------------------------------------------------------+
void ManagePositions()
{
   for(int i = PositionsTotal() - 1; i >= 0; i--)
   {
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket))
      {
         string symbol = PositionGetString(POSITION_SYMBOL);
         long magic = PositionGetInteger(POSITION_MAGIC);
         
         if(symbol == _Symbol && magic == 12345)
         {
            // Check if we should close based on bias change
            if((PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && currentBias < -BiasThreshold) ||
               (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && currentBias > BiasThreshold))
            {
               // Close position due to bias reversal
               if(trade.PositionClose(ticket))
               {
                  Print("Position closed due to bias reversal. Ticket: ", ticket);
               }
               else
               {
                  Print("Failed to close position. Error: ", GetLastError(), " - ", trade.ResultRetcodeDescription());
               }
            }
         }
      }
   }
}

こ/ja/セクションでは、幅優先探索を用いて市場構造グラフをレベル順にトラバースすることで、EA/ja/コア推論エンジンを実装します。PerformBFSTraversal関数は選択されたルートスイングからトラバーサルを開始し、接続されたスイングを時間的順序に従って段階的に訪問し、それぞれ/ja/ノードにルートから/ja/構造的距離を反映する深さレベルを割り当てます。各ノードが訪問される際には、過去/ja/スイングと/ja/関係に基づいて強気または弱気に分類され、方向性/ja/解釈が個別/ja/価格ポイントではなく進化する市場構造に基づいておこなわれるようにします。

ClassifyNodeDirectionロジックは、スイングが強気構造または弱気構造を表すかを、そ/ja/同種/ja/直近スイングと/ja/比較によって決定します。高値と安値/ja/切り上がりは強気継続として解釈され、切り下がりは弱気/ja/意図を示します。比較対象となる過去スイングが存在しない場合には、アルゴリズムはスイングレベルと現在価格/ja/相互作用に基づいて評価し、構造/ja/境界領域においても方向性/ja/文脈を維持します。こ/ja/多層的な分類アプローチにより、各BFSレベルは時間/ja/経過とともに展開する構造を反映した意味/ja/ある方向情報を保持します。

最終的に、トラバーサル中に生成された方向シグナルは単一/ja/正規化された市場バイアスへと集約されます。CalculateMarketBias関数はより低いBFSレベルに高い重みを割り当てることで、遠い過去よりも直近/ja/構造が強く影響するように設計されています。こ/ja/バイアスはUpdateTradingPermissionsを通じて取引実行/ja/許可条件へと変換され、強制的に取引をおこなう/ja/ではなくフィルタリングとして機能します。そ/ja/後/ja/関数群はコンファメーション、注文実行、ポジション管理を処理し、取引が支配的な構造バイアスと一致するようにし、基礎となる市場方向が反転した場合には速やかにポジションを解消するよう設計されています。

//+------------------------------------------------------------------+
//| Update Visualization                                             |
//+------------------------------------------------------------------+
void UpdateVisualization()
{
   RemoveVisualization();
   
   // Draw swing nodes
   for(int i = 0; i < nodeCount; i++)
   {
      if(nodes[i].level >= 0 && nodes[i].index < 200)  // Only show recent 200 bars
      {
         string objName = "SwingNode_" + IntegerToString(i);
         
         // Create object based on swing type
         if(nodes[i].isHigh)
         {
            // Draw swing high as downward arrow
            ObjectCreate(0, objName, OBJ_ARROW_DOWN, 0, nodes[i].time, nodes[i].price);
            ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_TOP);
         }
         else
         {
            // Draw swing low as upward arrow
            ObjectCreate(0, objName, OBJ_ARROW_UP, 0, nodes[i].time, nodes[i].price);
            ObjectSetInteger(0, objName, OBJPROP_ANCHOR, ANCHOR_BOTTOM);
         }
         
         // Set color based on bullish/bearish classification
         if(nodes[i].bullish)
            ObjectSetInteger(0, objName, OBJPROP_COLOR, BullishColor);
         else
            ObjectSetInteger(0, objName, OBJPROP_COLOR, BearishColor);
         
         // Set size
         ObjectSetInteger(0, objName, OBJPROP_WIDTH, 2);
         
         // Add level annotation
         string labelName = "Label_" + IntegerToString(i);
         ObjectCreate(0, labelName, OBJ_TEXT, 0, nodes[i].time, nodes[i].price);
         ObjectSetString(0, labelName, OBJPROP_TEXT, "L" + IntegerToString(nodes[i].level));
         ObjectSetInteger(0, labelName, OBJPROP_COLOR, clrWhite);
         ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 8);
         ObjectSetInteger(0, labelName, OBJPROP_ANCHOR, ANCHOR_UPPER);
         
         // Adjust label position based on swing type
         if(nodes[i].isHigh)
            ObjectSetDouble(0, labelName, OBJPROP_PRICE, nodes[i].price + 10 * _Point);
         else
            ObjectSetDouble(0, labelName, OBJPROP_PRICE, nodes[i].price - 10 * _Point);
      }
   }
   
   // Draw edges between nodes
   for(int e = 0; e < edgeCount; e++)
   {
      int fromIdx = edges[e].from;
      int toIdx = edges[e].to;
      
      // Only draw edges for visible nodes
      if(fromIdx < nodeCount && toIdx < nodeCount && 
         nodes[fromIdx].index < 200 && nodes[toIdx].index < 200)
      {
         string edgeName = "Edge_" + IntegerToString(e);
         ObjectCreate(0, edgeName, OBJ_TREND, 0, nodes[fromIdx].time, nodes[fromIdx].price, 
                     nodes[toIdx].time, nodes[toIdx].price);
         ObjectSetInteger(0, edgeName, OBJPROP_COLOR, clrGray);
         ObjectSetInteger(0, edgeName, OBJPROP_WIDTH, 1);
         ObjectSetInteger(0, edgeName, OBJPROP_STYLE, STYLE_DASH);
      }
   }
   
   // Draw bias indicator
   string biasObjName = "BiasIndicator";
   ObjectCreate(0, biasObjName, OBJ_RECTANGLE_LABEL, 0, 0, 0);
   ObjectSetInteger(0, biasObjName, OBJPROP_XDISTANCE, 10);
   ObjectSetInteger(0, biasObjName, OBJPROP_YDISTANCE, 10);
   ObjectSetInteger(0, biasObjName, OBJPROP_XSIZE, 150);
   ObjectSetInteger(0, biasObjName, OBJPROP_YSIZE, 60);
   ObjectSetInteger(0, biasObjName, OBJPROP_BGCOLOR, clrBlack);
   ObjectSetInteger(0, biasObjName, OBJPROP_BORDER_TYPE, BORDER_FLAT);
   ObjectSetInteger(0, biasObjName, OBJPROP_BORDER_COLOR, clrGray);
   
   // Bias value
   string biasValueName = "BiasValue";
   ObjectCreate(0, biasValueName, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, biasValueName, OBJPROP_XDISTANCE, 20);
   ObjectSetInteger(0, biasValueName, OBJPROP_YDISTANCE, 20);
   ObjectSetString(0, biasValueName, OBJPROP_TEXT, "Bias: " + DoubleToString(currentBias, 3));
   ObjectSetInteger(0, biasValueName, OBJPROP_COLOR, clrWhite);
   ObjectSetInteger(0, biasValueName, OBJPROP_FONTSIZE, 12);
   
   // Trading status
   string statusName = "TradingStatus";
   ObjectCreate(0, statusName, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, statusName, OBJPROP_XDISTANCE, 20);
   ObjectSetInteger(0, statusName, OBJPROP_YDISTANCE, 40);
   
   string statusText = "Status: ";
   if(allowBuy)
      statusText += "BUY";
   else if(allowSell)
      statusText += "SELL";
   else
      statusText += "NEUTRAL";
   
   ObjectSetString(0, statusName, OBJPROP_TEXT, statusText);
   ObjectSetInteger(0, statusName, OBJPROP_COLOR, clrYellow);
   ObjectSetInteger(0, statusName, OBJPROP_FONTSIZE, 10);
}

//+------------------------------------------------------------------+
//| Remove Visualization                                             |
//+------------------------------------------------------------------+
void RemoveVisualization()
{
   // Remove all objects created by this EA
   int total = ObjectsTotal(0);
   for(int i = total - 1; i >= 0; i--)
   {
      string name = ObjectName(0, i);
      if(StringFind(name, "SwingNode_") == 0 ||
         StringFind(name, "Label_") == 0 ||
         StringFind(name, "Edge_") == 0 ||
         StringFind(name, "Bias") == 0 ||
         StringFind(name, "TradingStatus") == 0)
      {
         ObjectDelete(0, name);
      }
   }
}

こ/ja/セクションでは、UpdateVisualization関数が内部/ja/グラフベース市場構造をチャート上/ja/明確でリアルタイムな視覚要素へと変換します。まず、重複や古い情報/ja/表示を防ぐために、以前に描画されたすべて/ja/オブジェクトを削除し、表示が常に最新/ja/分析結果を反映するようにします。各スイングノードは矢印として描画され、スイング高値は下向き矢印、スイング安値は上向き矢印として表示されます。視認性とパフォーマンスを維持するため、こ/ja/描画は直近200バーに制限されます。さらに、各ノードにはBFS/ja/深さを示すレベルラベルが付与され、構造階層においてそ/ja/ノードがルートからど/ja/程度離れているかを視覚的に表現します。

個々/ja/ノード表示に加えて、こ/ja/関数はノード間/ja/関係性も可視化し、グラフ内/ja/エッジを表す破線/ja/トレンドラインを描画します。これにより、価格がど/ja/スイングから次/ja/スイングへと時間的に遷移しているかを視覚的にマッピングします。さらに、簡易オンチャートダッシュボードが生成され、現在/ja/市場バイアスおよびトレーディング許可状態(BUY、SELL、NEUTRALなど)が表示されることで、トレーダーがアルゴリズム/ja/構造的判断を即座に理解できるようにします。補完的なRemoveVisualization関数はクリーンアップ機構として機能し、命名規則に基づいてEAが作成したオブジェクト/ja/みを選択的に削除することで、チャートを整理された状態に保ち、各ビジュアライゼーション/ja/更新精度を維持しつつ、チャート表示を不必要に妨げません。


バックテスト結果

バックテストはH1時間足において2か月間(2025年10月1日から2025年12月1日まで)/ja/テストウィンドウで評価され、以下/ja/設定が使用されました。

入力パラメータ

それでは、エクイティカーブとバックテストを見ていきましょう。

エクイティカーブ

バックテスト


結論

本システムではまず市場構造をスイングハイおよびスイングローをノードとするグラフとして再定義し、時間的一貫性を持つ遷移を有向エッジとして表現することで、幅優先探索(BFS)を取引システムへ統合しました。スイングは生/ja/価格データから検出され、文脈的属性を持つノードとして構造化され、そ/ja/後、現実的な高値・安値/ja/推移を反映する形で接続されます。BFSトラバーサルは慎重に選択された履歴コンテキスト内/ja/ルートを起点として適用され、市場構造をレベルごとに層状に展開することを可能にします。こ/ja/トラバーサルにより測定可能な構造バイアスが生成され、それが明確な取引許可へと変換される一方で、専用/ja/可視化レイヤーがノード、エッジ、およびバイアス情報をチャート上に直接描画し、透明性と検証可能性を提供します。

結論として、BFSを取引に統合することにより、トレーダーは市場構造を解釈するため/ja/体系的かつ客観的な方法を得ることができ、主観的なスイング分析を数学的に基礎づけられたフレームワークへと置き換えることができます。プライスアクションを構造的レベルごとに段階的に処理することで、トレーダーはトレンド/ja/強さ、方向性バイアス、および有効なエントリー条件についてより明確な洞察を得ることができます。全体として、こ/ja/アプローチはトレーダーが構造、一貫性、明確さを持って取引することを支援し、単独/ja/価格変動ではなく市場/ja/階層的な流れそ/ja/も/ja/に沿った執行を可能にします。 

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

添付されたファイル |
BFS-EA.mq5 (30.2 KB)
MQL5取引ツール(第15回):Canvas/ja/ぼかし効果、影描画、滑らかなマウスホイールスクロール MQL5取引ツール(第15回):Canvas/ja/ぼかし効果、影描画、滑らかなマウスホイールスクロール
MQL5 Canvasダッシュボードを高度な視覚効果で強化します。具体的には、フォグオーバーレイ/ja/ため/ja/ぼかしグラデーション、ヘッダー/ja/影描画、そしてより滑らかな線や曲線を実現するアンチエイリアス描画を追加します。また、チャート/ja/ズームスケールに干渉しない滑らかなマウスホイールスクロールもテキストパネルに実装し、機能面でも改良を加えます。
ラリー・ウィリアムズ/ja/『市場/ja/秘密』(第9回):利益につながるパターン ラリー・ウィリアムズ/ja/『市場/ja/秘密』(第9回):利益につながるパターン
ラリー・ウィリアムズ/ja/短期取引パターンに関する実証研究です。定番/ja/パターンをMQL5で自動化し、実際/ja/市場データでテストし、そ/ja/一貫性、収益性、および実運用上/ja/有用性を評価します。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
初心者からエキスパートへ:サプライ&デマンドゾーンの統計的検証 初心者からエキスパートへ:サプライ&デマンドゾーンの統計的検証
サプライ&デマンド取引戦略の背後にある、見落とされがちな統計的基盤を明らかにします。MQL5とPythonをJupyter Notebookワークフローで連携させることで、マーケットに対する視覚的な仮定を定量的な洞察へと変換する体系的なデータ駆動型リサーチをおこないます。本記事では、データ収集からPythonによる統計分析、アルゴリズム設計、テスト、最終的な結論に至るまで、一連の研究プロセスを解説します。手法と結果の詳細については、本文をご参照ください。