の処理 トレードイベント in Expert Advisor を使って OnTrade() 関数
はじめに
MQLでExpertsを書くトレーダーは皆遅かれ早かれExpertがどのように働いているかを報告する必要に迫られます。Expertの動作についてSMSまたはe-mail 通知を実装する必要があるかもしれません。どのようなケースであれ、マーケットで起こるイベントやエキスパートによる動作を「捕まえ」、ユーザーに通知しなければなりません。
本記事では、トレードイベント処理の実装の仕方を教え、私の実装を提供します。
本記事では以下のイベントの処理を考えます。:
- ポジション
- オープン
- 追加
- 修正 (ストップロスとテイクプロフィットの変更)
- リバース
- 全体のポジションを閉じる
- 部分的なポジションを閉じる
- ペンディングオーダー
- 注文
- 修正
1. どのように作動するのか?
始める前に、 どのようにトレードイベントが働くのが概括的に説明します。そしてすべての必要な詳細はその場で説明します。
MQL5では事前定義 と カスタム イベントがあります。事前定義、特にトレードイベントに興味があります。
トレード操作が完了するたびにトレードイベントが発生します。各トレードイベント発生後、OnTrade() 関数が呼ばれます。オーダーとポジションの処理はまさにOnTrade() 関数内で行われます。
2. Expert テンプレート
そのため、 新しいExpert Advisorを作りましょう。MetaEditorでFile -> NewをクリックしMQL5 ウィザードを立ち上げます。Expert Advisorを選択し、 Nextをクリックします。「Expert Advisor の一般プロパティ」ダイアログで、Expert Advisor の名前 と自分自身のデータを必要な場合入力します。私は自分のExpert Advisor を「TradeControl」と名付けました。この名前を使うかまたは自分自身の名前をつけます。それ重要ではありません。expertを書くときにその場で作成されるのでパラメータは指定しません。
完了!Expert Advisor テンプレートが作成され、OnTrade() 関数をその中に追加しなければなりません。
結果、以下のコードを入手するはずです。:
//+------------------------------------------------------------------+ //| TradeControl_en.mq5 | //| Copyright KlimMalgin | //| | //+------------------------------------------------------------------+ #property copyright "KlimMalgin" #property link "" #property version "1.00" //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(0); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| OnTrade function | //+------------------------------------------------------------------+ void OnTrade() { //--- //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- } //+------------------------------------------------------------------+
3. ポジションを使う
最もシンプルなトレードイベント、オープニングとクロージングポジションから始めましょう。最初に、「Sell」 と 「Buy」 ボタンを押すとどんなプロセスがなぜ起こるか理解しなければなりません。
OnTrade() 関数内に呼び出しを置くと、:
Alert("The Trade event occurred");
マーケット関数 OnTrade() によるオープニングの後、我々の Alertが4回実行されることを見ます。:
図 1. アラート
なぜOnTrade() が4回呼ばれ、どのようにこれらのアラートに対応するのでしょうか?これを理解するためドキュメンテーションを見ましょう。:
ここで1つ念を押さなければなりません。:
この本記事の執筆中開発者と話をしている時に、ヒストリーの変更がOnTrade() 呼び出しをもたらさないことに気が付きました!事実とは、発注オーダーとオープンポジションのリストが変更された時だけ OnTrade() 関数が呼ばれます!トレードイベントハンドラーを開発する時、オーダーとディールが遅れてヒストリーに現れ、 OnTrade() 関数実行時にそれらを処理できないという事実に出くわすかもしれません。
それではイベントに戻りましょう。先ほど見たように、マーケットでオープンすると、トレードイベントが4回起こります。:
- マーケットでオープンするためのオーダー作成。
- ディール実行。
- 完了オーダーのへのヒストリー渡し。
- ポジションオープニング。
ターミナル内の処理を追跡するために、MetaTraderウインドウの「トレード」タブ 内のオーダーリストに注目しましょう。 :
図 2. 「トレード」タブ内のオーダー
ポジションをオープン (例、ダウン)すると、オーダーリストに、 開始 状態 (図. 2)のオーダーが現れます。これが発注オーダーのリストを変え、 トレードイベントが呼ばれます。OnTrade() 関数が起動されたのはこれが初めてです。作成されたオーダーによってディールが実行されます。このステージでOnTrade() 関数が2度目に実行されます。ディール が実行されてすぐ、完了オーダーとその実行されたディールがヒストリーに送られ、OnTrade() 関数が三度目に呼ばれます。最後のステージでポジションが実行されたディールでオープンになり、 OnTrade() 関数が4度目に呼ばれます。
ポジションオープニングの瞬間を「捉える」ために、OnTrade()呼び出しの際、常にオーダーリスト・オーダー ヒストリー ・ディールヒストリーを分析しなければなりません。これが今からしようとしていることです!
OnTrade() 関数が 呼ばれ、「Trade」 タブ内でオーダー数が変化したか知る必要があります。そのためには、以前の OnTrade() 呼び出し時と現時点でのリスト内のオーダー数を比較しなければなりません。その瞬間のリスト内のオーダー数を見つけるには OrdersTotal() 関数を使用します。 また以前の呼び出しでいくつのオーダーがリストされたか知るには、 OrdersTotal()値を各 OnTrade() 呼び出しで保持しなければなりません。このために特別変数を作成します。:
int OrdersPrev = 0; // Number of orders at the time of previous OnTrade() call
OnTrade() 関数の最後で、OrdersPrev 変数にOrdersTotal()値が割り当てられます。
また、Expert Advisorを実行しペンディングオーダーがすでにリストにある状況を考えるべきです。Expertはそれらを見つけることができるはずなので、 OnInit() 関数内で OrdersPrev 変数はまたOrdersTotal()値を割り当てられなければなりません。Expertで作成したこの変更はこのように見えます。:
int OrdersPrev = 0; // Number of orders at the time of previous OnTrade() call //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- OrdersPrev = OrdersTotal(); //--- return(0); } //+------------------------------------------------------------------+ //| OnTrade function | //+------------------------------------------------------------------+ void OnTrade() { //--- OrdersPrev = OrdersTotal(); //--- }
現在と以前の呼び出しのオーダー数がわかったので、 いつオーダーがリストに現れ、それがいついかなる理由で消えたかがわかります。これを行うために以下の条件をを使用します。:
if (OrdersPrev < OrdersTotal()) { // Order appeared } else if(OrdersPrev > OrdersTotal()) { // Order disappeared }
もし、 以前の呼び出しで今よりもオーダーが少ない場合、オーダーがリストに現れます。 (マルチオーダーは同時に現れることができません。) しかしもし反対の場合、つまり、以前の OnTrade() 呼び出しよりも 今 オーダーが少ない場合、オーダーが実行されるか、もしくはある理由によりキャンセルされます。ポジション使用のほとんどはこれらの2つの条件ではじまります。
ストップロスとテイクプロフィットのみが別々の作業を必要とします。OnTrade() 関数にポジションと働くコードを追加します。それを考えましょう。:
void OnTrade() { //--- Alert("Trade event occurred"); HistorySelect(start_date,TimeCurrent()); if (OrdersPrev < OrdersTotal()) { OrderGetTicket(OrdersTotal()-1);// Select the last order to work with _GetLastError=GetLastError(); Print("Error #",_GetLastError);ResetLastError(); //-- if (OrderGetInteger(ORDER_STATE) == ORDER_STATE_STARTED) { Alert(OrderGetTicket(OrdersTotal()-1),"Order has arrived for processing"); LastOrderTicket = OrderGetTicket(OrdersTotal()-1); // Saving the order ticket for further work } } else if(OrdersPrev > OrdersTotal()) { state = HistoryOrderGetInteger(LastOrderTicket,ORDER_STATE); // If order is not found, generate an error _GetLastError=GetLastError(); if (_GetLastError != 0){Alert("Error #",_GetLastError," Order is not found!");LastOrderTicket = 0;} Print("Error #",_GetLastError," state: ",state);ResetLastError(); // If order is fully executed if (state == ORDER_STATE_FILLED) { // Then analyze the last deal // -- Alert(LastOrderTicket, "Order executed, going to deal"); switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ENTRY)) { // Entering the market case DEAL_ENTRY_IN: Alert(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ORDER), " order invoked deal #",HistoryDealGetTicket(HistoryDealsTotal()-1)); switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE)) { case 0: // If volumes of position and deal are equal, then position has just been opened if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME))) { Alert("Buy position has been opened on pair ", HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); } else // If volumes of position and deal are not equal, then position has been incremented if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME))) { Alert("Buy position has incremented on pair ", HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); } break; case 1: // If volumes of position and deal are equal, then position has just been opened if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME))) { Alert("Sell position has been opened on pair ", HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); } else // If volumes of position and deal are not equal, then position has been incremented if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME))) { Alert("Sell position has incremented on pair ", HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); } break; default: Alert("Unprocessed code of type: ", HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE)); break; } break; // Exiting the market case DEAL_ENTRY_OUT: Alert(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ORDER), " order invoked deal #",HistoryDealGetTicket(HistoryDealsTotal()-1)); switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE)) { case 0: // If position, we tried to close, is still present, then we have closed only part of it if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) == true) { Alert("Part of Sell position has been closed on pair ", HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL), " with profit = ", HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT)); } else // If position is not found, then it is fully closed if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) == false) { Alert("Sell position has been closed on pair ", HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL), " with profit = ", HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT)); } break; case 1: // If position, we tried to close, is still present, then we have closed only part of it if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) == true) { Alert("Part of Buy position has been closed on pair ", HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL), " with profit = ", HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT)); } else // If position is not found, then it is fully closed if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) == false) { Alert("Buy position has been closed on pair ", HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL), " with profit = ", HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT)); } break; default: Alert("Unprocessed code of type: ", HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE)); break; } break; // Reverse case DEAL_ENTRY_INOUT: Alert(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ORDER), " order invoked deal #",HistoryDealGetTicket(HistoryDealsTotal()-1)); switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE)) { case 0: Alert("Sell is reversed to Buy on pair ", HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL), " resulting profit = ", HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT)); break; case 1: Alert("Buy is reversed to Sell on pair ", HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL), " resulting profit = ", HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_PROFIT)); break; default: Alert("Unprocessed code of type: ", HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE)); break; } break; // Indicates the state record case DEAL_ENTRY_STATE: Alert("Indicates the state record. Unprocessed code of type: ", HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE)); break; } // -- } } OrdersPrev = OrdersTotal(); //--- }
またプログラムの初めで以下の変数を宣言していることを確認して下さい。:
datetime start_date = 0; // Date, from which we begin to read history int OrdersPrev = 0; // Number of orders at the time of previous OnTrade() call int PositionsPrev = 0; // Number of positions at the time of previous OnTrade() call ulong LastOrderTicket = 0; // Ticket of the last processed order int _GetLastError=0; // Error code long state=0; // Order state
OnTrade()の内容に戻りましょう。
始めにアラートをコメントアウトできますがそれはさておき、次に HistorySelect() 関数に行きます。それはディールのリストと指定期間(関数の2つのパラメータによって定義される)のオーダーヒストリーを生成します。もしこの関数が、ディールとオーダーヒストリーに行く前に呼ばれない場合、ヒストリー リストが空なので情報を得ることが全くできません。HistorySelect() 呼び出し後、つい先ほど書かれたので条件が評価されます。
新しいオーダー来ると、まずそれを選択し、エラーをチェックします。:
OrderGetTicket(OrdersTotal()-1);// Select the last order for work _GetLastError=GetLastError(); Print("Error #",_GetLastError);ResetLastError();
オーダー選択後、GetLastError() 関数を使ってエラーコードを入手します。それから、Print() 関数を使ってコードをジャーナルに印刷し、 ResetLastError() 関数を使ってエラーコードをゼロにリセッします。これで他の状況で次にGetLastError() 呼び出されても、同じエラーコードになりません。
エラーチェックの後、 オーダーが正常に選択された場合、その状況を確認します。:
if (OrderGetInteger(ORDER_STATE) == ORDER_STATE_STARTED) { Alert(OrderGetTicket(OrdersTotal()-1),"Order has arrived for processing"); LastOrderTicket = OrderGetTicket(OrdersTotal()-1); // Saving the order ticket for further work }
もしオーダーが開始 状況の場合、つまり、正しさが確認されたがまだ承認されていない場合、近い将来それが実行されることが予想されます。その場合 オーダーが処理中でそのチケットを次のOnTrade() 呼び出しに保存するよう通知するアラート()をただ単に出します。Alert() の代わりに他の種類の通知を使えます。
上記コード内で
OrderGetTicket(OrdersTotal()-1)
が、最後の オーダーチケットをオーダー全リストから戻します。
OrdersTotal()-1 は最新のオーダーを入手する必要があることを示します。 OrdersTotal() 関数は総オーダー数戻すため、 (たとえばもしリストにオーダーが1つあれば OrdersTotal() は 1戻します。)、 そしてオーダーインデックス数は 0からカウントされるので、最後のオーダーのインデックスを入手するには、総オーダー数から1を引きます。 (もしOrdersTotal()が1戻し場合、このオーダーのインデックス数は0に等しいです)。そして今度は OrderGetTicket() 関数がオーダーチケットを戻し、その数がそれに渡されます。
それは最初の条件で、 通常、最初の OnTrade() 呼び出しをきっかけに起動します。次は、第二の OnTrade() 呼び出しで満たされる第二の条件で、オーダー 実行時、ヒストリーに残り、ポジションがオープンするべきです。
もし、 オーダー がリストにない場合、ヒストリーに残り、そこに絶対あるはずです!そのため、オーダーヒストリーに頼り、HistoryOrderGetInteger() 関数を使ってオーダー状況を入手します。そして特定オーダーのヒストリーデータを読むにはそのチケットが必要です。 このため、もし最初の条件で 受信オーダーチケットが最後のOrderTicket 変数に保存されていれば
このようにしてHistoryOrderGetInteger()の最初のパラメータとしてのオーダーチケットを示しているオーダー状況そして第二として 必要プロパティタイプを入手します。 オーダー状況を入手しようとした後、エラーコードを入手してそれをジャーナル内に書きます。あなたのオーダーがまだヒストリーを入手できない場合、 それが必要で、それに頼ります。 (経験的にそれはかなり可能です。本記事のはじめにこの事について書きました)。
もしエラーが発生したら、使用するデータがなく以下のいかなる条件も満たされないため処理が停止します。もし、 HistoryOrderGetInteger() 呼び出しが成功でオーダーの状況が「オーダーが完全に実行されました」の場合:
// If order is fully executed if (state == ORDER_STATE_FILLED)
そしてもう一つの通知を与えます:
// Then analyze the last deal // -- Alert(LastOrderTicket, "Order executed, going to deal");
そしてこのオーダーで呼び出されたディールの処理に行きましょう。最初に、(DEAL_ENTRYプロパティ)の方向を見つけます。方向は BuyまたはSellではなく、 Entering the market, Exiting the market, ReverseまたはIndication of 状況 recordです。このようにして、 DEAL_ENTRY プロパティを使って、オーダー がオープンポジション、クローズポジションまたはリバースに設定されているかがわかります。
ディールとその結果を分析するには、以下の構造を使ってをヒストリーを利用しましょう。:
switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_ENTRY)) { ... }
それはオーダーと同じように 働きます。 :
HistoryDealsTotal()は総ディール数を戻します。最新のディール数を入手するには、HistoryDealsTotal()値から1引きます。ディールの結果の数は HistoryDealGetTicket() 関数に渡され、をれが今度は 選択されたディールのチケットを HistoryDealGetInteger() 関数に渡します。そして、指定チケットとプロパティ タイプによるHistoryDealGetInteger() はディールの方向を戻します。.
Entering the marketの方向の詳細を考察しましょう。他の方向はほとんど同じように処理されるため簡単に取り上げます。:
HistoryDealGetInteger()から入手された式の値は、対の一方が見つかるまでケースブロック値と比較されます。例えば、Sell orderのオープニングなどマーケットに参入する場合、最初のブロックが実行されます。:
// Entering the market case DEAL_ENTRY_IN:
ブロックの始めでディール作成について通知されます。同時にこの通知がすべてOKであることを保証し、ディールが処理されます。
通知後、ディールのタイプを分析する別のスイッチロックが来ます。 :
switch(HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE)) { case 0: // If volumes of position and deal are equal, then position has just been opened if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME))) { Alert("Buy position has been opened on pair ", HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); } else // If volumes of position and deal are not equal, then position has been incremented if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME))) { Alert("Buy position has incremented on pair ", HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); } break; case 1: // If volumes of position and deal are equal, then position has just been opened if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) && (PositionGetDouble(POSITION_VOLUME) == HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME))) { Alert("Sell position has been opened on pair ", HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); } else // If volumes of position and deal are not equal, then position has been incremented if (PositionSelect(HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)) && (PositionGetDouble(POSITION_VOLUME) > HistoryDealGetDouble(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_VOLUME))) { Alert("Sell position has incremented on pair ", HistoryDealGetString(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_SYMBOL)); } break; default: Alert("Unprocessed code of type: ", HistoryDealGetInteger(HistoryDealGetTicket(HistoryDealsTotal()-1),DEAL_TYPE)); break; }
前と同じように、指定プロパティを除き、ヒストリーからディールについての情報を入手します。 この時、 BuyディールなのかSell ディールなのかわかるようにDEAL_TYPE を定義します。BuyとSell タイプだけを分析しますが、このほかに4つのタイプがあります。しかし、これらの残りの4つのディールタイプはあまり使われません。そのため4つのケースブロックの代わりに1つの デフォルトブロックを使います。それはタイプコードとともにAlert()を与えます。
コードでお気づきかもしれませんが、 BuyとSell ポジションのオープニングが処理されるだけでなく、その増分も処理されます。ポジションがいつ増加し、いつオープンしたかを知るために、ディールの結果である実行されたディールのボリュームとポジションを比較する必要があります。ポジションのボリュームが実行されたディールのボリュームに等しい場合このポジションはオープンされていて、もしポジションとディールのボリュームが違う場合ポジションが増加しています。これは、Buy ポジション ('0' ブロックの場合) と Sell ポジション( '1' ブロックの場合)の両方に当てはまります。 最後のブロックはデフォルトで、BuyとSell以外の全ての状況を扱います。全てのプロセスは HistoryDealGetInteger() 関数が戻すタイプコードの通知でできています。
そしてついにポジション使用についての最後の懸念です。これはストップロス とテイクプロフィット値の変化の処理です。どのポジションパラメータが変化したかを調べるには、現在と以前のパラメータの状況を比較する必要があります。ポジションパラメータの現在値は常サービス 関数を使って入手できますが以前の値は保存されるべきです。
このため、ポジションパラメータを構造配列に保存する特別な関数を書きます。 :
void GetPosition(_position &Array[]) { int _GetLastError=0,_PositionsTotal=PositionsTotal(); int temp_value=(int)MathMax(_PositionsTotal,1); ArrayResize(Array, temp_value); _ExpertPositionsTotal=0; for(int z=_PositionsTotal-1; z>=0; z--) { if(!PositionSelect(PositionGetSymbol(z))) { _GetLastError=GetLastError(); Print("OrderSelect() - Error #",_GetLastError); continue; } else { // If the position is found, then put its info to the array Array[z].type = PositionGetInteger(POSITION_TYPE); Array[z].time = PositionGetInteger(POSITION_TIME); Array[z].magic = PositionGetInteger(POSITION_MAGIC); Array[z].volume = PositionGetDouble(POSITION_VOLUME); Array[z].priceopen = PositionGetDouble(POSITION_PRICE_OPEN); Array[z].sl = PositionGetDouble(POSITION_SL); Array[z].tp = PositionGetDouble(POSITION_TP); Array[z].pricecurrent = PositionGetDouble(POSITION_PRICE_CURRENT); Array[z].comission = PositionGetDouble(POSITION_COMMISSION); Array[z].swap = PositionGetDouble(POSITION_SWAP); Array[z].profit = PositionGetDouble(POSITION_PROFIT); Array[z].symbol = PositionGetString(POSITION_SYMBOL); Array[z].comment = PositionGetString(POSITION_COMMENT); _ExpertPositionsTotal++; } } temp_value=(int)MathMax(_ExpertPositionsTotal,1); ArrayResize(Array,temp_value); }
この関数を使うには以下のコードをグローバル変数宣言のブロックに追加しなければなりません。 :
/* * * Structure that stores information about positions * */ struct _position { long type, // Position type magic; // Magic number for position datetime time; // Time of position opening double volume, // Position volume priceopen, // Position price sl, // ストップロス level for opened position tp, // テイクプロフィット level for opened position pricecurrent, // Symbol current price comission, // Commission swap, // Accumulated swap profit; // Current profit string symbol, // Symbol, by which the position has been opened comment; // Comment to position }; int _ExpertPositionsTotal = 0; _position PositionList[], // Array that stores info about position PrevPositionList[];
GetPosition() 関数プロトタイプはかなりまえに www.mql4.com 記事にありましたが、今それが見つけられず、そのソースを指定できません。この関数の詳細を掘り下げるつもりはありません。要点は、 レファレンスによってパラメータが_position typeの配列 ( ポジションフィールドに対応するフィールドを持つ構造)を渡し、そこでは現在のオープンポジションとそれらのパラメータの値ついての全ての情報が渡されます。
このポジションパラメータの変化を楽に追跡するために2つの _position type配列を作りましょう。これらはPositionList[] (ポジションの現在状況) とPrevPositionList[] (ポジションの以前の状況)です。
ポジションの使用を開始するために、次の呼び出しを OnInit() 内とOnTrade()の最後へ追加しなければなりません。:
GetPosition(PrevPositionList);
また、Ontrade()の初めに呼び出しを追加しなければなりません。 :
GetPosition(PositionList);
今、PositionList[] と PrevPositionList[] 配列で、現在と以前の OnTrade() 呼び出しのポジションついての情報がそれぞれ使えます。
それでは sl と tp内の実際の変化追跡コードを考えましょう。:
if ((PositionsPrev == PositionsTotal()) && (OrdersPrev == OrdersTotal())) { string _alerts = ""; bool modify = false; for (int i=0;i<_ExpertPositionssTotal;i++) { if (PrevPositionList[i].sl != PositionList[i].sl) { _alerts += "On pair "+PositionList[i].symbol+" ストップロス changed from "+ PrevPositionList[i].sl +" to "+ PositionList[i].sl +"\n"; modify = true; } if (PrevPositionList[i].tp != PositionList[i].tp) { _alerts += "On pair "+PositionList[i].symbol+" テイクプロフィット changed from "+ PrevPositionList[i].tp +" to "+ PositionList[i].tp +"\n"; modify = true; } } if (modify == true) { Alert(_alerts); modify = false; } }
かなりの準備作業のため、コードがあまり大きくないことがわかるります。 詳細を見ていきましょう。
それはすべて条件で始まります。:
if ((PositionsPrev == PositionsTotal()) && (OrdersPrev == OrdersTotal()))
ここでオーダー もポジションも 置かれず削除されていないことがわかります。もし条件があえば、 あるポジションまたはオーダーのパラメータが変化している可能性が高いです。
関数の始めで2つの変数が宣言されました。:
- _alerts - 全ての変化に関する通知を保管。
- modify - もしあったならば変化に関するメッセージを表示できるようにする。
次にループで各ポジションの以前と現在呼び出し OnTrade() のテイクプロフィットとストップロス値のマッチングを確認します。すべてのミスマッチ情報は _alerts 変数に保管され、後でAlert() 関数によって表示されます。ところで、ペンディングオーダー修正の処理は同じように実行されます。
今はポジションを終え、ペンディングオーダーの注文に進みましょう。
4. オーダーに取り組む
ペンディングオーダー注文イベントを開始しましょう。
新しいペンディングオーダーが現れると、トレードイベントが一回発生しますがこれだけではそれを処理するのに十分ではありません!ペンディングオーダーと働くコードを演算子ボディの中に置きます。:
if (OrdersPrev < OrdersTotal())
そして以下を入手 :
if (OrdersPrev < OrdersTotal()) { OrderGetTicket(OrdersTotal()-1);// Select the last order to work with _GetLastError=GetLastError(); Print("Error #",_GetLastError);ResetLastError(); //-- if (OrderGetInteger(ORDER_STATE) == ORDER_STATE_STARTED) { Alert(OrderGetTicket(OrdersTotal()-1),"Order has arrived for processing"); LastOrderTicket = OrderGetTicket(OrdersTotal()-1); // Saving the order ticket for further work } state = OrderGetInteger(ORDER_STATE); if (state == ORDER_STATE_PLACED) { switch(OrderGetInteger(ORDER_TYPE)) { case 2: Alert("Pending order Buy Limit #", OrderGetTicket(OrdersTotal()-1)," accepted!"); break; case 3: Alert("Pending order Sell Limit #", OrderGetTicket(OrdersTotal()-1)," accepted!"); break; case 4: Alert("Pending order Buy Stop #", OrderGetTicket(OrdersTotal()-1)," accepted!"); break; case 5: Alert("Pending order Sell Stop #", OrderGetTicket(OrdersTotal()-1)," accepted!"); break; case 6: Alert("Pending order Buy Stop Limit #", OrderGetTicket(OrdersTotal()-1)," accepted!"); break; case 7: Alert("Pending order Sell Stop Limit #", OrderGetTicket(OrdersTotal()-1)," accepted!"); break; } } }
ここでペンディングオーダーと働くコードが以下で始まります。:
state = OrderGetInteger(ORDER_STATE); if (state == ORDER_STATE_PLACED) {
まず、オーダー状況が確認されます。オーダーはORDER_STATE_PLACED 状況を持たなければなりません。つまり承諾されるべきです。もしこの条件があえば、 switch演算子が来て、オーダータイプによってメッセージを印刷します。
次に、オーダー修正時に発生するイベントに取り組みます。オーダーの修正はポジションの修正に似ています。同じように、オーダープロパティを保管する構造が作成されます。:
/* * * Structure that stores information about orders * */ struct _orders { datetime time_setup, // Time of order placement time_expiration, // Time of order expiration time_done; // Time of order execution or cancellation long type, // Order type state, // Order state type_filling, // Type of execution by remainder type_time, // Order lifetime ticket; // Order ticket long magic, // Id of Expert Advisor, that placed an order // (intended to ensure that each Expert // must place it's own unique number) position_id; // Position id, that is placed on order, // when it is executed. Each executed order invokes a // deal, that opens new or changes existing // position. Id of that position is placed on // executed order in this moment. double volume_initial, // Initial volume on order placement volume_current, // Unfilled volume price_open, // Price, specified in the order sl, // ストップロス level tp, // テイクプロフィット level price_current, // Current price by order symbol price_stoplimit; // Price of placing Limit order when StopLimit order is triggered string symbol, // Symbol, by which the order has been placed comment; // Comment }; int _ExpertOrdersTotal = 0; _orders OrderList[], // Arrays that store info about orders PrevOrderList[];
構造の各フィールドはオーダープロパティの1つに対応します。構造宣言の後、 int タイプの変数と2つの _orders type配列が宣言されます。The _ExpertOrdersTotal 変数は総オーダー数を保管し、OrderList[] と PrevOrderList[] 配列は 現在と以前の OnTrade() 呼び出しのオーダーについての情報をそれぞれ保管します。
その関数自身は以下の用に見えます。:
void GetOrders(_orders &OrdersList[]) { int _GetLastError=0,_OrdersTotal=OrdersTotal(); int temp_value=(int)MathMax(_OrdersTotal,1); ArrayResize(OrdersList,temp_value); _ExpertOrdersTotal=0; for(int z=_OrdersTotal-1; z>=0; z--) { if(!OrderGetTicket(z)) { _GetLastError=GetLastError(); Print("GetOrders() - Error #",_GetLastError); continue; } else { OrdersList[z].ticket = OrderGetTicket(z); OrdersList[z].time_setup = OrderGetInteger(ORDER_TIME_SETUP); OrdersList[z].time_expiration = OrderGetInteger(ORDER_TIME_EXPIRATION); OrdersList[z].time_done = OrderGetInteger(ORDER_TIME_DONE); OrdersList[z].type = OrderGetInteger(ORDER_TYPE); OrdersList[z].state = OrderGetInteger(ORDER_STATE); OrdersList[z].type_filling = OrderGetInteger(ORDER_TYPE_FILLING); OrdersList[z].type_time = OrderGetInteger(ORDER_TYPE_TIME); OrdersList[z].magic = OrderGetInteger(ORDER_MAGIC); OrdersList[z].position_id = OrderGetInteger(ORDER_POSITION_ID); OrdersList[z].volume_initial = OrderGetDouble(ORDER_VOLUME_INITIAL); OrdersList[z].volume_current = OrderGetDouble(ORDER_VOLUME_CURRENT); OrdersList[z].price_open = OrderGetDouble(ORDER_PRICE_OPEN); OrdersList[z].sl = OrderGetDouble(ORDER_SL); OrdersList[z].tp = OrderGetDouble(ORDER_TP); OrdersList[z].price_current = OrderGetDouble(ORDER_PRICE_CURRENT); OrdersList[z].price_stoplimit = OrderGetDouble(ORDER_PRICE_STOPLIMIT); OrdersList[z].symbol = OrderGetString(ORDER_SYMBOL); OrdersList[z].comment = OrderGetString(ORDER_COMMENT); _ExpertOrdersTotal++; } } temp_value=(int)MathMax(_ExpertOrdersTotal,1); ArrayResize(OrdersList,temp_value); }
GetPosition() 関数に似て、それは各発注オーダーのプロパティについての情報を読み、配列に入れ、それを入力パラメータとして渡します。その関数コードとその呼び出しは以下のように、あなたのエキスパートの最後に置かれなければなりません。 :
GetOrders(PrevOrderList);
OnInit() と OnTrade()の最後に置く。
GetOrders(OrderList);
OnTrade()始めに置く。
オーダーの修正を処理するコードを考えましょう。それはループでポジションの修正コードを補完します。:
for (int i = 0;i<_ExpertOrdersTotal;i++) { if (PrevOrderList[i].sl != OrderList[i].sl) { _alerts += "Order "+OrderList[i].ticket+" has changed ストップロス from "+ PrevOrderList[i].sl +" to "+ OrderList[i].sl +"\n"; modify = true; } if (PrevOrderList[i].tp != OrderList[i].tp) { _alerts += "Order "+OrderList[i].ticket+" has changed テイクプロフィット from "+ PrevOrderList[i].tp +" to "+ OrderList[i].tp +"\n"; modify = true; } }
ループは全てのオーダーを処理し、現在と以前の OnTrade() 呼び出しのストップロスとテイクプロフィットの値を比較します。もし違いがあれば_alerts 変数に保存され、ループ完了時に Alert() 関数に表示されます。
このコードは演算子の本体に置かれます。:
if ((PositionsPrev == PositionsTotal()) && (OrdersPrev == OrdersTotal())) {
ループ直後、ポジションと共に働く。
今のところトレードイベントとの取り組みは尽きません。 本記事は トレード イベント取り組みの主な原則のみ取り上げました。一般的に、メソッドが提供する可能性はとても大きく、本記事の範囲外です。
結論
トレードイベント (MQL5 言語の部分としての)と取り組める性能は潜在的に力強いツールで、やや速くオーダー検証のアルゴリズムを実装し、トレードレポートを生成できるだけでなく、システムリソースのコストとソースコードのボリューム削減も可能であり、開発者にとって疑いなく有益です。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/40
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索