
MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第24部): 未決取引リクエストの使用 - 初期実装(ポジションのオープン)
内容
概念
保留中リクエストの概念については、以前の記事ですでに言及しています。
本稿では、それが何であり、なぜそれが必要なのかを把握し、保留中リクエストの実装を開始します。
取引サーバエラーを受信して処理するにあたって、待機後にリクエストを繰り返す必要がある場合があります。最も単純な場合には、必要なミリ秒数でsleep()関数を実行してからリクエストを繰り返します。これは多くのプログラムでは十分です。ただし、プログラムは待機中には停止し、一時停止の完了を待ってからロジックを再開します。これはすべて取引メソッドで発生し、他のすべてのプログラム機能にはアクセスできません。
この欠点を回避するためには、待機後に繰り返しリクエストすることを必要とするエラーを引き起こす取引リクエストのコピーを作成し、そのリクエストを取引リクエストのリストに配置して、取引メソッドを終了できます。これにより、プログラムは取引メソッド内で待機せずにすみ、組み込みロジックに従って作業を再開できるようになります。取引クラスは、保留中リクエストのリストを常に監視します。リクエストを実行する時間になると(待機時間が終了すると)、ライブラリは適切な取引リクエストで必要な取引メソッドを呼び出します。その後、すべてが同じシーケンスで発生します。エラーが再発生した場合は、次の取引リクエストの前に取引メソッドを終了します。リクエストがエラーなしで実行され、サーバでキューに入れられた場合、保留中リクエストは取引リクエストのリストから削除されます。
これは、特に待機に時間がかかる場合に、待機中にプログラムの実行を中断しないように保留中リクエストを適用する方法の1つです。
保留中リクエストを適用する別の方法は、MQL4でのストップリミット注文の実装です。ストップリミット注文とは、ストップ注文とリミット注文の価格を含む二重指値注文で、価格がストップ注文に設定されたレベルに達すると、未決のリミット注文が設定されます。したがって、保留中リクエストにより、ストップリミット注文の操作ロジックを簡単に実装できます。リミット注文を出すための保留中リクエストを作成するだけで、リミット注文を出す瞬間(リクエストが行われる価格)は、保留中リクエストパラメータに書き込まれます。価格が指定された値に達するとすぐに、指値注文のリクエストがサーバに送信されます。このようなロジックは、ストップリミット注文の操作ロジックを完全に再現します。
保留中リクエストを適用する別の方法は、価格が指定されたレベルまたは指定された時間、あるいはその両方に達したときに取引リクエストを自動的にポジションに送信することです。
一般に、特定の条件に従って取引リクエストを送信するプロセスを自動化するオプションは多数あります。
データストレージとしてのマジックナンバーの使用
新しい保留中リクエストを作成するとき、この注文がこの保留中リクエストに応じて出されたものであることをプログラムが認識できるように、何らかの方法で印づける必要があります。つまり、注文またはポジションを正確に識別し、特定の保留中リクエストに関連付ける必要があります。さらに、この関連付けは緊急事態に留まる必要があります。
私はこのような関連付けを整理するさまざまな可能性について長い間考えた末、保留中リクエストIDを注文/ポジションのマジックナンバーに設定することにしました。これは、注文にもともと存在して変更されていない唯一のパラメータです。他のすべての方法は、信頼性が低い(注文/ポジションのコメントに格納)か、作成と保守(ファイルに保存)のために広範なリソースを必要とします。ターミナルのグローバル変数は、緊急の場合にさせ書き込むのに十分な時間がない可能性があり、したがってデータの関連性に対する完全な信頼性にかけているため、考慮しません。
あるのは、マジックナンバー値にデータを保存するという1つのソリューションだけです。以前、注文オブジェクトにグループIDを追加しました。そのIDにより、注文/ポジションを共通のグループにグループ化できます。これは、さまざまなEA(グリッドなど)に役立ちます。このようなIDは、サーバに物理的に出現した後にのみ注文オブジェクトに追加できますが、緊急事態では失われます。もちろん、すべてのコレクションはオブジェクトと一緒にHDDに保存されますが、それはかなり後で行われます。現時点では、保留中リクエストIDとともにグループIDを注文マジックナンバーに追加できます。
マジックナンバー値には複数のIDを保存できます。
- マジックナンバーID(EA入力で設定)
- 最初のグループID(0〜15のサブグループ番号、ゼロ-グループに属しません)
- 2番目のグループID(0〜15のサブグループ番号、ゼロ-グループに属しません)
- 保留中リクエストID(可能な数字は0~255、ゼロ— IDなし)
したがって、1番目および2番目の注文グループを設定して、各注文グループには、最大15個のサブグループを含めることができます。それはどのように役立つのでしょうか?特定の基準で単一のサブグループにグループしたい20の注文/ポジションがあるとします。最初のグループでそれらのサブグループ番号を1に設定します。また、他の基準に従って、同じ最初のグループの別のサブグループにグループ化したい別の20の注文/ポジションがあるとします。それらにサブグループ番号2を設定します。サブグループ3は、さらに他の20の注文/ポジションに割り当てられます。これで、最初のグループの各サブグループのハンドラーを使用して簡単に一緒に処理できる注文/ポジションの3つのグループができたことになります。既に確立されているグループへの所属を失うことなく、別のハンドラーを使用して、3つのうち2つのグループを他の方法で処理/分析する必要がある場合、それらを2番目のグループ(最大15のサブグループ)に割り当てることができます。これにより、サブグループ番号の数は多くなりますが、単一のグループに比べて、注文/ポジションをさまざまなグループに柔軟に組み合わせることができます。
ご覧のとおり、多くのことを計画していますが、ここでの問題は何でしょうか?問題は、MQL4のマジックナンバーが格納される整数値のサイズが4バイト(int)しかないという事実です。したがって、カスタムプログラムで設定できるマジックナンバーの値を犠牲にする必要があります。MQL5の場合、マジックナンバーのサイズはulongタイプによって設定されるので、より大きな値を設定し、より多くのデータを保存することができます。しかし、互換性の問題もあるので、何か(つまり、マジックナンバーのサイズ)を犠牲にする必要があります。これは2バイト(ushort)のみとなり、解放された2バイトは2つのグループののID(uchar)と未決注文 ID (uchar)に割り当てられます。
表には、マジックナンバー構造体とマジックナンバーのuint値でのMPデータの場所が表示されます。
------------------------------------------------------------------------- | bit |31 24|23 20|19 16|15 8|7 0| ------------------------------------------------------------------------- | byte | 3 | 2 | 1 | 0 | ------------------------------------------------------------------------- | type | uchar | uchar | ushort | ------------------------------------------------------------------------- | descr | request id | id2 | id1 | magic | -------------------------------------------------------------------------
表からは以下のことが分かります。
- マジックナンバー値は2バイトで、ushort型に対応するuint数の下位2バイト(ビット0〜15)に格納されます。プログラムではこの 0〜65535の値を持つマジックナンバータイプを使用する必要があります。
- 階層の次の1つは、2つのグループIDを格納するために使用され、ucharサイズを持つuint数のバイト2(ビット16〜23)です。
最初のグループIDはuchar数の下位4ビット(ビット16〜19)に格納され、2番目のグループIDはこのuchar番号の上位4ビット(ビット20〜23)に格納されます。
したがって、2つのグループを1バイトのuchar値に保存できます。各グループの数は、ゼロ(グループなし)から15(4ビットで格納できる最大値)まで変化します。 - 3番目と最後のuint数バイトには、保留中リクエストD(ビット24〜31)のuchar値を格納します。この値は、ゼロ(IDなし)から255まで変化する場合があります。これは、最大255個のアクティブな保留中リクエストを同時に持つことができることを意味します。
抽象注文クラスとイベントクラスを改善して、「マジックナンバー」プロパティ値にデータを保存しましょう。
しかしまずDefines.mqhで抽象注文オブジェクトの新しい整数プロパティを追加します。
//+------------------------------------------------------------------+ //| 注文、取引、ポジションの整数型プロパティ | //+------------------------------------------------------------------+ enum ENUM_ORDER_PROP_INTEGER { ORDER_PROP_TICKET = 0, // 注文チケット ORDER_PROP_MAGIC, // Real order magic number ORDER_PROP_TIME_OPEN, // Open time in milliseconds (MQL5 Deal time) ORDER_PROP_TIME_CLOSE, // Close time in milliseconds (MQL5 Execution or removal time - ORDER_TIME_DONE) ORDER_PROP_TIME_EXP, // 注文有効期限(未決注文用) ORDER_PROP_STATUS, // 注文ステータス(ENUM_ORDER_STATUS列挙体から) ORDER_PROP_TYPE, // Order/deal type ORDER_PROP_REASON, // 取引/注文/ポジション理由またはソース ORDER_PROP_STATE, // 注文ステータス(ENUM_ORDER_STATE列挙体から) ORDER_PROP_POSITION_ID, // ポジションID ORDER_PROP_POSITION_BY_ID, // 反対側のポジションID ORDER_PROP_DEAL_ORDER_TICKET, // Ticket of the order that triggered a deal ORDER_PROP_DEAL_ENTRY, // 取引の方向 – IN、OUT、IN/OUT ORDER_PROP_TIME_UPDATE, // Position change time in milliseconds ORDER_PROP_TICKET_FROM, // 親注文チケット ORDER_PROP_TICKET_TO, // 派生注文チケット ORDER_PROP_PROFIT_PT, // 利益(ポイント) ORDER_PROP_CLOSE_BY_SL, // ストップロスによる決済のフラグ ORDER_PROP_CLOSE_BY_TP, // テイクプロフィットによる決済のフラグ ORDER_PROP_MAGIC_ID, // Order's "magic number" ID ORDER_PROP_GROUP_ID1, // First order/position group ID ORDER_PROP_GROUP_ID2, // Second order/position group ID ORDER_PROP_PEND_REQ_ID, // Pending request ID ORDER_PROP_DIRECTION, // Type by direction (Buy, Sell) }; #define ORDER_PROP_INTEGER_TOTAL (24) // Total number of integer properties #define ORDER_PROP_INTEGER_SKIP (0) // Number of order properties not used in sorting //+------------------------------------------------------------------+
これらのプロパティは、前述のIDを保存するためのものです。IDはマジックナンバーの値に保存されます。3つの新しいプロパティを追加して1つを変更したため、適切なマクロ置換で注文の整数プロパティの総数を21から
24に変更します。
さらに、これらのプロパティによる並べ替えを、可能な順序と取引の並べ替え基準の列挙に追加します。
//+------------------------------------------------------------------+ //| Possible criteria of sorting orders and deals | //+------------------------------------------------------------------+ #define FIRST_ORD_DBL_PROP (ORDER_PROP_INTEGER_TOTAL-ORDER_PROP_INTEGER_SKIP) #define FIRST_ORD_STR_PROP (ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL-ORDER_PROP_INTEGER_SKIP) enum ENUM_SORT_ORDERS_MODE { //--- 整数型プロパティによって並び替える SORT_BY_ORDER_TICKET = 0, // Sort by an order ticket SORT_BY_ORDER_MAGIC, // Sort by an order magic number SORT_BY_ORDER_TIME_OPEN, // Sort by an order open time in milliseconds SORT_BY_ORDER_TIME_CLOSE, // Sort by an order close time in milliseconds SORT_BY_ORDER_TIME_EXP, // Sort by an order expiration date SORT_BY_ORDER_STATUS, // Sort by an order status (market order/pending order/deal/balance and credit operation) SORT_BY_ORDER_TYPE, // Sort by an order type SORT_BY_ORDER_REASON, // Sort by a deal/order/position reason/source SORT_BY_ORDER_STATE, // Sort by an order status SORT_BY_ORDER_POSITION_ID, // Sort by a position ID SORT_BY_ORDER_POSITION_BY_ID, // Sort by an opposite position ID SORT_BY_ORDER_DEAL_ORDER, // Sort by the order a deal is based on SORT_BY_ORDER_DEAL_ENTRY, // Sort by a deal direction – IN, OUT or IN/OUT SORT_BY_ORDER_TIME_UPDATE, // Sort by position change time in seconds SORT_BY_ORDER_TICKET_FROM, // Sort by a parent order ticket SORT_BY_ORDER_TICKET_TO, // Sort by a derived order ticket SORT_BY_ORDER_PROFIT_PT, // Sort by order profit in points SORT_BY_ORDER_CLOSE_BY_SL, // Sort by the flag of closing an order by StopLoss SORT_BY_ORDER_CLOSE_BY_TP, // Sort by the flag of closing an order by TakeProfit SORT_BY_ORDER_MAGIC_ID, // Sort by an order/position "magic number" ID SORT_BY_ORDER_GROUP_ID1, // Sort by the first order/position group ID SORT_BY_ORDER_GROUP_ID2, // Sort by the second order/position group ID SORT_BY_ORDER_PEND_REQ_ID, // Sort by a pending request ID SORT_BY_ORDER_DIRECTION, // Sort by direction (Buy, Sell) //--- 実数型プロパティによって並び替える SORT_BY_ORDER_PRICE_OPEN = FIRST_ORD_DBL_PROP, // Sort by open price SORT_BY_ORDER_PRICE_CLOSE, // Sort by close price SORT_BY_ORDER_SL, // Sort by StopLoss price SORT_BY_ORDER_TP, // Sort by TakeProfit price SORT_BY_ORDER_PROFIT, // Sort by profit SORT_BY_ORDER_COMMISSION, // Sort by commission SORT_BY_ORDER_SWAP, // Sort by swap SORT_BY_ORDER_VOLUME, // Sort by volume SORT_BY_ORDER_VOLUME_CURRENT, // Sort by unexecuted volume SORT_BY_ORDER_PROFIT_FULL, // Sort by profit+commission+swap SORT_BY_ORDER_PRICE_STOP_LIMIT, // Sort by Limit order when StopLimit order is activated //--- 文字列型プロパティによって並び替える SORT_BY_ORDER_SYMBOL = FIRST_ORD_STR_PROP, // Sort by symbol SORT_BY_ORDER_COMMENT, // Sort by comment SORT_BY_ORDER_COMMENT_EXT, // Sort by custom comment SORT_BY_ORDER_EXT_ID // Sort by order ID in an external trading system }; //+------------------------------------------------------------------+
操作ログに注文プロパティを正しく表示するには、新しいプロパティのインデックスと適切なメッセージをDatas.mqhファイルに追加します。
MSG_ORD_PROFIT_PT, // Profit in points MSG_ORD_MAGIC_ID, // Magic number ID MSG_ORD_GROUP_ID1, // First group ID MSG_ORD_GROUP_ID2, // Second group ID MSG_ORD_PEND_REQ_ID, // Pending request ID MSG_ORD_PRICE_OPEN, // Open price {"Прибыль в пунктах","Profit in points"}, {"Идентификатор магического номера","Magic number's identifier"}, {"Идентификатор первой группы","First group's identifier"}, {"Идентификатор второй группы","Second group's identifier"}, {"Идентификатор отложенного запроса","Pending request's identifier"}, {"Цена открытия","Price open"},
Order.mqh ファイルの抽象注文クラスに必要な変更を追加します。
クラスのprivateセクションで、注文プロパティ値から「マジックナンバー」を取得し、マジックナンバーのID(プログラム設定で設定されたマジックナンバー)、グループ1および2
IDおよび保留中リクエストID を追加します。
//+------------------------------------------------------------------+ //| 抽象注文クラス | //+------------------------------------------------------------------+ class COrder : public CObject { private: ulong m_ticket; // 選択された注文/取引チケット(MQL5) long m_long_prop[ORDER_PROP_INTEGER_TOTAL]; // 整数型プロパティ double m_double_prop[ORDER_PROP_DOUBLE_TOTAL]; // 実数型プロパティ string m_string_prop[ORDER_PROP_STRING_TOTAL]; // 文字列プロパティ //--- Return the index of the array the order's (1) double and (2) string properties are located at int IndexProp(ENUM_ORDER_PROP_DOUBLE property) const { return(int)property-ORDER_PROP_INTEGER_TOTAL; } int IndexProp(ENUM_ORDER_PROP_STRING property) const { return(int)property-ORDER_PROP_INTEGER_TOTAL-ORDER_PROP_DOUBLE_TOTAL; } //--- Data location in the magic number int value //----------------------------------------------------------- // bit 32|31 24|23 16|15 8|7 0| //----------------------------------------------------------- // byte | 3 | 2 | 1 | 0 | //----------------------------------------------------------- // data | uchar | uchar | ushort | //----------------------------------------------------------- // descr |pend req id| id2 | id1 | magic | //----------------------------------------------------------- //--- Return (1) the specified magic number, the ID of (2) the first group, (3) second group, (4) pending request from the magic number value ushort GetMagicID(void) const { return ushort(this.Magic() & 0xFFFF); } uchar GetGroupID1(void) const { return uchar(this.Magic()>>16) & 0x0F; } uchar GetGroupID2(void) const { return uchar((this.Magic()>>16) & 0xF0)>>4; } uchar GetPendReqID(void) const { return uchar(this.Magic()>>24) & 0xFF; } public: //--- デフォルトのコンストラクタ COrder(void){;}
注文マジックナンバーのuint値からushortマジックナンバー値を返すには、uint数の下位2バイトのみを変更せずに、マスク(0xFFFF)を適用します。上位2バイトはゼロで埋められます。uintをushortに変換すると、上位2バイトは自動的に破棄されます。
最初のグループIDを取得するには、最初にマジックナンバープロパティを16ビット右にシフトする必要があります(グループIDのuchar値がuint数のバイト0になるように)。次に、取得した数に0x0Fマスクが適用されます。マスクは、シフト中に取得した値の下位4ビットのみを残します。uintをucharに変換すると、数値の上位バイトがすべて破棄され、下位バイトにはマスクが適用されます。したがって、0〜15の4ビット値を取得します。
2番目のグループIDの取得は、必要な値がuchar値の上位4ビットにあるため異なります。したがって、まず最初のグループIDを取得するときと同じことを行います。つまり、マジックナンバープロパティの値を16ビット右にシフトし(グループIDのuchar値がuint数のバイト0になるように)、 取得した数に0xF0のマスクを適用します。マスクは、シフト中に取得した値の上位4ビットのみを残します。次に、取得した値をさらに4ビット右シフトし、ID番号を格納する上位ビットを0および15に修正します。
保留中リクエストID を取得するには、uint数の最も古いバイトを右に24ビットシフトして、この1バイトのuchar値がuint番号ゼロバイトになり、0xFFマスクを適用するようにします(実際には、uint数をuchar型に変換するときに下位バイトが1つ残るため、これは必要ありません)。
メソッドのブロックに4つの新しいプロパティを返すメソッドを追加して、抽象注文オブジェクトのプロパティに簡単にアクセスできるようにします。
//+------------------------------------------------------------------+ //| Methods of a simplified access to the order object properties | //+------------------------------------------------------------------+ //--- Return (1) ticket, (2) parent order ticket, (3) derived order ticket, (4) magic number, (5) order reason, //--- (6) position ID, (7) opposite position ID, (8) first group ID, (9) second group ID, //--- (10) pending request ID, (11) magic number ID, (12) type, (13) flag of closing by StopLoss, //--- (14) flag of closing by TakeProfit (15) open time, (16) close time, //--- (17) order expiration date, (18) state, (19) status, (20) type by direction long Ticket(void) const { return this.GetProperty(ORDER_PROP_TICKET); } long TicketFrom(void) const { return this.GetProperty(ORDER_PROP_TICKET_FROM); } long TicketTo(void) const { return this.GetProperty(ORDER_PROP_TICKET_TO); } long Magic(void) const { return this.GetProperty(ORDER_PROP_MAGIC); } long Reason(void) const { return this.GetProperty(ORDER_PROP_REASON); } long PositionID(void) const { return this.GetProperty(ORDER_PROP_POSITION_ID); } long PositionByID(void) const { return this.GetProperty(ORDER_PROP_POSITION_BY_ID); } long MagicID(void) const { return this.GetProperty(ORDER_PROP_MAGIC_ID); } long GroupID1(void) const { return this.GetProperty(ORDER_PROP_GROUP_ID1); } long GroupID2(void) const { return this.GetProperty(ORDER_PROP_GROUP_ID2); } long PendReqID(void) const { return this.GetProperty(ORDER_PROP_PEND_REQ_ID); } long TypeOrder(void) const { return this.GetProperty(ORDER_PROP_TYPE); } bool IsCloseByStopLoss(void) const { return (bool)this.GetProperty(ORDER_PROP_CLOSE_BY_SL); } bool IsCloseByTakeProfit(void) const { return (bool)this.GetProperty(ORDER_PROP_CLOSE_BY_TP); } long TimeOpen(void) const { return this.GetProperty(ORDER_PROP_TIME_OPEN); } long TimeClose(void) const { return this.GetProperty(ORDER_PROP_TIME_CLOSE); } datetime TimeExpiration(void) const { return (datetime)this.GetProperty(ORDER_PROP_TIME_EXP); } ENUM_ORDER_STATE State(void) const { return (ENUM_ORDER_STATE)this.GetProperty(ORDER_PROP_STATE); } ENUM_ORDER_STATUS Status(void) const { return (ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS); } ENUM_ORDER_TYPE TypeByDirection(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(ORDER_PROP_DIRECTION); }
また、抽象注文の新しいプロパティを設定する3つのメソッドを追加します。
//--- 完全な注文利益を取得する double ProfitFull(void) const { return this.Profit()+this.Comission()+this.Swap(); } //--- ポイント単位の注文利益を取得する int ProfitInPoints(void) const; //--- Set (1) the first group ID, (2) the second group ID, (3) the pending request ID, (4) custom comment void SetGroupID1(const long group_id) { this.SetProperty(ORDER_PROP_GROUP_ID1,group_id); } void SetGroupID2(const long group_id) { this.SetProperty(ORDER_PROP_GROUP_ID2,group_id); } void SetPendReqID(const long req_id) { this.SetProperty(ORDER_PROP_PEND_REQ_ID,req_id); } void SetCommentExt(const string comment_ext) { this.SetProperty(ORDER_PROP_COMMENT_EXT,comment_ext); } //+------------------------------------------------------------------+
閉じたクラスコンストラクタで、上記のメソッドを使用してID値で新しいプロパティフィールドに入力します。
//+------------------------------------------------------------------+ //| Closedパラメトリックコンストラクタ | //+------------------------------------------------------------------+ COrder::COrder(ENUM_ORDER_STATUS order_status,const ulong ticket) { //--- 整数型プロパティを保存する this.m_ticket=ticket; this.m_long_prop[ORDER_PROP_STATUS] = order_status; this.m_long_prop[ORDER_PROP_MAGIC] = this.OrderMagicNumber(); this.m_long_prop[ORDER_PROP_TICKET] = this.OrderTicket(); this.m_long_prop[ORDER_PROP_TIME_EXP] = this.OrderExpiration(); this.m_long_prop[ORDER_PROP_TYPE] = this.OrderType(); this.m_long_prop[ORDER_PROP_STATE] = this.OrderState(); this.m_long_prop[ORDER_PROP_DIRECTION] = this.OrderTypeByDirection(); this.m_long_prop[ORDER_PROP_POSITION_ID] = this.OrderPositionID(); this.m_long_prop[ORDER_PROP_REASON] = this.OrderReason(); this.m_long_prop[ORDER_PROP_DEAL_ORDER_TICKET] = this.DealOrderTicket(); this.m_long_prop[ORDER_PROP_DEAL_ENTRY] = this.DealEntry(); this.m_long_prop[ORDER_PROP_POSITION_BY_ID] = this.OrderPositionByID(); this.m_long_prop[ORDER_PROP_TIME_OPEN] = this.OrderOpenTimeMSC(); this.m_long_prop[ORDER_PROP_TIME_CLOSE] = this.OrderCloseTimeMSC(); this.m_long_prop[ORDER_PROP_TIME_UPDATE] = this.PositionTimeUpdateMSC(); //--- 実数型プロパティを保存する this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_OPEN)] = this.OrderOpenPrice(); this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_CLOSE)] = this.OrderClosePrice(); this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT)] = this.OrderProfit(); this.m_double_prop[this.IndexProp(ORDER_PROP_COMMISSION)] = this.OrderCommission(); this.m_double_prop[this.IndexProp(ORDER_PROP_SWAP)] = this.OrderSwap(); this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME)] = this.OrderVolume(); this.m_double_prop[this.IndexProp(ORDER_PROP_SL)] = this.OrderStopLoss(); this.m_double_prop[this.IndexProp(ORDER_PROP_TP)] = this.OrderTakeProfit(); this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME_CURRENT)] = this.OrderVolumeCurrent(); this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_STOP_LIMIT)] = this.OrderPriceStopLimit(); //--- 文字列プロパティを保存する this.m_string_prop[this.IndexProp(ORDER_PROP_SYMBOL)] = this.OrderSymbol(); this.m_string_prop[this.IndexProp(ORDER_PROP_COMMENT)] = this.OrderComment(); this.m_string_prop[this.IndexProp(ORDER_PROP_EXT_ID)] = this.OrderExternalID(); //--- 追加の整数型プロパティを保存する this.m_long_prop[ORDER_PROP_PROFIT_PT] = this.ProfitInPoints(); this.m_long_prop[ORDER_PROP_TICKET_FROM] = this.OrderTicketFrom(); this.m_long_prop[ORDER_PROP_TICKET_TO] = this.OrderTicketTo(); this.m_long_prop[ORDER_PROP_CLOSE_BY_SL] = this.OrderCloseByStopLoss(); this.m_long_prop[ORDER_PROP_CLOSE_BY_TP] = this.OrderCloseByTakeProfit(); this.m_long_prop[ORDER_PROP_MAGIC_ID] = this.GetMagicID(); this.m_long_prop[ORDER_PROP_GROUP_ID1] = this.GetGroupID1(); this.m_long_prop[ORDER_PROP_GROUP_ID2] = this.GetGroupID2(); this.m_long_prop[ORDER_PROP_PEND_REQ_ID] = this.GetPendReqID(); //--- 追加の実数型プロパティを保存する this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT_FULL)] = this.ProfitFull(); //--- Save additional string properties this.m_string_prop[this.IndexProp(ORDER_PROP_COMMENT_EXT)] = ""; } //+------------------------------------------------------------------+
整数プロパティの説明を返すメソッドに抽象注文のすべての新しいプロパティの説明の表示を追加します。
//+------------------------------------------------------------------+ //| 注文の整数型プロパティの説明を返す | //+------------------------------------------------------------------+ string COrder::GetPropertyDescription(ENUM_ORDER_PROP_INTEGER property) { return ( //--- 一般的なプロパティ property==ORDER_PROP_MAGIC ? CMessage::Text(MSG_ORD_MAGIC)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==ORDER_PROP_TICKET ? CMessage::Text(MSG_ORD_TICKET)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : " #"+(string)this.GetProperty(property) ) : property==ORDER_PROP_TICKET_FROM ? CMessage::Text(MSG_ORD_TICKET_FROM)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : " #"+(string)this.GetProperty(property) ) : property==ORDER_PROP_TICKET_TO ? CMessage::Text(MSG_ORD_TICKET_TO)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : " #"+(string)this.GetProperty(property) ) : property==ORDER_PROP_TIME_EXP ? CMessage::Text(MSG_ORD_TIME_EXP)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : (this.GetProperty(property)==0 ? CMessage::Text(MSG_LIB_PROP_NOT_SET)+": "+CMessage::Text(MSG_LIB_PROP_NOT_SET) : ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS)) ) : property==ORDER_PROP_TYPE ? CMessage::Text(MSG_ORD_TYPE)+": "+this.TypeDescription() : property==ORDER_PROP_DIRECTION ? CMessage::Text(MSG_ORD_TYPE_BY_DIRECTION)+": "+this.DirectionDescription() : property==ORDER_PROP_REASON ? CMessage::Text(MSG_ORD_REASON)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetReasonDescription(this.GetProperty(property)) ) : property==ORDER_PROP_POSITION_ID ? CMessage::Text(MSG_ORD_POSITION_ID)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": #"+(string)this.GetProperty(property) ) : property==ORDER_PROP_DEAL_ORDER_TICKET ? CMessage::Text(MSG_ORD_DEAL_ORDER_TICKET)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": #"+(string)this.GetProperty(property) ) : property==ORDER_PROP_DEAL_ENTRY ? CMessage::Text(MSG_ORD_DEAL_ENTRY)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetEntryDescription(this.GetProperty(property)) ) : property==ORDER_PROP_POSITION_BY_ID ? CMessage::Text(MSG_ORD_POSITION_BY_ID)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==ORDER_PROP_TIME_OPEN ? CMessage::Text(MSG_ORD_TIME_OPEN)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")" ) : property==ORDER_PROP_TIME_CLOSE ? CMessage::Text(MSG_ORD_TIME_CLOSE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")" ) : property==ORDER_PROP_TIME_UPDATE ? CMessage::Text(MSG_ORD_TIME_UPDATE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(this.GetProperty(property)!=0 ?TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")" : "0") ) : property==ORDER_PROP_STATE ? CMessage::Text(MSG_ORD_STATE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": \""+this.StateDescription()+"\"" ) : //--- 追加のプロパティ property==ORDER_PROP_STATUS ? CMessage::Text(MSG_ORD_STATUS)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": \""+this.StatusDescription()+"\"" ) : property==ORDER_PROP_PROFIT_PT ? ( this.Status()==ORDER_STATUS_MARKET_PENDING ? CMessage::Text(MSG_ORD_DISTANCE_PT) : CMessage::Text(MSG_ORD_PROFIT_PT) )+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==ORDER_PROP_CLOSE_BY_SL ? CMessage::Text(MSG_LIB_PROP_CLOSE_BY_SL)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(this.GetProperty(property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO)) ) : property==ORDER_PROP_CLOSE_BY_TP ? CMessage::Text(MSG_LIB_PROP_CLOSE_BY_TP)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(this.GetProperty(property) ? CMessage::Text(MSG_LIB_TEXT_YES) : CMessage::Text(MSG_LIB_TEXT_NO)) ) : property==ORDER_PROP_MAGIC_ID ? CMessage::Text(MSG_ORD_MAGIC_ID)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==ORDER_PROP_GROUP_ID1 ? CMessage::Text(MSG_ORD_GROUP_ID1)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==ORDER_PROP_GROUP_ID2 ? CMessage::Text(MSG_ORD_GROUP_ID2)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==ORDER_PROP_PEND_REQ_ID ? CMessage::Text(MSG_ORD_PEND_REQ_ID)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : "" ); } //+------------------------------------------------------------------+
抽象注文クラスの準備ができました。次に、イベントクラスを変更する必要があります。
Event.mqhファイルの抽象イベントクラスで、新しいIDを返すメソッドクラスのprotectedセクションに追加します。
protected: ENUM_TRADE_EVENT m_trade_event; // Trading event bool m_is_hedge; // Hedge account flag long m_chart_id; // Control program chart ID int m_digits; // Symbol's Digits() int m_digits_acc; // Number of decimal places for the account currency long m_long_prop[EVENT_PROP_INTEGER_TOTAL]; // Event integer properties double m_double_prop[EVENT_PROP_DOUBLE_TOTAL]; // Event real properties string m_string_prop[EVENT_PROP_STRING_TOTAL]; // Event string properties //--- return the flag presence in the trading event bool IsPresentEventFlag(const int event_code) const { return (this.m_event_code & event_code)==event_code; } //--- Return (1) the specified magic number, the ID of (2) the first group, (3) second group, (4) pending request from the magic number value ushort GetMagicID(void) const { return ushort(this.Magic() & 0xFFFF); } uchar GetGroupID1(void) const { return uchar(this.Magic()>>16) & 0x0F; } uchar GetGroupID2(void) const { return uchar((this.Magic()>>16) & 0xF0)>>4; } uchar GetPendReqID(void) const { return uchar(this.Magic()>>24) & 0xFF; } //--- 保護されたパラメトリックコンストラクタ CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket);
これらのメソッドは、上記の抽象注文クラスのメソッドに似ています。
抽象イベントクラスから派生した5つのクラス( EventModify.mqh、EventOrderPlaced.mqh、 EventOrderRemoved.mqh、EventPositionClose.mqh、EventPositionOpen.mqhファイル)、つまりイベントの簡単な説明のメソッドで、単一の文字列ではなく
//+------------------------------------------------------------------+ //| Create and return a short event message | //+------------------------------------------------------------------+ string CEventModify::EventsMessage(void) { //--- (1) header, (2) magic number string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n"; string magic=(this.Magic()!=0 ? ", "+CMessage::Text(MSG_ORD_MAGIC)+" "+(string)this.Magic() : ""); string text="";
各クラスに以下の文字列を追加します。
//+------------------------------------------------------------------+ //| Create and return a short event message | //+------------------------------------------------------------------+ string CEventModify::EventsMessage(void) { //--- (1) header, (2) magic number string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n"; string magic_id=((this.GetPendReqID()>0 || this.GetGroupID1()>0 || this.GetGroupID2()>0) ? " ("+(string)this.GetMagicID()+")" : ""); string group_id1=(this.GetGroupID1()>0 ? ", G1: "+(string)this.GetGroupID1() : ""); string group_id2=(this.GetGroupID2()>0 ? ", G2: "+(string)this.GetGroupID2() : ""); string magic=(this.Magic()!=0 ? ", "+CMessage::Text(MSG_ORD_MAGIC)+" "+(string)this.Magic()+magic_id+group_id1+group_id2 : ""); string text="";
複数のデータを単一のマジックナンバーの値に保存する場合、グループおよび保留中リクエストIDが上位2バイトに格納される一方マジックナンバーは下位2バイトにのみ保存されるため、操作に番号を表示すると、プログラム設定で設定された値とはまったく異なる値が表示されます 。したがって、ID(またはそれらの少なくとも1つ)がマジックナンバーの値に追加されると、操作ログに表示される値に各IDの説明が追加されます。
マジックナンバー値にデータを保存するために必要なすべての変更を実装したので、次に、保留中リクエストクラスと、ポジションを開くときにエラーが発生した場合に保留中リクエストを生成する最初の実装に移りましょう。
保留中リクエストクラス、リクエストの最初の実装
Trading.mqh取引クラスファイルでCTrading取引クラスの本体の前に、保留中リクエストオブジェクトを記述する新しいクラスを追加します。
//+------------------------------------------------------------------+ //| Pending request object class | //+------------------------------------------------------------------+ class CPendingReq : public CObject { private: MqlTradeRequest m_request; // Trade request structure uchar m_id; // Trading request ID int m_retcode; // Result a request is based on double m_price_create; // Price at the moment of a request generation ulong m_time_create; // Request generation time ulong m_time_activate; // Next attempt activation time ulong m_waiting_msc; // Waiting time between requests uchar m_current_attempt; // Current attempt index uchar m_total_attempts; // Number of attempts //--- Copy trading request data void CopyRequest(const MqlTradeRequest &request) { this.m_request=request; } //--- Compare CPendingReq objects by IDs virtual int Compare(const CObject *node,const int mode=0) const; public: //--- Return (1) the request structure, (2) the price at the moemnt of the request generation, //--- (3) request generation time, (4) current attempt time, //--- (5) waiting time between requests, (6) current attempt index, //--- (7) number of attempts, (8) request ID MqlTradeRequest MqlRequest(void) const { return this.m_request; } double PriceCreate(void) const { return this.m_price_create; } ulong TimeCreate(void) const { return this.m_time_create; } ulong TimeActivate(void) const { return this.m_time_activate; } ulong WaitingMSC(void) const { return this.m_waiting_msc; } uchar CurrentAttempt(void) const { return this.m_current_attempt; } uchar TotalAttempts(void) const { return this.m_total_attempts; } uchar ID(void) const { return this.m_id; } //--- Set (1) the price when creating a request, (2) request creation time, //--- (3) current attempt time, (4) waiting time between requests, //--- (5) current attempt index, (6) number of attempts, (7) request ID void SetPriceCreate(const double price) { this.m_price_create=price; } void SetTimeCreate(const ulong time) { this.m_time_create=time; } void SetTimeActivate(const ulong time) { this.m_time_activate=time; } void SetWaitingMSC(const ulong miliseconds) { this.m_waiting_msc=miliseconds; } void SetCurrentAttempt(const uchar number) { this.m_current_attempt=number; } void SetTotalAttempts(const uchar number) { this.m_total_attempts=number; } void SetID(const uchar id) { this.m_id=id; } //--- Constructors CPendingReq(void){;} CPendingReq(const uchar id,const double price,const ulong time,const MqlTradeRequest &request,const int retcode); }; //+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CPendingReq::CPendingReq(const uchar id,const double price,const ulong time,const MqlTradeRequest &request,const int retcode) : m_price_create(price), m_time_create(time), m_id(id), m_retcode(retcode) { this.CopyRequest(request); } //+------------------------------------------------------------------+ //| Compare CPendingReq objects by IDs | //+------------------------------------------------------------------+ int CPendingReq::Compare(const CObject *node,const int mode=0) const { const CPendingReq *compared_req=node; return(this.ID()>compared_req.ID() ? 1 : this.ID()<compared_req.ID() ? -1 : 0); return 0; } //+------------------------------------------------------------------+
このクラスはかなりわかりやすいと思います。コメントアウトされた文字列を記述する必要はありません。メソッド名とクラスメンバ変数によってすべてが明確になります。ただし、このオブジェクトと関連メソッド、および取引クラス機能がどのように機能するかは説明する必要があると思います。
サーバからエラーを受け取った場合、別のサーバリクエストを作成し、取引メソッドを終了します。新しく作成されたリクエストの待機時間が終了すると、リクエストはサーバに再度送信されます。エラーが再度受信された場合、保留中リクエストを作成する必要があるようですが、保留中リクエストはサーバエラを初めて受信したときに既に生成されています。したがって、保留中リクエストIDの存在は、受信した取引リクエストのマジックナンバーで確認されます。リクエストが存在する場合、リクエストはすでに作成されており、現在別の試行がサーバに送信されています。つまり、新しいリクエストは不要です。取引リクエストのマジックナンバーにIDがない場合、最初に最低のIDを特徴とする新しい保留中リクエストが生成され、プログラムが他のアクションを実行できるように取引メソッドが終了されます。
取引リクエストのリストは、常に取引クラスのタイマーで見ることができます。次のリクエストの待機時間が切れると、タイマーから適切な取引メソッドが呼び出されます。保留中リクエストのリストから各リクエストを確認するとき、適切なポジションの存在または成行注文とポジションのリスト内の注文が確認されます。現在のIDを持つ注文またはポジションが存在する場合、未決注文がその機能を果たしているため、リクエストリストから削除する必要があります。
実装を始めましょう。
クラスはすでにTrading.mqhファイルに追加しています。
次に、そのprivateセクションで未使用の保留中リクエストIDの最初の最小値を検索して返すメソッドを宣言します。
//--- Look for the first free pending request ID int GetFreeID(void); public: //--- コンストラクタ CTrading();
クラス本体の外側で実装しましょう。
//+------------------------------------------------------------------+ //| Look for the first free pending request ID | //+------------------------------------------------------------------+ int CTrading::GetFreeID(void) { int id=WRONG_VALUE; CPendingReq *element=new CPendingReq(); if(element==NULL) return 0; for(int i=1;i<256;i++) { element.SetID((uchar)i); this.m_list_request.Sort(); if(this.m_list_request.Search(element)==WRONG_VALUE) { id=i; break; } } delete element; return id; } //+------------------------------------------------------------------+
合計255個の独立した保留中リクエストが存在できます。各リクエストには、独自のプロパティ、取引試行間の独自の待機時間、および結果として、保留中リクエストオブジェクトの有効期間があります。これに関連して、たとえば255がリクエストIDによってすでに使用されているが、0、1またはその他の255未満のIDがすでに解放されていて新しい取引リクエストIDに使用できる場合があります 。このメソッドは、リリースされた最小のID番号を見つけるために使用されます。
保留中リクエストの一時クラスオブジェクトと値が-1のID
がまず作成されます。この値は、255個すべてが使用されていて空きIDがないことを通知します。値0は一時オブジェクト作成エラーを意味します。さらに、1から255までの可能なIDインデックス値によるループで、保留中リクエストのリスト内の現在のループインデックスに等しいIDを持つリクエストオブジェクトの存在を確認します。これを行うには、まずIDを一時オブジェクトのループインデックス番号に設定してから、リストのソート済みリストフラグを設定して、単にリスト内で同じIDを持つリクエストオブジェクトを探します。つまり、ループインデックスがIDとして設定されている一時オブジェクトに等しいリクエストオブジェクトを探します。そのようなオブジェクトがリストに見つからない場合、メソッドから返された値のループインデックスを設定し、ループを中断します。
ループの完了後、一時リクエストオブジェクトを削除してID値を返します。これは、-1または1〜255のいずれかです。
クラスのpublicセクションで、保留中リクエストを作成するためのメソッドを宣言して注文/ポジションの「マジックナンバー」ID値を設定/返すメソッドを追加します。
//--- Create a pending request bool CreatePendingRequest(const uchar id,const uchar attempts,const ulong wait,const MqlTradeRequest &request,const int retcode,CSymbol *symbol_obj); //--- Data location in the magic number int value //----------------------------------------------------------- // bit 32|31 24|23 16|15 8|7 0| //----------------------------------------------------------- // byte | 3 | 2 | 1 | 0 | //----------------------------------------------------------- // data | uchar | uchar | ushort | //----------------------------------------------------------- // descr |pend req id| id2 | id1 | magic | //----------------------------------------------------------- //--- Set the ID of the (1) first group, (2) second group, (3) pending request to the magic number value void SetGroupID1(const uchar group,uint &magic) { magic &=0xFFF0FFFF; magic |= uint(ConvToXX(group,0)<<16); } void SetGroupID2(const uchar group,uint &magic) { magic &=0xFF0FFFFF; magic |= uint(ConvToXX(group,1)<<16); } void SetPendReqID(const uchar id,uint &magic) { magic &=0x00FFFFFF; magic |= (uint)id<<24; } //--- Convert the value of 0 - 15 into the necessary uchar number bits (0 - lower, 1 - upper ones) uchar ConvToXX(const uchar number,const uchar index) const { return((number>15 ? 15 : number)<<(4*(index>1 ? 1 : index)));} //--- Return (1) the specified magic number, the ID of (2) the first group, (3) second group, (4) pending request from the magic number value ushort GetMagicID(const uint magic) const { return ushort(magic & 0xFFFF); } uchar GetGroupID1(const uint magic) const { return uchar(magic>>16) & 0x0F; } uchar GetGroupID2(const uint magic) const { return uchar((magic>>16) & 0xF0)>>4; } uchar GetPendReqID(const uint magic) const { return uchar(magic>>24) & 0xFF; }
値を返す値についてはすでに考察しました。ここでは同一なものが使用されます。異なるIDを設定するメソッドを考察しましょう。
2つのグループIDは1バイトに格納され、数値のID値は0から15(4バイト)の値しか取ることができないため、2番目のグループIDを設定するには、その値を4ビット左にシフトする必要があります。これにより、1バイト数の上位4ビットに格納できます。これはConvToXX()メソッドでおこなわれます。グループインデックス(0または1)に応じて、渡された数値(0-15)を左に4ビットシフト(2番目のグループ、インデックス1)する、またはシフトしません(最初のグループ、インデックス0)。
最初のグループID値を設定するには、まずID値を保存するバイトの下位4ビットをリセットする必要があります。これは、マスクをマジック値に適用することで実行できます。F値は、マスクの各ハーフバイト(4ビット)に使用されます。
言い換えると、変更せずに残す値には10進数の15(16進値のF)がビットに適用され、削除する値にはゼロを適用します。したがって、マジックナンバーの値に適用されるマスクは、0x FFF0FFFFとなります。
ここで
- FFFF — マジックナンバーID(EA設定で設定されたマジックナンバー)を残します。
- F0 — グループIDを保存するバイトの下位4ビットを削除(0)します。上位ビットは(F)に設定されたままで、2番目のグループIDを保存します。
- FF — 保留中リクエストIDの値を残します。
次に、ConvToXX()メソッドから取得したグループ番号を0のインデックスで配置し、16ビットをグループIDの保存用に準備されたバイトに左にシフトします。取得した番号が、グループIDは次の場所に保存されるのに必要なバイトになります。
2番目のグループID値を設定するには、ID値を保存するバイトの上位4ビットをリセットします。これを行うには、マジックナンバーに0xFF0FFFFFマスクを適用します。
ここで
- FFFF — マジックナンバーID(EA設定で設定されたマジックナンバー)を残します。
- 0F — グループIDを格納するバイトの上位4ビットを削除(0)します。下位ビットは(F)に設定されたままで、最初のグループIDを保存します。
- FF — 保留中リクエストIDの値を残します。
次に、ConvToXX()メソッドから取得したグループ番号を1のインデックスで配置し、16ビットをグループIDの保存用に準備されたバイトに左にシフトします。取得した番号が、グループIDは次の場所に保存されるのに必要なバイトになります。
2番目の保留中リクエストID値を設定するには、ID値を保存するバイトをリセットします。これを行うには、マジックナンバーに0x 00FFFFFFマスクを適用します。
ここで
- FFFF — マジックナンバーID(EA設定で設定されたマジックナンバー)を残します。
- FF — グループID値
- 00 — 保留中リクエストIDの値を削除します。
次に、保留中リクエストIDを保存するために準備されたバイトに左に24ビットシフトされたID uchar値を配置し、取得した数が保留中リクエストIDが保存される必要なバイトにそれを作成します。
クラス本体の外に保留中リクエストオブジェクトを作成するメソッドを実装します。
//+------------------------------------------------------------------+ //| Create a pending request | //+------------------------------------------------------------------+ bool CTrading::CreatePendingRequest(const uchar id,const uchar attempts,const ulong wait,const MqlTradeRequest &request,const int retcode,CSymbol *symbol_obj) { //--- Create a new pending request object CPendingReq *req_obj=new CPendingReq(id,symbol_obj.Bid(),symbol_obj.Time(),request,retcode); if(req_obj==NULL) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_FAILING_CREATE_PENDING_REQ)); return false; } //--- If failed to add the request to the list, display the appropriate message, //--- remove the created object and return 'false' if(!this.m_list_request.Add(req_obj)) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_FAILING_CREATE_PENDING_REQ)); delete req_obj; return false; } //--- Filled in the fields of a successfully created object by the values passed to the method req_obj.SetTimeActivate(symbol_obj.Time()+wait); req_obj.SetWaitingMSC(wait); req_obj.SetCurrentAttempt(0); req_obj.SetTotalAttempts(attempts); return true; } //+------------------------------------------------------------------+
メソッドは簡単です。新しいリクエストオブジェクトが作成され、保留中リクエストのリストに追加されます。メソッドに渡された値はオブジェクトフィールドに追加され(リクエストのアクティブ化時間はリクエスト作成時間+待機時間として計算)、trueが返されます。その他の場合はfalseが返されます。
前の記事で開発した取引クラスタイマーに、保留中リクエストを処理するロジックを追加します。
//+------------------------------------------------------------------+ //| Timer | //+------------------------------------------------------------------+ void CTrading::OnTimer(void) { //--- In a loop by the list of pending requests int total=this.m_list_request.Total(); for(int i=total-1;i>WRONG_VALUE;i--) { //--- receive the next request object CPendingReq *req_obj=this.m_list_request.At(i); if(req_obj==NULL) continue; //--- if the current attempt exceeds the defined number of trading attempts, //--- remove the current request object and move on to the next one if(req_obj.CurrentAttempt()>req_obj.TotalAttempts() || req_obj.CurrentAttempt()>=UCHAR_MAX) { this.m_list_request.Delete(i); continue; } //--- get the request structure and the symbol object a trading operation should be performed for MqlTradeRequest request=req_obj.MqlRequest(); CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(request.symbol); if(symbol_obj==NULL || !symbol_obj.RefreshRates()) continue; //--- Set the request activation time in the request object req_obj.SetTimeActivate(req_obj.TimeCreate()+req_obj.WaitingMSC()*(req_obj.CurrentAttempt()+1)); //--- If the current time is less than the request activation time, //--- this is not the request time - move on to the next request in the list if(symbol_obj.Time()<req_obj.TimeActivate()) continue; //--- Set the attempt number in the request object req_obj.SetCurrentAttempt(uchar(req_obj.CurrentAttempt()+1)); //--- Get the pending request ID uchar id=this.GetPendReqID((uint)request.magic); //--- Get the list of orders/positions containing the order/position with the pending request ID CArrayObj *list=this.m_market.GetList(ORDER_PROP_PEND_REQ_ID,id,EQUAL); if(::CheckPointer(list)==POINTER_INVALID) continue; //--- Depending on the type of action performed in the trading request switch(request.action) { //--- Open a position case TRADE_ACTION_DEAL : //--- if there is no position/order with the obtained pending request ID (the list is empty), send a trading request if(list.Total()==0) { this.OpenPosition((ENUM_POSITION_TYPE)request.type,request.volume,request.symbol,request.magic,request.sl,request.tp,request.comment,request.deviation); } //--- if a position/order with the obtained pending request ID is already present (the list is empty), the request has been handled and should be removed else this.m_list_request.Delete(i); break; //--- default: break; } } } //+------------------------------------------------------------------+
操作ロジックは、コードのコメントで詳細に説明されており、説明は不要です。注目に値する唯一のことは、次の取引リクエストのアクティベーション時間の計算です。時間は、「リクエストオブジェクト作成時間」+ミリ秒単位の待機時間*次の試行インデックスとして計算されます。したがって、リクエスト時間は最初のリクエスト作成時間と試行インデックスにバインドされます。試行回数が多いほど、オブジェクト生成からアクティブ化までの時間が長くなります。時間は個別に増加します。10秒間待機すると、最初の試行は10秒で、2番目の試行は20秒で、3番目の試行は30秒で行われます。したがって、取引の試行間の間隔は常に、それらの間の指定された待機時間以上になります。
エラーの処理方法を返すメソッドでは、取引サーバ接続不在エラーのコードを「待機」処理タイプを返すブロックにシフトします。以前は、コードは「保留中の取引リクエストを作成する」として処理されていました。
//+------------------------------------------------------------------+ //| Return the error handling method | //+------------------------------------------------------------------+ ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::ResultProccessingMethod(const uint result_code) { switch(result_code) { #ifdef __MQL4__ //--- Malfunctional trade operation case 9 : //--- Account disabled case 64 : //--- Invalid account number case 65 : return ERROR_CODE_PROCESSING_METHOD_DISABLE; //--- No error but result is unknown case 1 : //--- General error case 2 : //--- Old client terminal version case 5 : //--- Not enough rights case 7 : //--- Market closed case 132 : //--- Trading disabled case 133 : //--- Order is locked and being processed case 139 : //--- Buy only case 140 : //--- The number of open and pending orders has reached the limit set by the broker case 148 : //--- Attempt to open an opposite order if hedging is disabled case 149 : //--- Attempt to close a position on a symbol contradicts the FIFO rule case 150 : return ERROR_CODE_PROCESSING_METHOD_EXIT; //--- Invalid trading request parameters case 3 : //--- Invalid price case 129 : //--- Invalid stop levels case 130 : //--- Invalid volume case 131 : //--- Not enough money to perform the operation case 134 : //--- Expirations are denied by broker case 147 : return ERROR_CODE_PROCESSING_METHOD_CORRECT; //--- Trade server is busy case 4 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- No connection to the trade server case 6 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- Too frequent requests case 8 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- No price case 136 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- Broker is busy case 137 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- Too many requests case 141 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- Modification denied because the order is too close to market case 145 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- Trade context is busy case 146 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)1000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- Trade timeout case 128 : //--- Price has changed case 135 : //--- New prices case 138 : return ERROR_CODE_PROCESSING_METHOD_REFRESH; //--- MQL5 #else //--- Auto trading disabled by the server case 10026 : return ERROR_CODE_PROCESSING_METHOD_DISABLE; //--- Request canceled by a trader case 10007 : //--- Request expired case 10012 : //--- Trading disabled case 10017 : //--- Market closed case 10018 : //--- Order status changed case 10023 : //--- Request unchanged case 10025 : //--- Request blocked for handling case 10028 : //--- Transaction is allowed for live accounts only case 10032 : //--- The maximum number of pending orders is reached case 10033 : //--- Reached the maximum order and position volume for this symbol case 10034 : //--- Invalid or prohibited order type case 10035 : //--- Position with the specified ID already closed case 10036 : //--- A close order is already present for a specified position case 10039 : //--- The maximum number of open positions is reached case 10040 : //--- Request to activate a pending order is rejected, the order is canceled case 10041 : //--- Request is rejected, because the rule "Only long positions are allowed" is set for the symbol case 10042 : //--- Request is rejected, because the rule "Only short positions are allowed" is set for the symbol case 10043 : //--- Request is rejected, because the rule "Only closing of existing positions is allowed" is set for the symbol case 10044 : //--- Request is rejected, because the rule "Only closing of existing positions by FIFO rule is allowed" is set for the symbol case 10045 : return ERROR_CODE_PROCESSING_METHOD_EXIT; //--- Requote case 10004 : //--- Request rejected case 10006 : //--- Prices changed case 10020 : return ERROR_CODE_PROCESSING_METHOD_REFRESH; //--- Invalid request case 10013 : //--- Invalid request volume case 10014 : //--- Invalid request price case 10015 : //--- Invalid request stop levels case 10016 : //--- Insufficient funds for request execution case 10019 : //--- Invalid order expiration in a request case 10022 : //--- The specified type of order execution by balance is not supported case 10030 : //--- Closed volume exceeds the current position volume case 10038 : return ERROR_CODE_PROCESSING_METHOD_CORRECT; //--- No quotes to handle the request case 10021 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000; // ERROR_CODE_PROCESSING_METHOD_WAIT; //--- Too frequent requests case 10024 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000; // ERROR_CODE_PROCESSING_METHOD_WAIT //--- An order or a position is frozen case 10029 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)10000; // ERROR_CODE_PROCESSING_METHOD_WAIT; //--- No connection to the trade server case 10031 : return (ENUM_ERROR_CODE_PROCESSING_METHOD)20000; // ERROR_CODE_PROCESSING_METHOD_WAIT; //--- Request handling error case 10011 : //--- Auto trading disabled by the client terminal case 10027 : return ERROR_CODE_PROCESSING_METHOD_PENDING; //--- Order placed case 10008 : //--- Request executed case 10009 : //--- Request executed partially case 10010 : #endif //--- "OK" default: break; } return ERROR_CODE_PROCESSING_METHOD_OK; } //+------------------------------------------------------------------+
何故でしょうか。まず、待機中の保留中のリクエストの生成をテストするために、リクエスト間での20秒の待機を返します。それに加えて、これにより、取引サーバとの接続が復元されるのを待つ間、複数の取引試行を実行するのがより便利になります。とにかく、これは保留中のリクエストを処理する最初のテストバージョンであり、改善および変更されます。
ここでは概念をテストしているため、ポジション開き、取引サーバエラーを取得するためだけに保留中リクエストを作成します。取引リクエストの有効性をチェックする際は、ポジションを開くメソッド内で待機するので保留中リクエストは作成しません。
保留中のリクエスト作成ブロックをポジションを開くメソッドに追加します。
//+------------------------------------------------------------------+ //| Open a position | //+------------------------------------------------------------------+ template<typename SL,typename TP> bool CTrading::OpenPosition(const ENUM_POSITION_TYPE type, const double volume, const string symbol, const ulong magic=ULONG_MAX, const SL sl=0, const TP tp=0, const string comment=NULL, const ulong deviation=ULONG_MAX) { //--- Set the trading request result as 'true' and the error flag as "no errors" bool res=true; this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR; ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)type; ENUM_ACTION_TYPE action=(ENUM_ACTION_TYPE)order_type; //--- Get a symbol object by a symbol name. If failed to get CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(symbol); //--- If failed to get - write the "internal error" flag, display the message in the journal and return 'false' if(symbol_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ)); return false; } //--- get a trading object from a symbol object CTradeObj *trade_obj=symbol_obj.GetTradeObj(); //--- If failed to get - write the "internal error" flag, display the message in the journal and return 'false' if(trade_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ)); return false; } //--- Set the prices //--- If failed to set - write the "internal error" flag, set the error code in the return structure, //--- display the message in the journal and return 'false' if(!this.SetPrices(order_type,0,sl,tp,0,DFUN,symbol_obj)) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; trade_obj.SetResultRetcode(10021); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(10021)); // No quotes to process the request return false; } //--- Write the volume to the request structure this.m_request.volume=volume; //--- Get the method of handling errors from the CheckErrors() method while checking for errors in the request parameters double pr=(type==POSITION_TYPE_BUY ? symbol_obj.Ask() : symbol_obj.Bid()); ENUM_ERROR_CODE_PROCESSING_METHOD method=this.CheckErrors(this.m_request.volume,pr,action,order_type,symbol_obj,trade_obj,DFUN,0,this.m_request.sl,this.m_request.tp); //--- In case of trading limitations, funds insufficiency, //--- if there are limitations by StopLevel or FreezeLevel ... if(method!=ERROR_CODE_PROCESSING_METHOD_OK) { //--- If trading is completely disabled, set the error code to the return structure, //--- display a journal message, play the error sound and exit if(method==ERROR_CODE_PROCESSING_METHOD_DISABLE) { trade_obj.SetResultRetcode(MSG_LIB_TEXT_TRADING_DISABLE); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE)); if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order_type); return false; } //--- If the check result is "abort trading operation" - set the last error code to the return structure, //--- display a journal message, play the error sound and exit if(method==ERROR_CODE_PROCESSING_METHOD_EXIT) { int code=this.m_list_errors.At(this.m_list_errors.Total()-1); if(code!=NULL) { trade_obj.SetResultRetcode(code); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); } if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_OPERATION_ABORTED)); if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order_type); return false; } //--- If the check result is "waiting" - set the last error code to the return structure and display the message in the journal if(method==ERROR_CODE_PROCESSING_METHOD_WAIT) { int code=this.m_list_errors.At(this.m_list_errors.Total()-1); if(code!=NULL) { trade_obj.SetResultRetcode(code); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); } if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST)); //--- Instead of creating a pending request, we temporarily wait the required time period (the CheckErrors() method result is returned) ::Sleep(method); //--- after waiting, update all data symbol_obj.Refresh(); } //--- If the check result is "create a pending request", do nothing temporarily if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST)); } } //--- In the loop by the number of attempts for(int i=0;i<this.m_total_try;i++) { //--- Send the request res=trade_obj.OpenPosition(type,this.m_request.volume,this.m_request.sl,this.m_request.tp,magic,comment,deviation); //--- If the request is executed successfully or the asynchronous order sending mode is set, play the success sound //--- set for a symbol trading object for this type of trading operation and return 'true' if(res || trade_obj.IsAsyncMode()) { if(this.IsUseSounds()) trade_obj.PlaySoundSuccess(action,order_type); return true; } //--- If the request is not successful, play the error sound set for a symbol trading object for this type of trading operation else { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_TRY_N),string(i+1),". ",CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(trade_obj.GetResultRetcode())); if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order_type); //--- Get the error handling method method=this.ResultProccessingMethod(trade_obj.GetResultRetcode()); //--- If "Disable trading for the EA" is received as a result of sending a request, enable the disabling flag and end the attempt loop if(method==ERROR_CODE_PROCESSING_METHOD_DISABLE) { this.SetTradingDisableFlag(true); break; } //--- If "Exit the trading method" is received as a result of sending a request, end the attempt loop if(method==ERROR_CODE_PROCESSING_METHOD_EXIT) { break; } //--- If "Correct the parameters and repeat" is received as a result of sending a request - //--- correct the parameters and start the next iteration if(method==ERROR_CODE_PROCESSING_METHOD_CORRECT) { this.RequestErrorsCorrecting(this.m_request,order_type,trade_obj.SpreadMultiplier(),symbol_obj,trade_obj); continue; } //--- If "Update data and repeat" is received as a result of sending a request - //--- update data and start the next iteration if(method==ERROR_CODE_PROCESSING_METHOD_REFRESH) { symbol_obj.Refresh(); continue; } //--- If "Wait and repeat" or "Create a pending request" is received as a result of sending a request, //--- create a pending request and end the loop if(method>ERROR_CODE_PROCESSING_METHOD_REFRESH) { //--- If the trading request magic number, has no pending request ID if(this.GetPendReqID((uint)magic)==0) { //--- Waiting time in milliseconds: //--- for the "Wait and repeat" handling method, the waiting value corresponds to the 'method' value, //--- for the "Create a pending request" handling method - till there is a zero waiting time ulong wait=(method>ERROR_CODE_PROCESSING_METHOD_PENDING ? method : 0); //--- Look for the least of the possible IDs. If failed to find //--- or in case of an error while updating the current symbol data, return 'false' int id=this.GetFreeID(); if(id<1 || !symbol_obj.RefreshRates()) return false; //--- Write the request ID to the magic number, while a symbol name is set in the request structure //--- Set position and trading operation types (the remaining structure fields are already filled in) uint mn=(magic==ULONG_MAX ? (uint)trade_obj.GetMagic() : (uint)magic); this.SetPendReqID((uchar)id,mn); this.m_request.magic=mn; this.m_request.symbol=symbol_obj.Name(); this.m_request.action=TRADE_ACTION_DEAL; this.m_request.type=order_type; //--- Pass the number of trading attempts minus one to the pending request, //--- since there already has been one failed attempt uchar attempts=(this.m_total_try-1 < 1 ? 1 : this.m_total_try-1); this.CreatePendingRequest((uchar)id,attempts,wait,this.m_request,trade_obj.GetResultRetcode(),symbol_obj); break; } } } } //--- Return the result of sending a trading request in a symbol trading object return res; } //+------------------------------------------------------------------+
コードのコメントにはすべての詳細が含まれています。ただし、質問がある場合は、コメントセクションを使用してください。
ライブラリの CEngine基本オブジェクトクラスのEngine.mqhファイルを少し改善しましょう。
取引オブジェクトのプロパティを配置するメソッドのブロックに、取引試行回数を設定するメソッドを追加します。
//--- Set the following for the trading classes: //--- (1) correct filling policy, (2) filling policy, //--- (3) correct order expiration type, (4) order expiration type, //--- (5) magic number, (6) comment, (7) slippage, (8) volume, (9) order expiration date, //--- (10) the flag of asynchronous sending of a trading request, (11) logging level, (12) number of trading attempts void TradingSetCorrectTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol_name=NULL); void TradingSetTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol_name=NULL); void TradingSetCorrectTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol_name=NULL); void TradingSetTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol_name=NULL); void TradingSetMagic(const uint magic,const string symbol_name=NULL); void TradingSetComment(const string comment,const string symbol_name=NULL); void TradingSetDeviation(const ulong deviation,const string symbol_name=NULL); void TradingSetVolume(const double volume=0,const string symbol_name=NULL); void TradingSetExpiration(const datetime expiration=0,const string symbol_name=NULL); void TradingSetAsyncMode(const bool async_mode=false,const string symbol_name=NULL); void TradingSetLogLevel(const ENUM_LOG_LEVEL log_level=LOG_LEVEL_ERROR_MSG,const string symbol_name=NULL); void TradingSetTotalTry(const uchar attempts) { this.m_trading.SetTotalTry(attempts); } //--- Set standard sounds (symbol==NULL) for a symbol trading object, (symbol!=NULL) for trading objects of all symbols
メソッドは、単に同じ名前の取引クラスメソッドを呼び出します。
クラスのprivateセクションにグループID値をuchar値に変換するメソッドを追加し、publicセクションには、複合マジックナンバー値を作成して返すメソッドを宣言します。
//--- コンストラクタ/デストラクタ CEngine(); ~CEngine(); private: //--- Convert the value of 0 - 15 into the necessary uchar number bits (0 - lower, 1 - upper ones) uchar ConvToXX(const uchar number,const uchar index) const { return((number>15 ? 15 : number)<<(4*(index>1 ? 1 : index))); } public: //--- Create and return the composite magic number from the specified magic number value, the first and second group IDs and the pending request ID uint SetCompositeMagicNumber(ushort magic_id,const uchar group_id1=0,const uchar group_id2=0,const uchar pending_req_id=0); }; //+------------------------------------------------------------------+
上記の変換メソッドはすでに検討済みです。複合マジックナンバーの作成方法は、マジックナンバー、1番目と2番目のグループ、および保留中の注文IDの値を、サーバに送信するときに注文用の単一のマジックナンバーセットに結合するために使用されます。
クラス本体の外側で実装しましょう。
//+------------------------------------------------------------------+ //| Create and return the composite magic number | //| from the specified magic number value, | //| first and seconf group IDs and | //| the pending request ID | //+------------------------------------------------------------------+ uint CEngine::SetCompositeMagicNumber(ushort magic_id,const uchar group_id1=0,const uchar group_id2=0,const uchar pending_req_id=0) { uint magic=magic_id; this.m_trading.SetGroupID1(group_id1,magic); this.m_trading.SetGroupID2(group_id2,magic); this.m_trading.SetPendReqID(pending_req_id,magic); return magic; } //+------------------------------------------------------------------+
メソッドは、以前に考慮されたIDを設定するための取引クラスメソッドを使用して、(メソッドから返される)マジックナンバー値にすべて追加されるすべてのIDを受け取ります。
提供されたアイデアを確認するために必要なのは、これですべてです。
保留中のリクエストの生成と処理をテストするには、待機後に2番目のリクエストが必要なエラーをシミュレートする必要があります。覚えているかもしれませんが、「取引サーバへの接続なし」エラーの処理は20秒待機することです。デフォルトでは5回の取引試行があります。つまり、EAを起動し、インターネットを無効にし(取引サーバから切断)、ポジションを開こうとするだけです(テストEA取引パネルの[買/売]ボタン)。エラーが発生した後、20 * 5 = 100秒でインターネットを再び有効にし、EAが作成された保留中リクエストを処理する方法を観察します。100秒後(5回の繰り返し試行を完了するのに必要)、保留中リクエストはリクエストリストから自動的に削除されるはずです(接続がアクティブな場合のみ時間を取得できるため、サーバへの接続が復元された後)。現在、保留中リクエスト操作をテストしているため、この機能はまだ実装されていません。その上、この機能はまだ開発中であり、残りのすべての機能を実装するために変更が必要です。これは、取引サーバへの接続が復元された後、EAはどんな場合でもリクエストオブジェクトに設定された取引リクエストの送信を開始することを意味します。最初の繰り返し試行後、ポジションを開き、保留中リクエストオブジェクトをリクエストリストから削除する必要があります。
保留中リクエストとともに、マジックナンバー値に複数のIDを保存することを実装しました。これらのIDを送信されたリクエストのマジックナンバーに追加することをテストするために、グループ1と2の最初と2番目のサブグループ番号をランダムに選択し、それらを「マジックナンバー」注文プロパティに書き込みましょう。ポジションを開くと、操作ログは実際のポジション/発注済みマジックナンバーと設定で設定されたマジックナンバーID(マジックナンバーの真の値の後の括弧内)、およびグループ(G1およびG2として示される)1番目と2番目のサブグループIDの両方を表示します。
テスト
保留中リクエストをテストするには、前の記事のEAを\MQL5\Experts\TestDoEasy\Part26\にTestDoEasyPart26.mq5という名前で保存します。
EAの入力でマジックナンバーの型をulongとushortに変えます。これで、マジックナンバーの最大サイズは2バイト(65535)となります。また、取引試行数変数を追加します。
//--- 入力変数 input ushort InpMagic = 123; // Magic number input double InpLots = 0.1; // Lots input uint InpStopLoss = 150; // StopLoss in points input uint InpTakeProfit = 150; // TakeProfit in points input uint InpDistance = 50; // Pending orders distance (points) input uint InpDistanceSL = 50; // StopLimit orders distance (points) input uint InpSlippage = 5; // Slippage in points input uint InpSpreadMultiplier = 1; // Spread multiplier for adjusting stop-orders by StopLevel input uchar InpTotalAttempts = 5; // Number of trading attempts sinput double InpWithdrawal = 10; // Withdrawal funds (in tester) sinput uint InpButtShiftX = 40; // Buttons X shift sinput uint InpButtShiftY = 10; // Buttons Y shift input uint InpTrailingStop = 50; // Trailing Stop (points) input uint InpTrailingStep = 20; // Trailing Step (points) input uint InpTrailingStart = 0; // Trailing Start (points) input uint InpStopLossModify = 20; // StopLoss for modification (points) input uint InpTakeProfitModify = 60; // TakeProfit for modification (points) sinput ENUM_SYMBOLS_MODE InpModeUsedSymbols = SYMBOLS_MODE_CURRENT; // Mode of used symbols list sinput string InpUsedSymbols = "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY"; // List of used symbols (comma - separator) sinput bool InpUseSounds = true; // Use sounds //--- グローバル変数
グローバル変数で、magic_number変数の型をulongからushortに変更し、グループ値を保存するための2つの変数を追加します。
//--- グローバル変数 CEngine engine; SDataButt butt_data[TOTAL_BUTT]; string prefix; double lot; double withdrawal=(InpWithdrawal<0.1 ?0.1 : InpWithdrawal); ushort magic_number; uint stoploss; uint takeprofit; uint distance_pending; uint distance_stoplimit; uint slippage; bool trailing_on; double trailing_stop; double trailing_step; uint trailing_start; uint stoploss_to_modify; uint takeprofit_to_modify; int used_symbols_mode; string used_symbols; string array_used_symbols[]; bool testing; uchar group1; uchar group2; //+------------------------------------------------------------------+
OnInit()ハンドラーで、グループ変数を初期化し、初期状態を設定して擬似乱数を生成します。
//+------------------------------------------------------------------+ //| エキスパート初期化関数 | //+------------------------------------------------------------------+ int OnInit() { //--- Calling the function displays the list of enumeration constants in the journal //--- (the list is set in the strings 22 and 25 of the DELib.mqh file) for checking the constants validity //EnumNumbersTest(); //--- Set EA global variables prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"; testing=engine.IsTester(); for(int i=0;i<TOTAL_BUTT;i++) { butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i); butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i); } lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0)); magic_number=InpMagic; stoploss=InpStopLoss; takeprofit=InpTakeProfit; distance_pending=InpDistance; distance_stoplimit=InpDistanceSL; slippage=InpSlippage; trailing_stop=InpTrailingStop*Point(); trailing_step=InpTrailingStep*Point(); trailing_start=InpTrailingStart; stoploss_to_modify=InpStopLossModify; takeprofit_to_modify=InpTakeProfitModify; //--- Initialize random group numbers group1=0; group2=0; srand(GetTickCount()); //--- Initialize DoEasy library OnInitDoEasy();
ライブラリ初期化関数で、すべての取引オブジェクトにデフォルトのマジックナンバーと取引試行回数を設定します。
//+------------------------------------------------------------------+ //| Initializing DoEasy library | //+------------------------------------------------------------------+ void OnInitDoEasy() { //--- Check if working with the full list is selected used_symbols_mode=InpModeUsedSymbols; if((ENUM_SYMBOLS_MODE)used_symbols_mode==SYMBOLS_MODE_ALL) { int total=SymbolsTotal(false); string ru_n="\nКоличество символов на сервере "+(string)total+".\nМаксимальное количество: "+(string)SYMBOLS_COMMON_TOTAL+" символов."; string en_n="\nNumber of symbols on server "+(string)total+".\nMaximum number: "+(string)SYMBOLS_COMMON_TOTAL+" symbols."; string caption=TextByLanguage("Внимание!","Attention!"); string ru="Выбран режим работы с полным списком.\nВ этом режиме первичная подготовка списка коллекции символов может занять длительное время."+ru_n+"\nПродолжить?\n\"Нет\" - работа с текущим символом \""+Symbol()+"\""; string en="Full list mode selected.\nIn this mode, the initial preparation of the collection symbols list may take a long time."+en_n+"\nContinue?\n\"No\" - working with the current symbol \""+Symbol()+"\""; string message=TextByLanguage(ru,en); int flags=(MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2); int mb_res=MessageBox(message,caption,flags); switch(mb_res) { case IDNO : used_symbols_mode=SYMBOLS_MODE_CURRENT; break; default: break; } } //--- Fill in the array of used symbols used_symbols=InpUsedSymbols; CreateUsedSymbolsArray((ENUM_SYMBOLS_MODE)used_symbols_mode,used_symbols,array_used_symbols); //--- Set the type of the used symbol list in the symbol collection engine.SetUsedSymbols(array_used_symbols); //--- Displaying the selected mode of working with the symbol object collection Print(engine.ModeSymbolsListDescription(),TextByLanguage(". Number of used symbols: ",". Number of symbols used: "),engine.GetSymbolsCollectionTotal()); //--- Create resource text files engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_01",TextByLanguage("Звук упавшей монетки 1","Falling coin 1"),sound_array_coin_01); engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_02",TextByLanguage("Звук упавших монеток","Falling coins"),sound_array_coin_02); engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_03",TextByLanguage("Звук монеток","Coins"),sound_array_coin_03); engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_04",TextByLanguage("Звук упавшей монетки 2","Falling coin 2"),sound_array_coin_04); engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_01",TextByLanguage("Звук щелчка по кнопке 1","Button click 1"),sound_array_click_01); engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_02",TextByLanguage("Звук щелчка по кнопке 2","Button click 2"),sound_array_click_02); engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_03",TextByLanguage("Звук щелчка по кнопке 3","Button click 3"),sound_array_click_03); engine.CreateFile(FILE_TYPE_WAV,"sound_array_cash_machine_01",TextByLanguage("Звук кассового аппарата","Cash machine"),sound_array_cash_machine_01); engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_green",TextByLanguage("Изображение \"Зелёный светодиод\"","Image \"Green Spot lamp\""),img_array_spot_green); engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_red",TextByLanguage("Изображение \"Красный светодиод\"","Image \"Red Spot lamp\""),img_array_spot_red); //--- Pass all existing collections to the trading class engine.TradingOnInit(); //--- Set the default magic number for all used symbols engine.TradingSetMagic(engine.SetCompositeMagicNumber(magic_number)); //--- Set synchronous passing of orders for all used symbols engine.TradingSetAsyncMode(false); //--- Set the number of trading attempts in case of an error engine.TradingSetTotalTry(InpTotalAttempts); //--- Set standard sounds for trading objects of all used symbols engine.SetSoundsStandart(); //--- Set the general flag of using sounds engine.SetUseSounds(InpUseSounds); //--- Set the spread multiplier for symbol trading objects in the symbol collection engine.SetSpreadMultiplier(InpSpreadMultiplier); //--- Set controlled values for symbols //--- Get the list of all collection symbols CArrayObj *list=engine.GetListAllUsedSymbols(); if(list!=NULL && list.Total()!=0) { //--- In a loop by the list, set the necessary values for tracked symbol properties //--- By default, the LONG_MAX value is set to all properties, which means "Do not track this property" //--- It can be enabled or disabled (by setting the value less than LONG_MAX or vice versa - set the LONG_MAX value) at any time and anywhere in the program /* for(int i=0;i<list.Total();i++) { CSymbol* symbol=list.At(i); if(symbol==NULL) continue; //--- Set control of the symbol price increase by 100 points symbol.SetControlBidInc(100000*symbol.Point()); //--- Set control of the symbol price decrease by 100 points symbol.SetControlBidDec(100000*symbol.Point()); //--- Set control of the symbol spread increase by 40 points symbol.SetControlSpreadInc(400); //--- Set control of the symbol spread decrease by 40 points symbol.SetControlSpreadDec(400); //--- Set control of the current spread by the value of 40 points symbol.SetControlSpreadLevel(400); } */ } //--- Set controlled values for the current account CAccount* account=engine.GetAccountCurrent(); if(account!=NULL) { //--- Set control of the profit increase to 10 account.SetControlledValueINC(ACCOUNT_PROP_PROFIT,10.0); //--- Set control of the funds increase to 15 account.SetControlledValueINC(ACCOUNT_PROP_EQUITY,15.0); //--- Set profit control level to 20 account.SetControlledValueLEVEL(ACCOUNT_PROP_PROFIT,20.0); } } //+------------------------------------------------------------------+
ランダムなグループID値でマジックナンバーをテストするために、trueに等しいcomp_magicブール変数を導入し、ポジションを開く/保留中の注文を配置する機能における複合マジックナンバーを示します。magic_number変数を使用する代わりに、
comp_magic変数値に応じてマジック値を格納する新しい magic変数を導入します 。
magicの値(設定で定義された永続マジックナンバー、または指定されたマジックナンバー+ランダムグループ1および2のID値で構成される複合マジックナンバー)を設定するとき、comp_magic
値を確認します。trueの場合、複合マジックナンバーを使用します。falseの場合は、設定で定義されたものを使用します。
EA取引パネルボタンを押す処理を行うPressButtonEvents()関数を変更します。
//+------------------------------------------------------------------+ //| Handle pressing the buttons | //+------------------------------------------------------------------+ void PressButtonEvents(const string button_name) { bool comp_magic=true; // Temporary variable selecting the composite magic number with random group IDs string comment=""; //--- ボタン名を文字列IDに変換する string button=StringSubstr(button_name,StringLen(prefix)); //--- Random group 1 and 2 numbers within the range of 0 - 15 group1=(uchar)Rand(); group2=(uchar)Rand(); uint magic=(comp_magic ? engine.SetCompositeMagicNumber(magic_number,group1,group2) : magic_number); //--- ボタンが押下された場合 if(ButtonState(button_name)) { //--- BUTT_BUYボタンが押下されたら、買いポジションを開く if(button==EnumToString(BUTT_BUY)) { //--- 買いポジションを開く engine.OpenBuy(lot,Symbol(),magic,stoploss,takeprofit); // No comment - the default comment is to be set } //--- BUTT_BUY_LIMITボタンが押下されたら、BuyLimit注文を出す else if(button==EnumToString(BUTT_BUY_LIMIT)) { //--- BuyLimit注文を設定する engine.PlaceBuyLimit(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyLimit","Pending BuyLimit order")); } //--- BUTT_BUY_STOPボタンが押下されたら、BuyStopを設定する else if(button==EnumToString(BUTT_BUY_STOP)) { //--- BuyStop注文を設定する engine.PlaceBuyStop(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyStop","Pending BuyStop order")); } //--- BUTT_BUY_STOP_LIMITボタンが押下されたら、BuyStopLimitを設定する else if(button==EnumToString(BUTT_BUY_STOP_LIMIT)) { //--- BuyStopLimit注文を設定する engine.PlaceBuyStopLimit(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyStopLimit","Pending BuyStopLimit order")); } //--- BUTT_SELLボタンが押下されたら、売りポジションを開く else if(button==EnumToString(BUTT_SELL)) { //--- 売りポジションを開く engine.OpenSell(lot,Symbol(),magic,stoploss,takeprofit); // No comment - the default comment is to be set } //--- BUTT_SELL_LIMITボタンが押下されたら、SellLimitを設定する else if(button==EnumToString(BUTT_SELL_LIMIT)) { //--- SellLimit注文を設定する engine.PlaceSellLimit(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellLimit","Pending SellLimit order")); } //--- BUTT_SELL_STOPボタンが押下されたら、SellStopを設定する else if(button==EnumToString(BUTT_SELL_STOP)) { //--- SellStop注文を設定する engine.PlaceSellStop(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellStop","Pending SellStop order")); } //--- BUTT_SELL_STOP_LIMITボタンが押下されたら、SellStopLimitを設定する else if(button==EnumToString(BUTT_SELL_STOP_LIMIT)) { //--- SellStopLimit注文を設定する engine.PlaceSellStopLimit(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellStopLimit","Pending SellStopLimit order")); } //--- BUTT_CLOSE_BUYボタンが押下されたら、最大利益を持つ買いポジションを決済する else if(button==EnumToString(BUTT_CLOSE_BUY))
ライブラリ取引メソッドを呼び出すすべての文字列でmagic_number 値をmagicで置き換えます。
グループIDにランダムな値を設定するには、既に最小および最大の範囲値を備えたRand()関数によって返される値を追加します。この範囲内で、関数は擬似ランダム値を返します。
//+------------------------------------------------------------------+ //| A random value within the range | //+------------------------------------------------------------------+ uint Rand(const uint min=0, const uint max=15) { return (rand() % (max+1-min))+min; } //+------------------------------------------------------------------+
コンパイルしてEAを起動します。インターネットをオフにして、端末の右下隅に次の画像が表示されるまで待ちます。
インターネットを無効にして[売]をクリックすると、取引サーバはエラーを返し、次のエントリが操作ログに表示されます。
2019.11.26 15:34:48.661 CTrading::OpenPosition<uint,uint>: Invalid request: 2019.11.26 15:34:48.661 No connection with the trade server 2019.11.26 15:34:48.661 Correction of trade request parameters ... 2019.11.26 15:34:48.661 Trading attempt #1. Error: No connection with the trade server
エラーを受け取った後、ライブラリは、ショートポジションを開けなかったときにパラメータが設定された保留中リクエストを作成します。
保留中リクエストには、試行回数と20秒の待機時間も含まれます。
次に、取引サーバへの接続を復元できるようにインターネットを有効にします。
接続が復元されるとすぐに、ライブラリはサーバに送信する保留中リクエストの処理を開始します。その結果、操作ログエントリにポジションが表示されます。
2019.11.26 15:35:00.853 CTrading::OpenPosition<double,double>: Invalid request: 2019.11.26 15:35:00.853 Trading is prohibited for the current account 2019.11.26 15:35:00.853 Correction of trade request parameters ... 2019.11.26 15:35:00.853 Trading operation aborted 2019.11.26 15:35:01.192 CTrading::OpenPosition<double,double>: Invalid request: 2019.11.26 15:35:01.192 Trading is prohibited for the current account 2019.11.26 15:35:01.192 Correction of trade request parameters ... 2019.11.26 15:35:01.192 Trading operation aborted 2019.11.26 15:35:01.942 - Position is open: 2019.11.26 10:35:01.660 - 2019.11.26 15:35:01.942 EURUSD Opened 0.10 Sell #486405595 [0.10 Market-order Sell #486405595] at price 1.10126, sl 1.10285, tp 1.09985, Magic number 17629307 (123), G1: 13 2019.11.26 15:35:01.942 OnDoEasyEvent: Position is open
ご覧のとおり、取引サーバへの接続を復元した後、現在のアカウントの取引は遅れて有効になりました。
しかし、保留中リクエストはとにかく機能しました。...
また、操作ログの実際のマジックナンバー(17629307)に続いて、括弧(123)のEA設定で定義されたマジックナンバーに加えたエントリ「G1: 13」があって、最初のグループIDが13であることが分かります。2番目のグループは値がゼロなので、IDはありません。よって「G2: XX」2番目のグループIDエントリはありません。
注意事項
記事で説明されている保留中リクエスト、および実際の取引で添付されているテストEAで取引クラスの結果を実際の取引で使用しないでください。この記事、それに付随する資料、および結果は、保留中リクエストの概念をテストすることのみを目的としています。現在の状態では、これは完成した製品でも実際の取引を目的としたものでもなく、デモモードまたはテスター専用です。
次の段階
次の記事では、保留中リクエストクラスの開発を継続します。
現在のバージョンのライブラリのすべてのファイルは、テスト用EAファイルと一緒に以下に添付されているので、テストするにはダウンロードしてください。
質問、コメント、提案はコメント欄にお願いします。
シリーズのこれまでの記事:
第1部: 概念、データ管理
第2部:
過去の注文と取引のコレクション
第3部: 注文と取引のコレクション、検索と並び替え
第4部:
取引イベント概念
第5部: 取引イベントのクラスとコレクション取引イベントのプログラムへの送信
第6部:
ネッティング勘定イベント
第7部: StopLimit注文発動イベント、注文およびポジション変更イベントの機能の準備
第8部:
注文とポジションの変更イベント
第9部: MQL4との互換性 - データの準備
第10部:
MQL4との互換性 - ポジションオープンイベントと指値注文発動イベント
第11部: MQL4との互換性 - ポジション決済イベント
第12部:
口座オブジェクトクラスと口座オブジェクトコレクション
第13部: 口座オブジェクトイベント第14部:
銘柄オブジェクト
第15部: 銘柄オブジェクトコレクション
第16部:
銘柄コレクションイベント
第17部: ライブラリオブジェクトの相互作用
第18部:口座と他のライブラリオブジェクトの相互作用
第19部:ライブラリメッセージのクラス
第20部:プログラムリソースの作成と格納
第21部:取引クラス
- 基本クロスプラットフォーム取引オブジェクト
第22部:取引クラス - 基本取引クラス、制限の検証
第23部:取引クラス
- 基本取引クラス、パラメータ有効性の検証
第24部:取引クラス - 基本取引クラス、無効なパラメータの自動修正
第25部:取引クラス
- 取引サーバによって返されたエラーを処理する基本取引クラス
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/7394





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