English Русский 中文 Español Deutsch Português
preview
高度なICT取引システムの開発:インジケーターへのオーダーブロックの実装

高度なICT取引システムの開発:インジケーターへのオーダーブロックの実装

MetaTrader 5 |
208 18
Niquel Mendoza
Niquel Mendoza


1.0:はじめに 

本記事をご覧いただきありがとうございます。今回は、スマートマネーコンセプトインナーサークルトレーダーのオーダーブロック理論に基づいたインジケーターの開発方法を学びます。 

1.1:オーダーブロック作成の基礎 

オーダーブロックとは、チャート上で未決注文が蓄積されていると考えられるゾーンを指します。

この現象は通常、金融機関などの大口参加者が大規模なポジションを持ちたいと考えているにもかかわらず、市場に十分な流動性が存在せず、一度にすべての注文を約定させると価格に大きな影響を与えてしまう場合に発生します。需給の基本法則に従えば、たとえば買い注文の一部を執行するだけで価格は上昇し始め、さらに注文を執行するには、それに応じる売り手(=流動性)を積極的に市場から探し出す必要があります。

そのため、機関投資家は価格を大きく動かさないように、注文を複数の小さなロットに分割して執行します。こうすることで、ポジションを完成させる前に価格が意図せず大きく動いてしまうことを防ぎます。

このような理論から、チャート上でこうしたゾーンは「供給と需要のバランスが大きく崩れている領域(買いまたは売り)」として特定できます。以下では、こうしたゾーンを識別する3つの方法と、それをコードで実装する方法について解説していきます。

1.1.1:プライスアクションベースのロジック(初級レベル) 


オーダーブロックのロジックに入る前に、まずはその仕組みを理解するうえで欠かせないローソク足の構成要素について簡単に復習しておきましょう。

ローソク足は4つの価格情報で構成されています。                    

価格 説明
  高値 ローソク足の時間枠内で記録された最も高い価格
  安値 ローソク足の時間枠内で記録された最も低い価格
  始値 ローソク足が始まった時点の価格
  終値 ローソク足が終了した時点の価格   

これをよりよく理解するために、チャートの例を見てみましょう。

          OHCL

まず始めに、オーダーブロック理論において重要な最初の要素は、市場の不均衡を特定することです。この不均衡は、チャート上では同方向に連続する複数本のローソク足として視覚的に現れ、明確なトレンドの存在を示します。

例:4本連続の陽線による上昇トレンド

この例では、4本の連続した陽線(強気ローソク足)によって構成される上昇トレンドに注目します。以下のルールに従って判断します。      

ローソク足 説明
前のローソク足 上昇トレンドに先行する足で、通常は上昇が始まる起点より下でクローズします。
最初のローソク足 上昇の開始を示す足で、前のローソク足の始値より上でクローズします。
2~4番目のローソク足
いずれも前のローソク足の終値より上でクローズし、上昇の勢いを継続します。

ルール
  • 上昇条件:4本のローソク足すべてが連続して陽線であること。1本目の足がインバランスの開始を示し、それ以降の足がその勢いを確認します。
  • オーダーブロックの特定:前のローソク足+最初の陽線を含むゾーンがオーダーブロックとなります。このゾーンは、買い手が主導権を握ったエリアとして機能します。

以下に、4本の連続した陽線によって明確な価格のインバランスが示されているチャート例を視覚的に示します。

シンプルなOPの例

以下は、連続ローソク足に基づくオーダーブロックの識別ルールです。

側面 上昇オーダーブロック 下降オーダーブロック
ローソク足の条件 ローソク足 1、2、3、4 はすべて陽線であること(終値>始値) ローソク足 1、2、3、4 はすべて陰線であること(終値<始値)
ローソク足2の極値 ローソク足2の安値が、ローソク足1の実体の中央値より上であること
(例外:ハンマー)
ローソク足2の高値が、ローソク足1の実体の中央値より下であること
(例外:逆ハンマー)
ローソク足2の実体 ローソク足2の実体の少なくとも40%が、ローソク足1の高値を上回ること ローソク足2の実体の少なくとも40%が、ローソク足1の安値を下回ること
ローソク足3の極値  ローソク足3の安値が、ローソク足2の実体の上位25%より上にあること ローソク足3の高値が、ローソク足2の実体の下位25%より下にあること
ローソク足3の実体 ローソク足3の実体の半分が、ローソク足2の高値を上回っていること ローソク足3の実体の半分が、ローソク足2の安値を下回っていること

ルールの目的

これらの厳格な条件は、4本のローソク足パターンが十分に強力であることを確認するために設けられています。また、ゾーン内に残っている未決注文がまだ消化されていないことを検証し、オーダーブロックとしての有効性を担保するためのものです。

1.1.2:プライスアクションベースのロジックとインジケーター(中級レベル)

このより高度なアプローチでは、プライスアクションだけでなく、インジケーター(特にボリューム)を活用して動きの強さを検証します。

戦略の原則

先述の通り、大口の市場参加者による重要な取引は、しばしば比較的低い出来高から始まり、その後、注文の実行に伴い出来高が急増します。この出来高の急増は通常、2〜3本のローソク足にわたって現れ、オーダーブロックの形成を示唆します。

このロジックは、主に次の 2 つのシナリオに分類できます。

ケース1:出来高の増加を伴うオーダーブロック

このシナリオでは、出来高が著しく増加し始めた時点でオーダーブロックが形成されます。条件は次のとおりです。

  1. 動きの開始:比較的出来高の小さいローソク足でスタートし、これは蓄積の始まりを示します。
  2. 出来高の増加:次のローソク足で出来高が大きく増加し、注文の執行を示唆します。この増加は2〜3本のローソク足にわたって続くことがあります。
  3. オーダーブロックの確定:出来高の増加が始まったゾーンをオーダーブロックとして識別します。このゾーンでは未約定注文が処理されたと推定されます。

強気の例 

強気の出来高増加

弱気の例         

弱気の出来高増加

ケース2:単一の出来高急増を伴うオーダーブロック

このケースでは、特定の1本のローソク足において顕著な出来高の急増が観測され、そのローソク足を起点としてオーダーブロックが識別されます。プライスアクションと出来高の両方によって、3本連続のローソク足パターンがオーダーブロックとして認定されます。

ルール

側面 上昇オーダーブロック 下降オーダーブロック
ローソク足1の出来高急増
ローソク足1が3本の中で最も出来高が多く、かつ前のローソク足とローソク足2の出来高を上回っていること
ローソク足1が3本の中で最も出来高が少なく、かつ前のローソク足とローソク足2の出来高を下回っていること
ローソク足2の極値
ローソク足2の安値がローソク足1の実体の中央値より上であること(オーダーブロックの蓄積ゾーンがまだ未タッチであることを示す)
(例外:ローソク足1がハンマーの場合)
ローソク足2の高値ローソク足1の実体の中央値より下であること(オーダーブロックの蓄積ゾーンがまだ未タッチであることを示す)
(例外:ローソク足1が逆ハンマーの場合)
ローソク足2の実体 ローソク足2の実体の60%以上が ローソク足1の高値を上回っていること(上昇モメンタムの継続を示す) ローソク足2の実体の60%以上がローソク足1の安値を下回っていること(下降モメンタムの継続を示す)
ローソク足3の極値 ローソク足3の高値がローソク足2の始値より高いこと(上昇モメンタムの継続を示す) ローソク足3の安値がローソク足2の始値より高いこと(下降モメンタムの継続を示す)

強気の例 

強気の出来高ピーク

 弱気の例

弱気の出来高ピーク


2.0:オーダーブロックインジケーターの開発

2.1:入力とインジケーターパラメータの設定


いよいよ、多くの方がお待ちかねだったと思われる実装フェーズに入ります。これまで学んできた理論をすべてコードに落とし込んでいきましょう。 

1. まず、「カスタムインジケーター」タイプの新しいプログラムを作成します。

2. 次にインジケーターの名前と作成者の名前を書きます。

3. 次に、後続の計算のためにOnCalculateを選択します。

 4. [完了]を押して終了します。

この段階では、まだプロットを定義しません。

indicator_buffers 
indicator_plots 

「no indicator plot defined for indicator00」というエラーを回避するには

先頭に次のプレースホルダーコードを追加します。

#property  indicator_buffers 1
#property  indicator_plots 1

これにより警告が削除され、構成とロジックの実装を続行できるようになります。

まず入力を設定しましょう。

  • 強気オーダーブロックと弱気オーダーブロックの色

    これらの設定により、ユーザーは上昇および下降オーダーブロックを視覚的に区別できるよう、表示される色を自由に選択できます。

  • 矩形のカスタマイズオプション

    これらのオプションは、オーダーブロックを示す矩形の表示方法を制御します。

    • 枠線の太さ:矩形の枠線の太さを設定します。
    • 背景に表示:矩形をローソク足の背後に表示するか前面に表示するかを設定します。
    • 選択可能:チャート上の長角形をクリックして移動できるかどうかを設定します。
  • オーダーブロックの検索範囲

    このパラメータは、現在のローソク足から遡って何本分のバーを対象に有効なオーダーブロックを検索するかを定義します。この値を調整することで、インジケーターを異なる取引戦略や時間足に柔軟に適応させることが可能になります。

  • グループ化による入力の整理

    入力とは、ユーザーがプログラムの外部から自由に変更できる設定項目です。可読性や整理のしやすさを高めるために、以下のような考え方を用いています。     

    sinput

    上記のキーワードを使用することで、パラメータを

    group
  • プロパティでカテゴリごとにグループ化し、より整理された構造にできます。これにより、コードの可読性が向上するだけでなく、ユーザーがインジケーターの各機能(たとえば、矩形の設定やオーダーブロックの解析など)に関連するパラメータ群を直感的に認識しやすくなります。

    sinput group "--- Order Block Indicator settings ---"
    input          int  Rango_universal_busqueda = 500; // Universal range for searching order blocks
    input          int  Witdth_order_block = 1;         // Width of the order block lines
    
    input          bool Back_order_block = true; // Enable object to be drawn in the background
    input          bool Fill_order_block = true; // Enable fill for the order block rectangle
    
    input          color Color_Order_Block_Bajista = clrRed;   // Assign red color for bearish order block
    input          color Color_Order_Block_Alcista = clrGreen; // Assign green color for bullish order block
    

    2.2:主要な構造体と関数の作成

    このセクションでは、インジケーター内でオーダーブロック を管理するための主要な構造体と関数を定義します。これにより、各オーダーブロックの重要情報を保存・整理し、動的配列を使って効率的にデータ管理をおこなえるようになります。

    1. 最後に処理したローソク足の時間を保存する変数

    まず、最後に処理したローソク足の時間情報を保存する変数を作成します。これは、同じローソク足でオーダーブロックが重複して検出されるのを防ぎ、時間の経過に伴う正確な追跡を可能にするために必要です。

    datetime tiempo_ultima_vela;
    

    2. ATRインジケーターのハンドラ

    2番目のステップはインジケーターハンドラを作成することです。

    ATR (Average True Range)
    

    これは市場のボラティリティを測定し、インジケーターのロジックを補完します。このハンドラオーダーブロックの計算に使用できるように最初に初期化されます。

    int atr_i;
    

    3. オーダーブロックのデータを保存する構造体の作成

    次に、各オーダーブロックの関連データを保存する構造体を作成します。この構造体は時間、価格、ブロック名、およびミティゲーションされたかどうかの情報を含みます。加えて、検出されたすべてのオーダーブロックを保存する動的配列も作成します。

    struct OrderBlock
    {
       datetime time1;      // Time of the candle prior to the first candle of the order block
       double price1;       // Upper price level of the order block (level 1)
       double price2;       // Lower price level of the order block (level 2)
       string name;         // Name of the order block
       bool mitigated;      // Status of the order block (true if mitigated, false if not)
    };
    

    構造体フィールドの説明

    OrderBlock構造体は以下のフィールドで構成されます。

    • time1:最初のオーダーブロックのローソク足の直前のローソク足の時間を保存します。ブロックが形成された時点を知り、時間を比較するのに役立ちます。

    • price1オーダーブロックの最初の主要価格、または高値を表します。これは上昇オーダーブロックの場合、高値となります。

    • price2オーダーブロックの2番目の主要価格、または安値を表します。これは上昇オーダーブロックのの場合、安値となります。

    • name:チャート上でオーダーブロックを識別するための一意の名前を保存します。この名前はブロックを明確にラベル付けし、視覚的に認識しやすくするために使用されます。

    • mitigatedオーダーブロックがミティゲーションされた(価格がブロックレベルに達したまたは超えた)かどうかを示します。ミティゲーションされた場合はtrue、そうでなければfalseになります。

    4. オーダーブロックを格納する動的配列

    最後に、検出されたすべてのオーダーブロックを格納する動的配列を作成します。これらの配列により、複数のブロックをメモリに保存し、時間の経過とともに必要に応じてオンまたはオフにすることができます。

    OrderBlocks ob_bajistas[];
    OrderBlocks ob_alcistas[];
    

    OnInit関数

    OnInit関数は、インジケーターのすべての要素を初期化し、インジケーターが動作を開始する前にすべてが正常であることを確認するためのチェックを実行する役割を果たします。以下では、コード内で何が起こっているかをステップごとに説明します。

    1. 変数の初期化

    最初に、変数

    "tiempo_ultima_vela"

    の初期値が0に設定されます。この変数は、最後に処理されたローソク足の時間を保存するために重要であり、重複を避け、インジケーターのフローを適切に管理します。

    tiempo_ultima_vela = 0;

    次に、インジケーターハンドラが起動されます。

    "ATR" (Average True Range)

    ATRの期間は14本のローソク足です。ATRはその後、市場のボラティリティを測定するために使用され、オーダーブロックのロジックに貢献します。

    atr_i = iATR(_Symbol, PERIOD_CURRENT, 14);
    
    

    2. 入力パラメータの確認

    初期化の後、コードは変数

    Rango_universal_busqueda

    の値が40未満であるかどうかを確認します。この変数は、インジケーターがオーダーブロックを検索する範囲を定義します。この範囲が小さすぎると、インジケーターの精度や効果に影響を及ぼす可能性があるため、警告メッセージが表示され、インジケーターは停止し、INIT_PARAMETERS_INCORRECTの値を返します。

    if (Rango_universal_busqueda < 40)
    {
       Print("Search range too small");
       return (INIT_PARAMETERS_INCORRECT);
    }
    

    このチェックにより、インジケーターが正常に動作するために検索範囲が適切なサイズであることを確認し、動作に影響を与える不適切な設定を回避できます。

    3. ATRインジケーターの初期化の確認

    次のステップは、ATR インジケーターハンドラの初期化が正しくおこなわれたかどうかを確認することです。

    もし 

    "atr_i"

    INVALID_HANDLE

    と等しければ、インジケーターの作成中にエラーが発生したことを意味し、エラーメッセージが表示されて

    INIT_FAILED

    が返されます。これによりインジケーターが終了します。

    if (atr_i == INVALID_HANDLE)
    {
       Print("Error copying data for indicators");
       return (INIT_FAILED);
    }
    

    4. 動的配列のサイズ変更

    下降および上昇のオーダーブロックを格納する動的配列は、必要に応じてサイズが変更されますが、初期サイズは0に設定されます。これにより、プログラム開始時に両方の配列が空であり、新しいオーダーブロックを格納する準備が整っていることが保証されます。

    ArrayResize(ob_bajistas, 0);
    ArrayResize(ob_alcistas, 0);
    

    動的配列を使用することは、検出されたオーダーブロックの数を制御するうえで非常に重要です。必要に応じて配列のサイズを拡張または縮小できるためです。

    初期化の完了

    すべてのチェックと初期化がエラーなしで完了した場合、関数は次のようになります。

    OnInit()

    戻り値

    INIT_SUCCEEDED

    これは、インジケーターが適切に初期化され、チャート上で実行を開始する準備ができていることを意味します。

    return (INIT_SUCCEEDED);

    完全なコードは、次のとおりです。

    int OnInit()
      {
    //--- indicator buffers mapping
       tiempo_ultima_vela = 0;
       atr_i = iATR(_Symbol,PERIOD_CURRENT,14);
    
       if(Rango_universal_busqueda < 40)
         {
          Print("Search range too small");
          return (INIT_PARAMETERS_INCORRECT);
         }
    
       if(atr_i== INVALID_HANDLE)
         {
          Print("Error copying data of indicators");
          return(INIT_FAILED);
         }
    
       ArrayResize(ob_bajistas,0);
       ArrayResize(ob_alcistas,0);
    
    
    //---
       return(INIT_SUCCEEDED);
      }
    

    ここで、メモリを解放するために、インジケーターの初期化解除イベントにコードを追加します。

    void OnDeinit(const int reason)
      {
    //---
      Eliminar_Objetos();
    
      ArrayFree(ob_bajistas);
      ArrayFree(ob_alcistas);
       
      }
    
    Eliminar_Objetos();

    これは、作成した矩形を削除するために後で使用する関数になります。

    新しいローソク足の出現を確認する

    このコードの目的は、価格が変動するたびにインジケーターを実行するのではなく、新しいローソク足が形成されたときだけに処理をおこなうことで、インジケーターの効率を最適化することです。複数の銘柄やインジケーターを同時に使用している場合、価格変動ごとにインジケーターが動作すると、コンピュータリソースを無駄に消費することになります。

    そのため、最後に処理されたローソク足の時間をチェックし、新しいローソク足が検出された場合にのみ、インジケーター処理を有効化します。以下はコードの各部分の説明です。

    1. 初期化

    bool変数 

    "new_vela"

    がトリガーとして機能します。デフォルト値はfalseで、新しいローソク足が開始されていないことを意味します。

    bool new_vela = false; // We assign the trigger that tells us whether a new candle has opened to false

    2. 新しいローソク足の出現を確認する

    次のステップは、最後に処理されたローソク足の時間

    tiempo_ultima_vela

    が、チャート上の現在のローソク足の時間と異なるかどうかを確認します。関数

    iTime()

    は、指定されたローソク足の開始時刻を返します。この場合は最新のローソク足 (インデックスは 0) です。時間が一致しない場合は、新しいローソク足が出現したことを意味します。

    if(tiempo_ultima_vela != iTime(_Symbol, PERIOD_CURRENT, 0)) // Check if the current time is different from the stored time
    {
        new_vela = true;                                        // If it doesn't match, set the new candle indicator to true
        tiempo_ultima_vela = iTime(_Symbol, PERIOD_CURRENT, 0); // Update the last processed candle time
    }
    

    このコードブロックは、次の2つのタスクを実行します。

    1. 新しいローソク足が形成されたかどうかを確認する
    2. 将来使用するために、新しいローソク足の時刻でlast_sail_time変数を更新します。

    メインコードの実行

    変数 

    new_vela

    が真であれば、これは新しいロウソクが開始されたことを意味します。この場合、オーダーブロックを処理するメインインジケーターコード、またはその他の適切なロジックを実行できます。このチェックを実行することで、価格が変動するたびにコードが実行されることを回避し、チャートに新しいローソク足が表示されたときのみコードが実行されるようになります。

    if(new_vela == true)
    {
       // Here we will place the main code of the indicator that will run only when a new candlestick opens
    }
    

    ローソク足、出来高、ATRデータを格納する配列の作成

    このブロックには、ローソク足、ティックボリューム、ATR に関する重要な情報を格納する配列が含まれています。このデータは、プライスアクションの分析とオーダーブロックの識別に必要です。

    1. 配列の宣言

    対応する値を格納するために、double、datetime、long型の配列を宣言します。

    double openArray[];  // Stores Open price of candlesticks
    double closeArray[]; // Stores Close price of candlesticks
    double highArray[];  // Stores High price of candlesticks
    double lowArray[];   // Stores Low price of candlesticks
    datetime Time[];     // Stores the time of each candlestick
    double atr[];        // Stores ATR values, which indicator market volatility
    long Volumen[];      // Stores tick volume, representing the number of transactions in each candlestick
    

    2. 配列を時系列として設定する

    配列を時系列として動作させるには、ArraySetAsSeries関数を使用します。インデックス0が最新のローソク足を表すため、最新のローソク足データに簡単にアクセスできるようになります。

    ArraySetAsSeries(openArray, true);
    ArraySetAsSeries(closeArray, true);
    ArraySetAsSeries(highArray, true);
    ArraySetAsSeries(lowArray, true);
    ArraySetAsSeries(Time, true);
    ArraySetAsSeries(Volumen, true);
    ArraySetAsSeries(atr, true);
    

    3. ローソク足とATRデータのコピー

    次に、CopyOpen、CopyClose、CopyHigh、CopyLow、CopyTime、CopyTickVolume関数を使用して、ローソク足とティックボリュームのデータを目的の配列にコピーします。CopyBufferはATR値を取得するためにも使用されます。

    int copiedBars = CopyOpen(_Symbol, PERIOD_CURRENT, 0, (Rango_universal_busqueda * 2), openArray);
    if(copiedBars < 0)
    {
        Print("Error copying data from Open: ", GetLastError());
    }
    CopyClose(_Symbol, PERIOD_CURRENT, 0, (Rango_universal_busqueda * 2), closeArray);
    CopyHigh(_Symbol, PERIOD_CURRENT, 0, (Rango_universal_busqueda * 2), highArray);
    CopyLow(_Symbol, PERIOD_CURRENT, 0, (Rango_universal_busqueda * 2), lowArray);
    CopyTime(_Symbol, PERIOD_CURRENT, 0, (Rango_universal_busqueda * 2), Time);
    CopyTickVolume(_Symbol, PERIOD_CURRENT, 0, (Rango_universal_busqueda * 2), Volumen);
    CopyBuffer(atr_i, 0, 0, (Rango_universal_busqueda * 2), atr);
    

    4. エラー処理

    オープンデータをコピーする際、コピーされたバーの数が負の場合、それはエラーを示します。このとき、デバッグを支援するためにGetLastError関数を使ってエラーメッセージを表示します。

    オーダーブロック検出ロジックの実装準備

    オーダーブロック検出ロジックを実装する前に、以下のような必要な準備をおこないます。

    1. 直前の陽線の検出:パターンの最初のローソク足の前に陽線が存在するかを判定する関数を作成します。見つかった場合、その最初のローソク足を一番近いローソク足に割り当て、上昇の始まりからオーダーブロックを描画できるようにします。

    2. 矩形の描画:チャート上でオーダーブロックを視覚的に表現するための、矩形を描画する専用の関数を実装します。

    3. 配列管理:検出されたオーダーブロックを対応する配列に追加するための関数を開発します。これには以下が含まれます。

      • 重複チェック:追加しようとしているオーダーブロックがすでに記録されていないか確認する関数。これによって、新しいオーダーブロックのみが追加されます。
    4. オーダーブロックのミティゲーションオーダーブロックがミティゲーションされたかどうかを確認する関数を作成します。

    5. オーダーブロックの削除:通知やチャート表示を整理するために、オーダーブロックオーダーブロックを削除済みとしてマークする関数を追加します。

    これらの関数を使うことで、オーダーブロックを配列に追加し、重複のない新しいブロックのみを登録できます。以降はコード量が多いため、すべての行を詳細に解説することはせず、各セクションの簡潔な説明のみに留めます。

    1. 関数

    //+------------------------------------------------------------------+
    //|     Functions to Manage and Add Values to the Arrays             |
    //+------------------------------------------------------------------+
    void AddIndexToArray_alcistas(OrderBlocks &newVela_Order_block_alcista)
      {
       if(!IsDuplicateOrderBlock_alcista(newVela_Order_block_alcista))  // Here we check if the structure we are about to add already exists in the array
         {
          int num_orderblocks_alcista = ArraySize(ob_alcistas);         // We assign the variable "num_orderblocks_alcista" the current size of the ob_alcistas array
          ArrayResize(ob_alcistas, num_orderblocks_alcista + 1);        // Resize the array by increasing its size by 1 to make space for a new order block
          ob_alcistas[num_orderblocks_alcista] = newVela_Order_block_alcista; // Assign the new order block to the new index (last position) in the array
         }
      }
    
    bool IsDuplicateOrderBlock_alcista(const OrderBlocks &newBlock)
      {
       for(int i = 0; i < ArraySize(ob_alcistas); i++) //Start a loop to go through all positions of the ob_alcistas array
         {
          if(ob_alcistas[i].time1 == newBlock.time1 &&
             ob_alcistas[i].name == newBlock.name
            ) // Check if both time1 and name of the order block already exist in the array
            {
             return true; // If they do, return true (i.e., it is a duplicate)
             break        // Exit the loop
            }
         }
       return false; // If no duplicate is found, return false
      }
    
    // This would be the same logic but for bearish order blocks
    void AddIndexToArray_bajistas(OrderBlocks &newVela_Order_block_bajista)
      {
       if(!IsDuplicateOrderBlock_bajista(newVela_Order_block_bajista))
         {
          int num_orderblocks_bajistas = ArraySize(ob_bajistas);
          ArrayResize(ob_bajistas, num_orderblocks_bajistas + 1);
          ob_bajistas[num_orderblocks_bajistas] = newVela_Order_block_bajista;
         }
      }
    
    bool IsDuplicateOrderBlock_bajista(const OrderBlocks &newBlock)
      {
       for(int i = 0; i < ArraySize(ob_bajistas); i++)
         {
          if(ob_bajistas[i].time1 == newBlock.time1 &&
             ob_bajistas[i].name == newBlock.name
            )
            {
             return true; // Duplicate found
             break;
            }
         }
       return false;      // No duplicate found
      }
    

    すでに、オーダーブロックが重複していないかを確認し、動的配列に追加するための関数を実装しました。次に、これらのオーダーブロックをチャート上に描画する方法について説明します。

    このために、次の 2 つの主要な価格ポイントを使用します。

    • 価格1:強気オーダーブロックの場合、この価格は矩形の下辺を表します。弱気オーダーブロックの場合、この価格は上辺となります。

    • 価格2:強気オーダーブロックの場合、この価格は矩形の上辺を表します。弱気オーダーブロックの場合、この価格は下辺となります。

    強気の例

         強気OB

     弱気の例

          弱気オーダーブロック

    次に、オーダーブロックにおけるミティゲーション関数のデモンストレーションに移りましょう。

    //+------------------------------------------------------------------+
    //|             Functions for Order Blocks                           |
    //+------------------------------------------------------------------+
    datetime  mitigados_alcsitas(double price, double &openArray[], double &closeArray[], double &highArray[], double &lowArray[], datetime &Time[], datetime start, datetime end)
      {
       int startIndex = iBarShift(_Symbol,PERIOD_CURRENT,start); // Using iBarShift we find the index of the candle by passing the "start" time
       int endIndex = iBarShift(_Symbol, PERIOD_CURRENT, end);   // Using iBarShift we find the index of the candle by passing the "end" time
       NormalizeDouble(price,_Digits); // Normalize the price we will work with
    
       for(int i = startIndex - 2 ; i >= endIndex + 1 ; i--) // Start a loop from start (time1 of the order block) to end (time[1])
         {
          //terminated by endIndex which will be time[0] + 1 = time[1]  --> We are searching for mitigation from past to present (backward)
          NormalizeDouble(lowArray[i],_Digits);
          NormalizeDouble(openArray[i],_Digits);
          NormalizeDouble(highArray[i],_Digits);
          NormalizeDouble(openArray[i],_Digits);
          //Normalizamos todas laas variable
    
          if(price > lowArray[i] || price > openArray[i] || price > closeArray[i] || price > highArray[i]) // Check if OHLC closed below price
            {
             return Time[i]; //If mitigation is found, return the time of the candle where it happened
             Print("el orderblock tuvo mitigaciones", TimeToString(end));
            }
         }
    
       return 0; //If no mitigation was found, return 0
      }
    
    // the same in the bearish case but changing something
    // instead of the price closing below the price
    datetime  mitigado_bajista(double price, double &openArray[], double &closeArray[], double &highArray[], double &lowArray[], datetime &Time[], datetime start, datetime end)
      {
    
       int startIndex = iBarShift(_Symbol,PERIOD_CURRENT,start);
       int endIndex = iBarShift(_Symbol, PERIOD_CURRENT, end);
       NormalizeDouble(price,_Digits);
       for(int i = startIndex - 2 ; i >= endIndex + 1 ; i--)
         {
          NormalizeDouble(lowArray[i],_Digits);
          NormalizeDouble(openArray[i],_Digits);
          NormalizeDouble(highArray[i],_Digits);
          NormalizeDouble(openArray[i],_Digits);
          if(highArray[i] > price || closeArray[i] > price || openArray[i] > price || lowArray[i] > price)
            {
    
             return Time[i]; // returns the time of the candle found
             Print("el orderblock tuvo mitigaciones", TimeToString(end));
    
            }
         }
    
       return 0; // not mitigated so far
      }
    
    datetime esOb_mitigado_array_alcista(OrderBlocks &newblock, datetime end)
      {
       int endIndex = iBarShift(_Symbol, PERIOD_CURRENT, end);
       NormalizeDouble(newblock.price2,_Digits);
    
       for(int i = 0 ; i <  endIndex -2  ; i++)
         {
    
          double low = NormalizeDouble(iLow(_Symbol,PERIOD_CURRENT,i),_Digits);
          double close = NormalizeDouble(iClose(_Symbol,PERIOD_CURRENT,i),_Digits);
          double open = NormalizeDouble(iOpen(_Symbol,PERIOD_CURRENT,i),_Digits);
    
          if((newblock.price2 >= low || newblock.price2 >= open) || newblock.price2 >= close)
            {
             newblock.mitigated = true;
    
    
             return iTime(_Symbol,PERIOD_CURRENT,i); // returns the time of the found candle
            }
         }
    
       return 0; // not mitigated so far
      }
    
    datetime esOb_mitigado_array_bajista(OrderBlocks &newblock, datetime end)
      {
    
       int endIndex = iBarShift(_Symbol, PERIOD_CURRENT, end);
       NormalizeDouble(newblock.price2,_Digits);
    
       for(int i = 0 ; i<  endIndex -2  ; i++)
         {
    
          double high = NormalizeDouble(iHigh(_Symbol,PERIOD_CURRENT,i),_Digits);
          double close = NormalizeDouble(iClose(_Symbol,PERIOD_CURRENT,i),_Digits);
          double open = NormalizeDouble(iOpen(_Symbol,PERIOD_CURRENT,i),_Digits);
    
          if((high >= newblock.price2 || close >= newblock.price2) || open >= newblock.price2)
            {
             newblock.mitigated = true;
             // returns the time of the found candlestick
             return iTime(_Symbol,PERIOD_CURRENT,i);
            }
         }
    
       return 0; // not mitigated so far
      }
    
    

    これらの関数はオーダーブロックの状態をチェックする役割を担います。

    • ミティゲーションチェック関数:この関数はオーダーブロックのミティゲーションをチェックし、追加された構造を評価するために使用されます。

    • ミティゲーション活性化関数:「array」キーワードを含む 2 番目の関数は、オーダーブロックのミティゲーションステータスをアクティブにします。

    次に、矩形を描画する関数と、最も近い強気または弱気のローソク足を見つける関数について見ていきます。

    最も近い強気または弱気のローソク足を特定する関数は非常に重要です。この関数の目的は、特に強い値動きの始まりでオーダーブロックが形成される場合に、対応するローソク足を正確に検出することにあります。これにより、値動きの途中や終わりで誤って検出されることを防ぎ、分析の精度と効果を高めます。

    強気の例

    例関数OB+

    関数は次のとおりです。

    //+------------------------------------------------------------------+
    //|  Functions to find the nearest bullish or bearish candle         |
    //+------------------------------------------------------------------+
    int FindFurthestAlcista(datetime start, int numVelas)
      {
       int startVela = iBarShift(_Symbol, PERIOD_CURRENT, start); // Function to find the furthest bullish candle in a consecutive sequence
    // Initialize variables
       int furthestVela = 0;
       int counter_seguidas = 0;
    
       for(int i = startVela  + 1; i <= startVela + numVelas ; i++)  // Since the candle at "start" is already known to be bullish, we skip it (+1)
         {
          //then it is obvious that the candle at time 1 is bullish (in this function), that's why we increase +1, then we check that i is less than or equal to the index of startVela + num candles
          //here num candles would be the number of candles to search for
          double Close = NormalizeDouble(iClose(_Symbol,PERIOD_CURRENT,i),_Digits); //we get the open by index
          double Open = NormalizeDouble(iOpen(_Symbol,PERIOD_CURRENT,i),_Digits);  //we get the close by index
    
          if(Close > Open || Close == Open)  // we check if it's a bullish candle (close > open), that is, the close must be greater than the open
            {
             counter_seguidas++; // if this is true, we increase a variable by 1, which we will use later
            }
          else
             if(Open > Close)
               {
                furthestVela = startVela  + 1 + counter_seguidas; //if the found candle is not bullish, it is obviously bearish, therefore we assign the index of the previous candle to the one that started the bullish move
                // startVela: is the candle we passed, then we add 1 because as we said before, we will draw the order block one candle before the bullish move (i.e., bearish candle)
                // to this we add the counter of consecutive candles
    
                break; // exit the loop
               }
         }
    
    //we check if the body of the candle before the move is more than 30% larger than the candle that starts the move; if it is, we revert to the normal value
       double body1 = NormalizeDouble(iClose(_Symbol,PERIOD_CURRENT,(furthestVela -1)),_Digits) - NormalizeDouble(iOpen(_Symbol,PERIOD_CURRENT,(furthestVela -1)),_Digits);;
       double body_furtles =  NormalizeDouble(iOpen(_Symbol,PERIOD_CURRENT,furthestVela),_Digits) -  NormalizeDouble(iClose(_Symbol,PERIOD_CURRENT,furthestVela),_Digits);
    
       if(body_furtles > (1.3 * body1))
          furthestVela--;
    
       return furthestVela; // return the index of the found candle
      }
    
    // Function to search for the furthest bearish candle with consecutive bearish candles
    int FindFurthestBajista(datetime start, int numVelas)
      {
       int startVela = iBarShift(_Symbol, PERIOD_CURRENT, start); // Index of the initial candle
       int furthestVela = 0;     // Initialize variable
       int counter_seguidas = 0; // Counter of consecutive bearish candles
    
       for(int i = startVela + 1; i <= startVela + numVelas; i++)
         {
          double Close = NormalizeDouble(iClose(_Symbol, PERIOD_CURRENT, i), _Digits);
          double Open = NormalizeDouble(iOpen(_Symbol, PERIOD_CURRENT, i), _Digits);
    
          // If the candle is bearish
          if(Close < Open || Close == Open)
            {
             counter_seguidas++; // Increase the counter of consecutive bearish candles
            }
          // If the candle is bullish, we stop
          else
             if(Close > Open)
               {
                // Return the candle where the bearish sequence is interrupted by a bullish one
                furthestVela = startVela + 1 + counter_seguidas;
                break;
               }
         }
    
       return furthestVela;
      }
    

    ここで、矩形を描画する関数を作成する必要があります。

     void RectangleCreate(long chart_ID, string name, const int sub_window, datetime time1, double price1, datetime time2, double price2, color clr, int width, bool fill, bool back , ENUM_LINE_STYLE style , bool select = false)
      {
       ResetLastError(); // reset the error
    
     // check and create rectangles
       if(!ObjectCreate(chart_ID, name, OBJ_RECTANGLE, sub_window, time1, price1, time2, price2))
         {
          Print(__FUNCTION__, ": Failed to create a rectangle! Error code = ", GetLastError()  , "The name of the rectangle is: " , name); //if creation fails, print the function + error code and rectangle name
         }
    
      // set the properties of the rectangles
       ObjectSetInteger(chart_ID, name, OBJPROP_COLOR, clr);
       ObjectSetInteger(chart_ID, name, OBJPROP_STYLE, STYLE_SOLID);
       ObjectSetInteger(chart_ID, name, OBJPROP_WIDTH, width);
       ObjectSetInteger(chart_ID, name, OBJPROP_FILL, fill);
       ObjectSetInteger(chart_ID, name, OBJPROP_BACK, back);
       ObjectSetInteger(chart_ID, name, OBJPROP_SELECTABLE, select);
       ObjectSetInteger(chart_ID, name, OBJPROP_SELECTED, select);
       ObjectSetInteger(Chart_ID, name, OBJPROP_STYLE ,style);  
      }
    

    これらすべての関数の準備ができたら、次の部分に進みます。

    2.3:オーダーブロック検出ロジック


    このシステムのために開発したロジックは以下の通りです。

    1. 上記のロジックを使用してオーダーブロックを検出する。
    2. 構造体に値を割り当てる。 
    3. オーダーブロックを格納する構造を配列に追加する。
    4. オーダーブロックのミティゲーションを確認する。
    5. オーダーブロックを描画する。
    6. 警告する。
    これに基づいて、インジケーターのプログラミングを開始できます。

    まず、オーダーブロックの値を格納する構造体を作成します。

    • 4つの変数を作成します。これらはすべてOrderBlocks構造体の形を持ちます。
    • 強気オーダーブロック用に2つ(インジケーターとプライスアクションのオーダーブロック用)
    • 弱気オーダーブロック用に2つ(インジケーターとプライスアクションのオーダーブロック用)

    OrderBlocks  newVela_Order_block_alcista;
    OrderBlocks  newVela_Order_block_volumen;
    OrderBlocks newVela_Order_Block_bajista;
    OrderBlocks newVela_Order_Block_bajista_2;
    

    これらの構造体を追加すると、オーダーブロックの値が格納される変数がすでに存在します。

    後はロジックを使用してオーダーブロックを検出するだけです。さあ、始めましょう。

    まずは論理です。

    • オーダーブロックをローソク足の範囲内で検索し、インデックスを割り当てる必要があります。これは、ロジック内で設定した条件に基づいてローソク足パターンを検索するのと似ています。
    • このために、forループを使用します。

    コードは次のとおりです。    

    for(int i = Rango_universal_busqueda  ; i  > 5  ; i--)
       {
    
    //checking errors
    if(i + 3> ArraySize(highArray)  || i + 3 > ArraySize(atr))
    continue;
    if(i < 0)
    continue;
    
    //--------Variable Declaration--------------------------------------------//
    
    // Update candle indices
    int one_vela = i ; // central candlestick
    int  vela_atras_two = i +2;
    int vela_atras_one = one_vela +1;
    int two_vela = one_vela - 1;
    int tree_vela = one_vela - 2;
    int four_vela = one_vela -3;
    
    NormalizeDouble(highArray[vela_atras_one],_Digits);
    NormalizeDouble(lowArray[vela_atras_one ], _Digits);
    NormalizeDouble(closeArray[vela_atras_one ],_Digits);
    NormalizeDouble(openArray[vela_atras_one ],_Digits);
    
    NormalizeDouble(highArray[two_vela],_Digits);
    NormalizeDouble(lowArray[two_vela], _Digits);
    NormalizeDouble(closeArray[two_vela],_Digits);
    NormalizeDouble(openArray[two_vela],_Digits);
    
    NormalizeDouble(highArray[tree_vela],_Digits);
    NormalizeDouble(lowArray[tree_vela], _Digits);
    NormalizeDouble(closeArray[tree_vela],_Digits);
    NormalizeDouble(openArray[tree_vela],_Digits);
    
    NormalizeDouble(highArray[one_vela],_Digits);
    NormalizeDouble(lowArray[one_vela], _Digits);
    NormalizeDouble(closeArray[one_vela],_Digits);
    NormalizeDouble(openArray[one_vela],_Digits);
    
    // Calculate average body size of previous candles
    double body1 = closeArray[one_vela] - openArray[one_vela];
    double body2 = closeArray[two_vela] - openArray[two_vela];
    double body3 = closeArray[tree_vela] - openArray[two_vela];
    
    // Volume condition
    long Volumen_one_vela = Volumen[one_vela];
    long Volumen_two_vela = Volumen[two_vela];
    long volumen_vela_atras_one = Volumen[vela_atras_one];
    

    • 基本的に、このコードでは、ローソク足の最大値

    (Rango_universal_busqueda)

    から始めて、

    i  > 5

    によって、インデックス6で終わります。 

    ループでは、iの値から1を引いていきます。

    • 作業するローソク足のOHCLを大量に正規化する
    • ローソク足の実体をclose - openとして割り当てる
    • ピークのケースのみ、ティック値を取得する

    //Volume 
    long Volumen_one_vela = Volumen[one_vela];
    long Volumen_two_vela = Volumen[two_vela];
    long volumen_vela_atras_one = Volumen[vela_atras_one];
    

    次にもう1つのケースを追加します。それは ATR です。これは基本的に、一方向への強い価格変動を分析するために使用します。 

    • 以下に示すコードスニペットでは、基本的な論理条件とATRインジケーターを確認できます。
      //Boolean variables to detect if the case is met (only Price Action)
      bool esVelaCorrecta_case_normal =false;
      bool  esVela_Martillo = false;
      
      //Here we check that 4 consecutive bullish candles have formed with close > open
      if(
         closeArray[one_vela] > openArray[one_vela] &&
         closeArray[two_vela] > openArray[two_vela] &&
         closeArray[tree_vela] > openArray[tree_vela] &&
         closeArray[four_vela] > openArray[four_vela]
      )
        {
      
         esVelaCorrecta_case_normal =true; // if true, assign true to "esVelaCorrecta_case_normal"
        }
      else
         esVelaCorrecta_case_normal =false; // otherwise assign false
      
      bool fuerte_movimiento_alcista =false; // create a variable that activates only if a strong bullish movement occurs
      
      // Check if a movement of 6 consecutive bullish candles was created
      if(
         closeArray[one_vela + 2] > openArray[one_vela + 2] &&
         closeArray[one_vela + 1] > openArray[one_vela +1] &&
         closeArray[one_vela] > openArray[one_vela] &&
         closeArray[two_vela] > openArray[two_vela] &&
         closeArray[tree_vela] > openArray[tree_vela] &&
         closeArray[four_vela] > openArray[four_vela]
      )
        {
         fuerte_movimiento_alcista = true; // if true assign true to "fuerte_movimiento_alcista"
        }
      
      //verificamos si es vela martillo
      if(openArray[one_vela] - lowArray[one_vela] > closeArray[one_vela] - openArray[one_vela]) // check if lower wick is larger than the candle body
        {
         esVela_Martillo = true; // if so set "esVela_Martillo" to true
        }
      
      bool atr_case = false;
      
      if(atr[vela_atras_two] > atr[one_vela] && atr[two_vela] > atr[one_vela] && atr[two_vela] > atr[vela_atras_two] && closeArray[one_vela] > openArray[one_vela]
         && closeArray[four_vela] > openArray[four_vela] && closeArray[tree_vela] > openArray[tree_vela])
         atr_case = true;  // in this code we look for ATR to first fall in one candle
      //then rise, and candles 1, 3, 4 must be bullish; second candle not necessary for this case
      
      //Verification for normal case
      if((esVelaCorrecta_case_normal == true && ((lowArray[two_vela] > ((body1 *0.5)+openArray[one_vela]) && ((body2 * 0.4)+openArray[two_vela]) > highArray[one_vela]) || esVela_Martillo == true)
          && lowArray[tree_vela] > ((body2 * 0.25) +openArray[two_vela])) || fuerte_movimiento_alcista == true || atr_case == true)  
        {
         int furthestAlcista = FindFurthestAlcista(Time[one_vela],20); // call function to find previous bullish candles before "one_vela"
         if(furthestAlcista > 0) // whether or not found, will be > 0 since it returns previous candle index if none found
           {
      
            datetime time1 = Time[furthestAlcista];     //assign time of furthestAlcista candle to time1
            double price2 = openArray[furthestAlcista]; //assign open of furthestAlcista as price2 (usually drawn on a bearish candle)
            double price1 = lowArray[furthestAlcista];  //assign low of furthestAlcista as price1
      
            //assign mentioned variables to the structure
            newVela_Order_block_alcista.price1 = price1;
            newVela_Order_block_alcista.time1 = time1;
            newVela_Order_block_alcista.price2 = price2;
      
            case_OrderBlockAlcista_normal = true; //if all true, activate normal bullish case
           }
         else
            case_OrderBlockAlcista_normal =false;
      
        }
      //versión bajista
      
      bool case_OrderBlockBajista_normal = false;
      bool case_OrderBlockBajista_volumen = false;
      
      //---------------Conditions for Order Blocks--------------------//
      //+------------------------------------------------------------------+
      //| Conditions For Bearish Order Block case_normal                   |
      //+------------------------------------------------------------------+
      if(closeArray[one_vela] < openArray[one_vela]  &&
         closeArray[two_vela] < openArray[two_vela]  &&
         closeArray[tree_vela] < openArray[tree_vela]  &&
         closeArray[one_vela-3]< openArray[one_vela-3]
        )
        {
         esVelaCorrecta_case_normal =true;
        }
      else
         esVelaCorrecta_case_normal =false;
      
      bool a = false;
      
      if(atr[vela_atras_two] > atr[one_vela] && atr[two_vela] > atr[one_vela] && atr[two_vela] > atr[vela_atras_two] && esVelaCorrecta_case_normal)
         a= true;
      
      bool fuerte_movimiento_bajista =false;
      
      if(
         closeArray[one_vela + 2] < openArray[one_vela + 2] &&
         closeArray[one_vela + 1] < openArray[one_vela +1] &&
         closeArray[one_vela] < openArray[one_vela] &&
         closeArray[two_vela] < openArray[two_vela] &&
         closeArray[tree_vela] < openArray[tree_vela] &&
         closeArray[one_vela - 3] <= openArray[one_vela - 3]
      )
        {
         fuerte_movimiento_bajista = true;
        }
      
      // Verification for normal bearish case
      if((esVelaCorrecta_case_normal == true && highArray[two_vela] < ((body1 *0.70)+closeArray[one_vela]) && ((body2 * 0.4)+closeArray[two_vela]) < lowArray[one_vela] && highArray[tree_vela] < highArray[two_vela])
         || a == true || fuerte_movimiento_bajista == true
        )
        {
         int furthestBajista = FindFurthestBajista(Time[one_vela], 20);
         if(furthestBajista != -1)
           {
      
            datetime time1 = Time[furthestBajista];
            double price1 = closeArray[furthestBajista];
            double price2 = lowArray[furthestBajista];
      
            newVela_Order_Block_bajista.price1 = price1;
            newVela_Order_Block_bajista.time1 = time1;
            newVela_Order_Block_bajista.price2 = price2 ;
      
           }
         else
            case_OrderBlockBajista_normal =false;
        }
      //+------------------------------------------------------------------+
      

      各関数について、適切なコメント付きで説明していきましょう。私たちは特定のパターンを識別しようとしており、それを検出したときにブール変数をアクティブにします。

      次に、動きが始まるローソク足であるone_candleより前の強気のローソク足を確認します。

      最後に、オーダーブロックに対して価格と時間の値を割り当てます。

      ここからは 出来高 のケースに移ります。ここでは、出来高のピークと増加傾向にある出来高の両方を確認します。

      //condition orderblock volume --------------------------------//
      if(Volumen_one_vela  > Volumen_two_vela && Volumen_one_vela > volumen_vela_atras_one)
        {
         VolumenCorrecto = true; //here we check the volume peak
        }
      else
         VolumenCorrecto = false;
      
      //so that the bullish candle behind is bearish and 2 bullish
      if(closeArray[one_vela] > openArray[one_vela]  &&
         closeArray[two_vela] > openArray[two_vela])
        {
         VelaCorrecta_casevolumen = true;
        }
      
      //consecutive case
      bool case_vol_2 = false;
      if(Volumen[one_vela] > volumen_vela_atras_one && Volumen[two_vela] > Volumen[one_vela] && openArray[tree_vela] < closeArray[tree_vela] && openArray[four_vela] < closeArray[four_vela])
         case_vol_2 = true;
      
      //here we verify that the highlights do not mitigate the order block
      if((VolumenCorrecto == true && VelaCorrecta_casevolumen == true
          && ((lowArray[two_vela] > ((body1 * 0.5)+openArray[one_vela]) && ((body2 *0.6)+openArray[two_vela]) > highArray[one_vela]) || esVela_Martillo == true)
          && highArray[tree_vela] > openArray[two_vela]) || case_vol_2 == true)
        {
      //I already explained all this above, it is literally the same, we look for the closest bullish trend and assign a value to the one before it
         int furthestAlcista = FindFurthestAlcista(Time[one_vela],20);
         if(furthestAlcista > 0)
           {
      
            datetime time1 = Time[furthestAlcista];
            double price2 = openArray[furthestAlcista];
            double price1 = lowArray[furthestAlcista];
      
            newVela_Order_block_volumen.price1 = price1;
            newVela_Order_block_volumen.time1 = time1;
            newVela_Order_block_volumen.price2 = price2;
      
            case_orderblock_vol= true;
           }
         else
            case_orderblock_vol =false;
      
        }
      //Bearish version
      //+------------------------------------------------------------------+
      
      //+------------------------------------------------------------------+
      //| Condition for Bullish Order Block Case case_Volumen              |
      //+------------------------------------------------------------------+
      
      bool VelaCorrecta_casevolumen = false;
      bool VolumenCorrecto;
      //condition orderblock volume --------------------------------//
      //by peak volume
      if(Volumen_one_vela  > Volumen_two_vela && Volumen_one_vela > volumen_vela_atras_one)
        {
         VolumenCorrecto = true;
        }
      else
         VolumenCorrecto = false;
      //we look here for 2 consecutive bearish candles
      if(closeArray[one_vela] < openArray[one_vela]  &&
         closeArray[two_vela] < openArray[two_vela])
        {
      
         VelaCorrecta_casevolumen = true; //we set the variable "VelaCorrecta_casevolumen" to true
        }
      //we look for an increasing volume in addition to the 3rd candle and 4th candle being bearish
      bool case_vol_2 = false;
      if(Volumen[one_vela] > volumen_vela_atras_one && Volumen[two_vela] > Volumen[one_vela] && openArray[tree_vela] > closeArray[tree_vela] && openArray[four_vela] > closeArray[four_vela])
         case_vol_2 = true;
      
      if((VolumenCorrecto == true && VelaCorrecta_casevolumen == true && highArray[two_vela] < ((body1 * 0.5)+closeArray[one_vela]) && ((body2 *0.5)+closeArray[two_vela]) < lowArray[one_vela]) || case_vol_2 == true)   // verificamos si se cumple
        {
      // the peak volume case or increasing volume case
         int furthestBajista = FindFurthestBajista(Time[one_vela],20); //we look for the bearish candle closest to the 1st candle
      
         if(furthestBajista > 0)
           {
            //if this is true, which as I said before it will always be, we assign the candle values
            //to the structure variables to draw the rectangles
            datetime time1 = Time[furthestBajista];
            double price1 = closeArray[furthestBajista];
            double price2 = lowArray[furthestBajista];
      
            newVela_Order_Block_bajista_2.price1 = price1;
            newVela_Order_Block_bajista_2.time1 = time1;
            newVela_Order_Block_bajista_2.price2 = price2 ;
      
            case_OrderBlockBajista_volumen = true;
           }
         else
            case_OrderBlockBajista_volumen = false;
        }
      //+------------------------------------------------------------------+
      
      

      オーダーブロックの検出を実装したので、それらを配列に追加する必要があります。これにより、後でチャート上に表示できるようになります。

      このコードでは、以下の処理を行います。

      1. 変数mitigatedをfalseに初期化する。
      2. オーダーブロックのタイプに基づいて名前を設定し、one_sailに応じて時間を設定する。
      3. 最後に、オーダーブロックを動的配列に追加する。
      if(case_OrderBlockAlcista_normal == true
         && mitigados_alcsitas(newVela_Order_block_alcista.price2,openArray,closeArray,highArray,lowArray,Time,newVela_Order_block_alcista.time1,Time[0]) == 0)  //we verify that the order block has not been mitigated
        {
         newVela_Order_block_alcista.mitigated = false; //we activate the order block status as unmitigated = false
         newVela_Order_block_alcista.name =  "Order Block Alcista normal" + TimeToString(newVela_Order_block_alcista.time1) ;  //we assign the name "Normal Bullish Order Block" + the time of one_Vela
         AddIndexToArray_alcistas(newVela_Order_block_alcista); //we add to the array to then check if they are being mitigated and draw them
      
        }
      //the same would be for the volume case
      if(case_orderblock_vol == true
         && mitigados_alcsitas(newVela_Order_block_volumen.price2,openArray,closeArray,highArray,lowArray,Time,newVela_Order_block_volumen.time1,Time[0]) == 0)
        {
         newVela_Order_block_volumen.mitigated = false;
         newVela_Order_block_volumen.name =  "Order Block Alcista vol" + TimeToString(newVela_Order_block_volumen.time1) ;
         AddIndexToArray_alcistas(newVela_Order_block_volumen);
      
        }
      
        } 
      
      //--- Bearish version
      
      if(case_OrderBlockBajista_normal == true  && mitigado_bajista(newVela_Order_Block_bajista.price2,openArray, closeArray, highArray, lowArray, Time, Time[0],newVela_Order_Block_bajista.time1) == 0
        ) //we check if the bearish order block was not mitigated and the normal case is true
        {
         newVela_Order_Block_bajista.mitigated = false; //we initialize the state of the order block as unmitigated = false
         newVela_Order_Block_bajista.name = ("Order Block Bajista ")+ TimeToString(newVela_Order_Block_bajista.time1) ; //we assign the name as "Bearish Block Order" + the time of the 1st candle
         AddIndexToArray_bajistas(newVela_Order_Block_bajista); //we add the structure to the array
        }
      
      if(case_OrderBlockBajista_volumen == true   && mitigado_bajista(newVela_Order_Block_bajista_2.price2, openArray,closeArray,highArray,lowArray,Time,Time[0],newVela_Order_Block_bajista_2.time1)== 0
        )//we check if the bearish order block was not mitigated and the volume case is true
        {
         newVela_Order_Block_bajista_2.mitigated = false; //we initialize the state of the order block as unmitigated = false
         newVela_Order_Block_bajista_2.name = ("Order Block Bajista ") + TimeToString(newVela_Order_Block_bajista_2.time1) ; //we assign the name as "Bearish Block Order" + the time of the 1st candle
         AddIndexToArray_bajistas(newVela_Order_Block_bajista_2); //we add the structure to the array
      
        }
        } 
      //+------------------------------------------------------------------+
      

      次に、ミティゲーションの描画と検証に進みます。

      2.4:可視化 - 色とオーダーブロックのミティゲーションの確認


      このセクションでは、オーダーブロックの更新・描画、およびミティゲーション状態の有効化について見ていきます。

      •  オーダーブロックの情報を格納している配列 ob_bullishとob_bearishをループ処理して、オーダーブロックを描画します(これはすでに説明したとおりです)。
      •  オブジェクトの描画にはObjectMoveを使って移動させます。これはすべてを再描画することを避け、プログラムの効率を下げず、コンピューターリソースの消費も抑えるためです。

      ここまでの処理が完了したので、これらの要件を満たすために準備したコードを見てみましょう。

      for(int i = 0; i < ArraySize(ob_alcistas); i++) //We iterate through all the indexes of the array where the order blocks information is stored
        {
         datetime mitigadoTime = esOb_mitigado_array_alcista(ob_alcistas[i],ob_alcistas[i].time1); //we call the function that will tell us if index i has been mitigated or not. If it is, we activate its state to true
      
         if(ob_alcistas[i].mitigated == false)  //we verify that it has not been mitigated
           {
            if(mitigadoTime == 0) //We condition that the order block has not been touched by the price
              {
      
               if(ObjectFind(ChartID(),ob_alcistas[i].name) < 0) //we check if the object exists in the graph with ObjectFind
                 {
                  RectangleCreate(ChartID(), ob_alcistas[i].name, 0, ob_alcistas[i].time1, ob_alcistas[i].price1,
                                  Time[0], ob_alcistas[i].price2,Color_Order_Block_Alcista, Witdth_order_block, Fill_order_block, Back_order_block,STYLE_SOLID); //  we create the rectangle with the data
      
                 }
               else
                  ObjectMove(ChartID(),ob_alcistas[i].name,1,Time[0],ob_alcistas[i].price2);     //on the contrary, if the object exists, the only thing we will do is update it to the current time using anchor point 1
              }
           }
        }
      
      // Draw all order blocks from the orderBlocks array
      for(int i = 0; i < ArraySize(ob_bajistas); i++)
        {
         datetime mitigadoTime = esOb_mitigado_array_bajista(ob_bajistas[i],ob_bajistas[i].time1);
      
         if(ob_bajistas[i].mitigated == false)
           {
      
            if(mitigadoTime == 0)
              {
      
               if(ObjectFind(ChartID(),ob_bajistas[i].name) < 0)
                 {
                  RectangleCreate(ChartID(), ob_bajistas[i].name,0, ob_bajistas[i].time1, ob_bajistas[i].price1,
                                  Time[0], ob_bajistas[i].price2,Color_Order_Block_Bajista,Witdth_order_block,Fill_order_block,Back_order_block,STYLE_SOLID);
      
                 }
               else
                  ObjectMove(ChartID(),ob_bajistas[i].name,1,Time[0],ob_bajistas[i].price2);
      
              }
           }
      
        }
      //+------------------------------------------------------------------+
      
      • オーダーブロックの検出はforループ内で実行され、次の条件で実行されます。
      new_vela == true
      • ループの外側に矩形が描画されますが、次の条件も適用されます。
      new_vela == true

      2.5:オーダーブロックのミティゲーションとオブジェクトの削除に関するアラートの実装


      このセクションでは、オーダーブロックがミティゲーションされたときにアラートを実装する方法と、冒頭で述べた関数を作成する方法について説明します。

      Eliminar_Objetos()

      まず、論理の定義から始めましょう。

      1. 次の関数を呼び出す必要があります。

      esOb_mitigado_array_bajista  

      esOb_mitigado_array_alcista

      オーダーブロックがミティゲーションされたことを検出すると、ミティゲーションローソク足の時間を返し、オーダーブロックのステータスを true に設定します。これは、オーダーブロックがミティゲーションされていることと同等です。

      したがって、オーダーブロックがミティゲーションされているかどうかを確認するには、その状態を使用します。

      mitigated

      ここで、オーダーブロックの構造体を見ると、価格、時間、状態、名前があることがわかります。

      struct OrderBlocks
      {
       datetime time1;
       double price1;
       double price2;
       string name;
       bool mitigated;
       
      };
      

       この構造体から、アラートに関する次の2つの変数に特に注目します。

       string name;
       bool mitigated;
      

      • mitigated:このブール変数は、オーダーブロックがミティゲーションされたかどうかを知らせます。
      • name:このパラメータを使用して、ミティゲーションされたオーダーブロックが以前にミティゲーションされたかどうかを確認します。
      「mitigated」が一度有効になると、そのオーダーブロックは常にミティゲーションされた状態になります。したがって、インジケーターが何度もアラートを出さないようにするために、フィルターを設ける必要があります。このフィルターは、オーダーブロックの名前を使用します。

      ここで必要なのは以下の通りです。

      • 2つの配列:これらは 文字列型 で、動的配列とします。ミティゲーションされたオーダーブロックが増えるにつれてサイズを更新していく必要があるためです。
      • 文字列配列にエントリを追加する関数
      • 指定されたオーダーブロック名(文字列)が、すでに配列内に存在しているかをチェックする関数

      さて、これらの不足していた関数と配列を統合しました。

      • プログラムのグローバルセクションに移動し、次のように記述します。

      string pricetwo_eliminados_oba[];
      string pricetwo_eliminados_obb[];
      

      これらが必要な配列になります。

      次に、次の関数を作成します。

        bool Es_Eliminado_PriceTwo(string pName_ob , string &pArray_price_two_eliminados[])
        {
         bool a = false; // we create the variable "a" and initialize it to false
           for(int i = 0 ; i < ArraySize(pArray_price_two_eliminados) ; i++) // we traverse all the indices of the array passed as a reference
           {
            if(pName_ob == pArray_price_two_eliminados[i]) // we will compare all the positions in the array with the variable "pName_ob"
            { // if the comparison is identical the variable "a" becomes true
             a = true; 
              break; // we exit the loop
            } 
           } 
        return a; //we return the value of "a"
       }   
       
       //function to add values and assign a new size to the array passed by reference 
       void Agregar_Index_Array_1(string &array[], string pValor_Aagregar) {
          int num_array = ArraySize(array);
          if (ArrayResize(array, num_array + 1) == num_array + 1) {
              array[num_array] = pValor_Aagregar;
          } else {
            Print("Error resizing array");
          }
      }
      
      • これらの関数は、オーダーブロックがミティゲーションされた際に、すでにミティゲーションされていないかを確認するのに役立ちます。これにより、大量のアラートスパムを防ぐことができます。

      次に、OnCalculate内の部分に移動して、アラートの実装を完成させます。

      • オーダーブロックの配列のすべてのインデックスに対してループを作成する
      • if文でオーダーブロックの状態を確認し、さらにそのオーダーブロックの名前が、ミティゲーションされたオーダーブロックの名前を格納している文字列配列に含まれていないことを検証する
      • これらの条件がすべて真の場合、オーダーブロックがミティゲーションされたことをユーザーにアラートで通知する
      • 重複を避けるために、オーダーブロックの名前を文字列配列に追加する
      • ループをbreakで抜けて終了する
      // Loop through the order blocks
      for(int i = 0; i < ArraySize(ob_alcistas); i++)
        {
         if(ob_alcistas[i].mitigated == true && Es_Eliminado_PriceTwo(ob_alcistas[i].name, pricetwo_eliminados_oba) == false)
           {
            Alert("El order block alcista esta siendo mitigado: ", TimeToString(ob_alcistas[i].time1));
      
            Agregar_Index_Array_1(pricetwo_eliminados_oba, ob_alcistas[i].name);
      
            break;
           }
        }
      
      // Loop through the order blocks
      for(int i = 0; i < ArraySize(ob_bajistas); i++)
        {
         if(ob_bajistas[i].mitigated == true && Es_Eliminado_PriceTwo(ob_bajistas[i].name, pricetwo_eliminados_obb) == false)
           {
      
            Alert("El order block bajista esta siendo mitigado: ", TimeToString(ob_bajistas[i].time1));
      
            Agregar_Index_Array_1(pricetwo_eliminados_obb, ob_bajistas[i].name);
      
            break;
           }
        }
      //+------------------------------------------------------------------+
      

      アラートが完了したら、オブジェクトの削除に移りましょう。

      bool  ObjectDelete(
         long    chart_id,     // chart identifier
         string  name          // object name
         );
      

      次に現在のチャートIDが必要です。

      ChartID()

      オーダーブロック名も必要です。

      name

      これらを念頭に置いて、すべてのオーダーブロックの位置をループし、次を呼び出す必要があります。

      ObjectDelete() 

      作成したすべてのオブジェクトを削除するには、以下を使用します。

        void Eliminar_Objetos()
        {
        
        for(int i = 0 ; i < ArraySize(ob_alcistas) ; i++) // we iterate through the array of bullish order blocks 
        {
         ObjectDelete(ChartID(),ob_alcistas[i].name); // we delete the object using the name of the order block
        }
        for(int n = 0 ; n < ArraySize(ob_bajistas) ; n++) // we iterate through the array of bearish order blocks 
        {
        ObjectDelete(ChartID(),ob_bajistas[n].name);  // we delete the object using the name of the order block
        }
        
       }
      

      この時点でインジケーターの作業は完了していますが、関数を変更する必要があります。

      OnInit() 

      では、追加した新しい変数と配列が適切に処理されることを確認する必要があります。

      また 

      OnDeinit()

      を変更します。インジケーターが使用したリソースを解放するために、グラフィカルオブジェクトを削除し、オーダーブロックのデータが格納されている動的配列のメモリも解放します。

      また、ATRハンドラを正しく初期化していた場合は、それを適切に解放することも重要です。 これをおこなわないと、メモリリークやインジケーター終了時のエラーが発生する可能性があります。以下のようにしておこないます。

      if(atr_i != INVALID_HANDLE) 
          IndicatorRelease(atr_i);
      

      最終的に、

      OnDeinit()

      は次のようになります。

       void OnDeinit(const int reason)
        {
      //---
        Eliminar_Objetos();
      
        ArrayFree(ob_bajistas);
        ArrayFree(ob_alcistas);
        ArrayFree(pricetwo_eliminados_oba);
        ArrayFree(pricetwo_eliminados_obb); 
      
        if(atr_i  != INVALID_HANDLE) IndicatorRelease(atr_i );
      
        }
      
      //---
      
      int OnInit()
        {
      //--- indicator buffers mapping
        tiempo_ultima_vela = 0;
           atr_i = iATR(_Symbol,PERIOD_CURRENT,14);
           
           if(Rango_universal_busqueda < 40)
           {
            Print("Search range too small");
            return (INIT_PARAMETERS_INCORRECT);
           }  
           
            if( atr_i== INVALID_HANDLE)
           {
            Print("Error copying data for indicators");  
            return(INIT_FAILED);
           }
        
        ArrayResize(ob_bajistas,0);
        ArrayResize(ob_alcistas,0);
        ArrayResize(pricetwo_eliminados_oba,0);
        ArrayResize(pricetwo_eliminados_obb,0);
         
      //---
         return(INIT_SUCCEEDED);
        }
      


      3.0:結論 

      この記事では、次のことを学びました。

      • スマートマネーおよびインナーサークルトレーダーの概念に基づいたインジケーターの作成
      • アラートの設定
      • チャート上に矩形を描画する方法

      最終結果

                 オーダーブロックの例GIF

      ここまで読んでくださった方、本当にありがとうございます。高度な取引概念を学ぶことに対するあなたの熱意と忍耐に、心から感謝します。プログラミングには多くの可能性があり、期間中の高値・安値のような基本的な概念から、インテリジェントな自動売買ロボットの作成まで幅広く応用できます。この魅力的なプログラミングの世界をさらに深く探求するために、他の記事もぜひご覧ください。

      また、この記事を必要としているかもしれない方と共有していただけると嬉しいです。

      読んでいただいた感謝の気持ちとして、この記事で取り上げたインジケーターの全コードを含むファイルを用意しました。さらに、本連載はこれで終わりではありません。今後も続編を開発していく予定です。以下は、今後予定している記事の構成例です。

      第2回

      • このインジケーターにバッファとプロット(売買シグナル用)を統合する
      • シグナルが出たときの利確(TP)と損切り(SL)のレベルを実装(TP用に2本、SL用に2本のライン)する
      • オーダーブロックに基づく高度なオーダーブロック検出手法を導入する

      もし十分なサポートがあれば、第3回として、これまでに開発したインジケーターバッファを使用してエキスパートアドバイザー(EA)を構築する予定です。

      MetaQuotes Ltdによりスペイン語から翻訳されました。
      元の記事: https://www.mql5.com/es/articles/15899

      添付されたファイル |
      最後のコメント | ディスカッションに移動 (18)
      CapeCoddah
      CapeCoddah | 14 7月 2025 において 10:56

      こんにちは、

      ストラテジーテスターで 私のEA、EURUSD H4 1/1/2025-2/1/2025をテストしていたところ、終了時にブロックオーダーインジケーターに2つの問題があることを確認しました。

      1つ目は、テストウィンドウの外にある2/3/2025のブロックオーダーを選択してしまうこと、2つ目は、チャートシフト領域にブロックテキストを配置してしまうことです。


      お楽しみください。


      ケープ・コッダ

      CapeCoddah
      CapeCoddah | 16 7月 2025 において 10:34

      最初のインジケータの英語翻訳バージョンです。 私は、あなたの多くのコードコメントを英語で理解する必要があると判断し、DeepLが私に感銘を与えなかったため、Google Translateを再考しました。 私はまず、Googleが//行コメントを翻訳できるように、すべての//コメントを#/#に変更しました。そして、Translateに入力するために、テキストファイルをMS Word文書に変換しました。翻訳後、私は新しいドキュメントを開き、テキストファイルとして保存し、名前を変更し、新しいソースの構文解析を開始しました。 Translateは作業の90%を行ったと推定していますが、手動で変換が必要なスペースや文字を追加しました。 日間の作業の後、それはエラーなしでコンパイルされました。驚いたことに、 それは最初の試みで動作しました!1000バーのオリジナルインジケーターと比較してみましたが、全く同じでした。

      Niquel Mendoza
      Niquel Mendoza | 16 7月 2025 において 15:07
      CapeCoddah # :

      最初のインジケータの英語翻訳バージョンです。 私は、あなたの多くのコードコメントを英語で理解する必要があると判断し、DeepLが私に感銘を与えなかったため、Google Translateを再考しました。 私はまず、Googleが//行コメントを翻訳できるように、すべての//コメントを#/#に変更しました。そして、Translateに入力するために、テキストファイルをMS Word文書に変換しました。翻訳後、私は新しいドキュメントを開き、テキストファイルとして保存し、名前を変更し、新しいソースの構文解析を開始しました。 Translateは作業の90%を行ったと推定していますが、手動で変換が必要なスペースや文字を追加しました。 日間の作業の後、それはエラーなしでコンパイルされました。 驚いたことに、 それは最初の試みで動作しました!1000バーのオリジナルインジケーターと比較してみましたが、全く同じでした。

      CapeCoddahさん、こんにちは。私はいくつかのプロジェクトに携わっており、あなたのお役に立てる時間が限られていました。しかし今日、このインジケータの改良版の開発に時間を割くことができます。以下にコードを共有します。
      CapeCoddah
      CapeCoddah | 18 7月 2025 において 12:48

      残念ながら、このインジケータは構造的に欠陥があり、取引には役立たないようです。なぜなら、このインジケータは、以下のコードで太字で強調表示されているように、計算時に未知の将来の変数に対して計算を行っているからです。

      for( int i = Universal_search_range ; i > 5 ; i--) {
      //エラーチェック
      if( i + 3 > ArraySize(highArray) || i + 3 > ArraySize(atr))
      continue ;
      if( i < 0) continue;

      // ローソク足のインデックスを更新
      one_candle = i ;// 中央のローソク足
      candle_behind_two = i +2;
      candle_behind_one = one_candle +1;
      two_candle = one_candle - 1;
      three_candle = one_candle - 2;
      four_candle = one_candle -3
      ;

      // 直前のローソク足の平均出来高を計算
      body1 = MathAbs(closeArray[one_candle] - openArray[one_candle]);
      body2 = MathAbs(closeArray[two_candle] - openArray[two_candle]);
      body3 = MathAbs(closeArray[three_candle] - openArray[three_candle]);

      Niquel Mendoza
      Niquel Mendoza | 27 7月 2025 において 13:23
      CapeCoddah #:

      残念ながら、このインジケーターは構造的な欠陥があり、取引には役立たないようです。なぜなら、このインジケーターは、以下のコードで太字で強調されているように、計算時に未知の将来の変数に対して計算を行っているからです。

      for( int i = Universal_search_range ; i > 5 ; i--) {
      //エラーチェック
      if( i + 3 > ArraySize(highArray) || i + 3 > ArraySize(atr))
      continue ;
      if( i < 0) continue;

      // ローソク足のインデックスを更新
      one_candle = i ;// 中央のローソク足
      candle_behind_two = i +2;
      candle_behind_one = one_candle +1;
      two_candle = one_candle - 1;
      three_candle = one_candle - 2;
      four_candle = one_candle -3
      ;

      // 直前のローソク足の平均出来高を計算
      body1 = MathAbs(closeArray[one_candle] - openArray[one_candle]);
      body2 = MathAbs(closeArray[two_candle] - openArray[two_candle]);
      body3 = MathAbs(closeArray[three_candle] - openArray[three_candle]);

      CapeCoddahさん、こんにちは。例えば、このインジケーターは配列を直列に使ってすべての計算を行うので(一般的ではありませんが、通常は直列にしないで行います)、そうではないと思います、何が行われている "Universal_search_range "ローソク足(シリーズではローソク足0が最新であることを覚えておいてください)からローソク足6に移動するので、私は将来のローソク足が使用されていることはありませんし、それがそうであった場合、two_candleまたは他のインデックスは、範囲外の原因となる0未満の値になります。だから、ローソク足four_candle = one_candle - 3は、ループがi = 6の次にfour_candle = 3で終了した場合に0に最も近いだろう、だから、現在のローソク足が0であることを考慮に入れて、私は将来のローソク足を使用していないことを言うことができます。このネーミングは分かりにくいと思われるかもしれませんが、注文ブロックを取得する際に、one_velaが中心のローソク足と同じであることを考えると、その方が分かりやすかったので、このようにしました。そのため、強い動きを探している場合は、それに続くローソク足を評価する(直列で言えば、これは引き算になる)。

      MQL5におけるSQLiteの機能:銘柄とマジックナンバー別の取引統計を表示するダッシュボード MQL5におけるSQLiteの機能:銘柄とマジックナンバー別の取引統計を表示するダッシュボード
      この記事では、口座別、銘柄別、および取引戦略別に取引統計をダッシュボードに表示するインジケーターの作成について考察します。コードは、ドキュメントおよびデータベース操作に関する記事の例に基づいて実装します。
      取引におけるニューラルネットワーク:NAFSによるノード依存型グラフ表現 取引におけるニューラルネットワーク:NAFSによるノード依存型グラフ表現
      NAFS (Node-Adaptive Feature Smoothing)手法を紹介します。これは、パラメータの学習を必要としない非パラメトリックなノード表現生成手法です。NAFSは、各ノードの近傍ノードに基づいて特徴量を抽出し、それらを適応的に統合することで最終的なノード表現を生成します。
      リプレイシステムの開発(第72回):異例のコミュニケーション(I) リプレイシステムの開発(第72回):異例のコミュニケーション(I)
      私たちが本日作成する内容は、理解が難しいものになるでしょう。したがって本稿では、初期段階についてのみ説明します。この段階は次のステップに進むための重要な前提条件となるため、ぜひ注意深く読んでください。この資料の目的はあくまで学習にあります。提示された概念を実際に応用するのではなく、あくまで理解・習得することが目的です。
      取引におけるニューラルネットワーク:対照パターンTransformer(最終回) 取引におけるニューラルネットワーク:対照パターンTransformer(最終回)
      本連載の前回の記事では、Atom-Motif Contrastive Transformer (AMCT)フレームワークについて取り上げました。これは、対照学習を用いて、基本要素から複雑な構造に至るまでのあらゆるレベルで重要なパターンを発見することを目的とした手法です。この記事では、MQL5を用いたAMCTアプローチの実装を引き続き解説していきます。