MetaTrader 5でのトレードイベント

MetaQuotes | 1 2月, 2016

はじめに

トレード処理を実行する全てのコマンドは、MetaTrader5クライアントターミナルからトレードサーバーに送信リクエストを通して渡されます。それぞれのリクエストは、要求された処理に応じて正しく実行されます;さもなければ、最初の検査を通ることができず、さらなる処理を行うサーバーによって受理されません。

トレードサーバーによって受け取られたリクエストは、市場価格によって即座に実行されるか、未決注文の状態になるような注文形式で保存されます。注文は、実行されるか、キャンセルされるまでサーバーで保存されます。注文実行結果が、取引として表示されます。

取引は、特定のシンボルによってトレードポジションを変化させ、ポジションをオープン・クローズ、増加・減少、反転させることができます。従って、オープンポジションは常に一つかそれ以上の取引を実行した結果です。さらに詳しい情報は、MetaTrader 5の注文、ポジション、取引 という記事にて記載されています。

この記事は、コンセプト、用語、リクエストの送信から処理後のトレード履歴への移動までの流れの処理について記載しています。


クライアントターミナルからトレードサーバーへのリクエストの受け渡し

トレード処理を実行するため、注文をトレードシステムに送る必要があります。リクエストは常にクライアントターミナルから注文を送信することを通してトレードサーバーに送られます。手動か、MQL5プログラムを使用するかなど、いかにあなたがトレードを行おうとも、リクエストストラクチャーは正しく記載される必要があります。

手動でトレード処理を実行するために、F9キーを押してトレードリクエストを記入するためのダイアログウィンドウを開いて下さい。MQL5を通して自動的にトレードを行う際は、リクエストはOrderSend()関数を用いて送られます。間違いのあるリクエストはトレードサーバーのオーバーロードにつながるので、それぞれのリクエストはOrderCheck()関数を用いて送信される前にチェックする必要があります。MqlTradeCheckresultストラクチャーによって記載された変数にリクエストのチェック結果が格納されます。

重要:それぞれのリクエストは、トレードサーバーに送信される前にクライアントターミナルにて正誤をチェックされます。わざと間違えたリクエスト(百万ロット購入する、負の価格で購入する)は、クライアントターミナル外に渡されません。MQL5のミスによって引き起こされる不正リクエストからトレードサーバーを守るためにそれは実行されます。

リクエストがトレードサーバーについたら、最初のチェックを通ります;

サーバーの最初のチェックを通らない間違いのリクエストは拒否されます。クライアントターミナルは常にレスポンスを送ることによってリクエストのチェック結果について通知を受けています。トレードサーバーのレスポンスは、MqlTradeResult型の変数から取得され、リクエストを送信する際にOrderSend()関数にて2番目のパラメーターとして渡されます。

クライアントターミナルからのトレードリクエストのトレードサーバーへの送信

もしリクエストが最初のチェックを通れば、処理待ちのリクエストに配置されます。リクエストの処理結果として、(トレード処理を実行するためのコマンド)注文がトレードサーバーにて作成されます。しかしながら、注文の作成に至らない二種類のリクエストがあります。

  1. (損切り・利食いへ)ポジションを変更するリクエスト
  2. 未決注文を修正するリクエスト(価格レベルや期限切れ時間)

クライアントターミナルは、そのリクエストが受け取られ、MetaTrader5プラットフォームのサブシステムに配置されたというメッセージを受信します。サーバーは受け取られたリクエストをさらなる処理のためにリクエストキューに配置した後、以下のいずれかを実行します;

サーバーのキューのリクエストの期限は3分間のみです。その期間が過ぎれば、そのリクエストはリクエストキューから削除されます。


トレードイベントのトレードサーバーからクライアントターミナルへの送信

イベントモデルとイベントハンドリング関数は、MQL5言語にて実装されています。 定義済みイベントへのレスポンスでは、MQL5実行環境は適切な関数 - イベントハンドラを呼び出します。トレードイベントの処理のために、定義済みの関数OnTrade()があります;注文、ポジション、取引を扱うためのコードは、その中に格納されています。この関数は、エキスパートアドバイザーでのみ呼び出され、同じ名前や型で追加してもインジケーターやスクリプトでは使用されません。

トレードイベントは以下の場合にサーバーによって生成されます;

ある処理がいくつかのイベントを引き起こすことがあることに注意してください。例えば、未決注文を行うと、二つのイベントを発生させます;

  1. トレード履歴へ記述される取引が表示されます;
  2. アクティブな注文のリストから未決注文を注文履歴のリストへ移動させます。

複数のイベントのその他の例は、必要な額が単体の反対のオファーから取得できない場合、単体の注文に基づいていくつかの取引を実行することです。そのトレードサーバーが各イベントに関するメッセージを作成し、クライアントターミナルに送信します。そのため、OnTrade()関数は単体のイベントのための少しの間で呼び出されます。これは、MetaTrader5プラットフォームのトレードサブシステムにて注文の処理の手続きのシンプルな例です。

こちらがその例です: EURUSDで10ロットを購入するための未決注文の実行を待つ間、1、4、5ロットを売るための反対オファーが表示されます。それら三つのリクエストは、10ロットの必要な額を提供し、もしその規定が部分的にトレード処理の実行を許可した場合、一つずつ実行されます。

4つの注文の実行結果として、そのサーバーは既存の反対のリクエストに基づいて1, 4と5ロットの3つの取引を実行します。この場合、いくつのトレードイベントが生成されるでしょうか?一つのロットを売るための最初の反対のリクエストは1ロットの取引の実行につながります。これが最初のトレードイベントです(1ロット取引). しかし、10ロットを購入するための未決注文もまた変更されました;現在ではEURUSDの9ロットを購入するための注文になっています。未決注文の量の変化は、2番目のトレードイベントです(未決注文量の変化)

トレードイベントの生成

4つのロットの2番目の取引において、その他の二つのトレードイベントが生成されます。それぞれに関するメッセージがEURUSDの10ロット購入のための未決注文を開始するクライアントターミナルに送信されます。

最後の5ロットの取引が3つのトレードイベントを発生させます。

  1. 5ロットの取引、
  2. 取引額の変化、
  3. 注文のトレード履歴への移動。

取引の実行結果として、そのクライアントターミナルは次々に7つのトレードイベントを受け取ります(クライアントターミナルとトレードサーバー間の連携は安定しており、メッセージが損失されないと想定されています)。それらのメッセージは、OnTrade()関数を用いてエキスパートアドバイザーにて処理される必要があります。

重要:トレードに関する各メッセージは、いくつかのリクエストの結果として表れます。各リクエストは、いくつかのトレードイベントを発生させます。イベントの処理は、いくつかのステージにて実行され、それぞれの処理は注文、ポジション、トレード履歴の状態を変化させるので、「1リクエストー1トレードイベント」という分を信じることはできません。


トレードサーバーにより注文を処理する

実行待ちの全ての注文は、( 実行における条件が満たされるか、キャンセルされるか)最終的に履歴に移されます 注文のキャンセルにおいていくつかの手段があります:

アクティブな注文が履歴に移される理由がどのようなものでも、変更に関するメッセージはクライアントターミナルに送信されます。トレードイベントに関するメッセージは、全てのクライアントターミナルではなく、一致するアカウントに接続されたものにのみ送信されます。

重要:トレードサーバーによるリクエストを受け取った事実は、要求された処理の実行につながるわけではありません。そのリクエストがトレードサーバーに送信された後、検証を通ったことを意味します。

そのため、OrderSend()関数の記載によると以下のように記載されています:

返された値

リクエストのチェックが成功した場合、OrderSend()関数はtrueを返します- これは、トレード処理の実行が成功した印ではありません。. その関数の実行結果の詳しい記述に関して、 structure MqlTradeResultのフィールドを分析してください。


クライアントターミナルのトレードと履歴の更新

トレードイベントとトレード履歴における変化に関するメッセージは、個別のチャネルから送られます。OrderSend(9関数を用いて購入するリクエストを送信する際、リクエストの検証が成功して作成される注文チケットを取得できます。同時に、その注文はクライアントターミナルに表示されず、OrderSelect()関数を用いてそれを選択しようと失敗します。

トレードサーバーからの全てのメッセージは、クライアントターミナルに個別に送信されます。

上記の図にて、いかにトレードサーバーがその注文チケットをMQL5プログラムに伝えるかをご覧になれます。しかし、トレードイベントに関するメッセージはまだ到着していません。アクティブな注文リストの変化に関するメッセージは同様にまだ到着していません。

取引が実行された際に、新規注文が現れたことに関するメッセージがプログラムに到着する場合があります。従って、その注文はすでにアクティブな注文のリストになく、履歴にあります。リクエストの処理スピードがネットワークを通るメッセージの現在の配達スピードと比較するとかなり高いので、これは実際に起こる状況なのです。


MQL5のトレードイベントのハンドリング

トレードサーバーでの全ての処理やトレードイベントに関するメッセージの送信は非同期で実行されます。トレードアカウントにて何が変更されたかを確認するメソッドが一つだけあります。このメソッドは、トレード状態や履歴を記憶し、新しい状態と比較します。

こののトレードイベントを追跡するアルゴリズムは以下に記載されています:

  1. 注文、ポジション、取引のカウンターをグローバルスコープにて宣言する;
  2. MQL5プログラムキャッシュにリクエストされるトレード履歴の深度を決定します。キャッシュにロードする履歴が多いほど、多くのターミナルとコンピューターのリソースを消費します。
  3. 注文、ポジション、取引のカウンターをOnInit関数にて初期化します;
  4. トレード履歴をキャッシュにリクエストするハンドラ関数を決定します;
  5. トレード履歴のロード後、記憶された過去のデータと現在のものを比較し、トレードアカウントに何が起こったかを見つけます。

これが最もシンプルなアルゴリズムです。オープンポジションの数が変更されたか、変更の方向は何かを発見します。もし変更があれば、より詳しい情報を取得することができます。もし注文の数は変更されてないが、注文それ自体が修正されている場合、異なるアプローチが必要です;これに関してはこの記事では扱っていません。

カウンターの変化は、OnTrade()OnTick()関数にてチェックできます。

ステップごとにプログラムを書いてみましょう。

1.グローバルススコープでの注文、取引、ポジションのカウンター

int          orders;            // number of active orders
int          positions;         // number of open positions
int          deals;             // number of deals in the trade history cache
int          history_orders;    // number of orders in the trade history cache
bool         started=false;     // flag of initialization of the counters

2. キャッシュにロードされるトレード履歴の深度が入力変数 days にセットされます(この変数で記載された日数にてトレード履歴をロードします).

input    int days=7;            // depth of the trade history in days

//--- set the limit of the trade history on the global scope
datetime     start;             // start date of the trade history in cache
datetime     end;               // end date of the trade history in cache

3. カウンターと、トレード履歴の制限の初期化コードを読みやすくするために、InitCounters関数をカウンターの初期化から外に出します。

int OnInit()
  {
//---
   end=TimeCurrent();
   start=end-days*PeriodSeconds(PERIOD_D1);
   PrintFormat("Limits of the trade history to be loaded: start - %s, end - %s",
               TimeToString(start),TimeToString(end));
   InitCounters();
//---
   return(0);
  }

InitCounters()関数は、キャッシュ内のトレード履歴をロードしようとし、全てのカウンタを初期化します。また、もし履歴がうまくロードされれば、グローバル変数’started’の値が'true'にセットされ、カウンタが初期化されたことを示します。

//+------------------------------------------------------------------+
//|  initialization of the counters of positions, orders and deals   |
//+------------------------------------------------------------------+
void InitCounters()
  {
   ResetLastError();
//--- load history
   bool selected=HistorySelect(start,end);
   if(!selected)
     {
      PrintFormat("%s. Failed to load the history from %sから%sまでの履歴をキャッシュにロードすることに失敗しました。Error code: %d",
                  __FUNCTION__,TimeToString(start),TimeToString(end),GetLastError());
      return;
     }
//--- get current values
   orders=OrdersTotal();
   positions=PositionsTotal();
   deals=HistoryDealsTotal();
   history_orders=HistoryOrdersTotal();
   started=true;
   Print("The counters of orders, positions and deals are successfully initialized");
  }

4. トレードアカウントにおける変化のチェックはOnTickとOnTrader()ハンドラにて実行されます。'started'変数は、まず最初にチェックされます - もしその値が'true'なら、SimpleTraderProcessor()関数が呼ばれ、そうでなければ、カウンタの初期化関数initCounters()が呼ばれます。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   if(started) SimpleTradeProcessor();
   else InitCounters();
  }
//+------------------------------------------------------------------+
//| called when the Trade event occurs                               |
//+------------------------------------------------------------------+
void OnTrade()
  {
   if(started) SimpleTradeProcessor();
   else InitCounters();
  }

5.SimpleTraderProcessor()関数が、注文や取引、ポジションの変更が行われたか否かをチェックします。全てのチェックを実行した後、CheckStartDateInTraderHistory関数を呼び出し、必要であれば現在の状態に'start'値を近づけます。

//+------------------------------------------------------------------+
//| simple example of processing changes in trade and history        |
//+------------------------------------------------------------------+
void SimpleTradeProcessor()
  {
   end=TimeCurrent();
   ResetLastError();
//--- load history
   bool selected=HistorySelect(start,end);
   if(!selected)
     {
      PrintFormat("%s. Failed to load the history from %sから%sまでの履歴をキャッシュにロードすることに失敗しました。Error code: %d",
                  __FUNCTION__,TimeToString(start),TimeToString(end),GetLastError());
      return;
     }

//--- get current values
   int curr_orders=OrdersTotal();
   int curr_positions=PositionsTotal();
   int curr_deals=HistoryDealsTotal();
   int curr_history_orders=HistoryOrdersTotal();

//--- check whether the number of active orders has been changed
   if(curr_orders!=orders)
     {
      //--- number of active orders is changed
      PrintFormat("Number of orders has been changed. Previous number is %d, current number is %d",
                  orders,curr_orders);
     /*
       other actions connected with changes of orders
     */
      //--- update value
      orders=curr_orders;
     }

//--- change in the number of open positions
   if(curr_positions!=positions)
     {
      //--- number of open positions has been changed
      PrintFormat("Number of positions has been changed. Previous number is %d, current number is %d",
                  positions,curr_positions);
      /*
      other actions connected with changes of positions
      */
      //--- update value
      positions=curr_positions;
     }

//--- change in the number of deals in the trade history cache
   if(curr_deals!=deals)
     {
      //--- number of deals in the trade history cache has been changed
      PrintFormat("Number of deals has been changed. Previous number is %d, current number is %d",
                  deals,curr_deals);
      /*
       other actions connected with change of the number of deals
       */
      //--- update value
      deals=curr_deals;
     }

//--- change in the number of history orders in the trade history cache
   if(curr_history_orders!=history_orders)
     {
      //--- the number of history orders in the trade history cache has been changed
      PrintFormat("Number of orders in the history has been changed. Previous number is %d, current number is %d",
                  history_orders,curr_history_orders);
     /*
       other actions connected with change of the number of order in the trade history cache
      */
     //--- update value
     history_orders=curr_history_orders;
     }
//--- check whether it is necessary to change the limits of trade history to be requested in cache
   CheckStartDateInTradeHistory();
  }

CheckStartDateInTradeHistory() 関数は、現在の日にちであるcurr_start値のためにトレード履歴のリクエスト開始日を計算し、'start'値と比較します。もしそれらの違いが1日以上であれば、'start'は訂正され、注文履歴のカウンタが更新されます。

//+------------------------------------------------------------------+
//|  Changing start date for the request of trade history            |
//+------------------------------------------------------------------+
void CheckStartDateInTradeHistory()
  {
//--- initial interval, as if we started working right now
   datetime curr_start=TimeCurrent()-days*PeriodSeconds(PERIOD_D1);
//--- make sure that the start limit of the trade history period has not gone 
//--- more than 1 day over intended date
   if(curr_start-start>PeriodSeconds(PERIOD_D1))
     {
      //--- we need to correct the date of start of history loaded in the cache
      start=curr_start;
      PrintFormat("New start limit of the trade history to be loaded: start => %s",
                  TimeToString(start));

      //--- now load the trade history for the corrected interval again
      HistorySelect(start,end);

      //--- correct the counters of deals and orders in the history for further comparison
      history_orders=HistoryOrdersTotal();
      deals=HistoryDealsTotal();
     }
  }

エキスパートアドバイザー DemoTraderEventProcessing.mq5の全てのコードは記事に添付されています。


結論

トレーディングプラットフォームMetaTrader5の全ての処理は非同期で実行され、トレードアカウントにおける全ての変化に関するメッセージは個々で独立しています。従って、「1リクエストー1トレードイベント」というルールに基づいて、単体のイベントを追跡することは無駄です。もしトレードが起こった際に何が変化されたかを正確に知る必要があれば、OnTradeハンドラ呼び出し時に全ての取引、ポジション、注文を現在と過去の状態を比較新柄分析してください。