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

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

MetaTrader 5 |
117 1
Niquel Mendoza
Niquel Mendoza
  1. はじめに



はじめに

MQL5に関する記事へようこそ。本記事では、インジケーターにバッファとエントリーシグナルを追加し、自動売買戦略で活用するための主要な機能を完成させることに焦点を当てます。

本連載を初めてご覧になる方は、まず前回の記事をご確認ください。前回は、インジケーターの基本構造を一から作成し、基礎的な概念について解説しました。


板情報に基づく注文ブロック検出

本記事で紹介する、板情報を利用したオーダーブロック検出ロジックは以下の手順で構成されています。

アルゴリズム            

  1. 配列の作成:各ローソク足における出来高を保存するために、2つの配列を作成します。これにより、出来高データを効率的に整理、分析できるようになります。
  2. 板情報データの収集:イベント内。                                
void  OnBookEvent( )       

OnBookEvent()イベント内で市場の板情報の変化を検出し、新しい出来高データをリアルタイムで記録します。

      3. オーダーブロックの判定ルール:出来高データを配列に保存した後、プライスアクションのルールを併用してオーダーブロックを検証します。

板情報を用いたオーダーブロックの識別ルール

これまでのインジケーターでは、一定のローソク足範囲(x本分)内でオーダーブロックを探索していました。しかし、本記事で扱う板情報を利用した手法では、より限定的に「3本目のローソク足」(0が現在足とした場合)に注目します。

ルール 上昇オーダーブロック 下降オーダーブロック 
ローソク足3での出来高ピーク 足3の買いボリュームが、足2および足4の合計買い・売りボリュームを一定比率以上上回ること 足3の売りボリュームが、足2および足4の合計買い・売りボリュームを一定比率以上上回ること
3本連続のローソク足 連続する3本が陽線であること
(足1・2・3)
連続する3本が陰線であること
(足1・2・3)
ローソク足3の実体条件 足2の安値が、足3の実体の半分より上であること 足2の高値が、足3の実体の半分より下であること
ローソク足3の高値/安値 足3の高値が、足2の終値より下であること 足3の安値が、足2の終値より上であること

これらのルールにより、次のことが保証されます。

  • 売買の不均衡:特定のローソク足において、買いまたは売りの出来高が前後のローソク足を一定比率で上回る「明確な出来高の偏り」を確認します。
  • 不均衡時の実体コントロール:需給バランスの崩れによる未約定注文(オーダーブロック)が、次の足で吸収されていないことを確認し、ブロックの有効性を検証します。
  • 強い上昇または下降の動き:これらのパターンが、明確なトレンド方向への勢いを示すものかを確認し、プライスアクション上の強い不均衡を特定します。

これらの条件を満たすことで、板情報をもとにしたオーダーブロック検出ロジックを、MQL5のコードに変換して実装することが可能になります。


「注文書」イベントの初期化と完了、および配列の作成

配列の作成

オーダーブックを使用する前に、出来高データを格納する動的配列を作成する必要があります。これらの配列は次の型になります。

long

これらの配列は、それぞれ買いと売りの出来高を格納するために使用されます。

  1. プログラムのグローバルセクションに移動し、次のように動的配列を宣言します。

long buy_volume[];
long sell_volume[];

      2. 次に、OnInitイベント内で配列のサイズを1に変更し、各配列のインデックス0に値0を代入します。

  ArrayResize(buy_volume,1);
  ArrayResize(sell_volume,1);
  buy_volume[0] = 0.0;
  sell_volume[0] = 0.0;

板情報イベントの初期化と終了

板情報を開始する前に、この機能が利用可能かどうかを示すグローバル変数を作成します。 これにより、次のようなエラーを回避することができます。

INIT_FAILED
すべてのブローカーがすべての銘柄に対してマーケットデプスで取引出来高を提供しているわけではありません。この方法により、インジケーターはブローカーの提供状況に完全には依存しないようになります。

  • 取引したい銘柄が板情報に対応しているかどうかを確認するには、以下の手順を実行してください。

1. チャート左上の該当ボックスをクリックします。

2. 銘柄が板情報の出来高を利用可能かどうかを確認します。対応している場合は、以下のような確認が表示されます

板情報対応銘柄の例

板情報2

非対応銘柄の例:

ETHUSD板情報

このように、板情報の出来高はすべての銘柄で利用できるとは限らず、ブローカーに依存します。

板情報の初期化と完了に移りましょう。

1. グローバル制御変数

まず、板情報の利用可否を示すグローバルブール変数を定義します。

bool use_market_book = true; //true by default

この変数はデフォルトでtrueですが、初期化に失敗した場合はfalseに変更されます。

2. 板情報の初期化

板情報を初期化するには、次の関数を使用します。 

MarketBookAdd()

この関数は、指定した銘柄の板情報を開きます。引数には現在の銘柄を指定します。

_Symbol

OnInitイベント内で、初期化が成功したかどうかを確認します。

 if(!MarketBookAdd(_Symbol)) //Verify initialization of the order book for the current symbol
     {
      Print("Error Open Market Book: ", _Symbol, " LastError: ", _LastError); //Print error in case of failure
      use_market_book = false; //Mark use_market_book as false if initialization fails
     }

3. 板情報の終了

OnDeinitイベント内で、次の関数を使用して板情報を解放します。

 MarketBookRelease()

その後、クローズを確認し、結果に応じてメッセージを出力します。

//---
   if(MarketBookRelease(_Symbol)) //Verify if closure was successful
     Print("Order book successfully closed for: " , _Symbol); //Print success message if so
   else
     Print("Order book closed with errors for: " , _Symbol , "   Last error: " , GetLastError()); //Print error message with code if not


配列での取引量板情報データの収集

板情報の初期化が完了したら、関連データの収集を開始できます。このため、板情報に変更が発生するたびに呼び出されるOnBookEventイベントを作成します。

  1. OnBookEventの作成

void OnBookEvent(const string& symbol)
     2.  銘柄と板情報の可用性の確認
 if(symbol !=_Symbol || use_market_book == false)
      return; 
// Exit the event if conditions are not met

この条件チェックにより、完全なOnBookEvent関数は以下のように構成できます。   

void OnBookEvent(const string& symbol)
  {
   if(symbol !=_Symbol || use_market_book == false)
      return;
// Define array to store Market Book data
   MqlBookInfo book_info[];

// Retrieve Market Book data
   bool book_count = MarketBookGet(_Symbol,book_info);

// Verify if data was successfully obtained
   if(book_count == true)
     {
      // Iterate through Market Book data
      for(int i = 0; i < ArraySize(book_info); i++)
        {
         // Check if the record is a buy order (BID)
         if(book_info[i].type == BOOK_TYPE_BUY  || book_info[i].type ==  BOOK_TYPE_BUY_MARKET)
           {
           
            buy_volume[0] += book_info[i].volume;
           }
         // Check if the record is a sell order (ASK)
         if(book_info[i].type == BOOK_TYPE_SELL || book_info[i].type == BOOK_TYPE_SELL_MARKET)
           {
            sell_volume[0] += book_info[i].volume;
           }
        }
     }
   else
     {
      Print("No Market Book data retrieved.");
     }
  }

コードの説明

  • 出来高検索:板情報に変化が生じるたびに、OnBookEventは最新の注文データを取得し、出来高を集計します。
  • 配列の更新:買い注文の出来高はbuy_volume配列のインデックス0に、売り注文の出来高はsell_volume配列のインデックス0に加算されます。

新しいローソク足ごとに板情報の出来高を配列に蓄積し、さらに30要素などのローリング履歴を保持できるようにするためには、以下の調整が必要です。

1. 新しいローソク足の検証とカウンタの確認(1より大きいこと)

プログラム起動直後の誤検出を避け、配列が新しいローソク足の開始時にのみ更新される(少なくとも1回の開始後)ようにするため、カウンタ変数とnew_velaを組み合わせたチェックを実装します。これにより、配列の更新が実際に新しい情報が利用可能な場合にのみおこなわれるようになります。

静的変数の宣言
counterはOnCalculate関数の呼び出し間で値を保持できるよう、static変数として宣言します。new_vela変数は、新しいローソク足が開始したかどうかを示します。
static int counter = 0;

新しいローソク足およびカウンタ検証条件
counterが1より大きく、new_velaがtrueであり、かつuse_market_bookがtrueの場合にのみ、配列のサイズ変更と要素のシフトを実行します。これにより、誤った早期のサイズ変更を防止し、配列が有効なデータを持ち、マーケットブックが現在の銘柄に対して取引出来高を提供している場合にのみ更新されるようになります。

if(counter > 1 && new_vela == true && use_market_book == true)

カウンタ更新
新しいローソク足が検出されるたびに、カウンタを1増加させます。

counter++;

2. 配列サイズの制御

配列が最大サイズ(30要素)を超えないように確認します。もし超えた場合は、配列を30にリサイズし、最も古い要素を削除します。

if(ArraySize(buy_volume) >= 30)
{
   ArrayResize(buy_volume, 30); // Keep buy_volume size at 30
   ArrayResize(sell_volume, 30); // Keep sell_volume size at 30
}

3. 新しい値に合わせたサイズ変更

新しい出来高データを格納するために、配列のサイズを1つ拡張します。

ArrayResize(buy_volume, ArraySize(buy_volume) + 1);
ArrayResize(sell_volume, ArraySize(sell_volume) + 1);

4. 要素のシフト

すべての配列要素を1つずつ前方に移動します。これにより、最新のデータが常にインデックス0に保存され、古い値は高いインデックスにシフトされます。

for(int i = ArraySize(buy_volume) - 1; i > 0; i--)
{
   buy_volume[i] = buy_volume[i - 1];
   sell_volume[i] = sell_volume[i - 1];
}

5. 出来高の確認

配列のインデックス1の位置にある買いと売りの出来高を出力して、前回のローソク足に記録された出来高を確認します。

Print("Buy volume of the last candle: ", buy_volume[1]);
Print("Sell volume of the last candle: ", sell_volume[1]);

6. 出来高のリセット

両方の配列のインデックス0を0にリセットし、新しいローソク足の出来高を蓄積し始めるようにします。

buy_volume[0] = 0;
sell_volume[0] = 0;

7. 不整合なマーケットブックデータによるエラー防止の条件

追加の安全策として、buy_volumeとsell_volumeの最近の位置(インデックス3、2、1)の値がすべて0である場合、use_market_bookを自動的に無効化します。この調整は、銘柄がライブ取引でマーケットブックデータを持っているように見えても、ストラテジーテスターでは更新がおこなわれず、配列が正しく埋まらない場合があるため必要です。これにより、ゼロが記録され、インジケーターが誤った情報を扱う可能性があります。

この検証により、インジケーターが無効なデータを処理しないようにし、マーケットブックに有効な値が含まれている場合にのみuse_market_bookが適用されるようにします。

if(ArraySize(buy_volume) > 4 && ArraySize(sell_volume) > 4)
        {
         if(buy_volume[3] == 0 && sell_volume[3] == 0 &&  buy_volume[2] == 0 && sell_volume[2] == 0 &&  buy_volume[1] == 0 && sell_volume[1] == 0)  use_market_book = false;       
        }     

統合コードスニペット

if(counter > 1 && new_vela == true && use_market_book == true)
     {
      if(ArraySize(buy_volume) > 4 && ArraySize(sell_volume) > 4)
        {
         if(buy_volume[3] == 0 && sell_volume[3] == 0 &&  buy_volume[2] == 0 && sell_volume[2] == 0 &&  buy_volume[1] == 0 && sell_volume[1] == 0)  use_market_book = false;       
        }
      
       // If array size is greater than or equal to 30, resize to maintain a fixed length
     if(ArraySize(buy_volume) >= 30)
      {
      ArrayResize(buy_volume, 30); // Ensure buy_volume does not exceed 30 elements
      ArrayResize(sell_volume, 30); // Ensure sell_volume does not exceed 30 elements
      }   
  
      ArrayResize(buy_volume,ArraySize(buy_volume)+1);
      ArrayResize(sell_volume,ArraySize(sell_volume)+1);

      for(int i = ArraySize(buy_volume) - 1; i > 0; i--)
        {
         buy_volume[i] = buy_volume[i - 1];
         sell_volume[i] = sell_volume[i - 1];
        }

      // Reset volumes at index 0 to begin accumulating for the new candlestick 
      buy_volume[0] = 0;
      sell_volume[0] = 0;
     }


板情報を利用したオーダーブロック検出の戦略

この戦略は、これまで使用してきたロジックと同じ考え方に従いますが、1つの重要な違いがあります。それは、ループを使用して繰り返し処理をおこなう代わりに、ローソク足3に対して直接チェックをおこなう点です。基本的なロジックは同じであり、特定の条件を確認し、オーダーブロックの種類に応じて最も関連性の高いローソク足を特定し、対応する値を構造体に代入して、オーダーブロックを配列に追加します。ここでは同じプロセスをより簡略化した形で適用します。

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

OrderBlocks newVela_Order_block_Book_bajista;
OrderBlocks newVela_Order_block_Book;

1. 初期条件

最初に、buy_volumeおよびsell_volume配列が少なくとも5つの要素を持っているかどうかを確認します。これにより、分析に十分な履歴データが存在することを保証します。また、板情報を処理するためにuse_market_bookが有効になっていることを確認します。

if(ArraySize(buy_volume) >= 5 && ArraySize(sell_volume) >= 5 && use_market_book == true)

2. 制御変数の定義

特定の出来高条件が満たされているかどうかを示す変数case_bookを定義します。比率「ratio」は1.4に設定されており、買い出来高の顕著な増加を検出するための比較係数として機能します。

bool case_book = false;
double ratio = 1.4;

3. 買い出来高の条件(Case Book)

ここでは、インデックス3の買い出来高が、インデックス2および4の買い・売り出来高よりもratio倍以上大きいかどうかを確認します。この条件が満たされると、case_bookが有効化されます。

強気の場合

if(buy_volume[3] > buy_volume[4] * ratio && buy_volume[3] > buy_volume[2] * ratio &&
   buy_volume[3] > sell_volume[4] * ratio && buy_volume[3] > sell_volume[2] * ratio)
{
    case_book = true;
}
弱気の場合
if(sell_volume[3] > buy_volume[4]*ratio && sell_volume[3] > buy_volume[2]*ratio &&
sell_volume[3] > sell_volume[4]*ratio && sell_volume[3] > sell_volume[2]*ratio)
{
case_book = true;
}

4. ローソク足実体の計算

インデックス3のローソク足の実体(body_tree)を、始値から終値を引くことで計算します。

double body_tree = closeArray[3] - openArray[3]; 
double body_tree = openArray[3] - closeArray[3];

5. 強気セットアップの価格条件の確認

前述の条件(上の表参照)を評価します。

強気の場合

if(lowArray[2] > ((body_tree * 0.5) + openArray[3]) && highArray[3] < closeArray[2] &&
   closeArray[3] > openArray[3] && closeArray[2] > openArray[2] && closeArray[1] > openArray[1])

弱気の場合

if(highArray[2] < (openArray[3]-(body_tree * 0.5)) && lowArray[3] > closeArray[2] &&
            closeArray[3] < openArray[3] && closeArray[2] < openArray[2] && closeArray[1] < openArray[1])

6. 直近の強気ローソク足の特定

関数「FindFurthestAlcista」を呼び出し、インデックス3から20本分の範囲内で最も遠い強気のローソク足を検索します。これにより、強い上昇セットアップの基準となる参照ローソク足を特定します。強気ローソク足が見つかった場合、そのインデックスは0より大きくなり、処理を続行できます。

強気の場合

int furthestAlcista = FindFurthestAlcista(Time[3], 20);
if(furthestAlcista > 0)

7. オーダーブロックへの値の代入

すべての条件が満たされた場合、識別されたローソク足の値を使用して、オーダーブロック(newVela_Order_block_BookまたはnewVela_Order_block_Book_bearish)を定義します。

強気の場合

Print("Case Book Found");
datetime time1 = Time[furthestAlcista]; 
double price2 = openArray[furthestAlcista];
double price1 = lowArray[furthestAlcista]; 

//Assign the above variables to the structure
newVela_Order_block_Book.price1 = price1;
newVela_Order_block_Book.time1 = time1;
newVela_Order_block_Book.price2 = price2;
newVela_Order_block_Book.mitigated = false;
newVela_Order_block_Book.name = "Bullish Order Block Book " + TimeToString(newVela_Order_block_Book.time1);
AddIndexToArray_alcistas(newVela_Order_block_Book);

弱気の場合

Print("Case Book Found");
datetime time1 = Time[furthestBajista];
double price1 = closeArray[furthestBajista];
double price2 = lowArray[furthestBajista];

//Assign the above variables to the structure
newVela_Order_block_Book_bajista.price1 = price1;
newVela_Order_block_Book_bajista.time1 = time1;
newVela_Order_block_Book_bajista.price2 = price2;
newVela_Order_block_Book_bajista.mitigated = false;
newVela_Order_block_Book_bajista.name = "Order Block Bajista Book " + TimeToString(newVela_Order_block_Book_bajista.time1);
AddIndexToArray_bajistas(newVela_Order_block_Book_bajista);

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

if(ArraySize(buy_volume) >= 5 && ArraySize(sell_volume) >= 5 && use_market_book == true)
  {

   bool case_book = false;
   double ratio = 1.4;

   if(sell_volume[3] > buy_volume[4]*ratio && sell_volume[3] > buy_volume[2]*ratio &&
      sell_volume[3] > sell_volume[4]*ratio && sell_volume[3] > sell_volume[2]*ratio)
     {
      case_book = true;
     }
   double body_tree =   openArray[3] - closeArray[3];

   if(highArray[2] < (openArray[3]-(body_tree * 0.5)) && lowArray[3] > closeArray[2] &&
      closeArray[3] < openArray[3] && closeArray[2] < openArray[2] && closeArray[1] < openArray[1])
     {
      int furthestBajista = FindFurthestBajista(Time[3],20); //We call the "FindFurthestAlcista" function to find out if there are bullish candlesticks before "one candle"
      if(furthestBajista  > 0) // Whether or not there is a furthest Bullish candle, it will be greater than 0 since if there is none, the previous candlestick returns to "one candle".
        {
         Print("Case Book Found");
         datetime time1 = Time[furthestBajista];
         double price1 = closeArray[furthestBajista];
         double price2 = lowArray[furthestBajista];

         //Assign the above variables to the structure
         newVela_Order_block_Book_bajista.price1 = price1;
         newVela_Order_block_Book_bajista.time1 = time1;
         newVela_Order_block_Book_bajista.price2 = price2;
         newVela_Order_block_Book_bajista.mitigated = false;
         newVela_Order_block_Book_bajista.name = "Order Block Bajista Book " + TimeToString(newVela_Order_block_Book_bajista.time1);

         AddIndexToArray_bajistas(newVela_Order_block_Book_bajista);

        }
     }
  }
//--------------------    Bullish   -------------------- 

if(ArraySize(buy_volume) >= 5 && ArraySize(sell_volume) >= 5 && use_market_book == true)
  {

   bool case_book = false;
   double ratio = 1.4;


   if(buy_volume[3] > buy_volume[4]*ratio && buy_volume[3] > buy_volume[2]*ratio &&
      buy_volume[3] > sell_volume[4]*ratio && buy_volume[3] > sell_volume[2]*ratio)
     {
      case_book = true;
     }
   double body_tree =  closeArray[3] - openArray[3];

   if(lowArray[2] > ((body_tree * 0.5)+openArray[3]) && highArray[3] < closeArray[2] &&
      closeArray[3] > openArray[3] && closeArray[2] > openArray[2] && closeArray[1] > openArray[1])
     {
      int furthestAlcista = FindFurthestAlcista(Time[3],20); //We call the "FindFurthestAlcista" function to find out if there are bullish candlessticks before "one candle"
      if(furthestAlcista > 0) // Whether or not there is a furthest Bullish candle, it will be greater than 0 since if there is none, the previous candlestick returns to "one candle".
        {
         Print("Case Book Found");
         datetime time1 = Time[furthestAlcista]; //let's assign the index time of furthestAlcista to the variable time1
         double price2 = openArray[furthestAlcista]; //let's assign the open of furthestAlcista as price 2 (remember that we draw it on a bearish candlestick most of the time)
         double price1 = lowArray[furthestAlcista]; //let's assign the low of furthestAlcista as price 1

         //Assign the above variables to the structure
         newVela_Order_block_Book.price1 = price1;
         newVela_Order_block_Book.time1 = time1;
         newVela_Order_block_Book.price2 = price2;
         newVela_Order_block_Book.mitigated = false;
         newVela_Order_block_Book.name = "Bullish Order Block Book " + TimeToString(newVela_Order_block_Book.time1);

         AddIndexToArray_alcistas(newVela_Order_block_Book);

        }
     }
  }


インジケーターバッファの作成

MQL5でオーダーブロックインジケーターのバッファを作成し、設定するために、まず強気および弱気オーダーブロックの価格レベルを格納し表示するための2つのバッファと2つのプロットをグローバルに定義します。

1. バッファおよびプロットの宣言

プログラムのグローバルセクションで、オーダーブロックの価格データを格納するための2つのバッファを宣言します。さらに、チャート上にオーダーブロックを可視化するための2つのプロットを定義します。

#property  indicator_buffers 2
#property  indicator_plots 2
#property indicator_label1 "Bullish Order Block"
#property indicator_label2 "Bearish Order Block"

2. バッファ用の動的配列を作成する

強気および弱気オーダーブロックに対応する価格を格納するために、buyOrderBlockBufferとsellOrderBlockBufferという2つの動的配列を宣言します。これらの配列はインジケーターバッファにリンクされ、チャート上でオーダーブロックデータを視覚的に表示できるようにします。

//--- Define the buffers
double buyOrderBlockBuffer[];   // Buffer for bullish order blocks
double sellOrderBlockBuffer[];  // Buffer for bearish order blocks

説明

  • buyOrderBlockBuffer:強気オーダーブロックの価格レベルを格納し、価格がサポートを見つける可能性のあるポイントを表します。
  • sellOrderBlockBuffer:弱気オーダーブロックの価格レベルを格納し、価格がレジスタンスに遭遇する可能性のあるポイントを表します。


バッファを設定するためのOnInit関数の変更

このセクションでは、インジケーターバッファを設定するためにOnInit関数を調整し、強気および弱気オーダーブロックの配列をインジケーターバッファに割り当てます。これにより、インジケーターがチャート上にデータを正しく格納し表示できるようになります。

手順

1. SetIndexBufferを使用してデータバッファを割り当てる

OnInit内で、buyOrderBlockBufferおよびsellOrderBlockBuffer配列をSetIndexBuffer関数を使用してインジケーターバッファに割り当てます。これにより、配列がチャート上でデータを格納し表示できるようになります。

//--- Assign data buffers to the indicator
   SetIndexBuffer(0, buyOrderBlockBuffer, INDICATOR_DATA);
   SetIndexBuffer(1, sellOrderBlockBuffer, INDICATOR_DATA)

2. バッファを時系列として設定し、空の値で初期化する

データを時間の逆順(時系列のように)で表示するため、配列を時系列として設定します。また、実際の値が計算されるまで誤ったデータが表示されないよう、両方のバッファをEMPTY_VALUEで初期化します。

  ArraySetAsSeries(buyOrderBlockBuffer, true);
  ArraySetAsSeries(sellOrderBlockBuffer, true);
  ArrayFill(buyOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE
  ArrayFill(sellOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE


インジケーターへのバッファの実装(2)

このセクションでは、強気および弱気オーダーブロックの価格をインジケーターバッファに割り当てます。これらのバッファにより、各オーダーブロックの時間(time1)に対応するインデックスでデータを利用できるようになります。

1. 強気オーダーブロックの価格を割り当てる

ob_alcistas内で各強気ブロックを評価するループ内で、price2をbuyOrderBlockBufferに割り当てます。iBarShiftを使用して、time1がオーダーブロックの時間に一致するチャート上の正確なインデックスを取得します。

buyOrderBlockBuffer[iBarShift(_Symbol, _Period, ob_alcistas[i].time1)] = ob_alcistas[i].price2;

ここで、強気ブロックのprice2が対応するインデックスのbuyOrderBlockBufferに割り当てられ、バッファがチャート上でそのブロックの価格レベルを反映するようになります。

2. 弱気オーダーブロックの価格を割り当てる

同様に、ob_bajistas配列を走査し、各弱気ブロックのprice2を対応するインデックスにsellOrderBlockBufferへ設定します。

sellOrderBlockBuffer[iBarShift(_Symbol, _Period, ob_bajistas[i].time1)] = ob_bajistas[i].price2;

要約
  • iBarShiftは、ブロックのtimeがチャート上の位置と一致する正確なインデックスを特定します。
  • buyOrderBlockBufferおよびsellOrderBlockBufferは、それぞれprice2の値を受け取り、チャートおよびインジケーターの計算で正しいタイミングに価格を記録できるようにします。


入力パラメータ(Inputs)の更新

このセクションでは、ユーザーがテイクプロフィット(TP)およびストップロス(SL)の計算方法をカスタマイズできるように、入力パラメータを設定します。2つのオプション、ATR (Average True Range)またはPOINT(固定ポイント)を選択できる列挙型を作成します。

ENUM_TP_SL_STYLE

この列挙型により、ユーザーは2つのTPおよびSL計算モードのいずれかを選択できます。

  • ATR:平均的な価格変動幅(ボラティリティ)に基づいてTPおよびSLを設定し、現在の市場の変動に自動的に適応します。
  • POINT:ユーザーが定義した固定ポイント数でTPおよびSLを設定します。

enum ENUM_TP_SL_STYLE
  {
   ATR,
   POINT
  };

説明

  • ATR:ユーザーはATRに対する倍率を設定し、市場のボラティリティに応じてTPおよびSLの距離を自動的に拡大または縮小できます。倍率が大きいほど、TPとSLの距離が広くなります。

  • POINT:ユーザーが固定ポイント数を手動で定義し、ボラティリティに関係なく固定的なレベルを維持します。

続いて、インジケーターパラメータを整理して入力構造を定義します。sinputを使用して設定をセクションごとにグループ化し、より視覚的で整理されたパラメータ表示を実現します。これにより、ユーザーが簡単に設定をおこなえるようになります。

1. Strategyセクション 

TPおよびSLの計算スタイルを含むストラテジーグループを作成します。

sinput group "-- Strategy --"
input ENUM_TP_SL_STYLE tp_sl_style = POINT; // TP and SL style: ATR or fixed points

ここで、tp_sl_styleによりユーザーはTPおよびSLをATRに基づいて計算するか、固定ポイントで設定するかを選択できます。

2. 選択された方式ごとのTPおよびSL設定

各方式の特定の設定を考慮するために、ATR方式用と固定ポイント方式用の2つの追加グループを追加します。

ATRグループ:ATR倍率を指定する2つのdouble型入力変数を含め、ボラティリティに基づいてTPおよびSLの範囲を調整します。

sinput group " ATR "
input double Atr_Multiplier_1 = 1.5; // Multiplier for TP
input double Atr_Multiplier_2 = 2.0; // Multiplier for SL

POINTグループ:固定ポイントでTPおよびSLを定義するために、2つのint型入力変数を追加し、距離を手動かつ正確に制御できるようにします。

sinput group " POINT "
input int TP_POINT = 500; // Fixed points for TP
input int SL_POINT = 275; // Fixed points for SL

この構造により、パラメータが整理され分類され、使用しやすく明確になります。ユーザーはATRに基づく自動設定と固定ポイントによる手動設定のいずれかを直感的に選択できます。

パラメータの完全なコード

sinput group "--- Order Block Indicator settings ---"
sinput group "-- Order Block --"
input          int  Rango_universal_busqueda = 500;
input          int  Witdth_order_block = 1;

input          bool Back_order_block = true;
input          bool Fill_order_block = true;

input          color Color_Order_Block_Bajista = clrRed;
input          color Color_Order_Block_Alcista = clrGreen;

sinput group "-- Strategy --"
input          ENUM_TP_SL_STYLE tp_sl_style = POINT;

sinput group " ATR "
input          double Atr_Multiplier_1 = 1.5;
input          double Atr_Multiplier_2 = 2.0;
sinput group " POINT "
input          int TP_POINT = 500;
input          int SL_POINT = 275;


インジケーターシグナル生成のロジック

買いまたは売りのシグナルを生成するには、次の2つの静的変数が使用されます。

変数 詳細
time_およびtime_b オーダーブロックがミティゲートされた時刻を記録し、期限切れのために5ローソク足分のマージン(秒単位)を加えます。
buscar_obaおよびbuscar_obb 新たにミティゲートされたオーダーブロックの検索を制御します。条件に応じて有効または無効になります。

シグナル生成プロセス

ミティゲートされたオーダーブロックの検出
  • オーダーブロックがミティゲートされた場合、time_は現在時刻に5ローソク足分のマージンを加えた値に設定されます。
  • シグナル条件を検証している間は、searcher変数をfalseに設定して、新たな検索を一時停止します。
買いおよび売りのシグナル条件
  • シグナルは30期間の指数移動平均(EMA)とミティゲーション時間(time_)に基づいて評価されます。 
以下の表は具体的な条件をまとめたものです。
シグナルの種類   EMA条件
時間条件
 買  30期間EMAがローソク足1の終値より   time_が現在時刻より大きい
 売  30期間EMAがローソク足1の終値より   time_bが現在時刻より大きい

注意:これらの条件により、オーダーブロックがミティゲートされた後、5ローソク足分のマージン内でシグナルが生成されることが保証されます。

条件が満たされた場合と満たされない場合のアクション

状態   アクション
 達成 TPおよびSLバッファに値を設定し、対応する取引をおこないます。
 未達成 searcherをtrueにリセットし、time_とtime_bを0に設定。最大時間経過後に新しいオーダーブロックの検索が再開されます。

ブロック図

買いポジションを開くロジック

売りポジションを開くロジック


取引戦略の実装

まず、指数移動平均(EMA)のハンドルを作成します。

グローバル変数(配列とハンドル)を作成します。

int hanlde_ma;
double ma[]; 

OnInit内でハンドルを初期化し、有効な値が割り当てられているか確認します。

hanlde_ma = iMA(_Symbol,_Period,30,0,MODE_EMA,PRICE_CLOSE);

if(hanlde_ma == INVALID_HANDLE)
{
Print("The EMA indicator is not available. Failure: ", _LastError); 
return INIT_FAILED;
}
次に、検索状態とオーダーブロック(OB)の有効化時刻を管理する静的変数を宣言し、買いと売りのシナリオを区別します。
//Variables for buy
static bool buscar_oba = true;
static datetime time_ = 0;

//Variables for sell
static bool buscar_obb = true;
static datetime time_b = 0;

続いて、ソフトオーダーブロックをループ処理し、条件を追加します。

まず条件を追加します。

//Bullish case
 if(buscar_oba == true)
//Bearish case
 if(buscar_obb == true)

オーダーブロックがミティゲートされたかどうかを判定します。価格がOBと接触した場合、その時刻を記録し、検索を一時停止します。これは強気・弱気の両方のケースでおこないます。

// Bearish case
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) &&
       ObjectFind(ChartID(), ob_bajistas[i].name) >= 0) {
        
        Alert("The bearishorder block is being mitigated: ", TimeToString(ob_bajistas[i].time1));
        buscar_obb = false;  // Pause search
        time_b = iTime(_Symbol,_Period,1);  //  Record the mitigation time
        Agregar_Index_Array_1(pricetwo_eliminados_obb, ob_bajistas[i].name);
        break;
    }
}

// Bullish case
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) &&
       ObjectFind(ChartID(), ob_alcistas[i].name) >= 0) {
        
        Alert("The bullish order block is mitigated: ", TimeToString(ob_alcistas[i].time1));
        time_ = iTime(_Symbol,_Period,0);
        Agregar_Index_Array_1(pricetwo_eliminados_oba, ob_alcistas[i].name);
        buscar_oba = false;  // Pause search
        break;
    }
}

このセクションでは、緩和策が検出されるとシステムが検索を停止し、重複したシグナルを回避します。

取引実行の初期条件

戦略では、OBがミティゲートされ、最大待機時間が超過していない場合に買いまたは売りシグナルの検索を開始します。

// Buy
if(buscar_oba == false && time_ > 0 && new_vela) { /* Code for Buy */ }

// Sell
if(buscar_obb == false && time_b > 0 && new_vela) { /* Code for Sell */ }

これらの条件では

  1. buscar_obaまたはbuscar_obbがfalseであること(以前のミティゲーションを確認)
  2. time_またはtime_bが0より大きいこと(時刻が記録されていること)
  3. new_velaがtrueであること(新しいローソク足に対してのみロジックを適用し、重複動作を防ぐ)

買いまたは売り条件の検証

必要な条件を設定するため、まず最大待機時間を格納する変数を用意します。さらに、ローソク足1の終値とそのEMA値を取得します。終値はiCloseで取得し、EMAは配列に保存して履歴全体を保持します。

// Buy
double close_ = NormalizeDouble(iClose(_Symbol,_Period,1),_Digits);
datetime max_time_espera = time_ + (PeriodSeconds() * 5);

if(close_ > ma[1] && iTime(_Symbol,_Period,0) <= max_time_espera) {
    // Code for Buy...
}

// Sell
close_ = NormalizeDouble(iClose(_Symbol,_Period,1),_Digits);
max_time_espera = time_b + (PeriodSeconds() * 5);

if(close_ < ma[1] && iTime(_Symbol,_Period,0) <= max_time_espera) {
    // Code for Sell...
}

オーダーブロック検索のリセット

最大待機時間が経過しても条件が満たされない場合、検索をリセットして新しいOB検出を可能にします。

// Reset for Buy
if(iTime(_Symbol,_Period,0) > max_time_espera) {
    time_ = 0;
    buscar_oba = true;
}

// Reset for Sell
if(iTime(_Symbol,_Period,0) > max_time_espera) {
    time_b = 0;
    buscar_obb = true;
}

最後に、TPおよびSLを描画し、バッファに追加する関数が必要です。これは以下のコードで実現できます。

新しいセクションに進みましょう。


テイクプロフィット(TP)とストップロス(SL)レベルの設定

このセクションでは、TPおよびSLを計算する関数「GetTP_SL」を作成します。入力構成で前述したように、計算には、ATRまたは固定ポイントのいずれかを使用します。

1:関数の定義

GetTP_SL関数は、ポジションの始値、ポジションタイプ(ENUM_POSITION_TYPE)、および計算結果を格納する参照変数(tp1、tp2、sl1、sl2)をパラメータとして受け取ります。

void GetTP_SL(double price_open_position, ENUM_POSITION_TYPE type, double &tp1, double &tp2, double &sl1, double &sl2)

2:ATRの取得

ATRベースのレベルを計算するため、最新ローソク足のATR値を格納する配列が必要です。CopyBufferを使ってatr配列に値を取得します。

double atr[];
ArraySetAsSeries(atr, true);
CopyBuffer(atr_i, 0, 0, 1, atr);

3:ATRに基づくTPおよびSLの計算

tp_sl_styleがATRに設定されている場合、ATR値に定義された倍率(Atr_Multiplier_1とAtr_Multiplier_2)を掛け、ポジションタイプに応じて始値に加減してTPおよびSLを計算します。

if (type == POSITION_TYPE_BUY) {
    sl1 = price_open_position - (atr[0] * Atr_Multiplier_1);
    sl2 = price_open_position - (atr[0] * Atr_Multiplier_2);
    tp1 = price_open_position + (atr[0] * Atr_Multiplier_1);
    tp2 = price_open_position + (atr[0] * Atr_Multiplier_2);
}

if (type == POSITION_TYPE_SELL) {
    sl1 = price_open_position + (atr[0] * Atr_Multiplier_1);
    sl2 = price_open_position + (atr[0] * Atr_Multiplier_2);
    tp1 = price_open_position - (atr[0] * Atr_Multiplier_1);
    tp2 = price_open_position - (atr[0] * Atr_Multiplier_2);
}

4:固定ポイントに基づくTPおよびSLの計算

tp_sl_styleがPOINTに設定されている場合、指定されたポイント数(TP_POINTとSL_POINT)に銘柄のポイント値(_Point)を掛けて始値に加減します。ATRベースよりも簡単な計算方法です。

if (type == POSITION_TYPE_BUY) {
    sl1 = price_open_position - (SL_POINT * _Point);
    sl2 = price_open_position - (SL_POINT * _Point * 2);
    tp1 = price_open_position + (TP_POINT * _Point);
    tp2 = price_open_position + (TP_POINT * _Point * 2);
}

if (type == POSITION_TYPE_SELL) {
    sl1 = price_open_position + (SL_POINT * _Point);
    sl2 = price_open_position + (SL_POINT * _Point * 2);
    tp1 = price_open_position - (TP_POINT * _Point);
    tp2 = price_open_position - (TP_POINT * _Point * 2);
}


チャート上でTPとSLレベルを可視化する

このセクションでは、ラインとテキストオブジェクトを使ってチャート上にTPおよびSLレベルを描画する関数を作成します。

ラインの作成

bool TrendCreate(long            chart_ID,        // Chart ID
                 string          name,  // Line name
                 int             sub_window,      // Subwindow index
                 datetime              time1,           // Time of the first point
                 double                price1,          // Price of the first point
                 datetime              time2,           // Time of the second point
                 double                price2,          // Price of the second point
                 color           clr,        // Line color
                 ENUM_LINE_STYLE style, // Line style
                 int             width,           // Line width
                 bool            back,        // in the background
                 bool            selection    // Selectable form moving
                )
  {
   ResetLastError();
   if(!ObjectCreate(chart_ID,name,OBJ_TREND,sub_window,time1,price1,time2,price2))
     {
      Print(__FUNCTION__,
            ": ¡Failed to create trend line! Error code = ",GetLastError());
      return(false);
     }

   ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr);
   ObjectSetInteger(chart_ID,name,OBJPROP_STYLE,style);
   ObjectSetInteger(chart_ID,name,OBJPROP_WIDTH,width);
   ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back);
   ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection);
   ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection);
   ChartRedraw(chart_ID);
   return(true);
  }

テキストの場合

bool TextCreate(long              chart_ID,                // Chart ID
                string            name,              // Object name
                int               sub_window,             // Subwindow index
                datetime                time,                   // Anchor time
                double                  price,                  // Anchor price
                string            text,              // the text
                string            font,             // Font
                int               font_size,             // Font size
                color             clr,               // color
                double            angle,                // Text angle
                ENUM_ANCHOR_POINT anchor, // Anchor point
                bool              back=false,               // font
                bool              selection=false)          // Selectable for moving

  {

//--- reset error value
   ResetLastError();
//--- create "Text" object
   if(!ObjectCreate(chart_ID,name,OBJ_TEXT,sub_window,time,price))
     {
      Print(__FUNCTION__,
            ": ¡Failed to create object \"Text\"! Error code = ",GetLastError());
      return(false);
     }
   ObjectSetString(chart_ID,name,OBJPROP_TEXT,text);
   ObjectSetString(chart_ID,name,OBJPROP_FONT,font);
   ObjectSetInteger(chart_ID,name,OBJPROP_FONTSIZE,font_size);
   ObjectSetDouble(chart_ID,name,OBJPROP_ANGLE,angle);
   ObjectSetInteger(chart_ID,name,OBJPROP_ANCHOR,anchor);
   ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr);
   ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back);
   ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection);
   ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection);

   ChartRedraw(chart_ID);
   return(true);
  }

それでは関数の作成に移りましょう。

手順1:入力詳細

この関数は次のパラメータを受け取ります。

  • tp1、tp2:2つのテイクプロフィットレベルの値
  • sl1、sl2:2つのストップロスレベルの値
void DrawTP_SL( double tp1, double tp2, double sl1, double sl2)

手順2:時間の準備

まず、文字列「curr_time」を作成し、チャート上のローソク足の現在の日付と時刻を格納します。次に、extension_timeを計算し、現在時刻から15期間先まで延長してTPおよびSLのラインを右方向に投影します。text_timeはテキストラベルの位置をextension_timeより少し右に調整するために使用します。

string curr_time = TimeToString(iTime(_Symbol, _Period, 0));
datetime extension_time = iTime(_Symbol, _Period, 0) + (PeriodSeconds(PERIOD_CURRENT) * 15);
datetime text_time = extension_time + (PeriodSeconds(PERIOD_CURRENT) * 2);

手順3:TPおよびSLラインとラベルの描画

  1. テイクプロフィット1 (tp1)
  • TrendCreateを使用して、tp1の位置に緑色の点線(STYLE_DOT)を描画します。
  • TextCreateを使用して、tp1の位置に「TP1」というテキストラベルを追加します。
TrendCreate(ChartID(), curr_time + " TP1", 0, iTime(_Symbol, _Period, 0), tp1, extension_time, tp1, clrGreen, STYLE_DOT, 1, true, false);
TextCreate(ChartID(), curr_time + " TP1 - Text", 0, text_time, tp1, "TP1", "Arial", 8, clrGreen, 0.0, ANCHOR_CENTER);

      2.  テイクプロフィット2 (tp2)
  • tp2に別の緑の点線を描き、テキストラベル「TP2」を追加します。
TrendCreate(ChartID(), curr_time + " TP2", 0, iTime(_Symbol, _Period, 0), tp2, extension_time, tp2, clrGreen, STYLE_DOT, 1, true, false);
TextCreate(ChartID(), curr_time + " TP2 - Text", 0, text_time, tp2, "TP2", "Arial", 8, clrGreen, 0.0, ANCHOR_CENTER);

      3.  ストップロス1 (sl1)
  • sl1に赤い点線を描き、テキストラベル「SL1」を追加します。
TrendCreate(ChartID(), curr_time + " SL1", 0, iTime(_Symbol, _Period, 0), sl1, extension_time, sl1, clrRed, STYLE_DOT, 1, true, false);
TextCreate(ChartID(), curr_time + " SL1 - Text", 0, text_time, sl1, "SL1", "Arial", 8, clrRed, 0.0, ANCHOR_CENTER);

       4.  ストップロス2 (sl2)
  • 同様に、sl2に赤い線を描き、テキストラベル「SL2」を追加します。
TrendCreate(ChartID(), curr_time + " SL2", 0, iTime(_Symbol, _Period, 0), sl2, extension_time, sl2, clrRed, STYLE_DOT, 1, true, false);
TextCreate(ChartID(), curr_time + " SL2 - Text", 0, text_time, sl2, "SL2", "Arial", 8, clrRed, 0.0, ANCHOR_CENTER);

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

void DrawTP_SL(double tp1, double tp2, double sl1, double sl2)
  {


   string  curr_time = TimeToString(iTime(_Symbol,_Period,0));
   datetime extension_time = iTime(_Symbol,_Period,0) + (PeriodSeconds(PERIOD_CURRENT) * 15);
   datetime   text_time = extension_time + (PeriodSeconds(PERIOD_CURRENT) * 2);


   TrendCreate(ChartID(),curr_time+" TP1",0,iTime(_Symbol,_Period,0),tp1,extension_time,tp1,clrGreen,STYLE_DOT,1,true,false);
   TextCreate(ChartID(),curr_time+" TP1 - Text",0,text_time,tp1,"TP1","Arial",8,clrGreen,0.0,ANCHOR_CENTER);

   TrendCreate(ChartID(),curr_time+" TP2",0,iTime(_Symbol,_Period,0),tp2,extension_time,tp2,clrGreen,STYLE_DOT,1,true,false);
   TextCreate(ChartID(),curr_time+" TP2 - Text",0,text_time,tp2,"TP2","Arial",8,clrGreen,0.0,ANCHOR_CENTER);

   TrendCreate(ChartID(),curr_time+" SL1",0,iTime(_Symbol,_Period,0),sl1,extension_time,sl1,clrRed,STYLE_DOT,1,true,false);
   TextCreate(ChartID(),curr_time+" SL1 - Text",0,text_time,sl1,"SL1","Arial",8,clrRed,0.0,ANCHOR_CENTER);

   TrendCreate(ChartID(),curr_time+" SL2",0,iTime(_Symbol,_Period,0),sl2,extension_time,sl2,clrRed,STYLE_DOT,1,true,false);
   TextCreate(ChartID(),curr_time+" SL2 - Text",0,text_time,sl2,"SL2","Arial",8,clrRed,0.0,ANCHOR_CENTER);

  }


TPレベルとSLレベルのバッファの追加(4)

price2を格納する2つのバッファと同様に、TPおよびSL用の追加バッファを作成します。

#property indicator_label3 "Take Profit 1"
#property indicator_label4 "Take Profit 2"
#property indicator_label5 "Stop Loss 1"
#property indicator_label6 "Stop Loss 2"

プロットとバッファの数を2から6に増やします。

#property  indicator_buffers 6
#property  indicator_plots 6

バッファの配列を作成します。

double tp1_buffer[];
double tp2_buffer[];
double sl1_buffer[];
double sl2_buffer[];

配列を初期化し、系列として設定します。

SetIndexBuffer(2, tp1_buffer, INDICATOR_DATA);
SetIndexBuffer(3, tp2_buffer, INDICATOR_DATA);

SetIndexBuffer(4, sl1_buffer, INDICATOR_DATA);
SetIndexBuffer(5, sl2_buffer, INDICATOR_DATA);


ArraySetAsSeries(buyOrderBlockBuffer, true);
ArraySetAsSeries(sellOrderBlockBuffer, true);
ArrayFill(buyOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE
ArrayFill(sellOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE

ArraySetAsSeries(tp1_buffer, true);
ArraySetAsSeries(tp2_buffer, true);
ArrayFill(tp1_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE
ArrayFill(tp2_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE

ArraySetAsSeries(sl1_buffer, true);
ArraySetAsSeries(sl2_buffer, true);
ArrayFill(sl1_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE
ArrayFill(sl2_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE

これにより、TP値とSL値が正しく保存され、チャートに表示されます。


メインコードの最終処理とクリーンアップ

インジケーターを完成させるために、クリーンアップと最適化のコードを実装します。これにより、バックテスト性能が向上し、不要になった配列(OrderBlocksを格納する配列など)のメモリリソースが解放されます。

1. 配列のクリア

OnCalculate内で新しい日足ローソク足を監視します。最後のローソク足時刻をグローバル変数に保存します。

datetime    tiempo_ultima_vela_1;
新しい日次ローソク足が開くたびに、配列からメモリを解放して古いデータの蓄積を防ぎ、パフォーマンスを最適化します。
 if(tiempo_ultima_vela_1 != iTime(_Symbol,PERIOD_D1,  0))
     {
      Eliminar_Objetos();

      ArrayFree(ob_bajistas);
      ArrayFree(ob_alcistas);
      ArrayFree(pricetwo_eliminados_oba);
      ArrayFree(pricetwo_eliminados_obb);

      tiempo_ultima_vela_1 = iTime(_Symbol,PERIOD_D1,  0);
     }

2. OnDeinitの変更

OnDeinitで、EMAインジケーターハンドルを解放し、すべての配列をクリアします。これにより、インジケーターが削除されたときにメモリリソースが残らないことが保証されます。

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);
   if(hanlde_ma != INVALID_HANDLE) //EMA
      IndicatorRelease(hanlde_ma);

   ResetLastError();

    if(MarketBookRelease(_Symbol)) //Verify if closure was successful
     Print("Order book successfully closed for: " , _Symbol); //Print success message if so
   else
     Print("Order book closed with errors for: " , _Symbol , "   Last error: " , GetLastError()); //Print error message with code if not
  }

3. オブジェクト削除関数

Eliminar_Objetos関数を最適化し、TPおよびSLラインおよびオーダーブロック矩形を削除して、チャートを清潔に保ちます。
void Eliminar_Objetos()
  {

   for(int i = 0 ; i < ArraySize(ob_alcistas) ; i++) // iterate through the array of bullish order blocks
     {
      ObjectDelete(ChartID(),ob_alcistas[i].name); // delete the object using the order block's name
     }
   for(int n = 0 ; n < ArraySize(ob_bajistas) ; n++) // iterate through the array of bearish order blocks
     {
      ObjectDelete(ChartID(),ob_bajistas[n].name);  // delete the object using the order block's name
     }
 //Delete all TP and SL lines
   ObjectsDeleteAll(0," TP",-1,-1);
   ObjectsDeleteAll(0," SL",-1,-1);
  }

4. OnInitでの初期設定

インジケーターのショートネームとチャートプロットラベルを設定し、データウィンドウ上で正しく表示されるようにします。

   string short_name = "Order Block Indicator";
   IndicatorSetString(INDICATOR_SHORTNAME,short_name);

// Set data precision for digits

// Assign labels for each plot
   PlotIndexSetString(0, PLOT_LABEL, "Bullish Order Block");
   PlotIndexSetString(1, PLOT_LABEL, "Bearish Order Block");
   PlotIndexSetString(2, PLOT_LABEL, "Take Profit 1");
   PlotIndexSetString(3, PLOT_LABEL, "Take Profit 2");
   PlotIndexSetString(4, PLOT_LABEL, "Stop Loss 1");
   PlotIndexSetString(5, PLOT_LABEL, "Stop Loss 2");

5. 取引開始時のTPとSLレベルの設定

最後に、買いおよび売りのテイクプロフィット(TP)とストップロス(SL)レベルを設定します。買い場合はAsk価格を使用し、売りの場合はBid価格を使用します。計算後、DrawTP_SL関数を用いて、チャート上にTPおよびSLラインを描画し、モニタリングできるようにします。

//Buy
double ask= NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);

double tp1;
double tp2;
double sl1;
double sl2;
GetTP_SL(ask,POSITION_TYPE_BUY,tp1,tp2,sl1,sl2);

DrawTP_SL(tp1,tp2,sl1,sl2);

tp1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp1;
tp2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp2;
sl1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl1;
sl2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl2;

time_ = 0;
buscar_oba = true;

//Sell

double bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
double tp1;
double tp2;
double sl1;
double sl2;
GetTP_SL(bid,POSITION_TYPE_SELL,tp1,tp2,sl1,sl2);

DrawTP_SL(tp1,tp2,sl1,sl2);

tp1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp1;
tp2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp2;
sl1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl1;
sl2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl2;

time_b = 0;
buscar_obb = true;
ステップ  

 価格 Askを取得して正規化
Bidを取得して正規化
変数 TPとSLを格納する変数を初期化

(tp1、tp2、sl1、sl2) 
同じ変数を使用

(tp1、tp2、sl1、sl2) 
計算方法 GetTP_SLは買い取引のAsk価格に基づいてTPレベルとSLレベルを計算 GetTP_SLは、売り取引のBid価格に基づいてTPレベルとSLレベルを計算 
描画 DrawTP_SLは、買い取引のTP/SLレベルをチャート上に視覚的に表示 DrawTP_SLは売り取引のTP/SLレベルをチャート上に視覚的に表示
バッファ iBarShiftを使用して現在のバーインデックスを見つけ、TP/SLをバッファに保存

 (tp1_buffer、tp2_buffer、sl1_buffer、sl2_buffer)    
現在のバーのインデックスを見つけて、TP/SLを同じバッファに保存

 (tp1_buffer、tp2_buffer、sl1_buffer、sl2_buffer)   
静的変数  静的変数をリセットして、次の反復で強気注文の新しいブロックを検索

(静的変数:time_およびbuscar_oba)
静的変数をリセットして、次の反復で弱気注文の新しいブロックを検索

(静的変数:time_bおよびsearch_obb)


結論

この記事では、板情報の出来高に基づくオーダーブロックインジケーターの作成方法を解説し、元のインジケーターに追加バッファを加えることで機能を最適化する手法を紹介しました。

最終結果

最終例GIF

これで、オーダーブロックインジケーターの開発は完了です。次回の記事では、リスク管理クラスをゼロから作成し、このリスク管理を統合した取引ボットを開発する方法を解説します。ボットは、今回作成したインジケーターのシグナルバッファを活用して、より正確で自動化された取引をおこないます。

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

添付されたファイル |
最後のコメント | ディスカッションに移動 (1)
Vladislav Boyko
Vladislav Boyko | 4 10月 2025 において 19:24

https://www.mql5.com/ja/articles/16268

5.取引開始時のTPとSLレベルの設定

最後に、買いと売りの取引にテイクプロフィットとストップロスのレベルを設定します。買い 取引には アスク価格を使い、売り 取引には ビッド価格を使います。そして、チャート上にTPラインとSLラインを引いて監視します。

tp1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp1;
tp2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp2;
sl1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl1;
sl2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl2;

これは 少し単純化できそうです。

取引におけるニューラルネットワーク:Attentionメカニズムを備えたエージェントのアンサンブル(最終回) 取引におけるニューラルネットワーク:Attentionメカニズムを備えたエージェントのアンサンブル(最終回)
前回の記事では、複数のエージェントによるアンサンブルを用いて、異なるデータスケールのマルチモーダル時系列をクロス分析するマルチエージェント適応型フレームワーク「MASAAT」を紹介しました。今回は、このフレームワークのアプローチをMQL5で引き続き実装し、この研究を論理的な結論へと導きます。
リプレイシステムの開発(第78回):新しいChart Trade(V) リプレイシステムの開発(第78回):新しいChart Trade(V)
本記事では、受信側コードの一部の実装方法について解説します。ここでは、プロトコルの相互作用をテストし理解するためのエキスパートアドバイザー(EA)を実装します。ここで提示されるコンテンツは、教育目的のみに使用されることを意図しています。いかなる状況においても、提示された概念を学習し習得する以外の目的でアプリケーションを利用することは避けてください。
取引におけるニューラルネットワーク:予測符号化を備えたハイブリッド取引フレームワーク(StockFormer) 取引におけるニューラルネットワーク:予測符号化を備えたハイブリッド取引フレームワーク(StockFormer)
本記事では、予測符号化と強化学習(RL)アルゴリズムを組み合わせたハイブリッド取引システム「StockFormer」について解説します。本フレームワークは、統合型のDiversified Multi-Head Attention (DMH-Attn)機構を備えた3つのTransformerブランチを使用しています。DMH-Attnは、従来のAttentionモジュールを改良したもので、マルチヘッドのFeed-Forwardブロックを組み込むことにより、異なるサブスペースにわたる多様な時系列パターンを捉えることが可能です。
取引におけるニューラルネットワーク:Attentionメカニズムを備えたエージェントのアンサンブル(MASAAT) 取引におけるニューラルネットワーク:Attentionメカニズムを備えたエージェントのアンサンブル(MASAAT)
アテンション機構と時系列解析を組み合わせたマルチエージェント自己適応型ポートフォリオ最適化フレームワーク(MASAAT: Multi-Agent Self-Adaptive Portfolio Optimization Framework)を提案します。MASAATは、価格系列や方向性の変化を分析する複数のエージェントを生成し、異なる詳細レベルで資産価格の重要な変動を特定できるように設計されています。