内容
- 概念
- 实现
- 测试
- 下一步是什么?
概念
在之前致力于 MetaTrader
5 和 MetaTrader 4 的跨平台函数库的部分中,我们开发了用于创建用例函数的工具,可以从程序快速访问任何有关对冲/净持结算账户的订单和仓位数据。 这些函数用于跟踪订单和持仓发生的事件 — 下单,移除和激活挂单,以及开仓和平仓。
然而,用于跟踪已下的 StopLimit 挂单的激活,以及在场订单和持仓的修改功能尚未实现。
在本文中,我们将实现跟踪 StopLimit 挂单激活事件,该事件会导致设置限价挂单。
函数库将跟踪此类事件,并将必要的消息发送给程序,以便可以进一步利用事件。
实现
在测试 StopLimit 挂单的激活时,我注意到此事件未反映在帐户历史记录中,这意味着它不能简单地从“帐户历史记录”中“按原样”获取。
因此,我们需要跟踪现存订单的状态,直到它发生变化为止(在我们的例子中,这意味着使用相同的票据修改已下挂单的类型)。
我将从实际角度处理 StopLimit 挂单激活跟踪的实现。 除了开发所需的功能外,我将通过现有订单和持仓的变化(更改现有挂单的价格,止损和止盈价位,以及持仓的相同同等价位)令其跟踪其他事件。
准备好的功能逻辑如下:
我们可以访问帐户中所有有效订单和持仓的完整列表。 该列表还允许我们获取每个对象属性的当前状态。 若要跟踪受监控属性的变化,我们需要有一个附加列表,其中包含属性的“过去”状态,该状态在最初时将等于当前属性。
比较这两个列表中的对象属性时,只要检测到任何受监控属性的差异,就会认为属性已变化。 在这种情况下,立即创建“已变化”对象。 过去和已变化的属性都写入其中,对象将放置到新列表 — “已变化对象列表”。
然后,该列表将在跟踪帐户事件的类中处理。
当然,我们可以在检测到对象属性的变化后立即发送事件,但是我们可能会在一次即时报价中遇到多个对象变化的情况。
如果我们立即处理变化,我们只能处理封包中最后一个对象的更改,这是不可接受的。 这意味着我们应该创建所有已更改对象的列表,并在事件处理类中检查列表的大小。 在循环中处理已变化对象列表中的每个已变化对象。
这可以令我们避免在订单和仓位属性中丢失一些同时发生的变化。
在函数库论述的第三部分创建在场订单和持仓集合当中,我们决定更新列表,并存储已计算的当前和之前的哈希值作为票据,再加上仓位更改时间(以毫秒为单位)和 交易量。 这令我们能够不断跟踪订单和持仓的当前状态。 不过,为了跟踪订单和持仓属性的变化,这些数据还不足以计算哈希值。
- 我们需要研究导致订单价格变化的价格
- 我们还需要参考止损和止盈价位的变化。
这意味着,我们要将这三个价格添加到哈希值当中,简单地删除小数点,并增加单一订单的数字容量(考虑六位数报价),每个价格都会转换为七位数的 ulong 数值。 例如,如果价格为 1.12345,则哈希值为 1123450。
我们开始实现。
在 Defines.mqh 文件中添加枚举,包括可能的仓位和订单变化选项的标记,以及要跟踪的选项本身:
//+------------------------------------------------------------------+ //| List of flags of possible order and position change options | //+------------------------------------------------------------------+ enum ENUM_CHANGE_TYPE_FLAGS { CHANGE_TYPE_FLAG_NO_CHANGE = 0, // No changes CHANGE_TYPE_FLAG_TYPE = 1, // Order type change CHANGE_TYPE_FLAG_PRICE = 2, // Price change CHANGE_TYPE_FLAG_STOP = 4, // StopLoss change CHANGE_TYPE_FLAG_TAKE = 8, // TakeProfit change CHANGE_TYPE_FLAG_ORDER = 16 // Order properties change flag }; //+------------------------------------------------------------------+ //| Possible order and position change options | //+------------------------------------------------------------------+ enum ENUM_CHANGE_TYPE { CHANGE_TYPE_NO_CHANGE, // No changes CHANGE_TYPE_ORDER_TYPE, // Order type change CHANGE_TYPE_ORDER_PRICE, // Order price change CHANGE_TYPE_ORDER_PRICE_STOP_LOSS, // Order and StopLoss price change CHANGE_TYPE_ORDER_PRICE_TAKE_PROFIT, // Order and TakeProfit price change CHANGE_TYPE_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT, // Order, StopLoss and TakeProfit price change CHANGE_TYPE_ORDER_STOP_LOSS_TAKE_PROFIT, // StopLoss and TakeProfit change CHANGE_TYPE_ORDER_STOP_LOSS, // Order's StopLoss change CHANGE_TYPE_ORDER_TAKE_PROFIT, // Order's TakeProfit change CHANGE_TYPE_POSITION_STOP_LOSS_TAKE_PROFIT, // Change position's StopLoss and TakeProfit CHANGE_TYPE_POSITION_STOP_LOSS, // Change position's StopLoss CHANGE_TYPE_POSITION_TAKE_PROFIT, // Change position's TakeProfit }; //+------------------------------------------------------------------+
至于可能的订单和仓位属性变化选项的标志:
- 激活 StopLimit 挂单时设置 订单类型变化标志,
- 修改挂单价格时会设置价格变化标志,
- 止损和止盈变化标志不言自明,
- 订单标志用于标识订单(非仓位)属性变化
所有可能的订单和仓位变化选项的枚举涵盖我们将来要跟踪的所有选项。 在本文中,我们将仅实现对 StopLimit
挂单激活事件(CHANGE_TYPE_ORDER_TYPE)的跟踪。
将八个新事件(在识别期间发送到程序)添加到可能的帐户交易事件列表的 ENUM_TRADE_EVENT 枚举中:
//+------------------------------------------------------------------+ //| List of possible trading events on the account | //+------------------------------------------------------------------+ enum ENUM_TRADE_EVENT { TRADE_EVENT_NO_EVENT = 0, // No trading event TRADE_EVENT_PENDING_ORDER_PLASED, // Pending order placed TRADE_EVENT_PENDING_ORDER_REMOVED, // Pending order removed //--- enumeration members matching the ENUM_DEAL_TYPE enumeration members //--- (constant order below should not be changed, no constants should be added/deleted) TRADE_EVENT_ACCOUNT_CREDIT = DEAL_TYPE_CREDIT, // Accruing credit (3) TRADE_EVENT_ACCOUNT_CHARGE, // Additional charges TRADE_EVENT_ACCOUNT_CORRECTION, // Correcting entry TRADE_EVENT_ACCOUNT_BONUS, // Accruing bonuses TRADE_EVENT_ACCOUNT_COMISSION, // Additional commissions TRADE_EVENT_ACCOUNT_COMISSION_DAILY, // Commission charged at the end of a trading day TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY, // Commission charged at the end of a trading month TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY, // Agent commission charged at the end of a trading day TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY, // Agent commission charged at the end of a month TRADE_EVENT_ACCOUNT_INTEREST, // Accrued interest on free funds TRADE_EVENT_BUY_CANCELLED, // Canceled buy deal TRADE_EVENT_SELL_CANCELLED, // Canceled sell deal TRADE_EVENT_DIVIDENT, // Accruing dividends TRADE_EVENT_DIVIDENT_FRANKED, // Accruing franked dividends TRADE_EVENT_TAX = DEAL_TAX, // Tax //--- constants related to the DEAL_TYPE_BALANCE deal type from the DEAL_TYPE_BALANCE enumeration TRADE_EVENT_ACCOUNT_BALANCE_REFILL = DEAL_TAX+1, // Replenishing account balance TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL = DEAL_TAX+2, // Withdrawing funds from an account //--- Remaining possible trading events //--- (constant order below can be changed, constants can be added/deleted) TRADE_EVENT_PENDING_ORDER_ACTIVATED = DEAL_TAX+3, // Pending order activated by price TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL, // Pending order partially activated by price TRADE_EVENT_POSITION_OPENED, // Position opened TRADE_EVENT_POSITION_OPENED_PARTIAL, // Position opened partially TRADE_EVENT_POSITION_CLOSED, // Position closed TRADE_EVENT_POSITION_CLOSED_BY_POS, // Position closed partially TRADE_EVENT_POSITION_CLOSED_BY_SL, // Position closed by StopLoss TRADE_EVENT_POSITION_CLOSED_BY_TP, // Position closed by TakeProfit TRADE_EVENT_POSITION_REVERSED_BY_MARKET, // Position reversal by a new deal (netting) TRADE_EVENT_POSITION_REVERSED_BY_PENDING, // Position reversal by activating a pending order (netting) TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL, // Position reversal by partial market order execution (netting) TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL, // Position reversal by partial pending order activation (netting) TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET, // Added volume to a position by a new deal (netting) TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL, // Added volume to a position by partial activation of an order (netting) TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING, // Added volume to a position by activating a pending order (netting) TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL, // Added volume to a position by partial activation of a pending order (netting) TRADE_EVENT_POSITION_CLOSED_PARTIAL, // Position closed partially TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS, // Position closed partially by an opposite one TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL, // Position closed partially by StopLoss TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP, // Position closed partially by TakeProfit TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER, // StopLimit order activation TRADE_EVENT_MODIFY_ORDER_PRICE, // Changing order price TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS, // Changing order and StopLoss price TRADE_EVENT_MODIFY_ORDER_PRICE_TAKE_PROFIT, // Changing order and TakeProfit price TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT, // Changing order, StopLoss and TakeProfit price TRADE_EVENT_MODIFY_ORDER_STOP_LOSS_TAKE_PROFIT, // Changing order's StopLoss and TakeProfit price TRADE_EVENT_MODIFY_POSITION_STOP_LOSS, // Changing position StopLoss TRADE_EVENT_MODIFY_POSITION_TAKE_PROFIT, // Changing position TakeProfit };
最后,将新的描述 StopLimit 挂单激活的常量添加到事件原因枚举 ENUM_EVENT_REASON 列表中:
//+------------------------------------------------------------------+ //| Event reason | //+------------------------------------------------------------------+ enum ENUM_EVENT_REASON { EVENT_REASON_REVERSE, // Position reversal (netting) EVENT_REASON_REVERSE_PARTIALLY, // Position reversal by partial request execution (netting) EVENT_REASON_REVERSE_BY_PENDING, // Position reversal by pending order activation (netting) EVENT_REASON_REVERSE_BY_PENDING_PARTIALLY, // Position reversal in case of a pending order partial execution (netting) //--- All constants related to a position reversal should be located in the above list EVENT_REASON_ACTIVATED_PENDING, // Pending order activation EVENT_REASON_ACTIVATED_PENDING_PARTIALLY, // Pending order partial activation EVENT_REASON_STOPLIMIT_TRIGGERED, // StopLimit order activation EVENT_REASON_CANCEL, // Cancelation EVENT_REASON_EXPIRED, // Order expiration EVENT_REASON_DONE, // Request executed in full EVENT_REASON_DONE_PARTIALLY, // Request executed partially EVENT_REASON_VOLUME_ADD, // Add volume to a position (netting) EVENT_REASON_VOLUME_ADD_PARTIALLY, // Add volume to a position by a partial request execution (netting) EVENT_REASON_VOLUME_ADD_BY_PENDING, // Add volume to a position when a pending order is activated (netting) EVENT_REASON_VOLUME_ADD_BY_PENDING_PARTIALLY, // Add volume to a position when a pending order is partially executed (netting) EVENT_REASON_DONE_SL, // Closing by StopLoss EVENT_REASON_DONE_SL_PARTIALLY, // Partial closing by StopLoss EVENT_REASON_DONE_TP, // Closing by TakeProfit EVENT_REASON_DONE_TP_PARTIALLY, // Partial closing by TakeProfit EVENT_REASON_DONE_BY_POS, // Closing by an opposite position EVENT_REASON_DONE_PARTIALLY_BY_POS, // Partial closing by an opposite position EVENT_REASON_DONE_BY_POS_PARTIALLY, // Closing an opposite position by a partial volume EVENT_REASON_DONE_PARTIALLY_BY_POS_PARTIALLY, // Partial closing of an opposite position by a partial volume //--- Constants related to DEAL_TYPE_BALANCE deal type from the ENUM_DEAL_TYPE enumeration EVENT_REASON_BALANCE_REFILL, // Refilling the balance EVENT_REASON_BALANCE_WITHDRAWAL, // Withdrawing funds from the account //--- List of constants is relevant to TRADE_EVENT_ACCOUNT_CREDIT from the ENUM_TRADE_EVENT enumeration and shifted to +13 relative to ENUM_DEAL_TYPE (EVENT_REASON_ACCOUNT_CREDIT-3) EVENT_REASON_ACCOUNT_CREDIT, // Accruing credit EVENT_REASON_ACCOUNT_CHARGE, // Additional charges EVENT_REASON_ACCOUNT_CORRECTION, // Correcting entry EVENT_REASON_ACCOUNT_BONUS, // Accruing bonuses EVENT_REASON_ACCOUNT_COMISSION, // Additional commissions EVENT_REASON_ACCOUNT_COMISSION_DAILY, // Commission charged at the end of a trading day EVENT_REASON_ACCOUNT_COMISSION_MONTHLY, // Commission charged at the end of a trading month EVENT_REASON_ACCOUNT_COMISSION_AGENT_DAILY, // Agent commission charged at the end of a trading day EVENT_REASON_ACCOUNT_COMISSION_AGENT_MONTHLY, // Agent commission charged at the end of a month EVENT_REASON_ACCOUNT_INTEREST, // Accruing interest on free funds EVENT_REASON_BUY_CANCELLED, // Canceled buy deal EVENT_REASON_SELL_CANCELLED, // Canceled sell deal EVENT_REASON_DIVIDENT, // Accruing dividends EVENT_REASON_DIVIDENT_FRANKED, // Accruing franked dividends EVENT_REASON_TAX // Tax }; #define REASON_EVENT_SHIFT (EVENT_REASON_ACCOUNT_CREDIT-3)
我们已在 Defines.mqh 文件中进行了所有修改。
由于我们决定创建和存储受控订单列表,因此该列表存储的对象应含有最低的属性集合,以便定义在场订单和持仓对象某个之一发生变化的时刻。
我们来创建受控订单对象类。
在 Collections 函数库文件夹中创建新的 OrderControl.mqh 类。 将 CObject 标准库类设置为基类,并包含类操作所需的文件:
//+------------------------------------------------------------------+ //| OrderControl.mqh | //| 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" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Defines.mqh" #include "..\Objects\Orders\Order.mqh" #include <Object.mqh> //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class COrderControl : public CObject { private: public: COrderControl(); ~COrderControl(); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ COrderControl::COrderControl() { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ COrderControl::~COrderControl() { } //+------------------------------------------------------------------+
立即在类的私有部分声明所有必要的变量和方法:
private: ENUM_CHANGE_TYPE m_changed_type; // Order change type MqlTick m_tick; // Tick structure string m_symbol; // Symbol ulong m_position_id; // Position ID ulong m_ticket; // Order ticket long m_magic; // Magic number ulong m_type_order; // Order type ulong m_type_order_prev; // Previous order type double m_price; // Order price double m_price_prev; // Previous order price double m_stop; // StopLoss price double m_stop_prev; // Previous StopLoss price double m_take; // TakeProfit price double m_take_prev; // Previous TakeProfit price double m_volume; // Order volume datetime m_time; // Order placement time datetime m_time_prev; // Order previous placement time int m_change_code; // Order change code //--- return the presence of the property change flag bool IsPresentChangeFlag(const int change_flag) const { return (this.m_change_code & change_flag)==change_flag; } //--- Return the order parameters change type void CalculateChangedType(void);
所有类成员变量都有清晰的论述。 我应该对存储即时报价结构的变量加以澄清:当激活 StopLimit 挂单时,我们需要保存激活时间。 时间应以毫秒为单位,而 TimeCurrent() 则返回的时间不含毫秒值。 为了获得订单被激活时最后一次即时报价的毫秒时间,我们将使用 SymbolInfoTick() 标准函数填充 即时报价结构和数据,包括即时报价时间,以毫秒为单位。
订单变化代码 由我们在 ENUM_CHANGE_TYPE_FLAGS 枚举中论述的标志组成,并取决于发生的订单属性变更。
下面描述的 CalculateChangedType() 私有方法检查标志并创建订单变化代码。
在类的公有部分,分派接收和写入受控订单属性先前和当前状态的数据的方法,设置订单属性发生变化的类型的方法,设置订单变化后新状态的方法,返回所发生变化的类型的方法,和检查订单属性变化的方法,以及设置和返回
已发生的变化类型。 从在场订单和持仓集合类调用该方法,用于检测活动订单和持仓的变化。
//+------------------------------------------------------------------+ //| OrderControl.mqh | //| 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" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Defines.mqh" #include "..\Objects\Orders\Order.mqh" #include <Object.mqh> //+------------------------------------------------------------------+ //| Order and position control class | //+------------------------------------------------------------------+ class COrderControl : public CObject { private: ENUM_CHANGE_TYPE m_changed_type; // Order change type MqlTick m_tick; // Tick structure string m_symbol; // Symbol ulong m_position_id; // Position ID ulong m_ticket; // Order ticket long m_magic; // Magic number ulong m_type_order; // Order type ulong m_type_order_prev; // Previous order type double m_price; // Order price double m_price_prev; // Previous order price double m_stop; // StopLoss price double m_stop_prev; // Previous StopLoss price double m_take; // TakeProfit price double m_take_prev; // Previous TakeProfit price double m_volume; // Order volume datetime m_time; // Order placement time datetime m_time_prev; // Order previous placement time int m_change_code; // Order change code //--- return the presence of the property change flag bool IsPresentChangeFlag(const int change_flag) const { return (this.m_change_code & change_flag)==change_flag; } //--- Calculate the order parameters change type void CalculateChangedType(void); public: //--- Set the (1,2) current and previous type (2,3) current and previous price, (4,5) current and previous StopLoss, //--- (6,7) current and previous TakeProfit, (8,9) current and previous placement time, (10) volume void SetTypeOrder(const ulong type) { this.m_type_order=type; } void SetTypeOrderPrev(const ulong type) { this.m_type_order_prev=type; } void SetPrice(const double price) { this.m_price=price; } void SetPricePrev(const double price) { this.m_price_prev=price; } void SetStopLoss(const double stop_loss) { this.m_stop=stop_loss; } void SetStopLossPrev(const double stop_loss) { this.m_stop_prev=stop_loss; } void SetTakeProfit(const double take_profit) { this.m_take=take_profit; } void SetTakeProfitPrev(const double take_profit) { this.m_take_prev=take_profit; } void SetTime(const datetime time) { this.m_time=time; } void SetTimePrev(const datetime time) { this.m_time_prev=time; } void SetVolume(const double volume) { this.m_volume=volume; } //--- Set (1) change type, (2) new current status void SetChangedType(const ENUM_CHANGE_TYPE type) { this.m_changed_type=type; } void SetNewState(COrder* order); //--- Check and set order parameters change flags and return the change type ENUM_CHANGE_TYPE ChangeControl(COrder* compared_order); //--- Return (1,2,3,4) position ID, ticket, magic and symbol, (5,6) current and previous type (7,8) current and previous price, //--- (9,10) current and previous StopLoss, (11,12) current and previous TakeProfit, (13,14) current and previous placement time, (15) volume ulong PositionID(void) const { return this.m_position_id; } ulong Ticket(void) const { return this.m_ticket; } long Magic(void) const { return this.m_magic; } string Symbol(void) const { return this.m_symbol; } ulong TypeOrder(void) const { return this.m_type_order; } ulong TypeOrderPrev(void) const { return this.m_type_order_prev; } double Price(void) const { return this.m_price; } double PricePrev(void) const { return this.m_price_prev; } double StopLoss(void) const { return this.m_stop; } double StopLossPrev(void) const { return this.m_stop_prev; } double TakeProfit(void) const { return this.m_take; } double TakeProfitPrev(void) const { return this.m_take_prev; } ulong Time(void) const { return this.m_time; } ulong TimePrev(void) const { return this.m_time_prev; } double Volume(void) const { return this.m_volume; } //--- Return the change type ENUM_CHANGE_TYPE GetChangeType(void) const { return this.m_changed_type; } //--- Constructor COrderControl(const ulong position_id,const ulong ticket,const long magic,const string symbol) : m_change_code(CHANGE_TYPE_FLAG_NO_CHANGE), m_changed_type(CHANGE_TYPE_NO_CHANGE), m_position_id(position_id),m_symbol(symbol),m_ticket(ticket),m_magic(magic) {;} }; //+------------------------------------------------------------------+
类构造函数接收仓位 ID ,票据,魔幻数字和订单/仓位的品种。 在其初始化列表中,重置 订单变化标志,和所发生的变化类型,以及从所传递参数中获得的订单/仓位数据立即写入相应的类成员变量。
在类的实体外实现声明的方法.
计算订单/仓位参数变化类型的私有方法:
//+------------------------------------------------------------------+ //| Calculate order parameters change type | //+------------------------------------------------------------------+ void COrderControl::CalculateChangedType(void) { this.m_changed_type= ( //--- If the order flag is set this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_ORDER) ? ( //--- If StopLimit order is activated this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TYPE) ? CHANGE_TYPE_ORDER_TYPE : //--- If an order price is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_PRICE) ? ( //--- If StopLoss and TakeProfit are modified together with the price this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT : //--- If TakeProfit modified together with the price this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_ORDER_PRICE_TAKE_PROFIT : //--- If StopLoss modified together with the price this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_PRICE_STOP_LOSS : //--- Only order price is modified CHANGE_TYPE_ORDER_PRICE ) : //--- Price is not modified //--- If StopLoss and TakeProfit are modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_STOP_LOSS_TAKE_PROFIT : //--- If TakeProfit is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_ORDER_TAKE_PROFIT : //--- If StopLoss is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_STOP_LOSS : //--- No changes CHANGE_TYPE_NO_CHANGE ) : //--- Position //--- If position's StopLoss and TakeProfit are modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_POSITION_STOP_LOSS_TAKE_PROFIT : //--- If position's TakeProfit is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_POSITION_TAKE_PROFIT : //--- If position's StopLoss is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_POSITION_STOP_LOSS : //--- No changes CHANGE_TYPE_NO_CHANGE ); } //+------------------------------------------------------------------+
该方法根据 m_change_code 变量中是否存在标志,将取自先前声明的 ENUM_CHANGE_TYPE 枚举当中的所发生变化类型写入 m_changed_type 类成员变量。
与检查标志相关的所有操作都在方法代码清单的注释中描述,应当易于理解。
私有方法检查 m_change_code 变量中是否存在标志
bool IsPresentChangeFlag(const int change_flag const { return (this.m_change_code & change_flag)==change_flag }
该方法接收已检验标志。 通过逐位检查 m_change_code 变量,且 AND (与操作)进行检查,并返回比较的布尔结果(代码和标志值之间逐位操作)和检查的标志值。
该方法返回订单/仓位属性的相关新状态:
//+------------------------------------------------------------------+ //| Set the new relevant status | //+------------------------------------------------------------------+ void COrderControl::SetNewState(COrder* order) { if(order==NULL || !::SymbolInfoTick(this.Symbol(),this.m_tick)) return; //--- New type this.SetTypeOrderPrev(this.TypeOrder()); this.SetTypeOrder(order.TypeOrder()); //--- New price this.SetPricePrev(this.Price()); this.SetPrice(order.PriceOpen()); //--- New StopLoss this.SetStopLossPrev(this.StopLoss()); this.SetStopLoss(order.StopLoss()); //--- New TakeProfit this.SetTakeProfitPrev(this.TakeProfit()); this.SetTakeProfit(order.TakeProfit()); //--- New time this.SetTimePrev(this.Time()); this.SetTime(this.m_tick.time_msc); } //+------------------------------------------------------------------+
指向订单/仓位的指针,若其一属性发生了变化,即会被传递给方法。
一旦检测到其中一个订单/仓位属性的变化,我们需要保存新状态以便进一步检查,方法
首先将其当前属性状态保存为前值,且将订单传入方法的属性值作为当前状态记录。
当
保存事件发生的时间时,使用 SymbolInfoTick()标准函数接收即时报价时间(以毫秒为单位)。
从 CMarketCollection 类调用的主要方法,并定义发生的变化:
//+------------------------------------------------------------------+ //| Check and set order parameters change flags | //+------------------------------------------------------------------+ ENUM_CHANGE_TYPE COrderControl::ChangeControl(COrder *compared_order) { this.m_change_code=CHANGE_TYPE_FLAG_NO_CHANGE; if(compared_order==NULL || compared_order.Ticket()!=this.m_ticket) return CHANGE_TYPE_NO_CHANGE; if(compared_order.Status()==ORDER_STATUS_MARKET_ORDER || compared_order.Status()==ORDER_STATUS_MARKET_PENDING) this.m_change_code+=CHANGE_TYPE_FLAG_ORDER; if(compared_order.TypeOrder()!=this.m_type_order) this.m_change_code+=CHANGE_TYPE_FLAG_TYPE; if(compared_order.PriceOpen()!=this.m_price) this.m_change_code+=CHANGE_TYPE_FLAG_PRICE; if(compared_order.StopLoss()!=this.m_stop) this.m_change_code+=CHANGE_TYPE_FLAG_STOP; if(compared_order.TakeProfit()!=this.m_take) this.m_change_code+=CHANGE_TYPE_FLAG_TAKE; this.CalculateChangedType(); return this.GetChangeType(); } //+------------------------------------------------------------------+
方法接收指向已检查订单/仓位的指针,初始化变化代码。 如果所传递进行比较的订单为空对象,或其票据不等于当前受控订单的票据,则返回更改缺失代码。
然后检查受控和检验订单的所有跟踪属性。 若发现一处不匹配,则将描述此变化的必要标志添加到变化代码中。
接着,在
CalculateChangedType() 方法中依据完全形成的变化代码计算变化类型,利用 GetChangeType()
方法返回给调用程序 。
受控订单类的完整清单:
//+------------------------------------------------------------------+ //| OrderControl.mqh | //| 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" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Defines.mqh" #include "..\Objects\Orders\Order.mqh" #include <Object.mqh> //+------------------------------------------------------------------+ //| Order and position control class | //+------------------------------------------------------------------+ class COrderControl : public CObject { private: ENUM_CHANGE_TYPE m_changed_type; // Order change type MqlTick m_tick; // Tick structure string m_symbol; // Symbol ulong m_position_id; // Position ID ulong m_ticket; // Order ticket long m_magic; // Magic number ulong m_type_order; // Order type ulong m_type_order_prev; // Previous order type double m_price; // Order price double m_price_prev; // Previous order price double m_stop; // StopLoss price double m_stop_prev; // Previous StopLoss price double m_take; // TakeProfit price double m_take_prev; // Previous TakeProfit price double m_volume; // Order volume datetime m_time; // Order placement time datetime m_time_prev; // Order previous placement time int m_change_code; // Order change code //--- return the presence of the property change flag bool IsPresentChangeFlag(const int change_flag) const { return (this.m_change_code & change_flag)==change_flag; } //--- Calculate the order parameters change type void CalculateChangedType(void); public: //--- Set the (1,2) current and previous type (2,3) current and previous price, (4,5) current and previous StopLoss, //--- (6,7) current and previous TakeProfit, (8,9) current and previous placement time, (10) volume void SetTypeOrder(const ulong type) { this.m_type_order=type; } void SetTypeOrderPrev(const ulong type) { this.m_type_order_prev=type; } void SetPrice(const double price) { this.m_price=price; } void SetPricePrev(const double price) { this.m_price_prev=price; } void SetStopLoss(const double stop_loss) { this.m_stop=stop_loss; } void SetStopLossPrev(const double stop_loss) { this.m_stop_prev=stop_loss; } void SetTakeProfit(const double take_profit) { this.m_take=take_profit; } void SetTakeProfitPrev(const double take_profit) { this.m_take_prev=take_profit; } void SetTime(const datetime time) { this.m_time=time; } void SetTimePrev(const datetime time) { this.m_time_prev=time; } void SetVolume(const double volume) { this.m_volume=volume; } //--- Set (1) change type, (2) new current status void SetChangedType(const ENUM_CHANGE_TYPE type) { this.m_changed_type=type; } void SetNewState(COrder* order); //--- Check and set order parameters change flags and return the change type ENUM_CHANGE_TYPE ChangeControl(COrder* compared_order); //--- Return (1,2,3,4) position ID, ticket, magic and symbol, (5,6) current and previous type (7,8) current and previous price, //--- (9,10) current and previous StopLoss, (11,12) current and previous TakeProfit, (13,14) current and previous placement time, (15) volume ulong PositionID(void) const { return this.m_position_id; } ulong Ticket(void) const { return this.m_ticket; } long Magic(void) const { return this.m_magic; } string Symbol(void) const { return this.m_symbol; } ulong TypeOrder(void) const { return this.m_type_order; } ulong TypeOrderPrev(void) const { return this.m_type_order_prev; } double Price(void) const { return this.m_price; } double PricePrev(void) const { return this.m_price_prev; } double StopLoss(void) const { return this.m_stop; } double StopLossPrev(void) const { return this.m_stop_prev; } double TakeProfit(void) const { return this.m_take; } double TakeProfitPrev(void) const { return this.m_take_prev; } ulong Time(void) const { return this.m_time; } ulong TimePrev(void) const { return this.m_time_prev; } double Volume(void) const { return this.m_volume; } //--- Return the change type ENUM_CHANGE_TYPE GetChangeType(void) const { return this.m_changed_type; } //--- Constructor COrderControl(const ulong position_id,const ulong ticket,const long magic,const string symbol) : m_change_code(CHANGE_TYPE_FLAG_NO_CHANGE), m_changed_type(CHANGE_TYPE_NO_CHANGE), m_position_id(position_id),m_symbol(symbol),m_ticket(ticket),m_magic(magic) {;} }; //+------------------------------------------------------------------+ //| Check and set the order parameters change flags | //+------------------------------------------------------------------+ ENUM_CHANGE_TYPE COrderControl::ChangeControl(COrder *compared_order) { this.m_change_code=CHANGE_TYPE_FLAG_NO_CHANGE; if(compared_order==NULL || compared_order.Ticket()!=this.m_ticket) return CHANGE_TYPE_NO_CHANGE; if(compared_order.Status()==ORDER_STATUS_MARKET_ORDER || compared_order.Status()==ORDER_STATUS_MARKET_PENDING) this.m_change_code+=CHANGE_TYPE_FLAG_ORDER; if(compared_order.TypeOrder()!=this.m_type_order) this.m_change_code+=CHANGE_TYPE_FLAG_TYPE; if(compared_order.PriceOpen()!=this.m_price) this.m_change_code+=CHANGE_TYPE_FLAG_PRICE; if(compared_order.StopLoss()!=this.m_stop) this.m_change_code+=CHANGE_TYPE_FLAG_STOP; if(compared_order.TakeProfit()!=this.m_take) this.m_change_code+=CHANGE_TYPE_FLAG_TAKE; this.CalculateChangedType(); return this.GetChangeType(); } //+------------------------------------------------------------------+ //| Calculate the order parameters change type | //+------------------------------------------------------------------+ void COrderControl::CalculateChangedType(void) { this.m_changed_type= ( //--- If the order flag is set this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_ORDER) ? ( //--- If StopLimit order is activated this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TYPE) ? CHANGE_TYPE_ORDER_TYPE : //--- If an order price is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_PRICE) ? ( //--- If StopLoss and TakeProfit are modified together with the price this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT : //--- If TakeProfit modified together with the price this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_ORDER_PRICE_TAKE_PROFIT : //--- If StopLoss modified together with the price this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_PRICE_STOP_LOSS : //--- Only order price is modified CHANGE_TYPE_ORDER_PRICE ) : //--- Price is not modified //--- If StopLoss and TakeProfit are modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_STOP_LOSS_TAKE_PROFIT : //--- If TakeProfit is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_ORDER_TAKE_PROFIT : //--- If StopLoss is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_ORDER_STOP_LOSS : //--- No changes CHANGE_TYPE_NO_CHANGE ) : //--- Position //--- If position's StopLoss and TakeProfit are modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) && this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_POSITION_STOP_LOSS_TAKE_PROFIT : //--- If position's TakeProfit is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_TAKE) ? CHANGE_TYPE_POSITION_TAKE_PROFIT : //--- If position's StopLoss is modified this.IsPresentChangeFlag(CHANGE_TYPE_FLAG_STOP) ? CHANGE_TYPE_POSITION_STOP_LOSS : //--- No changes CHANGE_TYPE_NO_CHANGE ); } //+------------------------------------------------------------------+ //| Set the new relevant status | //+------------------------------------------------------------------+ void COrderControl::SetNewState(COrder* order) { if(order==NULL || !::SymbolInfoTick(this.Symbol(),this.m_tick)) return; //--- New type this.SetTypeOrderPrev(this.TypeOrder()); this.SetTypeOrder(order.TypeOrder()); //--- New price this.SetPricePrev(this.Price()); this.SetPrice(order.PriceOpen()); //--- New StopLoss this.SetStopLossPrev(this.StopLoss()); this.SetStopLoss(order.StopLoss()); //--- New TakeProfit this.SetTakeProfitPrev(this.TakeProfit()); this.SetTakeProfit(order.TakeProfit()); //--- New time this.SetTimePrev(this.Time()); this.SetTime(this.m_tick.time_msc); } //+------------------------------------------------------------------+
我们继续改进 CMarketCollection 在场订单和持仓集合类。
我们需要跟踪活跃订单和持仓中发生的属性变化。 由于我们在该类中收容了所有在场订单和持仓,因此在其中检查变化也是合理的。
包含受控订单类的文件。 在类的私有部分中,声明用于存储受控订单和仓位的列表,用于存储已变化订单和仓位的列表,用于存储订单变化类型的类成员变量,和用于存储由价格转换而来的哈希值比率的变量。
另外,声明私有方法:
将订单属性转换为哈希值的方法, 将订单或仓位添加到帐户挂单和仓位列表的方法,
创建受控订单, 并将受控订单添加到受控订单列表的方法,以及 创建并将变化订单添加到已变化订单列表中的方法,
按票据和仓位 ID 从受控订单列表中删除订单的方法, 按票据和仓位
ID 返回受控订单列表中的受控订单索引的方法,和 处理现有订单/仓位变化事件的程序。
在类的公有部分中,声明返回已创建的已变化订单列表的方法。
//+------------------------------------------------------------------+ //| MarketCollection.mqh | //| 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" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "ListObj.mqh" #include "..\Services\Select.mqh" #include "..\Objects\Orders\MarketOrder.mqh" #include "..\Objects\Orders\MarketPending.mqh" #include "..\Objects\Orders\MarketPosition.mqh" #include "OrderControl.mqh" //+------------------------------------------------------------------+ //| Collection of market orders and positions | //+------------------------------------------------------------------+ class CMarketCollection : public CListObj { private: struct MqlDataCollection { ulong hash_sum_acc; // Hash sum of all orders and positions on the account int total_market; // Number of market orders on the account int total_pending; // Number of pending orders on the account int total_positions; // Number of positions on the account double total_volumes; // Total volume of orders and positions on the account }; MqlDataCollection m_struct_curr_market; // Current data on market orders and positions on the account MqlDataCollection m_struct_prev_market; // Previous data on market orders and positions on the account CListObj m_list_all_orders; // List of pending orders and positions on the account CArrayObj m_list_control; // List of control orders CArrayObj m_list_changed; // List of changed orders COrder m_order_instance; // Order object for searching by property ENUM_CHANGE_TYPE m_change_type; // Order change type bool m_is_trade_event; // Trading event flag bool m_is_change_volume; // Total volume change flag double m_change_volume_value; // Total volume change value ulong m_k_pow; // Ratio for converting the price into a hash sum int m_new_market_orders; // Number of new market orders int m_new_positions; // Number of new positions int m_new_pendings; // Number of new pending orders //--- Save the current values of the account data status as previous ones void SavePrevValues(void) { this.m_struct_prev_market=this.m_struct_curr_market; } //--- Convert order data into a hash sum value ulong ConvertToHS(COrder* order) const; //--- Add an order or a position to the list of pending orders and positions on an account and sets the data on market orders and positions on the account bool AddToListMarket(COrder* order); //--- (1) Create and add a control order to the list of control orders, (2) a control order to the list of changed control orders bool AddToListControl(COrder* order); bool AddToListChanges(COrderControl* order_control); //--- Remove an order by a ticket or a position ID from the list of control orders bool DeleteOrderFromListControl(const ulong ticket,const ulong id); //--- Return the control order index in the list by a position ticket and ID int IndexControlOrder(const ulong ticket,const ulong id); //--- Handler of an existing order/position change event void OnChangeEvent(COrder* order,const int index); public: //--- Return the list of (1) all pending orders and open positions, (2) modified orders and positions CArrayObj* GetList(void) { return &this.m_list_all_orders; } CArrayObj* GetListChanges(void) { return &this.m_list_changed; } //--- 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 order, (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); }; //+------------------------------------------------------------------+
在类的构造函数中,为受控订单列表和已变化订单列表添加清除和排序,并按定义计算哈希值比率:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CMarketCollection::CMarketCollection(void) : m_is_trade_event(false),m_is_change_volume(false),m_change_volume_value(0) { this.m_list_all_orders.Sort(SORT_BY_ORDER_TIME_OPEN); this.m_list_all_orders.Clear(); ::ZeroMemory(this.m_struct_prev_market); this.m_struct_prev_market.hash_sum_acc=WRONG_VALUE; this.m_list_all_orders.Type(COLLECTION_MARKET_ID); this.m_list_control.Clear(); this.m_list_control.Sort(); this.m_list_changed.Clear(); this.m_list_changed.Sort(); this.m_k_pow=(ulong)pow(10,6); } //+------------------------------------------------------------------+
将订单属性转换为计算哈希和的数字的方法:
//+---------------------------------------------------------------------+ //| Convert the order price and its type into a number for the hash sum | //+---------------------------------------------------------------------+ ulong CMarketCollection::ConvertToHS(COrder *order) const { if(order==NULL) return 0; ulong price=ulong(order.PriceOpen()*this.m_k_pow); ulong stop=ulong(order.StopLoss()*this.m_k_pow); ulong take=ulong(order.TakeProfit()*this.m_k_pow); ulong type=order.TypeOrder(); ulong ticket=order.Ticket(); return price+stop+take+type+ticket; } //+------------------------------------------------------------------+
方法接收指向订单的指针,其数据应转换为数字。 然后通过简单的乘以先前在类构造函数
中计算的比率,将订单的实数型属性转换为哈希值数字,将所有属性值相加并返回为 ulong 数值<。
这些更改会影响到将对象添加到订单和仓位列表。 现在,这些相同类型的代码位于单一的 AddToListMarket() 方法中。 在订单和仓位列表中声明订单对象后,在受控订单列表中检查是否存在相同订单。 如果没有这样的订单,则使用 AddToListControl() 方法创建受控订单对象,并将其添加到受控订单列表中。 如果存在受控订单,则调用 OnChangeEvent()方法将当前订单属性与受控订单属性进行比较。
所有执行的操作都在代码注释中描述,并在方法清单中突出显示。
//+------------------------------------------------------------------+ //| Update the list of orders | //+------------------------------------------------------------------+ void CMarketCollection::Refresh(void) { ::ZeroMemory(this.m_struct_curr_market); this.m_is_trade_event=false; this.m_is_change_volume=false; this.m_new_pendings=0; this.m_new_positions=0; this.m_change_volume_value=0; this.m_list_all_orders.Clear(); #ifdef __MQL4__ int total=::OrdersTotal(); for(int i=0; i<total; i++) { if(!::OrderSelect(i,SELECT_BY_POS)) continue; long ticket=::OrderTicket(); //--- Get the control order index by a position ticket and ID int index=this.IndexControlOrder(ticket); ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderType(); if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL) { CMarketPosition *position=new CMarketPosition(ticket); if(position==NULL) continue; //--- Add a position object to the list of market orders and positions if(!this.AddToListMarket(position)) continue; //--- If there is no order in the list of control orders and positions, add it if(index==WRONG_VALUE) { if(!this.AddToListControl(order)) { ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольный ордер ","Failed to add control order "),order.TypeDescription()," #",order.Ticket()); } } //--- If the order is already present in the list of control orders, check it for changed properties if(index>WRONG_VALUE) { this.OnChangeEvent(position,index); } } else { CMarketPending *order=new CMarketPending(ticket); if(order==NULL) continue; //--- Add a pending order object to the list of market orders and positions if(!this.AddToListMarket(order)) continue; //--- If there is no order in the list of control orders and positions, add it if(index==WRONG_VALUE) { if(!this.AddToListControl(order)) { ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольный ордер ","Failed to add control order "),order.TypeDescription()," #",order.Ticket()); } } //--- If the order is already present in the list of control orders, check it for changed properties if(index>WRONG_VALUE) { this.OnChangeEvent(order,index); } } } //--- MQ5 #else //--- Positions int total_positions=::PositionsTotal(); for(int i=0; i<total_positions; i++) { ulong ticket=::PositionGetTicket(i); if(ticket==0) continue; CMarketPosition *position=new CMarketPosition(ticket); if(position==NULL) continue; //--- Add a position object to the list of market orders and positions if(!this.AddToListMarket(position)) continue; //--- Get the control order index by a position ticket and ID int index=this.IndexControlOrder(ticket,position.PositionID()); //--- If the order is not present in the list of control orders, add it if(index==WRONG_VALUE) { if(!this.AddToListControl(position)) { ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольую позицию ","Failed to add control position "),position.TypeDescription()," #",position.Ticket()); } } //--- If the order is already present in the list of control orders, check it for changed properties else if(index>WRONG_VALUE) { this.OnChangeEvent(position,index); } } //--- Orders int total_orders=::OrdersTotal(); for(int i=0; i<total_orders; i++) { ulong ticket=::OrderGetTicket(i); if(ticket==0) continue; ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderGetInteger(ORDER_TYPE); //--- Market order if(type<ORDER_TYPE_BUY_LIMIT) { CMarketOrder *order=new CMarketOrder(ticket); if(order==NULL) continue; //--- Add a market order object to the list of market orders and positions if(!this.AddToListMarket(order)) continue; } //--- Pending order else { CMarketPending *order=new CMarketPending(ticket); if(order==NULL) continue; //--- Add a pending order object to the list of market orders and positions if(!this.AddToListMarket(order)) continue; //--- Get the control order index by a position ticket and ID int index=this.IndexControlOrder(ticket,order.PositionID()); //--- If the order is not present in the control order list, add it if(index==WRONG_VALUE) { if(!this.AddToListControl(order)) { ::Print(DFUN_ERR_LINE,TextByLanguage("Не удалось добавить контрольный ордер ","Failed to add control order "),order.TypeDescription()," #",order.Ticket()); } } //--- If the order is already in the control order list, check it for changed properties else if(index>WRONG_VALUE) { this.OnChangeEvent(order,index); } } } #endif //--- First launch if(this.m_struct_prev_market.hash_sum_acc==WRONG_VALUE) { this.SavePrevValues(); } //--- If the hash sum of all orders and positions changed if(this.m_struct_curr_market.hash_sum_acc!=this.m_struct_prev_market.hash_sum_acc) { this.m_new_market_orders=this.m_struct_curr_market.total_market-this.m_struct_prev_market.total_market; this.m_new_pendings=this.m_struct_curr_market.total_pending-this.m_struct_prev_market.total_pending; this.m_new_positions=this.m_struct_curr_market.total_positions-this.m_struct_prev_market.total_positions; this.m_change_volume_value=::NormalizeDouble(this.m_struct_curr_market.total_volumes-this.m_struct_prev_market.total_volumes,4); this.m_is_change_volume=(this.m_change_volume_value!=0 ? true : false); this.m_is_trade_event=true; this.SavePrevValues(); } } //+------------------------------------------------------------------+
将订单和仓位添加到在场订单和仓位集合列表的方法:
//+--------------------------------------------------------------------------------+ //| Add an order or a position to the list of orders and positions on the account | //+--------------------------------------------------------------------------------+ bool CMarketCollection::AddToListMarket(COrder *order) { if(order==NULL) return false; ENUM_ORDER_STATUS status=order.Status(); if(this.m_list_all_orders.InsertSort(order)) { if(status==ORDER_STATUS_MARKET_POSITION) { this.m_struct_curr_market.hash_sum_acc+=order.GetProperty(ORDER_PROP_TIME_UPDATE_MSC)+this.ConvertToHS(order); this.m_struct_curr_market.total_volumes+=order.Volume(); this.m_struct_curr_market.total_positions++; return true; } if(status==ORDER_STATUS_MARKET_PENDING) { this.m_struct_curr_market.hash_sum_acc+=this.ConvertToHS(order); this.m_struct_curr_market.total_volumes+=order.Volume(); this.m_struct_curr_market.total_pending++; return true; } } else { ::Print(DFUN,order.TypeDescription()," #",order.Ticket()," ",TextByLanguage("не удалось добавить в список","failed to add to list")); delete order; } return false; } //+------------------------------------------------------------------+
指向被添加到集合列表的订单的指针将传递给该方法。 在将订单添加到集合列表后,存储在场订单和仓位当前状态以便后续处理,依据订单状态定义已变化订单和仓位数量。
- 如果这是一笔持仓,则仓位变化时间和计算的哈希值将加入常规哈希值,且整体仓位数量将会增加。
- 如果这是挂单,则将计算出的哈希值加入常规哈希值,且挂单的总数将会增加。
创建受控订单,并将其添加到受控订单列表的方法:
//+------------------------------------------------------------------+ //| Create and add an order to the list of control orders | //+------------------------------------------------------------------+ bool CMarketCollection::AddToListControl(COrder *order) { if(order==NULL) return false; COrderControl* order_control=new COrderControl(order.PositionID(),order.Ticket(),order.Magic(),order.Symbol()); if(order_control==NULL) return false; order_control.SetTime(order.TimeOpenMSC()); order_control.SetTimePrev(order.TimeOpenMSC()); order_control.SetVolume(order.Volume()); order_control.SetTime(order.TimeOpenMSC()); order_control.SetTypeOrder(order.TypeOrder()); order_control.SetTypeOrderPrev(order.TypeOrder()); order_control.SetPrice(order.PriceOpen()); order_control.SetPricePrev(order.PriceOpen()); order_control.SetStopLoss(order.StopLoss()); order_control.SetStopLossPrev(order.StopLoss()); order_control.SetTakeProfit(order.TakeProfit()); order_control.SetTakeProfitPrev(order.TakeProfit()); if(!this.m_list_control.Add(order_control)) { delete order_control; return false; } return true; } //+------------------------------------------------------------------+
将指向在场订单和持仓的指针传递给方法。 如果所传递的对象无效,则返回
false。
然后创建一个新的受控订单,以至其构造函数立即接收传递给该方法的的仓位
ID、票据、魔幻数字和订单对象的品种。
然后填写识别订单/仓位变化所需的所有数据。
如果将新的受控订单加入受控订单列表失败,则会删除订单并返回 “false”。
由于我们总是将新订单和仓位添加到受控订单和仓位列表中,因此经过长时间的运行后可能会变得非常笨重。 订单和持仓不会永远存在,并且它们的受控副本不应永久存储在占用内存的列表中。 若要从列表中删除不必要的受控订单,利用 DeleteOrderFromListControl()
方法按仓位票据和 ID 从受控订单列表中删除受控订单。
目前,该方法仅被声明,但尚未实现。 在跟踪订单和持仓变化的整体功能实现就绪之后,将会完成其实现。
该方法按仓位票据和 ID 返回受控订单列表中的受控订单索引:
//+------------------------------------------------------------------+ //| Return an order index by a ticket in the list of control orders | //+------------------------------------------------------------------+ int CMarketCollection::IndexControlOrder(const ulong ticket,const ulong id) { int total=this.m_list_control.Total(); for(int i=0;i<total;i++) { COrderControl* order=this.m_list_control.At(i); if(order==NULL) continue; if(order.PositionID()==id && order.Ticket()==ticket) return i; } return WRONG_VALUE; } //+------------------------------------------------------------------+
该方法接收订单/仓位票据和仓位 ID 。 在循环中搜索所有受控订单与票据和
ID 匹配的受控订单,并返回受控订单在列表中的索引。 如果未找到订单,则返回
-1。
现有订单/持仓变化的事件处理程序方法:
//+------------------------------------------------------------------+ //| Handler of changing an existing order/position | //+------------------------------------------------------------------+ void CMarketCollection::OnChangeEvent(COrder* order,const int index) { COrderControl* order_control=this.m_list_control.At(index); if(order_control!=NULL) { this.m_change_type=order_control.ChangeControl(order); ENUM_CHANGE_TYPE change_type=(order.Status()==ORDER_STATUS_MARKET_POSITION ? CHANGE_TYPE_ORDER_TAKE_PROFIT : CHANGE_TYPE_NO_CHANGE); if(this.m_change_type>change_type) { order_control.SetNewState(order); if(!this.AddToListChanges(order_control)) { ::Print(DFUN,TextByLanguage("Не удалось добавить модифицированный ордер в список изменённых ордеров","Could not add modified order to list of modified orders")); } } } } //+------------------------------------------------------------------+
方法接收指向已查验订单的指针,和受控订单列表中受控订单相应的索引。
按其索引从列表中获取受控订单,并检查受控订单属性中的变化是否与使用
ChangeControl() 方法检查的受控订单的属性相对应。 方法接收指向受控订单的指针。
如果发现区别,则该方法返回的变化类型会被写入
m_change_type 类成员变量。
接着,检查已验证订单的状态并设置数值,在该数值是上述认定已发生的变化。
对于
仓位,此值应超过
ENUM_CHANGE_TYPE 枚举中的 CHANGE_TYPE_ORDER_TAKE_PROFIT常量,因为等于或低于此常量的所有值仅与挂单相关。 对于挂单,该值应超过
CHANGE_TYPE_NO_CHANGE常量。
如果获得的 m_change_type
变量超过指定的,则检测到修改。 在这种情况下,将保存受控订单的当前状态以供后续检查,且将受控订单的副本放入已变化订单列表,以便后续在
CEventsCollection 类中处理列表。
创建变化的受控订单,并将其添加到已变化订单列表的方法:
//+------------------------------------------------------------------+ //|Create and add a control order to the list of changed orders | //+------------------------------------------------------------------+ bool CMarketCollection::AddToListChanges(COrderControl* order_control) { if(order_control==NULL) return false; COrderControl* order_changed=new COrderControl(order_control.PositionID(),order_control.Ticket(),order_control.Magic(),order_control.Symbol()); if(order_changed==NULL) return false; order_changed.SetTime(order_control.Time()); order_changed.SetTimePrev(order_control.TimePrev()); order_changed.SetVolume(order_control.Volume()); order_changed.SetTypeOrder(order_control.TypeOrder()); order_changed.SetTypeOrderPrev(order_control.TypeOrderPrev()); order_changed.SetPrice(order_control.Price()); order_changed.SetPricePrev(order_control.PricePrev()); order_changed.SetStopLoss(order_control.StopLoss()); order_changed.SetStopLossPrev(order_control.StopLossPrev()); order_changed.SetTakeProfit(order_control.TakeProfit()); order_changed.SetTakeProfitPrev(order_control.TakeProfitPrev()); order_changed.SetChangedType(order_control.GetChangeType()); if(!this.m_list_changed.Add(order_changed)) { delete order_changed; return false; } return true; } //+------------------------------------------------------------------+
方法接收指向已修改的受控订单的指针。 订单副本应放在已变化受控订单和仓位列表中。
接下来,创建一个新的受控订单。
它会立即收到与已变化受控订单相匹配的仓位 ID、票据、魔幻数字和品种。
之后,将已变化受控订单的属性简单地逐一复制到新创建的元素属性中。
最后,将新创建的已变化受控订单的副本放入已变化的订单列表。
如果新创建的订单无法放入列表,则删除新创建的订单对象,并返回
false。
我们已经完成了对 CMarketCollection 类的修改。 现在我们转到 CEventsCollection 类。
CEventsCollection 事件集合类应具有处理事件的能力,其在场订单和持仓集合类中创建的已变化订单列表不能为空。 这意味着它包含的已变化订单和持仓应加以处理(创建新事件,并将相应的消息发送给调用程序)。
除了已经存在的方法之外,我们将两个方法的定义添加到类的私有部分:新的创建新事件的重载方法,和处理变化的现有订单/持仓的方法,而
Refresh() 方法拥有
将已变化订单列表传递给方法的能力:
//+------------------------------------------------------------------+ //| EventsCollection.mqh | //| 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" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "ListObj.mqh" #include "..\Services\Select.mqh" #include "..\Objects\Orders\Order.mqh" #include "..\Objects\Events\EventBalanceOperation.mqh" #include "..\Objects\Events\EventOrderPlaced.mqh" #include "..\Objects\Events\EventOrderRemoved.mqh" #include "..\Objects\Events\EventPositionOpen.mqh" #include "..\Objects\Events\EventPositionClose.mqh" //+------------------------------------------------------------------+ //| 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 //--- Create a trading event depending on the (1) order 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 and return the list of market pending orders CArrayObj* GetListMarketPendings(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 flag of the event object presence in the event list bool IsPresentEventInList(CEvent* compared_event); //--- Handler of an existing order/position change void OnChangeEvent(CArrayObj* list_changes,CArrayObj* list_history,CArrayObj* list_market,const int index); 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, 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); }; //+------------------------------------------------------------------+
我们在类的实体外部实现新方法。
用于创建订单/持仓修改事件的重载方法:
//+------------------------------------------------------------------+ //| Create a trading event depending on the order change type | //+------------------------------------------------------------------+ void CEventsCollection::CreateNewEvent(COrderControl* order) { CEvent* event=NULL; //--- Pending StopLimit order placed if(order.GetChangeType()==CHANGE_TYPE_ORDER_TYPE) { this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_PLASED; event=new CEventOrderPlased(this.m_trade_event_code,order.Ticket()); } //--- if(event!=NULL) { event.SetProperty(EVENT_PROP_TIME_EVENT,order.Time()); // Event time event.SetProperty(EVENT_PROP_REASON_EVENT,EVENT_REASON_STOPLIMIT_TRIGGERED); // Event reason (from the ENUM_EVENT_REASON enumeration) event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrderPrev()); // Type of the order that triggered an event event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket()); // Ticket of the order that triggered an event event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order.TypeOrder()); // Event order type event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket()); // Event order ticket event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order.TypeOrder()); // Position first order type event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket()); // Position first order ticket event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID()); // Position ID event.SetProperty(EVENT_PROP_POSITION_BY_ID,0); // Opposite position ID event.SetProperty(EVENT_PROP_MAGIC_BY_ID,0); // Opposite position magic number event.SetProperty(EVENT_PROP_TYPE_ORD_POS_BEFORE,order.TypeOrderPrev()); // Position order type before changing the direction event.SetProperty(EVENT_PROP_TICKET_ORD_POS_BEFORE,order.Ticket()); // Position order ticket before changing direction 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_MAGIC_ORDER,order.Magic()); // Order magic number event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimePrev()); // First position order time event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PricePrev()); // Event price event.SetProperty(EVENT_PROP_PRICE_OPEN,order.Price()); // Order open price event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.Price()); // Order close price event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss()); // StopLoss order price event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit()); // TakeProfit order price event.SetProperty(EVENT_PROP_VOLUME_ORDER_INITIAL,order.Volume()); // Requested order volume event.SetProperty(EVENT_PROP_VOLUME_ORDER_EXECUTED,0); // Executed order volume event.SetProperty(EVENT_PROP_VOLUME_ORDER_CURRENT,order.Volume()); // Remaining (unexecuted) order volume event.SetProperty(EVENT_PROP_VOLUME_POSITION_EXECUTED,0); // Executed position volume event.SetProperty(EVENT_PROP_PROFIT,0); // Profit event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol()); // Order symbol event.SetProperty(EVENT_PROP_SYMBOL_BY_ID,order.Symbol()); // Opposite position symbol //--- Set the 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 already in the list.")); delete event; } } } //+------------------------------------------------------------------+
我在创建事件集合时,曾于函数库第五部分中论述过创建新事件的方法。
这种方法几乎完全相同。 唯一的区别是订单的类型,传递给方法的是指针。
在方法的最开始检查所发生的订单变化类型,并且在
m_trade_event_code 类成员变量中依据变化类型设置变化代码。
接着,创建匹配变化类型的事件,根据变化类型填充其属性,将事件放置到事件列表,并将其发送给
控制程序。
更新事件列表的改进方法:
//+------------------------------------------------------------------+ //| Update the event list | //+------------------------------------------------------------------+ void CEventsCollection::Refresh(CArrayObj* list_history, CArrayObj* list_market, CArrayObj* list_changes, 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); } } } } //--- 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 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); } } } } } //+------------------------------------------------------------------+
在函数库第五部分中论述创建事件集合时,也研究了此方法。 区别在于,在所添加的处理修改事件的代码块,其方法中变化订单列表的大小不为零。 列表中的每个变化订单都在循环中的 订单变化事件处理程序方法中进行处理:
//+------------------------------------------------------------------+ //| The handler of an existing order/position change event | //+------------------------------------------------------------------+ void CEventsCollection::OnChangeEvent(CArrayObj* list_changes,const int index) { COrderControl* order_changed=list_changes.Detach(index); if(order_changed!=NULL) { if(order_changed.GetChangeType()==CHANGE_TYPE_ORDER_TYPE) { this.CreateNewEvent(order_changed); } delete order_changed; } } //+------------------------------------------------------------------+
处理已变化订单列表时,我们需要从列表中获取修改后的订单,并在处理完成后从列表中删除订单对象和相应的指针,以避免重复处理同一事件。
幸运的是,当使用 CArrayObj
动态对象指针数组时,标准库提供了 Detach()
方法
,它接收来自指定仓位的元素,并将其从数组中删除。 换言之,我们按索引接收存储在数组中的对象指针,并从数组中删除该指针。
如果变化类型为 CHANGE_TYPE_ORDER_TYPE(订单类型变化— 触发 StopLimit
挂单,并将其转为限价订单),
创建新事件— StopLimit 挂单激活。 利用 Detach() 方法获得对象指针,并经处理后,指针(不再需要)会被简单地删除。
这样就完成了 CEventsCollection 类的改进。
为了使所有更改生效,应接收来自在场订单和持仓集合类的已变化订单列表,并应将其大小写入函数库的主对象 — 在 CEngine 类( TradeEventsControl() 方法)中。 当调用事件集合类的 Refresh() 事件更新方法时,应另外检查已变化订单列表的大小,并将已修改的订单列表传递给事件集合的 Refresh() 方法以便进行处理:
//+------------------------------------------------------------------+ //| Check trading events | //+------------------------------------------------------------------+ void CEngine::TradeEventsControl(void) { //--- Initialize the trading events code and flag 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()); //--- Receive the last account trading event this.m_acc_trade_event=this.m_events.GetLastTradeEvent(); } } //+------------------------------------------------------------------+
由于 StopLimit 挂单的激活导致放置限价挂单,我们将“修饰”此事件作为放置挂单,而事件原因是激活 StopLimit 挂单 EVENT_REASON_STOPLIMIT_TRIGGERED。 我们已经在 Defines.mqh 文件的 ENUM_EVENT_REASON 枚举中设置了该常量。
我们改进 EventOrderPlased 类,以便在日志中显示事件进程,并将其发送到控制程序:
只需添加 EVENT_REASON_STOPLIMIT_TRIGGERED 事件原因处理即可。
//+------------------------------------------------------------------+ //| EventOrderPlased.mqh | //| 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" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Event.mqh" //+------------------------------------------------------------------+ //| Placing a pending order event | //+------------------------------------------------------------------+ class CEventOrderPlased : public CEvent { public: //--- Constructor CEventOrderPlased(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_MARKET_PENDING,event_code,ticket) {} //--- Supported order properties (1) real, (2) integer virtual bool SupportProperty(ENUM_EVENT_PROP_INTEGER property); virtual bool SupportProperty(ENUM_EVENT_PROP_DOUBLE property); //--- (1) Display a brief message about the event in the journal, (2) Send the event to the chart virtual void PrintShort(void); virtual void SendEvent(void); }; //+------------------------------------------------------------------+ //| Return 'true' if the event supports the passed | //| integer property, otherwise return 'false' | //+------------------------------------------------------------------+ bool CEventOrderPlased::SupportProperty(ENUM_EVENT_PROP_INTEGER property) { if(property==EVENT_PROP_TYPE_DEAL_EVENT || property==EVENT_PROP_TICKET_DEAL_EVENT || property==EVENT_PROP_TYPE_ORDER_POSITION || property==EVENT_PROP_TICKET_ORDER_POSITION || property==EVENT_PROP_POSITION_ID || property==EVENT_PROP_POSITION_BY_ID || property==EVENT_PROP_TIME_ORDER_POSITION ) return false; return true; } //+------------------------------------------------------------------+ //| Return 'true' if the event supports the passed | //| real property, otherwise return 'false' | //+------------------------------------------------------------------+ bool CEventOrderPlased::SupportProperty(ENUM_EVENT_PROP_DOUBLE property) { if(property==EVENT_PROP_PRICE_CLOSE || property==EVENT_PROP_PROFIT ) return false; return true; } //+------------------------------------------------------------------+ //| Display a brief message about the event in the journal | //+------------------------------------------------------------------+ void CEventOrderPlased::PrintShort(void) { int digits=(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS); string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n"; string sl=(this.PriceStopLoss()>0 ? ", sl "+::DoubleToString(this.PriceStopLoss(),digits) : ""); string tp=(this.PriceTakeProfit()>0 ? ", tp "+::DoubleToString(this.PriceTakeProfit(),digits) : ""); string vol=::DoubleToString(this.VolumeOrderInitial(),DigitsLots(this.Symbol())); string magic=(this.Magic()!=0 ? TextByLanguage(", магик ",", magic ")+(string)this.Magic() : ""); string type=this.TypeOrderFirstDescription()+" #"+(string)this.TicketOrderEvent(); string event=TextByLanguage(" Установлен "," Placed "); string price=TextByLanguage(" по цене "," at price ")+::DoubleToString(this.PriceOpen(),digits); string txt=head+this.Symbol()+event+vol+" "+type+price+sl+tp+magic; //--- If StopLimit order is activated if(this.Reason()==EVENT_REASON_STOPLIMIT_TRIGGERED) { head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimeEvent())+" -\n"; event=TextByLanguage(" Сработал "," Triggered "); type= ( OrderTypeDescription(this.TypeOrderPosPrevious())+" #"+(string)this.TicketOrderEvent()+ TextByLanguage(" по цене "," at price ")+DoubleToString(this.PriceEvent(),digits)+" -->\n"+ vol+" "+OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderEvent()+ TextByLanguage(" на цену "," on price ")+DoubleToString(this.PriceOpen(),digits) ); txt=head+this.Symbol()+event+"("+TimeMSCtoString(this.TimePosition())+") "+vol+" "+type+sl+tp+magic; } ::Print(txt); } //+------------------------------------------------------------------+ //| Send the event to the chart | //+------------------------------------------------------------------+ void CEventOrderPlased::SendEvent(void) { this.PrintShort(); ::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.TicketOrderEvent(),this.PriceOpen(),this.Symbol()); } //+------------------------------------------------------------------+
这一切都很易于理解。 关注于简单的动作没有意义。
综上是用于跟踪 StopLimit 挂单激活的函数库改进。
测试
为了测试已实现的改进,我们将使用上一篇文章中的 EA。 只需将 TestDoEasyPart06.mq5 EA 从 \MQL5\Experts\TestDoEasy\Part06 文件夹重命名为 TestDoEasyPart07.mq5,并将其保存在新的 \MQL5\Experts\TestDoEasy\Part07 子文件夹当中。
编译 EA,在测试器中启动它,放置一笔 StopLimit 挂单,等待其激活:
下一步是什么?
本文中实现的功能包括快速添加其他事件跟踪的功能:修改挂单属性 — 它们的价格\止损和止盈价位,以及修改持仓的止损和止盈价位。 我们将在下一篇文章中研究这些任务。
下面附有当前版本函数库的所有文件,以及测试 EA 文件,供您测试和下载。
请在评论中留下您的问题,意见和建议。
返回内容
本系列中的前几篇文章:
第一部分 概念,数据管理。
第二部分
历史订单和成交集合交易。
第三部分 在场订单和持仓集合,安排搜索。
第四部分
交易事件。 概念。
第五部分 交易事件的类和集合。 将事件发送到程序。
第六部分
净持结算帐户事件。