MQL5 クックブック: 板情報の実装

Vasiliy Sokolov | 14 3月, 2016

コンテンツ表


イントロダクション

MQL5は常に進化し、毎年、より多くの情報を提供するように提案しています。そのようなデータの1つが板情報です。リミットオーダーのボリュームと価格帯を示す、特殊な表です。MetaTrader5では、リミットオーダーの板情報が搭載されていますが、常に十分という訳ではありません。まず、EAはシンプルかつ便利な方法で板情報にアクセスできなれければなりません。確かに、MQL5はそのような情報をもとに稼働する特徴がありますが、それらは別枠で算術計算を要求する程度の低い特徴です

しかし、すべての途中計算を省くことができます。板情報で稼働する特殊なクラスを記述すれば良いのです。複雑な計算は板情報の内部で実行され、クラス自体はDOMの価格とレベルの実行のための便利な方法を提供します。このクラスは、市場の深さでの価格の現在の状態を反映し、速やかになる指標の形で効率的なパネルを簡単に作成できるようになります。:


図1. パネル表示された板情報

この記事ではユーザーに板情報 (DOM) の利用法を提示します。また、CMarketBookクラスの実行原理についても説明します。これは、MQL5の標準ライブラリの拡張で、DOMの便利な使い方を提示します。

この記事の最初のチャプターの後では、MetaTrader5による一般的な板情報が興味深いものとなるでしょう。インジケーターにあるものと重複するようなものではなく、まったく新しいものを提示します。ユーザーフレンドリーな板情報の実践的な生成例で、オブジェクト指向プログラミングの原則が複雑なデータ構造の処理を可能にする様子を示したいと思います。MQL5でEAから直接、板情報へアクセスすることは難しくありません。そして、ビジュアル化することによりとても便利になります。

 

チャプター1. MetaTrader5での標準的な板情報とその使い方


1.1. MetaTrader5の標準的な板情報

MetaTrader 5 は中央取引でのトレードをサポートし、板情報の実行ツールを提供します。まず初めに、板はリミットオーダーのテーブルです。それは直近のアドバンスです。板情報を開くには、MetaTrader5をサポートしている取引所に接続し、メニュー "View" --> "Depth of Market" --> "Name of instrumentを選択する必要があります。ティックチャートのセパレートウィンドウとリミットオーダーのテーブルが表示されます。:


図2. MetaTrader5の標準的な板情報

MetaTrader5では板情報をより機能的にすることができます。特に、次の表示が可能です。:

  • Buy と Sell のリミットオーダーの価格帯とボリューム( 古典的なDOMの一般系);
  • 現在のスプレッドとリミットオーダーによる価格レベル (アドバンストモード);
  • ティックチャートと可視化された Bid, Ask 及び最後のトレードのボリューム;
  • 買いオーダー売りオーダーの合計レベル (2つのラインでティックチャートに表示).

板情報の特徴に関するこれらのリストは非常に印象的です。どのようにしてデータを取り出して扱うかを見ていきましょう。まず、どのようにして板情報が生成されているかと、その根本を知る必要があります。詳細は、 "モスクワ証券取引所の例によるトレードの原則" のチャプター"13を参照してください。売り手と買い手のマッチング板情報の交換". この件に関して十分な知識があるなら、表の理解に時間を費やすことはありません。

 

1.2. 板情報を使ったモデル

板情報は非常に動的なデータです。動的な市場では、リミットオーダーの表は一秒間に何度も変化します。したがって、プロセスに必要な実データのみを取得するようにしなければなりません。そうしないと、取得したデータとそれに費やすプロセスが許容範囲を超えてしまいます。そのため、MetatTrader5では、特殊なイベントモデルを採用していて、実際に使われる訳ではないプロセスデータは排除されます。このモデルの例を見てみましょう。 

新しいティックやトレードの実行などの市場で起こることはすべて、関連付けられた対応する関数を呼び出すことによって処理することができます。例えば、MQL5での新しいティックは、OnTick()イベントハンドで呼び出されます。チャートのリサイズなどでは OnChartEvent() 関数を使います。このイベントモデルは板情報の変化に対応します。例えば、誰かが売り買いのリミットオーダーを出したとすると、その状態は OnBookEvent() 関数のコールで変更されます。

ターミナルでは何十何百ものシンボルが利用可能なので、OnBookEvent関数のコールの回数は膨大なものになります。これを避けるために、ターミナルではインジケーターやEAの稼働時にどこから秒単位のクオートがあるかを前もって知らされる必要があります。 (板情報のデータもこの方法を取っています。)。特殊なシステム関数 MarketBookAdd をこれに使います。例えば、 Si-9.15の板情報に関するデータが欲しければ (ヒューチャ USD/RUR 2015年9月)、下記のコードをEAやインジケーターのOnInit内に記述しなければなりません。:

void OnInit()
{
   MarketBookAdd("Si-9.15");
}

この関数でいわゆる"予約状態"を生成し、ターミナルに通知します。これによりEA,インジケーターは Si-9.15.の板情報の変化の通知を受けることができます。他のものによる板情報の変化は利用不可能です。他のものによる場合、プログラムによるリソースが極端に少なくなります。

MarketBookAdd の逆の関数が MarketBookRelease 関数です。反対に、板情報からのデータを"非予約状態"にします。EA,インジケーターのアクセスを閉じるとき、プログラマーはOnDeinitセクションに記述した方が良いでしょう。 :

void OnDeinit(const int reason)
{
   MarketBookRelease("Si-9.15");
}

MarketBookAdd関数の呼び出しは基本的に板情報の変化の瞬間に行われます。これは、OnBookEvent()がコールされる瞬間でもあります。板情報を使うEA、インジケーターのシンプルな方法には3つの関数があります。:

  • OnInit - EA,インジケーターの初期関数 DOMの変化を受け取る記述をします。
  • OnDeinit - EA,インジケーターの終了関数 板情報から受け取るデータの解除をします。
  • OnBookEvent - 板情報が変化した後に呼び出されます。

シンプルなEA第一号にはこの3つの関数が含まれます。下記の例からもわかるように、板情報の変化がある度に: "Depth of Market for Si-9.15 was changed"がのメッセージがでます。:

//+------------------------------------------------------------------+
//|                                                       Expert.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   MarketBookAdd("Si-9.15");
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   MarketBookRelease("Si-9.15");
  }
//+------------------------------------------------------------------+
//| BookEvent function                                               |
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
  {
//---
   printf("Depth of Market for " + symbol +  " was changed"); 
  }
//+------------------------------------------------------------------+

重要なコードは黄色でマーカーしています。

 

1.3. MarketBookGet関数とMqlBookInfo構造体で第2レベルのクオートを受け取る

これで板情報の変更通知を受け取れるようになりました。次はDOMのデータにMarketBookGet関数を使ってアクセスする方法です。プロトタイプを使って使い方を見ていきましょう。

先述の通り、板情報は売りと買いのリミットオーダーの2つの箇所の表です。その他の表と同じように、板情報を配列で表記するのが最も簡単な方法です。配列のインデックスはテーブルの線の番号と一致させます。配列の値には、ボリューム、価格、適応タイプなどを入れます。ラインインデックスで表示した板情報を見てみましょう。:

ラインインデックス オーダータイプ ボリューム 価格
0 売りリミット 18 56844
1  売りリミット  1  56843
2  売りリミット  21  56842
3  買いリミット  9  56836
4  買いリミット  5  56835
5  買いリミット  15  56834

 テーブル1. 表による板情報

表のアクセスを簡単にするため、売りオーダーはピンク、買いオーダーは青くします。板情報の表は基本的に2次元配列です。1次元目はラインの番号、2次元目は3つの要素を入れます。 (オーダータイプ - 0, ボリューム - 1 、価格 - 2). しかし、多次元配列での稼働を避けるため、MQL5のMqlBookInfoを使います。これはすべての必要な値が含まれています。このようにして、板情報のインデックスは MqlBookInfo 構造体を含み、代わりとしてオーダータイプやボリューム、価格を格納します。構造体を定義しましょう。:

struct MqlBookInfo
  {
   ENUM_BOOK_TYPE   type;       //ENUM_BOOK_TYPE のオーダータイプ
   double           price;      //オーダー価格
   long             volume;     //ボリューム
  };

これで板情報への手法が明らかになりました。MarketBookGet 関数は MqlBookInfo 構造体の配列を返します。配列のインデックスは、価格テーブルのラインを表し、インデックス構造はボリューム、価格、オーダータイプを内包しています。Knowing this, we will try to get access to the first DOM order, and for this reason we will slightly modify the OnBookEvent function of our Expert Advisor from the previous example:

//+------------------------------------------------------------------+
//| BookEvent function                                               |
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
  {
//---
   //printf("Depth of Market " + symbol +  " changed"); 
   MqlBookInfo book[];
   MarketBookGet(symbol, book);
   if(ArraySize(book) == 0)
   {
      printf("Failed load market book price. Reason: " + (string)GetLastError());
      return;
   }
   string line = "Price: " + DoubleToString(book[0].price, Digits()) + "; ";
   line += "Volume: " + (string)book[0].volume + "; ";
   line += "Type: " + EnumToString(book[0].type);
   printf(line);
  }

EAをなにかしらのチャートで稼働させているとき、DOMとそのパラメータに関するレポートを受け取ります。

2015.06.05 15:54:17.189 Expert (Si-9.15,H1)     Price: 56464; Volume: 56; Type: BOOK_TYPE_SELL
2015.06.05 15:54:17.078 Expert (Si-9.15,H1)     Price: 56464; Volume: 56; Type: BOOK_TYPE_SELL
2015.06.05 15:54:17.061 Expert (Si-9.15,H1)     Price: 56464; Volume: 56; Type: BOOK_TYPE_SELL
...

前の表を見ると、最も悪いAskレートに対応するDOMのゼロインデックスが利用可能か推測できます。(BOOK_TYPE_SELL). また反対に、最も低いBidレートが得られた配列内で最後のインデックスを取ります。最も良いAskとBidは板情報の代替中央に位置します。得られた値の最初のデメリットは板情報の計算が最良の価格で実行されるということで、その価格は一般的に表の中央に位置しています。最も悪いAskとBidは二の次です。今後、CMarketBookクラスを分析するときに、特定のインデックスを与えることにより、この問題を解決します。

 

チャプター2. 板へのCMarketBookクラスを使った簡単なアクセス


2.1. CMarketInfoBookクラスのデザイン

最初のチャプターでは、板情報に関する実行関数について扱いました。セカンドレベルクオートへのアクセスを調整するイベントモデルの特徴を発見しました。このチャプターでは、CMarketBookクラスを生成し、板情報を簡単に利用できるようにします。最初のチャプタで得られた知識をもとに、クラスのプロパティがどのようなデータ型を持つべきかを考えます。

よって、このクラスをデザインする上で最初にするべきことは、受け取ったデータのリソース強度です。板情報は一秒間に何回も更新されます。それに付け加え、 MqlBookInfo 型の要素を多く含みます。したがって、単一の処理でクラスを稼働させなければなりません。板情報を別々のプロセスからアクセスするのではなく、複合的にコピーしたクラスを作れば十分です。:

CMarketBook("Si-9.15");            // Si-9.15 板情報
CMarketBook("ED-9.15");            // ED-9.15 板情報
CMarketBook("SBRF-9.15");          // SBRF-9.15 板情報
CMarketBook("GAZP-9.15");          // GAZP-9.15 板情報

気をつけなければならない2つ目のことは、データへの簡単なアクセスの構造です。リミットオーダーは高頻度の価格の流動があるため、板情報をオブジェクト指向の表にコピーすることは不可能です。したがって、今回のクラスは直接的にMqlBookInfo配列へのアクセスを行わなければなりません。これにはMarketBookGet関数を使います。例えば、DOMのゼロインデックスにアクセスするには、下記のようにします。:

CMarketBook BookOnSi("Si-9.15");
...
//+------------------------------------------------------------------+
//| BookEvent関数                                                     |
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
  {
//---
   MqlBookInfo info = BookOnSi.MarketBook[0];
  }
//+------------------------------------------------------------------+

MarketBookは、MarketBookGet関数を使って直接得られる配列です。しかし、我々のクラスの利便性は、リミットオーダーの配列に直接的にアクセスするということに基づいています。今回のクラスは板情報に特化したクラスになります。例えば、最善のAskを得るには、次のように書くだけで十分です。

double best_ask = BookOnSi.InfoGetDouble(MBOOK_BEST_ASK_PRICE);

最善のAskをEAで計算するよりも便利です。上のコードから、CMarketBookが、整数型や少数型にアクセスする、修飾子やInfoGetInteger や InfoGetDoubleを使うことは明らかです。これは,MQL5でのSymbolInfoDouble や OrderHistoryIntegerと同様です。プロパティを取得するためには、このプロパティの修飾子を指定しなければなりません。プロパティの修飾子を記述してみましょう。:

//+------------------------------------------------------------------+
//| DOMの整数型プロパティの修飾子を指定                                   |
//| .                                                                |
//+------------------------------------------------------------------+
enum ENUM_MBOOK_INFO_INTEGER
{
   MBOOK_BEST_ASK_INDEX,         // 最善のAsk価格
   MBOOK_BEST_BID_INDEX,         // 最善のBid価格
   MBOOK_LAST_ASK_INDEX,         // 最悪のAsk価格
   MBOOK_LAST_BID_INDEX,         // 最悪のBid価格
   MBOOK_DEPTH_ASK,              // 売りの数
   MBOOK_DEPTH_BID,              // 買いの数
   MBOOK_DEPTH_TOTAL             // DOMの合計数
};
//+------------------------------------------------------------------+
//| DOMの少数型のプロパティの修飾子を指定                                 |
//|                                                                  |
//+------------------------------------------------------------------+
enum ENUM_MBOOK_INFO_DOUBLE
{
   MBOOK_BEST_ASK_PRICE,         // 最善のAsk価格
   MBOOK_BEST_BID_PRICE,         // 最善のBid価格
   MBOOK_LAST_ASK_PRICE,         // 最悪のAsk価格 
   MBOOK_LAST_BID_PRICE,         // 最悪のBid価格
   MBOOK_AVERAGE_SPREAD          // AskとBidの平均スプレッド
};

InfoGet... グループのメソッドに加えて、我々のクラスは板情報の更新のRefreshメソッドがあります。我々のクラスは、Refresh()メソッドを呼び出して情報を更新することによって、必要な時にだけ更新を行う程度のリソースを使います。

 

2.2. 板の水準を使ったインデックスの計算

CMarketBook クラスはMqlBookInfo 配列のラッパー(wrapper)です。この配列からデータを迅速かつ簡単に取得するという意図によるものです。その結果として、このクラスによる実行には2つのリソースの消費しかありません。:

  • MarketBookGet関数のMqlBookInfo配列のコピー
  • 一般的によく使う価格の計算

MarketBookGet 関数の実行を加速させることはできませんが、その必要はありません。MQL5のすべての関数は最大効率化されています。しかし、必要なインデックスの計算を最速にすることはできます。もう一度、ENUM_MBOOK_INFO_INTEGER と ENUM_MBOOK_INFO_DOUBLE を見てみましょう。ご覧の通り、利用可能なプロパティはほとんど4つのインデックスに基づいています。:

  • ベストAsk; 
  • ベストBid;
  • ワーストBid;
  • ワーストAsk

また、3つの整数型プロパティを使います。:

  • 売りの数、板情報の売り (Askの厚み);
  • 買いの数、板情報の買い(Bidの厚み);
  • 板情報の合計値はDOMの要素の数と一致します。

MarketBookGet関数によって得られる配列はワーストAskから始まるので、ワーストAskが常に0であることは明らかです。ワーストBidのインデックスを探すことも簡単です。MqlInfoBook配列の最後のインデックスになります。(最後の要素のインデックスは配列の要素全体の数よりも少なくなります。):

ワーストAskのインデックス = 0

ワーストBidのインデックス = 板情報の配列の合計 - 1

整数のインデックス-は計算が簡単になります。よって、板情報の合計は常にMqlBookInfo配列の要素の合計と同じになります。Ask側からの板情報は次の通りです。:

Askの厚み = ベストAskのインデックス - ワーストAskのインデックス + 1

配列の始まりが0なので、インデックスを分かりやすくするために、必ず1を足します。しかし、ベストAskのインデックスに1を追加するとき、すでにベストBidのインデックスも取得しています。例えば、表1で2番目の数に1を足す場合、ベストの売りのリミットオーダー56 842からベストの買いのリミットオーダー56 836 に移します。また、ワーストAskのインデックスが常に0であることが分かっています。したがって、Askの深さの計算を減らすことができます。:

Askの深さ = ベストBidのインデックス

Bidの深さの計算はいくぶん違います。買いオーダーの数が、オーダーの合計から売りのオーダーの数を引いたものであることは明らかです。前述の式から、Askの厚みがベストBidのインデックスと同じであることが分かっているので、買いオーダーの数を決定する式を出すことは難しくありません。:

Bid の深さ = 板の合計 - ベストAskのインデックス

板の合計は常にBidの深さとAskの深さの合計と同じです。よって、板の要素数とも同じになります。:

板の合計 = 板情報の要素数の合計

分析的に、だいたいのインデックスが分かりました。計算を減らすことにより、直接のインデックスに代わるインデックスの計算方法を得ました。板情報へのアクセスが最も早くなるので、これはCMarketBookクラスではとても重要なことです。

実際のインデックスとは違い、この方法は平均スプレッドを知る必要があります。スプレッドはベストAskとベストBidの価格の差です。CMarketBookクラスでは、このパラメータの平均値を取得することができます。これにはInfoGetDoubleメソッドのMBOOK_AVERAGE_SPREADを呼び出します。CMarketBookは、Refreshメソッドで現在のスプレッドを計算し、記憶された値を元に平均値をだします。

しかし、まだベストBidとベストAskのインデックスを見つけられていません。これを解決するため、次のセクションに移ります。

 

2.3. 前の値に基づいた、ベストAsk/ベストBidの予測

ベストAskとベストBidの計算はこれまでのものよりもやや難易度が高いです。例えば、表1ではベストAskのインデックスは2で、ベストBidのインデックスは3です。この表は、6階層に単純化された板情報です。実際には、板情報はより大きく64もの売りと買いの階層があります。これは、DOMのアップデートが毎秒起こることを考えると、重要な数値です。

最も簡単な方法は"2つに分ける"ことです。つまり、表1での6という合計数を2で割り、図3のようなベストBidのインデックスを得ます。これにより、ベストAskのインデックスは2になります。しかし、この方法は売りの階層の数が買いの階層の数と等しい場合にのみ有効です。これは流動性のある相場では一般的ですが、流動性がない相場の場合は片方の階層がなくなることもあり得ます。

CMarketBookクラスはどの相場のどのような板でも稼働しなければなりません。そのため、2で割るという方法は今回は見送ります。2で割ったときに上手くいかなくなる事例として下図を挙げます。:


図3. Bidの数は必ずしもAskの数と一致しない

板情報は図3の通りです。左は2年ボンドローンです。 (OFZ2-9.15). 右はEUR/USDです。 (ED-9.15). OFZ2-9.15では買いの価格は4ですが、売りの価格層は8です。流動性があるED-9.15では、買いと売りの層が12で一致しています。ED-9.15のケースでは、2で割る方法が機能していますが、OFZ2では上手くいっていません。

インデックスを探すより信頼できる方法は、BOOK_TYPE_BUYタイプの最初のオーダーまでDOMを使うことです。前のインデックスは自動的にベストAskのインデックスになります。これはCMarketBookクラスが持つメソッドです。この記述を見てみましょう。:

void CMarketBook::SetBestAskAndBidIndex(void)
{
   if(!FindBestBid())
   {
      //ベストaskをスローフルサーチで探す
      int bookSize = ArraySize(MarketBook);   
      for(int i = 0; i < bookSize; i++)
      {
         if((MarketBook[i].type == BOOK_TYPE_BUY) || (MarketBook[i].type == BOOK_TYPE_BUY_MARKET))
         {
            m_best_ask_index = i-1;
            FindBestBid();
            break;
         }
      }
   }
}

このメソッドの主な意図はDOMの反復にあります。一度 BOOK_TYPE_BUYの最初のオーダーが板情報に合うと、ベストBidとベストAskのインデックスがセットされ、反復計算が中断されます。毎アップデートの板情報の反復計算はリソースに問題があります。

毎回の更新で計算させる代わりに、ベストBidとベストAskのインデックスを取得するリマインダーのオプションを得ます。板情報は通常、買いと売りの量が固定されています。したがって、板情報を毎回計算する必要はありません。前のインデックスを参照すれば十分で、それがベストBidかベストAskのインデックスかを調べればいいだけです。FindBestBidプライベートメソッドはこのために使います。中身を見てみましょう。:

//+------------------------------------------------------------------+
//| ベストAskからベストBidを見つける                                     |
//+------------------------------------------------------------------+
bool CMarketBook::FindBestBid(void)
{
   m_best_bid_index = -1;
   bool isBestAsk = m_best_ask_index >= 0 && m_best_ask_index < m_depth_total &&
                    (MarketBook[m_best_ask_index].type == BOOK_TYPE_SELL ||
                    MarketBook[m_best_ask_index].type == BOOK_TYPE_SELL_MARKET);
   if(!isBestAsk)return false;
   int bestBid = m_best_ask_index+1;
   bool isBestBid = bestBid >= 0 && bestBid < m_depth_total &&
                    (MarketBook[bestBid].type == BOOK_TYPE_BUY ||
                    MarketBook[bestBid].type == BOOK_TYPE_BUY_MARKET);
   if(isBestBid)
   {
      m_best_bid_index = bestBid;
      return true;
   }
   return false;
}

実行は簡単です。まず、このメソッドは現在のベストAskのインデックスがまだAskのインデックスであるかどうかを調べます。そして、ベストBidのインデックスをリセットし、ベストAskのインデックスの要素を参照しながら、もう一度探索します。:

int bestBid = m_best_ask_index+1;

見つかった要素がベストBidのインデックスの場合、前の板の状態は買いと売りと同じ数になります。そのため、SetBestAskAndBidIndexメソッドで、FindBestBidメソッドを反復の前に呼び出すので、DOMの反復は避けられます。よって、DOMの反復は、売り買いレベルの数の変化のある場合同様に、関数の最初のコールでのみ実行されます。

このコードは長くて一般的な板情報より複雑ですが、実行速度はより高速です。パフォーマンスの恩恵は、流動性がある大きな板情報で発揮されます。条件をチェックする単純な機構はかなり早く、チェックの量はfor文のループよりも少ないです。したがって、ベスト価格のインデックスを見つける意図を持つこのメソッドのパフォーマンスは、通常のものよりも高いものとなります。

 

2.4. GetDeviationByVolメソッドによる最大スリッページの決定

板情報は現在の相場の流動性を測るためにトレーダーに使われます。つまり、板情報はリスクを管理する道具の追加要素として利用されます。. 相場の流動性が低い場合、成行注文によるエントリーは高いスリッページを引き起こします。スリッページはいつも大きな損失となります。

そのような事態を避けるため、相場をコントロールするメソッドを使います。この件に関する詳しい情報は "EAの安全性をいかにして高めるか モスクワ取引所のトレードを例にして"を参照してください。したがって、ここではこの方法について詳細まで追いません。板情報にアクセスすることにより、どのように潜在的なスリッページを予測するかということにのみ焦点をあてます。スリッページのサイズは2つの要因で決まります。:

  • Bid側(売り側)の現在とAsk側(買い手側)の流動性;
  • 取引量

板情報の情報があると、オーダー価格のボリュームを見ることができます。オーダーのボリュームが分かれば、エントリーする際の加重平均を計算することができます。この価格とBid/Ask(エントリータイプによって変化)との差がスリッページです。

加重平均価格を手動で計算することは不可能です。ほんのわずかな間に大量の計算が要求されます。(板情報の状態は1秒間に何回も変わります。). よって、この作業はEAかインジケーターにやらせるのが自然です。

CMarketBookクラスには特殊な計算メソッド - GetDeviationByVol.があります。取引ボリュームがスリッページに影響するので、ボリュームを考慮に入れる必要があります。*このメソッドは、モスクワ取引所での取引を想定して、ボリュームの計算に整数型を使うため、少数型タイプにします。加えて、実行にはどちら側の流動性を計算するかを知る必要があるため、 ENUM_MBOOK_SIDE を使います。

//+------------------------------------------------------------------+
//| Side of MarketBook.                                              |
//+------------------------------------------------------------------+
enum ENUM_MBOOK_SIDE
{
   MBOOK_ASK,                    // Askサイド
   MBOOK_BID                     // Bidサイド
};

さて、GetDeviationByVolメソッドのソースコードを紹介しましょう。:

//+------------------------------------------------------------------+
//| ボリュームから偏差を取得Return -1.0 if deviation is                  |
//| ∞ (流動性が低い)                                                   |
//+------------------------------------------------------------------+
double CMarketBook::GetDeviationByVol(long vol, ENUM_MBOOK_SIDE side)
{
   int best_ask = InfoGetInteger(MBOOK_BEST_ASK_INDEX);
   int last_ask = InfoGetInteger(MBOOK_LAST_ASK_INDEX); 
   int best_bid = InfoGetInteger(MBOOK_BEST_BID_INDEX);
   int last_bid = InfoGetInteger(MBOOK_LAST_BID_INDEX);
   double avrg_price = 0.0;
   long volume_exe = vol;
   if(side == MBOOK_ASK)
   {
      for(int i = best_ask; i >= last_ask; i--)
      {
         long currVol = MarketBook[i].volume < volume_exe ?
                        MarketBook[i].volume : volume_exe ;   
         avrg_price += currVol * MarketBook[i].price;
         volume_exe -= MarketBook[i].volume;
         if(volume_exe <= 0)break;
      }
   }
   else
   {
      for(int i = best_bid; i <= last_bid; i++)
      {
         long currVol = MarketBook[i].volume < volume_exe ?
                        MarketBook[i].volume : volume_exe ;   
         avrg_price += currVol * MarketBook[i].price;
         volume_exe -= MarketBook[i].volume;
         if(volume_exe <= 0)break;
      }
   }
   if(volume_exe > 0)
      return -1.0;
   avrg_price/= (double)vol;
   double deviation = 0.0;
   if(side == MBOOK_ASK)
      deviation = avrg_price - MarketBook[best_ask].price;
   else
      deviation = MarketBook[best_bid].price - avrg_price;
   return deviation;
}

上記からもわかるように、このコードは結構な量があります。しかし、計算の原理は至極単純です。まず、板情報の反復計算は良い値から悪い値に向かって実行します。それぞれの方向で両サイド計算します。反復計算している間、現在のボリュームを合計に追加します。合計ボリュームがコールされ、それが要求ボリュームと一致した場合、ループから離脱します。これで、特定のボリュームの平均エントリー価格が計算されます。最後に、平均エントリー価格とベストBid/Askの差を計算します。絶対差は偏差になります。

このメソッドはDOMの反復計算の際に必要になります。DOMの2分の1だけ反復しますが、多くの場合部分的な計算で済みます。 それにもかかわらず、このような計算方法はDOMの流動性よりも多大な時間を必要とします。 したがって、この計算は別のメソッドとして直接的に実装されました。 つまり、はっきりとした状況で使える情報としてのみ利用できます。

 

2.5. CMarketBookクラスの実行サンプル

CMarketBookクラスの基本メソッドの実践に移りましょう。このサンプルはプログラミング初心者の方にとっても至極単純で分かりやすいものです。DOMの情報をもとにひとつの実行をする、テスト用のEAを書いてみましょう。確かに、今回の意図にはスクリプトもりとあてはまりますが、板情報へのアクセスはスクリプトでは不可能です。そのため、EAかインジケーターで試してみましょう。下記はEAのソースコードです。:

//+------------------------------------------------------------------+
//|                                               TestMarketBook.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#include <Trade\MarketBook.mqh>     // CMarketBookクラス
CMarketBook Book(Symbol());         //クラスの初期化

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
   PrintMbookInfo();
   return INIT_SUCCEEDED;
  }
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnTimer()
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Print MarketBook Info                                            |
//+------------------------------------------------------------------+
void PrintMbookInfo()
  {
   Book.Refresh();                  // 板情報の更新
   /* 統計情報の取得 */
   int total=Book.InfoGetInteger(MBOOK_DEPTH_TOTAL);
   int total_ask = Book.InfoGetInteger(MBOOK_DEPTH_ASK);
   int total_bid = Book.InfoGetInteger(MBOOK_DEPTH_BID);
   int best_ask = Book.InfoGetInteger(MBOOK_BEST_ASK_INDEX);
   int best_bid = Book.InfoGetInteger(MBOOK_BEST_BID_INDEX);

   printf("TOTAL DEPTH OF MARKET: "+(string)total);
   printf("NUMBER OF PRICE LEVELS FOR SELL: "+(string)total_ask);
   printf("NUMBER OF PRICE LEVELS FOR BUY: "+(string)total_bid);
   printf("INDEX OF BEST ASK PRICE: "+(string)best_ask);
   printf(INDEX OF BEST BID: "+(string)best_bid);
   
   double best_ask_price = Book.InfoGetDouble(MBOOK_BEST_ASK_PRICE);
   double best_bid_price = Book.InfoGetDouble(MBOOK_BEST_BID_PRICE);
   double last_ask = Book.InfoGetDouble(MBOOK_LAST_ASK_PRICE);
   double last_bid = Book.InfoGetDouble(MBOOK_LAST_BID_PRICE);
   double avrg_spread = Book.InfoGetDouble(MBOOK_AVERAGE_SPREAD);
   
   printf("BEST ASK PRICE: " + DoubleToString(best_ask_price, Digits()));
   printf("BEST BID PRICE: " + DoubleToString(best_bid_price, Digits()));
   printf("WORST ASK PRICE: " + DoubleToString(last_ask, Digits()));
   printf("WORST BID PRICE: " + DoubleToString(last_bid, Digits()));
   printf("AVERAGE SPREAD: " + DoubleToString(avrg_spread, Digits()));
  }
//+------------------------------------------------------------------+

このEAをOFZ2で稼働させると下記のレポートが出力されます。:

2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   AVERAGE SPREAD: 70
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   WORST BID PRICE: 9831
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   WORST ASK PRICE: 9999
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   BEST BID PRICE: 9840
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   BEST ASK PRICE: 9910
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   BEST BID INDEX: 7
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   BEST ASK INDEX: 6
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   NUMBER OF PRICE LEVELS FOR BUY: 2
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   NUMBER OF PRICE LEVELS FOR SELL: 7
2015.06.16 17:13:23.482 TestMarketBook (OFZ2-9.15,D1)   TOTAL DEPTH OF MARKET: 9

DOMのスクリーンショットとこのレポートを比較してみましょう。:


図4. テストリポート稼働時のOFZ2の板情報

出力されたインデックスが現在の板情報であることが分かります。 

 

チャプター3. パネルインジケーターとして板情報の作成


3.1. 板情報パネルのデザインの一般的な方法インジケータの生成

CMarketBookクラスへアクセスすると、板情報のパネルを生成することができます。インジケーターとしてパネルを作成してみます。インジケーターを使う理由は、EAの場合チャート上に1つしかセットできませんが、インジケーターの場合はいくつでもセットできるためです。チャートにパネル表示EAをセットしてしまうと、トレード用のEAをセットすることができなくなるので、非常に不便です。

今回はチャートに表示させたり、外したりできる板情報を実装します。表示と非表示には同じボタンを使います。:

 

図5. MetaTrader 5での標準トレードパネル

チャートの左上を基準として板情報を表示させます。これは板情報インジケーターがあるチャートじょうにEAがある可能性があるからです。右上にあるアイコンを隠してしまわないように、左に配置します。

インジケーターを作成する際、OnCalculateは必要ありません。これらの関数から得る情報を使わないので、それらの関数は空にしておきます。また、このインジケーターはグラフィック要素がないため、indicator_plots プロパティは0です。

OnBookEventが今回のメインとなる関数です。板情報の更新を受け取るため、現在のシンボルを設定します。MarketBookAdd関数を使って登録します。

板情報パネルはCBookPanelクラスに実装されています。これで、このクラスに関する詳細は以外、インジケーターを稼働させるためのコードが出来ました。:

//+------------------------------------------------------------------+
//|                                                   MarketBook.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window
#property indicator_plots 0
#include <Trade\MarketBook.mqh>
#include "MBookPanel.mqh"

CBookPanel Panel;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- インジケーターバッファマッピング
   MarketBookAdd(Symbol());
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| MarketBook change event                                          |
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
  {
   Panel.Refresh();
  }
//+------------------------------------------------------------------+
//| Chart events                                                     |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,         // イベントID 
                  const long& lparam,   // ロング型のイベントパラメーター
                  const double& dparam, //少数型のイベントパラメータ
                  const string& sparam) //文字型のイベントパラメータ
  {
   Panel.Event(id,lparam,dparam,sparam);
   ChartRedraw();
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
//---

//--- prev_calculatedの次の値をコール
   return(rates_total);
  }
//+------------------------------------------------------------------+

これにより、CBookPanelクラスは実行に必要な基本的な要素のみ含んでいます。: 板情報の矢印をクリックすると"MarketBook"ラベルがでます。インジケーターを稼働させるとき、チャートは下記のように見えます。:

 

図6. MarketBookパネルの位置

このクラスの各要素はCNodeクラスとは独立しています。このクラスは、下位クラスでオーバーライドすることができ、このような表示や非表示などの基本的な方法が含まれています。CNodeクラスはまた、各インスタンスのユニーク名を生成します。これにより、グラフィカルオブジェクトやプロパティの設定の標準関数が使いやすくなります。

 

3.2. クリックイベントのプロセスと板情報フォームの生成

現在のインジケーターでは矢印のクリックに反応しません。これが機能するようにしましょう。初めに行うことはOnChartEventイベントハンドラを入れることです。これをEventメソッドと呼びましょう。OnChartEventから得られるパラメータを使います。また、CNodeのベーシッククラスを拡張して、CArrayObj配列を作ります。これにはCNodeタイプのグラフィカル要素が含まれます。この先、同じようなタイプの要素を生成するのに役立ちます。

さて、CBookPanelクラスのソースコードと親クラスのCNodeが出来ました。: 

//+------------------------------------------------------------------+
//|                                                   MBookPanel.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#include <Trade\MarketBook.mqh>
#include "Node.mqh"
#include "MBookText.mqh"
#include "MBookFon.mqh"
//+------------------------------------------------------------------+
//| CBookPanel class                                                 |
//+------------------------------------------------------------------+
class CBookPanel : CNode
  {
private:
   CMarketBook       m_book;
   bool              m_showed;
   CBookText         m_text;
public:
   CBookPanel();
   ~CBookPanel();
   void              Refresh();
   virtual void Event(int id, long lparam, double dparam, string sparam);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CBookPanel::CBookPanel()
{
   m_elements.Add(new CBookFon(GetPointer(m_book)));
   ObjectCreate(ChartID(), m_name, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_XDISTANCE, 70);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_YDISTANCE, -3);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_COLOR, clrBlack);
   ObjectSetString(ChartID(), m_name, OBJPROP_FONT, "Webdings");
   ObjectSetString(ChartID(), m_name, OBJPROP_TEXT, CharToString(0x36));
}
CBookPanel::~CBookPanel(void)
{
   OnHide();
   m_text.Hide();
   ObjectDelete(ChartID(), m_name);
}

CBookPanel::Refresh(void)
{

}

CBookPanel::Event(int id, long lparam, double dparam, string sparam)
{
   switch(id)
   {
      case CHARTEVENT_OBJECT_CLICK:
      {
         if(sparam != m_name)return;
         if(!m_showed)OnShow();        
         else OnHide();
         m_showed = !m_showed;
      }
   }
}
//+------------------------------------------------------------------+

DOMの状態を更新するメソッドはまだ完全ではありません。これは後ほど作ります。現在の機能は板情報の最初のプロトタイプに近いものです。これまでのところ、矢印をクリックすると、グレーのキャンバスが表示されるのみです。もう一度クリックすると、消えます。:

 

図7. 板情報の外観

板情報はまだ完成には程遠いですが、少しずつ改良していきます。

 

3.3. 板情報のセル

セルは板情報の基盤になります。各セルはテーブル要素で、ボリュームと価格の情報が入ります。また、セルには色をつけます。: 買いリミットオーダーは青、売りリミットオーダーはピンクです。セルの数は各板情報によって異なります。したがって、すべてのセルは 動的に生成される必要があり、 CArrayObjデータコンテナに格納されます。すべてのセルが同じサイズと型を持つことから、変数型のセルのクラスはすべてのセルの型と同じになります。

セルがボリュームと価格を表示する際、CBookCeil クラスを使います。セルタイプはこのクラスのオブジェクトが生成されるときに指定されます。よって、各クラスのインスタンスはどのような情報が板から来るのかを通知され、表示の仕方や塗りつぶされる色が分かります。CBookCeilには2つのグラフィックプリミティブを使います。: テキストラベル OBJ_TEXT_LABEL と 長方形ラベルOBJ_RECTANBLE_LABELです。最初にテキストを表示し、板情報のセルを次に表示します。

CBookCeilクラスのソースコードです。:

//+------------------------------------------------------------------+
//|                                                   MBookPanel.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#include "Node.mqh"
#include <Trade\MarketBook.mqh>
#include "Node.mqh"
#include "MBookText.mqh"

#define BOOK_PRICE 0
#define BOOK_VOLUME 1

class CBookCeil : public CNode
{
private:
   long  m_ydist;
   long  m_xdist;
   int   m_index;
   int m_ceil_type;
   CBookText m_text;
   CMarketBook* m_book;
public:
   CBookCeil(int type, long x_dist, long y_dist, int index_mbook, CMarketBook* book);
   virtual void Show();
   virtual void Hide();
   virtual void Refresh();
   
};

CBookCeil::CBookCeil(int type, long x_dist, long y_dist, int index_mbook, CMarketBook* book)
{
   m_ydist = y_dist;
   m_xdist = x_dist;
   m_index = index_mbook;
   m_book = book;
   m_ceil_type = type;
}

void CBookCeil::Show()
{
   ObjectCreate(ChartID(), m_name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_XDISTANCE, m_xdist);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_YDISTANCE, m_ydist);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_COLOR, clrBlack);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_FONTSIZE, 9);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_BORDER_TYPE, BORDER_FLAT);
   m_text.Show();
   m_text.SetXDist(m_xdist+10);
   m_text.SetYDist(m_ydist+2);
   Refresh();
}

void CBookCeil::Refresh(void)
{
   ENUM_BOOK_TYPE type = m_book.MarketBook[m_index].type;
   if(type == BOOK_TYPE_BUY || type == BOOK_TYPE_BUY_MARKET)
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrCornflowerBlue);
   else if(type == BOOK_TYPE_SELL || type == BOOK_TYPE_SELL_MARKET)
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrPink);
   else
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrWhite);
   MqlBookInfo info = m_book.MarketBook[m_index];
   if(m_ceil_type == BOOK_PRICE)
      m_text.SetText(DoubleToString(info.price, Digits()));
   else if(m_ceil_type == BOOK_VOLUME)
      m_text.SetText((string)info.volume);
}

void CBookCeil::Hide(void)
{
   OnHide();
   m_text.Hide();
   ObjectDelete(ChartID(),m_name);
}

このクラスのメインは表示とリフレッシュのメソッドです。後ろの方では、セルタイプに応じて関連する色とボリューム・価格があります。セルを作るためには、まずタイプを指定しなければなりません。次に、X軸、Y軸のロケーション、このセルに応じたDOMのインデックス、情報を受け取るセルの板情報を指定します。

プライベートメソッド CreateCeils がクラス内でセルを生成します。以下はそのコードです。:

void CBookFon::CreateCeils()
{
   int total = m_book.InfoGetInteger(MBOOK_DEPTH_TOTAL);
   for(int i = 0; i < total; i++)
   {
      CBookCeil* Ceil = new CBookCeil(0, 12, i*15+20, i, m_book);
      CBookCeil* CeilVol = new CBookCeil(1, 63, i*15+20, i, m_book);
      m_elements.Add(Ceil);
      m_elements.Add(CeilVol);
      Ceil.Show();
      CeilVol.Show();
   }
}

矢印をクリックするとコールされ、板情報を拡張します。

これで板情報を新しくする準備が整いました。変更を加えた後、コンパイルします。するとインジケーターは新しくなります。:

 

図8. 板情報のインジケーターの最初のバージョン

3.4. 板のヒストグラムの表示

得られた板情報はすでに基本的な機能が含まれています。トレードレベル、ボリューム、売りと買いのリミット価格を表示します。これで、板情報の更新情報もその都度変わります。しかし、このテーブルでボリュームのトラッキングをするのは容易ではありません。例えば、MetaTrader 5で、 DOMはヒストグラムで表示されます。これは板情報の最大ボリュームに対して相対的な現在のボリュームです。また、今の板と同じように機能しません。

この問題を解くには別の方法が必要です。最も簡単な方法は、CBookCeilクラスで計算を直接的に行うことです。したがって、次のリフレッシュメソッドを書く必要があります。:

void CBookCeil::Refresh(void)
{
   ...
   MqlBookInfo info = m_book.MarketBook[m_index];
   ...
   //板情報の更新
   int begin = m_book.InfoGetInteger(MBOOK_LAST_ASK_INDEX);
   int end = m_book.InfoGetInteger(MBOOK_BEST_ASK_INDEX);
   long max_volume = 0;
   if(m_ceil_type != BOOK_VOLUME)return;
   for(int i = begin; i < end; i++)
   {
      if(m_book.MarketBook[i].volume > max_volume)
         max_volume = m_book.MarketBook[i].volume;
   }
   double delta = 1.0;
   if(max_volume > 0)
      delta = (info.volume/(double)max_volume);
   long size = (long)(delta * 50.0);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_XSIZE, size);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_YDISTANCE, m_ydist);
}

DOMの計算には、DOMの最大ボリュームがあり、現在の値は最大で割った値となります。得られた値は、テーブルセルの最大幅をかけられます。 (50ピクセルの一定値に応じて). キャンバスの幅はヒストグラムになります。:

 

図9. ヒストグラムボリュームの板

しかし、このコードの問題はDOMの計算が毎回行われることです。板情報には40もの要素があり、一回のループで800回の計算を行うことになります。板情報の各計算は片側のみ行われ、各セルには12回の計算があります。 (板の半分)。現在のコンピューターではこのタスクを行うことはできますが、最速ではなく効率的でもないので、とても不完全なものになります。

 

3.5. 板の最大ボリュームの計算と最適化

残念ながら、板の完全な繰り返し計算を取り除くことは不可能です。各板情報が更新されたあと、最大ボリュームとその価格が動的に変化します。しかし、その計算数を最小化することができます。この為に、DOMのリフレッシュごとに計算しない方法を習得する必要があります。2番目にしなければならないことは、コール回数を最小化することです。このためには、必要な時にだけ行う繰り返し計算をさせなければなりません。すべての計算を板のCMarketBookクラスに移します。そして、特殊な計算サブクラス CBookCalculationをCMarketBookに配置します。ソースコードは次の通りです。: 

class CMarketBook;

class CBookCalculation
{
private:
   int m_max_ask_index;         // 最大ボリュームのAskインデックス
   long m_max_ask_volume;       //最小ボリュームのAskインデックス
   
   int m_max_bid_index;         // 最大ボリュームのBidインデックス
   long m_max_bid_volume;       // 最小ボリュームのBidインデックス
   
   long m_sum_ask_volume;       // DOMのAskのボリュームの合計
   long m_sum_bid_volume;       // DOMのBidのボリュームの合計
   
   bool m_calculation;          // 計算が実行されるときのフラグ
   CMarketBook* m_book;         //板インジケーター
   
   void Calculation(void)
   {
      // ASKサイド
      int begin = (int)m_book.InfoGetInteger(MBOOK_LAST_ASK_INDEX);
      int end = (int)m_book.InfoGetInteger(MBOOK_BEST_ASK_INDEX);
      for(int i = begin; i < end; i++)
      {
         if(m_book.MarketBook[i].volume > m_max_ask_volume)
         {
            m_max_ask_index = i;
            m_max_ask_volume = m_book.MarketBook[i].volume;
         }
         m_sum_ask_volume += m_book.MarketBook[i].volume;
      }
      // Bidサイド
      begin = (int)m_book.InfoGetInteger(MBOOK_BEST_BID_INDEX);
      end = (int)m_book.InfoGetInteger(MBOOK_LAST_BID_INDEX);
      for(int i = begin; i < end; i++)
      {
         if(m_book.MarketBook[i].volume > m_max_bid_volume)
         {
            m_max_bid_index = i;
            m_max_bid_volume = m_book.MarketBook[i].volume;
         }
         m_sum_bid_volume += m_book.MarketBook[i].volume;
      }
      m_calculation = true;
   }
   
public:
   CBookCalculation(CMarketBook* book)
   {
      Reset();
      m_book = book;
   }
   
   void Reset()
   {
      m_max_ask_volume = 0.0;
      m_max_bid_volume = 0.0;
      m_max_ask_index = -1;
      m_max_bid_index = -1;
      m_sum_ask_volume = 0;
      m_sum_bid_volume = 0;
      m_calculation = false;
   }
   int GetMaxVolAskIndex()
   {
      if(!m_calculation)
         Calculation();
      return m_max_ask_index;
   }
   
   long GetMaxVolAsk()
   {
      if(!m_calculation)
         Calculation();
      return m_max_ask_volume;
   }
   int GetMaxVolBidIndex()
   {
      if(!m_calculation)
         Calculation();
      return m_max_bid_index;
   }
   
   long GetMaxVolBid()
   {
      if(!m_calculation)
         Calculation();
      return m_max_bid_volume;
   }
   long GetAskVolTotal()
   {
      if(!m_calculation)
         Calculation();
      return m_sum_ask_volume;
   }
   long GetBidVolTotal()
   {
      if(!m_calculation)
         Calculation();
      return m_sum_bid_volume;
   }
};

板の計算とリソース強度計算はCalculateプライベートメソッド内に隠れています。計算フラグ m_calculate がfalseにリセットされたときだけ、コールされます。このフラグのリセットはリセットメソッドでのみ起こります。このクラスはCMarketBookクラスで動くようにデザインされているため、このクラスだけアクセスできます。

板情報の更新の後、リフレッシュメソッドCMarketBookは計算モジュールの状態をリセットします。これにより、板情報の計算は2回のアップデートに一回以上起こらなくなります。また、予約注文が使われます。言い換えると、CBookCalcultae クラスの計算メソッドは、6分の1のパブリックメソッドからコールされたときにだけ、呼び出されます。

ボリュームの探索に加え、板情報の計算を行うこのクラスは、売りと買いのリミットオーダーの総数を含むフィールドを持ちます。配列の合計サイクルが計算されるので、これらのパラメーターを計算する追加の時間は必要ありません。

これで、板情報の毎時計算よりもスマートな方法ができました。これによりリソースを劇的に減らすことができ、板情報の実行を効率よく、高速にすることができます。

 

3.6. クリック機能の完了:ボリュームヒストグラムとラインの分割

インジケーターの完成は目前です。最大ボリュームを見つける実践的な必要性は効果的な計算をもたらします。もしこの先更なる計算パラメータが板情報に必要な場合、それは簡単です。そのためには、CBookCalculateクラスを拡張すれだけで済みます。その際は、適切な修飾子を ENUM_MBOOK_INFO_INTEGER と ENUM_MBOOK_INFO_DOUBLE に追加してください。

さて、リフレッシュメソッドを各セルで再書き込みし、稼働させなければなりません。:

void CBookCeil::Refresh(void)
{
   ENUM_BOOK_TYPE type = m_book.MarketBook[m_index].type;
   long max_volume = 0;
   if(type == BOOK_TYPE_BUY || type == BOOK_TYPE_BUY_MARKET)
   {
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrCornflowerBlue);
      max_volume = m_book.InfoGetInteger(MBOOK_MAX_BID_VOLUME);
   }
   else if(type == BOOK_TYPE_SELL || type == BOOK_TYPE_SELL_MARKET)
   {
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrPink);
      max_volume = m_book.InfoGetInteger(MBOOK_MAX_ASK_VOLUME); //ボリュームはすでに計算されています。再計算は起こりません。
   }
   else
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrWhite);
   MqlBookInfo info = m_book.MarketBook[m_index];
   if(m_ceil_type == BOOK_PRICE)
      m_text.SetText(DoubleToString(info.price, Digits()));
   else if(m_ceil_type == BOOK_VOLUME)
      m_text.SetText((string)info.volume);
   if(m_ceil_type != BOOK_VOLUME)return;
   double delta = 1.0;
   if(max_volume > 0)
      delta = (info.volume/(double)max_volume);
   long size = (long)(delta * 50.0);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_XSIZE, size);
   ObjectSetInteger(ChartID(), m_name, OBJPROP_YDISTANCE, m_ydist);
}

見た目上は、今回のインジケーターは従来のものと同じですが、実際には、ヒストグラムの計算スピードが劇的に良くなっています。これが今回のプログラミングが意図したところです。効率的で使いやすく、実装の際に複雑さがないものです。

ボリュームヒストグラムの見た目で、BidとAskのボリューム間のラインが非常にあいまいになりました。そのため、CBookPanel にCBookLineを追加し、ラインを追加します。:

class CBookLine : public CNode
{
private:
   long m_ydist;
public:
   CBookLine(long y){m_ydist = y;}
   virtual void Show()
   {
      ObjectCreate(ChartID(),     m_name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
      ObjectSetInteger(ChartID(), m_name, OBJPROP_YDISTANCE, m_ydist);
      ObjectSetInteger(ChartID(), m_name, OBJPROP_XDISTANCE, 13);
      ObjectSetInteger(ChartID(), m_name, OBJPROP_YSIZE, 3);
      ObjectSetInteger(ChartID(), m_name, OBJPROP_XSIZE, 108);
      ObjectSetInteger(ChartID(), m_name, OBJPROP_COLOR, clrBlack);
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BGCOLOR, clrBlack);
      ObjectSetInteger(ChartID(), m_name, OBJPROP_BORDER_TYPE, BORDER_FLAT);
   }
};

これは非常にシンプルなクラスで、基本的には、場所を決定します。Y軸の位置はShowメソッドでの生成時に計算されなければなりません。ベストAsk価格のインデックスが分かると、これは簡単です。:

long best_bid = m_book.InfoGetInteger(MBOOK_BEST_BID_INDEX);
long y = best_bid*15+19;

この場合、best_bidセルのインデックスは各セルの幅でかけられます。(15ピクセル) そして、19ピクセルが追加で足されます。

これで今回の板情報は最も小さい表示を取得し、実用の段階にまで来ました。確かに、もっと突き詰めることもできます。その場合、 MetaTrader 5 の板情報により近いものになります。

しかし、この記事での主となる目的は異なります。板情報パネルはCMarketBookクラスの可能性の提示として実装しました。これでクラスをより早く、より良く、より機能的にすることができ、目的を達成することができます。ここにショートビデオを用意しました。これにはこれまでの流れが載っています。下記は、DOMの動的表示です。:



3.7. リミットオーダーの合計数に関する情報のDOMのプロパティの追加

モスクワ取引所でのトレードの特殊な点は、リミットオーダーの総数がリアルタイムで見れることです。この記事では、特定のマーケットに位置しない板情報の実行にハイライトしています。しかし、この特定の情報はターミナルのシステムレベルで利用可能なものです。さらに言うと、板情報によるデータを拡張します。プロパティ修飾子を拡張し、これらのプロパティのサポートを板情報に直接的に含みます。

モスクワ証券取引では、次のリアルタイム情報があります。:

  • 現在の売りのリミットオーダーの数;
  • 現在の買いリミットオーダーの数;
  • 売りのリミットオーダーのボリュームの合計;
  • 買いのリミットオーダーのボリュームの合計;
  • 約定したポジションの数 (ヒューチャーのみ)

ポジションは直接的にはリミットオーダーの数とは関係ありませんが(現在の流動性では)、それにもかかわらず、この情報はリミットオーダーに関する情報への関連として必要になります。CMarketBookクラスへのアクセス介して適切にします。この情報にアクセスするには、SymbolInfoInteger とSymbolInfoDouble 関数を使わなければなりません。しかし、データが1つの場所からアクセスできるようにするには、板情報クラスを拡張しなければなりません。これには追加の要素やInfoGetInteger や InfoGetDouble 関数の変更が必要です。:

long CMarketBook::InfoGetInteger(ENUM_MBOOK_INFO_INTEGER property)
{
   switch(property)
   {
      ...
      case MBOOK_BUY_ORDERS:
         return SymbolInfoInteger(m_symbol, SYMBOL_SESSION_BUY_ORDERS);
      case MBOOK_SELL_ORDERS:
         return SymbolInfoInteger(m_symbol, SYMBOL_SESSION_SELL_ORDERS);
      ...
   }
   return 0;
}

 

double CMarketBook::InfoGetDouble(ENUM_MBOOK_INFO_DOUBLE property)
{
   switch(property)
   {
      ...
      case MBOOK_BUY_ORDERS_VOLUME:
         return SymbolInfoDouble(m_symbol, SYMBOL_SESSION_BUY_ORDERS_VOLUME);
      case MBOOK_SELL_ORDERS_VOLUME:
         return SymbolInfoDouble(m_symbol, SYMBOL_SESSION_SELL_ORDERS_VOLUME);
      case MBOOK_OPEN_INTEREST:
         return SymbolInfoDouble(m_symbol, SYMBOL_SESSION_INTEREST);
   }
   return 0.0;  
}

見てもわかるように、このコードは極めてシンプルです。実際、MQLの標準関数のコピーです。しかし、CMarketBookクラスを追加する意味は、ユーザーに便利で 一か所で操作できるモジュールを提供することであり、リミットオーダーやその価格に関する情報を得やすくするためです。


チャプター4. CMarketBookクラスのドキュメント

CMarketBook板情報の実行に必要なクラスの生成と説明は終わりました。チャプター4では、パブリックメソッドについて扱います。この章で、クラスの実行はよりシンプルで実用的なものとなり、トレードが可能となります。また、このチャプタ―では、このクラスの簡単なガイドブックとしても役立ちます。


4.1. 板の基本情報の取得メソッド

Refresh() メソッド

板情報の状態の更新OnBookEventのすべての呼び出しで、このメソッドを呼び出す必要があります。

void        Refresh(void);

Use

使い方の例はチャプター4のセクションにあります。

 

InfoGetInteger()メソッド

ENUM_MBOOK_INFO_INTEGERに応じて、板情報のプロパティの1つを返します。サポートされた特徴のリストはENUM_MBOOK_INFO_INTEGERにあります。

long        InfoGetInteger(ENUM_MBOOK_INFO_INTEGER property);

返り値

整数型-板情報のロングタイプ失敗した場合 -1を返します。

Use

使い方の例はチャプター4のセクションにあります。 

 

InfoGetDouble() メソッド

ENUM_MBOOK_INFO_DOUBLEに応じていた情報のプロパティの1つを返します。サポートされた特徴のリストはENUM_MBOOK_INFO_DOUBLEにあります。

double      InfoGetDouble(ENUM_MBOOK_INFO_DOUBLE property);

返り値

少数型の板情報のプロパティ失敗した場合 -1.0を返します。

Use

使い方の例はチャプター4のセクションにあります。  

 

IsAvailable() Method

板情報が利用可能な場合trueを返します。それ以外の場合falseを返します。このメソッドは板情報クラスの実行の前にコールしなければなりません。これは、実行可能かどうかを調べます。

bool        IsAvailable(void);

返り値

板情報が利用可能なときtrueを返し、それ以外の場合falseを返します。

 

SetMarketBookSymbol() method

板情報を得たいシンボルのセットCMarketBookクラスのインスタンスを生成するとき、板情報のシンボルをセットすることができます。コンストラクタで使われている名前と完全に一致していることを確認してください。

bool        SetMarketBookSymbol(string symbol);

返り値

'symbol'がトレード可能な場合trueを返し、そうでない場合falseを返す。

 

GetMarketBookSymbol() Method

現在のクラスのインスタンスがセットされているシンボルを返す 

string      GetMarketBookSymbol(void);

返り値

板情報が現在のクラスのインスタンスに表示している名前を返す選択されていない場合、もしくは利用できない場合NULL 

 

GetDeviationByVol() Method

潜在スリッページを返すこの値は想定されたスリッページで、板情報がエントリータイミングで変化すると、実際のスリッページはこの関数で得られたものとは異なる可能性があります。しかし、スリッページの予想に関しては十分に精確で、エントリーの際に利用でき、追加情報としても利用できます。

このメソッドには2つのパラメータがあります。: 取引量と決済のときの流動タイプ例えば、売りのリミットオーダーの流動性は買いリミットオーダーで使われ、この場合、 MBOOK_ASKのタイプはサイドとして指定されます。売りの場合、反対に、MBOOK_BIDが指定されます。DOMの再度に関するさらなる情報は、ENUM_BOOK_SIDE の説明を参照してください。

double     GetDeviationByVol(long vol, ENUM_MBOOK_SIDE side);

パラメータ:

  • [in] vol — 取引量;
  • [in] side — 取引をする板情報のサイド  

返り値

潜在スリッページの量はインストゥルメントポイントです。

 

4.2. CMarketBookクラスの列挙型と修飾子

ENUM_MBOOK_SIDEの列挙型

ENUM_BOOK_SIDEは、流動性のタイプを示す修飾子を含んでいます。下記はフィールドの列挙です。:

フィールド説明
MBOOK_ASK 売りのリミットオーダーによる流動性
MBOOK_BID 買いのリミットオーダーによる流動性

Note 

成行注文はすべてリミットオーダーで実行します。オーダーの方向によって、買いか売りのリミットオーダーを使います。買い取引の下方向は、売りリミットオーダーです。売り取引の下方向は買いリミットオーダーです。このようにして、修飾子は板情報から指示します。: 買い側 売り側修飾子はGetDeviationByVol 関数によって使われ、流動性のサイドを知るために要求されます。

 

ENUM_MBOOK_INFO_INTEGERの列挙型

ENUM_MBOOK_INFO_INTEGER は、プロパティ修飾子を含み、 InfoGetInteger メソッドを使って得られます。下記はフィールドの列挙です。:

フィールド説明
MBOOK_BEST_ASK_INDEX ベストAskのインデックス
MBOOK_BEST_BID_INDEX ベストBidのインデックス
MBOOK_LAST_ASK_INDEX ワーストのインデックス、もしくは、直近のAsk価格
MBOOK_LAST_BID_INDEX ワーストのインデックス、もしくは、直近のBid
MBOOK_DEPTH_ASK Askサイドからの板情報、もしくは、トレードレベルの合計
MBOOK_DEPTH_BID Bidサイドからの板情報、もしくは、トレードレベルの合計
MBOOK_DEPTH_TOTAL 買い、売りのトレードレベルの数か板情報の合計
MBOOK_MAX_ASK_VOLUME Askの最大ボリューム
MBOOK_MAX_ASK_VOLUME_INDEX Askの最大ボリュームのインデックス
MBOOK_MAX_BID_VOLUME Bidの最大ボリューム
MBOOK_MAX_BID_VOLUME_INDEX Bidの最大ボリュームのインデックス
MBOOK_ASK_VOLUME_TOTAL 売りリミットオーダーの合計数
MBOOK_BID_VOLUME_TOTAL  買いリミットオーダーの合計数
MBOOK_BUY_ORDERS 買いのリミットオーダーの合計ボリューム
MBOOK_SELL_ORDERS 売りのリミットオーダーの合計ボリューム

 

ENUM_MBOOK_INFO_DOUBLEの列挙型

ENUM_MBOOK_INFO_DOUBLEは、プロパティ修飾子を含み、 InfoGetDoubleメソッドを使って得られます。下記はフィールドの列挙です。:

フィールド説明
MBOOK_BEST_ASK_PRICE ベストAsk価格
MBOOK_BEST_BID_PRICE ベストBid価格
MBOOK_LAST_ASK_PRICE ワーストAsk価格
MBOOK_LAST_BID_PRICE ワーストBid価格
MBOOK_AVERAGE_SPREAD BidとAskの平均の差、スプレッド
MBOOK_OPEN_INTEREST  オープンポジション
MBOOK_BUY_ORDERS_VOLUME 買いオーダーの数
MBOOK_SELL_ORDERS_VOLUME  売りオーダーの数

 

4.3. CMarketBookクラスの利用例

これの例では、EAのソースコードとして板情報を表示さあせます。:

//+------------------------------------------------------------------+
//|                                               TestMarketBook.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#include <Trade\MarketBook.mqh>     // CMarketBook クラスを含ませる
CMarketBook Book(Symbol());         // 現在のクラスを初期化

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int OnInit()
  {
   PrintMbookInfo();
   return INIT_SUCCEEDED;
  }
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnTimer()
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Print MarketBook Info                                            |
//+------------------------------------------------------------------+
void PrintMbookInfo()
  {
   Book.Refresh();                                                   // 板情報の更新
//--- メイン整数値を取得
   int total=(int)Book.InfoGetInteger(MBOOK_DEPTH_TOTAL);            //板情報の合計を取得
   int total_ask = (int)Book.InfoGetInteger(MBOOK_DEPTH_ASK);        // 売り価格の総量を取得
   int total_bid = (int)Book.InfoGetInteger(MBOOK_DEPTH_BID);        // 買い価格の総量を取得
   int best_ask = (int)Book.InfoGetInteger(MBOOK_BEST_ASK_INDEX);    // ベストAskのインデックス
   int best_bid = (int)Book.InfoGetInteger(MBOOK_BEST_BID_INDEX);    // ベストBidのインデックス

//--- 基本スタティクスの表示
   printf("TOTAL DEPTH OF MARKET: "+(string)total);
   printf("NUMBER OF PRICE LEVELS FOR SELL: "+(string)total_ask);
   printf("NUMBER OF PRICE LEVELS FOR BUY: "+(string)total_bid);
   printf("INDEX OF BEST ASK PRICE: "+(string)best_ask);
   printf(INDEX OF BEST BID: "+(string)best_bid);
   
//--- 少数データの表示
   double best_ask_price = Book.InfoGetDouble(MBOOK_BEST_ASK_PRICE); // ベストAsk
   double best_bid_price = Book.InfoGetDouble(MBOOK_BEST_BID_PRICE); // ベストBid
   double last_ask = Book.InfoGetDouble(MBOOK_LAST_ASK_PRICE);       // ワーストAsk
   double last_bid = Book.InfoGetDouble(MBOOK_LAST_BID_PRICE);       //ワーストBid
   double avrg_spread = Book.InfoGetDouble(MBOOK_AVERAGE_SPREAD);    // 板情報の平均スプレッド
   
//--- 価格とスプレッドの表示
   printf("BEST ASK PRICE: " + DoubleToString(best_ask_price, Digits()));
   printf("BEST BID PRICE: " + DoubleToString(best_bid_price, Digits()));
   printf("WORST ASK PRICE: " + DoubleToString(last_ask, Digits()));
   printf("WORST BID PRICE: " + DoubleToString(last_bid, Digits()));
   printf("AVERAGE SPREAD: " + DoubleToString(avrg_spread, Digits()));
  }
//+------------------------------------------------------------------+

 

結論

この記事では動的にする方法について扱いました。技術的な観点から板情報を考察し、高いパフォーマンスのクラスを作成しました。例として、そのクラスをベースとした板情報インジケーターを作成し、価格チャートにコンパクトに表示させることに成功しました。

今回の板情報インジケーターは非常に基本的ですが、いくつかの点が欠落しています。しかし、主となる目的は達成されました。CMarketBookクラスを用いて、EAやインジケーターに簡単に導入できるようにし、分析の助けとすることができます。CMarketBookクラスをデザインする際、パフォーマンスに対してたくさんの気を使う必要がありました。それだけ、板情報は動的な表で、更新頻度が早いということです。

この記事で紹介したクラスは、スキャルピングや高速取引のベースになることでしょう。ご自身のシステムにぜひ追加してください。そのためには、板情報クラスをCMarketBookから生成し、拡張メソッドを書けば良いだけです。板情報によって得られる情報をもとに、あなたの労力が楽になり、システムを信頼に足るものにできると思います。