
MetaTrader 5でのトレードイベント
はじめに
トレード処理を実行する全てのコマンドは、MetaTrader5クライアントターミナルからトレードサーバーに送信リクエストを通して渡されます。それぞれのリクエストは、要求された処理に応じて正しく実行されます;さもなければ、最初の検査を通ることができず、さらなる処理を行うサーバーによって受理されません。
トレードサーバーによって受け取られたリクエストは、市場価格によって即座に実行されるか、未決注文の状態になるような注文形式で保存されます。注文は、実行されるか、キャンセルされるまでサーバーで保存されます。注文実行結果が、取引として表示されます。
取引は、特定のシンボルによってトレードポジションを変化させ、ポジションをオープン・クローズ、増加・減少、反転させることができます。従って、オープンポジションは常に一つかそれ以上の取引を実行した結果です。さらに詳しい情報は、MetaTrader 5の注文、ポジション、取引 という記事にて記載されています。
この記事は、コンセプト、用語、リクエストの送信から処理後のトレード履歴への移動までの流れの処理について記載しています。
クライアントターミナルからトレードサーバーへのリクエストの受け渡し
トレード処理を実行するため、注文をトレードシステムに送る必要があります。リクエストは常にクライアントターミナルから注文を送信することを通してトレードサーバーに送られます。手動か、MQL5プログラムを使用するかなど、いかにあなたがトレードを行おうとも、リクエストストラクチャーは正しく記載される必要があります。
手動でトレード処理を実行するために、F9キーを押してトレードリクエストを記入するためのダイアログウィンドウを開いて下さい。MQL5を通して自動的にトレードを行う際は、リクエストはOrderSend()関数を用いて送られます。間違いのあるリクエストはトレードサーバーのオーバーロードにつながるので、それぞれのリクエストはOrderCheck()関数を用いて送信される前にチェックする必要があります。MqlTradeCheckresultストラクチャーによって記載された変数にリクエストのチェック結果が格納されます。
リクエストがトレードサーバーについたら、最初のチェックを通ります;
- トレード処理を実行する十分な資産を持っているか;
- 特定の価格が正しいか;オープン価格、損切り、利食いなど;
- 特定の価格がすぐに実行する上で現在可能な価格なのか;
- 損切りと利食いのレベルはMarket Exectuion モードにないか;
- その注文量が正しいか;最小・最大量、ステップ、ポジションの最大値 (SYMBOL_VOLUME_MIN, SYMBOL_VOLUME_MAX, SYMBOL_VOLUME_STEP と SYMBOL_VOLUME_LIMIT);
- シンボルのk状況: 取引価格やトレード セッション、シンボルによるトレード可能性、トレードの特定のモード (例,ポジションのクローズのみ)など;
- トレードアカウントの状態:アカウントの特定の種類における異なる制限;
- その他のチェックがリクエストされたトレード処理に基づいて実行されます。
サーバーの最初のチェックを通らない間違いのリクエストは拒否されます。クライアントターミナルは常にレスポンスを送ることによってリクエストのチェック結果について通知を受けています。トレードサーバーのレスポンスは、MqlTradeResult型の変数から取得され、リクエストを送信する際にOrderSend()関数にて2番目のパラメーターとして渡されます。
もしリクエストが最初のチェックを通れば、処理待ちのリクエストに配置されます。リクエストの処理結果として、(トレード処理を実行するためのコマンド)注文がトレードサーバーにて作成されます。しかしながら、注文の作成に至らない二種類のリクエストがあります。
- (損切り・利食いへ)ポジションを変更するリクエスト
- 未決注文を修正するリクエスト(価格レベルや期限切れ時間)
クライアントターミナルは、そのリクエストが受け取られ、MetaTrader5プラットフォームのサブシステムに配置されたというメッセージを受信します。サーバーは受け取られたリクエストをさらなる処理のためにリクエストキューに配置した後、以下のいずれかを実行します;
- 未決注文を配置する;
- 市場価格によって即刻注文を実行する;
- 注文やポジションの修正を行う。
サーバーのキューのリクエストの期限は3分間のみです。その期間が過ぎれば、そのリクエストはリクエストキューから削除されます。
トレードイベントのトレードサーバーからクライアントターミナルへの送信
イベントモデルとイベントハンドリング関数は、MQL5言語にて実装されています。 定義済みイベントへのレスポンスでは、MQL5実行環境は適切な関数 - イベントハンドラを呼び出します。トレードイベントの処理のために、定義済みの関数OnTrade()があります;注文、ポジション、取引を扱うためのコードは、その中に格納されています。この関数は、エキスパートアドバイザーでのみ呼び出され、同じ名前や型で追加してもインジケーターやスクリプトでは使用されません。
トレードイベントは以下の場合にサーバーによって生成されます;
- アクティブな注文の変更、
- ポジションの変更、
- 取引の変更、
- トレード履歴の変更。
ある処理がいくつかのイベントを引き起こすことがあることに注意してください。例えば、未決注文を行うと、二つのイベントを発生させます;
- トレード履歴へ記述される取引が表示されます;
- アクティブな注文のリストから未決注文を注文履歴のリストへ移動させます。
複数のイベントのその他の例は、必要な額が単体の反対のオファーから取得できない場合、単体の注文に基づいていくつかの取引を実行することです。そのトレードサーバーが各イベントに関するメッセージを作成し、クライアントターミナルに送信します。そのため、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つのトレードイベントを発生させます。
- 5ロットの取引、
- 取引額の変化、
- 注文のトレード履歴への移動。
取引の実行結果として、そのクライアントターミナルは次々に7つのトレードイベントを受け取ります(クライアントターミナルとトレードサーバー間の連携は安定しており、メッセージが損失されないと想定されています)。それらのメッセージは、OnTrade()関数を用いてエキスパートアドバイザーにて処理される必要があります。
トレードサーバーにより注文を処理する
実行待ちの全ての注文は、( 実行における条件が満たされるか、キャンセルされるか)最終的に履歴に移されます 注文のキャンセルにおいていくつかの手段があります:
- 注文に基づいて取引を実行する;
- ディーラーによる注文の拒否;
- (手動のリクエストや、MQL5プログラムから自動リクエストなど)トレーダーの要求に基づく注文のキャンセル;
- トレーダーによってリクエストの送信時に決定されていたか、特定のトレードシステムの条件によって決定されていた注文の期限切れ;
- 実行条件が満たされた際にの取引実行のためのトレードアカウント上の資産の欠乏;
- 注文が記載ポリシーのためにキャンセルされう(部分的に記載された注文がキャンセルされる)
アクティブな注文が履歴に移される理由がどのようなものでも、変更に関するメッセージはクライアントターミナルに送信されます。トレードイベントに関するメッセージは、全てのクライアントターミナルではなく、一致するアカウントに接続されたものにのみ送信されます。
そのため、OrderSend()関数の記載によると以下のように記載されています:
返された値
リクエストのチェックが成功した場合、OrderSend()関数はtrueを返します- これは、トレード処理の実行が成功した印ではありません。. その関数の実行結果の詳しい記述に関して、 structure MqlTradeResultのフィールドを分析してください。
クライアントターミナルのトレードと履歴の更新
トレードイベントとトレード履歴における変化に関するメッセージは、個別のチャネルから送られます。OrderSend(9関数を用いて購入するリクエストを送信する際、リクエストの検証が成功して作成される注文チケットを取得できます。同時に、その注文はクライアントターミナルに表示されず、OrderSelect()関数を用いてそれを選択しようと失敗します。
上記の図にて、いかにトレードサーバーがその注文チケットをMQL5プログラムに伝えるかをご覧になれます。しかし、トレードイベントに関するメッセージはまだ到着していません。アクティブな注文リストの変化に関するメッセージは同様にまだ到着していません。
取引が実行された際に、新規注文が現れたことに関するメッセージがプログラムに到着する場合があります。従って、その注文はすでにアクティブな注文のリストになく、履歴にあります。リクエストの処理スピードがネットワークを通るメッセージの現在の配達スピードと比較するとかなり高いので、これは実際に起こる状況なのです。
MQL5のトレードイベントのハンドリング
トレードサーバーでの全ての処理やトレードイベントに関するメッセージの送信は非同期で実行されます。トレードアカウントにて何が変更されたかを確認するメソッドが一つだけあります。このメソッドは、トレード状態や履歴を記憶し、新しい状態と比較します。
こののトレードイベントを追跡するアルゴリズムは以下に記載されています:
- 注文、ポジション、取引のカウンターをグローバルスコープにて宣言する;
- MQL5プログラムキャッシュにリクエストされるトレード履歴の深度を決定します。キャッシュにロードする履歴が多いほど、多くのターミナルとコンピューターのリソースを消費します。
- 注文、ポジション、取引のカウンターをOnInit関数にて初期化します;
- トレード履歴をキャッシュにリクエストするハンドラ関数を決定します;
- トレード履歴のロード後、記憶された過去のデータと現在のものを比較し、トレードアカウントに何が起こったかを見つけます。
これが最もシンプルなアルゴリズムです。オープンポジションの数が変更されたか、変更の方向は何かを発見します。もし変更があれば、より詳しい情報を取得することができます。もし注文の数は変更されてないが、注文それ自体が修正されている場合、異なるアプローチが必要です;これに関してはこの記事では扱っていません。
カウンターの変化は、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ハンドラ呼び出し時に全ての取引、ポジション、注文を現在と過去の状態を比較新柄分析してください。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/232





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索