内容
- 修改事件类
- 测试订单和持仓修改
- 下一步是什么?
在上一篇文章中,我们实现了 跟踪 StopLimit 订单激活事件。 用于定义事件的整个功能是可扩展的,允许我们轻松添加对其他必要事件的搜索。
我们将 StopLimit 订单激活“限定”为放置新的挂单,这很合理,因为新订单类型需要新的下单事件。 在本文中,我们将跟踪不同类型的事件 — 修改已有的订单和持仓(我们已经了解了与其有关的下单和开单,这要归功于在程序中能获得相应事件)。
这意味着我们需要从 CEvent 抽象事件类派生出另一个(修改事件)类。
修改事件类
如同往常一样,我们从预备所需的枚举常量开始。
打开 Defines.mqh 函数库文件,并设置 the
number of skipped properties to zero(跳过的属性数量为零),在宏替换中包含排序时未用到的订单整数型属性的数量。
稍后我们将需要所有整数型订单属性。 之前,我们在搜索和排序时跳过了一个属性 — 枚举常量列表中的最后一个属性:
ORDER_PROP_DIRECTION — 按其方向类型(请记住,我们将所有未用到的属性放在枚举常量列表的末尾)。
在当前和以后的文章中,我们将需要此属性来搜索在场集合列表中的所有单向挂单。
//+------------------------------------------------------------------+ //| Order, deal, position integer properties | //+------------------------------------------------------------------+ enum ENUM_ORDER_PROP_INTEGER { ORDER_PROP_TICKET = 0, // Order ticket ORDER_PROP_MAGIC, // Order magic number ORDER_PROP_TIME_OPEN, // Open time (MQL5 Deal time) ORDER_PROP_TIME_CLOSE, // Close time (MQL5 Execution or removal time - ORDER_TIME_DONE) ORDER_PROP_TIME_OPEN_MSC, // Open time in milliseconds (MQL5 Deal time in msc) ORDER_PROP_TIME_CLOSE_MSC, // Close time in milliseconds (MQL5 Execution or removal time - ORDER_TIME_DONE_MSC) ORDER_PROP_TIME_EXP, // Order expiration date (for pending orders) ORDER_PROP_STATUS, // Order status (from the ENUM_ORDER_STATUS enumeration) ORDER_PROP_TYPE, // Order/deal type ORDER_PROP_REASON, // Deal/order/position reason or source ORDER_PROP_STATE, // Order status (from the ENUM_ORDER_STATE enumeration) ORDER_PROP_POSITION_ID, // Position ID ORDER_PROP_POSITION_BY_ID, // Opposite position ID ORDER_PROP_DEAL_ORDER_TICKET, // Ticket of the order that triggered a deal ORDER_PROP_DEAL_ENTRY, // Deal direction – IN, OUT or IN/OUT ORDER_PROP_TIME_UPDATE, // Position change time in seconds ORDER_PROP_TIME_UPDATE_MSC, // Position change time in milliseconds ORDER_PROP_TICKET_FROM, // Parent order ticket ORDER_PROP_TICKET_TO, // Derived order ticket ORDER_PROP_PROFIT_PT, // Profit in points ORDER_PROP_CLOSE_BY_SL, // Flag of closing by StopLoss ORDER_PROP_CLOSE_BY_TP, // Flag of closing by TakeProfit ORDER_PROP_GROUP_ID, // Order/position group ID ORDER_PROP_DIRECTION, // Direction type (Buy, Sell) }; #define ORDER_PROP_INTEGER_TOTAL (24) // Total number of integer properties #define ORDER_PROP_INTEGER_SKIP (0) // Number of order properties not used in sorting //+------------------------------------------------------------------+
由于我们删除了跳过属性,我们还需要添加按其排序的功能。 我们将按订单方向排序的条件添加到订单枚举中,并处理可能的排序条件:
//+------------------------------------------------------------------+ //| Possible criteria of orders and deals sorting | //+------------------------------------------------------------------+ #define FIRST_ORD_DBL_PROP (ORDER_PROP_INTEGER_TOTAL-ORDER_PROP_INTEGER_SKIP) #define FIRST_ORD_STR_PROP (ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL-ORDER_PROP_INTEGER_SKIP) enum ENUM_SORT_ORDERS_MODE { //--- Sort by integer properties SORT_BY_ORDER_TICKET = 0, // Sort by order ticket SORT_BY_ORDER_MAGIC = 1, // Sort by order magic number SORT_BY_ORDER_TIME_OPEN = 2, // Sort by order open time SORT_BY_ORDER_TIME_CLOSE = 3, // Sort by order close time SORT_BY_ORDER_TIME_OPEN_MSC = 4, // Sort by order open time in milliseconds SORT_BY_ORDER_TIME_CLOSE_MSC = 5, // Sort by order close time in milliseconds SORT_BY_ORDER_TIME_EXP = 6, // Sort by order expiration date SORT_BY_ORDER_STATUS = 7, // Sort by order status (market order/pending order/deal/balance, credit operation) SORT_BY_ORDER_TYPE = 8, // Sort by order type SORT_BY_ORDER_REASON = 9, // Sort by order/position reason/source SORT_BY_ORDER_STATE = 10, // Sort by order status SORT_BY_ORDER_POSITION_ID = 11, // Sort by position ID SORT_BY_ORDER_POSITION_BY_ID = 12, // Sort by opposite position ID SORT_BY_ORDER_DEAL_ORDER = 13, // Sort by order a deal is based on SORT_BY_ORDER_DEAL_ENTRY = 14, // Sort by deal direction – IN, OUT or IN/OUT SORT_BY_ORDER_TIME_UPDATE = 15, // Sort by position change time in seconds SORT_BY_ORDER_TIME_UPDATE_MSC = 16, // Sort by position change time in milliseconds SORT_BY_ORDER_TICKET_FROM = 17, // Sort by parent order ticket SORT_BY_ORDER_TICKET_TO = 18, // Sort by derived order ticket SORT_BY_ORDER_PROFIT_PT = 19, // Sort by order profit in points SORT_BY_ORDER_CLOSE_BY_SL = 20, // Sort by order closing by StopLoss flag SORT_BY_ORDER_CLOSE_BY_TP = 21, // Sort by order closing by TakeProfit flag SORT_BY_ORDER_GROUP_ID = 22, // Sort by order/position group ID SORT_BY_ORDER_DIRECTION = 23, // Sort by direction (Buy, Sell) //--- Sort by real properties SORT_BY_ORDER_PRICE_OPEN = FIRST_ORD_DBL_PROP, // Sort by open price SORT_BY_ORDER_PRICE_CLOSE = FIRST_ORD_DBL_PROP+1, // Sort by close price SORT_BY_ORDER_SL = FIRST_ORD_DBL_PROP+2, // Sort by StopLoss price SORT_BY_ORDER_TP = FIRST_ORD_DBL_PROP+3, // Sort by TakeProfit price SORT_BY_ORDER_PROFIT = FIRST_ORD_DBL_PROP+4, // Sort by profit SORT_BY_ORDER_COMMISSION = FIRST_ORD_DBL_PROP+5, // Sort by commission SORT_BY_ORDER_SWAP = FIRST_ORD_DBL_PROP+6, // Sort by swap SORT_BY_ORDER_VOLUME = FIRST_ORD_DBL_PROP+7, // Sort by volume SORT_BY_ORDER_VOLUME_CURRENT = FIRST_ORD_DBL_PROP+8, // Sort by unexecuted volume SORT_BY_ORDER_PROFIT_FULL = FIRST_ORD_DBL_PROP+9, // Sort by profit+commission+swap criterion SORT_BY_ORDER_PRICE_STOP_LIMIT= FIRST_ORD_DBL_PROP+10, // Sort by Limit order when StopLimit order is activated //--- Sort by string properties SORT_BY_ORDER_SYMBOL = FIRST_ORD_STR_PROP, // Sort by symbol SORT_BY_ORDER_COMMENT = FIRST_ORD_STR_PROP+1, // Sort by comment SORT_BY_ORDER_EXT_ID = FIRST_ORD_STR_PROP+2 // Sort by order ID in an external trading system }; //+------------------------------------------------------------------+
也许您还记得,若要创建事件 ID,我们使用一组由标志组成的事件代码,这些标志协同指示已发生事件的类型。 由于我们要跟踪修改事件,我们需要在交易事件标志枚举中添加必要的标志:
//+------------------------------------------------------------------+ //| List of trading event flags on the account | //+------------------------------------------------------------------+ enum ENUM_TRADE_EVENT_FLAGS { TRADE_EVENT_FLAG_NO_EVENT = 0, // No event TRADE_EVENT_FLAG_ORDER_PLASED = 1, // Pending order placed TRADE_EVENT_FLAG_ORDER_REMOVED = 2, // Pending order removed TRADE_EVENT_FLAG_ORDER_ACTIVATED = 4, // Pending order activated by price TRADE_EVENT_FLAG_POSITION_OPENED = 8, // Position opened TRADE_EVENT_FLAG_POSITION_CHANGED= 16, // Position changed TRADE_EVENT_FLAG_POSITION_REVERSE= 32, // Position reversed TRADE_EVENT_FLAG_POSITION_CLOSED = 64, // Position closed TRADE_EVENT_FLAG_ACCOUNT_BALANCE = 128, // Balance operation (clarified by a deal type) TRADE_EVENT_FLAG_PARTIAL = 256, // Partial execution TRADE_EVENT_FLAG_BY_POS = 512, // Executed by opposite position TRADE_EVENT_FLAG_PRICE = 1024, // Placement price modification TRADE_EVENT_FLAG_SL = 2048, // Executed by StopLoss TRADE_EVENT_FLAG_TP = 4096, // Executed by TakeProfit TRADE_EVENT_FLAG_ORDER_MODIFY = 8192, // Order modification TRADE_EVENT_FLAG_POSITION_MODIFY = 16384, // Position modification }; //+------------------------------------------------------------------+
我们将订单和持仓修改事件添加到可能的帐户交易事件列表中(之前,我们已经在列表中添加了一些事件,但这些只是初步的常量声明):
//+------------------------------------------------------------------+ //| 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 a market 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_ORDER_STOP_LOSS, // Changing order's StopLoss TRADE_EVENT_MODIFY_ORDER_TAKE_PROFIT, // Changing order's TakeProfit TRADE_EVENT_MODIFY_POSITION_STOP_LOSS_TAKE_PROFIT, // Changing position's StopLoss and TakeProfit TRADE_EVENT_MODIFY_POSITION_STOP_LOSS, // Changing position's StopLoss TRADE_EVENT_MODIFY_POSITION_TAKE_PROFIT, // Changing position's TakeProfit }; //+------------------------------------------------------------------+
由于我们要开发自 CEvent 抽象事件类派生的新类,我们需要为新的派生类设置另一个事件状态 — “修改”。
我们将它添加到事件状态枚举列表中:
//+------------------------------------------------------------------+ //| Event status | //+------------------------------------------------------------------+ enum ENUM_EVENT_STATUS { EVENT_STATUS_MARKET_POSITION, // Market position event (opening, partial opening, partial closing, adding volume, reversal) EVENT_STATUS_MARKET_PENDING, // Market pending order event (placing) EVENT_STATUS_HISTORY_PENDING, // Historical pending order event (removal) EVENT_STATUS_HISTORY_POSITION, // Historical position event (closing EVENT_STATUS_BALANCE, // Balance operation event (accruing balance, withdrawing funds and events from the ENUM_DEAL_TYPE enumeration) EVENT_STATUS_MODIFY // Order/position modification event }; //+------------------------------------------------------------------+
我们将“修改”事件的原因添加到事件原因枚举列表中:
//+------------------------------------------------------------------+ //| 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_MODIFY, // Modification 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, // Partial closing by an opposite position EVENT_REASON_DONE_PARTIALLY_BY_POS_PARTIALLY, // Closing 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) //+------------------------------------------------------------------+
如果我们想要始终觉察订单/持仓属性的变化,我们应该在修改之前添加订单/持仓属性价格(修改后的价格取自存在的属性),和在事件期间保存当前价格的整数型事件属性,以及将事件的实数型属性数量从 10 改为 15,并添加搜索和排序期间的未用到属性数量(在搜索和排序时,不会用到修改事件过程中的修改数据和价格):
//+------------------------------------------------------------------+ //| Event's real properties | //+------------------------------------------------------------------+ enum ENUM_EVENT_PROP_DOUBLE { EVENT_PROP_PRICE_EVENT = EVENT_PROP_INTEGER_TOTAL, // Price an event occurred at EVENT_PROP_PRICE_OPEN, // Order/deal/position open price EVENT_PROP_PRICE_CLOSE, // Order/deal/position close price EVENT_PROP_PRICE_SL, // StopLoss order/deal/position price EVENT_PROP_PRICE_TP, // TakeProfit Order/deal/position EVENT_PROP_VOLUME_ORDER_INITIAL, // Requested order volume EVENT_PROP_VOLUME_ORDER_EXECUTED, // Executed order volume EVENT_PROP_VOLUME_ORDER_CURRENT, // Remaining order volume EVENT_PROP_VOLUME_POSITION_EXECUTED, // Current executed position volume after a deal EVENT_PROP_PROFIT, // Profit //--- EVENT_PROP_PRICE_OPEN_BEFORE, // Order price before modification EVENT_PROP_PRICE_SL_BEFORE, // StopLoss price before modification EVENT_PROP_PRICE_TP_BEFORE, // TakeProfit price before modification EVENT_PROP_PRICE_EVENT_ASK, // Ask price during an event EVENT_PROP_PRICE_EVENT_BID, // Bid price during an event }; #define EVENT_PROP_DOUBLE_TOTAL (15) // Total number of event's real properties #define EVENT_PROP_DOUBLE_SKIP (5) // Number of order properties not used in sorting //+------------------------------------------------------------------+
我们修改相应宏替换的计算,以便在事件排序条件的枚举中正确查找事件的第一个字符串型属性的索引:
//+------------------------------------------------------------------+ //| Possible event sorting criteria | //+------------------------------------------------------------------+ #define FIRST_EVN_DBL_PROP (EVENT_PROP_INTEGER_TOTAL-EVENT_PROP_INTEGER_SKIP) #define FIRST_EVN_STR_PROP (EVENT_PROP_INTEGER_TOTAL-EVENT_PROP_INTEGER_SKIP+EVENT_PROP_DOUBLE_TOTAL-EVENT_PROP_DOUBLE_SKIP) enum ENUM_SORT_EVENTS_MODE { //--- Sort by integer properties SORT_BY_EVENT_TYPE_EVENT = 0, // Sort by event type SORT_BY_EVENT_TIME_EVENT = 1, // Sort by event time SORT_BY_EVENT_STATUS_EVENT = 2, // Sort by event status (from the ENUM_EVENT_STATUS enumeration) SORT_BY_EVENT_REASON_EVENT = 3, // Sort by event reason (from the ENUM_EVENT_REASON enumeration) SORT_BY_EVENT_TYPE_DEAL_EVENT = 4, // Sort by deal event type SORT_BY_EVENT_TICKET_DEAL_EVENT = 5, // Sort by deal event ticket SORT_BY_EVENT_TYPE_ORDER_EVENT = 6, // Sort by type of an order, based on which a deal event is opened (the last position order) SORT_BY_EVENT_TICKET_ORDER_EVENT = 7, // Sort by type of an order, based on which a position deal is opened (the first position order) SORT_BY_EVENT_TIME_ORDER_POSITION = 8, // Sort by time of an order, based on which a position deal is opened (the first position order) SORT_BY_EVENT_TYPE_ORDER_POSITION = 9, // Sort by type of an order, based on which a position deal is opened (the first position order) SORT_BY_EVENT_TICKET_ORDER_POSITION = 10, // Sort by a ticket of an order, based on which a position deal is opened (the first position order) SORT_BY_EVENT_POSITION_ID = 11, // Sort by position ID SORT_BY_EVENT_POSITION_BY_ID = 12, // Sort by opposite position ID SORT_BY_EVENT_MAGIC_ORDER = 13, // Sort by order/deal/position magic number SORT_BY_EVENT_MAGIC_BY_ID = 14, // Sort by opposite position magic number //--- Sort by real properties SORT_BY_EVENT_PRICE_EVENT = FIRST_EVN_DBL_PROP, // Sort by a price an event occurred at SORT_BY_EVENT_PRICE_OPEN = FIRST_EVN_DBL_PROP+1, // Sort by position open price SORT_BY_EVENT_PRICE_CLOSE = FIRST_EVN_DBL_PROP+2, // Sort by position close price SORT_BY_EVENT_PRICE_SL = FIRST_EVN_DBL_PROP+3, // Sort by position's StopLoss price SORT_BY_EVENT_PRICE_TP = FIRST_EVN_DBL_PROP+4, // Sort by position's TakeProfit price SORT_BY_EVENT_VOLUME_ORDER_INITIAL = FIRST_EVN_DBL_PROP+5, // Sort by initial volume SORT_BY_EVENT_VOLUME_ORDER_EXECUTED = FIRST_EVN_DBL_PROP+6, // Sort by the current volume SORT_BY_EVENT_VOLUME_ORDER_CURRENT = FIRST_EVN_DBL_PROP+7, // Sort by remaining volume SORT_BY_EVENT_VOLUME_POSITION_EXECUTED = FIRST_EVN_DBL_PROP+8, // Sort by remaining volume SORT_BY_EVENT_PROFIT = FIRST_EVN_DBL_PROP+9, // Sort by profit //--- Sort by string properties SORT_BY_EVENT_SYMBOL = FIRST_EVN_STR_PROP, // Sort by order/position/deal symbol SORT_BY_EVENT_SYMBOL_BY_ID // Sort by an opposite position symbol }; //+------------------------------------------------------------------+
我们改进 CEvent 抽象事件类。
由于我们会在日志中显示 CEvent 抽象事件派生类的数据,我们需要知道事件发生时相应品种报价中的小数位数 — 品种的 Digits()。 为了免于在每个派生类中每次都接收它,我们只需在父类中获取一次。
在类的私有部分中,声明用于存储事件品种的 Digits() 数值的类成员变量,并且在类构造函数的初始化列表中初始化该变量:
//+------------------------------------------------------------------+ //| Abstract event class | //+------------------------------------------------------------------+ class CEvent : public CObject { private: int m_event_code; // Event code //--- Return the index of the array the event's (1) double and (2) string properties are located at int IndexProp(ENUM_EVENT_PROP_DOUBLE property)const { return(int)property-EVENT_PROP_INTEGER_TOTAL; } int IndexProp(ENUM_EVENT_PROP_STRING property)const { return(int)property-EVENT_PROP_INTEGER_TOTAL-EVENT_PROP_DOUBLE_TOTAL; } protected: ENUM_TRADE_EVENT m_trade_event; // Trading event bool m_is_hedge; // Hedging account flag long m_chart_id; // Control program chart ID int m_digits; // Symbol Digits() int m_digits_acc; // Number of decimal places for the account currency long m_long_prop[EVENT_PROP_INTEGER_TOTAL]; // Event integer properties double m_double_prop[EVENT_PROP_DOUBLE_TOTAL]; // Event real properties string m_string_prop[EVENT_PROP_STRING_TOTAL]; // Event string properties //--- Return the flag presence in the trading event bool IsPresentEventFlag(const int event_code) const { return (this.m_event_code & event_code)==event_code; } //--- Protected parametric constructor CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket); public: //--- Default constructor CEvent(void){;} //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CEvent::CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket) : m_event_code(event_code),m_digits(0) { this.m_long_prop[EVENT_PROP_STATUS_EVENT] = event_status; this.m_long_prop[EVENT_PROP_TICKET_ORDER_EVENT] = (long)ticket; this.m_is_hedge=bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); this.m_digits_acc=(int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS); this.m_chart_id=::ChartID(); } //+------------------------------------------------------------------+
将新事件属性的描述添加到返回整数型和实数型属性描述的方法中:
//+------------------------------------------------------------------+ //| Return the description of the event's integer property | //+------------------------------------------------------------------+ string CEvent::GetPropertyDescription(ENUM_EVENT_PROP_INTEGER property) { return ( property==EVENT_PROP_TYPE_EVENT ? TextByLanguage("Тип события","Event's type")+": "+this.TypeEventDescription() : property==EVENT_PROP_TIME_EVENT ? TextByLanguage("Время события","Time of event")+": "+TimeMSCtoString(this.GetProperty(property)) : property==EVENT_PROP_STATUS_EVENT ? TextByLanguage("Статус события","Status of event")+": \""+this.StatusDescription()+"\"" : property==EVENT_PROP_REASON_EVENT ? TextByLanguage("Причина события","Reason of event")+": "+this.ReasonDescription() : property==EVENT_PROP_TYPE_DEAL_EVENT ? TextByLanguage("Тип сделки","Deal's type")+": "+DealTypeDescription((ENUM_DEAL_TYPE)this.GetProperty(property)) : property==EVENT_PROP_TICKET_DEAL_EVENT ? TextByLanguage("Тикет сделки","Deal's ticket")+": #"+(string)this.GetProperty(property) : property==EVENT_PROP_TYPE_ORDER_EVENT ? TextByLanguage("Тип ордера события","Event's order type")+": "+OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(property)) : property==EVENT_PROP_TYPE_ORDER_POSITION ? TextByLanguage("Тип ордера позиции","Position's order type")+": "+OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(property)) : property==EVENT_PROP_TICKET_ORDER_POSITION ? TextByLanguage("Тикет первого ордера позиции","Position's first order ticket")+": #"+(string)this.GetProperty(property) : property==EVENT_PROP_TICKET_ORDER_EVENT ? TextByLanguage("Тикет ордера события","Event's order ticket")+": #"+(string)this.GetProperty(property) : property==EVENT_PROP_POSITION_ID ? TextByLanguage("Идентификатор позиции","Position ID")+": #"+(string)this.GetProperty(property) : property==EVENT_PROP_POSITION_BY_ID ? TextByLanguage("Идентификатор встречной позиции","Opposite position's ID")+": #"+(string)this.GetProperty(property) : property==EVENT_PROP_MAGIC_ORDER ? TextByLanguage("Магический номер","Magic number")+": "+(string)this.GetProperty(property) : property==EVENT_PROP_MAGIC_BY_ID ? TextByLanguage("Магический номер встречной позиции","Magic number of opposite position")+": "+(string)this.GetProperty(property) : property==EVENT_PROP_TIME_ORDER_POSITION ? TextByLanguage("Время открытия позиции","Position's opened time")+": "+TimeMSCtoString(this.GetProperty(property)) : property==EVENT_PROP_TYPE_ORD_POS_BEFORE ? TextByLanguage("Тип ордера позиции до смены направления","Type order of position before changing direction")+": "+OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(property)) : property==EVENT_PROP_TICKET_ORD_POS_BEFORE ? TextByLanguage("Тикет ордера позиции до смены направления","Ticket order of position before changing direction")+": #"+(string)this.GetProperty(property) : property==EVENT_PROP_TYPE_ORD_POS_CURRENT ? TextByLanguage("Тип ордера текущей позиции","Type order of current position")+": "+OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(property)) : property==EVENT_PROP_TICKET_ORD_POS_CURRENT ? TextByLanguage("Тикет ордера текущей позиции","Ticket order of current position")+": #"+(string)this.GetProperty(property) : EnumToString(property) ); } //+------------------------------------------------------------------+ //| Return the description of the event's real property | //+------------------------------------------------------------------+ string CEvent::GetPropertyDescription(ENUM_EVENT_PROP_DOUBLE property) { int dg=(int)::SymbolInfoInteger(this.GetProperty(EVENT_PROP_SYMBOL),SYMBOL_DIGITS); int dgl=(int)DigitsLots(this.GetProperty(EVENT_PROP_SYMBOL)); return ( property==EVENT_PROP_PRICE_EVENT ? TextByLanguage("Цена на момент события","Price at the time of event")+": "+::DoubleToString(this.GetProperty(property),dg) : property==EVENT_PROP_PRICE_OPEN ? TextByLanguage("Цена открытия","Open price")+": "+::DoubleToString(this.GetProperty(property),dg) : property==EVENT_PROP_PRICE_CLOSE ? TextByLanguage("Цена закрытия","Close price")+": "+::DoubleToString(this.GetProperty(property),dg) : property==EVENT_PROP_PRICE_SL ? TextByLanguage("Цена StopLoss","StopLoss price")+": "+::DoubleToString(this.GetProperty(property),dg) : property==EVENT_PROP_PRICE_TP ? TextByLanguage("Цена TakeProfit","TakeProfit price")+": "+::DoubleToString(this.GetProperty(property),dg) : property==EVENT_PROP_VOLUME_ORDER_INITIAL ? TextByLanguage("Начальный объём ордера","Order initial volume")+": "+::DoubleToString(this.GetProperty(property),dgl) : property==EVENT_PROP_VOLUME_ORDER_EXECUTED ? TextByLanguage("Исполненный объём ордера","Order executed volume")+": "+::DoubleToString(this.GetProperty(property),dgl) : property==EVENT_PROP_VOLUME_ORDER_CURRENT ? TextByLanguage("Оставшийся объём ордера","Order remaining volume")+": "+::DoubleToString(this.GetProperty(property),dgl) : property==EVENT_PROP_VOLUME_POSITION_EXECUTED ? TextByLanguage("Текущий объём позиции","Position current volume")+": "+::DoubleToString(this.GetProperty(property),dgl) : property==EVENT_PROP_PROFIT ? TextByLanguage("Профит","Profit")+": "+::DoubleToString(this.GetProperty(property),this.m_digits_acc) : property==EVENT_PROP_PRICE_OPEN_BEFORE ? TextByLanguage("Цена открытия до модификации","Price open before modification")+": "+::DoubleToString(this.GetProperty(property),dg) : property==EVENT_PROP_PRICE_SL_BEFORE ? TextByLanguage("Цена StopLoss до модификации","StopLoss price before modification")+": "+::DoubleToString(this.GetProperty(property),dg) : property==EVENT_PROP_PRICE_TP_BEFORE ? TextByLanguage("Цена TakeProfit до модификации","TakeProfit price before modification")+": "+::DoubleToString(this.GetProperty(property),dg) : property==EVENT_PROP_PRICE_EVENT_ASK ? TextByLanguage("Цена Ask в момент события","Ask price at the time of event")+": "+::DoubleToString(this.GetProperty(property),dg) : property==EVENT_PROP_PRICE_EVENT_BID ? TextByLanguage("Цена Bid в момент события","Bid price at the time of event")+": "+::DoubleToString(this.GetProperty(property),dg) : EnumToString(property) ); } //+------------------------------------------------------------------+
我们在返回交易事件名称的方法中添加事件缺失描述,新添加事件的描述和未知事件的描述:
//+------------------------------------------------------------------+ //| Return the trading event name | //+------------------------------------------------------------------+ string CEvent::TypeEventDescription(void) const { ENUM_TRADE_EVENT event=this.TypeEvent(); return ( event==TRADE_EVENT_NO_EVENT ? TextByLanguage("Нет торгового события","No trading event") : event==TRADE_EVENT_PENDING_ORDER_PLASED ? TextByLanguage("Отложенный ордер установлен","Pending order placed") : event==TRADE_EVENT_PENDING_ORDER_REMOVED ? TextByLanguage("Отложенный ордер удалён","Pending order removed") : event==TRADE_EVENT_ACCOUNT_CREDIT ? TextByLanguage("Начисление кредита","Credit") : event==TRADE_EVENT_ACCOUNT_CHARGE ? TextByLanguage("Дополнительные сборы","Additional charge") : event==TRADE_EVENT_ACCOUNT_CORRECTION ? TextByLanguage("Корректирующая запись","Correction") : event==TRADE_EVENT_ACCOUNT_BONUS ? TextByLanguage("Перечисление бонусов","Bonus") : event==TRADE_EVENT_ACCOUNT_COMISSION ? TextByLanguage("Дополнительные комиссии","Additional commission") : event==TRADE_EVENT_ACCOUNT_COMISSION_DAILY ? TextByLanguage("Комиссия, начисляемая в конце торгового дня","Daily commission") : event==TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY ? TextByLanguage("Комиссия, начисляемая в конце месяца","Monthly commission") : event==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY ? TextByLanguage("Агентская комиссия, начисляемая в конце торгового дня","Daily agent commission") : event==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY ? TextByLanguage("Агентская комиссия, начисляемая в конце месяца","Monthly agent commission") : event==TRADE_EVENT_ACCOUNT_INTEREST ? TextByLanguage("Начисления процентов на свободные средства","Interest rate") : event==TRADE_EVENT_BUY_CANCELLED ? TextByLanguage("Отмененная сделка покупки","Canceled buy deal") : event==TRADE_EVENT_SELL_CANCELLED ? TextByLanguage("Отмененная сделка продажи","Canceled sell deal") : event==TRADE_EVENT_DIVIDENT ? TextByLanguage("Начисление дивиденда","Dividend operations") : event==TRADE_EVENT_DIVIDENT_FRANKED ? TextByLanguage("Начисление франкированного дивиденда","Franked (non-taxable) dividend operations") : event==TRADE_EVENT_TAX ? TextByLanguage("Начисление налога","Tax charges") : event==TRADE_EVENT_ACCOUNT_BALANCE_REFILL ? TextByLanguage("Пополнение средств на балансе","Balance refill") : event==TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL ? TextByLanguage("Снятие средств с баланса","Withdrawals") : event==TRADE_EVENT_PENDING_ORDER_ACTIVATED ? TextByLanguage("Отложенный ордер активирован ценой","Pending order activated") : event==TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL ? TextByLanguage("Отложенный ордер активирован ценой частично","Pending order activated partially") : event==TRADE_EVENT_POSITION_OPENED ? TextByLanguage("Позиция открыта","Position open") : event==TRADE_EVENT_POSITION_OPENED_PARTIAL ? TextByLanguage("Позиция открыта частично","Position open partially") : event==TRADE_EVENT_POSITION_CLOSED ? TextByLanguage("Позиция закрыта","Position closed") : event==TRADE_EVENT_POSITION_CLOSED_PARTIAL ? TextByLanguage("Позиция закрыта частично","Position closed partially") : event==TRADE_EVENT_POSITION_CLOSED_BY_POS ? TextByLanguage("Позиция закрыта встречной","Position closed by opposite position") : event==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS ? TextByLanguage("Позиция закрыта встречной частично","Position closed partially by opposite position") : event==TRADE_EVENT_POSITION_CLOSED_BY_SL ? TextByLanguage("Позиция закрыта по StopLoss","Position closed by StopLoss") : event==TRADE_EVENT_POSITION_CLOSED_BY_TP ? TextByLanguage("Позиция закрыта по TakeProfit","Position closed by TakeProfit") : event==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL ? TextByLanguage("Позиция закрыта частично по StopLoss","Position closed partially by StopLoss") : event==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP ? TextByLanguage("Позиция закрыта частично по TakeProfit","Position closed partially by TakeProfit") : event==TRADE_EVENT_POSITION_REVERSED_BY_MARKET ? TextByLanguage("Разворот позиции по рыночному запросу","Position reversal by market request") : event==TRADE_EVENT_POSITION_REVERSED_BY_PENDING ? TextByLanguage("Разворот позиции срабатыванием отложенного ордера","Position reversal by triggering pending order") : event==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET ? TextByLanguage("Добавлен объём к позиции по рыночному запросу","Added volume to position by market request") : event==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING ? TextByLanguage("Добавлен объём к позиции активацией отложенного ордера","Added volume to position by activation of pending order") : event==TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL ? TextByLanguage("Разворот позиции частичным исполнением запроса","Position reversal by partial completion of market request") : event==TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL ? TextByLanguage("Разворот позиции частичным срабатыванием отложенного ордера","Position reversal by partial activation of pending order") : event==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL ? TextByLanguage("Добавлен объём к позиции частичным исполнением запроса","Added volume to position by partial completion of market request") : event==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL ? TextByLanguage("Добавлен объём к позиции активацией отложенного ордера","Added volume to position by partial activation of pending order") : event==TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER ? TextByLanguage("Сработал StopLimit-ордер","StopLimit order triggered.") : event==TRADE_EVENT_MODIFY_ORDER_PRICE ? TextByLanguage("Модифицирована цена установки ордера ","Order price modified") : event==TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS ? TextByLanguage("Модифицированы цена установки и StopLoss ордера","Order price and StopLoss modified") : event==TRADE_EVENT_MODIFY_ORDER_PRICE_TAKE_PROFIT ? TextByLanguage("Модифицированы цена установки и TakeProfit ордера","Order price and TakeProfit modified") : event==TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT ? TextByLanguage("Модифицированы цена установки, StopLoss и TakeProfit ордера","Order price, StopLoss and TakeProfit modified") : event==TRADE_EVENT_MODIFY_ORDER_STOP_LOSS_TAKE_PROFIT ? TextByLanguage("Модифицированы цены StopLoss и TakeProfit ордера","Order StopLoss and TakeProfit modified") : event==TRADE_EVENT_MODIFY_ORDER_STOP_LOSS ? TextByLanguage("Модифицирован StopLoss ордера","Order StopLoss modified") : event==TRADE_EVENT_MODIFY_ORDER_TAKE_PROFIT ? TextByLanguage("Модифицирован TakeProfit ордера","Order TakeProfit modified") : event==TRADE_EVENT_MODIFY_POSITION_STOP_LOSS_TAKE_PROFIT ? TextByLanguage("Модифицированы цены StopLoss и TakeProfit позиции","Position StopLoss and TakeProfit modified") : event==TRADE_EVENT_MODIFY_POSITION_STOP_LOSS ? TextByLanguage("Модифицирован StopLoss позиции","Position StopLoss modified") : event==TRADE_EVENT_MODIFY_POSITION_TAKE_PROFIT ? TextByLanguage("Модифицирован TakeProfit позиции","Position TakeProfit modified") : EnumToString(event) ); } //+------------------------------------------------------------------+
在返回事件原因描述的方法里添加两个新原因:
//+------------------------------------------------------------------+ //| Return the name of the deal/order/position reason | //+------------------------------------------------------------------+ string CEvent::ReasonDescription(void) const { ENUM_EVENT_REASON reason=this.Reason(); return ( reason==EVENT_REASON_ACTIVATED_PENDING ? TextByLanguage("Активирован отложенный ордер","Pending order activated") : reason==EVENT_REASON_ACTIVATED_PENDING_PARTIALLY ? TextByLanguage("Частичное срабатывание отложенного ордера","Pending order partially triggered") : reason==EVENT_REASON_STOPLIMIT_TRIGGERED ? TextByLanguage("Срабатывание StopLimit-ордера","StopLimit order triggered") : reason==EVENT_REASON_MODIFY ? TextByLanguage("Модификация","Modified") : reason==EVENT_REASON_CANCEL ? TextByLanguage("Отмена","Canceled") : reason==EVENT_REASON_EXPIRED ? TextByLanguage("Истёк срок действия","Expired") : reason==EVENT_REASON_DONE ? TextByLanguage("Рыночный запрос, выполненный в полном объёме","Fully completed market request") : reason==EVENT_REASON_DONE_PARTIALLY ? TextByLanguage("Выполненный частично рыночный запрос","Partially completed market request") : reason==EVENT_REASON_VOLUME_ADD ? TextByLanguage("Добавлен объём к позиции","Added volume to position") : reason==EVENT_REASON_VOLUME_ADD_PARTIALLY ? TextByLanguage("Добавлен объём к позиции частичным исполнением заявки","Volume added to the position by request partial completion") : reason==EVENT_REASON_VOLUME_ADD_BY_PENDING ? TextByLanguage("Добавлен объём к позиции активацией отложенного ордера","Added volume to position by activating pending order") : reason==EVENT_REASON_VOLUME_ADD_BY_PENDING_PARTIALLY ? TextByLanguage("Добавлен объём к позиции частичной активацией отложенного ордера","Added volume to position by partial activation of pending order") : reason==EVENT_REASON_REVERSE ? TextByLanguage("Разворот позиции","Position reversal") : reason==EVENT_REASON_REVERSE_PARTIALLY ? TextByLanguage("Разворот позиции частичным исполнением заявки","Position reversal by partial completion of request") : reason==EVENT_REASON_REVERSE_BY_PENDING ? TextByLanguage("Разворот позиции при срабатывании отложенного ордера","Position reversal on triggered pending order") : reason==EVENT_REASON_REVERSE_BY_PENDING_PARTIALLY ? TextByLanguage("Разворот позиции при при частичном срабатывании отложенного ордера","Position reversal on partially triggered pending order") : reason==EVENT_REASON_DONE_SL ? TextByLanguage("Закрытие по StopLoss","Close by StopLoss triggered") : reason==EVENT_REASON_DONE_SL_PARTIALLY ? TextByLanguage("Частичное закрытие по StopLoss","Partial close by StopLoss triggered") : reason==EVENT_REASON_DONE_TP ? TextByLanguage("Закрытие по TakeProfit","Close by TakeProfit triggered") : reason==EVENT_REASON_DONE_TP_PARTIALLY ? TextByLanguage("Частичное закрытие по TakeProfit","Partial close by TakeProfit triggered") : reason==EVENT_REASON_DONE_BY_POS ? TextByLanguage("Закрытие встречной позицией","Closed by opposite position") : reason==EVENT_REASON_DONE_PARTIALLY_BY_POS ? TextByLanguage("Частичное закрытие встречной позицией","Closed partially by opposite position") : reason==EVENT_REASON_DONE_BY_POS_PARTIALLY ? TextByLanguage("Закрытие частью объёма встречной позиции","Closed by incomplete volume of opposite position") : reason==EVENT_REASON_DONE_PARTIALLY_BY_POS_PARTIALLY ? TextByLanguage("Частичное закрытие частью объёма встречной позиции","Closed partially by incomplete volume of opposite position") : reason==EVENT_REASON_BALANCE_REFILL ? TextByLanguage("Пополнение баланса","Balance refill") : reason==EVENT_REASON_BALANCE_WITHDRAWAL ? TextByLanguage("Снятие средств с баланса","Withdrawal from the balance") : reason==EVENT_REASON_ACCOUNT_CREDIT ? TextByLanguage("Начисление кредита","Credit") : reason==EVENT_REASON_ACCOUNT_CHARGE ? TextByLanguage("Дополнительные сборы","Additional charge") : reason==EVENT_REASON_ACCOUNT_CORRECTION ? TextByLanguage("Корректирующая запись","Correction") : reason==EVENT_REASON_ACCOUNT_BONUS ? TextByLanguage("Перечисление бонусов","Bonus") : reason==EVENT_REASON_ACCOUNT_COMISSION ? TextByLanguage("Дополнительные комиссии","Additional commission") : reason==EVENT_REASON_ACCOUNT_COMISSION_DAILY ? TextByLanguage("Комиссия, начисляемая в конце торгового дня","Daily commission") : reason==EVENT_REASON_ACCOUNT_COMISSION_MONTHLY ? TextByLanguage("Комиссия, начисляемая в конце месяца","Monthly commission") : reason==EVENT_REASON_ACCOUNT_COMISSION_AGENT_DAILY ? TextByLanguage("Агентская комиссия, начисляемая в конце торгового дня","Daily agent commission") : reason==EVENT_REASON_ACCOUNT_COMISSION_AGENT_MONTHLY ? TextByLanguage("Агентская комиссия, начисляемая в конце месяца","Monthly agent commission") : reason==EVENT_REASON_ACCOUNT_INTEREST ? TextByLanguage("Начисления процентов на свободные средства","Interest rate") : reason==EVENT_REASON_BUY_CANCELLED ? TextByLanguage("Отмененная сделка покупки","Canceled buy deal") : reason==EVENT_REASON_SELL_CANCELLED ? TextByLanguage("Отмененная сделка продажи","Canceled sell deal") : reason==EVENT_REASON_DIVIDENT ? TextByLanguage("Начисление дивиденда","Dividend operations") : reason==EVENT_REASON_DIVIDENT_FRANKED ? TextByLanguage("Начисление франкированного дивиденда","Franked (non-taxable) dividend operations") : reason==EVENT_REASON_TAX ? TextByLanguage("Начисление налога","Tax charges") : EnumToString(reason) ); } //+------------------------------------------------------------------+
在类的公有部分中的简化访问事件属性部分添加返回添加的新属性的方法:
//+------------------------------------------------------------------+ //| Methods of simplified access to event object properties | //+------------------------------------------------------------------+ //--- Return (1) event type, (2) event time in milliseconds, (3) event status, (4) event reason, (5) deal type, (6) deal ticket, //--- (7) order type, based on which a deal was executed, (8) position opening order type, (9) position last order ticket, //--- (10) position first order ticket, (11) position ID, (12) opposite position ID, (13) magic number, (14) opposite position magic number, (15) position open time ENUM_TRADE_EVENT TypeEvent(void) const { return (ENUM_TRADE_EVENT)this.GetProperty(EVENT_PROP_TYPE_EVENT); } long TimeEvent(void) const { return this.GetProperty(EVENT_PROP_TIME_EVENT); } ENUM_EVENT_STATUS Status(void) const { return (ENUM_EVENT_STATUS)this.GetProperty(EVENT_PROP_STATUS_EVENT); } ENUM_EVENT_REASON Reason(void) const { return (ENUM_EVENT_REASON)this.GetProperty(EVENT_PROP_REASON_EVENT); } ENUM_DEAL_TYPE TypeDeal(void) const { return (ENUM_DEAL_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT); } long TicketDeal(void) const { return this.GetProperty(EVENT_PROP_TICKET_DEAL_EVENT); } ENUM_ORDER_TYPE TypeOrderEvent(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_EVENT); } ENUM_ORDER_TYPE TypeFirstOrderPosition(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION); } long TicketOrderEvent(void) const { return this.GetProperty(EVENT_PROP_TICKET_ORDER_EVENT); } long TicketFirstOrderPosition(void) const { return this.GetProperty(EVENT_PROP_TICKET_ORDER_POSITION); } long PositionID(void) const { return this.GetProperty(EVENT_PROP_POSITION_ID); } long PositionByID(void) const { return this.GetProperty(EVENT_PROP_POSITION_BY_ID); } long Magic(void) const { return this.GetProperty(EVENT_PROP_MAGIC_ORDER); } long MagicCloseBy(void) const { return this.GetProperty(EVENT_PROP_MAGIC_BY_ID); } long TimePosition(void) const { return this.GetProperty(EVENT_PROP_TIME_ORDER_POSITION); } //--- When changing position direction, return (1) previous position order type, (2) previous position order ticket //--- (3) current position order type, (4) current position order ticket //--- (5) position type and (6) ticket before changing direction, (7) position type and (8) ticket after changing direction ENUM_ORDER_TYPE TypeOrderPosPrevious(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORD_POS_BEFORE); } long TicketOrderPosPrevious(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TICKET_ORD_POS_BEFORE); } ENUM_ORDER_TYPE TypeOrderPosCurrent(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORD_POS_CURRENT); } long TicketOrderPosCurrent(void) const { return (ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TICKET_ORD_POS_CURRENT);} ENUM_POSITION_TYPE TypePositionPrevious(void) const { return PositionTypeByOrderType(this.TypeOrderPosPrevious()); } ulong TicketPositionPrevious(void) const { return this.TicketOrderPosPrevious(); } ENUM_POSITION_TYPE TypePositionCurrent(void) const { return PositionTypeByOrderType(this.TypeOrderPosCurrent()); } ulong TicketPositionCurrent(void) const { return this.TicketOrderPosCurrent(); } //--- Return (1) the price the event occurred at, (2) open price, (3) close price, //--- (4) StopLoss price, (5) TakeProfit price, (6) profit, (7) requested order volume, //--- (8) executed order volume, (9) remaining order volume, (10) executed position volume double PriceEvent(void) const { return this.GetProperty(EVENT_PROP_PRICE_EVENT); } double PriceOpen(void) const { return this.GetProperty(EVENT_PROP_PRICE_OPEN); } double PriceClose(void) const { return this.GetProperty(EVENT_PROP_PRICE_CLOSE); } double PriceStopLoss(void) const { return this.GetProperty(EVENT_PROP_PRICE_SL); } double PriceTakeProfit(void) const { return this.GetProperty(EVENT_PROP_PRICE_TP); } double Profit(void) const { return this.GetProperty(EVENT_PROP_PROFIT); } double VolumeOrderInitial(void) const { return this.GetProperty(EVENT_PROP_VOLUME_ORDER_INITIAL); } double VolumeOrderExecuted(void) const { return this.GetProperty(EVENT_PROP_VOLUME_ORDER_EXECUTED); } double VolumeOrderCurrent(void) const { return this.GetProperty(EVENT_PROP_VOLUME_ORDER_CURRENT); } double VolumePositionExecuted(void) const { return this.GetProperty(EVENT_PROP_VOLUME_POSITION_EXECUTED); } //--- When modifying prices, return (1) order price, (2) StopLoss and (3) TakeProfit before modification double PriceOpenBefore(void) const { return this.GetProperty(EVENT_PROP_PRICE_OPEN_BEFORE); } double PriceStopLossBefore(void) const { return this.GetProperty(EVENT_PROP_PRICE_SL_BEFORE); } double PriceTakeProfitBefore(void) const { return this.GetProperty(EVENT_PROP_PRICE_TP_BEFORE); } double PriceEventAsk(void) const { return this.GetProperty(EVENT_PROP_PRICE_EVENT_ASK); } double PriceEventBid(void) const { return this.GetProperty(EVENT_PROP_PRICE_EVENT_BID); } //--- Return the (1) symbol and (2) opposite position symbol string Symbol(void) const { return this.GetProperty(EVENT_PROP_SYMBOL); } string SymbolCloseBy(void) const { return this.GetProperty(EVENT_PROP_SYMBOL_BY_ID); } //+------------------------------------------------------------------+
由于大多数类的属性都是在事件集合类的 CreateNewEvent() 方法中填充,然后通过调用 CEvent 类的 SetTypeEvent() 方法设置事件类型,在 CEvent 类中设置品种的 Digits(),以及在 SetTypeEvent() 方法中设置定义的修改事件:
//+------------------------------------------------------------------+ //| Decode the event code and set a trading event | //+------------------------------------------------------------------+ void CEvent::SetTypeEvent(void) { //--- Set event symbol Digits() this.m_digits=(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS); //--- Pending order placed (check for matching the event code since there can only be one flag here) if(this.m_event_code==TRADE_EVENT_FLAG_ORDER_PLASED) { this.m_trade_event=TRADE_EVENT_PENDING_ORDER_PLASED; this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } //--- Pending order removed (check for matching the event code since there can only be one flag here) if(this.m_event_code==TRADE_EVENT_FLAG_ORDER_REMOVED) { this.m_trade_event=TRADE_EVENT_PENDING_ORDER_REMOVED; this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } //--- Pending order is modified if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_ORDER_MODIFY)) { //--- If the placement price is modified if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_PRICE)) { this.m_trade_event=TRADE_EVENT_MODIFY_ORDER_PRICE; //--- If StopLoss and TakeProfit are modified if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL) && this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP)) this.m_trade_event=TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT; //--- If StopLoss is modified else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL)) this.m_trade_event=TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS; //--- If TakeProfit is modified else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP)) this.m_trade_event=TRADE_EVENT_MODIFY_ORDER_PRICE_TAKE_PROFIT; } //--- If the placement price is not modified else { //--- If StopLoss and TakeProfit are modified if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL) && this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP)) this.m_trade_event=TRADE_EVENT_MODIFY_ORDER_STOP_LOSS_TAKE_PROFIT; //--- If StopLoss is modified else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL)) this.m_trade_event=TRADE_EVENT_MODIFY_ORDER_STOP_LOSS; //--- If TakeProfit is modified else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP)) this.m_trade_event=TRADE_EVENT_MODIFY_ORDER_TAKE_PROFIT; } this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } //--- If a position is modified if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_MODIFY)) { //--- If StopLoss and TakeProfit are modified if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL) && this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP)) this.m_trade_event=TRADE_EVENT_MODIFY_POSITION_STOP_LOSS_TAKE_PROFIT; //--- If StopLoss is modified else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL)) this.m_trade_event=TRADE_EVENT_MODIFY_POSITION_STOP_LOSS; //--- If TakeProfit is modified else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP)) this.m_trade_event=TRADE_EVENT_MODIFY_POSITION_TAKE_PROFIT; this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } //--- Position opened (Check the presence of multiple flags in the event code) if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_OPENED)) { //--- If an existing position is changed if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_CHANGED)) { //--- If a pending order is activated by a price if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_ORDER_ACTIVATED)) { //--- If this is a position reversal if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_REVERSE)) { //--- check the partial closure flag and set the //--- "position reversal by activation of a pending order" or "position reversal by partial activation of a pending order" trading event this.m_trade_event= ( !this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_REVERSED_BY_PENDING : TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL ); this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } //--- If this is adding a volume to a position else { //--- check the partial opening flag and set the //--- "added volume to a position by activating a pending order" or "added volume to a position by partially activating a pending order" trading event this.m_trade_event= ( !this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING : TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL ); this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } } //--- If a position was changed by a market deal else { //--- If this is a position reversal if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_REVERSE)) { //--- check the partial opening flag and set the "position reversal" or "position reversal by partial execution" trading event this.m_trade_event= ( !this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_REVERSED_BY_MARKET : TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL ); this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } //--- If this is adding a volume to a position else { //--- check the partial opening flag and set "added volume to a position" or "added volume to a position by partial execution" trading event this.m_trade_event= ( !this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET : TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL ); this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } } } //--- If a new position is opened else { //--- If a pending order is activated by a price if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_ORDER_ACTIVATED)) { //--- check the partial opening flag and set "pending order activated" or "pending order partially activated" trading event this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_PENDING_ORDER_ACTIVATED : TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL); this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } //--- check the partial opening flag and set the "Position opened" or "Position partially opened" trading event this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_OPENED : TRADE_EVENT_POSITION_OPENED_PARTIAL); this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } } //--- Position closed (Check the presence of multiple flags in the event code) if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_CLOSED)) { //--- if a position is closed by StopLoss if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL)) { //--- check the partial closing flag and set the "Position closed by StopLoss" or "Position partially closed by StopLoss" trading event this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_SL : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL); this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } //--- if a position is closed by TakeProfit else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP)) { //--- check the partial closure flag and set the "Position closed by TakeProfit" or "Position partially closed by TakeProfit" trading event this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_TP : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP); this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } //--- if a position is closed by an opposite one else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_BY_POS)) { //--- check the partial closure flag and set the "Position closed by opposite one" or "Position partially closed by opposite one" trading event this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_POS : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS); this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } //--- If a position is closed else { //--- check the partial closure flag and set the "Position closed" or "Position partially closed" trading event this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED : TRADE_EVENT_POSITION_CLOSED_PARTIAL); this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } } //--- Balance operation on the account (clarify the event by deal type) if(this.m_event_code==TRADE_EVENT_FLAG_ACCOUNT_BALANCE) { //--- Initialize a trading event this.m_trade_event=TRADE_EVENT_NO_EVENT; //--- Take a deal type ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT); //--- if a deal is a balance operation if(deal_type==DEAL_TYPE_BALANCE) { //--- check the deal profit and set an event (funds deposit or withdrawal) this.m_trade_event=(this.GetProperty(EVENT_PROP_PROFIT)>0 ? TRADE_EVENT_ACCOUNT_BALANCE_REFILL : TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL); } //--- The remaining balance operation types match the ENUM_DEAL_TYPE enumeration starting from DEAL_TYPE_CREDIT else if(deal_type>DEAL_TYPE_BALANCE) { //--- set the event this.m_trade_event=(ENUM_TRADE_EVENT)deal_type; } this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event); return; } } //+------------------------------------------------------------------+
方法清单中的代码注释描述了所有必要的检查和操作,因此对于已注释的操作无需赘述。 我相信,此处的一切都很简单易懂。
如此抽象事件类的改进既已完成。
展望未来,应当注意的是,在在测试 EA 中检查挂单的价格修改跟踪时,找到离价格最远的订单就很有必要了。 遍寻订单的属性,我意识到函数库对此没有快速和多方面的解决方案。 所以,我们将使用其中一个额外的整数型订单属性 — 以点数为单位的利润。
对于挂单,这是订单距价格的点数距离。 因此,为了找到距价格最远的订单,我们只需要查找“利润”点数(距离)最高的的订单。
这种情况类似于按方向搜索所有挂单。 为了查找与价格相距最远的挂单,我们在一个方向上选择所有订单,并按最大距离对获得的列表进行排序。
结果就是,我们从各种类型的订单中取得一笔订单,尽管在一个方向上(BuyLimit,BuyStop 和 BuyStopLimit 都是买入。 对于卖出来说则相反)。
我们在 Order.mqh 抽象订单类清单中修改按其方向获取订单类型的方法:
//+------------------------------------------------------------------+ //| Return order profit in points | //+------------------------------------------------------------------+ int COrder::ProfitInPoints(void) const { MqlTick tick={0}; string symbol=this.Symbol(); if(!::SymbolInfoTick(symbol,tick)) return 0; ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)this.TypeOrder(); double point=::SymbolInfoDouble(symbol,SYMBOL_POINT); if(type==ORDER_TYPE_CLOSE_BY || point==0) return 0; if(this.Status()==ORDER_STATUS_HISTORY_ORDER) return int(type==ORDER_TYPE_BUY ? (this.PriceClose()-this.PriceOpen())/point : type==ORDER_TYPE_SELL ? (this.PriceOpen()-this.PriceClose())/point : 0); else if(this.Status()==ORDER_STATUS_MARKET_POSITION) { if(type==ORDER_TYPE_BUY) return int((tick.bid-this.PriceOpen())/point); else if(type==ORDER_TYPE_SELL) return int((this.PriceOpen()-tick.ask)/point); } else if(this.Status()==ORDER_STATUS_MARKET_PENDING) { if(type==ORDER_TYPE_BUY_LIMIT || type==ORDER_TYPE_BUY_STOP || type==ORDER_TYPE_BUY_STOP_LIMIT) return (int)fabs((tick.bid-this.PriceOpen())/point); else if(type==ORDER_TYPE_SELL_LIMIT || type==ORDER_TYPE_SELL_STOP || type==ORDER_TYPE_SELL_STOP_LIMIT) return (int)fabs((this.PriceOpen()-tick.ask)/point); } return 0; } //+------------------------------------------------------------------+
此处,我们添加挂单检查,和返回自订单价格到当前价格的距离。
我们在抽象订单类的整数型属性的方法中添加显示当前价格距挂单价的点数距离:
//+------------------------------------------------------------------+ //| Return description of an order's integer property | //+------------------------------------------------------------------+ string COrder::GetPropertyDescription(ENUM_ORDER_PROP_INTEGER property) { return ( //--- General properties property==ORDER_PROP_MAGIC ? TextByLanguage("Магик","Magic")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+(string)this.GetProperty(property) ) : property==ORDER_PROP_TICKET ? TextByLanguage("Тикет","Ticket")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : " #"+(string)this.GetProperty(property) ) : property==ORDER_PROP_TICKET_FROM ? TextByLanguage("Тикет родительского ордера","Parent order ticket")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : " #"+(string)this.GetProperty(property) ) : property==ORDER_PROP_TICKET_TO ? TextByLanguage("Тикет наследуемого ордера","Inherited order ticket")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : " #"+(string)this.GetProperty(property) ) : property==ORDER_PROP_TIME_OPEN ? TextByLanguage("Время открытия","Time open")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS) ) : property==ORDER_PROP_TIME_CLOSE ? TextByLanguage("Время закрытия","Close time")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS) ) : property==ORDER_PROP_TIME_EXP ? TextByLanguage("Дата экспирации","Expiration date")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : (this.GetProperty(property)==0 ? TextByLanguage(": Не задана",": Not set") : ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS)) ) : property==ORDER_PROP_TYPE ? TextByLanguage("Тип","Type")+": "+this.TypeDescription() : property==ORDER_PROP_DIRECTION ? TextByLanguage("Тип по направлению","Type by direction")+": "+this.DirectionDescription() : property==ORDER_PROP_REASON ? TextByLanguage("Причина","Reason")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+this.GetReasonDescription(this.GetProperty(property)) ) : property==ORDER_PROP_POSITION_ID ? TextByLanguage("Идентификатор позиции","Position ID")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": #"+(string)this.GetProperty(property) ) : property==ORDER_PROP_DEAL_ORDER_TICKET ? TextByLanguage("Сделка на основании ордера с тикетом","Deal by order ticket")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": #"+(string)this.GetProperty(property) ) : property==ORDER_PROP_DEAL_ENTRY ? TextByLanguage("Направление сделки","Deal entry")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+this.GetEntryDescription(this.GetProperty(property)) ) : property==ORDER_PROP_POSITION_BY_ID ? TextByLanguage("Идентификатор встречной позиции","Opposite position ID")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+(string)this.GetProperty(property) ) : property==ORDER_PROP_TIME_OPEN_MSC ? TextByLanguage("Время открытия в милисекундах","Open time in milliseconds")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")" ) : property==ORDER_PROP_TIME_CLOSE_MSC ? TextByLanguage("Время закрытия в милисекундах","Close time in milliseconds")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")" ) : property==ORDER_PROP_TIME_UPDATE ? TextByLanguage("Время изменения позиции","Position change time")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+(this.GetProperty(property)!=0 ? ::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS) : "0") ) : property==ORDER_PROP_TIME_UPDATE_MSC ? TextByLanguage("Время изменения позиции в милисекундах","Time to change the position in milliseconds")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+(this.GetProperty(property)!=0 ? TimeMSCtoString(this.GetProperty(property))+" ("+(string)this.GetProperty(property)+")" : "0") ) : property==ORDER_PROP_STATE ? TextByLanguage("Состояние","Statе")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": \""+this.StateDescription()+"\"" ) : //--- Additional property property==ORDER_PROP_STATUS ? TextByLanguage("Статус","Status")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": \""+this.StatusDescription()+"\"" ) : property==ORDER_PROP_PROFIT_PT ? ( this.Status()==ORDER_STATUS_MARKET_PENDING ? TextByLanguage("Дистанция от цены в пунктах","Distance from price in points") : TextByLanguage("Прибыль в пунктах","Profit in points") )+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+(string)this.GetProperty(property) ) : property==ORDER_PROP_CLOSE_BY_SL ? TextByLanguage("Закрытие по StopLoss","Close by StopLoss")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+(this.GetProperty(property) ? TextByLanguage("Да","Yes") : TextByLanguage("Нет","No")) ) : property==ORDER_PROP_CLOSE_BY_TP ? TextByLanguage("Закрытие по TakeProfit","Close by TakeProfit")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+(this.GetProperty(property) ? TextByLanguage("Да","Yes") : TextByLanguage("Нет","No")) ) : property==ORDER_PROP_GROUP_ID ? TextByLanguage("Идентификатор группы","Group ID")+ (!this.SupportProperty(property) ? TextByLanguage(": Свойство не поддерживается",": Property not supported") : ": "+(string)this.GetProperty(property) ) : "" ); } //+------------------------------------------------------------------+
在此,我们检查订单状态,如果这是已存在挂单,则显示有关距离的消息,否则显示有关利润点数的消息。
抽象订单类的修改既已完成。
现在我们需要创建另一个继承自 CEvent 抽象事件类的类。 这是一个修改事件类。
在第六篇文章中实现了针对净持结算帐户的操作时,我们改进了开仓类事件:CEventPositionOpen 类现在具有创建短消息文本的方法,具体取决于事件状态和某些事件对象的存在属性。
在创建新的修改事件时,我们也会这样做 — 检查修改事件类型,并根据获取的类型创建事件文本。 此外,向控制程序所在图表发送事件时,我们需要在
EventChartCustom() 函数的 dparam
参数中定义所要传递的价格。 在开仓事件类中,我们使用此参数来传递开仓价,而在修改事件类中,可能有多个价格变动选项,我们需要
决定在用户事件的 dparam 参数中要发送的价格:
- 只能更改一笔订单价格 — 发送新的挂单价格,
- 订单和止损价格可以更改 — 发送新的挂单价格,
- 订单和止盈价格可以更改 — 发送新的挂单价格,
- 订单,止损和止盈价格可以更改 — 发送新的挂单价格,
- 订单止损可以更改 — 发送一个新的止损价格,
- 订单止盈可以更改 — 发送一个新的止盈价格,
- 持仓止损可以更改 — 发送持仓止损价格,
- 持仓止盈可以更改 — 发送持仓止盈价格,
- 持仓止损和止盈可以更改 — 发送开仓价,
我们可以看到,在更改单个价格时,我们将更改的价格传递给事件。 当同时更改若干个价格时,我们仅发送开仓或开单价(反过来也可以更改)。 在自定义程序中,您可以按所发生的修改事件的类型来声明每个价格(在同时修改期间)的变化。
在函数库的 \MQL5\Include\DoEasy\Objects\Events 文件夹的新 EventModify.mqh 文件中,创建新的 CEventModify 类。
将
CEvent 抽象事件类设置为它的基类。
不要忘记将抽象事件类的文件包含在修改类文件当中。
由于该类相对较小,我将在此提供完整清单供您研究。 我
在函数库论述的第六部分中所详述的类,与 CEventPositionOpen 的更改实现类似。
//+------------------------------------------------------------------+ //| EventModify.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 CEventModify : public CEvent { private: double m_price; // Price passed to an event //--- Create and return a brief event description string EventsMessage(void); public: //--- Constructor CEventModify(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_MODIFY,event_code,ticket),m_price(0) {} //--- 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 CEventModify::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 CEventModify::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 CEventModify::PrintShort(void) { ::Print(this.EventsMessage()); } //+------------------------------------------------------------------+ //| Send the event to the chart | //+------------------------------------------------------------------+ void CEventModify::SendEvent(void) { this.PrintShort(); ::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.TicketOrderEvent(),this.m_price,this.Symbol()); } //+------------------------------------------------------------------+ //| Create and return a brief event message | //+------------------------------------------------------------------+ string CEventModify::EventsMessage(void) { //--- (1) header, (2) magic number string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n"; string magic=(this.Magic()!=0 ? TextByLanguage(", магик ",", magic ")+(string)this.Magic() : ""); string text=""; //--- Pending order price is modified if(this.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE) { string order=OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderPosCurrent(); string price="["+::DoubleToString(this.PriceOpenBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceOpen(),this.m_digits)+"]"; text=order+TextByLanguage(": модифицирована цена: ",": modified price: ")+price+magic; this.m_price=this.PriceOpen(); } //--- Pending order price and StopLoss are modified else if(this.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS) { string order=OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderPosCurrent(); string price="["+::DoubleToString(this.PriceOpenBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceOpen(),this.m_digits)+"]"; string sl="["+::DoubleToString(this.PriceStopLossBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceStopLoss(),this.m_digits)+"]"; text=order+TextByLanguage(": модифицирована цена: ",": modified price: ")+price+TextByLanguage(" и"," and")+" StopLoss: "+sl+magic; this.m_price=this.PriceOpen(); } //--- Pending order price and TakeProfit are modified else if(this.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_TAKE_PROFIT) { string order=OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderPosCurrent(); string price="["+::DoubleToString(this.PriceOpenBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceOpen(),this.m_digits)+"]"; string tp="["+::DoubleToString(this.PriceTakeProfitBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceTakeProfit(),this.m_digits)+"]"; text=order+TextByLanguage(": модифицирована цена: ",": modified price: ")+price+TextByLanguage(" и"," and")+" TakeProfit: "+tp+magic; this.m_price=this.PriceOpen(); } //--- Pending order price, as well as its StopLoss and TakeProfit are modified else if(this.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT) { string order=OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderPosCurrent(); string price="["+::DoubleToString(this.PriceOpenBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceOpen(),this.m_digits)+"]"; string sl="["+::DoubleToString(this.PriceStopLossBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceStopLoss(),this.m_digits)+"]"; string tp="["+::DoubleToString(this.PriceTakeProfitBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceTakeProfit(),this.m_digits)+"]"; text=order+TextByLanguage(": модифицирована цена: ",": modified price: ")+price+", StopLoss: "+sl+TextByLanguage(" и"," and")+" TakeProfit: "+tp+magic; this.m_price=this.PriceOpen(); } //--- Pending order StopLoss is modified else if(this.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_STOP_LOSS) { string order=OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderPosCurrent(); string sl="["+::DoubleToString(this.PriceStopLossBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceStopLoss(),this.m_digits)+"]"; text=order+TextByLanguage(": модифицирован StopLoss: ",": modified StopLoss: ")+sl+magic; this.m_price=this.PriceStopLoss(); } //--- Pending order TakeProfit is modified else if(this.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_TAKE_PROFIT) { string order=OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderPosCurrent(); string tp="["+::DoubleToString(this.PriceTakeProfitBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceTakeProfit(),this.m_digits)+"]"; text=order+TextByLanguage(": модифицирован TakeProfit: ",": modified TakeProfit: ")+tp+magic; this.m_price=this.PriceTakeProfit(); } //--- Pending order StopLoss and TakeProfit are modified else if(this.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_STOP_LOSS_TAKE_PROFIT) { string order=OrderTypeDescription(this.TypeOrderPosCurrent())+" #"+(string)this.TicketOrderPosCurrent(); string sl="["+::DoubleToString(this.PriceStopLossBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceStopLoss(),this.m_digits)+"]"; string tp="["+::DoubleToString(this.PriceTakeProfitBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceTakeProfit(),this.m_digits)+"]"; text=order+TextByLanguage(": модифицирован StopLoss: ",": modified StopLoss: ")+sl+TextByLanguage(" и"," and")+" TakeProfit: "+tp+magic; this.m_price=this.PriceOpen(); } //--- Position StopLoss is modified else if(this.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_STOP_LOSS) { string order=PositionTypeDescription(this.TypePositionCurrent())+" #"+(string)this.TicketPositionCurrent(); string sl="["+::DoubleToString(this.PriceStopLossBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceStopLoss(),this.m_digits)+"]"; text=order+TextByLanguage(": модифицирован StopLoss: ",": modified StopLoss: ")+sl+magic; this.m_price=this.PriceStopLoss(); } //--- Position TakeProfit is modified else if(this.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_TAKE_PROFIT) { string order=PositionTypeDescription(this.TypePositionCurrent())+" #"+(string)this.TicketPositionCurrent(); string tp="["+::DoubleToString(this.PriceTakeProfitBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceTakeProfit(),this.m_digits)+"]"; text=order+TextByLanguage(": модифицирован TakeProfit: ",": modified TakeProfit: ")+tp+magic; this.m_price=this.PriceTakeProfit(); } //--- Position StopLoss and TakeProfit are modified else if(this.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_STOP_LOSS_TAKE_PROFIT) { string order=PositionTypeDescription(this.TypePositionCurrent())+" #"+(string)this.TicketPositionCurrent(); string sl="["+::DoubleToString(this.PriceStopLossBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceStopLoss(),this.m_digits)+"]"; string tp="["+::DoubleToString(this.PriceTakeProfitBefore(),this.m_digits)+" --> "+::DoubleToString(this.PriceTakeProfit(),this.m_digits)+"]"; text=order+TextByLanguage(": модифицирован StopLoss: ",": modified StopLoss: ")+sl+TextByLanguage(" и"," and")+" TakeProfit: "+tp+magic; this.m_price=this.PriceOpen(); } return head+this.Symbol()+" "+text; } //+------------------------------------------------------------------+
现在我们需要定义修改现有订单和持仓的事件,创建一个新事件,并将其添加到事件集合类的事件集合列表中。
我们针对来自函数库文件夹的 \MQL5\Include\DoEasy\Collections 的 EventsCollection.mqh 文件中的 CEventsCollection 类进行必要的改进。
包含新修改事件类的文件。
在类的私有部分,声明类成员变量
— 存储即时报价数据的结构。 它用于获取最后修改事件价格的数据。
//+------------------------------------------------------------------+ //| 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" #include "..\Objects\Events\EventModify.mqh" //+------------------------------------------------------------------+ //| Account event collection | //+------------------------------------------------------------------+ class CEventsCollection : public CListObj { private: CListObj m_list_events; // Event list bool m_is_hedge; // Hedging 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
在类的构造函数中初始化即时报价结构:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CEventsCollection::CEventsCollection(void) : m_trade_event(TRADE_EVENT_NO_EVENT),m_trade_event_code(TRADE_EVENT_FLAG_NO_EVENT) { this.m_list_events.Clear(); this.m_list_events.Sort(SORT_BY_EVENT_TIME_EVENT); this.m_list_events.Type(COLLECTION_EVENTS_ID); this.m_is_hedge=bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); this.m_chart_id=::ChartID(); ::ZeroMemory(this.m_tick); } //+------------------------------------------------------------------+
在函数库论述的第七部分中,我们开发了一段重载方法用于创建新事件。 现在我们有两个方法 —
当帐户中订单和持仓数量更改时创建事件的方法,以及在现有订单或持仓更改(修改)时创建新事件的方法。
应改进第二种方法,以便能够跟踪订单和持仓修改事件(在第七部分中,该方法仅处理 StopLimit 订单激活事件)。
我们添加处理订单/持仓修改事件、以及在修改之前保存订单/持仓属性的代码:
//+------------------------------------------------------------------+ //| Create a trading event depending on the order change type | //+------------------------------------------------------------------+ void CEventsCollection::CreateNewEvent(COrderControl* order) { if(!::SymbolInfoTick(order.Symbol(),this.m_tick)) { Print(DFUN,TextByLanguage("Не удалось получить текущие цены по символу события ","Failed to get current prices by event symbol "),order.Symbol()); return; } CEvent* event=NULL; //--- Pending StopLimit order activated 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()); } //--- Modification else { //--- Pending order price is modified if(order.GetChangeType()==CHANGE_TYPE_ORDER_PRICE) this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_MODIFY+TRADE_EVENT_FLAG_PRICE; //--- Pending order price and StopLoss are modified else if(order.GetChangeType()==CHANGE_TYPE_ORDER_PRICE_STOP_LOSS) this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_MODIFY+TRADE_EVENT_FLAG_PRICE+TRADE_EVENT_FLAG_SL; //--- Pending order price and TakeProfit are modified else if(order.GetChangeType()==CHANGE_TYPE_ORDER_PRICE_TAKE_PROFIT) this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_MODIFY+TRADE_EVENT_FLAG_PRICE+TRADE_EVENT_FLAG_TP; //--- Pending order price, as well as its StopLoss and TakeProfit are modified else if(order.GetChangeType()==CHANGE_TYPE_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT) this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_MODIFY+TRADE_EVENT_FLAG_PRICE+TRADE_EVENT_FLAG_SL+TRADE_EVENT_FLAG_TP; //--- Pending order StopLoss is modified else if(order.GetChangeType()==CHANGE_TYPE_ORDER_STOP_LOSS) this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_MODIFY+TRADE_EVENT_FLAG_SL; //--- Pending order TakeProfit is modified else if(order.GetChangeType()==CHANGE_TYPE_ORDER_TAKE_PROFIT) this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_MODIFY+TRADE_EVENT_FLAG_TP; //--- Pending order StopLoss and TakeProfit are modified else if(order.GetChangeType()==CHANGE_TYPE_ORDER_STOP_LOSS_TAKE_PROFIT) this.m_trade_event_code=TRADE_EVENT_FLAG_ORDER_MODIFY+TRADE_EVENT_FLAG_SL+TRADE_EVENT_FLAG_TP; //--- Position StopLoss is modified else if(order.GetChangeType()==CHANGE_TYPE_POSITION_STOP_LOSS) this.m_trade_event_code=TRADE_EVENT_FLAG_POSITION_MODIFY+TRADE_EVENT_FLAG_SL; //--- Position TakeProfit is modified else if(order.GetChangeType()==CHANGE_TYPE_POSITION_TAKE_PROFIT) this.m_trade_event_code=TRADE_EVENT_FLAG_POSITION_MODIFY+TRADE_EVENT_FLAG_TP; //--- Position StopLoss and TakeProfit are modified else if(order.GetChangeType()==CHANGE_TYPE_POSITION_STOP_LOSS_TAKE_PROFIT) this.m_trade_event_code=TRADE_EVENT_FLAG_POSITION_MODIFY+TRADE_EVENT_FLAG_SL+TRADE_EVENT_FLAG_TP; //--- Create a modification event event=new CEventModify(this.m_trade_event_code,order.Ticket()); } //--- Create an event 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,PositionTypeByOrderType((ENUM_ORDER_TYPE)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()); // First position order type event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket()); // First position 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_PRICE_OPEN_BEFORE,order.PricePrev()); // Order price before modification event.SetProperty(EVENT_PROP_PRICE_SL_BEFORE,order.StopLossPrev()); // StopLoss price before modification event.SetProperty(EVENT_PROP_PRICE_TP_BEFORE,order.TakeProfitPrev()); // TakeProfit price 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 magic number event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimePrev()); // Position first order time event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PricePrev()); // Price the event occurred at event.SetProperty(EVENT_PROP_PRICE_OPEN,order.Price()); // Order placement price event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.Price()); // Order close price event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss()); // Order StopLoss price event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit()); // Order TakeProfit 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; } } } //+------------------------------------------------------------------+
各种修改类型的处理条件相对容易,并已在代码注释中阐述。 依据订单/持仓更改类型,使用一组标志创建事件代码。 在创建新的修改事件时,代码将发送到 CEventModify 类构造函数。
由颜色标记的代码块用于保存新订单/持仓属性,它将被添加到所有保存持仓/订单属性的类方法中。 我们不会在此赘述,因为它们的代码是相同的。 可以在下面的附件中找到它们。
现在,已有订单和持仓修改事件的测试全都准备就绪。
测试订单和持仓修改
若要执行测试,我们需要修补第七篇文章中已有的一组测试 EA 按钮。
我们再加上三个按钮和它们的按键处理程序:设置止损,设置止盈,和全部尾随。
若未设止损/止盈,前两个按钮为所有订单/持仓设置止损和止盈,而第三个按钮将有两个状态 — 启用/禁用,即按下它时,按钮保持按下状态,两个尾随函数开始工作。 结果就是,EA 随价格变化开始尾随所有持仓和所有挂单的止损价位。
再次按下按钮会禁用两个尾随。
我们将 \MQL5\Experts\TestDoEasy\Part07 中的 TestDoEasyPart07.mq5 EA 保存到新的 \MQL5\Experts\TestDoEasy\Part08 文件夹中,并命名为 TestDoEasyPart08.mq5。
在按钮枚举中添加三个新常量,并在相应的宏替换中将按钮总数从 17 更改为 20:
//--- enums enum ENUM_BUTTONS { BUTT_BUY, BUTT_BUY_LIMIT, BUTT_BUY_STOP, BUTT_BUY_STOP_LIMIT, BUTT_CLOSE_BUY, BUTT_CLOSE_BUY2, BUTT_CLOSE_BUY_BY_SELL, BUTT_SELL, BUTT_SELL_LIMIT, BUTT_SELL_STOP, BUTT_SELL_STOP_LIMIT, BUTT_CLOSE_SELL, BUTT_CLOSE_SELL2, BUTT_CLOSE_SELL_BY_BUY, BUTT_DELETE_PENDING, BUTT_CLOSE_ALL, BUTT_PROFIT_WITHDRAWAL, BUTT_SET_STOP_LOSS, BUTT_SET_TAKE_PROFIT, BUTT_TRAILING_ALL }; #define TOTAL_BUTT (20)
在输入中添加指定止损距价格的距离、尾随步进、开始尾随的利润点数的变量,以及单击相应按钮(InpStopLoss
和 InpTakeProfit 参数用于在开单/下挂单后立即设置停止水平)设置
止损和止盈点数。
在全局变量列表中添加必要的变量以便存储新添加的输入值和标志变量,指示尾随函数的激活:
//--- input variables input ulong InpMagic = 123; // Magic number input double InpLots = 0.1; // Lots input uint InpStopLoss = 50; // StopLoss in points input uint InpTakeProfit = 50; // TakeProfit in points input uint InpDistance = 50; // Pending orders distance (points) input uint InpDistanceSL = 50; // StopLimit orders distance (points) input uint InpSlippage = 0; // Slippage in points input double InpWithdrawal = 10; // Withdrawal funds (in tester) input uint InpButtShiftX = 40; // Buttons X shift input uint InpButtShiftY = 10; // Buttons Y shift input uint InpTrailingStop = 50; // Trailing Stop (points) input uint InpTrailingStep = 20; // Trailing Step (points) input uint InpTrailingStart = 0; // Trailing Start (points) input uint InpStopLossModify = 20; // StopLoss for modification (points) input uint InpTakeProfitModify = 60; // TakeProfit for modification (points) //--- global variables CEngine engine; CTrade trade; SDataButt butt_data[TOTAL_BUTT]; string prefix; double lot; double withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal); ulong magic_number; uint stoploss; uint takeprofit; uint distance_pending; uint distance_stoplimit; uint slippage; bool trailing_on; double trailing_stop; double trailing_step; uint trailing_start; uint stoploss_to_modify; uint takeprofit_to_modify; //+------------------------------------------------------------------+
由于这是一个测试 EA,因此在调试函数库时,程序操作通常会伴随严重错误结束。 在这些情况下,所有已绘制的图形对象(按钮)都会保留在图表上。 修复错误并重新启动 EA 后,无法重新绘制这些按钮。 您必须再次启动它,令其首先自 OnDeinit() 处理程序中删除图表中现有按钮,以便它能够在下次启动时在干净图表上重新绘制所有按钮。
在 OnInit() 处理程序里添加检查图表上是否存在按钮,设置尾随函数变量的值,和停止级别,在绘制所有按钮后检查尾随按钮激活标志,如果标志已设置则启用按钮。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Calling the function displays the list of enumeration constants in the journal, //--- (the list is set in the strings 22 and 25 of the DELib.mqh file) for checking the constants validity //EnumNumbersTest(); //--- check for undeleted objects if(IsPresentObects(prefix)) ObjectsDeleteAll(0,prefix); //--- set global variables prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"; for(int i=0;i<TOTAL_BUTT;i++) { butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i); butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i); } lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0)); magic_number=InpMagic; stoploss=InpStopLoss; takeprofit=InpTakeProfit; distance_pending=InpDistance; distance_stoplimit=InpDistanceSL; slippage=InpSlippage; trailing_stop=InpTrailingStop*Point(); trailing_step=InpTrailingStep*Point(); trailing_start=InpTrailingStart; stoploss_to_modify=InpStopLossModify; takeprofit_to_modify=InpTakeProfitModify; //--- create buttons if(!CreateButtons(InpButtShiftX,InpButtShiftY)) return INIT_FAILED; //--- set button trailing ButtonState(butt_data[TOTAL_BUTT-1].name,trailing_on); //--- setting trade parameters trade.SetDeviationInPoints(slippage); trade.SetExpertMagicNumber(magic_number); trade.SetTypeFillingBySymbol(Symbol()); trade.SetMarginMode(); trade.LogLevel(LOG_LEVEL_NO); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
我们来编写查验图表上存在含有指定前缀的图形对象的函数,和用于跟踪按钮状态的函数。
为了更方便地阅读代码,我们将跟踪从 EA 的 OnTick() 处理程序转移到一个单独的函数中:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+ //| Tracking the buttons' status | //+------------------------------------------------------------------+ void PressButtonsControl(void) { int total=ObjectsTotal(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); } } //+------------------------------------------------------------------+
我们修改设置按钮对象状态的函数:
//+------------------------------------------------------------------+ //| Set the button status | //+------------------------------------------------------------------+ void ButtonState(const string name,const bool state) { ObjectSetInteger(0,name,OBJPROP_STATE,state); if(name==butt_data[TOTAL_BUTT-1].name) { if(state) ObjectSetInteger(0,name,OBJPROP_BGCOLOR,C'220,255,240'); else ObjectSetInteger(0,name,OBJPROP_BGCOLOR,C'240,240,240'); } } //+------------------------------------------------------------------+
此处:
设置按钮状态 (启用/禁用),
如果这是最后一个按钮,且
如果它为“启用”,则更改按钮对象背景颜色
否则,将背景颜色恢复为“禁用”状态。
由于我们有三个新按钮,添加将新按钮对象的名称转换为文本的函数,以便取其名称创建按钮文本:
//+------------------------------------------------------------------+ //| Convert enumeration into the button text | //+------------------------------------------------------------------+ string EnumToButtText(const ENUM_BUTTONS member) { string txt=StringSubstr(EnumToString(member),5); StringToLower(txt); StringReplace(txt,"set_take_profit","Set TakeProfit"); StringReplace(txt,"set_stop_loss","Set StopLoss"); StringReplace(txt,"trailing_all","Trailing All"); StringReplace(txt,"buy","Buy"); StringReplace(txt,"sell","Sell"); StringReplace(txt,"_limit"," Limit"); StringReplace(txt,"_stop"," Stop"); StringReplace(txt,"close_","Close "); StringReplace(txt,"2"," 1/2"); StringReplace(txt,"_by_"," by "); StringReplace(txt,"profit_","Profit "); StringReplace(txt,"delete_","Delete "); return txt; } //+------------------------------------------------------------------+
现在我们需要按下三个新按钮。 为实现此目的,在按钮处理函数的 PressButtonEvents() 最后添加以下代码(在按下提款按钮处理代码块之后):
//--- If the BUTT_PROFIT_WITHDRAWAL button is pressed: Withdraw funds from the account if(button==EnumToString(BUTT_PROFIT_WITHDRAWAL)) { //--- If the program is launched in the tester if(MQLInfoInteger(MQL_TESTER)) { //--- Emulate funds withdrawal TesterWithdrawal(withdrawal); } } //--- If the BUTT_SET_STOP_LOSS button is pressed: Place StopLoss to all orders and positions where it is not present if(button==EnumToString(BUTT_SET_STOP_LOSS)) { SetStopLoss(); } //--- If the BUTT_SET_TAKE_PROFIT button is pressed: Place TakeProfit to all orders and positions where it is not present if(button==EnumToString(BUTT_SET_TAKE_PROFIT)) { SetTakeProfit(); } //--- Wait for 1/10 of a second Sleep(100); //--- "Unpress" the button (if this is not a trailing button) if(button!=EnumToString(BUTT_TRAILING_ALL)) ButtonState(button_name,false); //--- If the BUTT_TRAILING_ALL button is pressed else { //--- Set the color of the active button ButtonState(button_name,true); trailing_on=true; } //--- re-draw the chart ChartRedraw(); } //--- Return the inactive button color (if this is a trailing button) else if(button==EnumToString(BUTT_TRAILING_ALL)) { ButtonState(button_name,false); trailing_on=false; //--- re-draw the chart ChartRedraw(); } } //+------------------------------------------------------------------+
正如我们所见,这里调用了两个新函数:SetStopLoss() 和 SetTakeProfit()。 它们允许您设置相应的订单和持仓价位:
//+------------------------------------------------------------------+ //| Set StopLoss to all orders and positions | //+------------------------------------------------------------------+ void SetStopLoss(void) { if(stoploss_to_modify==0) return; //--- Set StopLoss to all positions where it is absent CArrayObj* list=engine.GetListMarketPosition(); list=CSelect::ByOrderProperty(list,ORDER_PROP_SL,0,EQUAL); if(list==NULL) return; int total=list.Total(); for(int i=total-1;i>=0;i--) { COrder* position=list.At(i); if(position==NULL) continue; double sl=CorrectStopLoss(position.Symbol(),position.TypeByDirection(),0,stoploss_to_modify); trade.PositionModify(position.Ticket(),sl,position.TakeProfit()); } //--- Set StopLoss to all pending orders where it is absent list=engine.GetListMarketPendings(); list=CSelect::ByOrderProperty(list,ORDER_PROP_SL,0,EQUAL); if(list==NULL) return; total=list.Total(); for(int i=total-1;i>=0;i--) { COrder* order=list.At(i); if(order==NULL) continue; double sl=CorrectStopLoss(order.Symbol(),(ENUM_ORDER_TYPE)order.TypeOrder(),order.PriceOpen(),stoploss_to_modify); trade.OrderModify(order.Ticket(),order.PriceOpen(),sl,order.TakeProfit(),trade.RequestTypeTime(),trade.RequestExpiration(),order.PriceStopLimit()); } } //+------------------------------------------------------------------+ //| Set TakeProfit to all orders and positions | //+------------------------------------------------------------------+ void SetTakeProfit(void) { if(takeprofit_to_modify==0) return; //--- Set TakeProfit to all positions where it is absent CArrayObj* list=engine.GetListMarketPosition(); list=CSelect::ByOrderProperty(list,ORDER_PROP_TP,0,EQUAL); if(list==NULL) return; int total=list.Total(); for(int i=total-1;i>=0;i--) { COrder* position=list.At(i); if(position==NULL) continue; double tp=CorrectTakeProfit(position.Symbol(),position.TypeByDirection(),0,takeprofit_to_modify); trade.PositionModify(position.Ticket(),position.StopLoss(),tp); } //--- Set TakeProfit to all pending orders where it is absent list=engine.GetListMarketPendings(); list=CSelect::ByOrderProperty(list,ORDER_PROP_TP,0,EQUAL); if(list==NULL) return; total=list.Total(); for(int i=total-1;i>=0;i--) { COrder* order=list.At(i); if(order==NULL) continue; double tp=CorrectTakeProfit(order.Symbol(),(ENUM_ORDER_TYPE)order.TypeOrder(),order.PriceOpen(),takeprofit_to_modify); trade.OrderModify(order.Ticket(),order.PriceOpen(),order.StopLoss(),tp,trade.RequestTypeTime(),trade.RequestExpiration(),order.PriceStopLimit()); } } //+------------------------------------------------------------------+
函数十分简单。 我们来看看将止盈设置不存在的所有订单和持仓:
首先,检查停止损失的设置点数。 如果该值为零,则立即退出,因为此处无需更改。
接着,仅接收持仓列表,并且按止盈等于零排序,即持仓未设止盈。
接下来,最终列表利用循环从中取得持仓,利用在函数库论述的第四部分中讲述的服务函数计算每笔持仓的正确止盈,将其发送到标准库的
CTrade 类的持仓修改方法。
若要为订单设置止盈,我们要得到挂单的列表,并执行上述操作。
现在我们只需要编写尾随持仓停止和订单放置价格的函数:
//+------------------------------------------------------------------+ //| Trailing stop of a position with the maximum profit | //+------------------------------------------------------------------+ void TrailingPositions(void) { MqlTick tick; if(!SymbolInfoTick(Symbol(),tick)) return; double stop_level=StopLevel(Symbol(),2)*Point(); //--- Get the list of all open positions CArrayObj* list=engine.GetListMarketPosition(); //--- Select only Buy positions from the list CArrayObj* list_buy=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- Sort the list by profit considering commission and swap list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the index of the Buy position with the maximum profit int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL); if(index_buy>WRONG_VALUE) { COrder* buy=list_buy.At(index_buy); if(buy!=NULL) { //--- Calculate the new StopLoss double sl=NormalizeDouble(tick.bid-trailing_stop,Digits()); //--- If the price and the StopLevel based on it are higher than the new StopLoss (the distance by StopLevel is maintained) if(tick.bid-stop_level>sl) { //--- If the new StopLoss level exceeds the trailing step based on the current StopLoss if(buy.StopLoss()+trailing_step<sl) { //--- If we trail at any profit or position profit in points exceeds the trailing start, modify StopLoss if(trailing_start==0 || buy.ProfitInPoints()>(int)trailing_start) trade.PositionModify(buy.Ticket(),sl,buy.TakeProfit()); } } } } //--- Select only Sell positions from the list CArrayObj* list_sell=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- Sort the list by profit considering commission and swap list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get Sell position index with the maximum profit int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL); if(index_sell>WRONG_VALUE) { COrder* sell=list_sell.At(index_sell); if(sell!=NULL) { //--- Calculate the new StopLoss double sl=NormalizeDouble(tick.ask+trailing_stop,Digits()); //--- If the price and StopLevel based on it are below the new StopLoss (the distance by StopLevel is maintained) if(tick.ask+stop_level<sl) { //--- If the new StopLoss level is below the trailing step based on the current StopLoss or a position has no StopLoss if(sell.StopLoss()-trailing_step>sl || sell.StopLoss()==0) { //--- If we trail at any profit or position profit in points exceeds the trailing start value, modify StopLoss if(trailing_start==0 || sell.ProfitInPoints()>(int)trailing_start) trade.PositionModify(sell.Ticket(),sl,sell.TakeProfit()); } } } } } //+------------------------------------------------------------------+ //| Trailing the farthest pending orders | //+------------------------------------------------------------------+ void TrailingOrders(void) { MqlTick tick; if(!SymbolInfoTick(Symbol(),tick)) return; double stop_level=StopLevel(Symbol(),2)*Point(); //--- Get the list of all placed orders CArrayObj* list=engine.GetListMarketPendings(); //--- Select only Buy orders from the list CArrayObj* list_buy=CSelect::ByOrderProperty(list,ORDER_PROP_DIRECTION,ORDER_TYPE_BUY,EQUAL); //--- Sort the list by distance from the price in points (by profit in points) list_buy.Sort(SORT_BY_ORDER_PROFIT_PT); //--- Get the index of the Buy order with the greatest distance int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_PT); if(index_buy>WRONG_VALUE) { COrder* buy=list_buy.At(index_buy); if(buy!=NULL) { //--- If the order is below the price (BuyLimit) and it should be "elevated" following the price if(buy.TypeOrder()==ORDER_TYPE_BUY_LIMIT) { //--- Calculate the new order price and stop levels based on it double price=NormalizeDouble(tick.ask-trailing_stop,Digits()); double sl=(buy.StopLoss()>0 ? NormalizeDouble(price-(buy.PriceOpen()-buy.StopLoss()),Digits()) : 0); double tp=(buy.TakeProfit()>0 ? NormalizeDouble(price+(buy.TakeProfit()-buy.PriceOpen()),Digits()) : 0); //--- If the calculated price is below the StopLevel distance based on Ask order price (the distance by StopLevel is maintained) if(price<tick.ask-stop_level) { //--- If the calculated price exceeds the trailing step based on the order placement price, modify the order price if(price>buy.PriceOpen()+trailing_step) { trade.OrderModify(buy.Ticket(),price,sl,tp,trade.RequestTypeTime(),trade.RequestExpiration(),buy.PriceStopLimit()); } } } //--- If the order exceeds the price (BuyStop and BuyStopLimit), and it should be "decreased" following the price else { //--- Calculate the new order price and stop levels based on it double price=NormalizeDouble(tick.ask+trailing_stop,Digits()); double sl=(buy.StopLoss()>0 ? NormalizeDouble(price-(buy.PriceOpen()-buy.StopLoss()),Digits()) : 0); double tp=(buy.TakeProfit()>0 ? NormalizeDouble(price+(buy.TakeProfit()-buy.PriceOpen()),Digits()) : 0); //--- If the calculated price exceeds the StopLevel based on Ask order price (the distance by StopLevel is maintained) if(price>tick.ask+stop_level) { //--- If the calculated price is lower than the trailing step based on order price, modify the order price if(price<buy.PriceOpen()-trailing_step) { trade.OrderModify(buy.Ticket(),price,sl,tp,trade.RequestTypeTime(),trade.RequestExpiration(),(buy.PriceStopLimit()>0 ? price-distance_stoplimit*Point() : 0)); } } } } } //--- Select only Sell order from the list CArrayObj* list_sell=CSelect::ByOrderProperty(list,ORDER_PROP_DIRECTION,ORDER_TYPE_SELL,EQUAL); //--- Sort the list by the distance from the price in points (by profit in points) list_sell.Sort(SORT_BY_ORDER_PROFIT_PT); //--- Get the index of the Sell order having the greatest distance int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_PT); if(index_sell>WRONG_VALUE) { COrder* sell=list_sell.At(index_sell); if(sell!=NULL) { //--- If the order exceeds the price (SellLimit), and it needs to be "decreased" following the price if(sell.TypeOrder()==ORDER_TYPE_SELL_LIMIT) { //--- Calculate the new order price and stop levels based on it double price=NormalizeDouble(tick.bid+trailing_stop,Digits()); double sl=(sell.StopLoss()>0 ? NormalizeDouble(price+(sell.StopLoss()-sell.PriceOpen()),Digits()) : 0); double tp=(sell.TakeProfit()>0 ? NormalizeDouble(price-(sell.PriceOpen()-sell.TakeProfit()),Digits()) : 0); //--- If the calculated price exceeds the StopLevel distance based on the Bid order price (the distance by StopLevel is maintained) if(price>tick.bid+stop_level) { //--- If the calculated price is below the trailing step based on the order price, modify the order price if(price<sell.PriceOpen()-trailing_step) { trade.OrderModify(sell.Ticket(),price,sl,tp,trade.RequestTypeTime(),trade.RequestExpiration(),sell.PriceStopLimit()); } } } //--- If the order is below the price (SellStop and SellStopLimit), and it should be "elevated" following the price else { //--- Calculate the new order price and stop levels based on it double price=NormalizeDouble(tick.bid-trailing_stop,Digits()); double sl=(sell.StopLoss()>0 ? NormalizeDouble(price+(sell.StopLoss()-sell.PriceOpen()),Digits()) : 0); double tp=(sell.TakeProfit()>0 ? NormalizeDouble(price-(sell.PriceOpen()-sell.TakeProfit()),Digits()) : 0); //--- If the calculated price is below the StopLevel distance based on the Bid order price (the distance by StopLevel is maintained) if(price<tick.bid-stop_level) { //--- If the calculated price exceeds the trailing step based on the order price, modify the order price if(price>sell.PriceOpen()+trailing_step) { trade.OrderModify(sell.Ticket(),price,sl,tp,trade.RequestTypeTime(),trade.RequestExpiration(),(sell.PriceStopLimit()>0 ? price+distance_stoplimit*Point() : 0)); } } } } } } //+------------------------------------------------------------------+
这些函数未包含任何新内容。 所有必要的动作都已直接在代码注释中说明。 我相信,您完全能够自行研究代码而不会有太多困难。
由于我们现在多了三个按钮,因此在按钮面板创建函数里调整了按钮坐标的计算(参见最终清单)。
在 OnTick()
处理程序中调用所有尾随函数:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Initialize the last trading event static ENUM_TRADE_EVENT last_event=WRONG_VALUE; //--- If working in the tester if(MQLInfoInteger(MQL_TESTER)) { engine.OnTimer(); PressButtonsControl(); } //--- If the last trading event changed if(engine.LastTradeEvent()!=last_event) { last_event=engine.LastTradeEvent(); } //--- If the trailing flag is set if(trailing_on) { TrailingPositions(); TrailingOrders(); } } //+------------------------------------------------------------------+
测试 EA 的完整清单:
//+------------------------------------------------------------------+ //| 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> #include <Trade\Trade.mqh> //--- enums enum ENUM_BUTTONS { BUTT_BUY, BUTT_BUY_LIMIT, BUTT_BUY_STOP, BUTT_BUY_STOP_LIMIT, BUTT_CLOSE_BUY, BUTT_CLOSE_BUY2, BUTT_CLOSE_BUY_BY_SELL, BUTT_SELL, BUTT_SELL_LIMIT, BUTT_SELL_STOP, BUTT_SELL_STOP_LIMIT, BUTT_CLOSE_SELL, BUTT_CLOSE_SELL2, BUTT_CLOSE_SELL_BY_BUY, BUTT_DELETE_PENDING, BUTT_CLOSE_ALL, BUTT_PROFIT_WITHDRAWAL, BUTT_SET_STOP_LOSS, BUTT_SET_TAKE_PROFIT, BUTT_TRAILING_ALL }; #define TOTAL_BUTT (20) //--- structures struct SDataButt { string name; string text; }; //--- input variables input ulong InpMagic = 123; // Magic number input double InpLots = 0.1; // Lots input uint InpStopLoss = 50; // StopLoss in points input uint InpTakeProfit = 50; // TakeProfit in points input uint InpDistance = 50; // Pending orders distance (points) input uint InpDistanceSL = 50; // StopLimit orders distance (points) input uint InpSlippage = 0; // Slippage in points input double InpWithdrawal = 10; // Withdrawal funds (in tester) input uint InpButtShiftX = 40; // Buttons X shift input uint InpButtShiftY = 10; // Buttons Y shift input uint InpTrailingStop = 50; // Trailing Stop (points) input uint InpTrailingStep = 20; // Trailing Step (points) input uint InpTrailingStart = 0; // Trailing Start (points) input uint InpStopLossModify = 20; // StopLoss for modification (points) input uint InpTakeProfitModify = 60; // TakeProfit for modification (points) //--- global variables CEngine engine; CTrade trade; SDataButt butt_data[TOTAL_BUTT]; string prefix; double lot; double withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal); ulong magic_number; uint stoploss; uint takeprofit; uint distance_pending; uint distance_stoplimit; uint slippage; bool trailing_on; double trailing_stop; double trailing_step; uint trailing_start; uint stoploss_to_modify; uint takeprofit_to_modify; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Calling the function displays the list of enumeration constants in the journal, //--- (the list is set in the strings 22 and 25 of the DELib.mqh file) for checking the constants validity //EnumNumbersTest(); //--- check for undeleted objects if(IsPresentObects(prefix)) ObjectsDeleteAll(0,prefix); //--- set global variables prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"; for(int i=0;i<TOTAL_BUTT;i++) { butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i); butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i); } lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0)); magic_number=InpMagic; stoploss=InpStopLoss; takeprofit=InpTakeProfit; distance_pending=InpDistance; distance_stoplimit=InpDistanceSL; slippage=InpSlippage; trailing_stop=InpTrailingStop*Point(); trailing_step=InpTrailingStep*Point(); trailing_start=InpTrailingStart; stoploss_to_modify=InpStopLossModify; takeprofit_to_modify=InpTakeProfitModify; //--- create buttons if(!CreateButtons(InpButtShiftX,InpButtShiftY)) return INIT_FAILED; //--- set button trailing ButtonState(butt_data[TOTAL_BUTT-1].name,trailing_on); //--- setting trade parameters trade.SetDeviationInPoints(slippage); trade.SetExpertMagicNumber(magic_number); trade.SetTypeFillingBySymbol(Symbol()); trade.SetMarginMode(); trade.LogLevel(LOG_LEVEL_NO); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- delete objects ObjectsDeleteAll(0,prefix); Comment(""); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Initialize the last trading event static ENUM_TRADE_EVENT last_event=WRONG_VALUE; //--- If working in the tester if(MQLInfoInteger(MQL_TESTER)) { engine.OnTimer(); PressButtonsControl(); } //--- If the last trading event changed if(engine.LastTradeEvent()!=last_event) { last_event=engine.LastTradeEvent(); } //--- If the trailing flag is set if(trailing_on) { TrailingPositions(); TrailingOrders(); } } //+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { if(!MQLInfoInteger(MQL_TESTER)) engine.OnTimer(); } //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(MQLInfoInteger(MQL_TESTER)) return; if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,"BUTT_")>0) { PressButtonEvents(sparam); } if(id>=CHARTEVENT_CUSTOM) { ushort event=ushort(id-CHARTEVENT_CUSTOM); Print(DFUN,"id=",id,", event=",EnumToString((ENUM_TRADE_EVENT)event),", lparam=",lparam,", dparam=",DoubleToString(dparam,Digits()),", sparam=",sparam); } } //+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+ //| Tracking the buttons' status | //+------------------------------------------------------------------+ void PressButtonsControl(void) { int total=ObjectsTotal(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); } } //+------------------------------------------------------------------+ //| Create the button panel | //+------------------------------------------------------------------+ bool CreateButtons(const int shift_x=30,const int shift_y=0) { int h=18,w=84,offset=2; int cx=offset+shift_x,cy=offset+shift_y+(h+1)*(TOTAL_BUTT/2)+3*h+1; int x=cx,y=cy; int shift=0; for(int i=0;i<TOTAL_BUTT;i++) { x=x+(i==7 ? w+2 : 0); if(i==TOTAL_BUTT-6) x=cx; y=(cy-(i-(i>6 ? 7 : 0))*(h+1)); if(!ButtonCreate(butt_data[i].name,x,y,(i<TOTAL_BUTT-6 ? w : w*2+2),h,butt_data[i].text,(i<4 ? clrGreen : i>6 && i<11 ? clrRed : clrBlue))) { Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),butt_data[i].text); return false; } } ChartRedraw(0); return true; } //+------------------------------------------------------------------+ //| Create the button | //+------------------------------------------------------------------+ bool ButtonCreate(const string name,const int x,const int y,const int w,const int h,const string text,const color clr,const string font="Calibri",const int font_size=8) { if(ObjectFind(0,name)<0) { if(!ObjectCreate(0,name,OBJ_BUTTON,0,0,0)) { Print(DFUN,TextByLanguage("не удалось создать кнопку! Код ошибки=","Could not create button! Error code="),GetLastError()); return false; } ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false); ObjectSetInteger(0,name,OBJPROP_HIDDEN,true); ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x); ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y); ObjectSetInteger(0,name,OBJPROP_XSIZE,w); ObjectSetInteger(0,name,OBJPROP_YSIZE,h); ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_LOWER); ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,font_size); ObjectSetString(0,name,OBJPROP_FONT,font); ObjectSetString(0,name,OBJPROP_TEXT,text); ObjectSetInteger(0,name,OBJPROP_COLOR,clr); ObjectSetString(0,name,OBJPROP_TOOLTIP,"\n"); ObjectSetInteger(0,name,OBJPROP_BORDER_COLOR,clrGray); return true; } return false; } //+------------------------------------------------------------------+ //| Return the button status | //+------------------------------------------------------------------+ bool ButtonState(const string name) { return (bool)ObjectGetInteger(0,name,OBJPROP_STATE); } //+------------------------------------------------------------------+ //| Set the button status | //+------------------------------------------------------------------+ void ButtonState(const string name,const bool state) { ObjectSetInteger(0,name,OBJPROP_STATE,state); if(name==butt_data[TOTAL_BUTT-1].name) { if(state) ObjectSetInteger(0,name,OBJPROP_BGCOLOR,C'220,255,240'); else ObjectSetInteger(0,name,OBJPROP_BGCOLOR,C'240,240,240'); } } //+------------------------------------------------------------------+ //| Transform enumeration into the button text | //+------------------------------------------------------------------+ string EnumToButtText(const ENUM_BUTTONS member) { string txt=StringSubstr(EnumToString(member),5); StringToLower(txt); StringReplace(txt,"set_take_profit","Set TakeProfit"); StringReplace(txt,"set_stop_loss","Set StopLoss"); StringReplace(txt,"trailing_all","Trailing All"); StringReplace(txt,"buy","Buy"); StringReplace(txt,"sell","Sell"); StringReplace(txt,"_limit"," Limit"); StringReplace(txt,"_stop"," Stop"); StringReplace(txt,"close_","Close "); StringReplace(txt,"2"," 1/2"); StringReplace(txt,"_by_"," by "); StringReplace(txt,"profit_","Profit "); StringReplace(txt,"delete_","Delete "); return txt; } //+------------------------------------------------------------------+ //| Handle pressing the buttons | //+------------------------------------------------------------------+ void PressButtonEvents(const string button_name) { //--- Convert 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 trade.Buy(NormalizeLot(Symbol(),lot),Symbol(),0,sl,tp); } //--- If the BUTT_BUY_LIMIT button is pressed: Place BuyLimit else if(button==EnumToString(BUTT_BUY_LIMIT)) { //--- Get correct order placement relative to StopLevel double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending); //--- Get 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 trade.BuyLimit(lot,price_set,Symbol(),sl,tp); } //--- If the BUTT_BUY_STOP button is pressed: Set BuyStop else if(button==EnumToString(BUTT_BUY_STOP)) { //--- Get correct order placement relative to StopLevel double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending); //--- Get correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set,takeprofit); //--- Set BuyStop order trade.BuyStop(lot,price_set,Symbol(),sl,tp); } //--- If the BUTT_BUY_STOP_LIMIT button is pressed: Set BuyStopLimit else if(button==EnumToString(BUTT_BUY_STOP_LIMIT)) { //--- Get the correct BuyStop order placement price relative to StopLevel double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending); //--- Calculate BuyLimit order price relative to BuyStop level considering StopLevel double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_stoplimit,price_set_stop); //--- Get correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,takeprofit); //--- Set BuyStopLimit order trade.OrderOpen(Symbol(),ORDER_TYPE_BUY_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp); } //--- If the BUTT_SELL button is pressed: Open Sell position else if(button==EnumToString(BUTT_SELL)) { //--- Get the correct StopLoss and TakeProfit prices relative to StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL,0,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL,0,takeprofit); //--- Open Sell position trade.Sell(lot,Symbol(),0,sl,tp); } //--- If the BUTT_SELL_LIMIT button is pressed: Set SellLimit else if(button==EnumToString(BUTT_SELL_LIMIT)) { //--- Get correct order placement relative to StopLevel double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_pending); //--- Get correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,takeprofit); //--- Set SellLimit order trade.SellLimit(lot,price_set,Symbol(),sl,tp); } //--- If the BUTT_SELL_STOP button is pressed: Set SellStop else if(button==EnumToString(BUTT_SELL_STOP)) { //--- Get correct order placement relative to StopLevel double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending); //--- Get correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set,takeprofit); //--- Set SellStop order trade.SellStop(lot,price_set,Symbol(),sl,tp); } //--- If the BUTT_SELL_STOP_LIMIT button is pressed: Set SellStopLimit else if(button==EnumToString(BUTT_SELL_STOP_LIMIT)) { //--- Get the correct SellStop order price relative to StopLevel double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending); //--- Calculate SellLimit order price relative to SellStop level considering StopLevel double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_stoplimit,price_set_stop); //--- Get correct StopLoss and TakeProfit prices relative to the order placement level considering StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,takeprofit); //--- Set SellStopLimit order trade.OrderOpen(Symbol(),ORDER_TYPE_SELL_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp); } //--- If the BUTT_CLOSE_BUY button is pressed: Close Buy with the maximum profit else if(button==EnumToString(BUTT_CLOSE_BUY)) { //--- Get the list of all open positions CArrayObj* list=engine.GetListMarketPosition(); //--- Select only Buy positions from the list list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- Sort the list by profit considering commission and swap list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the index of the Buy position with the maximum profit int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); if(position!=NULL) { //--- Get the Buy position ticket and close the position by the ticket trade.PositionClose(position.Ticket()); } } } //--- If the BUTT_CLOSE_BUY2 button is pressed: Close the half of the Buy with the maximum profit else if(button==EnumToString(BUTT_CLOSE_BUY2)) { //--- Get the list of all open positions CArrayObj* list=engine.GetListMarketPosition(); //--- Select only Buy positions from the list list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- Sort the list by profit considering commission and swap list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the index of the Buy position with the maximum profit int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); if(position!=NULL) { //--- Calculate the closed volume and close the half of the Buy position by the ticket if(engine.IsHedge()) trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.Volume()/2.0)); else trade.Sell(NormalizeLot(position.Symbol(),position.Volume()/2.0)); } } } //--- If the BUTT_CLOSE_BUY_BY_SELL button is pressed: Close Buy with the maximum profit by the opposite Sell with the maximum profit else if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL)) { //--- Get the list of all open positions CArrayObj* list_buy=engine.GetListMarketPosition(); //--- Select only Buy positions from the list list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- Sort the list by profit considering commission and swap list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the index of the Buy position with the maximum profit int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL); //--- Get the list of all open positions CArrayObj* list_sell=engine.GetListMarketPosition(); //--- Select only Sell positions from the list list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- Sort the list by profit considering commission and swap list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the index of the Sell position with the maximum profit int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL); if(index_buy>WRONG_VALUE && index_sell>WRONG_VALUE) { //--- Select the Buy position with the maximum profit COrder* position_buy=list_buy.At(index_buy); //--- Select the Sell position with the maximum profit COrder* position_sell=list_sell.At(index_sell); if(position_buy!=NULL && position_sell!=NULL) { //--- Close the Buy position by the opposite Sell one trade.PositionCloseBy(position_buy.Ticket(),position_sell.Ticket()); } } } //--- If the BUTT_CLOSE_SELL button is pressed: Close Sell with the maximum profit else if(button==EnumToString(BUTT_CLOSE_SELL)) { //--- Get the list of all open positions CArrayObj* list=engine.GetListMarketPosition(); //--- Select only Sell positions from the list list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- Sort the list by profit considering commission and swap list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the index of the Sell position with the maximum profit int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); if(position!=NULL) { //--- Get the Sell position ticket and close the position by the ticket trade.PositionClose(position.Ticket()); } } } //--- If the BUTT_CLOSE_SELL2 button is pressed: Close the half of the Sell with the maximum profit else if(button==EnumToString(BUTT_CLOSE_SELL2)) { //--- Get the list of all open positions CArrayObj* list=engine.GetListMarketPosition(); //--- Select only Sell positions from the list list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- Sort the list by profit considering commission and swap list.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the index of the Sell position with the maximum profit int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list.At(index); if(position!=NULL) { //--- Calculate the closed volume and close the half of the Sell position by the ticket if(engine.IsHedge()) trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.Volume()/2.0)); else trade.Buy(NormalizeLot(position.Symbol(),position.Volume()/2.0)); } } } //--- If the BUTT_CLOSE_SELL_BY_BUY button is pressed: Close Sell with the maximum profit by the opposite Buy with the maximum profit else if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY)) { //--- Get the list of all open positions CArrayObj* list_sell=engine.GetListMarketPosition(); //--- Select only Sell positions from the list list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- Sort the list by profit considering commission and swap list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the index of the Sell position with the maximum profit int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL); //--- Get the list of all open positions CArrayObj* list_buy=engine.GetListMarketPosition(); //--- Select only Buy positions from the list list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- Sort the list by profit considering commission and swap list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the index of the Buy position with the maximum profit int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL); if(index_sell>WRONG_VALUE && index_buy>WRONG_VALUE) { //--- Select the Sell position with the maximum profit COrder* position_sell=list_sell.At(index_sell); //--- Select the Buy position with the maximum profit COrder* position_buy=list_buy.At(index_buy); if(position_sell!=NULL && position_buy!=NULL) { //--- Close the Sell position by the opposite Buy one trade.PositionCloseBy(position_sell.Ticket(),position_buy.Ticket()); } } } //--- If the BUTT_CLOSE_ALL is pressed: Close all positions starting with the one with the least profit else if(button==EnumToString(BUTT_CLOSE_ALL)) { //--- Get the list of all open positions CArrayObj* list=engine.GetListMarketPosition(); if(list!=NULL) { //--- Sort the list by profit considering commission and swap list.Sort(SORT_BY_ORDER_PROFIT_FULL); int total=list.Total(); //--- In the loop from the position with the least profit for(int i=0;i<total;i++) { COrder* position=list.At(i); if(position==NULL) continue; //--- close each position by its ticket trade.PositionClose(position.Ticket()); } } } //--- If the BUTT_DELETE_PENDING button is pressed: Remove the first pending order else if(button==EnumToString(BUTT_DELETE_PENDING)) { //--- Get the list of all orders CArrayObj* list=engine.GetListMarketPendings(); if(list!=NULL) { //--- Sort the list by placement time list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); int total=list.Total(); //--- In the loop from the position with the most amount of time for(int i=total-1;i>=0;i--) { COrder* order=list.At(i); if(order==NULL) continue; //--- delete the order by its ticket trade.OrderDelete(order.Ticket()); } } } //--- If the BUTT_PROFIT_WITHDRAWAL button is pressed: Withdraw funds from the account if(button==EnumToString(BUTT_PROFIT_WITHDRAWAL)) { //--- If the program is launched in the tester if(MQLInfoInteger(MQL_TESTER)) { //--- Emulate funds withdrawal TesterWithdrawal(withdrawal); } } //--- If the BUTT_SET_STOP_LOSS button is pressed: Place StopLoss to all orders and positions where it is not present if(button==EnumToString(BUTT_SET_STOP_LOSS)) { SetStopLoss(); } //--- If the BUTT_SET_TAKE_PROFIT button is pressed: Place TakeProfit to all orders and positions where it is not present if(button==EnumToString(BUTT_SET_TAKE_PROFIT)) { SetTakeProfit(); } //--- Wait for 1/10 of a second Sleep(100); //--- "Unpress" the button (if this is not a trailing button) if(button!=EnumToString(BUTT_TRAILING_ALL)) ButtonState(button_name,false); //--- If the BUTT_TRAILING_ALL button is pressed else { //--- Set the color of the active button ButtonState(button_name,true); trailing_on=true; } //--- re-draw the chart ChartRedraw(); } //--- Return the inactive button color (if this is a trailing button) else if(button==EnumToString(BUTT_TRAILING_ALL)) { ButtonState(button_name,false); trailing_on=false; //--- re-draw the chart ChartRedraw(); } } //+------------------------------------------------------------------+ //| Set StopLoss to all orders and positions | //+------------------------------------------------------------------+ void SetStopLoss(void) { if(stoploss_to_modify==0) return; //--- Set StopLoss to all positions where it is absent CArrayObj* list=engine.GetListMarketPosition(); list=CSelect::ByOrderProperty(list,ORDER_PROP_SL,0,EQUAL); if(list==NULL) return; int total=list.Total(); for(int i=total-1;i>=0;i--) { COrder* position=list.At(i); if(position==NULL) continue; double sl=CorrectStopLoss(position.Symbol(),position.TypeByDirection(),0,stoploss_to_modify); trade.PositionModify(position.Ticket(),sl,position.TakeProfit()); } //--- Set StopLoss to all pending orders where it is absent list=engine.GetListMarketPendings(); list=CSelect::ByOrderProperty(list,ORDER_PROP_SL,0,EQUAL); if(list==NULL) return; total=list.Total(); for(int i=total-1;i>=0;i--) { COrder* order=list.At(i); if(order==NULL) continue; double sl=CorrectStopLoss(order.Symbol(),(ENUM_ORDER_TYPE)order.TypeOrder(),order.PriceOpen(),stoploss_to_modify); trade.OrderModify(order.Ticket(),order.PriceOpen(),sl,order.TakeProfit(),trade.RequestTypeTime(),trade.RequestExpiration(),order.PriceStopLimit()); } } //+------------------------------------------------------------------+ //| Set TakeProfit to all orders and positions | //+------------------------------------------------------------------+ void SetTakeProfit(void) { if(takeprofit_to_modify==0) return; //--- Set TakeProfit to all positions where it is absent CArrayObj* list=engine.GetListMarketPosition(); list=CSelect::ByOrderProperty(list,ORDER_PROP_TP,0,EQUAL); if(list==NULL) return; int total=list.Total(); for(int i=total-1;i>=0;i--) { COrder* position=list.At(i); if(position==NULL) continue; double tp=CorrectTakeProfit(position.Symbol(),position.TypeByDirection(),0,takeprofit_to_modify); trade.PositionModify(position.Ticket(),position.StopLoss(),tp); } //--- Set TakeProfit to all pending orders where it is absent list=engine.GetListMarketPendings(); list=CSelect::ByOrderProperty(list,ORDER_PROP_TP,0,EQUAL); if(list==NULL) return; total=list.Total(); for(int i=total-1;i>=0;i--) { COrder* order=list.At(i); if(order==NULL) continue; double tp=CorrectTakeProfit(order.Symbol(),(ENUM_ORDER_TYPE)order.TypeOrder(),order.PriceOpen(),takeprofit_to_modify); trade.OrderModify(order.Ticket(),order.PriceOpen(),order.StopLoss(),tp,trade.RequestTypeTime(),trade.RequestExpiration(),order.PriceStopLimit()); } } //+------------------------------------------------------------------+ //| Trailing stop of a position with the maximum profit | //+------------------------------------------------------------------+ void TrailingPositions(void) { MqlTick tick; if(!SymbolInfoTick(Symbol(),tick)) return; double stop_level=StopLevel(Symbol(),2)*Point(); //--- Get the list of all open positions CArrayObj* list=engine.GetListMarketPosition(); //--- Select only Buy positions from the list CArrayObj* list_buy=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); //--- Sort the list by profit considering commission and swap list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the index of the Buy position with the maximum profit int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL); if(index_buy>WRONG_VALUE) { COrder* buy=list_buy.At(index_buy); if(buy!=NULL) { //--- Calculate the new StopLoss double sl=NormalizeDouble(tick.bid-trailing_stop,Digits()); //--- If the price and the StopLevel based on it are higher than the new StopLoss (the distance by StopLevel is maintained) if(tick.bid-stop_level>sl) { //--- If the new StopLoss level exceeds the trailing step based on the current StopLoss if(buy.StopLoss()+trailing_step<sl) { //--- If we trail at any profit or position profit in points exceeds the trailing start, modify StopLoss if(trailing_start==0 || buy.ProfitInPoints()>(int)trailing_start) trade.PositionModify(buy.Ticket(),sl,buy.TakeProfit()); } } } } //--- Select only Sell positions from the list CArrayObj* list_sell=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); //--- Sort the list by profit considering commission and swap list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get Sell position index with the maximum profit int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL); if(index_sell>WRONG_VALUE) { COrder* sell=list_sell.At(index_sell); if(sell!=NULL) { //--- Calculate the new StopLoss double sl=NormalizeDouble(tick.ask+trailing_stop,Digits()); //--- If the price and StopLevel based on it are below the new (the distance by StopLevel is maintained) if(tick.ask+stop_level<sl) { //--- If the new StopLoss level is below the trailing step based on the current StopLoss or a position has no StopLoss if(sell.StopLoss()-trailing_step>sl || sell.StopLoss()==0) { //--- If we trail at any profit or position profit in points exceeds the trailing start value, modify StopLoss if(trailing_start==0 || sell.ProfitInPoints()>(int)trailing_start) trade.PositionModify(sell.Ticket(),sl,sell.TakeProfit()); } } } } } //+------------------------------------------------------------------+ //| Trailing the farthest pending orders | //+------------------------------------------------------------------+ void TrailingOrders(void) { MqlTick tick; if(!SymbolInfoTick(Symbol(),tick)) return; double stop_level=StopLevel(Symbol(),2)*Point(); //--- Get the list of all placed orders CArrayObj* list=engine.GetListMarketPendings(); //--- Select only Buy orders from the list CArrayObj* list_buy=CSelect::ByOrderProperty(list,ORDER_PROP_DIRECTION,ORDER_TYPE_BUY,EQUAL); //--- Sort the list by distance from the price in points (by profit in points) list_buy.Sort(SORT_BY_ORDER_PROFIT_PT); //--- Get the index of the Buy order with the greatest distance int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_PT); if(index_buy>WRONG_VALUE) { COrder* buy=list_buy.At(index_buy); if(buy!=NULL) { //--- If the order is below the price (BuyLimit) and it should be "elevated" following the price if(buy.TypeOrder()==ORDER_TYPE_BUY_LIMIT) { //--- Calculate the new order price and stop levels based on it double price=NormalizeDouble(tick.ask-trailing_stop,Digits()); double sl=(buy.StopLoss()>0 ? NormalizeDouble(price-(buy.PriceOpen()-buy.StopLoss()),Digits()) : 0); double tp=(buy.TakeProfit()>0 ? NormalizeDouble(price+(buy.TakeProfit()-buy.PriceOpen()),Digits()) : 0); //--- If the calculated price is below the StopLevel distance based on Ask order price (the distance by StopLevel is maintained) if(price<tick.ask-stop_level) { //--- If the calculated price exceeds the trailing step based on the order placement price, modify the order price if(price>buy.PriceOpen()+trailing_step) { trade.OrderModify(buy.Ticket(),price,sl,tp,trade.RequestTypeTime(),trade.RequestExpiration(),buy.PriceStopLimit()); } } } //--- If the order exceeds the price (BuyStop and BuyStopLimit), and it should be "decreased" following the price else { //--- Calculate the new order price and stop levels based on it double price=NormalizeDouble(tick.ask+trailing_stop,Digits()); double sl=(buy.StopLoss()>0 ? NormalizeDouble(price-(buy.PriceOpen()-buy.StopLoss()),Digits()) : 0); double tp=(buy.TakeProfit()>0 ? NormalizeDouble(price+(buy.TakeProfit()-buy.PriceOpen()),Digits()) : 0); //--- If the calculated price exceeds the StopLevel based on Ask order price (the distance by StopLevel is maintained) if(price>tick.ask+stop_level) { //--- If the calculated price is lower than the trailing step based on order price, modify the order price if(price<buy.PriceOpen()-trailing_step) { trade.OrderModify(buy.Ticket(),price,sl,tp,trade.RequestTypeTime(),trade.RequestExpiration(),(buy.PriceStopLimit()>0 ? price-distance_stoplimit*Point() : 0)); } } } } } //--- Select only Sell order from the list CArrayObj* list_sell=CSelect::ByOrderProperty(list,ORDER_PROP_DIRECTION,ORDER_TYPE_SELL,EQUAL); //--- Sort the list by the distance from the price in points (by profit in points) list_sell.Sort(SORT_BY_ORDER_PROFIT_PT); //--- Get the index of the Sell order having the greatest distance int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_PT); if(index_sell>WRONG_VALUE) { COrder* sell=list_sell.At(index_sell); if(sell!=NULL) { //--- If the order exceeds the price (SellLimit), and it needs to be "decreased" following the price if(sell.TypeOrder()==ORDER_TYPE_SELL_LIMIT) { //--- Calculate the new order price and stop levels based on it double price=NormalizeDouble(tick.bid+trailing_stop,Digits()); double sl=(sell.StopLoss()>0 ? NormalizeDouble(price+(sell.StopLoss()-sell.PriceOpen()),Digits()) : 0); double tp=(sell.TakeProfit()>0 ? NormalizeDouble(price-(sell.PriceOpen()-sell.TakeProfit()),Digits()) : 0); //--- If the calculated price exceeds the StopLevel distance based on the Bid order price (the distance by StopLevel is maintained) if(price>tick.bid+stop_level) { //--- If the calculated price is below the trailing step based on the order price, modify the order price if(price<sell.PriceOpen()-trailing_step) { trade.OrderModify(sell.Ticket(),price,sl,tp,trade.RequestTypeTime(),trade.RequestExpiration(),sell.PriceStopLimit()); } } } //--- If the order is below the price (SellStop and SellStopLimit), and it should be "elevated" following the price else { //--- Calculate the new order price and stop levels based on it double price=NormalizeDouble(tick.bid-trailing_stop,Digits()); double sl=(sell.StopLoss()>0 ? NormalizeDouble(price+(sell.StopLoss()-sell.PriceOpen()),Digits()) : 0); double tp=(sell.TakeProfit()>0 ? NormalizeDouble(price-(sell.PriceOpen()-sell.TakeProfit()),Digits()) : 0); //--- If the calculated price is below the StopLevel distance based on the Bid order price (the distance by StopLevel is maintained) if(price<tick.bid-stop_level) { //--- If the calculated price exceeds the trailing step based on the order price, modify the order price if(price>sell.PriceOpen()+trailing_step) { trade.OrderModify(sell.Ticket(),price,sl,tp,trade.RequestTypeTime(),trade.RequestExpiration(),(sell.PriceStopLimit()>0 ? price+distance_stoplimit*Point() : 0)); } } } } } } //+------------------------------------------------------------------+
我们来编译 EA。
将 止损点数和止盈点数设置为零,即开仓和下挂单时没有停止级别。 将止损修改(点数)和止盈修改(点数)设置为 20 和 60(默认值)—
按下按钮时会设置这些止损和止盈价位。
在测试器中启动 EA 并设置挂单。 然后按下按钮,逐一设置止损和止盈。 设置价位,并在日志中显示相应的项目。 接着,启用尾随,并观察订单如何追随价格变化,并在日志帐中显示相应的项目。
由订单触发的持仓有其自身的尾随止损价位,并在日志中显示相应的项目。
净持:
对冲:
下一步是什么?
在接下来的文章中,我们将扩展函数库,并实现与 MQL4 的兼容性。 更多令人兴奋的事情也会来临。
下面附有当前版本函数库的所有文件,以及测试 EA 文件,供您测试和下载。
在评论中留下您的问题、意见和建议。
返回内容
系列中的前几篇文章:
第一部分 概念,数据管理。
第二部分
历史订单和成交集合。
第三部分 在场订单和持仓集合,安排搜索。
第四部分
交易事件。 概念。
第五部分 交易事件的类和集合。 将事件发送到程序。
第六部分
净持帐户事件。
第七部分 StopLimit 订单激活事件,准备订单和持仓修改事件的功能。