MQL5での取引戦略の自動化(第35回):ブレーカーブロック取引システムの作成
はじめに
前回の記事(第34回)では、MetaQuotes Language 5 (MQL5)を用いてトレンドラインブレイクアウトシステムを開発しました。このシステムでは、スイングポイントを使用してサポートおよびレジスタンスのトレンドラインを特定し、R²(決定係数)による適合度で検証した上で、ブレイクアウト取引を実行し、チャート上で動的に可視化しました。本記事(第35回)では、ブレーカーブロック取引システムを作成します。本システムは、レンジ相場を検出し、スイングポイントでブレーカーブロックを検証した上で、リテスト取引を実行できるカスタマイズ可能なリスクパラメータと視覚的フィードバックを備えています。本記事では以下のトピックを扱います。
この記事を読み終える頃には、ブレーカーブロックのリテスト取引をおこなうMQL5戦略が動作する状態で完成し、カスタマイズも可能になっています。それでは、さっそく始めましょう。
ブレーカーブロック戦略フレームワークを理解する
ブレーカーブロック戦略とは、価格が狭いレンジ内で推移するレンジ相場を特定し、その後のブレイクアウトとインパルスムーブ(勢いのある値動き)を捉える取引戦略です。このとき形成されるオーダーブロックが無効化されると、ブレーカーブロックとして機能し、リテスト取引の候補となります。戦略の基本コンセプトは、価格が大きく動いた後にこれらのブロックに戻ってくるタイミングを捉え、ブレイクアウト方向への取引を、定義されたストップロスとテイクプロフィットとともに実行することです。さらに、チャート上で視覚的に確認できる要素を組み合わせて分析精度を高めます。以下に、代表的なブレーカーブロックの種類を示します。
弱気ブレーカーブロック

強気ブレーカーブロック

戦略では、まず指定した本数のバーを分析してレンジ相場を検出し、価格がレンジを抜けたタイミングでブレイクアウトを特定します。その後、乗数(マルチプライヤー)ベースの閾値を用いてインパルスムーブを確認します。さらに、スイングポイントを用いたブレーカーブロックの検証ロジックを実装し、リテスト時にカスタマイズ可能なパラメータで取引を実行します。ブロックは動的なラベルや矢印で可視化し、ブレーカーブロックの発生と取引機会を特定するシステムを構築します。簡単に言うと、下図のように目標を視覚的に表現できます。

MQL5での実装
MQL5でプログラムを作成するには、まずMetaEditorを開き、ナビゲーターで[Experts]フォルダを探します。[新規]タブをクリックして指示に従い、ファイルを作成します。ファイルが作成されたら、コーディング環境で、まずプログラム全体で使用する入力パラメータとグローバル変数をいくつか宣言する必要があります。
//+------------------------------------------------------------------+ //| Breaker Block Strategy EA.mq5 | //| Copyright 2025, Allan Munene Mutiiria. | //| https://t.me/Forex_Algo_Trader | //+------------------------------------------------------------------+ #property copyright "Forex Algo-Trader, Allan" #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #property strict #include <Trade/Trade.mqh> //--- Include Trade library for position management CTrade obj_Trade; //--- Instantiate trade object for order operations
実装は、まず「#include <Trade/Trade.mqh>」を使用して取引ライブラリを読み込むことから始めます。このライブラリには、売買などの取引操作を管理するための組み込み関数が用意されています。次に、CTradeクラスを用いてobj_Tradeという取引オブジェクトを初期化します。これにより、エキスパートアドバイザー(EA)は売買注文をプログラムから自動的に実行できるようになります。この設定により、手動での操作を必要とせずに効率的な注文実行が可能となります。その後、ユーザーがユーザインタフェース(UI)から動作を変更・制御できるよう、外部入力パラメータを定義します。
//+------------------------------------------------------------------+ //| Input Parameters | //+------------------------------------------------------------------+ input double tradeLotSize = 0.01; // Trade size for each position in lots input bool enableTrading = true; // Toggle to enable or disable automated trading input bool enableTrailingStop = true; // Toggle to enable or disable trailing stop input double trailingStopPoints = 30; // Distance in points for trailing stop adjustment input double minProfitToTrail = 50; // Minimum profit in points before trailing starts input int uniqueMagicNumber = 12345; // Unique identifier for EA trades input int consolidationBars = 7; // Number of bars to check for consolidation range input double maxConsolidationSpread = 50; // Maximum allowed spread in points for consolidation input int barsToWaitAfterBreakout = 3; // Bars to wait after breakout before impulse check input double impulseMultiplier = 1.0; // Multiplier for detecting impulsive price moves input double stopLossDistance = 1500; // Stop loss distance in points from entry input double takeProfitDistance = 1500; // Take profit distance in points from entry input double moveAwayDistance = 50; // Distance in points for price to move away post-invalidation input color bullishColor = clrGreen; // Base color for bullish order/breaker blocks input color bearishColor = clrRed; // Base color for bearish order/breaker blocks input color labelTextColor = clrBlack; // Color for text labels on blocks input bool enableSwingValidation = true; // Enable validation of swing points for block invalidation input bool showSwingPoints = true; // Show swing point labels if validation enabled input color swingLabelColor = clrWhite; // Color for swing point labels input int swingFontSize = 10; // Font size for swing point labels
ここでは、プログラムの動作を設定するための入力パラメータを定義します。tradeLotSizeはポジションサイズを指定するもので、enableTradingとenableTrailingStopはそれぞれ売買の実行とトレーリングストップの有効・無効を制御します。さらに、trailingStopPointsとminProfitToTrailを使用することで、トレーリングストップの挙動をより細かく調整できます。uniqueMagicNumberは、EAが発注したポジションを識別するための一意の識別子です。レンジの検出にはconsolidationBarsとmaxConsolidationSpreadを用い、ブレイクアウトの判定にはbarsToWaitAfterBreakoutとimpulseMultiplierを使って価格の勢いとタイミングを評価します。それ以外の入力項目は直感的に理解しやすい内容です。理解を容易にするためにコメントを追加しました。最後に、システム全体の制御に必要なグローバル変数を定義していきます。
//+------------------------------------------------------------------+ //| Structure for price and index | //+------------------------------------------------------------------+ struct PriceAndIndex { //--- Define structure for price and index data double price; //--- Store price value (high or low) int index; //--- Store bar index of price }; //+------------------------------------------------------------------+ //| Global variables for market state tracking | //+------------------------------------------------------------------+ PriceAndIndex rangeHighestHigh = {0, 0}; //--- Track highest high in consolidation range PriceAndIndex rangeLowestLow = {0, 0}; //--- Track lowest low in consolidation range bool isBreakoutDetected = false; //--- Flag for breakout detection double lastImpulseLow = 0.0; //--- Store low price after breakout for impulse double lastImpulseHigh = 0.0; //--- Store high price after breakout for impulse int breakoutBarNumber = -1; //--- Store bar index of breakout datetime breakoutTimestamp = 0; //--- Store timestamp of breakout string blockNames[]; //--- Store names of block objects datetime blockEndTimes[]; //--- Store end times of blocks bool invalidatedStatus[]; //--- Track invalidation status of blocks string blockTypes[]; //--- Track block types (OB/BB, bullish/bearish) bool movedAwayStatus[]; //--- Track if price moved away after invalidation bool retestedStatus[]; //--- Track if block was retested string blockLabels[]; //--- Store label object names for blocks datetime creationTimes[]; //--- Store creation times of blocks datetime invalidationTimes[]; //--- Store invalidation times of blocks double invalidationSwings[]; //--- Store swing high/low at invalidation bool isBullishImpulse = false; //--- Flag for bullish impulsive move bool isBearishImpulse = false; //--- Flag for bearish impulsive move #define OB_Prefix "OB REC " //--- Define prefix for order block names
ここでは、システムのデータ構造と状態管理を確立します。まず、PriceAndIndexという構造体を定義し、価格値(高値または安値)と対応するバーのインデックスを保持できるようにします。これにより、レンジ相場の境界を正確に追跡できます。次に、グローバル変数を初期化します。rangeHighestHighとrangeLowestLowはPriceAndIndexのインスタンスとして、レンジ内の最高値と最安値を保持します。isBreakoutDetectedはブレイクアウトの発生を示すフラグとしてfalseで初期化し、lastImpulseLowとlastImpulseHighはブレイクアウト後のインパルスムーブの確認用に0.0で初期化します。ブレイクアウトのタイミングを追跡するため、breakoutBarNumberは-1、breakoutTimestampは0で設定します。さらに、配列としてblockNames、blockEndTimes、invalidatedStatus、blockTypes、movedAwayStatus、retestedStatus、blockLabels、creationTimes、invalidationTimes、invalidationSwingsを用意し、ブロックオブジェクトの管理、期限切れや無効化の状態、種類(オーダーブロックまたはブレーカーブロック、ブルリッシュまたはベアリッシュ)、リテスト状況、関連するスイングポイントを扱います。
最後に、オーダーブロックオブジェクトを一貫した名前で管理するために、OB_Prefixを「OB REC」として定義します。次に、無効化されたオーダーブロックの色を暗くするヘルパー関数や、イベントハンドラを扱うための関数を定義していきます。
//+------------------------------------------------------------------+ //| Darken color by factor | //+------------------------------------------------------------------+ color DarkenColor(color colorValue, double factor = 0.8) { int red = int((colorValue & 0xFF) * factor); //--- Extract and darken red component int green = int(((colorValue >> 8) & 0xFF) * factor); //--- Extract and darken green component int blue = int(((colorValue >> 16) & 0xFF) * factor); //--- Extract and darken blue component return (color)(red | (green << 8) | (blue << 16)); //--- Combine components into color } //+------------------------------------------------------------------+ //| Price data accessors | //+------------------------------------------------------------------+ double high(int index) { return iHigh(_Symbol, _Period, index); //--- Return high price for specified index } double low(int index) { return iLow(_Symbol, _Period, index); //--- Return low price for specified index } double close(int index) { return iClose(_Symbol, _Period, index); //--- Return close price for specified index } datetime time(int index) { return iTime(_Symbol, _Period, index); //--- Return time for specified index } //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { obj_Trade.SetExpertMagicNumber(uniqueMagicNumber); //--- Set magic number for trade identification return(INIT_SUCCEEDED); //--- Return initialization success } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { ObjectsDeleteAll(0, OB_Prefix); //--- Remove all objects with OB prefix ChartRedraw(0); //--- Redraw chart to clear objects }
ここからは、システムのユーティリティおよびライフサイクル管理用の関数を実装していきます。まず、DarkenColor関数を作成します。この関数はカラー値と任意の係数(デフォルトは0.8)を受け取り、ビット演算を使って赤、緑、青の成分を抽出します(colorValue & 0xFF、(colorValue >> 8) & 0xFF、(colorValue >> 16) & 0xFF)。抽出した各成分を係数で暗くし、再びビットシフトとOR演算で組み合わせる(red | (green << 8) | (blue << 16))ことで、無効化されたブロックを視覚的に区別するための暗色を返します。
次に、アクセサ関数としてhigh、low、close、timeを作成します。これらは、現在のシンボルと時間足に対して指定したバーインデックスの高値(iHigh)、安値(iLow)、終値(iClose)、およびタイムスタンプ(iTime)を返し、価格データの取得を簡素化します。その後、OnInit関数内では、obj_Tradeに対してSetExpertMagicNumberを呼び出し、uniqueMagicNumberで取引にタグを付けて識別できるようにし、初期化が成功した場合はINIT_SUCCEEDEDを返します。OnDeinit関数では、ObjectsDeleteAllを用いてOB_Prefixで作成されたすべてのチャートオブジェクトを削除し、ChartRedrawを呼び出してチャートを更新することで、リソースをクリーンに解放します。これで、次はメインのOnTickイベントハンドラに進み、システムの主要な制御ロジックを実装する準備が整いました。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { static bool isNewBar = false; //--- Track new bar status int currentBarCount = iBars(_Symbol, _Period); //--- Get current bar count static int previousBarCount = currentBarCount; //--- Store previous bar count if (previousBarCount == currentBarCount) { //--- Check if no new bar isNewBar = false; //--- Set no new bar } else { //--- New bar detected isNewBar = true; //--- Set new bar flag previousBarCount = currentBarCount; //--- Update previous bar count } if (!isNewBar) return; //--- Exit if not new bar int startBarIndex = 1; //--- Set start index for analysis int chartScale = (int)ChartGetInteger(0, CHART_SCALE); //--- Get chart zoom scale int dynamicFontSize = 8 + (chartScale * 2); //--- Calculate dynamic font size if (!isBreakoutDetected) { //--- Check if no breakout detected if (rangeHighestHigh.price == 0 && rangeLowestLow.price == 0) { //--- Check if range not set bool isConsolidated = true; //--- Assume consolidation for (int i = startBarIndex; i < startBarIndex + consolidationBars - 1; i++) { //--- Iterate consolidation bars if (MathAbs(high(i) - high(i + 1)) > maxConsolidationSpread * Point() || MathAbs(low(i) - low(i + 1)) > maxConsolidationSpread * Point()) { //--- Check spread isConsolidated = false; //--- Mark as not consolidated break; //--- Exit loop } } if (isConsolidated) { //--- Confirm consolidation rangeHighestHigh.price = high(startBarIndex); //--- Set initial high rangeHighestHigh.index = startBarIndex; //--- Set high index for (int i = startBarIndex + 1; i < startBarIndex + consolidationBars; i++) { //--- Find highest high if (high(i) > rangeHighestHigh.price) { //--- Check higher high rangeHighestHigh.price = high(i); //--- Update highest high rangeHighestHigh.index = i; //--- Update high index } } rangeLowestLow.price = low(startBarIndex); //--- Set initial low rangeLowestLow.index = startBarIndex; //--- Set low index for (int i = startBarIndex + 1; i < startBarIndex + consolidationBars; i++) { //--- Find lowest low if (low(i) < rangeLowestLow.price) { //--- Check lower low rangeLowestLow.price = low(i); //--- Update lowest low rangeLowestLow.index = i; //--- Update low index } } Print("Consolidation range established: Highest High = ", rangeHighestHigh.price, " at index ", rangeHighestHigh.index, " and Lowest Low = ", rangeLowestLow.price, " at index ", rangeLowestLow.index); //--- Log range } } else { //--- Range already set double currentHigh = high(1); //--- Get current high double currentLow = low(1); //--- Get current low if (currentHigh <= rangeHighestHigh.price && currentLow >= rangeLowestLow.price) { //--- Check within range Print("Range extended: High = ", currentHigh, ", Low = ", currentLow); //--- Log range extension } else { //--- Outside range Print("No extension: Bar outside range."); //--- Log no extension } } } }
まず、レンジ相場を検出するロジックを定義します。OnTick関数内では、iBarsで取得した現在のバー数と静的変数previousBarCountを比較することで新しいバーを追跡します。新しいバーが検出された場合はisNewBarをtrueに設定し、previousBarCountを更新します。新しいバーがなければisNewBarをfalseにして処理を終了します。その後、ChartGetIntegerを用いてチャートのズームスケール(CHART_SCALE)を取得し、ラベルのサイズを動的に調整するためにdynamicFontSizeを8にスケールの2倍を加えた値として計算します。ブレイクアウトが検出されていない場合(isBreakoutDetectedがfalse)かつレンジが設定されていない場合(rangeHighestHigh.priceおよびrangeLowestLow.priceが0)、レンジ相場をチェックします。具体的には、startBarIndexを1としてconsolidationBars本分のバーをループし、隣接するバーの高値・安値の差が「maxConsolidationSpread * Point()」以内であることをMathAbsで確認します。条件を超える場合はisConsolidatedをfalseに設定します。
レンジ相場が成立した場合、rangeHighestHigh.priceとrangeLowestLow.priceにstartBarIndexの高値と安値を設定し、その後consolidationBars本分のバーをループして、最も高い高値と最も低い安値およびそのインデックスを更新します。レンジはPrint関数でログに記録されます。既存のレンジがある場合は、現在のバーの高値・安値(high(1)、low(1))がrangeHighestHigh.priceとrangeLowestLow.priceの範囲内にあるかを確認し、範囲内であれば拡張として、範囲外であれば拡張なしとしてログに記録します。この時点で、これらの価格情報を用いてオーダーブロックの検出、可視化、管理をおこなう準備が整います。ブレーカーブロックとして利用する前に、まずオーダーブロックを無効化する必要があるためです。
if (rangeHighestHigh.price > 0 && rangeLowestLow.price > 0) { //--- Check if range defined double currentClosePrice = close(1); //--- Get current close price if (currentClosePrice > rangeHighestHigh.price) { //--- Check upward breakout Print("Upward breakout at ", currentClosePrice, " > ", rangeHighestHigh.price); //--- Log breakout isBreakoutDetected = true; //--- Set breakout flag } else if (currentClosePrice < rangeLowestLow.price) { //--- Check downward breakout Print("Downward breakout at ", currentClosePrice, " < ", rangeLowestLow.price); //--- Log breakout isBreakoutDetected = true; //--- Set breakout flag } } if (isBreakoutDetected) { //--- Process breakout Print("Breakout detected. Resetting for the next range."); //--- Log reset breakoutBarNumber = 1; //--- Set breakout bar index breakoutTimestamp = TimeCurrent(); //--- Set breakout timestamp lastImpulseHigh = rangeHighestHigh.price; //--- Store high for impulse check lastImpulseLow = rangeLowestLow.price; //--- Store low for impulse check isBreakoutDetected = false; //--- Reset breakout flag rangeHighestHigh.price = 0; //--- Clear highest high rangeHighestHigh.index = 0; //--- Clear high index rangeLowestLow.price = 0; //--- Clear lowest low rangeLowestLow.index = 0; //--- Clear low index } if (breakoutBarNumber >= 0 && TimeCurrent() > breakoutTimestamp + barsToWaitAfterBreakout * PeriodSeconds()) { //--- Check impulse window double impulseRange = lastImpulseHigh - lastImpulseLow; //--- Calculate impulse range double impulseThresholdPrice = impulseRange * impulseMultiplier; //--- Calculate impulse threshold isBullishImpulse = false; //--- Reset bullish impulse flag isBearishImpulse = false; //--- Reset bearish impulse flag for (int i = 1; i <= barsToWaitAfterBreakout; i++) { //--- Check bars for impulse double closePrice = close(i); //--- Get close price if (closePrice >= lastImpulseHigh + impulseThresholdPrice) { //--- Check bullish impulse isBullishImpulse = true; //--- Set bullish impulse flag Print("Impulsive upward move: ", closePrice, " >= ", lastImpulseHigh + impulseThresholdPrice); //--- Log bullish impulse break; //--- Exit loop } else if (closePrice <= lastImpulseLow - impulseThresholdPrice) { //--- Check bearish impulse isBearishImpulse = true; //--- Set bearish impulse flag Print("Impulsive downward move: ", closePrice, " <= ", lastImpulseLow - impulseThresholdPrice); //--- Log bearish impulse break; //--- Exit loop } } if (!isBullishImpulse && !isBearishImpulse) { //--- Check no impulse Print("No impulsive movement detected."); //--- Log no impulse } bool isOrderBlockValid = isBearishImpulse || isBullishImpulse; //--- Validate order block if (isOrderBlockValid) { //--- Process valid order block datetime blockStartTime = iTime(_Symbol, _Period, consolidationBars + barsToWaitAfterBreakout + 1); //--- Set block start time double blockTopPrice = lastImpulseHigh; //--- Set block top price int visibleBarsOnChart = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); //--- Get visible bars datetime blockEndTime = blockStartTime + (visibleBarsOnChart / 1) * PeriodSeconds(); //--- Set block end time double blockBottomPrice = lastImpulseLow; //--- Set block bottom price string blockName = OB_Prefix + "(" + TimeToString(blockStartTime) + ")"; //--- Generate block name color blockColor = isBullishImpulse ? bullishColor : bearishColor; //--- Set block color string blockLabel = isBullishImpulse ? "Bullish Order Block" : "Bearish Order Block"; //--- Set block label string blockType = isBullishImpulse ? "OB-bullish" : "OB-bearish"; //--- Set block type if (ObjectFind(0, blockName) < 0) { //--- Check if block exists ObjectCreate(0, blockName, OBJ_RECTANGLE, 0, blockStartTime, blockTopPrice, blockEndTime, blockBottomPrice); //--- Create block rectangle ObjectSetInteger(0, blockName, OBJPROP_TIME, 0, blockStartTime); //--- Set start time ObjectSetDouble(0, blockName, OBJPROP_PRICE, 0, blockTopPrice); //--- Set top price ObjectSetInteger(0, blockName, OBJPROP_TIME, 1, blockEndTime); //--- Set end time ObjectSetDouble(0, blockName, OBJPROP_PRICE, 1, blockBottomPrice); //--- Set bottom price ObjectSetInteger(0, blockName, OBJPROP_FILL, true); //--- Enable fill ObjectSetInteger(0, blockName, OBJPROP_COLOR, blockColor); //--- Set block color ObjectSetInteger(0, blockName, OBJPROP_BACK, false); //--- Set to foreground datetime labelTime = blockStartTime + (blockEndTime - blockStartTime) / 2; //--- Calculate label time double labelPrice = (blockTopPrice + blockBottomPrice) / 2; //--- Calculate label price string labelObjectName = blockName + " Label"; //--- Generate label name ObjectCreate(0, labelObjectName, OBJ_TEXT, 0, labelTime, labelPrice); //--- Create label ObjectSetString(0, labelObjectName, OBJPROP_TEXT, blockLabel); //--- Set label text ObjectSetInteger(0, labelObjectName, OBJPROP_COLOR, labelTextColor); //--- Set label color ObjectSetInteger(0, labelObjectName, OBJPROP_FONTSIZE, dynamicFontSize); //--- Set label font size ObjectSetInteger(0, labelObjectName, OBJPROP_ANCHOR, ANCHOR_CENTER); //--- Set label anchor ChartRedraw(0); //--- Redraw chart ArrayResize(blockNames, ArraySize(blockNames) + 1); //--- Resize block names array blockNames[ArraySize(blockNames) - 1] = blockName; //--- Add block name ArrayResize(blockEndTimes, ArraySize(blockEndTimes) + 1); //--- Resize end times array blockEndTimes[ArraySize(blockEndTimes) - 1] = blockEndTime; //--- Add end time ArrayResize(invalidatedStatus, ArraySize(invalidatedStatus) + 1); //--- Resize invalidated status invalidatedStatus[ArraySize(invalidatedStatus) - 1] = false; //--- Set invalidated status ArrayResize(blockTypes, ArraySize(blockTypes) + 1); //--- Resize block types array blockTypes[ArraySize(blockTypes) - 1] = blockType; //--- Add block type ArrayResize(movedAwayStatus, ArraySize(movedAwayStatus) + 1); //--- Resize moved away status movedAwayStatus[ArraySize(movedAwayStatus) - 1] = false; //--- Set moved away status ArrayResize(retestedStatus, ArraySize(retestedStatus) + 1); //--- Resize retested status retestedStatus[ArraySize(retestedStatus) - 1] = false; //--- Set retested status ArrayResize(blockLabels, ArraySize(blockLabels) + 1); //--- Resize block labels array blockLabels[ArraySize(blockLabels) - 1] = labelObjectName; //--- Add label name ArrayResize(creationTimes, ArraySize(creationTimes) + 1); //--- Resize creation times creationTimes[ArraySize(creationTimes) - 1] = time(1); //--- Set creation time ArrayResize(invalidationTimes, ArraySize(invalidationTimes) + 1); //--- Resize invalidation times invalidationTimes[ArraySize(invalidationTimes) - 1] = 0; //--- Set invalidation time ArrayResize(invalidationSwings, ArraySize(invalidationSwings) + 1); //--- Resize invalidation swings invalidationSwings[ArraySize(invalidationSwings) - 1] = 0.0; //--- Set invalidation swing Print("Order Block created: ", blockName); //--- Log block creation } } breakoutBarNumber = -1; //--- Reset breakout bar breakoutTimestamp = 0; //--- Reset breakout timestamp lastImpulseHigh = 0; //--- Reset impulse high lastImpulseLow = 0; //--- Reset impulse low isBullishImpulse = false; //--- Reset bullish impulse isBearishImpulse = false; //--- Reset bearish impulse }
ここでは、ブレイクアウトの検出とオーダーブロック作成のロジックを実装します。まず、レンジ相場が定義されているかを確認します(rangeHighestHigh.priceおよびrangeLowestLow.priceが0より大きい場合)。次に、現在のバーの終値(close(1))を取得し、これがrangeHighestHigh.priceを上回っていれば上方向のブレイクアウトとしてログに記録し、isBreakoutDetectedをtrueに設定します。逆に、rangeLowestLow.priceを下回っていれば下方向のブレイクアウトとしてログに記録し、同様にフラグを立てます。isBreakoutDetectedがtrueの場合、ブレイクアウトをログに記録し、breakoutBarNumberを1、breakoutTimestampをTimeCurrentに設定します。さらに、lastImpulseHighとlastImpulseLowを保存し、レンジ変数とブレイクアウトフラグをリセットします。
その後、breakoutBarNumberが0以上でかつ現在の時間が「breakoutTimestamp + barsToWaitAfterBreakout * PeriodSeconds」を超えている場合、インパルスレンジ(lastImpulseHigh - lastImpulseLow)および閾値(impulseRange * impulseMultiplier)を計算します。そして、barsToWaitAfterBreakout本のバーの中で、終値が「lastImpulseHigh + impulseThresholdPrice」を超えていればisBullishImpulse、「lastImpulseLow - impulseThresholdPrice」を下回っていればisBearishImpulseとして判定します。
インパルスが検出されなければログに記録し、ブルリッシュまたはベアリッシュのインパルスが有効であれば、ObjectCreate (OBJ_RECTANGLE)を用いてオーダーブロックを作成します。開始時間にはiTime、上下の価格にはlastImpulseHighとlastImpulseLowを設定し、終了時間はChartGetInteger(CHART_VISIBLE_BARS)で算出します。プロパティとしてOBJPROP_FILLやOBJPROP_COLOR(bullishColorまたはbearishColor)を設定し、中央にラベルをOBJ_TEXTで配置します。さらに、配列blockNames、blockEndTimes、invalidatedStatus、blockTypes、movedAwayStatus、retestedStatus、blockLabels、creationTimes、invalidationSwingsを更新します。最後に、ブレイクアウト関連の変数をリセットします。これにより、ブレイクアウトを検出しオーダーブロックを可視化するシステムが構築されます。コンパイルすると、次の結果が得られます。

これで初期オーダーブロックを検出できるようになったため、次に、該当する価格ブレイクアウトが発生した際にそれらを無効化としてマークし、プライスアクションのスイングポイントで無効化を確認するロジックを定義します。無効化されたブロックは、視覚的に区別するために色を暗くします。
for (int j = ArraySize(blockNames) - 1; j >= 0; j--) { //--- Iterate through blocks string currentBlockName = blockNames[j]; //--- Get current block name bool doesBlockExist = false; //--- Initialize block existence flag double blockHigh = ObjectGetDouble(0, currentBlockName, OBJPROP_PRICE, 0); //--- Get block high double blockLow = ObjectGetDouble(0, currentBlockName, OBJPROP_PRICE, 1); //--- Get block low datetime blockStartTime = (datetime)ObjectGetInteger(0, currentBlockName, OBJPROP_TIME, 0); //--- Get block start datetime blockEndTime = (datetime)ObjectGetInteger(0, currentBlockName, OBJPROP_TIME, 1); //--- Get block end color blockCurrentColor = (color)ObjectGetInteger(0, currentBlockName, OBJPROP_COLOR); //--- Get block color if (time(1) < blockEndTime) { //--- Check if block still valid doesBlockExist = true; //--- Set block exists } if (StringFind(blockTypes[j], "OB-") == 0 && !invalidatedStatus[j]) { //--- Check valid order block bool invalidated = false; //--- Initialize invalidation flag string newBlockType = ""; //--- Initialize new block type color invalidatedColor = clrNONE; //--- Initialize invalidated color string newLabel = ""; //--- Initialize new label bool isForBullishBB = false; //--- Initialize bullish breaker block flag double breakPrice = 0.0; //--- Initialize break price int arrowCode = 0; //--- Initialize arrow code int anchor = 0; //--- Initialize anchor if (blockTypes[j] == "OB-bearish" && close(1) > blockHigh) { //--- Check bearish block invalidation isForBullishBB = true; //--- Set bullish breaker block breakPrice = blockHigh; //--- Set break price arrowCode = 233; //--- Set upward arrow anchor = ANCHOR_BOTTOM; //--- Set bottom anchor newBlockType = "Invalidated-bearish"; //--- Set invalidated type invalidatedColor = DarkenColor(bearishColor); //--- Darken bearish color newLabel = "Invalidated Bearish Order Block"; //--- Set invalidated label } else if (blockTypes[j] == "OB-bullish" && close(1) < blockLow) { //--- Check bullish block invalidation isForBullishBB = false; //--- Set bearish breaker block breakPrice = blockLow; //--- Set break price arrowCode = 234; //--- Set downward arrow anchor = ANCHOR_TOP; //--- Set top anchor newBlockType = "Invalidated-bullish"; //--- Set invalidated type invalidatedColor = DarkenColor(bullishColor); //--- Darken bullish color newLabel = "Invalidated Bullish Order Block"; //--- Set invalidated label } else { //--- No invalidation continue; //--- Skip to next block } bool validSwingForInvalidation = true; //--- Assume valid swing int swingShift = -1; //--- Initialize swing shift double swingPrice = 0.0; //--- Initialize swing price if (enableSwingValidation) { //--- Check swing validation int creationShift = iBarShift(_Symbol, _Period, creationTimes[j], false); //--- Get creation bar shift if (creationShift > 1) { //--- Ensure enough bars double extreme = isForBullishBB ? blockLow : blockHigh; //--- Set extreme price bool isBearishOB = isForBullishBB; //--- Set bearish OB flag if (isBearishOB) { //--- Handle bearish OB double minLow = extreme; //--- Initialize minimum low for (int k = creationShift - 1; k > 1; k--) { //--- Find lower low if (low(k) < minLow) { //--- Check lower low minLow = low(k); //--- Update minimum low swingShift = k; //--- Update swing shift } } validSwingForInvalidation = minLow < extreme; //--- Validate swing swingPrice = minLow; //--- Set swing price } else { //--- Handle bullish OB double maxHigh = extreme; //--- Initialize maximum high for (int k = creationShift - 1; k > 1; k--) { //--- Find higher high if (high(k) > maxHigh) { //--- Check higher high maxHigh = high(k); //--- Update maximum high swingShift = k; //--- Update swing shift } } validSwingForInvalidation = maxHigh > extreme; //--- Validate swing swingPrice = maxHigh; //--- Set swing price } } else { //--- Insufficient bars validSwingForInvalidation = false; //--- Invalidate swing } } if (validSwingForInvalidation) { //--- Confirm swing validation invalidated = true; //--- Set invalidated flag } if (invalidated) { //--- Process invalidation ObjectSetInteger(0, currentBlockName, OBJPROP_COLOR, invalidatedColor); //--- Update block color ObjectDelete(0, blockLabels[j]); //--- Delete old label datetime labelTime = blockStartTime + (blockEndTime - blockStartTime) / 2; //--- Calculate new label time double labelPrice = (blockHigh + blockLow) / 2; //--- Calculate new label price string newLabelObjectName = currentBlockName + " Label"; //--- Generate new label name ObjectCreate(0, newLabelObjectName, OBJ_TEXT, 0, labelTime, labelPrice); //--- Create new label ObjectSetString(0, newLabelObjectName, OBJPROP_TEXT, newLabel); //--- Set label text ObjectSetInteger(0, newLabelObjectName, OBJPROP_COLOR, labelTextColor); //--- Set label color ObjectSetInteger(0, newLabelObjectName, OBJPROP_FONTSIZE, dynamicFontSize); //--- Set label font size ObjectSetInteger(0, newLabelObjectName, OBJPROP_ANCHOR, ANCHOR_CENTER); //--- Set label anchor string arrowName = currentBlockName + "_break_arrow"; //--- Generate arrow name if (ObjectFind(0, arrowName) < 0) { //--- Check if arrow exists ObjectCreate(0, arrowName, OBJ_ARROW, 0, time(1), breakPrice); //--- Create break arrow ObjectSetInteger(0, arrowName, OBJPROP_ARROWCODE, arrowCode); //--- Set arrow code ObjectSetInteger(0, arrowName, OBJPROP_ANCHOR, anchor); //--- Set arrow anchor ObjectSetInteger(0, arrowName, OBJPROP_COLOR, invalidatedColor); //--- Set arrow color } if (enableSwingValidation && showSwingPoints && swingShift > 0) { //--- Check swing point display string swingLabelName = currentBlockName + "_invalid_swing"; //--- Generate swing label name if (ObjectFind(0, swingLabelName) < 0) { //--- Check if swing label exists datetime swingTime = time(swingShift); //--- Get swing time ObjectCreate(0, swingLabelName, OBJ_TEXT, 0, swingTime, swingPrice); //--- Create swing label string swingText = isForBullishBB ? "LL" : "HH"; //--- Set swing text ObjectSetString(0, swingLabelName, OBJPROP_TEXT, swingText); //--- Set swing label text ObjectSetInteger(0, swingLabelName, OBJPROP_COLOR, swingLabelColor); //--- Set swing label color ObjectSetInteger(0, swingLabelName, OBJPROP_FONTSIZE, swingFontSize); //--- Set swing label font size ObjectSetInteger(0, swingLabelName, OBJPROP_ANCHOR, isForBullishBB ? ANCHOR_LEFT_UPPER : ANCHOR_LEFT_LOWER); //--- Set swing label anchor } } ChartRedraw(0); //--- Redraw chart invalidatedStatus[j] = true; //--- Set invalidated status blockTypes[j] = newBlockType; //--- Update block type movedAwayStatus[j] = false; //--- Reset moved away status retestedStatus[j] = false; //--- Reset retested status blockLabels[j] = newLabelObjectName; //--- Update label name invalidationTimes[j] = time(1); //--- Set invalidation time invalidationSwings[j] = isForBullishBB ? high(1) : low(1); //--- Set invalidation swing Print("Order Block invalidated: ", currentBlockName); //--- Log invalidation } } if (!doesBlockExist) { //--- Check if block expired ArrayRemove(blockNames, j, 1); //--- Remove block name ArrayRemove(blockEndTimes, j, 1); //--- Remove end time ArrayRemove(invalidatedStatus, j, 1); //--- Remove invalidated status ArrayRemove(blockTypes, j, 1); //--- Remove block type ArrayRemove(movedAwayStatus, j, 1); //--- Remove moved away status ArrayRemove(retestedStatus, j, 1); //--- Remove retested status ArrayRemove(blockLabels, j, 1); //--- Remove label name ArrayRemove(creationTimes, j, 1); //--- Remove creation time ArrayRemove(invalidationTimes, j, 1); //--- Remove invalidation time ArrayRemove(invalidationSwings, j, 1); //--- Remove invalidation swing Print("Removed expired block at index ", j); //--- Log block removal } }
オーダーブロックの管理および無効化のロジックを実装するために、まずblockNames配列を逆順にループし、各ブロックを処理します。ブロックの高値と安値はObjectGetDoubleで、開始時間と終了時間はObjectGetIntegerで取得し、現在のバーの時間(time(1))が終了時間より前であれば、そのブロックは存在すると判断します。有効なオーダーブロック(タイプがOB-で始まり、かつ無効化されていないもの)については、無効化条件を確認します。具体的には、OB-bearishの場合、終値(close(1))がブロックの高値を上回ると、上方向のブレーカーブロックを作成します。この際、ObjectCreate (OBJ_ARROW)で上向き矢印(コード233)を描画し、DarkenColorで色を暗くして区別します。同様に、OB-bullishの場合は終値がブロックの安値を下回ると、下方向のブレーカーブロックを作成し、下向き矢印(コード234)で表示します。MQL5ではWingdingsフォントの膨大なコードリストが用意されているため、必要に応じて任意の記号を選択して表示に利用することができます。

enableSwingValidationがtrueの場合、ブロックをスイングポイントで検証します。弱気ブロックでは安値更新を、強気ブロックでは高値更新をiBarShiftおよびlowやhighを用いて確認し、有効であればObjectSetIntegerやObjectCreate (OBJ_TEXT)でブロックの色やラベルを更新します。さらに、showSwingPointsが有効な場合は、スイングポイントにラベル(「LL」または「HH」)をObjectCreateで追加し、該当する時間と価格に表示します。ブロックが無効化された場合は、invalidatedStatus、blockTypes、invalidationTimesなどのブロック状態を更新し、無効化をログに記録するとともに、リテスト状態をリセットします。ブロックが存在しない場合は、ArrayRemoveを使用してinvalidatedStatusなどの状態を格納している配列から削除し、削除をログに記録します。その後、ChartRedrawを呼び出してチャートを再描画します。コンパイル後は、この処理により以下のような結果が得られるはずです。

これでオーダーブロック無効化の第2ステップが完了したため、次のステップに進みます。ここでは、無効化されたオーダーブロックを追跡し、価格がそれらをリテストするのを待ち、リテストが確認されたタイミングでブレーカーブロックとしてマークします。
for (int j = ArraySize(blockNames) - 1; j >= 0; j--) { //--- Iterate invalidated blocks if (StringFind(blockTypes[j], "Invalidated-") != 0) continue; //--- Skip non-invalidated string currentBlockName = blockNames[j]; //--- Get current block name double blockHigh = ObjectGetDouble(0, currentBlockName, OBJPROP_PRICE, 0); //--- Get block high double blockLow = ObjectGetDouble(0, currentBlockName, OBJPROP_PRICE, 1); //--- Get block low datetime blockStartTime = (datetime)ObjectGetInteger(0, currentBlockName, OBJPROP_TIME, 0); //--- Get block start datetime blockEndTime = (datetime)ObjectGetInteger(0, currentBlockName, OBJPROP_TIME, 1); //--- Get block end bool isForBullishBB = (blockTypes[j] == "Invalidated-bearish"); //--- Check for bullish breaker block datetime currentBarTime = time(1); //--- Get current bar time if (currentBarTime <= invalidationTimes[j]) continue; //--- Skip if same or earlier bar if (!movedAwayStatus[j]) { //--- Check if not moved away if (isForBullishBB && close(1) > blockHigh + moveAwayDistance * _Point) { //--- Check bullish move away movedAwayStatus[j] = true; //--- Set moved away Print("Moved away for bullish BB setup: ", currentBlockName); //--- Log move away } else if (!isForBullishBB && close(1) < blockLow - moveAwayDistance * _Point) { //--- Check bearish move away movedAwayStatus[j] = true; //--- Set moved away Print("Moved away for bearish BB setup: ", currentBlockName); //--- Log move away } } if (movedAwayStatus[j] && !retestedStatus[j]) { //--- Check for retest bool retestCondition = false; //--- Initialize retest condition if (isForBullishBB && low(1) <= blockHigh && close(1) > blockHigh) { //--- Check bullish retest retestCondition = true; //--- Set retest condition } else if (!isForBullishBB && high(1) >= blockLow && close(1) < blockLow) { //--- Check bearish retest retestCondition = true; //--- Set retest condition } bool validSwingForRetest = true; //--- Assume valid swing int swingShift = -1; //--- Initialize swing shift double swingPrice = 0.0; //--- Initialize swing price if (enableSwingValidation && retestCondition) { //--- Check swing validation int invalidShift = iBarShift(_Symbol, _Period, invalidationTimes[j], false); //--- Get invalidation shift if (invalidShift > 1) { //--- Ensure enough bars double extreme = invalidationSwings[j]; //--- Get invalidation swing if (isForBullishBB) { //--- Handle bullish breaker block double maxHigh = extreme; //--- Initialize maximum high for (int k = invalidShift - 1; k > 1; k--) { //--- Find higher high if (high(k) > maxHigh) { //--- Check higher high maxHigh = high(k); //--- Update maximum high swingShift = k; //--- Update swing shift } } validSwingForRetest = maxHigh > extreme; //--- Validate swing swingPrice = maxHigh; //--- Set swing price } else { //--- Handle bearish breaker block double minLow = extreme; //--- Initialize minimum low for (int k = invalidShift - 1; k > 1; k--) { //--- Find lower low if (low(k) < minLow) { //--- Check lower low minLow = low(k); //--- Update minimum low swingShift = k; //--- Update swing shift } } validSwingForRetest = minLow < extreme; //--- Validate swing swingPrice = minLow; //--- Set swing price } } else { //--- Insufficient bars validSwingForRetest = false; //--- Invalidate swing } } } }
無効化されたブレーカーブロックのリテスト検出ロジックを実装するために、まずblockNames配列を逆順にループし、blockTypesに「Invalidated-」が含まれるブロックを処理します。各ブロックについて、高値と安値はObjectGetDoubleで、開始と終了の時刻はObjectGetIntegerで取得します。ブロックが強気ブレーカーブロック(Invalidated-bearish)かどうかを判定します。現在のバーの時間(time(1))がinvalidationTimesより前であれば、そのブロックはスキップされます。まだ「移動済み」となっていないブロック(movedAwayStatusがfalse)については、終値(close(1))が強気の場合は「blockHigh + moveAwayDistance *_Point」を超え、弱気の場合は「blockLow - moveAwayDistance * _Point」を下回るかをチェックし、条件を満たせばmovedAwayStatusをtrueに設定します。
移動済みだがリテストされていないブロック(retestedStatusがfalse)については、low(1)がblockHighに到達し、かつclose(1)がその上で終値を付けた場合を強気のリテストと判定します。一方、high(1)がblockLowに到達し、かつclose(1)がその下で終値を付けた場合を弱気のリテストと判定します。さらに、enableSwingValidationが有効で、リテスト条件を満たす場合には、iBarShiftを使用して無効化バーを取得し、強気の場合は高値更新、弱気の場合はその後の安値更新を確認することでスイングを検証します。これにより、リテストの有効性(validSwingForRetest)とスイング価格(swingPrice)を設定し、価格変動後に有効なリテストの機会を特定するシステムを構築します。この仕組みにより、リテストの追跡、ブロックのブレーカーブロックへのマーキング、色の変更による視覚的区別、さらにポジションのオープンまでおこなうことが可能になります。そのためのロジックを以下に実装します。
if (retestCondition && validSwingForRetest) { //--- Confirm retest and swing if (enableTrading) { //--- Check trading enabled double entryPrice = 0.0; //--- Initialize entry price double stopLossPrice = 0.0; //--- Initialize stop loss double takeProfitPrice = 0.0; //--- Initialize take profit if (isForBullishBB) { //--- Handle bullish trade entryPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits); //--- Set entry at ask stopLossPrice = entryPrice - stopLossDistance * _Point; //--- Set stop loss takeProfitPrice = entryPrice + takeProfitDistance * _Point; //--- Set take profit obj_Trade.Buy(tradeLotSize, _Symbol, entryPrice, stopLossPrice, takeProfitPrice); //--- Execute buy trade Print("Buy trade on bullish BB retest: ", currentBlockName); //--- Log buy trade } else { //--- Handle bearish trade entryPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits); //--- Set entry at bid stopLossPrice = entryPrice + stopLossDistance * _Point; //--- Set stop loss takeProfitPrice = entryPrice - takeProfitDistance * _Point; //--- Set take profit obj_Trade.Sell(tradeLotSize, _Symbol, entryPrice, stopLossPrice, takeProfitPrice); //--- Execute sell trade Print("Sell trade on bearish BB retest: ", currentBlockName); //--- Log sell trade } } color bbColor = isForBullishBB ? clrBlueViolet : clrOrange; //--- Set breaker block color ObjectSetInteger(0, currentBlockName, OBJPROP_COLOR, bbColor); //--- Update block color ObjectDelete(0, blockLabels[j]); //--- Delete old label string newLabel = isForBullishBB ? "Bullish Breaker Block" : "Bearish Breaker Block"; //--- Set new label datetime labelTime = blockStartTime + (blockEndTime - blockStartTime) / 2; //--- Calculate label time double labelPrice = (blockHigh + blockLow) / 2; //--- Calculate label price string newLabelObjectName = currentBlockName + " Label"; //--- Generate new label name ObjectCreate(0, newLabelObjectName, OBJ_TEXT, 0, labelTime, labelPrice); //--- Create new label ObjectSetString(0, newLabelObjectName, OBJPROP_TEXT, newLabel); //--- Set label text ObjectSetInteger(0, newLabelObjectName, OBJPROP_COLOR, labelTextColor); //--- Set label color ObjectSetInteger(0, newLabelObjectName, OBJPROP_FONTSIZE, dynamicFontSize); //--- Set label font size ObjectSetInteger(0, newLabelObjectName, OBJPROP_ANCHOR, ANCHOR_CENTER); //--- Set label anchor if (enableSwingValidation && showSwingPoints && swingShift > 0) { //--- Check swing point display string swingLabelName = currentBlockName + "_retest_swing"; //--- Generate swing label name if (ObjectFind(0, swingLabelName) < 0) { //--- Check if swing label exists datetime swingTime = time(swingShift); //--- Get swing time ObjectCreate(0, swingLabelName, OBJ_TEXT, 0, swingTime, swingPrice); //--- Create swing label string swingText = isForBullishBB ? "HH" : "LL"; //--- Set swing text ObjectSetString(0, swingLabelName, OBJPROP_TEXT, swingText); //--- Set swing label text ObjectSetInteger(0, swingLabelName, OBJPROP_COLOR, swingLabelColor); //--- Set swing label color ObjectSetInteger(0, swingLabelName, OBJPROP_FONTSIZE, swingFontSize); //--- Set swing label font size ObjectSetInteger(0, swingLabelName, OBJPROP_ANCHOR, isForBullishBB ? ANCHOR_LEFT_LOWER : ANCHOR_LEFT_UPPER); //--- Set swing label anchor } } ChartRedraw(0); //--- Redraw chart blockTypes[j] = isForBullishBB ? "BB-bullish" : "BB-bearish"; //--- Update block type retestedStatus[j] = true; //--- Set retested status blockLabels[j] = newLabelObjectName; //--- Update label name Print("Converted to ", newLabel, ": ", currentBlockName); //--- Log conversion }
最後に、リテストされたブレーカーブロックに対する取引と可視化のロジックを実装します。リテストが確認された場合(retestConditionとvalidSwingForRetestがtrue)かつ取引が有効な場合(enableTrading)、取引を実行します。強気のブレーカーブロック(isForBullishBB)の場合、SymbolInfoDoubleを使ってエントリーをAsk価格に設定し、ストップロスを「エントリー価格から「stopLossDistance *_Point」下、テイクプロフィットをエントリー価格から「takeProfitDistance * _Point」上に計算し、obj_Trade.Buyで買いを実行します。弱気の場合はBid価格を使用し、ストップロスを上に、テイクプロフィットを下に設定してobj_Trade.Sellで売りを実行し、それに応じてログを記録します。
その後、ブロックの見た目を更新します。強気の場合は色をclrBlueViolet、弱気の場合はclrOrangeにObjectSetIntegerで設定し、古いラベルをObjectDeleteで削除、新しいOBJ_TEXTラベル(「Bullish Breaker Block」または「Bearish Breaker Block」)をブロックの中央にObjectCreateで作成し、labelTextColorとdynamicFontSizeを適用します。さらに、enableSwingValidationとshowSwingPointsがtrueで、かつ有効なswingShiftがある場合、スイングラベル(強気は「HH」、弱気は「LL」)をスイングの時間と価格にObjectCreateで追加し、swingLabelColorとswingFontSizeを適用します。最後に、blockTypesを「BB-bullish」または「BB-bearish」に更新し、retestedStatusをtrueに設定、blockLabelsを更新、変換をログに記録し、チャートを再描画します。コンパイル後の結果は以下の通りです。

画像から、ブレーカーブロックを検出して可視化し、取引できることが確認できます。次におこなうべきは、利益を最大化するためのトレーリングストップロジックを追加することです。これには関数を定義してコードをモジュール化します。
//+------------------------------------------------------------------+ //| Apply trailing stop to open positions | //+------------------------------------------------------------------+ void applyTrailingStop(double trailingPoints, CTrade &trade_object, int magicNo = 0) { double buyStopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - trailingPoints * _Point, _Digits); //--- Calculate buy stop loss double sellStopLoss = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + trailingPoints * _Point, _Digits); //--- Calculate sell stop loss for (int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through open positions ulong ticket = PositionGetTicket(i); //--- Get position ticket if (ticket > 0 && PositionGetString(POSITION_SYMBOL) == _Symbol && (magicNo == 0 || PositionGetInteger(POSITION_MAGIC) == magicNo)) { //--- Verify position if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && buyStopLoss > PositionGetDouble(POSITION_PRICE_OPEN) && (buyStopLoss > PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0)) { //--- Check buy trailing trade_object.PositionModify(ticket, buyStopLoss, PositionGetDouble(POSITION_TP)); //--- Update buy stop loss } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && sellStopLoss < PositionGetDouble(POSITION_PRICE_OPEN) && (sellStopLoss < PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0)) { //--- Check sell trailing trade_object.PositionModify(ticket, sellStopLoss, PositionGetDouble(POSITION_TP)); //--- Update sell stop loss } } } } //--- Call the function on every tick //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if (enableTrailingStop) { //--- Check if trailing stop enabled applyTrailingStop(trailingStopPoints, obj_Trade, uniqueMagicNumber); //--- Apply trailing stop } //--- }
ここでは、トレーリングストップ機能を実装し、それをイベント駆動ロジックに統合します。まず、applyTrailingStop関数を開発します。この関数では、買いポジション用のストップロスを現在のBid価格(SymbolInfoDoubleのSYMBOL_BID)から「trailingPoints *_Point」を差し引いた値として計算し、売りポジション用のストップロスを現在のAsk価格(SYMBOL_ASK)に「trailingPoints * _Point」を加えた値として計算します。いずれも、銘柄の桁数に合わせてNormalizeDoubleにより正規化します。次に、PositionsTotalを使用して保有中のポジションを後ろから順に反復処理し、PositionGetTicketによって各ポジションのチケット番号を取得します。その後、PositionGetStringおよびPositionGetString 関数を用いて、対象の銘柄と「magicNo」(0でない場合)に一致しているかを確認します。
買いポジション(POSITION_TYPE_BUY)の場合は、「buyStopLoss」が建値(PositionGetDouble(POSITION_PRICE_OPEN))よりも上にあり、かつ現在のストップロスより高い、またはストップロスが未設定であることを確認したうえで、trade_object.PositionModifyを使用して更新します。売りポジションの場合は、sellStopLossが建値よりも下にあり、かつ現在のストップロスより低い、または未設定であることを確認し、同様に更新します。最後に、OnTick関数内でenableTrailingStopがtrueであるかを確認し、毎ティックごとにtrailingStopPoints、obj_Trade、uniqueMagicNumberを引数としてapplyTrailingStopを呼び出し、保有中のポジションを管理します。コンパイルすると、次の結果が得られます。

画像から分かるように、価格が有利な方向へ動いた場合、トレーリングストップは完全に有効化されます。以下は、弱気および強気のブレーカーブロックの両方に対する統合テストです。

可視化からわかるように、プログラムはすべてのエントリー条件を正確に特定・検証し、条件が満たされた場合にはそれぞれのパラメーターでポジションをオープンしています。これで、狙い通りの動作が実現できていることがわかります。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。
バックテスト
徹底的なバックテストによって、次の結果が得られました。
バックテストグラフ

バックテストレポート

結論
本記事では、MQL5を用いて、レンジ(保ち合い)を識別し、スイングポイントによってブレーカーブロックを検証し、カスタマイズ可能なリスクパラメータおよびトレーリングストップを用いてリテスト取引を実行するブレーカーブロック取引システムを構築しました。本システムは、動的なラベルや矢印を用いてオーダーブロックおよびブレーカーブロックを可視化し、取引判断の明確性を高めています。
免責条項:本記事は教育目的のみを意図したものです。取引には重大な財務リスクが伴い、市場の変動によって損失が生じる可能性があります。本プログラムを実際の市場で運用する前に、十分なバックテストと慎重なリスク管理が不可欠です。
このブレーカーブロック戦略により、価格のリテスト局面を捉えるための準備が整い、取引の旅路においてさらなる改良を加えていくことが可能となります。取引をお楽しみください。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/19638
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
初心者からエキスパートへ:MQL5を使用したバックエンド操作モニター
プライスアクション分析ツールキットの開発(第42回):ボタンロジックと統計レベルを用いたインタラクティブチャートの検証
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
MQL5での取引戦略の自動化(第34回):R²適合度を用いたトレンドラインブレイクアウトシステム
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
共有ファイルを追加してみましたが、描画されないと 言うか、チャート上で何も起こらないか、取引が行われません。
よろしくお願いします。