MetaTrader 4でMQL5ウィザードの既製エキスパートアドバイザーが機能
Stanislav Korotky | 17 5月, 2017
MetaTrader 4およびMetaTrader 5クライアント端末では、組み込みの MQLウィザードを使用してMQLプログラムのプロトタイプを作成できます。両端末バージョンのウィザードは似ていますが、依然として重要な違いが1つあります。MetaTrader 5ウィザードには既製エキスパートアドバイザー生成オプションがありますが、MetaTrader 4ウィザードにはありません。これは、これらのEAが標準MQLライブラリクラス、すなわち端末で利用可能なヘッダファイルのセットに基づいているという事実と関連しています。MetaTrader 4にはこのようなライブラリも含まれていますが、MQL5の取引クラスはありません。特に、取引注文の作成や送信、指標値や価格アクションに基づいたシグナル計算、トレーリング、資産管理などを受け持つクラスは存在しませんが、これらの機能はすべて自動的に生成されたエキスパートアドバイザーを構築するために必要な基礎となります。
この状況は、MQL5が段階的に発展してきたことに起因します。この新しい言語はもともとMetaTrader 5に登場し、この端末のための標準ライブラリが開発されました。MQL5がMetaTrader 4に統合されたのは後になってです。しかし、取引関数は2つの端末のAPIでは非常に異なります。そのため、標準ライブラリはMT4に部分的にのみ移動され、取引クラスは除かれました。その結果、MetaTrader 4ウィザードは既成エキスパートアドバイザーを生成することはできません。
同時に、MetaTrader 4はまだまだ普及しており、既製エキスパートアドバイザーを生成する機能は非常に便利です。MetaTrader 4ではすでに新機能は追加されておらず新しいバージョンではエラー修正のみが行われていますが、ウィザードがさらに改善される可能性はほとんどありません。ただし、MetaTrader 5ウィザードを使用して生成されたコードをMetaTrader 4に移動するのは自由です。このコードの動作に必要なのは、オリジナルMQL API MetaTrader 4に適合した標準ライブラリの一連の取引クラスです。つまり、MetaTrader 4で利用できないクラスをMetaTrader 5標準ライブラリからコピーし、それらのためのMetaTrader 5取引環境のエミュレーションを実装する必要があります。
計画
どのような仕事も、ある先行計画に執着するときに最もうまくいくものです。このアプローチの例はいわゆるウォーターフォールモデルで、開発に使用されます。ここでの状況は開発に加えて記事の形で説明を追加するというものなのでこのモデルが適しています。しかし、実際にはエクストリームプログラミングのような柔軟なアプローチの1つを使用して、MQL5コードをMQL4(またはその逆)に移動するのがより効率的です。モットーは、計画をより少なく、行動をより多くすることです。文字通りこれは、ソースコードをコンパイルして発生したエラーを修正できることを意味します。本稿で私が提案している計画はいきなり現れたものではありません。それは、コンパイラによって生成される「ヒント」に基づいて徐々に形成されたものです。
2つの端末のライブラリを比較すると、MetaTrader 4にはTrade、Expert 、Modelsフォルダがないことがわかります。したがって、主なタスクは、これらのフォルダからすべてのクラスをMetaTrader 4に移植することです。それに加えてIndicatorsフォルダで何かの修正が必要であることは明らかです。これはMetaTrader 4ライブラリにはありますが、これらの端末での指標を使用した操作の原則は異なります。しかし、どのような場合でも、ライブラリファイルの編集はできるだけ少なくするという原則が従われるべきです。なぜならライブラリは時々更新されるからです。この場合、編集内容を公式にカスタマイズする必要があります。
コピーされたすべてのファイルは、ある程度、MetaTrader 5の取引MQL APIを参照しています。したがって、同じプログラミングインターフェイスを保持しつつ多かれ少なかれ完全な定義と関数のセットを開発して、すべての呼び出しをMetaTrader 4の継承されたMQL APIに変換する必要があります。エミュレートされた取引環境に何を含めるべきかを詳細に検討しましょう。型から始めましょう。型は建物、つまりアルゴリズムとプログラム自体を建てるのに使われるレンガと同じです。
最も単純な型は列挙型です。それらは構造体を通して直接的または間接的にほとんどの関数で使用されます。したがって、適応の順序は列挙型、構造体、定数、関数となります。
列挙型
必要な列挙の一部は既にMetaTrader 4に転送されています。例は、ENUM_ORDER_TYPE、ENUM_ORDER_PROPERTY_INTEGER、ENUM_ORDER_PROPERTY_DOUBLE、 ENUM_ORDER_PROPERTY_STRINGの注文プロパティです。これは便利に見える一方、列挙のすべてがMetaTrader 5と同じように定義されているわけではないので、問題にもなります。
たとえば、MetaTrader 5のENUM_ORDER_TYPEには、MetaTrader 4よりも多くの種類の注文が含まれています。ENUM_ORDER_TYPEをそのままにしておくと、コピーされたコードが不足している要素を参照するため、コンパイルエラーが発生します。列挙を再定義することはできません。したがって、最も簡単な解決策は、下記のようなプリプロセッサのマクロ定義です。
// ENUM_ORDER_TYPEの拡張 #define ORDER_TYPE_BUY_STOP_LIMIT ((ENUM_ORDER_TYPE)6) #define ORDER_TYPE_SELL_STOP_LIMIT ((ENUM_ORDER_TYPE)7) #define ORDER_TYPE_CLOSE_BY ((ENUM_ORDER_TYPE)8)
他のMetaTrader 4で利用できない列挙型は、たとえばMT5との類推によって定義できます。
enum ENUM_ORDER_TYPE_FILLING { ORDER_FILLING_FOK, ORDER_FILLING_IOC, ORDER_FILLING_RETURN };
したがって、以下の列挙を定義する(または定数を追加する)必要があります。一見すると、列挙が多すぎるように見えます。しかし、過程は簡単でそれらをドキュメントからコピーするだけです(適切なセクションへのリンクは以下にあります。アスタリスクは、いくつかの調整が必要な既存の列挙を示しています)。
- 注文
- ENUM_ORDER_TYPE_TIME
- ENUM_ORDER_STATE
- ENUM_ORDER_TYPE_FILLING
- ENUM_ORDER_TYPE (*)
- ENUM_ORDER_PROPERTY_INTEGER (*)
- ENUM_ORDER_PROPERTY_STRING (*)
- ポジション
- ENUM_POSITION_TYPE
- ENUM_POSITION_PROPERTY_INTEGER
- ENUM_POSITION_PROPERTY_DOUBLE
- ENUM_POSITION_PROPERTY_STRING
- 取引
- ENUM_DEAL_ENTRY
- ENUM_DEAL_TYPE
- ENUM_DEAL_PROPERTY_INTEGER
- ENUM_DEAL_PROPERTY_DOUBLE
- ENUM_DEAL_PROPERTY_STRING
- 取引操作型
- ENUM_TRADE_REQUEST_ACTIONS
MetaTrader 4にはすでにENUM_SYMBOL_INFO_INTEGER、ENUM_SYMBOL_INFO_DOUBLE、ENUM_SYMBOL_INFO_STRINGなどの銘柄を記述する列挙型の定義が含まれています。その中のいくつかの要素は予約されていますが(ドキュメントに書かれているように)動作しません。これらはMetaTrader 5と異なるMetaTrader 4プラットフォームの制限事項であり、そのまま受け入れられる必要があります。私たちにとっては、プロジェクトでこれらの列挙を定義する必要がないことだけが重要です。
構造体
列挙に加えて、MetaTrader 5の関数では構造体が使用されます。それらの定義はドキュメントでも見ることができます(適切なセクションへのリンクは以下にあります)。
マクロの定義
上記の型に加えて、MT5のソースコードは多くの定数を使用します。このプロジェクトでこれらの定数を定義する最も簡単な方法は、プリプロセッサの#define指示文を使うことです。
- 取引サーバのリターンコード
- TRADE_RETCODE_...
- 銘柄情報
- 有効期限フラグのセット:SYMBOL_EXPIRATION_...
- 注文処理フラグのセット:SYMBOL_FILLING_...
- 注文型フラグのセット:SYMBOL_ORDER_...
取引関数
この計画の最後及び最も重要なポイントは取引関数を含みます。実装は上記のすべての型と定数を定義した後にのみ開始できます。
取引関数のリストは印象的です。それは4つのグループに分けることができます。
- 注文
- ポジション
- 注文履歴
- 取引履歴
最後に、以下の単純な置換を使用します。
#define MQL5InfoInteger MQLInfoInteger #define MQL5InfoString MQLInfoString
実際、上記は同じ端末カーネル関数ですが、MQL5とMQL4ではその名前が少し異なります。
このまま実装に進む前に、MetaTrader 5取引モデルをMetaTrader 4取引モデルに反映させる方法を定義する必要があります。
リフレクション
MetaTrader 5とMetaTrader 4のエンティティを較べてみましょう。MT4で始める方が簡単です。ここでは、「注文」という普遍的な概念が使用されています。この概念は実質的に、成行注文、未決注文、取引操作の履歴などのすべてを指します。注文はこれらすべての場合で異なる状態にあります。MT5では、成行注文はポジション、未決注文は注文、履歴は取引として書かれています。
最も単純なケースでは、MetaTrader 5の操作は次のとおりです。ポジションを形成するためには、市場参入のための注文が取引サーバに送られる。ポジションを決済するには、別の注文が送信されます。これは市場からエグジットするための注文です。各注文は、対応する取引の枠内で実行され、取引は取引履歴に追加されます。したがって、エミュレートされたMT5取引環境では、1つのMT4成行注文は以下のように表示される必要があります。
- エントリー注文
- エントリー取引
- ポジション
- エグジット注文
- エグジット取引
MetaTrader 5はもともと純粋なネットプラットフォームでした。つまり、一度に1つの銘柄について存在できるのは1ポジションだけでした。銘柄のすべての注文は、その取引総量を増加、減少または完全に削除、またはその総逆指値および指値を変更するものでした。このモードはMetaTrader 4では使用できません。MegaTrader 5にヘッジのサポートが追加されていなかったら、このプロジェクトを実装するのは非常に困難だったでしょう。これは、MetaTrader 4で採用されたモードです。各注文の実行は、反対の注文を含む複数の注文が同じ銘柄上に存在するように、別個の「ポジション」(MetaTrader 5に関して)を形成します。
実装
MetaTrader 5環境のエミュレーション
簡易化のために、型、定数、関数などのエミュレートされた環境全体を単一のMT5Bridge.mqhヘッダファイルに配置します。プログラミングスタイルをよくするにはこれらはおそらく別々のファイルに配置する必要があるでしょう。この構造化は、大規模プロジェクトやチームプロジェクトにとって特に重要です。ただし、配布とインストールの面ではファイルがひとつであるとより便利です。
私たちの計画によれば、すべての列挙型、定数、構造体を定義する必要があります。これはよくあるコピー作業で、複雑ではありません。詳細を説明する必要はありません。計画段階で提供されたコメントで十分です。ドキュメンテーションの取引関数に関する情報をもう一度確認し、これらのすべての関数のコードを記述するなど、より知的な部分に進むことにしましょう。
現在の操作から始めましょう。これには市場操作や未決注文の処理、ポジションなどがあります。
この目的のためには、超普遍MT5関数であるOrderSend が必要です。
bool OrderSend(MqlTradeRequest &request, MqlTradeResult &result) {
この関数ではリクエストの型によってMT4注文型のうちの1つを使用する必要があります。
int cmd; result.retcode = 0; switch(request.type) { case ORDER_TYPE_BUY: cmd = OP_BUY; break; case ORDER_TYPE_SELL: cmd = OP_SELL; break; case ORDER_TYPE_BUY_LIMIT: cmd = OP_BUYLIMIT; break; case ORDER_TYPE_SELL_LIMIT: cmd = OP_SELLLIMIT; break; case ORDER_TYPE_BUY_STOP: cmd = OP_BUYSTOP; break; case ORDER_TYPE_SELL_STOP: cmd = OP_SELLSTOP; break; default: Print("Unsupported request type:", request.type); return false; }
actionフィールドから渡された操作コードは、注文の配置、削除、および変更をさまざまな方法で処理することができます。例えば、成行注文や未決注文は以下のように実装することができます。
ResetLastError(); if(request.action == TRADE_ACTION_DEAL || request.action == TRADE_ACTION_PENDING) { if(request.price == 0) { if(cmd == OP_BUY) { request.price = MarketInfo(request.symbol, MODE_ASK); } else if(cmd == OP_SELL) { request.price = MarketInfo(request.symbol, MODE_BID); } } if(request.position > 0) { if(!OrderClose((int)request.position, request.volume, request.price, (int)request.deviation)) { result.retcode = GetLastError(); } else { result.retcode = TRADE_RETCODE_DONE; result.deal = request.position | 0x8000000000000000; result.order = request.position | 0x8000000000000000; result.volume = request.volume; result.price = request.price; } } else { int ticket = OrderSend(request.symbol, cmd, request.volume, request.price, (int)request.deviation, request.sl, request.tp, request.comment, (int)request.magic, request.expiration); if(ticket == -1) { result.retcode = GetLastError(); } else { result.retcode = TRADE_RETCODE_DONE; result.deal = ticket; result.order = ticket; result.request_id = ticket; if(OrderSelect(ticket, SELECT_BY_TICKET)) { result.volume = OrderLots(); result.price = OrderOpenPrice() > 0 ?OrderOpenPrice() : request.price; result.comment = OrderComment(); result.ask = MarketInfo(OrderSymbol(), MODE_ASK); result.bid = MarketInfo(OrderSymbol(), MODE_BID); } else { result.volume = request.volume; result.price = request.price; result.comment = ""; } } } }
基本的な作業は、多くのパラメータを持つ通常のMT4OrderSend 関数によって実行されます。呼び出し後、演算結果は適切に出力構造体に書き込まれます。
MetaTrader 5では、既存の成行注文は反対方向の別の注文を開くことによって決済され、決済されたポジションの識別子はpositionフィールドに渡されます。この場合、つまりpositionフィールドが空でない場合、上記のコードはOrderClose 関数を使用して注文の決済を試みます。ここでは、注文のチケットはポジション識別子として使用されます。MT4では各注文が独自のポジションを形成するためこれは論理的です。取引には同じチケットが与えられます。
ポジションを決済する仮想(実際には存在しない)注文に関しては、元の数値の上位ビットが1に設定されたものがチケット番号として人為的に使用されます。これは後で注文と取引を列挙するときに使用されます。
次に、ポジションレベルの変更を実装する方法を見てみましょう。
else if(request.action == TRADE_ACTION_SLTP) // ポジションを変更する { if(OrderSelect((int)request.position, SELECT_BY_TICKET)) { if(!OrderModify((int)request.position, OrderOpenPrice(), request.sl, request.tp, 0)) { result.retcode = GetLastError(); } else { result.retcode = TRADE_RETCODE_DONE; result.deal = OrderTicket(); result.order = OrderTicket(); result.request_id = OrderTicket(); result.volume = OrderLots(); result.comment = OrderComment(); } } else { result.retcode = TRADE_RETCODE_POSITION_CLOSED; } }
この目的にOrderModifyが使用されているのは明確です。
この関数は未決注文を変更するためにも使用されます。
else if(request.action == TRADE_ACTION_MODIFY) // 未決注文を変更する { if(OrderSelect((int)request.order, SELECT_BY_TICKET)) { if(!OrderModify((int)request.order, request.price, request.sl, request.tp, request.expiration)) { result.retcode = GetLastError(); } else { result.retcode = TRADE_RETCODE_DONE; result.deal = OrderTicket(); result.order = OrderTicket(); result.request_id = OrderTicket(); result.price = request.price; result.volume = OrderLots(); result.comment = OrderComment(); } } else { result.retcode = TRADE_RETCODE_INVALID_ORDER; } }
未決注文の削除は、標準のOrderDelete関数によって実行されます。
else if(request.action == TRADE_ACTION_REMOVE) { if(!OrderDelete((int)request.order)) { result.retcode = GetLastError(); } else { result.retcode = TRADE_RETCODE_DONE; } }
最後に、操作による決済(反対のポジションによるポジションの決済)は、MetaTrader 4のコンテキストでは反対の注文を決済することと同じです。
else if(request.action == TRADE_ACTION_CLOSE_BY) { if(!OrderCloseBy((int)request.position, (int)request.position_by)) { result.retcode = GetLastError(); } else { result.retcode = TRADE_RETCODE_DONE; } } return true; }
OrderSendに加えて、MetaTrader 5には非同期的なOrderSendAsync関数が存在します。これは実装せず、ライブラリで非同期モードを使用するすべてのケースを無効にします。つまり、実際には同期バージョンで置き換えます。
注文を出すにはよくOrderCalcMargin、OrderCalcProfit、OrderCheckの3つの関数の呼び出しが伴います。
下記はMetaTrader 4で利用可能なツールを使用してそれらを実装するバージョンの1つです。
int EnumOrderType2Code(int action){ // ORDER_TYPE_BUY/ORDER_TYPE_SELLと派生 return (action % 2 == 0) ?OP_BUY : OP_SELL; }
bool OrderCalcMargin( ENUM_ORDER_TYPE action, string symbol, double volume, double price, double &margin ) { int cmd = EnumOrderType2Code(action); double m = AccountFreeMarginCheck(symbol, cmd, volume); if(m <= 0 || GetLastError() == ERR_NOT_ENOUGH_MONEY) { return false; } margin = AccountFreeMargin() - m; return true; }
bool OrderCalcProfit( ENUM_ORDER_TYPE action, string symbol, double volume, double price_open, double price_close, double &profit ) { int cmd = EnumOrderType2Code(action); if(cmd > -1) { int points = (int)((price_close - price_open) / MarketInfo(symbol, MODE_POINT)); if(cmd == OP_SELL) points = -points; profit = points * volume * MarketInfo(symbol, MODE_TICKVALUE) / (MarketInfo(symbol, MODE_TICKSIZE) / MarketInfo(symbol, MODE_POINT)); return true; } return false; } bool OrderCheck(const MqlTradeRequest &request, MqlTradeCheckResult &result) { if(request.volume > MarketInfo(request.symbol, MODE_MAXLOT) || request.volume < MarketInfo(request.symbol, MODE_MINLOT) || request.volume != MathFloor(request.volume / MarketInfo(request.symbol, MODE_LOTSTEP)) * MarketInfo(request.symbol, MODE_LOTSTEP)) { result.retcode = TRADE_RETCODE_INVALID_VOLUME; return false; } double margin; if(!OrderCalcMargin(request.type, request.symbol, request.volume, request.price, margin)) { result.retcode = TRADE_RETCODE_NO_MONEY; return false; } if((request.action == TRADE_ACTION_DEAL || request.action == TRADE_ACTION_PENDING) && SymbolInfoInteger(request.symbol, SYMBOL_TRADE_MODE) == SYMBOL_TRADE_EXECUTION_MARKET && (request.sl != 0 || request.tp != 0)) { result.retcode = TRADE_RETCODE_INVALID_STOPS; return false; } result.balance = AccountBalance(); result.equity = AccountEquity(); result.profit = AccountEquity() - AccountBalance(); result.margin = margin; result.margin_free = AccountFreeMargin(); result.margin_level = 0; result.comment = ""; return true; }
ここではAccountEquity、AccountFreeMargin、AccountFreeMarginCheckなどの組み込み関数や、銘柄のポイント値やMarketInfoの呼び出しによって実現できるその他の設定を積極的に使用しています 。
ポジション総数を取得するには、開いている成行注文の数を返すだけで十分です。
int PositionsTotal() { int count = 0; for(int i = 0; i < ::OrdersTotal(); i++) { if(OrderSelect(i, SELECT_BY_POS)) { if(OrderType() <= OP_SELL) { count++; } } } return count; }
番号でポジションの銘柄を取得するには、すべての銘柄をループし、成行注文のみを数える必要があります。
string PositionGetSymbol(int index) { int count = 0; for(int i = 0; i < ::OrdersTotal(); i++) { if(OrderSelect(i, SELECT_BY_POS)) { if(OrderType() <= OP_SELL) { if(index == count) { return OrderSymbol(); } count++; } } } return ""; }
番号でポジションのチケットを取得する関数も同様に構築されます。
ulong PositionGetTicket(int index) { int count = 0; for(int i = 0; i < ::OrdersTotal(); i++) { if(OrderSelect(i, SELECT_BY_POS)) { if(OrderType() <= OP_SELL) { if(index == count) { return OrderTicket(); } count++; } } } return 0; }
銘柄名でポジションを選択するには、成行注文をループし、一致する銘柄を持った最初のポジションで停止します。
bool PositionSelect(string symbol) { for(int i = 0; i < ::OrdersTotal(); i++) { if(OrderSelect(i, SELECT_BY_POS)) { if(OrderSymbol() == symbol && (OrderType() <= OP_SELL)) { return true; } } } return false; }
チケットによるポジション選択は、ループなしで実施することができます。
bool PositionSelectByTicket(ulong ticket) { if(OrderSelect((int)ticket, SELECT_BY_TICKET)) { if(OrderType() <= OP_SELL) { return true; } } return false; }
選択されたポジションのプロパティは、MetaTrader 5で使用される _GetDouble、_GetInteger、_GetStringの3つの関数から返されます。ここでは、ポジションでの実装を紹介します。注文や取引については非常によく似ているので、ここでは分析しません。ただし、これらの関数のコードは添付ファイルで使用できます。
// Position = 注文、OP_BUYまたはOP_SELLのみ ENUM_POSITION_TYPE Order2Position(int type) { return type == OP_BUY ?POSITION_TYPE_BUY : POSITION_TYPE_SELL; } bool PositionGetInteger(ENUM_POSITION_PROPERTY_INTEGER property_id, long &long_var) { switch(property_id) { case POSITION_TICKET: case POSITION_IDENTIFIER: long_var = OrderTicket(); return true; case POSITION_TIME: case POSITION_TIME_UPDATE: long_var = OrderOpenTime(); return true; case POSITION_TIME_MSC: case POSITION_TIME_UPDATE_MSC: long_var = OrderOpenTime() * 1000; return true; case POSITION_TYPE: long_var = Order2Position(OrderType()); return true; case POSITION_MAGIC: long_var = OrderMagicNumber(); return true; } return false; } bool PositionGetDouble(ENUM_POSITION_PROPERTY_DOUBLE property_id, double &double_var) { switch(property_id) { case POSITION_VOLUME: double_var = OrderLots(); return true; case POSITION_PRICE_OPEN: double_var = OrderOpenPrice(); return true; case POSITION_SL: double_var = OrderStopLoss(); return true; case POSITION_TP: double_var = OrderTakeProfit(); return true; case POSITION_PRICE_CURRENT: double_var = MarketInfo(OrderSymbol(), OrderType() == OP_BUY ?MODE_BID : MODE_ASK); return true; case POSITION_COMMISSION: double_var = OrderCommission(); return true; case POSITION_SWAP: double_var = OrderSwap(); return true; case POSITION_PROFIT: double_var = OrderProfit(); return true; } return false; } bool PositionGetString(ENUM_POSITION_PROPERTY_STRING property_id, string &string_var) { switch(property_id) { case POSITION_SYMBOL: string_var = OrderSymbol(); return true; case POSITION_COMMENT: string_var = OrderComment(); return true; } return false; }
実際には成行注文であるポジションと同様に、未決注文を処理する一連の関数の実装も必要です。しかし、一つ困難なことがあります。 OrdersTotal関数やその他のOrderGet_関数はカーネルで定義済みで、組み込み関数はオーバーライドできないために実装できません。コンパイラは次のエラーを返します。
'OrderGetString' - override system function MT5Bridge.mqh(システム関数のオーバーライド)
したがって、すべての関数の名前をOrder_で始まる名前に変えなければなりません。未決注文のみが処理されるので名前をPendingOrder_で始めることは理にかなっています。例として:
int PendingOrdersTotal() { int count = 0; for(int i = 0; i < ::OrdersTotal(); i++) { if(OrderSelect(i, SELECT_BY_POS)) { if(OrderType() > OP_SELL) { count++; } } } return count; }
標準ライブラリのコードでは、これらの新しい関数へのすべての呼び出しをMT5Bridge.mqhから置き換える必要があります。
注文チケットを数値で返す OrderGetTicket関数はMetaTrader 4には存在しないため、名前を変更する必要はなく、MetaTrader 5 APIに従って使用します。
ulong OrderGetTicket(int index) { int count = 0; for(int i = 0; i < ::OrdersTotal(); i++) { if(OrderSelect(i, SELECT_BY_POS)) { if(OrderType() > OP_SELL) { if(index == count) { return OrderTicket(); } count++; } } } return 0; }
MetaTrader 4にはMetaTrader 5と比較してパラメータのリストが拡張されたOrderSelect関数が存在するため、呼び出しをそのままにして必要なSELECT_BY_TICKETパラメータを追加します。
未決注文のプロパティを読み取る関数の完全な実装は、添付のヘッダーファイルで利用できます。
次に、注文及び取引の履歴を扱う関数を見てみましょう。それらの実装にはいくつか工夫が必要です。以下のバリアント(多くの可能なバリアントのうちの1つ)は、その単純さのために選択されました。
MetaTrader 4の各成行注文は、2つのMT5形式の注文(エントリーとエグジット)として履歴に表示されます。さらに、履歴には対応する一対の取引が含まれている必要があります。未決注文はそのまま表示されます。履歴は、チケットを含む2つの配列に格納されます。
int historyDeals[], historyOrders[];
それらはMQL5 APIのHistorySelect関数で埋められます。
bool HistorySelect(datetime from_date, datetime to_date) { int deals = 0, orders = 0; ArrayResize(historyDeals, 0); ArrayResize(historyOrders, 0); for(int i = 0; i < OrdersHistoryTotal(); i++) { if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY)) { if(OrderOpenTime() >= from_date || OrderCloseTime() <= to_date) { if(OrderType() <= OP_SELL) // deal { ArrayResize(historyDeals, deals + 1); historyDeals[deals] = OrderTicket(); deals++; } ArrayResize(historyOrders, orders + 1); historyOrders[orders] = OrderTicket(); orders++; } } } return true; }
配列がいっぱいになると、履歴のサイズが取得できます。
int HistoryDealsTotal() { return ArraySize(historyDeals) * 2; } int HistoryOrdersTotal() { return ArraySize(historyOrders) * 2; }
MetaTrader 4の各注文は、MetaTrader 5では2つの注文または2つの取引として表されるため、配列のサイズに2を掛けます。未決注文には必要ではありませんが、アプローチの共通性を保つために2チケットが予約されており、そのうちの1つは使用されません(下記のHistoryOrderGetTicket関数を参照)。市場参入取引は、この取引を生成したMetaTrader 4の注文で使用されたのと同じチケットを持ちます。エグジット取引の場合、このチケットには単一の上位ビットが追加されます。
ulong HistoryDealGetTicket(int index) { if(OrderSelect(historyDeals[index / 2], SELECT_BY_TICKET, MODE_HISTORY)) { // 奇数 - 参入 - 正、偶数 - エグジット - 負 return (index % 2 == 0) ?OrderTicket() : (OrderTicket() | 0x8000000000000000); } return 0; }
履歴内の偶数には常に参入チケット(リアル)が含まれ、奇数はエグジットチケット(バーチャル)です。
注文については少し複雑です。未決注文がふくまれ、それはそのまま表示されます。この場合、偶数は未決注文の正しいチケットを返し、次の奇数は0を返します。
ulong HistoryOrderGetTicket(int index) { if(OrderSelect(historyOrders[index / 2], SELECT_BY_TICKET, MODE_HISTORY)) { if(OrderType() <= OP_SELL) { return (index % 2 == 0) ?OrderTicket() : (OrderTicket() | 0x8000000000000000); } else if(index % 2 == 0) // 未決注文が一度返される { return OrderTicket(); } else { Print("History order ", OrderType(), " ticket[", index, "]=", OrderTicket(), " -> 0"); } } return 0; }
チケットによる取引の選択は、上位ビットを追加してこの特徴を考慮して実装されます(ここでは抜かします)。
bool HistoryDealSelect(ulong ticket) { ticket &= ~0x8000000000000000; return OrderSelect((int)ticket, SELECT_BY_TICKET, MODE_HISTORY); }
注文については、すべてが完全に類似しています。
#define HistoryOrderSelect HistoryDealSelect
HistoryDealSelectやHistoryDealGetTicketを使用して取引を選択すると、取引プロパティにアクセスするための関数の実装を書くことができます。
#define REVERSE(type) ((type + 1) % 2) ENUM_DEAL_TYPE OrderType2DealType(const int type) { static ENUM_DEAL_TYPE types[] = {DEAL_TYPE_BUY, DEAL_TYPE_SELL, -1, -1, -1, -1, DEAL_TYPE_BALANCE}; return types[type]; } bool HistoryDealGetInteger(ulong ticket_number, ENUM_DEAL_PROPERTY_INTEGER property_id, long &long_var) { bool exit = ((ticket_number & 0x8000000000000000) != 0); ticket_number &= ~0x8000000000000000; if(OrderSelect((int)ticket_number, SELECT_BY_TICKET, MODE_HISTORY)) { switch(property_id) { case DEAL_TICKET: case DEAL_ORDER: case DEAL_POSITION_ID: long_var = OrderTicket(); return true; case DEAL_TIME: long_var = exit ?OrderCloseTime() : OrderOpenTime(); return true; case DEAL_TIME_MSC: long_var = (exit ?OrderCloseTime() : OrderOpenTime()) * 1000; return true; case DEAL_TYPE: long_var = OrderType2DealType(exit ?REVERSE(OrderType()) : OrderType()); return true; case DEAL_ENTRY: long_var = exit ?DEAL_ENTRY_OUT : DEAL_ENTRY_IN; return true; case DEAL_MAGIC: long_var = OrderMagicNumber(); return true; } } return false; } bool HistoryDealGetDouble(ulong ticket_number, ENUM_DEAL_PROPERTY_DOUBLE property_id, double &double_var) { bool exit = ((ticket_number & 0x8000000000000000) != 0); ticket_number &= ~0x8000000000000000; switch(property_id) { case DEAL_VOLUME: double_var = OrderLots(); return true; case DEAL_PRICE: double_var = exit ?OrderClosePrice() : OrderOpenPrice(); return true; case DEAL_COMMISSION: double_var = exit?0 : OrderCommission(); return true; case DEAL_SWAP: double_var = exit ?OrderSwap() : 0; return true; case DEAL_PROFIT: double_var = exit ?OrderProfit() : 0; return true; } return false; } bool HistoryDealGetString(ulong ticket_number, ENUM_DEAL_PROPERTY_STRING property_id, string &string_var) { switch(property_id) { case DEAL_SYMBOL: string_var = OrderSymbol(); return true; case DEAL_COMMENT: string_var = OrderComment(); return true; } return false; }
アイデアが明確であることを願っています。履歴中の注文を処理するための関数のグループ。
標準ライブラリファイルの変更
ライブラリに必要な編集のいくつかは、関数の実装中に既に議論されています。変更の完全なリストはこのプロジェクトで作成されたファイルとMetaTrader 5のファイルを比較して取得できます。さらに、最も重要な点のみが考慮され、軽微な修正に対するコメントは省略されます。MT5Bridge.mqhとつながるための新しい #include指示文は多くのファイルにみられます。
標準ライブラリファイルの主な変更点の表
ファイル/メソッド | 変更点 | |
---|---|---|
Trade.mqh | SetAsyncMode | サポートされていない非同期モードに関連する行の削除 |
SetMarginMode | ACCOUNT_MARGIN_MODE_RETAIL_HEDGINGモードの明示的な指定 | |
OrderOpen | 有効期限モードを設定するフラグの組み合わせのSYMBOL_EXPIRATION_GTC | SYMBOL_EXPIRATION_SPECIFIEDによる明示的な指定 | |
OrderTypeCheck | 存在しない型の処理であるORDER_TYPE_BUY_STOP_LIMIT、ORDER_TYPE_SELL_STOP_LIMITの除外 | |
OrderSend | 存在しないOrderSendAsync関数の呼び出しの削除 | |
OrderInfo.mqh | OrderGetInteger、OrderGetDouble、OrderGetStringの呼び出しのPendingOrderプレフィックスを付けた関数名による置き換え | |
OrderSelect(m_ticket)の呼び出しのすべてのOrderSelect((int)m_ticket, SELECT_BY_TICKET)による置き換え | ||
PositionInfo.mqh | FormatPosition SelectByIndex |
ACCOUNT_MARGIN_MODE_RETAIL_HEDGINGマージンモードの設定 |
SymbolInfo.mqh | Refresh | MetaTrader 4でサポートされていない多数のチェックの削除 |
AccountInfo.mqh | MarginMode | ACCOUNT_MARGIN_MODE_RETAIL_HEDGING定数を返す |
Expert.mqh | TimeframeAdd TimeframesFlags |
サポートされていない時間枠の削除 |
ExpertBase.mqh | #include <Indicators\IndicatorsExt.mqh>の追加 | |
SetMarginMode | ACCOUNT_MARGIN_MODE_RETAIL_HEDGINGを無条件に設定 |
IndicatorsExt.mqh ファイルは、標準のIndicators.mqhファイルのマイナーエラーの修正に必要です。さらに、指標に必要な別のTimeSeriesExt.mqhヘッダファイルが含まれています。
TimeSeriesExt.mqhファイルには、MetaTrader 5スタイルで取引するために必要な、MetaTrader 4の標準 TimeSeries.mqhファイルでは使用できないクラスの説明が含まれています。
具体的に言うとこれはCTickVolumeBuffer、CSpreadBuffer、CiSpread、 CiTickVolume、CRealVolumeBuffer、CiRealVolumeクラスです。それらの多くは何もしないスタブです(対応する関数はMetaTrader 4では利用できないので何もできません)。
テスト
MetaTrader 4のインクルードディレクトリ(サブディレクトリの階層を保持)に標準ライブラリの適合された取引クラスを保存し、MT5Bridge.mqhをInclude/Tradeフォルダにコピーすることで、MetaTrader 5ウィザードによって生成されたエキスパートアドバイザーをMetaTrader 4でコンパイルして実行することができます。
MetaTrader 5パッケージには、生成されたエキスパートアドバイザーの例が含まれています( Experts/Advisorsフォルダ内)。これらのEAの1つであるExpertMACD.mq5を使用して作業します。それを MQL4/Expertsフォルダにコピーして名前をExpertMACD.mq4に変えます。エディタでのコンパイルには、およそ次の結果が必要です。
MetaTrader 4でのMetaTrader 5からのエキスパートアドバイザーのコンパイル
ご覧のとおり、ライブラリファイルはつなげられ、エラーや警告なしで処理されます。もちろん、コンパイルエラーがなくてもプログラムロジックに問題がないことは保証されませんが、これはさらに実用的なテストのための作業です。
コンパイルしたエキスパートアドバイザーをMetaTrader 4テスターでデフォルト設定で実行しましょう。
MetaTrader 5で生成されたExpert AdvisorのMetaTrader 4テストレポート
ログをチェックして、明らかな注文処理エラーがないことを確認できます。
EURUSD M15チャートでは、決済逆指値や決済指値の設定のしかたを含めて、エキスパートアドバイザーの取引は良好に見えます。
MetaTrader 4のでMetaTrader 5ウィザードからのEAの作業を示すチャートウィンドウ
MetaTrader 5ストラテジーテスターの結果と比較しましょう。
生成されたエキスパートアドバイザーのMetaTrader 5テストレポート
明らかに違いがあります。これらは、相場の違い(たとえば、MetaTrader 5はフローティングスプレッドを使用)とテスターで使用されるさまざまなアルゴリズムによって説明できます。一般的に、テストは似ています。取引数とバランス曲線の全体的な性質はほぼ同じです。
もちろん、ユーザのみなさんはMetaTrader 5ウィザードで完全に任意のモジュールセットを使用して独自のエキスパートアドバイザーを生成してMetaTrader 4に簡単に転送することができます。特に、プロジェクトのテスト中には、トレールストップ機能と可変ロットサイズを持つエキスパートアドバイザーをテストしました。
終わりに
MetaTrader 5ウィザードを使用して生成されたエキスパートアドバイザーをMetaTrader 4プラットフォームに移行するのに可能な方法を考察しました。主な利点は、MetaTrader 5標準ライブラリの既存クラスの取引クラスコードを最大限に活用した実装の容易さです。主な欠点は、コンピュータに両方の端末が存在する必要性です。そのうちの1つはEASを生成するために使用され、2つ目ではこれらのEAが実行されます。
以下は2つのファイルです。
- 標準ライブラリの変更されたファイルを含むアーカイブ。MetaTrader 4のInclude ディレクトリにサブディレクトリの階層を保存してアンパックします。アーカイブには端末パッケージにないファイルしか含まれていないので、既存のファイルが上書きされる可能性はありません。
- MT5Bridge.mqhファイルはInclude/Tradeにコピーされるべきです。
このバージョンのライブラリは、MetaTrader 5ビルド1545から入手しました。将来のビルドには標準ライブラリの変更が含まれている可能性があり、これは有用です(エミュレータの編集でファイルを再マージする必要があるかもしれません)。完璧な解決策は、MetaQuotesの標準ライブラリバージョンを用意することです。このバージョンでは、MetaTrader 5とMetaTrader 4の2つのバージョンの実装はもともと条件付きコンパイル指令を使用して結合されます。
MetaTrader 4でMetaTrader 5取引環境の完全なエミュレーションを実装することはできません。新しい端末は、古い端末では利用できない新しい機能をカーネルレベルで提供しているからです。したがって、生成されたエキスパートアドバイザーで使用されているモジュールの一部が機能しなくなる可能性があります。
また、提供されているエミュレータの実装はベータ版で配布されており、隠れたエラーが含まれている可能性があることを忘れないでください。長くて多才なテストだけが、実際の取引に適した最終的なソフトウェア製品を手に入れるのに役立ちます。 ソースコードが使用可能なことで私たちはこれを協力して行うことができます。