内容
- 测试 EA
- 改进函数库
- 测试
- 下一步是什么?
测试 EA
在上一篇文章中,我们剔除了 MQL4 和 MQL5 之间存在差异的相关库文件中的错误,并引入了 MQL4 历史订单和仓位的集合。 在本文中,我们将继续在函数库中合并 MQL4 和 MQL5,并定义开仓和激活挂单的事件。
改进步骤的顺序将会颠倒。 以前,我们介绍了测试 EA 所遵循的功能。 现在,为了理解需要改进的内容,我们需要启动测试 EA,并查看其工作原理以及在何处不能工作。 那些不起作用的部分就是需要改进的。
为实现此目的,我们从 \MQL5\Experts\TestDoEasy\Part08 文件夹里取出函数库论述第八部分中的测试 EA TestDoEasyPart08.mq5,并将其保存在 MetaTrader 4 文件夹\MQL4\Experts\TestDoEasy\Part10 下,命名为 TestDoEasyPart10.mq4。
我们试着编译它。 这最终导致 34 个编译错误。 几乎所有这些都与 MQL4 标准库中缺少交易类有关:
我们转到第一个错误,提示缺少包含文件
并且修复它— 该文件仅针对 MQL5 才会包含:
//+------------------------------------------------------------------+ //| TestDoEasyPart08.mq5 | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //--- includes #include <DoEasy\Engine.mqh> #ifdef __MQL5__ #include <Trade\Trade.mqh> #endif //--- enums
编译结束时会有 33 个错误。 再次移动到第一个错误,其提示在声明 CTrade 交易类对象时缺少类型 — 它在 MQL4 中不存在。
我们像以前一样使用条件编译指令:
//--- global variables CEngine engine; #ifdef __MQL5__ CTrade trade; #endif SDataButt butt_data[TOTAL_BUTT];
编译。 现在,CTrade 类的“交易”对象已经为 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 指令将所有交易对象实例分支到整个 EA 代码中的条件编译指令 — MQL4 代码将会放于此处。 编辑和编辑前述的内容之后,我们利用未知交易类型的第一个错误:
//--- If the BUTT_BUY button is pressed: Open Buy position if(button==EnumToString(BUTT_BUY)) { //--- Get the correct StopLoss and TakeProfit prices relative to StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit); //--- Open Buy position #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 );
其中第一个参数是 图表 ID(0 - 当前),
而在 MQL4 中,该函数现在有两种调用形式。 第一个与 MQL5 的相同:
int ObjectsTotal( long chart_id, // chart ID int sub_window=-1, // window index int type=-1 // object type );
而第二个已过时,只有一个参数:
int ObjectsTotal( int type=EMPTY // object type );
在 MQL5 中,将 0 作为图表 ID 传递给函数(当前图表)不会引起任何冲突和疑虑,但在 MQL4 中,编译器会根据所传递的参数来定义调用类型。 在这种情况下,它无法准确定义我们所传递的是否为当前图表 ID(0),以及应否调用第一种形式(毕竟,其他两个参数设置为其默认值,其意味着调用函数时我们不必为它们传递数值),或者我们传递窗口索引(或对象类型),此时应调用第二种形式。
此处的解决方案很简单 — 传递子窗口索引(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 交易函数调用的代码段。
实现调用 MQL4 交易函数,必须从 PressButtonEvents() 函数开始处理按钮按下事件,并持续到清单结尾。 代码非常庞大,而必要函数的选择是毋庸置疑的。 所以,我不会在这里出示代码。 您可以在文章附带的文件中找到它。 我们只查看一下按下两个按钮的处理 — 用于开多头持仓的按钮和用于放置 BuyLimit 挂单的按钮:
//+------------------------------------------------------------------+ //| Handle pressing the buttons | //+------------------------------------------------------------------+ void PressButtonEvents(const string button_name) { //--- Convert the button name into its string ID string button=StringSubstr(button_name,StringLen(prefix)); //--- If the button is pressed if(ButtonState(button_name)) { //--- If the BUTT_BUY button is pressed: Open Buy position if(button==EnumToString(BUTT_BUY)) { //--- Get the correct StopLoss and TakeProfit prices relative to StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit); //--- Open Buy position #ifdef __MQL5__ trade.Buy(lot,Symbol(),0,sl,tp); #else Buy(lot,Symbol(),magic_number,sl,tp); #endif } //--- If the BUTT_BUY_LIMIT button is pressed: Set BuyLimit else if(button==EnumToString(BUTT_BUY_LIMIT)) { //--- Get the correct order placement price relative to StopLevel double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending); //--- Get the correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,takeprofit); //--- Set BuyLimit order #ifdef __MQL5__ trade.BuyLimit(lot,price_set,Symbol(),sl,tp); #else BuyLimit(lot,price_set,Symbol(),magic_number,sl,tp); #endif } //--- If the BUTT_BUY_STOP button is pressed: Set BuyStop
在测试函数库代码时,我注意到一些奇怪的事情:MQL4 在没有改进代码的情况下,会隔一段时间后才能在日志中显示看到的事件。 在深入研究此事后,我意识到原因是由 CEngine 计时器中工作的计时器集合的计数器所导致。 在函数库论述的第三部分中,我们开发了计时器集合计数器,在创建函数库基本对象时,曾为其设置了最小延迟 16 毫秒。 然而,由于我们未曾使用测试器中的计时器,并直接从 OnTick() 调用 OnTimer() 函数库响应程序来依据即时报价操作,因此 16 毫秒的延迟转变为延迟 16 笔即时报价。 为了修复这个问题,我稍微修改了 CEngine 类,引入了返回测试器标志的方法,并在 OnTimer() 响应程序中处理测试器中的工作,然后在测试器中工作时从 EA 的OnTick() 调用。
修改则是创建了一个类私有成员变量,以及返回变量值的方法:
//+------------------------------------------------------------------+ //| Library basis class | //+------------------------------------------------------------------+ class CEngine : public CObject { private: CHistoryCollection m_history; // Collection of historical orders and deals CMarketCollection m_market; // Collection of market orders and deals CEventsCollection m_events; // Collection of events CArrayObj m_list_counters; // List of timer counters bool m_first_start; // First launch flag bool m_is_hedge; // Hedge account flag 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; // Account trading event //--- Return counter index by id public: //--- Return the list of market (1) positions, (2) pending orders and (3) market orders CArrayObj* GetListMarketPosition(void); CArrayObj* GetListMarketPendings(void); CArrayObj* GetListMarketOrders(void); //--- Return the list of historical (1) orders, (2) removed pending orders, (3) deals, (4) all position market orders by its id CArrayObj* GetListHistoryOrders(void); CArrayObj* GetListHistoryPendings(void); CArrayObj* GetListDeals(void); CArrayObj* GetListAllOrdersByPosID(const ulong position_id); //--- Reset the last trading event 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; } //--- Create the timer counter void CreateCounter(const int id,const ulong frequency,const ulong pause); //--- Timer void OnTimer(void); //--- Constructor/destructor CEngine(); ~CEngine(); }; //+------------------------------------------------------------------+
此测试器标志变量的值则是在类构造函数中设置:
//+------------------------------------------------------------------+ //| CEngine constructor | //+------------------------------------------------------------------+ 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 timer | //+------------------------------------------------------------------+ 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 中,此字段则立即填写上订单票据,而这是不正确的。
转到抽象订单的平仓类构造函数,然后找到包含仓位 ID 的订单属性代码:
//+------------------------------------------------------------------+ //| Closed parametric constructor | //+------------------------------------------------------------------+ COrder::COrder(ENUM_ORDER_STATUS order_status,const ulong ticket) { //--- Save integer properties 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(); //--- Save real properties 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(); //--- Save string properties 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(); //--- Save additional integer properties 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; //--- Save additional real properties this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT_FULL)] = this.ProfitFull(); } //+------------------------------------------------------------------+
这是由 OrderPositionID() 方法完成的。 正如我们所见,在 MQL4 中,立即将票证设置为 ID :
//+------------------------------------------------------------------+ //| Return position 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(删除挂单时没有开仓)。 这就是我们所做的工作:
//+------------------------------------------------------------------+ //| Return position 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 timer | //+------------------------------------------------------------------+ 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; //--- Update the lists this.m_market.Refresh(); this.m_history.Refresh(); //--- First launch actions 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 无法找到导致开仓的订单。 但是,我们已有了一个控制订单列表,用于跟踪订单和仓位属性的变化。 我们尚未清除此不必要数据列表。 此列表将帮助我们跟踪导致开仓的订单并辨别事件 — 市价订单或挂单激活。
将 返回控制订单列表的公共方法加入在场订单和持仓集合(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; } //--- Return the list of orders and positions with an open time from begin_time to end_time CArrayObj* GetListByTime(const datetime begin_time=0,const datetime end_time=0); //--- Return the list of orders and positions by selected (1) double, (2) integer and (3) string property fitting a compared condition 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; } //--- Constructor CMarketCollection(void); //--- Update the list of pending orders and positions 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; //--- Update the lists this.m_market.Refresh(); this.m_history.Refresh(); //--- First launch actions 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; } //--- Return the list by selected (1) integer, (2) real and (3) string properties meeting the compared criterion 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; } //--- Reset the last trading event void ResetLastTradeEvent(void) { this.m_trade_event=TRADE_EVENT_NO_EVENT; } //--- Constructor 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。 类型和 ID 将在代码块中定义,用于处理 MQL4 的开仓事件。
将它们添加到类的私有部分:
//+------------------------------------------------------------------+ //| 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. 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 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 挂单的标签变为红色。 但函数库会看到所有订单和持仓修改事件。
所有测试器针对 MQL4 交易函数的调整均要添加到 DELib.mqh 文件中 。 我们创建另一个函数,它根据传递给它的挂单类型返回买/卖位置类型,并在选择箭头颜色的代码里用直接检查订单类型替代检查订单类型:
//+------------------------------------------------------------------+ //| 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 文件,供您测试和下载。
在评论中留下您的问题、意见和建议。
返回目录
系列中的前几篇文章:
第一部分 概念,数据管理。
第二部分 历史订单和成交集合。
第三部分 在场订单和持仓集合,安排搜索。
第四部分 交易事件。 概念。
第五部分 交易事件类和集合。 将事件发送给程序。
第六部分 净持帐户事件。
第七部分 StopLimit 挂单激活事件,为订单和持仓修改事件准备功能。
第八部分 订单和持仓修改事件。
第九部分 与 MQL4 的兼容性 - 准备数据。