MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第10部): MQL4との互換性 - ポジションオープンイベントと指値注文発動イベント
内容
テストEA
前の記事では、MQL4とMQL5の違いに関連するライブラリファイルのエラーを削除し、MQL4の注文とポジション履歴のコレクションを導入しました。本稿では、ライブラリでのMQL4とMQL5の合併を継続し、ポジションを開くイベントと未決注文の発動イベントを定義します。
改善手順の順序は逆になります。以前は、機能を導入してからEAをテストしました。ここでは、何を改善する必要があるのかを理解するために、テストEAを起動して、何が機能して何がしないかを確認する必要があります。機能しないものは改善すべきものです。
これを達成するには、\MQL5\Experts\TestDoEasy\Part08フォルダにある第8部のテストEATestDoEasyPart08.mq5をMetaTrader 4フォルダー( \MQL4\Experts\TestDoEasy\Part10)にTestDoEasyPart10.mq4という名前で保存します。
コンパイルしてみると、最終的に34件のコンパイルエラーが発生します。それらのほとんどすべては、MQL4標準ライブラリにト取引クラスが存在しないことに関連しています。
インクルードファイルが存在しないことを示す最初のエラーに移動して、
修正します。ファイルはMQL5でのみインクルードします。
//+------------------------------------------------------------------+ //| TestDoEasyPart08.mq5 | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/ja/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/ja/users/artmedia70" #property version "1.00" //--- include #include <DoEasy\Engine.mqh> #ifdef __MQL5__ #include <Trade\Trade.mqh> #endif //--- 列挙体
エラーが33件になっています。再び最初のエラーに移動します。これは、CTrade取引クラスオブジェクトを宣言するときに欠落している型を示します。この型はMQL4には存在しません。
以前と同様に、条件付きコンパイルディレクティブを使用しましょう。
//--- グローバル変数 CEngine engine; #ifdef __MQL5__ CTrade trade; #endif SDataButt butt_data[TOTAL_BUTT];
コンパイルします。今度のエラーは、CTradeクラスの「trade」オブジェクトがMQL4に知られていないことによるので、これを同じように修正します。
//--- setting trade parameters #ifdef __MQL5__ trade.SetDeviationInPoints(slippage); trade.SetExpertMagicNumber(magic_number); trade.SetTypeFillingBySymbol(Symbol()); trade.SetMarginMode(); trade.LogLevel(LOG_LEVEL_NO); #endif //--- return(INIT_SUCCEEDED); }
#elseディレクティブを使用して、すべてのtradeオブジェクトインスタンスをEAコード全体で条件付きコンパイルディレクティブに収めます。 MQL4コードは そこに配置します。以前の編集とコンパイルを行った後、未知のtradeタイプの最初のエラーを使用しましょう。
//--- BUTT_BUYボタンが押下されたら、買いポジションを開く if(button==EnumToString(BUTT_BUY)) { //--- StopLevelに相対した正しいストップロスとテイクプロフィット価格を取得する double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit); //--- 買いポジションを開く #ifdef __MQL5__ trade.Buy(lot,Symbol(),0,sl,tp); #else #endif }
すべての「trade」オブジェクトインスタンスを条件付きコンパイルディレクティブに入れた後には、パラメータの欠如によって、コンパイラが使用するオーバーロード関数の呼び出しを正確に定義できないことを示す別のエラーが発生します。
コードをよく見ると、コンパイラが混乱する理由が明らかになります。
//+------------------------------------------------------------------+ //| Return the flag of a prefixed object presence | //+------------------------------------------------------------------+ bool IsPresentObects(const string object_prefix) { for(int i=ObjectsTotal(0)-1;i>=0;i--) if(StringFind(ObjectName(0,i,0),object_prefix)>WRONG_VALUE) return true; return false; } //+------------------------------------------------------------------+
MQL5では、この関数には単一の呼び出し形式のみがあります。
int ObjectsTotal( long chart_id, // chart ID int sub_window=-1, // window index int type=-1 // object type );
ここで、1番目のパラメータはチャートIDです(0~現在)。
一方、MQL4では、この関数には、しばらくの間2つの呼び出し形式が存在してきました。1番目のものはMQL5と同じです。
int ObjectsTotal( long chart_id, // chart ID int sub_window=-1, // window index int type=-1 // object type );
2番目のものは古く、パラメータは1つのみです。
int ObjectsTotal( int type=EMPTY // object type );
MQL5では、関数にチャートIDとして0(現在のチャート)を渡すことによって矛盾や疑いが生じることはありませんが、MQL4では、コンパイラは渡されたパラメータを使用して呼び出しタイプを定義する必要があります。この場合、現在のチャートID(0)が渡されていて1番目のの呼び出しフォームを使用する必要があるのか(結局のところ、他の2つのパラメータはデフォルト値に設定されるため、関数に渡す必要はありません)、ウィンドウインデックス(またはオブジェクトタイプ)が渡されていて2番目の呼び出しフォームを使用する必要があるのかを正確に定義できません。
ここでの解決策は簡単で、2番目のパラメータとしてサブウィンドウインデックス(0=メインチャートウィンドウ)を渡すことです。
//+------------------------------------------------------------------+ //| Return the flag of a prefixed object presence | //+------------------------------------------------------------------+ bool IsPresentObects(const string object_prefix) { for(int i=ObjectsTotal(0,0)-1;i>=0;i--) if(StringFind(ObjectName(0,i,0),object_prefix)>WRONG_VALUE) return true; return false; } //+------------------------------------------------------------------+
そして
//+------------------------------------------------------------------+ //| Manage button status | //+------------------------------------------------------------------+ void PressButtonsControl(void) { int total=ObjectsTotal(0,0); for(int i=0;i<total;i++) { string obj_name=ObjectName(0,i); if(StringFind(obj_name,prefix+"BUTT_")<0) continue; PressButtonEvents(obj_name); } } //+------------------------------------------------------------------+
これで、すべてがエラーなしでコンパイルされます。テストを開始する前に、条件付きコンパイルディレクティブを使用してコードから除外したため、EAにはMQL4取引関数が含まれていないことに注意してください。これらを追加する必要があります。
テスター用のコードを作成する際、実際の口座またはデモ口座で取引する際に必要なチェックを実装せず、代わりに最小限のチェックに制限します。
注文チケットとポジションチケット、および計算された価格レベルが関数に渡されるため、行うべきことは、そのチケットで注文/ポジションを選択し、決済タイプと時間を確認することだけです。タイプが注文またはポジションのタイプと一致しない場合、適切なメッセージを表示し、エラーで関数を終了します。注文が削除されるかポジションが決済された場合、メッセージを表示してエラーで終了します。次に、開始/決済/変更のための関数を呼び出し、その実行結果を返します。
DELib.mqhファイルのリストの最後に、必要なすべてのMQL4テスター関数を記述します。
#ifdef __MQL4__ //+------------------------------------------------------------------+ //| MQL4 temporary functions for the tester | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Open Buy position | //+------------------------------------------------------------------+ bool Buy(const double volume,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2) { string sym=(symbol==NULL ? Symbol() : symbol); double price=0; ResetLastError(); if(!SymbolInfoDouble(sym,SYMBOL_ASK,price)) { Print(DFUN,TextByLanguage("Не удалось получить цену Ask. Ошибка ","Could not get Ask price. Error "),(string)GetLastError()); return false; } if(!OrderSend(sym,ORDER_TYPE_BUY,volume,price,deviation,sl,tp,comment,(int)magic,0,clrBlue)) { Print(DFUN,TextByLanguage("Не удалось открыть позицию Buy. Ошибка ","Failed to open a Buy position. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Set pending BuyLimit order | //+------------------------------------------------------------------+ bool BuyLimit(const double volume,const double price_set,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2) { string sym=(symbol==NULL ? Symbol() : symbol); ResetLastError(); if(!OrderSend(sym,ORDER_TYPE_BUY_LIMIT,volume,price_set,deviation,sl,tp,comment,(int)magic,0,clrBlue)) { Print(DFUN,TextByLanguage("Не удалось установить ордер BuyLimit. Ошибка ","Could not place order BuyLimit. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Set pending BuyStop order | //+------------------------------------------------------------------+ bool BuyStop(const double volume,const double price_set,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2) { string sym=(symbol==NULL ? Symbol() : symbol); ResetLastError(); if(!OrderSend(sym,ORDER_TYPE_BUY_STOP,volume,price_set,deviation,sl,tp,comment,(int)magic,0,clrBlue)) { Print(DFUN,TextByLanguage("Не удалось установить ордер BuyStop. Ошибка ","Could not place order BuyStop. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Open Sell position | //+------------------------------------------------------------------+ bool Sell(const double volume,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2) { string sym=(symbol==NULL ? Symbol() : symbol); double price=0; ResetLastError(); if(!SymbolInfoDouble(sym,SYMBOL_BID,price)) { Print(DFUN,TextByLanguage("Не удалось получить цену Bid. Ошибка ","Could not get Bid price. Error "),(string)GetLastError()); return false; } if(!OrderSend(sym,ORDER_TYPE_SELL,volume,price,deviation,sl,tp,comment,(int)magic,0,clrRed)) { Print(DFUN,TextByLanguage("Не удалось открыть позицию Sell. Ошибка ","Failed to open a Sell position. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Set pending SellLimit order | //+------------------------------------------------------------------+ bool SellLimit(const double volume,const double price_set,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2) { string sym=(symbol==NULL ? Symbol() : symbol); ResetLastError(); if(!OrderSend(sym,ORDER_TYPE_SELL_LIMIT,volume,price_set,deviation,sl,tp,comment,(int)magic,0,clrRed)) { Print(DFUN,TextByLanguage("Не удалось установить ордер SellLimit. Ошибка ","Could not place order SellLimit. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Set pending SellStop order | //+------------------------------------------------------------------+ bool SellStop(const double volume,const double price_set,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2) { string sym=(symbol==NULL ? Symbol() : symbol); ResetLastError(); if(!OrderSend(sym,ORDER_TYPE_SELL_STOP,volume,price_set,deviation,sl,tp,comment,(int)magic,0,clrRed)) { Print(DFUN,TextByLanguage("Не удалось установить ордер SellStop. Ошибка ","Could not place order SellStop. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Close position by ticket | //+------------------------------------------------------------------+ bool PositionClose(const ulong ticket,const double volume=0,const int deviation=2) { ResetLastError(); if(!OrderSelect((int)ticket,SELECT_BY_TICKET)) { Print(DFUN,TextByLanguage("Не удалось выбрать позицию. Ошибка ","Could not select position. Error "),(string)GetLastError()); return false; } if(OrderCloseTime()>0) { Print(DFUN,TextByLanguage("Позиция уже закрыта","Position already closed")); return false; } ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType(); if(type>ORDER_TYPE_SELL) { Print(DFUN,TextByLanguage("Ошибка. Не позиция: ","Error. Not position: "),OrderTypeDescription(type)," #",ticket); return false; } double price=0; color clr=clrNONE; if(type==ORDER_TYPE_BUY) { price=SymbolInfoDouble(OrderSymbol(),SYMBOL_BID); clr=clrBlue; } else { price=SymbolInfoDouble(OrderSymbol(),SYMBOL_ASK); clr=clrRed; } double vol=(volume==0 || volume>OrderLots() ? OrderLots() : volume); ResetLastError(); if(!OrderClose((int)ticket,vol,price,deviation,clr)) { Print(DFUN,TextByLanguage("Не удалось закрыть позицию. Ошибка ","Could not close position. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Close position by an opposite one | //+------------------------------------------------------------------+ bool PositionCloseBy(const ulong ticket,const ulong ticket_by) { ResetLastError(); if(!OrderSelect((int)ticket,SELECT_BY_TICKET)) { Print(DFUN,TextByLanguage("Не удалось выбрать позицию. Ошибка ","Could not select position. Error "),(string)GetLastError()); return false; } if(OrderCloseTime()>0) { Print(DFUN,TextByLanguage("Позиция уже закрыта","Position already closed")); return false; } ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType(); if(type>ORDER_TYPE_SELL) { Print(DFUN,TextByLanguage("Ошибка. Не позиция: ","Error. Not position: "),OrderTypeDescription(type)," #",ticket); return false; } ResetLastError(); if(!OrderSelect((int)ticket_by,SELECT_BY_TICKET)) { Print(DFUN,TextByLanguage("Не удалось выбрать встречную позицию. Ошибка ","Could not select the opposite position. Error "),(string)GetLastError()); return false; } if(OrderCloseTime()>0) { Print(DFUN,TextByLanguage("Встречная позиция уже закрыта","Opposite position already closed")); return false; } ENUM_ORDER_TYPE type_by=(ENUM_ORDER_TYPE)OrderType(); if(type_by>ORDER_TYPE_SELL) { Print(DFUN,TextByLanguage("Ошибка. Встречная позиция не является позицией: ","Error. Opposite position is not a position: "),OrderTypeDescription(type_by)," #",ticket_by); return false; } color clr=(type==ORDER_TYPE_BUY ? clrBlue : clrRed); ResetLastError(); if(!OrderCloseBy((int)ticket,(int)ticket_by,clr)) { Print(DFUN,TextByLanguage("Не удалось закрыть позицию встречной. Ошибка ","Could not close position by opposite position. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Remove a pending order by ticket | //+------------------------------------------------------------------+ bool PendingOrderDelete(const ulong ticket) { ResetLastError(); if(!OrderSelect((int)ticket,SELECT_BY_TICKET)) { Print(DFUN,TextByLanguage("Не удалось выбрать ордер. Ошибка ","Could not select order. Error "),(string)GetLastError()); return false; } if(OrderCloseTime()>0) { Print(DFUN,TextByLanguage("Ордер уже удалён","Order already deleted")); return false; } ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType(); if(type<ORDER_TYPE_SELL || type>ORDER_TYPE_SELL_STOP) { Print(DFUN,TextByLanguage("Ошибка. Не ордер: ","Error. Not order: "),PositionTypeDescription((ENUM_POSITION_TYPE)type)," #",ticket); return false; } color clr=(type<ORDER_TYPE_SELL_LIMIT ? clrBlue : clrRed); ResetLastError(); if(!OrderDelete((int)ticket,clr)) { Print(DFUN,TextByLanguage("Не удалось удалить ордер. Ошибка ","Could not delete order. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Modify position by ticket | //+------------------------------------------------------------------+ bool PositionModify(const ulong ticket,const double sl,const double tp) { ResetLastError(); if(!OrderSelect((int)ticket,SELECT_BY_TICKET)) { Print(DFUN,TextByLanguage("Не удалось выбрать позицию. Ошибка ","Could not select position. Error "),(string)GetLastError()); return false; } ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType(); if(type>ORDER_TYPE_SELL) { Print(DFUN,TextByLanguage("Ошибка. Не позиция: ","Error. Not position: "),OrderTypeDescription(type)," #",ticket); return false; } if(OrderCloseTime()>0) { Print(DFUN,TextByLanguage("Ошибка. Для модификации выбрана закрытая позиция: ","Error. Closed position selected for modification: "),PositionTypeDescription((ENUM_POSITION_TYPE)type)," #",ticket); return false; } color clr=(type==ORDER_TYPE_BUY ? clrBlue : clrRed); ResetLastError(); if(!OrderModify((int)ticket,OrderOpenPrice(),sl,tp,0,clr)) { Print(DFUN,TextByLanguage("Не удалось модифицировать позицию. Ошибка ","Failed to modify position. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Modify pending order by ticket | //+------------------------------------------------------------------+ bool PendingOrderModify(const ulong ticket,const double price_set,const double sl,const double tp) { ResetLastError(); if(!OrderSelect((int)ticket,SELECT_BY_TICKET)) { Print(DFUN,TextByLanguage("Не удалось выбрать ордер. Ошибка ","Could not select order. Error "),(string)GetLastError()); return false; } ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType(); if(type<ORDER_TYPE_SELL || type>ORDER_TYPE_SELL_STOP) { Print(DFUN,TextByLanguage("Ошибка. Не ордер: ","Error. Not order: "),PositionTypeDescription((ENUM_POSITION_TYPE)type)," #",ticket); return false; } if(OrderCloseTime()>0) { Print(DFUN,TextByLanguage("Ошибка. Для модификации выбран удалённый ордер: ","Error. Deleted order selected for modification: "),OrderTypeDescription(type)," #",ticket); return false; } color clr=(type<ORDER_TYPE_SELL_LIMIT ? clrBlue : clrRed); ResetLastError(); if(!OrderModify((int)ticket,price_set,sl,tp,0,clr)) { Print(DFUN,TextByLanguage("Не удалось модифицировать ордер. Ошибка ","Failed to modify order. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ #endif
これらの関数は一時的なもので、近い将来にMQL5およびMQL4の本格的な取引クラスが作成されたときに削除されます。
ここで、EAコードでMQL4取引関数を呼び出すために残しておいた場所に、新しく記述された関数の呼び出しを追加する必要があります。Ctrl+Fを押して、検索ボックスにtradeと入力して、MQL4取引関数の呼び出しが設定されるコードのパッセージをすばやく見つけます。
ボタン押下イベントを処理するためのPressButtonEvents()関数から最後まで必要なMQL4取引関数の呼び出しを実装します。コードは非常にかさばりますが、必要な関数の選択は明確です。したがって、ここではコードを提示しないので、記事に添付されているファイルでご覧ください。ここでは、2つのボタン(買いポジションを開くボタンとBuyLimit指値注文ボタン)の押下の処理のみを見ていきます。
//+------------------------------------------------------------------+ //| ボタン押下を処理する | //+------------------------------------------------------------------+ void PressButtonEvents(const string button_name) { //--- ボタン名を文字列IDに変換する string button=StringSubstr(button_name,StringLen(prefix)); //--- ボタンが押下された場合 if(ButtonState(button_name)) { //--- BUTT_BUYボタンが押下されたら、買いポジションを開く if(button==EnumToString(BUTT_BUY)) { //--- StopLevelに相対した正しいストップロスとテイクプロフィット価格を取得する double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit); //--- 買いポジションを開く #ifdef __MQL5__ trade.Buy(lot,Symbol(),0,sl,tp); #else Buy(lot,Symbol(),magic_number,sl,tp); #endif } //--- BUTT_BUY_LIMITボタンが押下されたら、BuyLimitを設定する else if(button==EnumToString(BUTT_BUY_LIMIT)) { //--- StopLevelに相対した正しい注文価格を取得する double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending); //--- StopLevelを考慮して、発注レベルに対する正しいストップロスとテイクプロフィットの価格を取得する double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,takeprofit); //--- BuyLimit注文を設定する #ifdef __MQL5__ trade.BuyLimit(lot,price_set,Symbol(),sl,tp); #else BuyLimit(lot,price_set,Symbol(),magic_number,sl,tp); #endif } //--- BUTT_BUY_STOPボタンが押下されたら、BuyStopを設定する
ライブラリコードをテストすると、奇妙なことに気付きました。コードの改善なしでは、MQL4によって確認されるイベントは、しばらくしてから操作ログに表示されるということです。問題を掘り下げて調べたところ、その理由はCEngineタイマーで動作するコレクションタイマーのカウンターにあることがわかりました。コレクションタイマーカウンターの最小遅延は、第3部でライブラリの基本オブジェクトを作成するときに16ミリ秒に設定されています。しかし、テスターでタイマーを使用せず、OnTick()から直接OnTimer()ライブラリハンドラーを呼び出してティックで動作するため、16ミリ秒の遅延は16ティックの遅延に変わるのです。これを修正するために、テスターフラグを返すメソッドを導入し、OnTimer()ハンドラーでテスターの作業を処理するメソッドを導入するCEngineクラスを少し変更しました。これは、テスターで作業するときにEAのOnTick()から呼び出されます。
この変更を加えるために、privateクラスメンバー変数と変数値を返すメソッドが作成されました。
//+------------------------------------------------------------------+ //| ライブラリ基本クラス | //+------------------------------------------------------------------+ class CEngine : public CObject { private: CHistoryCollection m_history; // 過去の注文と取引のコレクション CMarketCollection m_market; // 注文と取引のコレクション CEventsCollection m_events; // Collection of events CArrayObj m_list_counters; // タイマーカウンタのリスト bool m_first_start; // 初期実行フラグ bool m_is_hedge; // ヘッジ勘定フラグ bool m_is_tester; // Flag of working in the tester bool m_is_market_trade_event; // Flag of an account trading event bool m_is_history_trade_event; // Flag of an account history trading event ENUM_TRADE_EVENT m_acc_trade_event; // 口座取引イベント //--- Return counter index by id public: //--- 市場の(1)ポジション、(2)未決注文、(3)成行注文のリストを返す CArrayObj* GetListMarketPosition(void); CArrayObj* GetListMarketPendings(void); CArrayObj* GetListMarketOrders(void); //--- 過去の(1)注文、(2)削除済み未決注文、(3)取引、(4)ID別の成行注文のポジションリストを返す CArrayObj* GetListHistoryOrders(void); CArrayObj* GetListHistoryPendings(void); CArrayObj* GetListDeals(void); CArrayObj* GetListAllOrdersByPosID(const ulong position_id); //--- 直近の取引イベントをリセットする void ResetLastTradeEvent(void) { this.m_events.ResetLastTradeEvent(); } //--- Return the (1) last trading event, (2) hedge account flag, (3) flag of working in the tester ENUM_TRADE_EVENT LastTradeEvent(void) const { return this.m_acc_trade_event; } bool IsHedge(void) const { return this.m_is_hedge; } bool IsTester(void) const { return this.m_is_tester; } //--- タイマーカウンタを作成する void CreateCounter(const int id,const ulong frequency,const ulong pause); //--- タイマー void OnTimer(void); //--- コンストラクタ/デストラクタ CEngine(); ~CEngine(); }; //+------------------------------------------------------------------+
このテスターフラグ変数値はクラスコンストラクタで設定されます。
//+------------------------------------------------------------------+ //| CEngineコンストラクタ | //+------------------------------------------------------------------+ CEngine::CEngine() : m_first_start(true),m_acc_trade_event(TRADE_EVENT_NO_EVENT) { this.m_list_counters.Sort(); this.m_list_counters.Clear(); this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_PAUSE); this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif; this.m_is_tester=::MQLInfoInteger(MQL_TESTER); ::ResetLastError(); #ifdef __MQL5__ if(!::EventSetMillisecondTimer(TIMER_FREQUENCY)) ::Print(DFUN,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(string)::GetLastError()); //---__MQL4__ #else if(!this.IsTester() && !::EventSetMillisecondTimer(TIMER_FREQUENCY)) ::Print(DFUN,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(string)::GetLastError()); #endif } //+------------------------------------------------------------------+
CEngineクラスのOnTimer()ハンドラーで、テスターでの作業を確認し、作業がテスターで実行されているかどうかに応じて、タイマーカウンターまたはティックのいずれかで動作します。
//+------------------------------------------------------------------+ //| CEngineタイマー | //+------------------------------------------------------------------+ void CEngine::OnTimer(void) { //--- Timer of historical orders, deals, market orders and positions collections int index=this.CounterIndex(COLLECTION_COUNTER_ID); if(index>WRONG_VALUE) { CTimerCounter* counter=this.m_list_counters.At(index); if(counter!=NULL) { //--- If this is not a tester if(!this.IsTester()) { //--- If unpaused, work with the collections events if(counter.IsTimeDone()) this.TradeEventsControl(); } //--- If this is a tester, work with collection events by tick else { this.TradeEventsControl(); } } } } //+------------------------------------------------------------------+
EAをコンパイルし、テスターで起動してボタンを試してください。
このメッセージは、ライブラリがいくつかのイベント(指値注文の設定と、注文およびポジションパラメータの変更)を確認していることを示しています。他のイベントは確認できません。
エラーに対処しましょう。
ライブラリの改善
最初に確認する必要があるのは、指値注文の削除がライブラリに表示されない理由です。すべてのイベントはイベントコレクションクラスのCEventsCollection::Refresh()メソッドで追跡されます。ここで興味があるのは口座履歴イベントです。メソッドに渡して、MQL5履歴注文と取引のコレクションの変更の追跡を担当するコードを見てみましょう。
} //--- If the event is in the account history if(is_history_event) { //--- If the number of historical orders increased if(new_history_orders>0) { //--- Receive the list of removed pending orders only CArrayObj* list=this.GetListHistoryPendings(list_history); if(list!=NULL) { Print(DFUN); //--- Sort the new list by order removal time list.Sort(SORT_BY_ORDER_TIME_CLOSE_MSC); //--- Take the number of orders equal to the number of newly removed ones from the end of the list in a loop (the last N events) int total=list.Total(), n=new_history_orders; for(int i=total-1; i>=0 && n>0; i--,n--) { //--- Receive an order from the list. If this is a removed pending order without a position ID, //--- this is an order removal - set a trading event COrder* order=list.At(i); if(order!=NULL && order.Status()==ORDER_STATUS_HISTORY_PENDING && order.PositionID()==0) this.CreateNewEvent(order,list_history,list_market); } } } //--- If the number of deals increased
ポジションIDを指定する注文プロパティは未記入(ゼロに等しい)です。正しいパッセージを見つけた後、MQL5で未決注文の削除(発動ではなく)を正確に識別するためにその機能を使用したことがわかります(MQL5では、注文が発動されて取引とポジションに至った場合、ポジションIDは注文の発動の結果として開かれたポジションのIDと等しくなります)。MQL4では、このフィールドには注文チケットがすぐに入力されますが、これは誤りです。
注文の抽象クラスのClosedコンストラクタに移動し、ポジション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_OPEN] = (long)(ulong)this.OrderOpenTime(); this.m_long_prop[ORDER_PROP_TIME_CLOSE] = (long)(ulong)this.OrderCloseTime(); this.m_long_prop[ORDER_PROP_TIME_EXP] = (long)(ulong)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_MSC] = this.OrderOpenTimeMSC(); this.m_long_prop[ORDER_PROP_TIME_CLOSE_MSC] = this.OrderCloseTimeMSC(); this.m_long_prop[ORDER_PROP_TIME_UPDATE] = (long)(ulong)this.PositionTimeUpdate(); this.m_long_prop[ORDER_PROP_TIME_UPDATE_MSC] = (long)(ulong)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_GROUP_ID] = 0; //--- 追加の実数型プロパティを保存する this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT_FULL)] = this.ProfitFull(); } //+------------------------------------------------------------------+
これはOrderPositionID()メソッドで行われます。お分かりのように、MQL4ではチケットは直ちにIDとして設定されます。
//+------------------------------------------------------------------+ //| ポジションIDを返す | //+------------------------------------------------------------------+ long COrder::OrderPositionID(void) const { #ifdef __MQL4__ return ::OrderTicket(); #else long res=0; switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS)) { case ORDER_STATUS_MARKET_POSITION : res=::PositionGetInteger(POSITION_IDENTIFIER); break; case ORDER_STATUS_MARKET_ORDER : case ORDER_STATUS_MARKET_PENDING : res=::OrderGetInteger(ORDER_POSITION_ID); break; case ORDER_STATUS_HISTORY_PENDING : case ORDER_STATUS_HISTORY_ORDER : res=::HistoryOrderGetInteger(m_ticket,ORDER_POSITION_ID); break; case ORDER_STATUS_DEAL : res=::HistoryDealGetInteger(m_ticket,DEAL_POSITION_ID); break; default : res=0; break; } return res; #endif } //+------------------------------------------------------------------+
最初は、そこに0を設定する必要があります(注文を削除するときにポジションはありません)。以下をご覧ください。
//+------------------------------------------------------------------+ //| ポジションIDを返す | //+------------------------------------------------------------------+ long COrder::OrderPositionID(void) const { #ifdef __MQL4__ return 0; #else long res=0; switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS)) { case ORDER_STATUS_MARKET_POSITION : res=::PositionGetInteger(POSITION_IDENTIFIER); break; case ORDER_STATUS_MARKET_ORDER : case ORDER_STATUS_MARKET_PENDING : res=::OrderGetInteger(ORDER_POSITION_ID); break; case ORDER_STATUS_HISTORY_PENDING : case ORDER_STATUS_HISTORY_ORDER : res=::HistoryOrderGetInteger(m_ticket,ORDER_POSITION_ID); break; case ORDER_STATUS_DEAL : res=::HistoryDealGetInteger(m_ticket,DEAL_POSITION_ID); break; default : res=0; break; } return res; #endif } //+------------------------------------------------------------------+
EAをコンパイルし、テスターで起動して、未決注文を設定および削除します。
ここで未決注文削除イベントが追跡されます。
未決注文の発動を待つと、このイベントは、単純なポジションが開かれる場合と同様、ライブラリには表示されないことが再びわかります。理由を定義しましょう。
覚えておいでのように、すべてがCEngineクラスのOnTimer()ハンドラから始まります。
//+------------------------------------------------------------------+ //| CEngineタイマー | //+------------------------------------------------------------------+ void CEngine::OnTimer(void) { //--- Timer of historical orders, deals, market orders and positions collections int index=this.CounterIndex(COLLECTION_COUNTER_ID); if(index>WRONG_VALUE) { CTimerCounter* counter=this.m_list_counters.At(index); if(counter!=NULL) { //--- If this is not a tester if(!this.IsTester()) { //--- If unpaused, work with the collections events if(counter.IsTimeDone()) this.TradeEventsControl(); } //--- If this is a tester, work with collection events by tick else { this.TradeEventsControl(); } } } } //+------------------------------------------------------------------+
コードによると、イベントはTradeEventsControl()メソッドで管理されます。どんなイベントの場合でも、イベントコレクションクラスのイベントを更新するメソッドであるCEventsCollection::Refresh()を呼び出します。
//+------------------------------------------------------------------+ //| Check trading events | //+------------------------------------------------------------------+ void CEngine::TradeEventsControl(void) { //--- Initialize the trading events code and flags this.m_is_market_trade_event=false; this.m_is_history_trade_event=false; //--- リストを更新する this.m_market.Refresh(); this.m_history.Refresh(); //--- 初回実行 if(this.IsFirstStart()) { this.m_acc_trade_event=TRADE_EVENT_NO_EVENT; return; } //--- Check the changes in the market status and account history this.m_is_market_trade_event=this.m_market.IsTradeEvent(); this.m_is_history_trade_event=this.m_history.IsTradeEvent(); //--- If there is any event, send the lists, the flags and the number of new orders and deals to the event collection, and update it int change_total=0; CArrayObj* list_changes=this.m_market.GetListChanges(); if(list_changes!=NULL) change_total=list_changes.Total(); if(this.m_is_history_trade_event || this.m_is_market_trade_event || change_total>0) { this.m_events.Refresh(this.m_history.GetList(),this.m_market.GetList(),list_changes, this.m_is_history_trade_event,this.m_is_market_trade_event, this.m_history.NewOrders(),this.m_market.NewPendingOrders(), this.m_market.NewMarketOrders(),this.m_history.NewDeals()); //--- Get the account's last trading event this.m_acc_trade_event=this.m_events.GetLastTradeEvent(); } } //+------------------------------------------------------------------+
ここで、履歴コレクションと市場コレクションのリスト、コレクション変更のフラグ、新しい過去の注文とアクティブな市場注文とポジションの数、新しい取引の数をメソッドに送信します。しかし、よく見ると、メソッドが受信するのは、新しい市場ポジションの数ではなくライブラリでまだ使用していない新しい市場での注文の数であることがわかります。これは私のエラーです。当初、すべてはMQL5用に開発されましたが、MQL4メソッド用に新しいポジションの数を送信する必要があります。MQL5では、新しいポジションは取引の数によって定義されます。MQL4メソッドに渡されたデータを入力すると、エラーが発生しました。これで、このメソッドが新しい市場ポジションを確認できない理由が明らかになりました。
これを修正して、ついでに別の問題を解決しましょう。MQL5とは異なり、MQL4はポジションに至った注文を見つけることができません。ただし、注文とポジションのプロパティの変更を追跡するための管理注文のリストは既にあります。この不要なデータのリストはまだクリアされていません。このリストは、ポジションに至った注文を追跡し、イベント(成行注文または未決注文の発動)を識別するのに役立ちます。
コントロール注文のリストを返すpublicメソッドを市場の注文とポジションのコレクション(MarketCollection.mqhファイルのCMarketCollectionクラス)に追加します。
public: //--- Return the list (1) of all pending orders and open positions, (2) control orders and positions CArrayObj* GetList(void) { return &this.m_list_all_orders; } CArrayObj* GetListChanges(void) { return &this.m_list_changed; } CArrayObj* GetListControl(void) { return &this.m_list_control; } //--- 開いた時間がbegin_time~end_timeの注文とポジションのリストを返す CArrayObj* GetListByTime(const datetime begin_time=0,const datetime end_time=0); //--- 比較された条件に適合する選択された(1)double、(2)integer、(3)文字列プロパティによって注文とポジションのリストを返す CArrayObj* GetList(ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); } CArrayObj* GetList(ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); } CArrayObj* GetList(ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); } //--- Return the number of (1) new market orders, (2) new pending orders, (3) new positions, (4) occurred trading event flag, (5) changed volume int NewMarketOrders(void) const { return this.m_new_market_orders; } int NewPendingOrders(void) const { return this.m_new_pendings; } int NewPositions(void) const { return this.m_new_positions; } bool IsTradeEvent(void) const { return this.m_is_trade_event; } double ChangedVolumeValue(void) const { return this.m_change_volume_value; } //--- コンストラクタ CMarketCollection(void); //--- 未決注文とポジションのリストを更新する void Refresh(void); }; //+------------------------------------------------------------------+リストのデータを使用するには、CEventsCollectionクラスのRefresh()メソッドに渡す必要があります。
これを行うには、上記の必要なすべての変更を記述します。
//+------------------------------------------------------------------+ //| Check trading events | //+------------------------------------------------------------------+ void CEngine::TradeEventsControl(void) { //--- Initialize the trading events code and flags this.m_is_market_trade_event=false; this.m_is_history_trade_event=false; //--- リストを更新する this.m_market.Refresh(); this.m_history.Refresh(); //--- 初回実行 if(this.IsFirstStart()) { this.m_acc_trade_event=TRADE_EVENT_NO_EVENT; return; } //--- Check the changes in the market status and account history this.m_is_market_trade_event=this.m_market.IsTradeEvent(); this.m_is_history_trade_event=this.m_history.IsTradeEvent(); //--- If there is any event, send the lists, the flags and the number of new orders and deals to the event collection, and update it int change_total=0; CArrayObj* list_changes=this.m_market.GetListChanges(); if(list_changes!=NULL) change_total=list_changes.Total(); if(this.m_is_history_trade_event || this.m_is_market_trade_event || change_total>0) { this.m_events.Refresh(this.m_history.GetList(),this.m_market.GetList(),list_changes,this.m_market.GetListControl(), this.m_is_history_trade_event,this.m_is_market_trade_event, this.m_history.NewOrders(),this.m_market.NewPendingOrders(), this.m_market.NewPositions(),this.m_history.NewDeals()); //--- Get the account's last trading event this.m_acc_trade_event=this.m_events.GetLastTradeEvent(); } } //+------------------------------------------------------------------+
ここで、CEngineクラスのTradeEventsControl()メソッドに、CEventsCollectionクラスのRefresh()メソッドにさらに別のリスト(管理注文のリスト)の受け渡しを追加し、メソッドには誤って新しい市場の注文数が渡されるかわりに新しいポジション数が渡されます。
CEventsCollectionクラス本体のRefresh()メソッドの定義を修正しましょう。
public: //--- Select events from the collection with time within the range from begin_time to end_time CArrayObj *GetListByTime(const datetime begin_time=0,const datetime end_time=0); //--- Return the full event collection list "as is" CArrayObj *GetList(void) { return &this.m_list_events; } //--- 比較された基準を満たす選択された(1)整数、(2)実数、(3)文字列プロパティののリストを返す CArrayObj *GetList(ENUM_EVENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode); } CArrayObj *GetList(ENUM_EVENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode); } CArrayObj *GetList(ENUM_EVENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode); } //--- Update the list of events void Refresh(CArrayObj* list_history, CArrayObj* list_market, CArrayObj* list_changes, CArrayObj* list_control, const bool is_history_event, const bool is_market_event, const int new_history_orders, const int new_market_pendings, const int new_market_positions, const int new_deals); //--- Set the control program chart ID void SetChartID(const long id) { this.m_chart_id=id; } //--- Return the last trading event on the account ENUM_TRADE_EVENT GetLastTradeEvent(void) const { return this.m_trade_event; } //--- 直近の取引イベントをリセットする void ResetLastTradeEvent(void) { this.m_trade_event=TRADE_EVENT_NO_EVENT; } //--- コンストラクタ CEventsCollection(void); }; //+------------------------------------------------------------------+
クラス本体の外部の実装も修正します。
//+------------------------------------------------------------------+ //| Update the event list | //+------------------------------------------------------------------+ void CEventsCollection::Refresh(CArrayObj* list_history, CArrayObj* list_market, CArrayObj* list_changes, CArrayObj* list_control, const bool is_history_event, const bool is_market_event, const int new_history_orders, const int new_market_pendings, const int new_market_positions, const int new_deals) {
イベントコレクションクラスのイベントのイベントリストを更新するメソッドには、MQL4のポジションを開くイベントの処理がまだありません。 このために、いくつかのメソッドが必要になります。
ポジションのリストを取得するには、それを取得する方法が必要です。また、注文のタイプを定義するために管理注文のリストを使用するメソッドがありません。
また、管理注文のリストにある発注タイプとポジションIDを保存するために、2つのprivateクラスメンバーが必要です。
タイプとIDは、MQL4の市場ポジションオープンイベントを処理するためのコードブロックで定義されます。
これらをprivateクラスセクションに追加します。
//+------------------------------------------------------------------+ //| Collection of account events | //+------------------------------------------------------------------+ class CEventsCollection : public CListObj { private: CListObj m_list_events; // List of events bool m_is_hedge; // Hedge account flag long m_chart_id; // Control program chart ID int m_trade_event_code; // Trading event code ENUM_TRADE_EVENT m_trade_event; // Account trading event CEvent m_event_instance; // Event object for searching by property MqlTick m_tick; // Last tick structure ulong m_position_id; // Position ID (MQL4) ENUM_ORDER_TYPE m_type_first; // Opening order type (MQL4) //--- Create a trading event depending on the order (1) status and (2) change type void CreateNewEvent(COrder* order,CArrayObj* list_history,CArrayObj* list_market); void CreateNewEvent(COrderControl* order); //--- Create an event for a (1) hedging account, (2) netting account void NewDealEventHedge(COrder* deal,CArrayObj* list_history,CArrayObj* list_market); void NewDealEventNetto(COrder* deal,CArrayObj* list_history,CArrayObj* list_market); //--- Select from the list and return the list of (1) market pending orders, (2) open positions CArrayObj* GetListMarketPendings(CArrayObj* list); CArrayObj* GetListPositions(CArrayObj* list); //--- Select from the list and return the list of historical (1) removed pending orders, (2) deals, (3) all closing orders CArrayObj* GetListHistoryPendings(CArrayObj* list); CArrayObj* GetListDeals(CArrayObj* list); CArrayObj* GetListCloseByOrders(CArrayObj* list); //--- Return the list of (1) all position orders by its ID, (2) all deal positions by its ID //--- (3) all market entry deals by position ID, (4) all market exit deals by position ID, //--- (5) all position reversal deals by position ID CArrayObj* GetListAllOrdersByPosID(CArrayObj* list,const ulong position_id); CArrayObj* GetListAllDealsByPosID(CArrayObj* list,const ulong position_id); CArrayObj* GetListAllDealsInByPosID(CArrayObj* list,const ulong position_id); CArrayObj* GetListAllDealsOutByPosID(CArrayObj* list,const ulong position_id); CArrayObj* GetListAllDealsInOutByPosID(CArrayObj* list,const ulong position_id); //--- Return the total volume of all deals (1) IN, (2) OUT of the position by its ID double SummaryVolumeDealsInByPosID(CArrayObj* list,const ulong position_id); double SummaryVolumeDealsOutByPosID(CArrayObj* list,const ulong position_id); //--- Return the (1) first, (2) last and (3) closing order from the list of all position orders, //--- (4) an order by ticket, (5) market position by ID, //--- (6) the last and (7) penultimate InOut deal by position ID COrder* GetFirstOrderFromList(CArrayObj* list,const ulong position_id); COrder* GetLastOrderFromList(CArrayObj* list,const ulong position_id); COrder* GetCloseByOrderFromList(CArrayObj* list,const ulong position_id); COrder* GetHistoryOrderByTicket(CArrayObj* list,const ulong order_ticket); COrder* GetPositionByID(CArrayObj* list,const ulong position_id); //--- Return the type of the opening order by the position ticket (MQL4) ENUM_ORDER_TYPE GetTypeFirst(CArrayObj* list,const ulong ticket); //--- Return the flag of the event object presence in the event list bool IsPresentEventInList(CEvent* compared_event); //--- Existing order/position change event handler void OnChangeEvent(CArrayObj* list_changes,const int index); public:
クラス本体外でポジションのリストを受け取るためのメソッドを実装します。
//+------------------------------------------------------------------+ //| Select only market positions from the list | //+------------------------------------------------------------------+ CArrayObj* CEventsCollection::GetListPositions(CArrayObj *list) { if(list.Type()!=COLLECTION_MARKET_ID) { Print(DFUN,TextByLanguage("Ошибка. Список не является списком рыночной коллекции","Error. The list is not a list of the market collection")); return NULL; } CArrayObj* list_positions=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_POSITION,EQUAL); return list_positions; } //+------------------------------------------------------------------+
市場の注文とポジションの完全なリストはメソッドに渡され、 「成行ポジション」ステータスで並び替えられます。呼び出し側プログラムに結果のリストが返されます。
ポジションに至った注文のタイプを返すメソッドを作成しましょう。
//+------------------------------------------------------------------+ //| Return the type of an opening order by position ticket (MQL4) | //+------------------------------------------------------------------+ ENUM_ORDER_TYPE CEventsCollection::GetTypeFirst(CArrayObj* list,const ulong ticket) { if(list==NULL) return WRONG_VALUE; int total=list.Total(); for(int i=0;i<total;i++) { COrderControl* ctrl=list.At(i); if(ctrl==NULL) continue; if(ctrl.Ticket()==ticket) return (ENUM_ORDER_TYPE)ctrl.TypeOrder(); } return WRONG_VALUE; } //+------------------------------------------------------------------+
管理注文のリストと新しく開かれたポジションがメソッドに渡されます。次に、リストの先頭からループで(未決注文が他のポジションの前に配置され、そのチケットが先に来ると仮定)リストから管理注文を取得してチケットを関数に渡されたチケットと比較します。チケットが見つかったら、この注文は、チケットがメソッドに渡されたポジションを開いた注文なので、注文タイプを返します。そのようなチケットを持つ注文が見つからない場合は、-1を返します。
ここで、MQL4でポジションでイベントの処理を改善できます。
イベントリスト更新メソッドにMQL4の「ポジションを開く」イベントの処理を追加します。
//+------------------------------------------------------------------+ //| Update the event list | //+------------------------------------------------------------------+ void CEventsCollection::Refresh(CArrayObj* list_history, CArrayObj* list_market, CArrayObj* list_changes, CArrayObj* list_control, const bool is_history_event, const bool is_market_event, const int new_history_orders, const int new_market_pendings, const int new_market_positions, const int new_deals) { //--- Exit if the lists are empty if(list_history==NULL || list_market==NULL) return; //--- If the event is in the market environment if(is_market_event) { //--- if the order properties were changed int total_changes=list_changes.Total(); if(total_changes>0) { for(int i=total_changes-1;i>=0;i--) { this.OnChangeEvent(list_changes,i); } } //--- if the number of placed pending orders increased if(new_market_pendings>0) { //--- Receive the list of the newly placed pending orders CArrayObj* list=this.GetListMarketPendings(list_market); if(list!=NULL) { //--- Sort the new list by order placement time list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); //--- Take the number of orders equal to the number of newly placed ones from the end of the list in a loop (the last N events) int total=list.Total(), n=new_market_pendings; for(int i=total-1; i>=0 && n>0; i--,n--) { //--- Receive an order from the list, if this is a pending order, set a trading event COrder* order=list.At(i); if(order!=NULL && order.Status()==ORDER_STATUS_MARKET_PENDING) this.CreateNewEvent(order,list_history,list_market); } } } #ifdef __MQL4__ //--- If the number of positions increased if(new_market_positions>0) { //--- Get the list of open positions CArrayObj* list=this.GetListPositions(list_market); if(list!=NULL) { //--- Sort the new list by a position open time list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); //--- Take the number of positions equal to the number of newly placed open positions from the end of the list in a loop (the last N events) int total=list.Total(), n=new_market_positions; for(int i=total-1; i>=0 && n>0; i--,n--) { //--- Receive a position from the list. If this is a position, search for opening order data and set a trading event COrder* position=list.At(i); if(position!=NULL && position.Status()==ORDER_STATUS_MARKET_POSITION) { //--- Find an order and set (1) a type of an order that led to opening a position and a (2) position ID this.m_type_first=this.GetTypeFirst(list_control,position.Ticket()); this.m_position_id=position.Ticket(); this.CreateNewEvent(position,list_history,list_market); } } } } #endif } //--- If the event is in the account history if(is_history_event) { //--- If the number of historical orders increased if(new_history_orders>0) { //--- Receive the list of removed pending orders only CArrayObj* list=this.GetListHistoryPendings(list_history); if(list!=NULL) { //--- Sort the new list by order removal time list.Sort(SORT_BY_ORDER_TIME_CLOSE_MSC); //--- Take the number of orders equal to the number of newly removed pending ones from the end of the list in a loop (the last N events) int total=list.Total(), n=new_history_orders; for(int i=total-1; i>=0 && n>0; i--,n--) { //--- Receive an order from the list. これがポジションIDなしの削除された未決注文の場合、 //--- this is an order removal - set a trading event COrder* order=list.At(i); if(order!=NULL && order.Status()==ORDER_STATUS_HISTORY_PENDING && order.PositionID()==0) this.CreateNewEvent(order,list_history,list_market); } } } //--- If the number of deals increased if(new_deals>0) { //--- Receive the list of deals only CArrayObj* list=this.GetListDeals(list_history); if(list!=NULL) { //--- Sort the new list by deal time list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); //--- Take the number of deals equal to the number of new ones from the end of the list in a loop (the last N events) int total=list.Total(), n=new_deals; for(int i=total-1; i>=0 && n>0; i--,n--) { //--- Receive a deal from the list and set a trading event COrder* order=list.At(i); if(order!=NULL) this.CreateNewEvent(order,list_history,list_market); } } } } } //+------------------------------------------------------------------+
MQL4で新しいポジションの開始を処理したり、未決注文を発動したりするためのすべてのアクションはコードのコメントに記述されており、追加の説明は不要です。
次に、新しいイベントを作成するためのCEventsCollection::CreateNewEvent()メソッドに移動し、MQL4の「ポジションを開く」イベントを作成するコードブロックを見つけます(ブロックの開始はコードのコメントで示されています)。「ポジションを開く」イベントの定義と開かれた理由を補完し、ポジションデータに適切な注文のデータおよびポジション
IDを追加します。
//--- Position opened (__MQL4__) if(status==ORDER_STATUS_MARKET_POSITION) { //--- Set the "position opened" trading event code this.m_trade_event_code=TRADE_EVENT_FLAG_POSITION_OPENED; //--- Set the "request executed partially" reason ENUM_EVENT_REASON reason=EVENT_REASON_DONE; //--- If an opening order is a pending one if(this.m_type_first>ORDER_TYPE_SELL && this.m_type_first<ORDER_TYPE_BALANCE) { //--- set the "pending order activated" reason reason=EVENT_REASON_ACTIVATED_PENDING; //--- add a pending order activation flag to the event code this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_ACTIVATED; } CEvent* event=new CEventPositionOpen(this.m_trade_event_code,order.Ticket()); if(event!=NULL) { event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpenMSC()); // Event time event.SetProperty(EVENT_PROP_REASON_EVENT,reason); // Event reason (from the ENUM_EVENT_REASON enumeration) event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,this.m_type_first); // Event deal type event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket()); // Event deal ticket event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,this.m_type_first); // Type of the order that triggered an event deal (the last position order) event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,this.m_type_first); // Type of an order that triggered a position deal (the first position order) event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket()); // Ticket of an order, based on which a deal event is opened (the last position order) event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket()); // Ticket of an order, based on which a position event is opened (the first position order) event.SetProperty(EVENT_PROP_POSITION_ID,this.m_position_id); // Position ID event.SetProperty(EVENT_PROP_POSITION_BY_ID,order.PositionByID()); // Opposite position ID event.SetProperty(EVENT_PROP_MAGIC_BY_ID,0); // Opposite position magic number event.SetProperty(EVENT_PROP_TYPE_ORD_POS_BEFORE,order.TypeOrder()); // Position order type before direction changed event.SetProperty(EVENT_PROP_TICKET_ORD_POS_BEFORE,order.Ticket()); // Position order ticket before direction changed event.SetProperty(EVENT_PROP_TYPE_ORD_POS_CURRENT,order.TypeOrder()); // Current position order type event.SetProperty(EVENT_PROP_TICKET_ORD_POS_CURRENT,order.Ticket()); // Current position order ticket event.SetProperty(EVENT_PROP_PRICE_OPEN_BEFORE,order.PriceOpen()); // Order price before modification event.SetProperty(EVENT_PROP_PRICE_SL_BEFORE,order.StopLoss()); // StopLoss before modification event.SetProperty(EVENT_PROP_PRICE_TP_BEFORE,order.TakeProfit()); // TakeProfit before modification event.SetProperty(EVENT_PROP_PRICE_EVENT_ASK,this.m_tick.ask); // Ask price during an event event.SetProperty(EVENT_PROP_PRICE_EVENT_BID,this.m_tick.bid); // Bid price during an event event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic()); // Order/deal/position magic number event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimeOpenMSC()); // Time of an order, based on which a position deal is opened (the first position order) event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen()); // Event price event.SetProperty(EVENT_PROP_PRICE_OPEN,order.PriceOpen()); // Order/deal/position open price event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose()); // Order/deal/position close price event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss()); // StopLoss position price event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit()); // TakeProfit position price event.SetProperty(EVENT_PROP_VOLUME_ORDER_INITIAL,order.Volume()); // Requested order volume event.SetProperty(EVENT_PROP_VOLUME_ORDER_EXECUTED,order.Volume()-order.VolumeCurrent()); // Executed order volume event.SetProperty(EVENT_PROP_VOLUME_ORDER_CURRENT,order.VolumeCurrent()); // Remaining (unexecuted) order volume event.SetProperty(EVENT_PROP_VOLUME_POSITION_EXECUTED,order.Volume()); // Executed position volume event.SetProperty(EVENT_PROP_PROFIT,order.Profit()); // Profit event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol()); // Order symbol event.SetProperty(EVENT_PROP_SYMBOL_BY_ID,order.Symbol()); // Opposite position symbol //--- Set control program chart ID, decode the event code and set the event type event.SetChartID(this.m_chart_id); event.SetTypeEvent(); //--- Add the event object if it is not in the list if(!this.IsPresentEventInList(event)) { this.m_list_events.InsertSort(event); //--- Send a message about the event and set the value of the last trading event event.SendEvent(); this.m_trade_event=event.TradeEvent(); } //--- If the event is already present in the list, remove a new event object and display a debugging message else { ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event is already in the list.")); delete event; } } } //--- New deal (__MQL5__)
すべての変更を行った後、ライブラリはMQL4注文のポジションの開始と発動を「確認」できる必要があります。
テスト
適用された変更を確認しましょう。TestDoEasyPart10.mq4をコンパイルし、テスターで起動し、ポジションを開始および決済し、未決注文を出して、それらのいずれかが発動するまで待機します。ストップレベルとトレーリングがアクティブになっていることを確認します(ポジションと未決注文を変更します)。ライブラリがMQL4用に「確認」したすべてのイベントは、テスターの操作ログに表示されます。
テスターの操作ログを注意深く観察すると、ライブラリはまだ終値を確認できないことがわかります。BuyLimit#3の未決注文が発動すると、操作ログのエントリは[BuyLimit #3]が発動して、Buy #3ポジションにつながったことを通知します。これで、ライブラリは未決注文の発動イベントを確認し、ポジションの発信元の注文を認識できます。また、変更機能にはわずかな欠落が見られます。トレーリングで変更されたBuyStop #1未決注文のラベルが赤になります。ただし、ライブラリはすべての注文およびポジションの変更イベントを確認します。
DELib.mqhファイルでテスターのMQL4取引関数に修正を追加します。渡された未決注文のタイプに応じて買い/売りポジションタイプを返す別の関数を作成し、注文タイプの矢印の色を選択するための文字列で注文タイプのチェックを注文タイプの方向によるチェックに置き換えます。
//+------------------------------------------------------------------+ //| Modifying a pending order by ticket | //+------------------------------------------------------------------+ bool PendingOrderModify(const ulong ticket,const double price_set,const double sl,const double tp) { ResetLastError(); if(!OrderSelect((int)ticket,SELECT_BY_TICKET)) { Print(DFUN,TextByLanguage("Не удалось выбрать ордер. Ошибка ","Could not select order. Error "),(string)GetLastError()); return false; } ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType(); if(type<ORDER_TYPE_BUY_LIMIT || type>ORDER_TYPE_SELL_STOP) { Print(DFUN,TextByLanguage("Ошибка. Не ордер: ","Error. Not order: "),PositionTypeDescription((ENUM_POSITION_TYPE)type)," #",ticket); return false; } if(OrderCloseTime()>0) { Print(DFUN,TextByLanguage("Ошибка. Для модификации выбран удалённый ордер: ","Error. Deleted order selected for modification: "),OrderTypeDescription(type)," #",ticket); return false; } color clr=(TypeByPendingDirection(type)==ORDER_TYPE_BUY ? clrBlue : clrRed); ResetLastError(); if(!OrderModify((int)ticket,price_set,sl,tp,0,clr)) { Print(DFUN,TextByLanguage("Не удалось модифицировать ордер. Ошибка ","Failed to modify order. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Return the type by a pending order direction | //+------------------------------------------------------------------+ ENUM_ORDER_TYPE TypeByPendingDirection(const ENUM_ORDER_TYPE type) { if(type==ORDER_TYPE_BUY_LIMIT || type==ORDER_TYPE_BUY_STOP) return ORDER_TYPE_BUY; if(type==ORDER_TYPE_SELL_LIMIT || type==ORDER_TYPE_SELL_STOP) return ORDER_TYPE_SELL; return WRONG_VALUE; } //+------------------------------------------------------------------+
次の段階
次の記事では、ポジション決済の追跡を実装し、現在のバージョンでMQL4の追跡イベントに発生する可能性のあるエラーを修正します。現在、発注と注文の削除はMQL5コードによって追跡されており、MQL4で作業する際に考慮すべき微妙な違いがある場合があります。
現在のバージョンのライブラリのすべてのファイルは、テスト用EAファイルと一緒に以下に添付されているので、テストするにはダウンロードしてください。
質問、コメント、提案はコメント欄にお願いします。
シリーズのこれまでの記事:
第1部: 概念、データ管理
第2部:
過去の注文と取引のコレクション
第3部:注文と取引のコレクション、検索と並び替え
第4部:
取引イベント概念
第5部: 取引イベントのクラスとコレクション取引イベントのプログラムへの送信
第6部:
ネッティング勘定イベント
第7部: StopLimit注文アクティブ化イベント。注文イベントと位置変更イベントの機能を準備します。
第8部:
注文とポジションの変更イベント。第9部:MQL4との互換性 - データの準備
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/6767
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索