自動で動くEAを作る(第08回):OnTradeTransaction
はじめに
以前の「自動で動くEAを作る(第06回):口座の種類(I)」稿と「自動で動くEAを作る(第07回):口座の種類(II)」稿では 、自動売買をおこなうEAを設計する際に注意すべき点を中心に解説しました。
自動化のためのEAコードがどのように機能すべきかを実際に理解する前に、EAが取引サーバとどのように相互作用するのかを理解する必要があります。図01をご覧ください。
図01:メッセージの流れ
図01は、EAが取引サーバに注文やリクエストを送信するためのメッセージフローです。矢印の向きに注意してください。
矢印が双方向になるのは、C_OrdersクラスがOrderSend関数を使ってサーバに注文を送るときだけで、このとき構造体を介してサーバから応答を受け取るからです。この点以外は、すべて方向性が決まっています。しかし、ここでは、成行注文を出す、あるいはオーダーブックに注文を出すためのプロセスだけを示しています。このように、非常にシンプルなシステムになっています。
100%自動化されたEAの場合、まだ必要なものがあります。また、自動化を最小限に抑えたEAでは、やはり細部を追加する必要があります。すべてはEAとC_Managerクラスの間でおこなわれることになります。EAの他の部分でコードを追加することはありません。あと1つ言うことがあります。100%自動化されたEAでは、C_Mouseクラスを削除する必要があります(100%自動化されたEAでは、このクラスは役に立ちません)。メッセージフローとは何かを理解することは非常に重要であり、これがなければこれ以上話を進めることはできません。
コントロール機能、アクセシビリティ機能の追加
最大の問題は、MQL5言語がそのような機能を提供しているにもかかわらず、多くのMQL5言語ユーザーがEAを作成するために一部の機能を使用していないことです。それが無知から来るものなのか他の理由なのかは問題ではありません。MQL5が提供するすべての機能を使用するのであれば、コードの堅牢性と信頼性を高めるために、この言語が自由に使えるリソースをいくつか使用することを考える必要があります。
まず最初に、C_Managerクラスに3つの新しい関数を追加します。EAをリリースしたり、EAが何をしようとしているのかを知るために、クラスのために役立てます。このうち、最初の関数を以下に示します。
inline void EraseTicketPendig(const ulong ticket) { m_TicketPending = (ticket == m_TicketPending ? 0 : m_TicketPending); }
通知されたチケットが保留チケットと等しい場合、保留チケットの値を削除する関数です。通常、このようなことは実際には起こりません。未決注文は、EAによって削除されることはありません。通常、削除はトレーダーやEAユーザーの干渉によって起こりますが、これは好ましくありません。ただし、EAがオーダーブックに発注した未決注文がユーザーによって削除されたことに気づいた場合は、C_Managerクラスに通知して、必要に応じてEAがオーダーブックに新しい未決注文を発注できるようにする必要があります。
次の新しい関数は以下の通りです。
void PedingToPosition(void) { ResetLastError(); if ((m_bAccountHedging) && (m_Position.Ticket > 0)) SetUserError(ERR_Unknown); else m_Position.Ticket = (m_Position.Ticket == 0 ? m_TicketPending : m_Position.Ticket); m_TicketPending = 0; if (_LastError != ERR_SUCCESS) UpdatePosition(m_Position.Ticket); CheckToleranceLevel(); }
EAはこのコードを使用して、未決注文がちょうどポジションになったこと、またはポジションに何らかの変更が加えられたことをC_Managerクラスに通知します。なお、この関数を実行すると、C_Managerクラスは未決注文チケットを削除し、EAが新たに未決注文を発注できるようになります。ただし、前回の記事で説明した関数で解析されるクリティカルエラーがない場合に限り、ケースは継続されます。しかし、この関数は実際には単独では動作せず、以下に示すような別の関数が必要です。
void UpdatePosition(const ulong ticket) { int ret; if ((ticket == 0) || (ticket != m_Position.Ticket)) return; if (PositionSelectByTicket(m_Position.Ticket)) { ret = SetInfoPositions(); m_StaticLeverage += (ret > 0 ? ret : 0); }else ZeroMemory(m_Position); ResetLastError(); }
C_Managerクラスには、他に2つの関数が欠けていますが、これらは自動化関数であるため、今は詳しく説明しません。
これで、だいぶ完成度が上がり、ようやくC_ManagerクラスとEAの相性がよくなってきました。どちらも効果があり、攻撃的になったり、非友好的になったりしないように気を配ることができます。このように、EAとC_Managerクラス間のメッセージフローは、図02のようになります。
図02:新しい関数によるメッセージフロー
このフローは複雑すぎる、あるいはまったく機能していないと思われるかもしれませんが、まさにこれまで実装されてきたものなのです。
図02を見ると、EAのコードが非常に複雑だと思われるかもしれませんが、多くの人がEAに必要なコードと考えるものよりも、はるかにシンプルなものです。特に自動化されたEAとなるとそうです。次のことを忘れないでください。EAが実際に取引を発生させることはありません。EAは取引サーバと通信するための手段やツールに過ぎません。つまり、実際にはトリガーがかかると反応するだけなのです。
この理解をもとに、自動化される前の現状のEAコードを見ていきましょう。しかし、ご覧になっていない方のために、EAコードは前回登場した記事(自動で動くEAを作る(第05回):マニュアルトリガー(II))から大きな変化はありません。 実際に変更されたのは、以下の通りです。
int OnInit() { manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08); mouse = new C_Mouse(user05, user06, user07, user03, user02, user01); (*manager).CheckToleranceLevel(); return INIT_SUCCEEDED; }
実際には、新しい行を追加するだけでよかったのです。これにより、より深刻な、あるいは重大なエラーが発生したかどうかを確認することができました。しかし、EAがC_Managerクラスに対して、受注システムとの関連で何が起きているかを知らせるにはどうしたらいいのかという疑問が湧いてきます。このような状況で、取引サーバでおこなわれていることを知るにはどうしたらいいのか、試行錯誤している人も多いのではないでしょうか。ただし、ここに危険が潜んでいます。
まず最初に理解していただきたいのは、MetaTrader 5プラットフォームとMQL5言語は、単なる普通のツールではないということです。常に情報を検索しなければならないようなプログラムは、実際には作れないでしょう。これは、システムがプロセスではなく、イベントに基づくものであるためです。イベントベースのプログラミングでは、ステップで考える必要はなく、別の方法で考える必要があります。
これを理解するために、次のように考えてみてください。車で移動する場合、基本的には「ある目的地に到着すること」が目的でしょう。しかし、その途中で、起こるであろう、一見すると相互に関連性がないようなことを解決しなければなりません。しかし、これらのことはすべて、例えば、ブレーキをかけたり、加速したり、何か予期せぬことが起こって道を変えなければならなくなったりと、方向性に影響を及ぼします。これらの出来事が起こりうることは知っていても、いつ起こるかはわかりません。
それが、イベントベースのプログラミングです。特定の言語が特定の仕事のために提供する、いくつかのイベントにアクセスすることができます。あるイベントが引き起こす問題を解決するためのロジックを作るだけで、何らかの有用な結果が得られます。
MQL5では、その種類に応じて、処理できる(しなければならない)イベントが用意されています。このロジックを理解しようとすると、多くの人が迷ってしまうのですが、決して複雑ではありません。これを理解すれば、プログラミングはもっと簡単になります。なぜなら、言語そのものが、どんな問題にも対処できる必要な手段を提供してくれるからです。
これが1点目です。主にMQL5言語を使用して問題を解決します。それで何となく物足りないなら、具体的な機能性を追加します。C/C++やPythonなど他の言語を使うこともできますが、まずはMQL5を使ってみてください。
2点目です。どこから情報が入ってきても、それを鵜呑みにするようなことはしてはいけません。可能な限り単純に、MetaTrader 5プラットフォームが生成するイベントを使用し、それに対応する必要があります。
3点目です。関数を使ったり、コードにあまり役に立たないイベントを使おうとしたりしないでください。必要なものを正確に使い、常に適材適所のイベントを心がけましょう。
これらの3つのポイントに基づき、EAがC_ManagerクラスやMetaTrader 5プラットフォームから提供されるデータを受け取る必要があるその他のクラスとやり取りするために、3つのオプションから選択することができます。最初の選択肢は、新しいチケットを受け取るごとにイベントトリガーを使用することです。このイベントでは、OnTick関数が呼び出されます。しかし、心から、この関数を使うことをお勧めしません。その理由は、また別の機会にご紹介します。
第二の選択肢は、OnTime関数を起動するタイムイベントを使用することですが、この選択肢は、今やっていることに適していません。これは、タイムイベントがトリガーされるたびに、注文またはポジションのリストをチェックし続けなければならないからです。これは全く効果がなく、EAをMetaTrader 5プラットフォームの重荷にしています。
最後の選択肢は、OnTrade関数を起動するTradeイベントを使用することです。注文システムに変化があるたびに、つまり新しい注文やポジションの変化があるたびにトリガーします。しかし、OnTrade癌数は、あまり適していない場合もあれば、ある作業を省くことができ、よりシンプルにすることができる場合もあります。OnTradeを使う代わりに、OnTradeTransactionを使うことにします。
OnTradeTransactionとは何ですか、何のためにあるのですか?
これはMQL5が持つイベント処理関数の中で最も複雑なものだと思われますので、この記事を参考に学んでみてください。この関数の使い方について理解したこと、学んだことをできるだけ説明し、情報提供するように心がけます。
少なくともこの最初の段階では、説明を簡単にするために、EA内の関数コードを見てみましょう。
void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result) { switch (trans.type) { case TRADE_TRANSACTION_POSITION: manager.UpdatePosition(trans.position); break; case TRADE_TRANSACTION_ORDER_DELETE: if (trans.order == trans.position) (*manager).PendingToPosition(); else (*manager).UpdatePosition(trans.position); break; case TRADE_TRANSACTION_REQUEST: if ((request.symbol == _Symbol) && (result.retcode == TRADE_RETCODE_DONE) && (request.magic == def_MAGIC_NUMBER)) switch (request.action) { case TRADE_ACTION_DEAL: (*manager).UpdatePosition(request.order); break; case TRADE_ACTION_SLTP: (*manager).UpdatePosition(trans.position); break; case TRADE_ACTION_REMOVE: (*manager).EraseTicketPending(request.order); break; } break; } }
特に、自分の注文やポジションに何が起こっているかを知るために他の方法を利用することに慣れている人にとっては、このコードはかなり奇妙に思えるかもしれませんが、OnTradeTransaction関数がどのように機能するかを本当に理解すれば、すべてのEAでこの関数を使うようになることは保証します。
ただし、この仕組みを説明するために、ログファイルのファイルやログファイルに見られるパターンを追ってロジックを見ようとすると、おかしくなってしまうので、可能な限りログファイルのデータで話をするのは避けたいと思います。これは、データにパターンがない場合があるからです。理由は、この関数がイベントハンドラであるためです。これらのイベントは取引サーバから来るので、ログファイルについては忘れてください。ここでは、取引サーバから送信されるイベントのうち、表示される順番に関係なく処理することに焦点を当てましょう。
ここでは、基本的に3つの構造とその中身について見ていきます。これらの構造体は、取引サーバによって埋められます。ここでおこなうことは、サーバから提供されたものの処理であることを理解しなければなりません。ここで確認している取引定数は、実際にEAで必要なものです。作成するものによっては、もっと多くの定数が必要な場合があります。その内容は、「取引トランザクションの種類」ドキュメントでご確認ください。11種類の列挙があり、それぞれが特定のものを表すことがわかります。
MqlTradeTransaction構造体を参照する変数を使用している箇所があることに注意してください。この構造体は非常に複雑ですが、サーバが見て理解するという点では複雑です。しかし、私たちにとっては、実際にどのようなことを確認し、分析し、知りたいと思うかによります。 この構造体の「タイプ」フィールドは、さらなるシステム化を可能にするため、注目するべきです。このコードでは、サーバが実行する TRADE_TRANSACTION_REQUEST、 TRADE_TRANSACTION_ORDER_DELETE、TRADE_TRANSACTION_POSITIONの3種類のトランザクションを扱います。そのため、ここで使われているのです。
トランザクションの種類を説明するには例がないと難しいので、まずは1行しかないTRADE_TRANSACTION_POSITIONについて見てみましょう。
case TRADE_TRANSACTION_POSITION: manager.UpdatePosition(trans.position); break;
このイベントは、オープンポジションでトリガーされます。どのポジションでもいいわけではなく、何らかの変更が加えられたポジションのみで何かが起こったときのみにです。サーバがそれについて通知します。 C_Managerクラスに渡して、EAが観測したポジションであれば更新されるようにします。それ以外の場合は無視されます。これにより、どのポジションが実際に変更されたのかを把握するための時間を大幅に短縮することができます。
次はTRADE_TRANSACTION_ORDER_DELETEです。 そのコードは、多くの人にとって分かりにくいかもしれません。
case TRADE_TRANSACTION_ORDER_DELETE: if (trans.order == trans.position) (*manager).PendingToPosition(); else (*manager).UpdatePosition(trans.position); break;
注文がポジションに変換されると、その注文がトリガーとなって、注文が削除されたことがサーバから報告されるイベントが発生します。ポジションが決済されると、同じことが起こり、同じイベントが発生します。注文がポジションに変換されたことを報告するイベントと、ポジションが決済されたことを報告するイベントの違いは、サーバから提供される値にあります。
注文がポジションに変換されると、ポジションチケットで指定された値が得られるので、C_Managerに注文がポジションに変わったことを通知します。ポジションが決済された場合は、これらの値は異なるものになります。しかし、ポジションがあって、注文が追加され、そのためにポジションのボリュームが変化することもあり得ます。その場合、trans.orderとtrans.positionの値は異なります。この場合、C_Managerに対して更新をリクエストします。.
このイベントは場合によってはTRADE_TRANSACTION_POSITIONに付随します。しかし、必ずしもそうとは限りません。説明を簡単にするために情報を分けて説明しましょう。このコードを理解することは非常に重要です。
まず、trans.orderとtrans.positionが同じになる場合について説明します。常に違うことを期待しないでください。それらが同じとき、サーバはTRADE_TRANSACTION_ORDER_DELETE列挙を開始しますが、これは単独で発生するのではなく、他の列挙を伴って発生します。全部を処理する必要はなく、この特定のものだけを処理するのです。サーバから注文がポジションになったとの通知があります。このとき、注文はクローズされ、クローズした注文と同じチケット値でポジションがオープンされます。
しかし、サーバがTRADE_TRANSACTION_POSITION列挙を送信してこないこともあります。 最初はこの列挙を待っているかもしれませんが、サーバは単にそれを発動しないだけです。しかし、確実に削除がトリガーされるきっかけになります。指示された値は等しくなります。この場合、これはオーダーブックにあった注文で、それがポジションになったということがわかりますが、成行注文の場合、すべてが少し違っています。このケースは後ほどご紹介します。
さて、trans.orderとtrans.positionが異なる場合、サーバは他の列挙もトリガーすることになります。繰り返しになりますが、具体的なものが来ることをあてにしないでください。サーバではトリガーしないのに、私が使っているものでは発動するということが起こるかもしれません。この場合、ここでは分析しないが、何らかの理由でポジションが決済されたところであることを示します。いずれにせよ、TradeTransactionイベントで受け取った構造体の情報を受け取ることになります。よって、このイベントハンドラは、情報を探しに行かなくてもいいという面白さがあるのです。イベントはそこにあるので、正しい構造体にアクセスして情報を読むだけでいいのです。このような形でチェックがおこなわれていることは明らかでしょうか。
通常、このイベントハンドラを使用しないプログラムでは、プログラマーはループを作成して、すべてのポジションや未決注文を調べ、どれが約定または決済されたかを確認します。これでは、EAが、もっと簡単に捕捉できる全く無駄なことに忙殺されるため、時間の無駄です。これは、取引サーバがすでに、どの未決注文がクローズされたか、どのポジションが開かれたか、またはどのポジションが決済されたかを知らせてくれるなど、すべての重労働を代わりにおこなってくれているからです。そして、その情報を知るために、ここではループを作ります。
さて、ここからは説明に一番時間がかかる部分ですが、それでもすべてのケースを網羅するわけではありません。その理由は、前のケースと同じです:すべてのケースを説明するのは、事例がないと難しいのです。しかし、ここで説明されたことは、多くの人に役立つことでしょう。便宜上、これから勉強するフラグメントをご覧ください。以下のようになります。
case TRADE_TRANSACTION_REQUEST: if ((request.symbol == _Symbol) && (result.retcode == TRADE_RETCODE_DONE) && (request.magic == def_MAGIC_NUMBER)) switch (request.action) { case TRADE_ACTION_DEAL: (*manager).UpdatePosition(request.order); break; case TRADE_ACTION_SLTP: (*manager).UpdatePosition(trans.position); break; case TRADE_ACTION_REMOVE: (*manager).EraseTicketPending(request.order); break; } break;
このTRADE_TRANSACTION_REQUEST列挙は、ほぼすべてのケースでトリガーされます。むしろ、そうでなければおかしいです。よって、テストに関しても、その中でできることが多いです。しかし、これはサーバが大量に発射する列挙であるため、この中にあるものをフィルタリングする必要があります。
通常、サーバは、EAまたはプラットフォームから何らかのリクエストがあった後に、この列挙をトリガーします。ユーザーが受注システムに関連する操作をおこなった場合です。しかし、サーバが単にこの列挙をトリガーすることもあるので、毎回これを当てにしないでください。時には明白な理由もなくです。このため、提供される情報はフィルタリングしなければなりません。
まず、アセットのフィルタリングをおこないます。これにはどんな構造体でも使えますが、私はこちらの方が好きです 。次に、リクエストがサーバに受け入れられるかどうかを確認します。そのために、このテストを使用します。そして最後に、さらにフィルタリングするためのマジックナンバーを確認します。さて、ここからが一番迷うところです。残りのコードの埋め方がわからないからです。
スイッチを使ってアクションの種類を確認する場合、サーバ上でおこなわれるアクションを解析することはありません(今後もありません)。実際にはそんなことはしません。実際には、EAかプラットフォームのどちらかがサーバに送信したものを、まさにカウンターチェックすることになります。アクションは6種類あり、定番です。これらはENUM_TRADE_REQUEST_ACTIONS列挙にあります。 作業を簡略化するために、次の表を見てみましょう。ドキュメントに記載されているのと同じです。説明を簡単にするために使ったのですが、私の説明はドキュメントと少し違っています。
アクションタイプ | アクションの説明 |
---|---|
TRADE_ACTION_DEAL | 市場価格で執行される取引注文を出す |
TRADE_ACTION_PENDING | 指定されたパラメータに従って実行される注文をオーダーブックに登録する |
TRADE_ACTION_SLTP | ポジションのストップロス値とテイクプロフィット値を変更する |
TRADE_ACTION_MODIFY | 注文ブールに入っている未決注文のパラメータを変更する |
TRADE_ACTION_REMOVE | オーダーブックに残っている未決注文を削除する |
TRADE_ACTION_CLOSE_BY | ポジションを決済する |
表01
この連載の最初から私たちがプログラミングしてきたことに本当に従っているにもかかわらず、十分な注意を払わなかったとしたら、プログラミングするときに、コードのどこでActionタイプフィールドが使われていたかを確認する必要があるでしょう。OnTradeTransactionイベントハンドラはカウントされないので、これは言うまでもありません。これらの列挙はすでに使用されています。でも、どこででしょうか。C_Ordersクラスでです。
ソースコードを開き、C_OrdersクラスのCreateOrder、ToMarket、ModifyPricePoints、ClosePositionプロシージャに注目してください。TRADE_ACTION_CLOSE_BY列挙を使用しないClosePositionを除いたそれぞれを検討することにします。
なぜここ、OnTradeTransactionのイベントハンドラでそんなに重要なのでしょうか。これは、TRADE_TRANSACTION_REQUESTの列挙がどのアクションタイプを指すかを分析する際に、同じ列挙を見るからです。そのため、OnTradeTransactionイベントハンドラのコードには、TRADE_ACTION_DEAL、TRADE_ACTION_SLTP、TRADE_ACTION_REMOVEがあります。EAはまさにこれらに注目する必要があります。
しかし、それ以外はどうでしょうか。自動化されたEAを作成する目的などでは、他のタイプは重要ではありません。他のタイプの適用方法を確認したい場合は、「一からの取引エキスパートアドバイザーの開発(第25部):システムの堅牢性の提供(II)」をご覧ください。他の列挙がどのように使用できるかはこの中で示しています。
これらのケースがどこから来るのかを説明したので、それぞれのケースが何をするのか、以下に示すものから分解して説明しましょう。
case TRADE_ACTION_DEAL: (*manager).UpdatePosition(request.order); break;
この列挙は、成行注文が執行されたときに呼び出されます。しかし、このケースに限ったことではありません。後1つ呼び出されることがあります。TRADE_TRANSACTION_ORDER_DELETEについてお話しした際に、trans.orderとtrans.positionが等しい場合があることをお話ししました。これは、サーバがTRADE_ACTION_DEALをトリガーした場合の2つ目のケースです。よって、チケットの値をポジションとして追加することができるようになります。ただし、何かあった場合、例えば、別のポジションがまだ開いている場合などは、エラーが発生してEAが終了してしまうので注意が必要です。ここでは紹介していませんが、UpdatePositionのコードで確認することができます。
以下の次のコードをご覧ください。
case TRADE_ACTION_SLTP: (*manager).UpdatePosition(trans.position); break;
この列挙は、テイクプロフィットやストップロスと呼ばれるリミット値を変更する際にトリガーされます。単にstopとtakeの値を更新します。このバージョンは非常にシンプルで、もう少し面白くする方法もありますが、今はこれだけで十分です。そして、コードにある最後の列挙は、すぐ下にあります。
case TRADE_ACTION_REMOVE: (*manager).EraseTicketPending(request.order); break;
このコードは注文が削除されたときにトリガーされ、EAが未決注文を送信できるように、C_Managerクラスに通知する必要があります。通常、未決注文はオーダーブックから削除されませんが、ユーザーが削除してしまった可能性があります。注文が偶然または意図的にオーダーブックから削除され、EAがC_Managerクラスに通知しない場合、EAが別の未決注文を送信することができなくなります。
結論
今回は、受注システムに関する問題をより早く、より良く処理するために、イベントハンドリングシステムをどのように活用するかを考えてみました。このシステムにより、EAは必要なデータを常に検索する必要がなくなり、より速く動作するようになります。確かに、まだ自動化されていないEAを扱っていますが、近々、基本的なものであっても自動化を追加し、ブレークイーブンとトレーリングストップをEAに管理させる予定です。添付のファイルは、過去3回の記事で取り上げたコードの完全版を提供するものです。このコードをよく読んで、すべてが実際にどのように動作するのかを理解することをお勧めします。
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/11248
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索