MQL5でのスイング極値とプルバック(第1回):マルチタイムフレームインジケータの開発
目次
はじめに
以前の開発では、下位足(LTF)の価格変動からスイングハイとスイングローを体系的に識別することで、Swing Extremes and Pullbacksインジケータを構築しました。このアプローチでは、短期的な構造転換、つまり、高値更新(Higher High)、安値更新(Lower Low)、押し戻し、モメンタムの鈍化を分析対象としました。これらの要素を利用することで、高い時間軸で明確な反転シグナルが現れる前に、潜在的な反転ポイントを予測することを目的としました。さらに、スイング検出ロジックとプルバック検証ルールを組み合わせることで、LTFの生のボラティリティを、構造化され、解釈しやすい市場挙動モデルへ変換するフレームワークを構築しました。
次のステップでは、この構造ロジックをコードへ直接組み込み、インジケータを完全に自動化します。自動化では、有効なスイングポイントの判定、不要な価格ノイズのフィルタリング、ルールベースによるプルバック確認、リアルタイムでの市場構造更新の処理をアルゴリズムによって定義します。これにより、システムはLTF市場構造を継続的にスキャンし、裁量判断なしで価格変化を解釈できるようになります。自動化された構造分析によって、より客観的で一貫性のある、より早い段階での反転予測が可能となり、これは最終的には執行精度とトレード判断の効率向上につながります。
自動化の概要
自動化の観点では、極端な弱気方向への値動きは、正確なルールベースの実行モデルへ変換されます。価格がLTFの直近安値を大きく下抜け、その状態でチャートが更新された場合、システムはLTF_LastHighとLTF_LastLowの両方が現在の市場価格より上に位置しているかを評価します。これは、直近レンジ下方への構造的なディスプレイスメントを確認するものです。これらの条件が満たされると、ロジックはこの値動きを継続的な下降ではなく、エクゾースション(相場疲弊)として解釈し、自動的に買いトレードを実行します。その後、システムはLTF_LastHighをテイクプロフィットレベルとして継承し、過去の構造への平均回帰をターゲットとします。これにより裁量判断は完全に排除され、トレードは構造的なインバランスと事前定義された閾値のみに基づいて実行されます。その結果、弱気方向への拡張が統計的に行き過ぎた状態になった場合でも、一貫したエントリーを実現できます。

逆に、極端な強気方向への値動きでは、自動化は反対のロジックを適用します。価格がLTFの直近高値を急速に上抜け、その状態でチャートが更新された場合、アルゴリズムはLTF_LastHighとLTF_LastLowの両方が現在の市場価格より下に位置しているかを確認します。これは、上方向への構造的な過剰拡張を確認するものです。この条件が成立すると、システムは価格が直近の構造的な範囲を超えて伸びていると判断し、自動的に売りトレードを実行します。この場合、LTF_LastLowがテイクプロフィットレベルとして継承され、過去の構造へ向かうプルバックを狙ったポジションが構築されます。

導入手順
//+------------------------------------------------------------------+ //| Auto Swing Extremes.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include <Trade/Trade.mqh> CTrade trade; //+------------------------------------------------------------------+ //| Input Parameters | //+------------------------------------------------------------------+ input ENUM_TIMEFRAMES HTF = PERIOD_H1; // Higher Timeframe input ENUM_TIMEFRAMES LTF = PERIOD_M5; // Lower Timeframe input int SwingBars = 3; // Bars for swing detection input double RiskPercent = 1.0; // Risk per trade (%) input int Stop_Loss = 2000; // StopLoss input bool Visualize = true; // Show swing points & structure //+------------------------------------------------------------------+ //| Swing Point Structure | //+------------------------------------------------------------------+ struct SwingPoint { datetime time; double price; int type; // 1 = High, -1 = Low int barIndex; void Reset() { time = 0; price = 0.0; type = 0; barIndex = 0; } };
まず、<Trade/Trade.mqh>をインクルードし、CTradeオブジェクトを作成することで、トレード環境を初期化します。これにより、プログラム上でトレードの実行および管理を構造化された形で処理できるようになります。次に、システムの動作を制御する主要な入力パラメータを定義します。これには、マルチタイムフレームによる市場構造分析で使用する上位足(HTF)と下位足(LTF)、有効なスイングハイおよびスイングローを検出するために使用するSwingBarsの数、1トレードあたりの口座リスク割合、固定ストップロス値、そしてチャート上に構造要素を表示するかどうかを切り替えるビジュアライゼーション設定が含まれます。これらの入力項目によってシステムを柔軟に設定できるようになり、コアロジックを変更することなく、異なる市場環境、銘柄、トレーダーの好みに合わせて戦略を適応させることが可能になります。
次に、SwingPoint構造体を定義し、コード上で市場構造を正式に表現します。この構造体には、スイングの時間情報、価格レベル、タイプ(1はスイングハイ、-1はスイングローを示します)、そしてバーインデックス位置が格納されます。これにより、市場の転換点を追跡するために必要なすべての重要なデータを1つの構造化されたオブジェクトとして管理できます。また、含まれているReset()関数によって、スイングポイントを安全にクリアし、再利用できるようになります。これにより、更新処理中でもデータの整合性を維持できます。スイング情報を構造化されたオブジェクトとして整理することで、自動化された市場構造検出システムを構築するための基盤を確立します。
//+------------------------------------------------------------------+ //| Market Structure States | //+------------------------------------------------------------------+ enum STRUCTURE_STATE { STRUCT_NEUTRAL, STRUCT_BULLISH, STRUCT_BEARISH }; //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ SwingPoint htf_lastHigh, htf_prevHigh; SwingPoint htf_lastLow, htf_prevLow; SwingPoint ltf_lastHigh, ltf_prevHigh; SwingPoint ltf_lastLow, ltf_prevLow; STRUCTURE_STATE htfBias = STRUCT_NEUTRAL; STRUCTURE_STATE ltfStructure = STRUCT_NEUTRAL; datetime lastHtfUpdate = 0; datetime lastLtfUpdate = 0; // Track last traded swing to avoid multiple entries on same swing datetime lastBuySwingTime = 0; datetime lastSellSwingTime = 0; double lastBuySwingPrice = 0; double lastSellSwingPrice = 0; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Validate timeframe combination if(PeriodSeconds(HTF) <= PeriodSeconds(LTF)) { Alert("HTF must be higher than LTF!"); return INIT_PARAMETERS_INCORRECT; } // Initialize swing points htf_lastHigh.Reset(); htf_prevHigh.Reset(); htf_lastLow.Reset(); htf_prevLow.Reset(); ltf_lastHigh.Reset(); ltf_prevHigh.Reset(); ltf_lastLow.Reset(); ltf_prevLow.Reset(); // Initialize trade tracking lastBuySwingTime = 0; lastSellSwingTime = 0; lastBuySwingPrice = 0; lastSellSwingPrice = 0; // Initial structure detection UpdateHTFStructure(); UpdateLTFStructure(); return INIT_SUCCEEDED; }
このセクションでは、戦略の方向性判断を制御する市場構造ロジックのフレームワークを定義します。STRUCTURE_STATE列挙型は、ニュートラル、強気、弱気という3つの市場状態を定義し、任意の時点におけるHTFのバイアスとLTFの構造を明確に分類できるようにします。グローバル変数SwingPointでは、HTFおよびLTFそれぞれの最新および過去の高値・安値を追跡するための変数を定義します。これにより、連続する構造ポイント間の比較(例:高値切り上げや安値切り下げ)が可能になります。さらに、htfBiasやltfStructureなどの状態変数を使用して、現在有効な市場構造の方向性を保持します。また、lastHtfUpdateおよびlastLtfUpdateのタイムスタンプによって、新しいバーが形成された場合のみ構造更新を実行するよう制御します。これにより、処理効率を維持し、不要な再計算を防止します。
トレード制御の安全機能もグローバルスコープに組み込まれており、同一のスイングレベルから重複したエントリーが発生することを防止します。lastBuySwingTime、lastSellSwingTimeおよびそれぞれに対応する価格情報を保存することで、システムは同一の構造シグナルに対して複数回トレードを実行することを回避します。OnInit()内では、まず選択されたHTFが実際にLTFよりも上位の時間足であるかを検証します。これにより、正しいマルチタイムフレーム階層が維持されます。その後、すべてのスイング構造とトレード追跡用変数をリセットし、クリーンな初期状態を確立します。続いて、UpdateHTFStructure()およびUpdateLTFStructure()を使用して初期市場構造スキャンを実行します。これにより、ライブ実行が開始される前に、システムを現在の市場環境と同期させます。
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Clean up objects ObjectsDeleteAll(0, "MS_"); ChartRedraw(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { static datetime lastLtfBar = 0; datetime currentLtfBar = iTime(_Symbol, LTF, 0); // Process on new LTF bar if(currentLtfBar != lastLtfBar) { lastLtfBar = currentLtfBar; // Update HTF bias if needed (on new HTF bar) if(TimeCurrent() - lastHtfUpdate >= PeriodSeconds(HTF)) { UpdateHTFStructure(); lastHtfUpdate = TimeCurrent(); } // Store previous LTF structure for comparison STRUCTURE_STATE prevLtfStructure = ltfStructure; // Update LTF structure UpdateLTFStructure(); // Check for structure breaks CheckStructureBreak(); // Try to execute trades if structure changed or conditions met if(ltfStructure != prevLtfStructure || (ltf_lastHigh.price > 0 && ltf_lastLow.price > 0)) { TryExecuteTrade(); } // Update visualization if(Visualize) UpdateVisualization(); } }
ここでは、エキスパートアドバイザー(EA)のクリーンアップ処理とリアルタイム実行フローを管理します。OnDeinit()では、「MS_」をプレフィックスとして持つすべてのチャートオブジェクトを削除します。これにより、EAがチャートから解除された際に、市場構造関連の描画要素がすべてクリアされます。その後、チャートの再描画を実行し、表示状態の整合性を維持します。OnTick()では、LTFの新しいバー形成時のみ、システムロジックを処理します。これはタイムスタンプを比較することで判定され、すべてのティックごとに不要な計算が実行されることを防ぎます。新しいバーが形成されると、十分な時間が経過している場合に限り、HTFのバイアスを更新します。その後、LTF構造を更新し、構造ブレイクを確認するとともに、以前の構造状態と現在の構造状態を比較します。市場構造転換が発生した場合、または有効なスイングデータが存在する場合、TryExecuteTrade()を通じてトレード実行を試みます。また、ビジュアライゼーション機能が有効になっている場合は、最新の市場構造状態を反映するようにチャート描画を更新します。これにより、ロジック処理と視覚的なフィードバックが常に同期された状態で維持されます。
//+------------------------------------------------------------------+ //| Update Higher Timeframe Structure | //+------------------------------------------------------------------+ void UpdateHTFStructure() { // Get HTF data MqlRates htfRates[]; ArraySetAsSeries(htfRates, true); CopyRates(_Symbol, HTF, 0, 100, htfRates); if(ArraySize(htfRates) < SwingBars * 2 + 1) return; // Detect swing points on HTF for(int i = SwingBars; i < ArraySize(htfRates) - SwingBars; i++) { bool isHigh = true; bool isLow = true; // Check if current bar is swing high for(int j = 1; j <= SwingBars; j++) { if(htfRates[i].high <= htfRates[i-j].high || htfRates[i].high <= htfRates[i+j].high) { isHigh = false; } if(htfRates[i].low >= htfRates[i-j].low || htfRates[i].low >= htfRates[i+j].low) { isLow = false; } } // Update swing highs if(isHigh) { htf_prevHigh = htf_lastHigh; htf_lastHigh.time = htfRates[i].time; htf_lastHigh.price = htfRates[i].high; htf_lastHigh.type = 1; htf_lastHigh.barIndex = i; } // Update swing lows if(isLow) { htf_prevLow = htf_lastLow; htf_lastLow.time = htfRates[i].time; htf_lastLow.price = htfRates[i].low; htf_lastLow.type = -1; htf_lastLow.barIndex = i; } } // Update HTF bias htfBias = DetectStructure(htf_lastHigh, htf_prevHigh, htf_lastLow, htf_prevLow); } //+------------------------------------------------------------------+ //| Update Lower Timeframe Structure | //+------------------------------------------------------------------+ void UpdateLTFStructure() { // Get LTF data MqlRates ltfRates[]; ArraySetAsSeries(ltfRates, true); CopyRates(_Symbol, LTF, 0, 100, ltfRates); if(ArraySize(ltfRates) < SwingBars * 2 + 1) return; // Detect swing points on LTF for(int i = SwingBars; i < ArraySize(ltfRates) - SwingBars; i++) { bool isHigh = true; bool isLow = true; // Check if current bar is swing high for(int j = 1; j <= SwingBars; j++) { if(ltfRates[i].high <= ltfRates[i-j].high || ltfRates[i].high <= ltfRates[i+j].high) { isHigh = false; } if(ltfRates[i].low >= ltfRates[i-j].low || ltfRates[i].low >= ltfRates[i+j].low) { isLow = false; } } // Update swing highs if(isHigh) { ltf_prevHigh = ltf_lastHigh; ltf_lastHigh.time = ltfRates[i].time; ltf_lastHigh.price = ltfRates[i].high; ltf_lastHigh.type = 1; ltf_lastHigh.barIndex = i; } // Update swing lows if(isLow) { ltf_prevLow = ltf_lastLow; ltf_lastLow.time = ltfRates[i].time; ltf_lastLow.price = ltfRates[i].low; ltf_lastLow.type = -1; ltf_lastLow.barIndex = i; } } // Update LTF structure ltfStructure = DetectStructure(ltf_lastHigh, ltf_prevHigh, ltf_lastLow, ltf_prevLow); }
UpdateHTFStructure()関数は、定義されたSwingBarsパラメータに基づき、HTFの価格データを分析し、有効なスイングハイとスイングローを検出します。この関数では、最新のHTFローソク足100本を取得し、十分なデータが存在することを確認した後、各対象バーをスキャンして市場構造上の極値として有効かどうかを判定します。あるバーの高値が、指定された範囲内に存在する周囲のバーの高値よりも高い場合、そのバーはスイングハイとして分類されます。同様に、あるバーの安値が周囲のバーの安値よりも低い場合、そのバーはスイングローとして分類されます。新しいスイングが確定すると、直前のスイング情報は保持され、最新のスイング情報が時間、価格、タイプ、インデックスとともに更新されます。すべての候補バーの処理が完了すると、DetectStructure()を使用して、最新および過去のスイングハイ・スイングローを比較し、上位時間足全体のバイアス(強気、弱気、ニュートラル)を判定します。
UpdateLTFStructure()関数では、より細かな価格データを対象として、LTF上で同様のロジックを適用します。この関数は、同じスイング検出プロセスを使用して各バーを評価し、許可された範囲内のバーに対して構造的な高値と安値を確認します。対称的な比較条件によってスイングポイントを確定し、保存されているLTFスイング変数を適切に更新します。最新および過去のスイングポイントを両方保持することで、システムは市場が高値切り上げを形成しているのか、安値切り下げを形成しているのか、または構造転換へ移行しているのかを判断できます。最後のステップでは、DetectStructure()を使用して更新されたLTF市場構造状態を割り当てます。これにより、トレードロジックと反転検出が、両方の時間足において継続的に更新され、客観的に定義された市場構造に基づいて動作することを保証します。
//+------------------------------------------------------------------+ //| Detect Market Structure | //+------------------------------------------------------------------+ STRUCTURE_STATE DetectStructure(SwingPoint &lastHigh, SwingPoint &prevHigh, SwingPoint &lastLow, SwingPoint &prevLow) { // Need at least two highs and two lows if(lastHigh.price == 0 || prevHigh.price == 0 || lastLow.price == 0 || prevLow.price == 0) return STRUCT_NEUTRAL; bool isHigherHigh = lastHigh.price > prevHigh.price; bool isHigherLow = lastLow.price > prevLow.price; bool isLowerLow = lastLow.price < prevLow.price; bool isLowerHigh = lastHigh.price < prevHigh.price; if(isHigherHigh && isHigherLow) return STRUCT_BULLISH; if(isLowerLow && isLowerHigh) return STRUCT_BEARISH; return STRUCT_NEUTRAL; }DetectStructure()関数は、最新および過去のスイングハイ・スイングローを比較することで、現在の市場構造を判定します。まず、十分なスイングデータが存在するかを確認します。判定には少なくとも2つの確定したスイングハイと2つの確定したスイングローが必要であり、この条件を満たさない場合はニュートラル状態を返します。その後、構造の進行状態を評価します。最新の高値が過去の高値より高く、かつ最新の安値が過去の安値より高い場合、市場は強気構造(Higher HighとHigher Low)として分類されます。逆に、最新の安値が過去の安値より低く、かつ最新の高値が過去の高値より低い場合、市場構造は弱気構造(Lower LowとLower High)として判定されます。どちらの条件も完全には満たさない場合、関数はニュートラル状態を返します。これは、レンジ形成や市場構造の移行状態を示します。
//+------------------------------------------------------------------+ //| New Sell Condition | //+------------------------------------------------------------------+ bool CheckSellCondition() { // Need valid LTF swing points if(ltf_lastHigh.price == 0 || ltf_lastLow.price == 0) return false; // Get current price double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID); // Check if price is above both last high and last low bool priceAboveHigh = currentPrice > ltf_lastHigh.price; bool priceAboveLow = currentPrice > ltf_lastLow.price; // Check if this is a new swing (not already traded) bool isNewSwing = (ltf_lastHigh.time != lastSellSwingTime) || (ltf_lastHigh.price != lastSellSwingPrice); return (priceAboveHigh && priceAboveLow && isNewSwing); } //+------------------------------------------------------------------+ //| New Buy Condition | //+------------------------------------------------------------------+ bool CheckBuyCondition() { // Need valid LTF swing points if(ltf_lastLow.price == 0) return false; // Get current price double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK); // Check if price is below last low bool priceBelowLow = currentPrice < ltf_lastLow.price; // Check if this is a new swing (not already traded) bool isNewSwing = (ltf_lastLow.time != lastBuySwingTime) || (ltf_lastLow.price != lastBuySwingPrice); return (priceBelowLow && isNewSwing); } //+------------------------------------------------------------------+ //| Execute trade | //+------------------------------------------------------------------+ void ExecuteTrade(ENUM_ORDER_TYPE tradeType, string symbol) { // Get current price double price = (tradeType == ORDER_TYPE_BUY) ? SymbolInfoDouble(symbol, SYMBOL_ASK) : SymbolInfoDouble(symbol, SYMBOL_BID); price = NormalizeDouble(price, _Digits); // Calculate stop loss double sl = CalculateSL(tradeType == ORDER_TYPE_BUY); sl = NormalizeDouble(sl, _Digits); // Calculate take profit based on your requirements double tp = 0; if(tradeType == ORDER_TYPE_BUY) { // For Buy: TP at next structure level (last high) tp = (ltf_lastHigh.price > 0) ? ltf_lastHigh.price : price + (MathAbs(price - sl) * 2); } else if(tradeType == ORDER_TYPE_SELL) // ORDER_TYPE_SELL { // For Sell: TP at last low (as requested) tp = (ltf_lastLow.price > 0) ? ltf_lastLow.price : price - (MathAbs(price - sl) * 2); } tp = NormalizeDouble(tp, _Digits); // Ensure TP is valid (not too close to entry) double minDistance = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE) * 10; if(MathAbs(price - tp) < minDistance) { Print("TP too close to entry, adjusting..."); if(tradeType == ORDER_TYPE_BUY) tp = price + (MathAbs(price - sl) * 2); else tp = price - (MathAbs(price - sl) * 2); } // Calculate lot size based on risk double volume = CalculateLotSize(price, sl); // Ensure volume is within valid range double volume_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); double volume_min = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double volume_max = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); // Normalize volume to step size volume = floor(volume / volume_step) * volume_step; volume = MathMax(volume, volume_min); volume = MathMin(volume, volume_max); // Prepare trade comment string comment = StringFormat("MS_%s_%s", (tradeType == ORDER_TYPE_BUY) ? "BUY" : "SELL", TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES)); // Execute trade using CTrade bool success = trade.PositionOpen(symbol, tradeType, volume, price, sl, tp, comment); if(success) { // Update swing tracking to avoid multiple entries if(tradeType == ORDER_TYPE_BUY) { lastBuySwingTime = ltf_lastLow.time; lastBuySwingPrice = ltf_lastLow.price; } else { lastSellSwingTime = ltf_lastHigh.time; lastSellSwingPrice = ltf_lastHigh.price; } Print(StringFormat("Trade Opened: %s | Price: %.5f | SL: %.5f | TP: %.5f | Lots: %.2f", (tradeType == ORDER_TYPE_BUY) ? "BUY" : "SELL", price, sl, tp, volume)); } else { Print("Trade failed: ", trade.ResultRetcodeDescription()); } } //+------------------------------------------------------------------+ //| Try Execute Trade | //+------------------------------------------------------------------+ void TryExecuteTrade() { // Check if position already exists if(PositionSelect(_Symbol)) { Print("Position already exists, waiting for closure..."); return; } // For Sell positions: Price above both last high and last low if(CheckSellCondition()) { // Optional: Check HTF bias filter if(htfBias == STRUCT_BEARISH || htfBias == STRUCT_NEUTRAL) { Print("SELL condition met: Price above LTF high and low"); OpenSell(); return; } } // For Buy positions: Price below last low if(CheckBuyCondition()) { // Optional: Check HTF bias filter if(htfBias == STRUCT_BULLISH || htfBias == STRUCT_NEUTRAL) { Print("BUY condition met: Price below LTF low"); OpenBuy(); return; } } }
CheckSellCondition()およびCheckBuyCondition()関数は、LTFのディスプレイスメントに基づいたトレードエントリー条件を定義します。売りセットアップでは、まず有効なスイングデータが存在することを確認します。その後、現在のBid価格が最新のLTFスイングハイおよびLTFスイングローの両方を上回っているかを確認します。これは、直近のLTF構造を超えた強気方向への過剰拡張を示します。さらに、このスイングがすでにトレード済みではないことを、保存されているスイング時間と価格情報を比較して確認し、重複エントリーを防止します。同様に、買い条件では、価格が最新のLTFスイングローを下回っていることを確認し、弱気方向へのエクゾースション(相場疲弊)を示します。また、このスイングが新しいものであることを検証した上で、条件成立時にtrueを返します。これらの条件によって、市場構造上の極端な価格変動を、客観的でルールベースのエントリーシグナルへ変換します。同時に、1スイングにつき1回のみトレードを許可する制御を適用することで、規律あるエントリー管理を実現します。
ExecuteTrade()関数は、トレード生成およびリスク管理全体を処理します。まず、正しいエントリー価格を決定します(買いの場合はAsk、売りの場合はBid)。次に、専用関数を使用してストップロスを計算し、構造に基づいてテイクプロフィットを動的に設定します。買いトレードでは最新のLTF高値をTPとして使用し、売りトレードでは最新のLTF安値をTPとして使用します。構造ベースのTPレベルが利用できない場合、またはエントリー価格から近すぎる場合、システムはリスクリワード比に基づいたフォールバック計算を使用します。ポジションサイズは口座リスク割合に基づいて計算され、その後、ブローカーの制約である最小ロット、最大ロット、ロットステップ幅に合わせて正規化されます。トレードはCTradeオブジェクトを使用して実行され、成功した場合は対象スイングの情報が保存されます。これにより、同一のLTF構造レベルに対する繰り返しエントリーを防止します。
TryExecuteTrade()は、最終的な売買判断のゲートキーパーとして機能します。最初に、対象銘柄ですでにポジションが存在するかを確認し、重複したトレードを防止します。ポジションが存在しない場合、売り条件(価格が最新のLTF高値およびLTF安値の両方を上回っている状態)を評価し、必要に応じてHTFバイアスによるフィルターを適用します。HTF構造が一致している場合、またはニュートラル状態の場合のみトレードが許可されます。条件が成立すると、売り注文を実行します。続いて、買い条件(価格が最新のLTF安値を下回っている状態)を評価します。この場合もHTFバイアスによるフィルターを任意で適用し、HTF構造が強気またはニュートラルの場合のみトレードを許可します。この多層的な処理によって、トレードは構造的ディスプレイスメント、スイングの新規性、HTFバイアスが同時に一致した場合のみ実行されます。これにより、規律ある構造ベースの自動売買ロジックが実現されます。
//+------------------------------------------------------------------+ //| Check Structure Break | //+------------------------------------------------------------------+ void CheckStructureBreak() { MqlRates current[]; ArraySetAsSeries(current, true); CopyRates(_Symbol, LTF, 0, 1, current); if(ArraySize(current) < 1) return; // Bullish structure break if(ltfStructure == STRUCT_BULLISH && current[0].close < ltf_lastLow.price) { ResetLTFStructure(); } // Bearish structure break if(ltfStructure == STRUCT_BEARISH && current[0].close > ltf_lastHigh.price) { ResetLTFStructure(); } } //+------------------------------------------------------------------+ //| Reset LTF Structure | //+------------------------------------------------------------------+ void ResetLTFStructure() { ltf_lastHigh.Reset(); ltf_prevHigh.Reset(); ltf_lastLow.Reset(); ltf_prevLow.Reset(); ltfStructure = STRUCT_NEUTRAL; // Also reset trade tracking lastBuySwingTime = 0; lastSellSwingTime = 0; lastBuySwingPrice = 0; lastSellSwingPrice = 0; } //+------------------------------------------------------------------+ //| Update Visualization | //+------------------------------------------------------------------+ void UpdateVisualization() { ObjectsDeleteAll(0, "MS_"); // Draw HTF swing points if(htf_lastHigh.price > 0) DrawSwingPoint(htf_lastHigh, "HTF_High", clrRed, HTF); if(htf_prevHigh.price > 0) DrawSwingPoint(htf_prevHigh, "HTF_PrevHigh", clrRed, HTF); if(htf_lastLow.price > 0) DrawSwingPoint(htf_lastLow, "HTF_Low", clrBlue, HTF); if(htf_prevLow.price > 0) DrawSwingPoint(htf_prevLow, "HTF_PrevLow", clrBlue, HTF); // Draw LTF swing points if(ltf_lastHigh.price > 0) DrawSwingPoint(ltf_lastHigh, "LTF_High", clrOrange, LTF); if(ltf_prevHigh.price > 0) DrawSwingPoint(ltf_prevHigh, "LTF_PrevHigh", clrOrange, LTF); if(ltf_lastLow.price > 0) DrawSwingPoint(ltf_lastLow, "LTF_Low", clrGreen, LTF); if(ltf_prevLow.price > 0) DrawSwingPoint(ltf_prevLow, "LTF_PrevLow", clrGreen, LTF); // Draw current price lines for reference double currentBid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double currentAsk = SymbolInfoDouble(_Symbol, SYMBOL_ASK); CreateHorizontalLine("Current_Bid", currentBid, clrYellow, STYLE_DASH); CreateHorizontalLine("Current_Ask", currentAsk, clrYellow, STYLE_DASH); // Draw trade condition zones if(ltf_lastHigh.price > 0) CreateHorizontalLine("Sell_Zone_Min", ltf_lastHigh.price, clrRed, STYLE_SOLID); if(ltf_lastLow.price > 0) { CreateHorizontalLine("Buy_Zone_Max", ltf_lastLow.price, clrBlue, STYLE_SOLID); CreateHorizontalLine("Sell_TP_Level", ltf_lastLow.price, clrGreen, STYLE_DOT); } // Draw structure labels DrawStructureLabels(); }
ここでは、LTF市場構造の整合性を監視し、構造ブレイクが発生した場合に自動的にリセットする処理を実装します。CheckStructureBreak()関数は、最新のLTFローソク足を取得し、その終値を現在の構造的な極値と比較します。市場が強気構造の場合、価格が最新のLTFスイングローを下回ってクローズすると、構造崩壊(ブレイクダウン)と判断され、ResetLTFStructure()が実行されます。逆に、市場が弱気構造の場合、価格が最新のLTFスイングハイを上回ってクローズすると、同様に構造がリセットされます。ResetLTFStructure()関数では、すべてのLTFスイングポイントをクリアし、構造状態をニュートラルへ設定します。また、トレード追跡用の変数もリセットします。これにより、その後のロジックでは新しく形成された市場構造のみが考慮されるようになります。
UpdateVisualization()関数は、HTFおよびLTFのスイングポイント、現在価格、主要なトレードゾーンをリアルタイムで明確に可視化します。最初に、「MS_」プレフィックスを持つ既存のチャートオブジェクトをすべて削除します。その後、HTFおよびLTFの関連するすべてのスイングポイントを、色分けされたマーカーで描画します。現在のBid価格とAsk価格は、参照用として黄色の破線で表示されます。また、売り・買いレベルやテイクプロフィットレベルなどのトレードゾーンは、実線または点線を使用して描画されます。
//+------------------------------------------------------------------+ //| Draw Swing Point | //+------------------------------------------------------------------+ void DrawSwingPoint(SwingPoint &sp, string name, color clr, ENUM_TIMEFRAMES tf) { ObjectCreate(0, "MS_" + name, OBJ_ARROW, 0, sp.time, sp.price); ObjectSetInteger(0, "MS_" + name, OBJPROP_ARROWCODE, sp.type == 1 ? 218 : 217); ObjectSetInteger(0, "MS_" + name, OBJPROP_COLOR, clr); ObjectSetInteger(0, "MS_" + name, OBJPROP_WIDTH, 3); ObjectSetString(0, "MS_" + name, OBJPROP_TOOLTIP, StringFormat("%s: %.5f", sp.type == 1 ? "High" : "Low", sp.price)); } //+------------------------------------------------------------------+ //| Draw Structure Labels | //+------------------------------------------------------------------+ void DrawStructureLabels() { string biasText = "HTF Bias: "; switch(htfBias) { case STRUCT_BULLISH: biasText += "BULLISH"; break; case STRUCT_BEARISH: biasText += "BEARISH"; break; default: biasText += "NEUTRAL"; break; } string ltfText = "LTF Structure: "; switch(ltfStructure) { case STRUCT_BULLISH: ltfText += "HH+HL"; break; case STRUCT_BEARISH: ltfText += "LL+LH"; break; default: ltfText += "NEUTRAL"; break; } // Current trade conditions string conditionText = ""; if(CheckBuyCondition()) conditionText = "BUY Condition: ACTIVE (Price < LTF Low)"; else if(CheckSellCondition()) conditionText = "SELL Condition: ACTIVE (Price > LTF High & Low)"; else conditionText = "No Trade Condition Met"; // Create label objects CreateLabel("MS_BiasLabel", biasText, 10, 20, clrDarkOrange); CreateLabel("MS_LTFLabel", ltfText, 10, 40, clrDarkOrange); CreateLabel("MS_ConditionLabel", conditionText, 10, 60, (CheckBuyCondition() || CheckSellCondition()) ? clrLime : clrGray); // Display LTF swing prices if(ltf_lastHigh.price > 0) CreateLabel("MS_LTFHighLabel", StringFormat("LTF High: %.5f", ltf_lastHigh.price), 10, 80, clrOrange); if(ltf_lastLow.price > 0) CreateLabel("MS_LTFLowLabel", StringFormat("LTF Low: %.5f", ltf_lastLow.price), 10, 100, clrGreen); } //+------------------------------------------------------------------+ //| Create Text Label | //+------------------------------------------------------------------+ void CreateLabel(string name, string text, int x, int y, color clr) { ObjectCreate(0, "MS_" + name, OBJ_LABEL, 0, 0, 0); ObjectSetString(0, "MS_" + name, OBJPROP_TEXT, text); ObjectSetInteger(0, "MS_" + name, OBJPROP_XDISTANCE, x); ObjectSetInteger(0, "MS_" + name, OBJPROP_YDISTANCE, y); ObjectSetInteger(0, "MS_" + name, OBJPROP_COLOR, clr); ObjectSetInteger(0, "MS_" + name, OBJPROP_FONTSIZE, 10); } //+------------------------------------------------------------------+ //| Create Horizontal Line | //+------------------------------------------------------------------+ void CreateHorizontalLine(string name, double price, color clr, ENUM_LINE_STYLE style) { ObjectCreate(0, "MS_" + name, OBJ_HLINE, 0, 0, price); ObjectSetInteger(0, "MS_" + name, OBJPROP_COLOR, clr); ObjectSetInteger(0, "MS_" + name, OBJPROP_STYLE, style); ObjectSetInteger(0, "MS_" + name, OBJPROP_WIDTH, 1); }
このコードセクションでは、チャート上に市場構造とトレード条件を包括的に可視化します。DrawSwingPoint()関数は、スイングハイおよびスイングローを矢印で表示します。視認性を高めるために色分けされ、さらに正確な価格とタイプ(高値または安値)を表示するツールチップ情報も付与されます。DrawStructureLabels()関数はこれを補完し、HTFバイアス、現在のLTF構造、アクティブなトレード条件をテキスト情報として表示します。これにより、トレーダーは市場状況を素早く把握できます。また、ラベルにはLTFスイングハイおよびLTFスイングローの価格情報も表示され、潜在的な反転ゾーンの可視化をサポートするとともに、即時の意思決定を支援します。
これらの視覚要素をサポートするため、CreateLabel()およびCreateHorizontalLine()ヘルパー関数を使用して、テキストラベルと水平基準ラインの作成処理を統一します。ラベルは指定されたスクリーン座標上に配置され、色やフォントサイズをカスタマイズできます。一方、水平ラインは現在のBid/Ask、スイングハイ・安値、トレードゾーンなどの重要な価格レベルを示します。これらの関数を組み合わせることで、構造データを明確で実用的なビジュアルフレームワークへ変換します。これにより、トレーダーは市場の極値と潜在的なトレード機会を、リアルタイムでチャート上から直接確認できるようになります。
バックテスト結果
バックテストはH1時間足において2か月間(2025年12月1日から2026年1月1日まで)のテストウィンドウで評価され、デフォルト設定が使用されました。 

結論
HTFおよびLTFの市場構造を完全にプログラム化されたシステムへ変換することでSwing Extremes and Pullbacksインジケータを自動化しました。この自動化システムは、HTFとLTFの両方でスイングハイおよびスイングローを検出し、価格アクションを継続的に監視して構造ブレイクを確認します。また、潜在的な反転を示す過度な価格拡張を識別します。トレード実行ルールも組み込まれており、たとえば価格が直近のLTF安値を下回ってクローズした場合に買いを実行し、価格が直近のLTF高値および安値の両方を上回った場合に売りを実行します。また、テイクプロフィットレベルは関連するスイングポイントから自動的に継承されます。さらに、可視化機能も自動化されており、スイングポイント、トレードゾーン、市場バイアスラベルをリアルタイムでチャート上に表示できます。これにより、手動分析を必要とせず、構造化された市場状況を即座に把握できます。
結論として、この自動化によって意思決定プロセスが効率化され、市場構造の極値に基づいて客観的にトレードを実行することで、感情的なバイアスや手動監視への依存を低減します。トレーダーは、このシステムによって複数の時間足を同時に監視し、潜在的なプルバックや反転を早期に検出し、事前計算されたリスクおよび利益目標に基づいてトレードを実行できます。自動実行機能と直感的なビジュアルフィードバックの両方を提供することで、このツールは効率性、一貫性、明確性を向上させ、より簡単で、より迅速かつ規律あるトレード環境を実現します。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/21330
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
Neuro-Structural Trading Engine (NSTE)(第I回):プロップファーム対応マルチ口座システムの構築方法
初心者からエキスパートへ:イントラデイ戦略の自動化
ラリー・ウィリアムズの『市場の秘密』(第12回):相場環境に基づくスマッシュデー反転トレード
MQL5でカスタムインジケータを作成する(第8回):出来高統合によるより深いマーケットプロファイル分析
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
この指標はどのように入手すればよいのでしょうか
あなたのEAが失敗するのは、実装しようとしているコンセプトのロジックに問題があるからです。