グラフ理論:取引における幅優先探索(BFS)/ja/応用
目次
はじめに
金融市場は表面的にはカオスに見えることが多いですが、価格は一貫してある領域から別/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
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
MQL5取引ツール(第15回):Canvas/ja/ぼかし効果、影描画、滑らかなマウスホイールスクロール
ラリー・ウィリアムズ/ja/『市場/ja/秘密』(第9回):利益につながるパターン
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
初心者からエキスパートへ:サプライ&デマンドゾーンの統計的検証
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索