在开发函数库功能时,我介绍了运用延后请求进行交易的概念。 此概念具有两个操作选项 — 处理交易服务器错误,和在程序设置的条件下正常发送交易订单。从第二十六部分文章开始,我一直在逐步实现运用延后请求来处理交易服务器错误,从而能够在需要解决错误后将订单重新发送至服务器之时,调整参数并等待重发交易订单至服务器。
从本文开始,我们将开发一种功能,允许在特定条件下运用延后请求进行交易。
该函数库功能允许用户以编程方式创建条件,并遵此条件将交易订单发送到服务器。
例如:
这些示例都很简单,允许会有很多条件及其组合。 在此阶段,我们着手开发因帐户属性、品种和当前帐户上发生事件而产生变化的控制。 来自这三个列表中的条件能够以任意组合设置。
我们先从简单的事情入手 — 控制品种和帐户属性值的变化。 随后将控制帐户事件,并对它们作出反应。
为了把延后请求对象融合为交易逻辑的一部分(在一定条件下发送交易订单),我们需要在此对象中实现附加数据,从而存储延后请求激活条件,及其控制和处理方法。 数据存储将以二维数组的形式制定。 第一个维度用于存储条件编号(根据需要存储多个条件),而第二个维度将包含在第一维中指定编号条件的所有数据 — 条件源类型(品种、帐户或事件),条件本身(为每个源创建枚举),比较方法(>,<,==,!=,>=,<=),所跟踪属性的引用值,及其当前值。
在延后请求对象中设置的条件要在管理延后请求的类计时器中加以控制。 满足延后请求对象中设置的所有条件后,在同一个类里立即将激活的延后请求发送到服务器。
在当前文章中,我们着手利用延后请求来创建和检查交易 — 在特定条件下开仓。 我们仅跟踪测试 EA 中的两个条件 — 价格和时间。 条件可以分别设置(按价格或按时间),也可以联合设置(按价格和时间)。
与往常一样,我们首先添加新函数库消息的索引,以及相应的文本。
在 Datas.mqh 文件中编写所有必要的消息索引:
//--- CEvent MSG_EVN_EVENT, // Event MSG_EVN_TYPE, // Event type
...
//--- CAccount MSG_ACC_ACCOUNT, // Account MSG_ACC_PROP_LOGIN, // Account number
...
MSG_LIB_TEXT_REQUEST, // Pending request # MSG_LIB_TEXT_REQUEST_ACTIVATED, // Pending request activated: # MSG_LIB_TEXT_REQUEST_DATAS, // Trading request parameters MSG_LIB_TEXT_PEND_REQUEST_DATAS, // Pending trading request parameters MSG_LIB_TEXT_PEND_REQUEST_CREATED, // Pending request created MSG_LIB_TEXT_PEND_REQUEST_DELETED, // Removed due to expiration MSG_LIB_TEXT_PEND_REQUEST_EXECUTED, // Removed due to execution MSG_LIB_TEXT_PEND_REQUEST_GETTING_FAILED, // Failed to obtain a pending request object from the list MSG_LIB_TEXT_PEND_REQUEST_FAILED_ADD_PARAMS, // Failed to add request activation parameters. Error: MSG_LIB_TEXT_PEND_REQUEST_PRICE_CREATE, // Price at the moment of request generation
...
MSG_LIB_TEXT_PEND_REQUEST_ACTUAL_EXPIRATION, // Actual order lifetime MSG_LIB_TEXT_PEND_REQUEST_NO_FREE_IDS, // No free IDs to create a pending request MSG_LIB_TEXT_PEND_REQUEST_ACTIVATION_TERMS, // Activation conditions MSG_LIB_TEXT_PEND_REQUEST_CRITERION, // Criterion MSG_LIB_TEXT_PEND_REQUEST_ADD_CRITERIONS, // Added pending request activation conditions }; //+------------------------------------------------------------------+
并添上与新加索引相对应的消息文本:
//--- CEvent {"Событие","Event"}, {"Тип события","Event's type"},
...
//--- CAccount {"Аккаунт","Account"}, {"Номер счёта","Account number"},
...
{"Отложенный запрос #","Pending request #"}, {"Активирован отложенный запрос: #","Pending request activated: #"}, {"Параметры торгового запроса","Trade request parameters"}, {"Параметры отложенного торгового запроса","Pending trade request parameters"}, {"Создан отложенный запрос","Pending request created"}, {"Удалён в связи с окончанием времени его действия","Deleted due to expiration"}, {"Удалён в связи с его исполнением","Deleted due completed"}, {"Не удалось получить объект-отложенный запрос из списка","Failed to get pending request object from list"}, {"Не удалось добавить параметры активации запроса. Ошибка: ","Failed to add request activation parameters. Error: "}, {"Цена в момент создания запроса","Price at time of request create"},
...
{"Фактическое время жизни ордера","Actual of order lifetime"}, {"Нет свободных идентификаторов для создания отложенного запроса","No free IDs to create a pending request"}, {"Условия активации","Activation terms"}, {"Критерий","Criterion"}, {"Добавлены условия активации отложенного запроса","Pending request activation conditions added"}, }; //+---------------------------------------------------------------------+
鉴于单个延后请求对象处理的受控条件来源完全不同(在这种情况下,也许是帐户、品种和帐户事件,以后我们可能还会添加其他内容),故此我们需要掌握这些参数的数据源,以便跟踪指定延后请求的激活条件。 当跟踪帐户和品种参数时,它们可已有匹配的属性索引,而属性本身却完全不同。 为避免混淆,我们将指定数据源,并据其跟踪属性值。
在 Defines.mqh 文件中,编写延后请求激活来源的枚举:
//+------------------------------------------------------------------+ //| Pending request type | //+------------------------------------------------------------------+ enum ENUM_PEND_REQ_TYPE { PEND_REQ_TYPE_ERROR=PENDING_REQUEST_ID_TYPE_ERR, // Pending request created based on the return code or error PEND_REQ_TYPE_REQUEST=PENDING_REQUEST_ID_TYPE_REQ, // Pending request created by request }; //+------------------------------------------------------------------+ //| Pending request activation source | //+------------------------------------------------------------------+ enum ENUM_PEND_REQ_ACTIVATION_SOURCE { PEND_REQ_ACTIVATION_SOURCE_ACCOUNT, // Pending request activated by account data PEND_REQ_ACTIVATION_SOURCE_SYMBOL, // Pending request activated by symbol data PEND_REQ_ACTIVATION_SOURCE_EVENT, // Pending request activated by trading event data }; //+------------------------------------------------------------------+ //| Integer properties of a pending trading request | //+------------------------------------------------------------------+ enum ENUM_PEND_REQ_PROP_INTEGER {
另外,添加激活延后请求的可能准则的枚举。
对于由账户、品种和事件属性激活的准则,则使用单独的枚举:
//+------------------------------------------------------------------+ //| Possible criteria for activating requests by account properties | //+------------------------------------------------------------------+ enum ENUM_PEND_REQ_ACTIVATE_BY_ACCOUNT_PROP { //--- long PEND_REQ_ACTIVATE_BY_ACCOUNT_EMPTY = MSG_LIB_PROP_NOT_SET, // Value not set PEND_REQ_ACTIVATE_BY_ACCOUNT_LEVERAGE = MSG_ACC_PROP_LEVERAGE, // Activate by a provided leverage PEND_REQ_ACTIVATE_BY_ACCOUNT_LIMIT_ORDERS = MSG_ACC_PROP_LIMIT_ORDERS, // Activate by a maximum allowed number of active pending orders PEND_REQ_ACTIVATE_BY_ACCOUNT_TRADE_ALLOWED = MSG_ACC_PROP_TRADE_ALLOWED, // Activate by the permission to trade for the current account from the server side PEND_REQ_ACTIVATE_BY_ACCOUNT_TRADE_EXPERT = MSG_ACC_PROP_TRADE_EXPERT, // Activate by the permission to trade for an EA from the server side //--- double PEND_REQ_ACTIVATE_BY_ACCOUNT_BALANCE = MSG_ACC_PROP_BALANCE, // Activate by an account balance in the deposit currency PEND_REQ_ACTIVATE_BY_ACCOUNT_CREDIT = MSG_ACC_PROP_CREDIT, // Activate by credit in a deposit currency PEND_REQ_ACTIVATE_BY_ACCOUNT_PROFIT = MSG_ACC_PROP_PROFIT, // Activate by the current profit on the account in the deposit currency PEND_REQ_ACTIVATE_BY_ACCOUNT_EQUITY = MSG_ACC_PROP_EQUITY, // Sort by an account equity in the deposit currency PEND_REQ_ACTIVATE_BY_ACCOUNT_MARGIN = MSG_ACC_PROP_MARGIN, // Activate by an account reserved margin in the deposit currency PEND_REQ_ACTIVATE_BY_ACCOUNT_MARGIN_FREE = MSG_ACC_PROP_MARGIN_FREE, // Activate by account free funds available for opening a position in the deposit currency PEND_REQ_ACTIVATE_BY_ACCOUNT_MARGIN_LEVEL = MSG_ACC_PROP_MARGIN_LEVEL, // Activate by account margin level in % PEND_REQ_ACTIVATE_BY_ACCOUNT_MARGIN_INITIAL = MSG_ACC_PROP_MARGIN_INITIAL, // Activate by funds reserved on an account to ensure a guarantee amount for all pending orders PEND_REQ_ACTIVATE_BY_ACCOUNT_MARGIN_MAINTENANCE = MSG_ACC_PROP_MARGIN_MAINTENANCE, // Activate by funds reserved on an account to ensure a minimum amount for all open positions PEND_REQ_ACTIVATE_BY_ACCOUNT_ASSETS = MSG_ACC_PROP_ASSETS, // Activate by the current assets on the account PEND_REQ_ACTIVATE_BY_ACCOUNT_LIABILITIES = MSG_ACC_PROP_LIABILITIES, // Activate by the current liabilities on the account PEND_REQ_ACTIVATE_BY_ACCOUNT_COMMISSION_BLOCKED = MSG_ACC_PROP_COMMISSION_BLOCKED // Activate by the current amount of blocked commissions on the account }; //+------------------------------------------------------------------+ //| Possible criteria for activating requests by symbol properties | //+------------------------------------------------------------------+ enum ENUM_PEND_REQ_ACTIVATE_BY_SYMBOL_PROP { PEND_REQ_ACTIVATE_BY_SYMBOL_EMPTY = MSG_LIB_PROP_NOT_SET, // Value not set //--- double PEND_REQ_ACTIVATE_BY_SYMBOL_BID = MSG_LIB_PROP_BID, // Activate by Bid - the best price at which a symbol can be sold PEND_REQ_ACTIVATE_BY_SYMBOL_ASK = MSG_LIB_PROP_ASK, // Activate by Ask - best price, at which an instrument can be bought PEND_REQ_ACTIVATE_BY_SYMBOL_LAST = MSG_LIB_PROP_LAST, // Activate by the last deal price //--- long PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_DEALS = MSG_SYM_PROP_SESSION_DEALS, // Activate by number of deals in the current session PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_BUY_ORDERS = MSG_SYM_PROP_SESSION_BUY_ORDERS, // Activate by number of Buy orders at the moment PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_SELL_ORDERS = MSG_SYM_PROP_SESSION_SELL_ORDERS, // Activate by number of Sell orders at the moment PEND_REQ_ACTIVATE_BY_SYMBOL_VOLUME = MSG_SYM_PROP_VOLUME, // Activate by the last deal volume PEND_REQ_ACTIVATE_BY_SYMBOL_VOLUMEHIGH = MSG_SYM_PROP_VOLUMEHIGH, // Activate by maximum Volume per day PEND_REQ_ACTIVATE_BY_SYMBOL_VOLUMELOW = MSG_SYM_PROP_VOLUMELOW, // Activate by minimum Volume per day PEND_REQ_ACTIVATE_BY_SYMBOL_TIME = MSG_SYM_PROP_TIME, // Activate by the last quote time PEND_REQ_ACTIVATE_BY_SYMBOL_SPREAD = MSG_SYM_PROP_SPREAD, // Activate by spread in points PEND_REQ_ACTIVATE_BY_SYMBOL_START_TIME = MSG_SYM_PROP_START_TIME, // Activate by an instrument trading start date (usually used for futures) PEND_REQ_ACTIVATE_BY_SYMBOL_EXPIRATION_TIME = MSG_SYM_PROP_EXPIRATION_TIME, // Activate by an instrument trading completion date (usually used for futures) PEND_REQ_ACTIVATE_BY_SYMBOL_TRADE_STOPS_LEVEL = MSG_SYM_PROP_TRADE_STOPS_LEVEL, // Activate by the minimum indent from the current close price (in points) for setting Stop orders PEND_REQ_ACTIVATE_BY_SYMBOL_TRADE_FREEZE_LEVEL = MSG_SYM_PROP_TRADE_FREEZE_LEVEL, // Activate by trade operation freeze distance (in points) //--- double PEND_REQ_ACTIVATE_BY_SYMBOL_BIDHIGH = MSG_SYM_PROP_BIDHIGH, // Activate by a maximum Bid of the day PEND_REQ_ACTIVATE_BY_SYMBOL_BIDLOW = MSG_SYM_PROP_BIDLOW, // Activate by a minimum Bid of the day PEND_REQ_ACTIVATE_BY_SYMBOL_ASKHIGH = MSG_SYM_PROP_ASKHIGH, // Activate by a maximum Ask of the day PEND_REQ_ACTIVATE_BY_SYMBOL_ASKLOW = MSG_SYM_PROP_ASKLOW, // Activate by a minimum Ask of the day PEND_REQ_ACTIVATE_BY_SYMBOL_LASTHIGH = MSG_SYM_PROP_LASTHIGH, // Activate by the maximum Last of the day PEND_REQ_ACTIVATE_BY_SYMBOL_LASTLOW = MSG_SYM_PROP_LASTLOW, // Activate by the minimum Last of the day PEND_REQ_ACTIVATE_BY_SYMBOL_VOLUME_REAL = MSG_SYM_PROP_VOLUME_REAL, // Activate by Volume of the day PEND_REQ_ACTIVATE_BY_SYMBOL_VOLUMEHIGH_REAL = MSG_SYM_PROP_VOLUMEHIGH_REAL, // Activate by a maximum Volume of the day PEND_REQ_ACTIVATE_BY_SYMBOL_VOLUMELOW_REAL = MSG_SYM_PROP_VOLUMELOW_REAL, // Activate by a minimum Volume of the day PEND_REQ_ACTIVATE_BY_SYMBOL_OPTION_STRIKE = MSG_SYM_PROP_OPTION_STRIKE, // Activate by an option execution price PEND_REQ_ACTIVATE_BY_SYMBOL_TRADE_ACCRUED_INTEREST = MSG_SYM_PROP_TRADE_ACCRUED_INTEREST, // Activate by an accrued interest PEND_REQ_ACTIVATE_BY_SYMBOL_TRADE_FACE_VALUE = MSG_SYM_PROP_TRADE_FACE_VALUE, // Activate by a face value – initial bond value set by an issuer PEND_REQ_ACTIVATE_BY_SYMBOL_TRADE_LIQUIDITY_RATE = MSG_SYM_PROP_TRADE_LIQUIDITY_RATE, // Activate by a liquidity rate – the share of an asset that can be used for a margin PEND_REQ_ACTIVATE_BY_SYMBOL_SWAP_LONG = MSG_SYM_PROP_SWAP_LONG, // Activate by a long swap value PEND_REQ_ACTIVATE_BY_SYMBOL_SWAP_SHORT = MSG_SYM_PROP_SWAP_SHORT, // Activate by a short swap value PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_VOLUME = MSG_SYM_PROP_SESSION_VOLUME, // Activate by a summary volume of the current session deals PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_TURNOVER = MSG_SYM_PROP_SESSION_TURNOVER, // Activate by a summary turnover of the current session PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_INTEREST = MSG_SYM_PROP_SESSION_INTEREST, // Activate by a summary open interest PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_BUY_ORDERS_VOLUME = MSG_SYM_PROP_SESSION_BUY_ORDERS_VOLUME, // Activate by the current volume of Buy orders PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_SELL_ORDERS_VOLUME= MSG_SYM_PROP_SESSION_SELL_ORDERS_VOLUME, // Activate by the current volume of Sell orders PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_OPEN = MSG_SYM_PROP_SESSION_OPEN, // Activate by an open price of the current session PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_CLOSE = MSG_SYM_PROP_SESSION_CLOSE, // Activate by a close price of the current session PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_AW = MSG_SYM_PROP_SESSION_AW, // Activate by an average weighted session price PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_PRICE_SETTLEMENT = MSG_SYM_PROP_SESSION_PRICE_SETTLEMENT, // Activate by a settlement price of the current session PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_PRICE_LIMIT_MIN = MSG_SYM_PROP_SESSION_PRICE_LIMIT_MIN, // Activate by a minimum session price PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_PRICE_LIMIT_MAX = MSG_SYM_PROP_SESSION_PRICE_LIMIT_MAX, // Activate by a maximum session price }; //+------------------------------------------------------------------+ //| Possible criteria for activating requests by events | //+------------------------------------------------------------------+ enum ENUM_PEND_REQ_ACTIVATE_BY_EVENT { PEND_REQ_ACTIVATE_BY_EVENT_EMPTY = MSG_LIB_PROP_NOT_SET, // Value not set PEND_REQ_ACTIVATE_BY_EVENT_POSITION_OPENED = MSG_EVN_STATUS_MARKET_POSITION, // Position opened PEND_REQ_ACTIVATE_BY_EVENT_POSITION_CLOSED = MSG_EVN_STATUS_HISTORY_POSITION, // Position closed PEND_REQ_ACTIVATE_BY_EVENT_PENDING_ORDER_PLASED = MSG_EVN_PENDING_ORDER_PLASED, // Pending order placed PEND_REQ_ACTIVATE_BY_EVENT_PENDING_ORDER_REMOVED = MSG_EVN_PENDING_ORDER_REMOVED, // Pending order removed PEND_REQ_ACTIVATE_BY_EVENT_ACCOUNT_CREDIT = MSG_EVN_ACCOUNT_CREDIT, // Accruing credit (3) PEND_REQ_ACTIVATE_BY_EVENT_ACCOUNT_CHARGE = MSG_EVN_ACCOUNT_CHARGE, // Additional charges PEND_REQ_ACTIVATE_BY_EVENT_ACCOUNT_CORRECTION = MSG_EVN_ACCOUNT_CORRECTION, // Correcting entry PEND_REQ_ACTIVATE_BY_EVENT_ACCOUNT_BONUS = MSG_EVN_ACCOUNT_BONUS, // Charging bonuses PEND_REQ_ACTIVATE_BY_EVENT_ACCOUNT_COMISSION = MSG_EVN_ACCOUNT_COMISSION, // Additional commissions PEND_REQ_ACTIVATE_BY_EVENT_ACCOUNT_COMISSION_DAILY = MSG_EVN_ACCOUNT_COMISSION_DAILY, // Commission charged at the end of a day PEND_REQ_ACTIVATE_BY_EVENT_ACCOUNT_COMISSION_MONTHLY = MSG_EVN_ACCOUNT_COMISSION_MONTHLY, // Commission charged at the end of a trading month PEND_REQ_ACTIVATE_BY_EVENT_ACCOUNT_COMISSION_AGENT_DAILY = MSG_EVN_ACCOUNT_COMISSION_AGENT_DAILY, // Agent commission charged at the end of a trading day PEND_REQ_ACTIVATE_BY_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY = MSG_EVN_ACCOUNT_COMISSION_AGENT_MONTHLY, // Agent commission charged at the end of a month PEND_REQ_ACTIVATE_BY_EVENT_ACCOUNT_INTEREST = MSG_EVN_ACCOUNT_INTEREST, // Accruing interest on free funds PEND_REQ_ACTIVATE_BY_EVENT_BUY_CANCELLED = MSG_EVN_BUY_CANCELLED, // Canceled buy deal PEND_REQ_ACTIVATE_BY_EVENT_SELL_CANCELLED = MSG_EVN_SELL_CANCELLED, // Canceled sell deal PEND_REQ_ACTIVATE_BY_EVENT_DIVIDENT = MSG_EVN_DIVIDENT, // Accruing dividends PEND_REQ_ACTIVATE_BY_EVENT_DIVIDENT_FRANKED = MSG_EVN_DIVIDENT_FRANKED, // Accrual of franked dividend PEND_REQ_ACTIVATE_BY_EVENT_TAX = MSG_EVN_TAX, // Tax accrual PEND_REQ_ACTIVATE_BY_EVENT_ACCOUNT_BALANCE_REFILL = MSG_EVN_BALANCE_REFILL, // Replenishing account balance PEND_REQ_ACTIVATE_BY_EVENT_ACCOUNT_BALANCE_WITHDRAWAL = MSG_EVN_BALANCE_WITHDRAWAL, // Withdrawing funds from an account PEND_REQ_ACTIVATE_BY_EVENT_PENDING_ORDER_ACTIVATED = MSG_EVN_ACTIVATED_PENDING, // Pending order activated by price PEND_REQ_ACTIVATE_BY_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL = MSG_EVN_ACTIVATED_PENDING_PARTIALLY, // Pending order partially activated by price PEND_REQ_ACTIVATE_BY_EVENT_POSITION_OPENED_PARTIAL = MSG_EVN_POSITION_OPENED_PARTIALLY, // Position opened partially PEND_REQ_ACTIVATE_BY_EVENT_POSITION_CLOSED_PARTIAL = MSG_EVN_POSITION_CLOSED_PARTIALLY, // Position closed partially PEND_REQ_ACTIVATE_BY_EVENT_POSITION_CLOSED_BY_POS = MSG_EVN_POSITION_CLOSED_BY_POS, // Position closed by an opposite one PEND_REQ_ACTIVATE_BY_EVENT_POSITION_CLOSED_PARTIAL_BY_POS = MSG_EVN_POSITION_CLOSED_PARTIALLY_BY_POS, // Position partially closed by an opposite one PEND_REQ_ACTIVATE_BY_EVENT_POSITION_CLOSED_BY_SL = MSG_EVN_POSITION_CLOSED_BY_SL, // Position closed by StopLoss PEND_REQ_ACTIVATE_BY_EVENT_POSITION_CLOSED_BY_TP = MSG_EVN_POSITION_CLOSED_BY_TP, // Position closed by TakeProfit PEND_REQ_ACTIVATE_BY_EVENT_POSITION_CLOSED_PARTIAL_BY_SL = MSG_EVN_POSITION_CLOSED_PARTIALLY_BY_SL, // Position closed partially by StopLoss PEND_REQ_ACTIVATE_BY_EVENT_POSITION_CLOSED_PARTIAL_BY_TP = MSG_EVN_POSITION_CLOSED_PARTIALLY_BY_TP, // Position closed partially by TakeProfit PEND_REQ_ACTIVATE_BY_EVENT_POSITION_REVERSED_BY_MARKET = MSG_EVN_POSITION_REVERSED_BY_MARKET, // Position reversal by a new deal (netting) PEND_REQ_ACTIVATE_BY_EVENT_POSITION_REVERSED_BY_PENDING = MSG_EVN_POSITION_REVERSED_BY_PENDING, // Position reversal by activating a pending order (netting) PEND_REQ_ACTIVATE_BY_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL = MSG_EVN_POSITION_REVERSE_PARTIALLY, // Position reversal by partial market order execution (netting) PEND_REQ_ACTIVATE_BY_EVENT_POSITION_VOLUME_ADD_BY_MARKET = MSG_EVN_POSITION_VOLUME_ADD_BY_MARKET, // Added volume to a position by a new deal (netting) PEND_REQ_ACTIVATE_BY_EVENT_POSITION_VOLUME_ADD_BY_PENDING = MSG_EVN_POSITION_VOLUME_ADD_BY_PENDING, // Added volume to a position by activating a pending order (netting) PEND_REQ_ACTIVATE_BY_EVENT_MODIFY_ORDER_PRICE = MSG_EVN_MODIFY_ORDER_PRICE, // Order price change PEND_REQ_ACTIVATE_BY_EVENT_MODIFY_ORDER_PRICE_SL = MSG_EVN_MODIFY_ORDER_PRICE_SL, // Changing order and StopLoss price PEND_REQ_ACTIVATE_BY_EVENT_MODIFY_ORDER_PRICE_TP = MSG_EVN_MODIFY_ORDER_PRICE_TP, // Order and TakeProfit price change PEND_REQ_ACTIVATE_BY_EVENT_MODIFY_ORDER_PRICE_SL_TP = MSG_EVN_MODIFY_ORDER_PRICE_SL_TP, // Changing order, StopLoss and TakeProfit price PEND_REQ_ACTIVATE_BY_EVENT_MODIFY_ORDER_SL_TP = MSG_EVN_MODIFY_ORDER_SL_TP, // Changing order's StopLoss and TakeProfit price PEND_REQ_ACTIVATE_BY_EVENT_MODIFY_ORDER_SL = MSG_EVN_MODIFY_ORDER_SL, // Modify StopLoss order PEND_REQ_ACTIVATE_BY_EVENT_MODIFY_ORDER_TP = MSG_EVN_MODIFY_ORDER_TP, // Modify TakeProfit order PEND_REQ_ACTIVATE_BY_EVENT_MODIFY_POSITION_SL_TP = MSG_EVN_MODIFY_POSITION_SL_TP, // Change position's StopLoss and TakeProfit PEND_REQ_ACTIVATE_BY_EVENT_MODIFY_POSITION_SL = MSG_EVN_MODIFY_POSITION_SL, // Modify position's StopLoss PEND_REQ_ACTIVATE_BY_EVENT_MODIFY_POSITION_TP = MSG_EVN_MODIFY_POSITION_TP, // Modify position's TakeProfit PEND_REQ_ACTIVATE_BY_EVENT_POSITION_VOL_ADD_BY_MARKET_PARTIAL = MSG_EVN_REASON_ADD_PARTIALLY, // Added volume to a position by partial execution of a market order (netting) PEND_REQ_ACTIVATE_BY_EVENT_POSITION_VOL_ADD_BY_PENDING_PARTIAL = MSG_EVN_REASON_ADD_BY_PENDING_PARTIALLY, // Added volume to a position by partial activation of a pending order (netting) PEND_REQ_ACTIVATE_BY_EVENT_TRIGGERED_STOP_LIMIT_ORDER = MSG_EVN_REASON_STOPLIMIT_TRIGGERED, // StopLimit order activation PEND_REQ_ACTIVATE_BY_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL= MSG_EVN_REASON_REVERSE_BY_PENDING_PARTIALLY, // Position reversal by activating a pending order (netting) }; //+------------------------------------------------------------------+
枚举常量的值等于相应品种、帐户和事件属性的文本消息的常量值。 这令我们在日志中显示消息时,免于附加识别所描述的常量到底属于品种、账户或事件的必要。 取而代之,我们仅需简单地利用常量本身的索引即可显示消息。
利用激活条件的三个不同枚举,我们最终可为编译所需延后请求激活条件设置三个枚举常量的任意组合。
在 DELib.mqh 服务函数文件中,添加返回比较类型描述的函数:
//+------------------------------------------------------------------+ //| Return the comparison type description | //+------------------------------------------------------------------+ string ComparisonTypeDescription(const ENUM_COMPARER_TYPE type) { switch((int)type) { case EQUAL : return " == "; case MORE : return " > "; case LESS : return " < "; case EQUAL_OR_MORE : return " >= "; case EQUAL_OR_LESS : return " <= "; default : return " != "; } } //+------------------------------------------------------------------+
在许多函数库文件中,原有的 “STOP_LOSS” 和 “ TAKE_PROFIT” 字符串枚举常量的名称已被修改。 这些字符串的出现已分别替换为 “SL” 和 “TP”。
抽象延后请求的基准对象现在均由所有函数库对象的基准对象中继承。
将所有函数库对象的基准对象文件包含到 CPendRequest 类文件中,并令该类继承基准对象:
//+------------------------------------------------------------------+ //| PendRequest.mqh | //| Copyright 2019, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2019, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include <Object.mqh> #include "..\..\Services\DELib.mqh" #include "..\..\Objects\BaseObj.mqh" //+------------------------------------------------------------------+ //| Abstract pending trading request class | //+------------------------------------------------------------------+ class CPendRequest : public CBaseObj {
在该类的私密部分,声明存储跟踪延后请求激活条件数据的数组:
//+------------------------------------------------------------------+ //| Abstract pending trading request class | //+------------------------------------------------------------------+ class CPendRequest : public CBaseObj { private: MqlTradeRequest m_request; // Trade request structure CPause m_pause; // Pause class object /* Data on a pending request activation in the array: The first dimension contains the activation criteria number The second one features: m_activated_control[criterion number][0] - controlled property source m_activated_control[criterion number][1] - controlled property m_activated_control[criterion number][2] - type of comparing a controlled property with an actual value (=,>,<,!=,>=,<=) m_activated_control[criterion number][3] - property reference value for activation m_activated_control[criterion number][4] - actual property value */ double m_activated_control[][5]; // Array of reference values of the pending request activation criterion //--- Copy trading request data void CopyRequest(const MqlTradeRequest &request);
在同一私密部分中,添加返回 EA 设置中设定的魔幻数字和 ID 的方法,以及第一组和第二组 ID。
另外,声明返回已成功检查受控属性及其实际值标志的方法,比较两个受控属性值的方法,和返回所跟踪属性小数位数的方法,以便在日志中正确显示数值:
//--- Return (1) the magic number, ID of the (2) magic number, (3) the first group, (4) the second group, //--- (5) hedging account flag, (6) flag indicating the real property is equal to the value ulong GetMagic(void) const { return this.GetProperty(PEND_REQ_PROP_MQL_REQ_MAGIC); } ushort GetMagicID(void) const { return CBaseObj::GetMagicID((uint)this.GetProperty(PEND_REQ_PROP_MQL_REQ_MAGIC)); } uchar GetGroupID1(void) const { return CBaseObj::GetGroupID1((uint)this.GetProperty(PEND_REQ_PROP_MQL_REQ_MAGIC));} uchar GetGroupID2(void) const { return CBaseObj::GetGroupID2((uint)this.GetProperty(PEND_REQ_PROP_MQL_REQ_MAGIC));} bool IsHedge(void) const { return this.m_is_hedge; } bool IsEqualByMode(const int mode,const double value) const; bool IsEqualByMode(const int mode,const long value) const; //--- Return the flags indicating the pending request has completed changing each of the order/position parameters bool IsCompletedVolume(void) const; bool IsCompletedPrice(void) const; bool IsCompletedStopLimit(void) const; bool IsCompletedStopLoss(void) const; bool IsCompletedTakeProfit(void) const; bool IsCompletedTypeFilling(void) const; bool IsCompletedTypeTime(void) const; bool IsCompletedExpiration(void) const; //--- Return the flag of a successful check of a controlled object property and the appropriate actual property bool IsComparisonCompleted(const uint index) const; //--- Compare two data source values by a comparison type bool IsCompared(const double actual_value,const double control_value,const ENUM_COMPARER_TYPE compare) const; //--- Return the number of decimal places of a controlled property int DigitsControlledValue(const uint index) const; public:
返回魔幻数字和组 ID 的方法,调用 CBaseObj 父对象的同名方法,我们从中继承了基准抽象延后请求对象。
在类的公开部分简化访问请求对象属性的方法模块中,添加所有必要公开方法的声明,我们将进一步加以研究:
//+------------------------------------------------------------------+ //| Methods of a simplified access to the request object properties | //+------------------------------------------------------------------+ //--- Return (1) request structure, (2) status, (3) type, (4) price at the moment of the request generation, //--- (5) request generation time, (6) next attempt activation time, //--- (7) waiting time between requests, (8) current attempt index, //--- (9) number of attempts, (10) request ID //--- (11) result a request is based on, //--- (12) order ticket, (13) position ticket, (14) trading operation type MqlTradeRequest MqlRequest(void) const { return this.m_request; } ENUM_PEND_REQ_STATUS Status(void) const { return (ENUM_PEND_REQ_STATUS)this.GetProperty(PEND_REQ_PROP_STATUS); } ENUM_PEND_REQ_TYPE TypeRequest(void) const { return (ENUM_PEND_REQ_TYPE)this.GetProperty(PEND_REQ_PROP_TYPE); } double PriceCreate(void) const { return this.GetProperty(PEND_REQ_PROP_PRICE_CREATE); } ulong TimeCreate(void) const { return this.GetProperty(PEND_REQ_PROP_TIME_CREATE); } ulong TimeActivate(void) const { return this.GetProperty(PEND_REQ_PROP_TIME_ACTIVATE); } ulong WaitingMSC(void) const { return this.GetProperty(PEND_REQ_PROP_WAITING); } uchar CurrentAttempt(void) const { return (uchar)this.GetProperty(PEND_REQ_PROP_CURRENT_ATTEMPT); } uchar TotalAttempts(void) const { return (uchar)this.GetProperty(PEND_REQ_PROP_TOTAL); } uchar ID(void) const { return (uchar)this.GetProperty(PEND_REQ_PROP_ID); } int Retcode(void) const { return (int)this.GetProperty(PEND_REQ_PROP_RETCODE); } ulong Order(void) const { return this.GetProperty(PEND_REQ_PROP_MQL_REQ_ORDER); } ulong Position(void) const { return this.GetProperty(PEND_REQ_PROP_MQL_REQ_POSITION); } ENUM_TRADE_REQUEST_ACTIONS Action(void) const { return (ENUM_TRADE_REQUEST_ACTIONS)this.GetProperty(PEND_REQ_PROP_MQL_REQ_ACTION); } //--- Return the actual (1) volume, (2) order, (3) limit order, //--- (4) stoploss order and (5) takeprofit order prices, (6) order filling type, //--- (7) order expiration type and (8) order lifetime double ActualVolume(void) const { return this.GetProperty(PEND_REQ_PROP_ACTUAL_VOLUME); } double ActualPrice(void) const { return this.GetProperty(PEND_REQ_PROP_ACTUAL_PRICE); } double ActualStopLimit(void) const { return this.GetProperty(PEND_REQ_PROP_ACTUAL_STOPLIMIT); } double ActualSL(void) const { return this.GetProperty(PEND_REQ_PROP_ACTUAL_SL); } double ActualTP(void) const { return this.GetProperty(PEND_REQ_PROP_ACTUAL_TP); } ENUM_ORDER_TYPE_FILLING ActualTypeFilling(void) const { return (ENUM_ORDER_TYPE_FILLING)this.GetProperty(PEND_REQ_PROP_ACTUAL_TYPE_FILLING); } ENUM_ORDER_TYPE_TIME ActualTypeTime(void) const { return (ENUM_ORDER_TYPE_TIME)this.GetProperty(PEND_REQ_PROP_ACTUAL_TYPE_TIME); } datetime ActualExpiration(void) const { return (datetime)this.GetProperty(PEND_REQ_PROP_ACTUAL_EXPIRATION); } //--- Set (1) the price when creating a request, (2) request creation time, //--- (3) current attempt time, (4) waiting time between requests, //--- (5) current attempt index, (6) number of attempts, (7) ID, //--- (8) order ticket, (9) position ticket, (10) pending request type void SetPriceCreate(const double price) { this.SetProperty(PEND_REQ_PROP_PRICE_CREATE,price); } void SetTimeCreate(const ulong time) { this.SetProperty(PEND_REQ_PROP_TIME_CREATE,time); this.m_pause.SetTimeBegin(time); } void SetTimeActivate(const ulong time) { this.SetProperty(PEND_REQ_PROP_TIME_ACTIVATE,time); } void SetWaitingMSC(const ulong miliseconds) { this.SetProperty(PEND_REQ_PROP_WAITING,miliseconds); this.m_pause.SetWaitingMSC(miliseconds); } void SetCurrentAttempt(const uchar number) { this.SetProperty(PEND_REQ_PROP_CURRENT_ATTEMPT,number); } void SetTotalAttempts(const uchar number) { this.SetProperty(PEND_REQ_PROP_TOTAL,number); } void SetID(const uchar id) { this.SetProperty(PEND_REQ_PROP_ID,id); } void SetOrder(const ulong ticket) { this.SetProperty(PEND_REQ_PROP_MQL_REQ_ORDER,ticket); } void SetPosition(const ulong ticket) { this.SetProperty(PEND_REQ_PROP_MQL_REQ_POSITION,ticket); } void SetTypeRequest(const ENUM_PEND_REQ_TYPE type) { this.SetProperty(PEND_REQ_PROP_TYPE,type); } //--- Set the actual (1) volume, (2) order, (3) limit order, //--- (4) stoploss order and (5) takeprofit order prices, (6) order filling type, //--- (7) order expiration type and (8) order lifetime void SetActualVolume(const double volume) { this.SetProperty(PEND_REQ_PROP_ACTUAL_VOLUME,volume); } void SetActualPrice(const double price) { this.SetProperty(PEND_REQ_PROP_ACTUAL_PRICE,price); } void SetActualStopLimit(const double price) { this.SetProperty(PEND_REQ_PROP_ACTUAL_STOPLIMIT,price); } void SetActualSL(const double price) { this.SetProperty(PEND_REQ_PROP_ACTUAL_SL,price); } void SetActualTP(const double price) { this.SetProperty(PEND_REQ_PROP_ACTUAL_TP,price); } void SetActualTypeFilling(const ENUM_ORDER_TYPE_FILLING type) { this.SetProperty(PEND_REQ_PROP_ACTUAL_TYPE_FILLING,type); } void SetActualTypeTime(const ENUM_ORDER_TYPE_TIME type) { this.SetProperty(PEND_REQ_PROP_ACTUAL_TYPE_TIME,type); } void SetActualExpiration(const datetime expiration) { this.SetProperty(PEND_REQ_PROP_ACTUAL_EXPIRATION,expiration); } //--- Set a controlled property and a comparison method for a request activation criteria data by its index - both the actual one and the one in the object of //--- account, symbol or trading event property value (depends on 'source' value) for activating a pending request void SetNewActivationProperties(const ENUM_PEND_REQ_ACTIVATION_SOURCE source, const int property, const double control_value, const ENUM_COMPARER_TYPE comparer_type, const double actual_value); //--- Set a (1) controlled property, (2) comparison type, (3) object value and //--- (4) actual controlled property value for activating a pending request bool SetActivationProperty(const uint index,const ENUM_PEND_REQ_ACTIVATION_SOURCE source,const int property); bool SetActivationComparerType(const uint index,const ENUM_COMPARER_TYPE comparer_type); bool SetActivationControlValue(const uint index,const double value); bool SetActivationActualValue(const uint index,const double value); //--- Return (1) a pending request activation source, (2) controlled property, (3) comparison type, //--- (4) object value,(5) actual controlled property value for activating a pending request ENUM_PEND_REQ_ACTIVATION_SOURCE GetActivationSource(const uint index) const; int GetActivationProperty(const uint index) const; ENUM_COMPARER_TYPE GetActivationComparerType(const uint index) const; double GetActivationControlValue(const uint index) const; double GetActivationActualValue(const uint index) const; //--- Return the flag of a successful check of all controlled object properties and the appropriate actual properties bool IsAllComparisonCompleted(void) const;
SetTypeRequest() 方法将传递给方法的类型赋值给“延后请求类型”属性。 “基于错误代码的延后请求”或“依据请求创建的延后请求”均可作为类型。 根据“错误代码”参数,在类构造函数中自动设置对象的延后请求类型。 如果代码等于零,则这是由程序请求创建的延后请求。 因此,该方法现在于任何地方均未用到。 若您突然需要从外部(就个人而言,我还未能提出这一要求)快速更改延后请求类型的情况下,才会创建它。
将返回受控属性描述的方法声明添加到相应的方法模块之中:
//+------------------------------------------------------------------+ //| Descriptions of request object properties | //+------------------------------------------------------------------+ //--- Get description of a request (1) integer, (2) real and (3) string property string GetPropertyDescription(ENUM_PEND_REQ_PROP_INTEGER property); string GetPropertyDescription(ENUM_PEND_REQ_PROP_DOUBLE property); string GetPropertyDescription(ENUM_PEND_REQ_PROP_STRING property); //--- Return the description of a (1) controlled property, (2) comparison type, (3) controlled property value in the object, //--- (4) actual controlled property value for activating a pending request, (5) total number of activation conditions string GetActivationPropertyDescription(const uint index) const; string GetActivationComparerTypeDescription(const uint index) const; string GetActivationControlValueDescription(const uint index) const; string GetActivationActualValueDescription(const uint index) const; uint GetActivationCriterionTotal(void) const { return ::ArrayRange(this.m_activated_control,0); } //--- Return the names of pending request object parameters string StatusDescription(void) const; string TypeRequestDescription(void) const; string IDDescription(void) const; string RetcodeDescription(void) const; string TimeCreateDescription(void) const; string TimeActivateDescription(void) const; string TimeWaitingDescription(void) const; string CurrentAttemptDescription(void) const; string TotalAttemptsDescription(void) const; string PriceCreateDescription(void) const; string TypeFillingActualDescription(void) const; string TypeTimeActualDescription(void) const; string ExpirationActualDescription(void) const; string VolumeActualDescription(void) const; string PriceActualDescription(void) const; string StopLimitActualDescription(void) const; string StopLossActualDescription(void) const; string TakeProfitActualDescription(void) const; //--- Return the names of trading request structures parameters in the request object string MqlReqActionDescription(void) const; string MqlReqMagicDescription(void) const; string MqlReqOrderDescription(void) const; string MqlReqSymbolDescription(void) const; string MqlReqVolumeDescription(void) const; string MqlReqPriceDescription(void) const; string MqlReqStopLimitDescription(void) const; string MqlReqStopLossDescription(void) const; string MqlReqTakeProfitDescription(void) const; string MqlReqDeviationDescription(void) const; string MqlReqTypeOrderDescription(void) const; string MqlReqTypeFillingDescription(void) const; string MqlReqTypeTimeDescription(void) const; string MqlReqExpirationDescription(void) const; string MqlReqCommentDescription(void) const; string MqlReqPositionDescription(void) const; string MqlReqPositionByDescription(void) const; //--- Display (1) description of request properties (full_prop=true - all properties, false - only supported ones), //--- (2) request activation parameters, (3) short message about the request, (4) short request name (3 and 4 - implementation in the class descendants) void Print(const bool full_prop=false); void PrintActivations(void); virtual void PrintShort(void){;} virtual string Header(void){return NULL;} }; //+------------------------------------------------------------------+
GetActivationCriterionTotal() 方法返回激活条件数据之数组的第一维大小,换句话说,即延后请求对象中的激活条件数量。
在类构造函数中,会将第一维中的激活条件数据之数组的大小置为零:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CPendRequest::CPendRequest(const ENUM_PEND_REQ_STATUS status, const uchar id, const double price, const ulong time, const MqlTradeRequest &request, const int retcode) { this.CopyRequest(request); this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif; this.m_digits=(int)::SymbolInfoInteger(this.GetProperty(PEND_REQ_PROP_MQL_REQ_SYMBOL),SYMBOL_DIGITS); int dg=(int)DigitsLots(this.GetProperty(PEND_REQ_PROP_MQL_REQ_SYMBOL)); this.m_digits_lot=(dg==0 ? 1 : dg); this.SetProperty(PEND_REQ_PROP_STATUS,status); this.SetProperty(PEND_REQ_PROP_ID,id); this.SetProperty(PEND_REQ_PROP_RETCODE,retcode); this.SetProperty(PEND_REQ_PROP_TYPE,this.GetProperty(PEND_REQ_PROP_RETCODE)>0 ? PEND_REQ_TYPE_ERROR : PEND_REQ_TYPE_REQUEST); this.SetProperty(PEND_REQ_PROP_TIME_CREATE,time); this.SetProperty(PEND_REQ_PROP_PRICE_CREATE,price); this.m_pause.SetTimeBegin(this.GetProperty(PEND_REQ_PROP_TIME_CREATE)); this.m_pause.SetWaitingMSC(this.GetProperty(PEND_REQ_PROP_WAITING)); ::ArrayResize(this.m_activated_control,0,10); } //+------------------------------------------------------------------+
当逐个添加连续的激活条件时,激活条件数据之数组的大小会自动更改。
于显示延后请求对象数据完整列表的方法中,在显示其所有属性后,添加显示激活条件列表:
//+------------------------------------------------------------------+ //| Display the pending request properties in the journal | //+------------------------------------------------------------------+ void CPendRequest::Print(const bool full_prop=false) { int header_code= ( this.GetProperty(PEND_REQ_PROP_STATUS)==PEND_REQ_STATUS_OPEN ? MSG_LIB_TEXT_PEND_REQUEST_STATUS_OPEN : this.GetProperty(PEND_REQ_PROP_STATUS)==PEND_REQ_STATUS_CLOSE ? MSG_LIB_TEXT_PEND_REQUEST_STATUS_CLOSE : this.GetProperty(PEND_REQ_PROP_STATUS)==PEND_REQ_STATUS_SLTP ? MSG_LIB_TEXT_PEND_REQUEST_STATUS_SLTP : this.GetProperty(PEND_REQ_PROP_STATUS)==PEND_REQ_STATUS_PLACE ? MSG_LIB_TEXT_PEND_REQUEST_STATUS_PLACE : this.GetProperty(PEND_REQ_PROP_STATUS)==PEND_REQ_STATUS_REMOVE ? MSG_LIB_TEXT_PEND_REQUEST_STATUS_REMOVE : this.GetProperty(PEND_REQ_PROP_STATUS)==PEND_REQ_STATUS_MODIFY ? MSG_LIB_TEXT_PEND_REQUEST_STATUS_MODIFY : WRONG_VALUE ); ::Print("============= \"",CMessage::Text(header_code),"\" ============="); int beg=0, end=PEND_REQ_PROP_INTEGER_TOTAL; for(int i=beg; i<end; i++) { ENUM_PEND_REQ_PROP_INTEGER prop=(ENUM_PEND_REQ_PROP_INTEGER)i; if(!full_prop && !this.SupportProperty(prop)) continue; ::Print(this.GetPropertyDescription(prop)); } ::Print("------"); beg=end; end+=PEND_REQ_PROP_DOUBLE_TOTAL; for(int i=beg; i<end; i++) { ENUM_PEND_REQ_PROP_DOUBLE prop=(ENUM_PEND_REQ_PROP_DOUBLE)i; if(!full_prop && !this.SupportProperty(prop)) continue; ::Print(this.GetPropertyDescription(prop)); } ::Print("------"); beg=end; end+=PEND_REQ_PROP_STRING_TOTAL; for(int i=beg; i<end; i++) { ENUM_PEND_REQ_PROP_STRING prop=(ENUM_PEND_REQ_PROP_STRING)i; if(!full_prop && !this.SupportProperty(prop)) continue; ::Print(this.GetPropertyDescription(prop)); } this.PrintActivations(); ::Print("================== ",CMessage::Text(MSG_LIB_PARAMS_LIST_END),": \"",CMessage::Text(header_code),"\" ==================\n"); } //+------------------------------------------------------------------+
实现在日志显示里显示延后请求激活条件的方法:
//+------------------------------------------------------------------+ //| Display request activation parameters in the journal | //+------------------------------------------------------------------+ void CPendRequest::PrintActivations(void) { //--- Get the size of the activation conditions data array's first dimension, //--- if it exceeds zero, send all data written in the data array to the journal int range=::ArrayRange(this.m_activated_control,0); if(range>0) { ::Print("--- ",CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_ACTIVATION_TERMS)," ---"); for(int i=0;i<range;i++) { ENUM_PEND_REQ_ACTIVATION_SOURCE source=(ENUM_PEND_REQ_ACTIVATION_SOURCE)this.m_activated_control[i][0]; string type= ( source==PEND_REQ_ACTIVATION_SOURCE_ACCOUNT ? CMessage::Text(MSG_ACC_ACCOUNT) : source==PEND_REQ_ACTIVATION_SOURCE_SYMBOL ? CMessage::Text(MSG_LIB_PROP_SYMBOL) : source==PEND_REQ_ACTIVATION_SOURCE_EVENT ? CMessage::Text(MSG_EVN_EVENT) : "" ); ::Print(" - ",CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_CRITERION)," #",string(i+1),". ",type,": ",this.GetActivationPropertyDescription(i)); } } ::Print(""); } //+------------------------------------------------------------------+
在激活条件数据数组中创建激活延后请求新条件的方法:
//+------------------------------------------------------------------+ //| Set a controlled property, values | //| and comparison method for activating a pending request | //+------------------------------------------------------------------+ void CPendRequest::SetNewActivationProperties(const ENUM_PEND_REQ_ACTIVATION_SOURCE source, const int property, const double control_value, const ENUM_COMPARER_TYPE comparer_type, const double actual_value) { int range=::ArrayRange(this.m_activated_control,0); if(::ArrayResize(this.m_activated_control,range+1,10)==WRONG_VALUE) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_FAILED_ADD_PARAMS)); return; } this.m_activated_control[range][0]=source; this.m_activated_control[range][1]=property; this.m_activated_control[range][2]=comparer_type; this.m_activated_control[range][3]=control_value; this.m_activated_control[range][4]=actual_value; } //+---------------------------------------------------------------------+
激活数据源、激活条件、受控和实际激活条件值,以及比较方法均要传递给该方法。
激活条件数据之数组的大小加 1 ,且将方法输入中传递来的数值填充到数组中的所有必需数据。 该方法应仅用于添加新的激活条件。
以下方法调整请求对象中已经存在的激活条件:
//+---------------------------------------------------------------------+ //| Set a controlled property to activate a request | //+---------------------------------------------------------------------+ bool CPendRequest::SetActivationProperty(const uint index,const ENUM_PEND_REQ_ACTIVATION_SOURCE source,const int property) { int range=::ArrayRange(this.m_activated_control,0); if((int)index>range-1 || range==0) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(4002)); return false; } this.m_activated_control[index][0]=source; this.m_activated_control[index][1]=property; return true; } //+------------------------------------------------------------------+ //| Set the object property comparison type | //| with the actual one for a pending request activation | //+------------------------------------------------------------------+ bool CPendRequest::SetActivationComparerType(const uint index,const ENUM_COMPARER_TYPE comparer_type) { int range=::ArrayRange(this.m_activated_control,0); if((int)index>range-1 || range==0) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(4002)); return false; } this.m_activated_control[index][2]=comparer_type; return true; } //+------------------------------------------------------------------+ //| Set the controlled property | //| value for activating a pending request | //+------------------------------------------------------------------+ bool CPendRequest::SetActivationControlValue(const uint index,const double value) { int range=::ArrayRange(this.m_activated_control,0); if((int)index>range-1 || range==0) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(4002)); return false; } this.m_activated_control[index][3]=value; return true; } //+------------------------------------------------------------------+ //| Set the actual value | //| of a controlled property in the request object | //+------------------------------------------------------------------+ bool CPendRequest::SetActivationActualValue(const uint index,const double value) { int range=::ArrayRange(this.m_activated_control,0); if((int)index>range-1 || range==0) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(4002)); return false; } this.m_activated_control[index][4]=value; return true; } //+------------------------------------------------------------------+
为 SetActivationProperty()设置受控属性的方法将接收索引和两个条件参数 — 条件来源(品种、帐户或事件),和激活条件本身(来自上述研究的相应枚举),此举在于条件是由两个参数组成 — 属性变化来源和类型。 设置激活值的其他方法仅接收索引和参数值。
激活条件编号用作索引。 如果只有一个条件,则索引应等于零。 在两种情况下,取决于我们要修改的条件,索引应等于 0 或 1,依此类推。 当传递的索引超出数组的第一维大小时,在日志中会出现无效索引记录,并返回 false。<br4 />
返回激活条件参数的方法:
//+------------------------------------------------------------------+ //| Return a pending request activation source | //+------------------------------------------------------------------+ ENUM_PEND_REQ_ACTIVATION_SOURCE CPendRequest::GetActivationSource(const uint index) const { int range=::ArrayRange(this.m_activated_control,0); if((int)index>range-1 || range==0) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(4002)); return WRONG_VALUE; } return (ENUM_PEND_REQ_ACTIVATION_SOURCE)this.m_activated_control[index][0]; } //+------------------------------------------------------------------+ //| Return a controlled property to activate a request | //+------------------------------------------------------------------+ int CPendRequest::GetActivationProperty(const uint index) const { int range=::ArrayRange(this.m_activated_control,0); if((int)index>range-1 || range==0) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(4002)); return WRONG_VALUE; } return (int)this.m_activated_control[index][1]; } //+------------------------------------------------------------------+ //| Return the object property comparison type | //| with the actual one for a pending request activation | //+------------------------------------------------------------------+ ENUM_COMPARER_TYPE CPendRequest::GetActivationComparerType(const uint index) const { int range=::ArrayRange(this.m_activated_control,0); if((int)index>range-1 || range==0) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(4002)); return WRONG_VALUE; } return (ENUM_COMPARER_TYPE)this.m_activated_control[index][2]; } //+------------------------------------------------------------------+ //| Return the controlled property | //| value for activating a pending request | //+------------------------------------------------------------------+ double CPendRequest::GetActivationControlValue(const uint index) const { int range=::ArrayRange(this.m_activated_control,0); if((int)index>range-1 || range==0) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(4002)); return EMPTY_VALUE; } return this.m_activated_control[index][3]; } //+------------------------------------------------------------------+ //| Return the actual value | //| of a controlled property in the request object | //+------------------------------------------------------------------+ double CPendRequest::GetActivationActualValue(const uint index) const { int range=::ArrayRange(this.m_activated_control,0); if((int)index>range-1 || range==0) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(4002)); return EMPTY_VALUE; } return this.m_activated_control[index][4]; } //+------------------------------------------------------------------+
在此,所有操作都与设置激活属性类似,而例外则是,所有条件属性会被一次性返回,因此在每个方法中仅传递一个请求的激活条件索引就足够了。 如果传递了无效索引,则在日志中将出现无效索引记录。 对于返回整数值的方法,返回 -1,而对于返回实数值的方法,则返回 EMPTY_VALUE。
按指定的比较类型比较两个数值的方法:
//+------------------------------------------------------------------+ //| Compare two data source values by a comparison type | //+------------------------------------------------------------------+ bool CPendRequest::IsCompared(const double actual_value,const double control_value,const ENUM_COMPARER_TYPE compare) const { switch((int)compare) { case EQUAL : return(actual_value<control_value || actual_value>control_value ? false : true); case NO_EQUAL : return(actual_value<control_value || actual_value>control_value ? true : false); case MORE : return(actual_value>control_value ? true : false); case LESS : return(actual_value<control_value ? true : false); case EQUAL_OR_MORE : return(actual_value<control_value ? false : true); case EQUAL_OR_LESS : return(actual_value>control_value ? false : true); default : break; } return false; } //+------------------------------------------------------------------+
该方法接收欲比较属性的当前值,将与当前值进行比较的参考值,及比较类型。
根据比较类型,将当前属性值与其参考值进行比较,并返回比较结果。
该方法按激活条件数据之数组中的索引比较激活条件,并返回成功标志:
//+----------------------------------------------------------------------+ //| Return the flag of a successful check of a controlled object property| //| and the appropriate real property | //+----------------------------------------------------------------------+ bool CPendRequest::IsComparisonCompleted(const uint index) const { //--- If the controlled property is not set, return 'false' if(this.m_activated_control[index][1]==MSG_LIB_PROP_NOT_SET) return false; //--- Return the result of the specified comparison of a controlled property value with a real one ENUM_COMPARER_TYPE comparer=(ENUM_COMPARER_TYPE)this.m_activated_control[index][2]; return this.IsCompared(this.m_activated_control[index][4],this.m_activated_control[index][3],comparer); } //+------------------------------------------------------------------+
该方法返回延后请求激活条件之一的激活标志。 方法在输入里传递激活条件数据之数组的检查条件索引。 利用上述 IsCompared() 方法执行比较,然后返回比较结果。
该方法检查请求对象所创建的所有激活条件,并返回成功标志:
//+------------------------------------------------------------------+ //| Return the flag of successful check of all controlled properties | //| of the object and the appropriate actual properties | //+------------------------------------------------------------------+ bool CPendRequest::IsAllComparisonCompleted(void) const { bool res=true; int range=::ArrayRange(this.m_activated_control,0); if(range==0) return false; for(int i=0;i<range;i++) res &=this.IsComparisonCompleted(i); return res; } //+-------------------------------------------------------------------+
这是一种通用方法,允许检查任何延后请求对象的激活时间。
此处,激活条件数据之数组的第一维循环中,IsComparisonCompleted() 方法将检查标志添加到检查结果( res 变量)之中。 该检查定义了受控属性循环是否与索引匹配。 直至循环完成,将返回所有条件的检查结果。 如果至少一个条件未能满足,或者数据数组在第一维中的大小为零,则结果为 false。
返回小数位数的方法,以便在日志中正确显示激活条件:
//+-------------------------------------------------------------------+ //|Return the number of decimal places of a controlled property | //+-------------------------------------------------------------------+ int CPendRequest::DigitsControlledValue(const uint index) const { int dg=0; //--- Depending on the activation condition source, check the activation conditions //--- and write the required number of decimal places to the result switch((int)this.m_activated_control[index][0]) { //--- Account. If an activation condition is an integer value, then 0, //--- if it is a real value, then the number of decimal places in the current currency case PEND_REQ_ACTIVATION_SOURCE_ACCOUNT : dg=(this.m_activated_control[index][1]<PEND_REQ_ACTIVATE_BY_ACCOUNT_BALANCE ? 0 : this.m_digits_currency); break; //--- Symbol. Depending on a condition, write either a number of decimal places in a symbol quote, //--- or a number of decimal places in the current currency, or a number of decimal places in the lot value, or zero case PEND_REQ_ACTIVATION_SOURCE_SYMBOL : //--- digits if( (this.m_activated_control[index][1]<PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_DEALS && this.m_activated_control[index][1]>PEND_REQ_ACTIVATE_BY_SYMBOL_EMPTY) || this.m_activated_control[index][1]==PEND_REQ_ACTIVATE_BY_SYMBOL_OPTION_STRIKE || this.m_activated_control[index][1]>PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_SELL_ORDERS_VOLUME || (this.m_activated_control[index][1]>PEND_REQ_ACTIVATE_BY_SYMBOL_TRADE_FREEZE_LEVEL && this.m_activated_control[index][1]<PEND_REQ_ACTIVATE_BY_SYMBOL_VOLUME_REAL) ) dg=this.m_digits; //--- не digits else if( this.m_activated_control[index][1]>PEND_REQ_ACTIVATE_BY_SYMBOL_LASTLOW) { //--- digits currency if( (this.m_activated_control[index][1]>PEND_REQ_ACTIVATE_BY_SYMBOL_OPTION_STRIKE && this.m_activated_control[index][1]<PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_VOLUME) || this.m_activated_control[index][1]==PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_TURNOVER ) dg=this.m_digits_currency; //--- digits lots else dg=(this.m_digits_lot==0 ? 1 : this.m_digits_lot); } //--- 0 else dg=0; break; //--- Default is zero default: break; } return dg; } //+------------------------------------------------------------------+
该方法检查激活源,以及取决于来源的激活条件。 根据激活条件,系统将返回一个品种报价中的小数位数,或者返回当前帐户货币的小数位数,或者返回品种手数中的小数位数,或者返回零。
该方法返回受控属性的文本描述:
//+------------------------------------------------------------------+ //| Return the controlled property description by index | //+------------------------------------------------------------------+ string CPendRequest::GetActivationPropertyDescription(const uint index) const { //--- Get the activation source and, depending on that source, create a description text for a type of comparison with the reference value ENUM_PEND_REQ_ACTIVATION_SOURCE source=(ENUM_PEND_REQ_ACTIVATION_SOURCE)this.m_activated_control[index][0]; string value= ( source==PEND_REQ_ACTIVATION_SOURCE_EVENT ? "" : ( this.m_activated_control[index][1]==MSG_LIB_PROP_NOT_SET ? "" : this.GetActivationComparerTypeDescription(index)+this.GetActivationControlValueDescription(index) ) ); //--- Return the activation conditions description + comparison type + controlled value return ( source==PEND_REQ_ACTIVATION_SOURCE_ACCOUNT ? CMessage::Text((ENUM_PEND_REQ_ACTIVATE_BY_ACCOUNT_PROP)this.m_activated_control[index][1])+value : source==PEND_REQ_ACTIVATION_SOURCE_SYMBOL ? CMessage::Text((ENUM_PEND_REQ_ACTIVATE_BY_SYMBOL_PROP)this.m_activated_control[index][1])+value : source==PEND_REQ_ACTIVATION_SOURCE_EVENT ? CMessage::Text((ENUM_PEND_REQ_ACTIVATE_BY_EVENT)this.m_activated_control[index][1])+value : "" ); } //+------------------------------------------------------------------+
该方法接收激活条件数据之数组中的条件索引。 按索引获取激活来源,并根据索引获取其余的文本消息。 这些消息将用于安排和返回最终文本。
返回比较类型描述的方法:
//+------------------------------------------------------------------+ //| Return the comparison type description | //+------------------------------------------------------------------+ string CPendRequest::GetActivationComparerTypeDescription(const uint index) const { return ComparisonTypeDescription((ENUM_COMPARER_TYPE)this.m_activated_control[index][2]); } //+------------------------------------------------------------------+
该方法简单地按传递给方法的参数指定的激活条件索引返回数据之数组中设置的比较类型的文本描述。
该方法返回请求对象中受控属性值描述:
//+------------------------------------------------------------------+ //| Return a controlled property value description in an object | //+------------------------------------------------------------------+ string CPendRequest::GetActivationControlValueDescription(const uint index) const { return ( this.m_activated_control[index][3]!=EMPTY_VALUE ? ( this.m_activated_control[index][0]==PEND_REQ_ACTIVATION_SOURCE_SYMBOL && this.m_activated_control[index][1]==PEND_REQ_ACTIVATE_BY_SYMBOL_TIME ? ::TimeToString((ulong)this.m_activated_control[index][3]) : ::DoubleToString(this.m_activated_control[index][3],this.DigitsControlledValue(index)) ) : "" ); } //+------------------------------------------------------------------+
该方法接收条件索引。
按检查过的索引将受控属性值放置到数组。 若其等于指定的索引,且不等于“空值”(EMPTY_VALUE),则条件及其类型检验完毕。 若检查结果为品种时间,则返回时间的文本说明,否则,依据正确的小数位数返回整数型或实数型的文本说明。
该方法在请求对象中返回实际的受控属性值描述:
//+------------------------------------------------------------------+ //|Return an actual controlled property value description | //+------------------------------------------------------------------+ string CPendRequest::GetActivationActualValueDescription(const uint index) const { return ( this.m_activated_control[index][4]!=EMPTY_VALUE ? ( this.m_activated_control[index][0]==PEND_REQ_ACTIVATION_SOURCE_SYMBOL && this.m_activated_control[index][1]==PEND_REQ_ACTIVATE_BY_SYMBOL_TIME ? ::TimeToString((ulong)this.m_activated_control[index][4]) : ::DoubleToString(this.m_activated_control[index][4],this.DigitsControlledValue(index)) ) : "" ); } //+------------------------------------------------------------------+
除了数据是按激活条件数据之数组的第二维中的索引 4 得到的,该方法与上一个方法相同。 这些就是抽象延后请求基准对象的全部修改。
现在我们针对抽象请求基准对象的后代对象类进行一些改进。
由于我们已经实现了两种类型的延后请求 — 按错误代码、和按请求,而第二种对象并不代表存在某些属性 — 诸如服务器返回码(此处始终为零)、请求激活时间 (第二种类型的请求其时间可指定为请求激活条件之一,且其位于延后交易请求的激活条件数据之数组当中)、等待时间(此处根本未使用)、和当前尝试次数( 在此进行一次尝试,然后发送标准交易订单,并按交易服务器返回码进行处理)。
有关于此,我们补充延后请求基准对象的所有后代对象,即它们的方法要返回该对象是否支持整数型属性,并在每个后代对象的 PrintShort() 方法里添加调用在日志中显示延后请求激活条件列表的方法。
将以下修改添加到抽象延后请求基准对象的 PendReqOpen.mqh,PendReqClose.mqh,PendReqSLTP.mqh,PendReqPlace.mqh,PendReqRemove.mqh 和 PendReqModify.mqh 文件中(以 CPendReqOpen 类为例)://+------------------------------------------------------------------+ //| Return 'true' if an order supports a passed | //| integer property, otherwise return 'false' | //+------------------------------------------------------------------+ bool CPendReqOpen::SupportProperty(ENUM_PEND_REQ_PROP_INTEGER property) { if( (this.GetProperty(PEND_REQ_PROP_TYPE)==PEND_REQ_TYPE_REQUEST && (property==PEND_REQ_PROP_RETCODE || property==PEND_REQ_PROP_TIME_ACTIVATE || property==PEND_REQ_PROP_WAITING || property==PEND_REQ_PROP_CURRENT_ATTEMPT ) ) || property==PEND_REQ_PROP_MQL_REQ_ORDER || property==PEND_REQ_PROP_MQL_REQ_POSITION || property==PEND_REQ_PROP_MQL_REQ_POSITION_BY || property==PEND_REQ_PROP_MQL_REQ_EXPIRATION || property==PEND_REQ_PROP_MQL_REQ_TYPE_TIME ) return false; return true; } //+------------------------------------------------------------------+
系统确保这个对象是因请求而创建的。 如果是,则上述属性会被排除。
//+------------------------------------------------------------------+ //| Display a brief message with request data in the journal | //+------------------------------------------------------------------+ void CPendReqOpen::PrintShort(void) { string params=this.GetProperty(PEND_REQ_PROP_MQL_REQ_SYMBOL)+" "+::DoubleToString(this.GetProperty(PEND_REQ_PROP_MQL_REQ_VOLUME),this.m_digits_lot)+" "+ OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(PEND_REQ_PROP_MQL_REQ_TYPE)); string price=CMessage::Text(MSG_LIB_TEXT_REQUEST_PRICE)+" "+::DoubleToString(this.GetProperty(PEND_REQ_PROP_MQL_REQ_PRICE),this.m_digits); string sl=this.GetProperty(PEND_REQ_PROP_MQL_REQ_SL)>0 ? ", "+CMessage::Text(MSG_LIB_TEXT_REQUEST_SL)+" "+::DoubleToString(this.GetProperty(PEND_REQ_PROP_MQL_REQ_SL),this.m_digits) : ""; string tp=this.GetProperty(PEND_REQ_PROP_MQL_REQ_TP)>0 ? ", "+CMessage::Text(MSG_LIB_TEXT_REQUEST_TP)+" "+::DoubleToString(this.GetProperty(PEND_REQ_PROP_MQL_REQ_TP),this.m_digits) : ""; string time=this.IDDescription()+", "+CMessage::Text(MSG_LIB_TEXT_CREATED)+" "+TimeMSCtoString(this.GetProperty(PEND_REQ_PROP_TIME_CREATE)); string attempts=CMessage::Text(MSG_LIB_TEXT_ATTEMPTS)+" "+(string)this.GetProperty(PEND_REQ_PROP_TOTAL); string wait=CMessage::Text(MSG_LIB_TEXT_WAIT)+" "+::TimeToString(this.GetProperty(PEND_REQ_PROP_WAITING)/1000,TIME_SECONDS); string end=CMessage::Text(MSG_LIB_TEXT_END)+" "+ TimeMSCtoString(this.GetProperty(PEND_REQ_PROP_TIME_CREATE)+this.GetProperty(PEND_REQ_PROP_WAITING)*this.GetProperty(PEND_REQ_PROP_TOTAL)); //--- string message=CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_STATUS_OPEN)+": "+ "\n- "+params+", "+price+sl+tp+ "\n- "+time+", "+attempts+", "+wait+", "+end; ::Print(message); this.PrintActivations(); } //+------------------------------------------------------------------+
在 “+end” 字符串之后,我们删除了加入的文本换行代码(+“\n”),并在 ::Print(message); 字符串之后删除调用显示激活条件列表的方法。 如果条件数组的大小为零(对象是按错误代码创建的),则 PrintActivations() 除了文本换行代码(“\n”)外不打印任何内容。 否则,该方法将显示写入数据数组的所有条件的完整列表。
一些类里与日志显示有关内容已有了细微变化。 在此啰嗦没有意义。 您可以在附件中发现它们。
现在我们关注交易类。
在 CTrading 基准交易类中,将三个类成员变量和 GetFreeID() 方法从私密部分移至受保护部分:
private: CArrayInt m_list_errors; // Error list bool m_is_trade_disable; // Flag disabling trading bool m_use_sound; // The flag of using sounds of the object trading events uchar m_total_try; // Number of trading attempts MqlTradeRequest m_request; // Trading request prices ENUM_TRADE_REQUEST_ERR_FLAGS m_error_reason_flags; // Flags of error source in a trading method ENUM_ERROR_HANDLING_BEHAVIOR m_err_handling_behavior; // Behavior when handling error //--- Add the error code to the list
这些变量和方法在后代类中是必需的。 故此,它们应该位于受保护的区域中,以便衍生类能够访问到它们(不需要将它们放在公开区域 — 禁止外部访问它们)。 在该类的受保护部分中,添加返回带有延后请求 ID 的场内订单/持仓标志的方法。
作为结果,类的受保护部分如下所示:
//+------------------------------------------------------------------+ //| Trading class | //+------------------------------------------------------------------+ class CTrading : public CBaseObj { protected: CAccount *m_account; // Pointer to the current account object CSymbolsCollection *m_symbols; // Pointer to the symbol collection list CMarketCollection *m_market; // Pointer to the list of the collection of market orders and positions CHistoryCollection *m_history; // Pointer to the list of the collection of historical orders and deals CEventsCollection *m_events; // Pointer to the event collection list CArrayObj m_list_request; // List of pending requests uchar m_total_try; // Number of trading attempts MqlTradeRequest m_request; // Trade request structure ENUM_TRADE_REQUEST_ERR_FLAGS m_error_reason_flags; // Flags of error source in a trading method //--- Look for the first free pending request ID int GetFreeID(void); //--- Return the flag of a market order/position with a pending request ID bool IsPresentOrderByID(const uchar id); private:
从私密部分移出的变量和方法,以及新定义的方法在此会以彩色突出显示。
在该类的公开部分中,添加方法声明,按其在列表中的 ID 返回指向请求对象的指针,并声明返回品种交易对象记录等级的方法:
//--- Create a pending request bool CreatePendingRequest(const ENUM_PEND_REQ_STATUS status, const uchar id, const uchar attempts, const ulong wait, const MqlTradeRequest &request, const int retcode, CSymbol *symbol_obj, COrder *order); //--- Return (1) the pointer to the request object by its ID in the list, //--- (2) the logging level of a symbol trading object CPendRequest *GetPendRequestByID(const uchar id); ENUM_LOG_LEVEL GetTradeObjLogLevel(const string symbol_name); }; //+------------------------------------------------------------------+
我们在类主体之外实现这些方法。
实现返回品种交易对象日志记录等级的方法:
//+------------------------------------------------------------------+ //| Return the logging level of a symbol trading object | //+------------------------------------------------------------------+ ENUM_LOG_LEVEL CTrading::GetTradeObjLogLevel(const string symbol_name) { CTradeObj *trade_obj=this.GetTradeObjBySymbol(symbol_name,DFUN); return(trade_obj!=NULL ? trade_obj.GetLogLevel() : LOG_LEVEL_NO_MSG); } //+------------------------------------------------------------------+
该方法接收品种的名称,该品种交易对象应设置了记录级别。 从品种对象中获取交易对象。 如果收到对象,则返回对象的日志记录级别,否则返回禁用日志记录状态。
实现按列表中的 ID 返回指向请求对象指针的方法:
//+------------------------------------------------------------------+ //| Return the pointer to the request object by its ID in the list | //+------------------------------------------------------------------+ CPendRequest* CTrading::GetPendRequestByID(const uchar id) { int index=this.GetIndexPendingRequestByID(id); if(index==WRONG_VALUE) return NULL; return this.m_list_request.At(index); } //+------------------------------------------------------------------+
该方法接收请求 ID,然后按其 ID 从列表中获取延后请求对象索引。 如果列表中没有对象,则返回 NULL。 否则,按其在列表中的索引从列表中返回所获对象。。
实现按延后请求 ID 返回场内订单/持仓标志的方法:
//+------------------------------------------------------------------+ //| Return the flag of a market order/position | //| with a pending request ID | //+------------------------------------------------------------------+ bool CTrading::IsPresentOrderByID(const uchar id) { CArrayObj *list=this.m_market.GetList(ORDER_PROP_PEND_REQ_ID,id,EQUAL); return(list==NULL ? false : list.Total()!=0); } //+------------------------------------------------------------------+
该方法接收延后请求 ID。 接下来,按延后请求 ID 接收场内订单/持仓列表,及其数值。 如果未得到列表,或该列表为空(期望 ID没有对应的订单/持仓),则返回 false,否则返回 true。
在方法中我们还要加入其它检查,返回未占用 ID:
//+------------------------------------------------------------------+ //| Look for the first free pending request ID | //+------------------------------------------------------------------+ int CTrading::GetFreeID(void) { int id=WRONG_VALUE; CPendRequest *element=new CPendRequest(); if(element==NULL) return 0; for(int i=1;i<256;i++) { element.SetID((uchar)i); this.m_list_request.Sort(SORT_BY_PEND_REQ_ID); if(this.m_list_request.Search(element)==WRONG_VALUE) { if(this.IsPresentOrderByID((uchar)i)) continue; id=i; break; } } delete element; return id; } //+------------------------------------------------------------------+
为什么我们还需要另外检查订单/持仓是否有相应的 ID? 如果我们有一个已激活的延后请求,并基于该请求开立了一笔持仓,则该请求将从延后请求列表中被删除,且其 ID 可供创建新的延后请求时占用。
在创建新的延后请求时,其 ID 将等于当前开立持仓所用的 ID。 当满足新的延后请求激活条件时,将检查相同 ID 的持仓是否存在(该持仓应已存在,因为它用先前的 ID 开仓),且新的延后请求即会被删除。 由于存在相同 ID 的持仓,因此该请求被视为已执行。 换句话说,请求并非发送订单到服务器,而是被删除了。
有两种解决方案可避免这种情况:引入一个附加的标识来定义其是否为相同 ID 的另一个请求,亦或简单地检查列表中是否存在相同 ID 延后请求产生的持仓。
第二种选择对我来说似乎占用资源较少,尽管它有一个局限性,即在相同 ID 的持仓未平仓之前, 无法使用该空闲 ID。 换句话说,我们严守不同延后请求 ID 只能限定 255 笔持仓。
主要交易类别的改进至此完毕。
现在,我们最后确定 CTradingControl 交易管理类,它是 CTrading 主要交易类的后代。
在上一篇文章中开发延后请求管理类时,我们在类计时器中引入了处理延后请求对象的方法。
由于我们是按服务器返回代码创建单一类型的延后请求,因此将整个处理代码放在类计时器中就足够了。
如今,我们将添加按程序请求创建第二种类型的延后请求。
这意味着我们需要创建两个处理程序 — 第一个处理程序用于按错误代码创建请求,而第二个处理程序用于按请求创建请求。
由此,我们将引入两个延后请求对象的处理程序,将其按所处理请求的类型划分,同时在一个单独的方法中为两个处理程序创建相同的代码。 在这种情况下,我们只需要在计时器中检查请求类型,并调用相应的处理程序即可完成这两种类型的延后请求处理。
我们在类主体中添加所有必要的内容,并分析它们:
//+------------------------------------------------------------------+ //| Class for managing pending trading requests | //+------------------------------------------------------------------+ class CTradingControl : public CTrading { private: //--- Set actual order/position data to a pending request object void SetOrderActualProperties(CPendRequest *req_obj,const COrder *order); //--- Handler of pending requests created (1) by error code, (2) by request void OnPReqByErrCodeHandler(CPendRequest *req_obj,const int index); void OnPReqByRequestHandler(CPendRequest *req_obj,const int index); //--- Check a pending request relevance (activated or not) bool CheckPReqRelevance(CPendRequest *req_obj,const MqlTradeRequest &request,const int index); //--- Update relevant values of controlled properties in pending request objects, void RefreshControlActualDatas(CPendRequest *req_obj,const CSymbol *symbol); //--- Return the relevant (1) account, (2) symbol, (3) event data to control activation double GetActualDataAccount(const int property); double GetActualDataSymbol(const int property,const CSymbol *symbol); double GetActualDataEvent(const int property); public: //--- Return itself CTradingControl *GetObject(void) { return &this; } //--- Timer virtual void OnTimer(void); //--- Constructor CTradingControl(); //--- (1) Create a pending request (1) to open a position, (2) to place a pending order template<typename SL,typename TP> int OpenPositionPending(const ENUM_POSITION_TYPE type, const double volume, const string symbol, const ulong magic=ULONG_MAX, const SL sl=0, const TP tp=0, const uchar group_id1=0, const uchar group_id2=0, const string comment=NULL, const ulong deviation=ULONG_MAX, const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); template<typename PS,typename PL,typename SL,typename TP> int PlaceOrderPending( const ENUM_ORDER_TYPE order_type, const double volume, const string symbol, const PS price_stop, const PL price_limit=0, const SL sl=0, const TP tp=0, const ulong magic=ULONG_MAX, const string comment=NULL, const datetime expiration=0, const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); //--- Set pending request activation criteria bool SetNewActivationProperties(const uchar id, const ENUM_PEND_REQ_ACTIVATION_SOURCE source, const int property, const double control_value, const ENUM_COMPARER_TYPE comparer_type, const double actual_value); }; //+------------------------------------------------------------------+
从现在开始,我们需要将受控参数上的数据设置到延后请求对象(尽管之前我们还曾根据请求所基于的订单添加了相关数据),为了把相关订单数据设置到请求对象,将 SetActualProperties() 方法重命名为 SetOrderActualProperties(),从而避免混淆。
在本文中,我们仅利用延后请求处理开仓,故此创建延后请求的方法仍然超出本文讨论范围之外。
我们来研讨创建延后请求的开仓方法:
//+------------------------------------------------------------------+ //| Create a pending request for opening a position | //+------------------------------------------------------------------+ template<typename SL,typename TP> int CTradingControl::OpenPositionPending(const ENUM_POSITION_TYPE type, const double volume, const string symbol, const ulong magic=ULONG_MAX, const SL sl=0, const TP tp=0, const uchar group_id1=0, const uchar group_id2=0, const string comment=NULL, const ulong deviation=ULONG_MAX, const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { //--- Exit if the global trading ban flag is set if(this.IsTradingDisable()) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE)); return false; } //--- Set the trading request result as 'true' and the error flag as "no errors" bool res=true; this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR; ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)type; ENUM_ACTION_TYPE action=(ENUM_ACTION_TYPE)order_type; //--- Get a symbol object by a symbol name. CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(symbol); //--- If failed to get - write the "internal error" flag, display the message in the journal and return 'false' if(symbol_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ)); return false; } //--- get a trading object from a symbol object CTradeObj *trade_obj=symbol_obj.GetTradeObj(); //--- If failed to get - write the "internal error" flag, display the message in the journal and return 'false' if(trade_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ)); return false; } //--- Set the prices //--- If failed to set - write the "internal error" flag, set the error code in the return structure, //--- display the message in the journal and return 'false' if(!this.SetPrices(order_type,0,sl,tp,0,DFUN,symbol_obj)) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; trade_obj.SetResultRetcode(10021); trade_obj.SetResultComment(CMessage::Text(trade_obj.GetResultRetcode())); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(10021)); // No quotes to process the request return false; } //--- Look for the least of the possible IDs. If failed to find, return 'false' int id=this.GetFreeID(); if(id<1) { //--- No free IDs to create a pending request if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_NO_FREE_IDS)); return false; } //--- Write the volume, deviation, comment and filling type to the request structure this.m_request.volume=volume; this.m_request.deviation=(deviation==ULONG_MAX ? trade_obj.GetDeviation() : deviation); this.m_request.comment=(comment==NULL ? trade_obj.GetComment() : comment); this.m_request.type_filling=(type_filling>WRONG_VALUE ? type_filling : trade_obj.GetTypeFilling()); //--- Write pending request object ID to the magic number, add group IDs to the magic number value //--- and fill in the remaining unfilled trading request structure fields uint mn=(magic==ULONG_MAX ? (uint)trade_obj.GetMagic() : (uint)magic); this.SetPendReqID((uchar)id,mn); if(group_id1>0) this.SetGroupID1(group_id1,mn); if(group_id2>0) this.SetGroupID2(group_id2,mn); this.m_request.magic=mn; this.m_request.action=TRADE_ACTION_DEAL; this.m_request.symbol=symbol_obj.Name(); this.m_request.type=order_type; //--- As a result of creating a pending trading request, return either its ID or -1 if unsuccessful if(this.CreatePendingRequest(PEND_REQ_STATUS_OPEN,(uchar)id,1,ulong(END_TIME-(ulong)::TimeCurrent()),this.m_request,0,symbol_obj,NULL)) return id; return WRONG_VALUE; } //+------------------------------------------------------------------+
该方法是来自系列文章第二十六部分(及其后续)里开仓方法的简化版本,若交易服务器出错的情况下会创建延后请求。 以前曾对此进行了详细研讨,因此这里不再赘述。
该方法接收所有开仓的必要数据。 填充交易请求结构字段,并将其发送给延后请求的创建方法。
如果成功创建了延后请求,则返回新创建的延后请求 ID,否则返回 -1。
此处采用的最大可能等待时间,计算公式为终端中最大可能时间与当前时间之间的差值,以此作为重复尝试的间隔延迟。 因此,延后请求采用该最大可能生存期(最远至 3000 年 12 月 31 日)。
设置延后请求激活条件的方法:
//+------------------------------------------------------------------+ //| Set pending request activation criteria | //+------------------------------------------------------------------+ bool CTradingControl::SetNewActivationProperties(const uchar id, const ENUM_PEND_REQ_ACTIVATION_SOURCE source, const int property, const double control_value, const ENUM_COMPARER_TYPE comparer_type, const double actual_value) { CPendRequest *req_obj=this.GetPendRequestByID(id); if(req_obj==NULL) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_GETTING_FAILED)); return false; } req_obj.SetNewActivationProperties(source,property,control_value,comparer_type,actual_value); return true; } //+------------------------------------------------------------------+
该方法接收一个延后请求 ID,之后会在该请求里添加新的激活条件,它们是激活来源(品种、账户或事件)、激活条件、参考值、比较类型、和激活请求所需的受控属性的实际值。
接下来,按传递给方法的 ID 接收延后请求对象,并采用传递给方法的参数为其创建新的激活条件。
检查延后请求相关性的方法:
//+------------------------------------------------------------------+ //| Checking the pending request relevance | //+------------------------------------------------------------------+ bool CTradingControl::CheckPReqRelevance(CPendRequest *req_obj,const MqlTradeRequest &request,const int index) { //--- If this is a position opening or placing a pending order if((req_obj.Action()==TRADE_ACTION_DEAL && req_obj.Position()==0) || req_obj.Action()==TRADE_ACTION_PENDING) { //--- Get the pending request ID uchar id=this.GetPendReqID((uint)request.magic); //--- Get the list of orders/positions containing the order/position with the pending request ID CArrayObj *list=this.m_market.GetList(ORDER_PROP_PEND_REQ_ID,id,EQUAL); if(::CheckPointer(list)==POINTER_INVALID) return false; //--- If the order/position is present, the request is handled: remove it and proceed to the next (leave the method for the external loop) if(list.Total()>0) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(req_obj.Header(),": ",CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_EXECUTED)); this.m_list_request.Delete(index); return false; } } //--- Otherwise: full and partial position closure, removing an order, modifying order parameters and position stop orders else { CArrayObj *list=NULL; //--- if this is a position closure, including a closure by an opposite one if((req_obj.Action()==TRADE_ACTION_DEAL && req_obj.Position()>0) || req_obj.Action()==TRADE_ACTION_CLOSE_BY) { //--- Get a position with the necessary ticket from the list of open positions list=this.m_market.GetList(ORDER_PROP_TICKET,req_obj.Position(),EQUAL); if(::CheckPointer(list)==POINTER_INVALID) return false; //--- If the market has no such position, the request is handled: remove it and proceed to the next (leave the method for the external loop) if(list.Total()==0) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(req_obj.Header(),": ",CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_EXECUTED)); this.m_list_request.Delete(index); return false; } //--- Otherwise, if the position still exists, this is a partial closure else { //--- Get the list of all account trading events list=this.m_events.GetList(); if(list==NULL) return false; //--- In the loop from the end of the account trading event list int events_total=list.Total(); for(int j=events_total-1; j>WRONG_VALUE; j--) { //--- get the next trading event CEvent *event=list.At(j); if(event==NULL) continue; //--- If this event is a partial closure or there was a partial closure when closing by an opposite one if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL || event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS) { //--- If a position ticket in a trading event coincides with the ticket in a pending trading request if(event.PositionID()==req_obj.Position()) { //--- Get a position object from the list of market positions CArrayObj *list_orders=this.m_market.GetList(ORDER_PROP_POSITION_ID,req_obj.Position(),EQUAL); if(list_orders==NULL || list_orders.Total()==0) break; COrder *order=list_orders.At(list_orders.Total()-1); if(order==NULL) break; //--- Set actual position data to the pending request object this.SetOrderActualProperties(req_obj,order); //--- If (executed request volume + unexecuted request volume) is equal to the requested volume in a pending request - //--- the request is handled: remove it and break the loop by the list of account trading events if(req_obj.GetProperty(PEND_REQ_PROP_MQL_REQ_VOLUME)==event.VolumeOrderExecuted()+event.VolumeOrderCurrent()) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(req_obj.Header(),": ",CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_EXECUTED)); this.m_list_request.Delete(index); break; } } } } //--- If a handled pending request object was removed by the trading event list in the loop, move on to the next one (leave the method for the external loop) if(::CheckPointer(req_obj)==POINTER_INVALID) return false; } } //--- If this is a modification of position stop orders if(req_obj.Action()==TRADE_ACTION_SLTP) { //--- Get the list of all account trading events list=this.m_events.GetList(); if(list==NULL) return false; //--- In the loop from the end of the account trading event list int events_total=list.Total(); for(int j=events_total-1; j>WRONG_VALUE; j--) { //--- get the next trading event CEvent *event=list.At(j); if(event==NULL) continue; //--- If this is a change of the position's stop orders if(event.TypeEvent()>TRADE_EVENT_MODIFY_ORDER_TP) { //--- If a position ticket in a trading event coincides with the ticket in a pending trading request if(event.PositionID()==req_obj.Position()) { //--- Get a position object from the list of market positions CArrayObj *list_orders=this.m_market.GetList(ORDER_PROP_POSITION_ID,req_obj.Position(),EQUAL); if(list_orders==NULL || list_orders.Total()==0) break; COrder *order=list_orders.At(list_orders.Total()-1); if(order==NULL) break; //--- Set actual position data to the pending request object this.SetOrderActualProperties(req_obj,order); //--- If all modifications have worked out - //--- the request is handled: remove it and break the loop by the list of account trading events if(req_obj.IsCompleted()) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(req_obj.Header(),": ",CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_EXECUTED)); this.m_list_request.Delete(index); break; } } } } //--- If a handled pending request object was removed by the trading event list in the loop, move on to the next one (leave the method for the external loop) if(::CheckPointer(req_obj)==POINTER_INVALID) return false; } //--- If this is a pending order removal if(req_obj.Action()==TRADE_ACTION_REMOVE) { //--- Get the list of removed pending orders from the historical list list=this.m_history.GetList(ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_PENDING,EQUAL); if(::CheckPointer(list)==POINTER_INVALID) return false; //--- Leave a single order with the necessary ticket in the list list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,req_obj.Order(),EQUAL); //--- If the order is present, the request is handled: remove it and proceed to the next (leave the method for the external loop) if(list.Total()>0) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(req_obj.Header(),": ",CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_EXECUTED)); this.m_list_request.Delete(index); return false; } } //--- If this is a pending order modification if(req_obj.Action()==TRADE_ACTION_MODIFY) { //--- Get the list of all account trading events list=this.m_events.GetList(); if(list==NULL) return false; //--- In the loop from the end of the account trading event list int events_total=list.Total(); for(int j=events_total-1; j>WRONG_VALUE; j--) { //--- get the next trading event CEvent *event=list.At(j); if(event==NULL) continue; //--- If this event involves any change of modified pending order parameters if(event.TypeEvent()>TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER && event.TypeEvent()<TRADE_EVENT_MODIFY_POSITION_SL_TP) { //--- If an order ticket in a trading event coincides with the ticket in a pending trading request if(event.TicketOrderEvent()==req_obj.Order()) { //--- Get an order object from the list CArrayObj *list_orders=this.m_market.GetList(ORDER_PROP_TICKET,req_obj.Order(),EQUAL); if(list_orders==NULL || list_orders.Total()==0) break; COrder *order=list_orders.At(0); if(order==NULL) break; //--- Set actual order data to the pending request object this.SetOrderActualProperties(req_obj,order); //--- If all modifications have worked out - //--- the request is handled: remove it and break the loop by the list of account trading events if(req_obj.IsCompleted()) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(req_obj.Header(),": ",CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_EXECUTED)); this.m_list_request.Delete(index); break; } } } } } } //--- Exit if the pending request object has been removed after checking its operation (leave the method for the external loop) return(::CheckPointer(req_obj)==POINTER_INVALID ? false : true); } //+------------------------------------------------------------------+
该方法检查延后请求的执行,并在确认执行完毕后将其删除。 我们已在交易管理类计时器的代码中研究过此代码。 由于我们已按延后请求的类型分为两个处理程序,且两个处理程序的代码相似,故此将其放入单独的方法之中。 在每个处理程序中都会调用它。
按错误代码创建延后请求的处理程序:
//+------------------------------------------------------------------+ //| Handler of pending requests created by error code | //+------------------------------------------------------------------+ void CTradingControl::OnPReqByErrCodeHandler(CPendRequest *req_obj,const int index) { //--- get the request structure and the symbol object a trading operation should be performed for MqlTradeRequest request=req_obj.MqlRequest(); CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(request.symbol); if(symbol_obj==NULL || !symbol_obj.RefreshRates()) return; //--- Set the flag disabling trading in the terminal by two properties simultaneously //--- (the AutoTrading button in the terminal and the Allow Automated Trading option in the EA settings) //--- If any of the two properties is 'false', the flag is 'false' as well bool terminal_trade_allowed=::TerminalInfoInteger(TERMINAL_TRADE_ALLOWED); terminal_trade_allowed &=::MQLInfoInteger(MQL_TRADE_ALLOWED); //--- if the error has been caused by trading disabled on the terminal side and has been eliminated if(req_obj.Retcode()==10027 && terminal_trade_allowed) { //--- if the current attempt has not exceeded the defined number of trading attempts yet if(req_obj.CurrentAttempt()<req_obj.TotalAttempts()+1) { //--- Set the request creation time equal to its creation time minus waiting time, i.e. send the request immediately //--- Also, decrease the number of a successful attempt since during the next attempt, its number is increased, and if this is the last attempt, //--- it is not executed. However, this is related to fixing the error cause by a user, which means we need to give more time for the last attempt req_obj.SetTimeCreate(req_obj.TimeCreate()-req_obj.WaitingMSC()); req_obj.SetCurrentAttempt(uchar(req_obj.CurrentAttempt()>0 ? req_obj.CurrentAttempt()-1 : 0)); } } //--- if the current attempt exceeds the defined number of trading attempts, //--- or the current time exceeds the waiting time of all attempts //--- remove the current request object and proceed to the next (leave the method for the external loop) if(req_obj.CurrentAttempt()>req_obj.TotalAttempts() || req_obj.CurrentAttempt()>=UCHAR_MAX || (long)symbol_obj.Time()>long(req_obj.TimeCreate()+req_obj.WaitingMSC()*(req_obj.TotalAttempts()+1))) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(req_obj.Header(),": ",CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_DELETED)); this.m_list_request.Delete(index); return; } //--- Check the relevance of a pending request and exit to the external loop if the request is handled or an error occurs if(!this.CheckPReqRelevance(req_obj,request,index)) return; //--- Set the request activation time in the request object req_obj.SetTimeActivate(req_obj.TimeCreate()+req_obj.WaitingMSC()*(req_obj.CurrentAttempt()+1)); //--- If the current time is less than the request activation time, //--- this is not the request time - move on to the next request in the list (leave the method for the external loop) if((long)symbol_obj.Time()<(long)req_obj.TimeActivate()) return; //--- Set the attempt number in the request object req_obj.SetCurrentAttempt(uchar(req_obj.CurrentAttempt()+1)); //--- Display the number of a trading attempt in the journal if(this.m_log_level>LOG_LEVEL_NO_MSG) { ::Print(CMessage::Text(MSG_LIB_TEXT_RE_TRY_N)+(string)req_obj.CurrentAttempt()+":"); req_obj.PrintShort(); } //--- Depending on the type of action performed in the trading request switch(request.action) { //--- Opening/closing a position case TRADE_ACTION_DEAL : //--- If no ticket is present in the request structure - this is opening a position if(request.position==0) this.OpenPosition((ENUM_POSITION_TYPE)request.type,request.volume,request.symbol,request.magic,request.sl,request.tp,request.comment,request.deviation,request.type_filling); //--- If the ticket is present in the request structure - this is a position closure else this.ClosePosition(request.position,request.volume,request.comment,request.deviation); break; //--- Modify StopLoss/TakeProfit position case TRADE_ACTION_SLTP : this.ModifyPosition(request.position,request.sl,request.tp); break; //--- Close by an opposite one case TRADE_ACTION_CLOSE_BY : this.ClosePositionBy(request.position,request.position_by); break; //--- //--- Place a pending order case TRADE_ACTION_PENDING : this.PlaceOrder(request.type,request.volume,request.symbol,request.price,request.stoplimit,request.sl,request.tp,request.magic,request.comment,request.expiration,request.type_time,request.type_filling); break; //--- Modify a pending order case TRADE_ACTION_MODIFY : this.ModifyOrder(request.order,request.price,request.sl,request.tp,request.stoplimit,request.expiration,request.type_time,request.type_filling); break; //--- Remove a pending order case TRADE_ACTION_REMOVE : this.DeleteOrder(request.order); break; //--- default: break; } } //+------------------------------------------------------------------+
我们也曾在交易管理类计时器中研究过此代码。 唯一的区别是检查请求激活的处理已改为调用相应的方法。
按请求创建的延后请求的处理程序:
//+------------------------------------------------------------------+ //| The handler of pending requests created by request | //+------------------------------------------------------------------+ void CTradingControl::OnPReqByRequestHandler(CPendRequest *req_obj,const int index) { //--- get the request structure and the symbol object a trading operation should be performed for MqlTradeRequest request=req_obj.MqlRequest(); CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(request.symbol); if(symbol_obj==NULL || !symbol_obj.RefreshRates()) return; //--- Check the relevance of a pending request and exit to the external loop if the request is handled or an error occurs if(!this.CheckPReqRelevance(req_obj,request,index)) return; //--- Update relevant data on request activation conditions this.RefreshControlActualDatas(req_obj,symbol_obj); //--- If all pending request activation conditions are met if(req_obj.IsAllComparisonCompleted()) { //--- Set the attempt number in the request object req_obj.SetCurrentAttempt(uchar(req_obj.CurrentAttempt()+1)); //--- Display the request activation message in the journal if(this.m_log_level>LOG_LEVEL_NO_MSG) { ::Print(CMessage::Text(MSG_LIB_TEXT_REQUEST_ACTIVATED)+(string)req_obj.ID()+":"); req_obj.PrintShort(); } //--- Depending on the type of action performed in the trading request switch(request.action) { //--- Opening/closing a position case TRADE_ACTION_DEAL : //--- If no ticket is present in the request structure - this is opening a position if(request.position==0) this.OpenPosition((ENUM_POSITION_TYPE)request.type,request.volume,request.symbol,request.magic,request.sl,request.tp,request.comment,request.deviation,request.type_filling); //--- If the ticket is present in the request structure - this is a position closure else this.ClosePosition(request.position,request.volume,request.comment,request.deviation); break; //--- Modify StopLoss/TakeProfit position case TRADE_ACTION_SLTP : this.ModifyPosition(request.position,request.sl,request.tp); break; //--- Close by an opposite one case TRADE_ACTION_CLOSE_BY : this.ClosePositionBy(request.position,request.position_by); break; //--- //--- Place a pending order case TRADE_ACTION_PENDING : this.PlaceOrder(request.type,request.volume,request.symbol,request.price,request.stoplimit,request.sl,request.tp,request.magic,request.comment,request.expiration,request.type_time,request.type_filling); break; //--- Modify a pending order case TRADE_ACTION_MODIFY : this.ModifyOrder(request.order,request.price,request.sl,request.tp,request.stoplimit,request.expiration,request.type_time,request.type_filling); break; //--- Remove a pending order case TRADE_ACTION_REMOVE : this.DeleteOrder(request.order); break; //--- default: break; } } } //+------------------------------------------------------------------+
该方法比前一种方法稍微简单一些,因为它仅检查激活和发送交易订单的时间(满足延后请求激活条件)。
其中也调用了检查请求激活的方法。 同样,系统跟踪在其参数中设置的延后请求激活条件是否被触发。 如果所有条件都被触发,则将交易订单发送到服务器。
该方法会更新延后请求对象中受控属性的相关值:
//+------------------------------------------------------------------+ //| Update relevant values of controlled properties | //| in pending request objects | //+------------------------------------------------------------------+ void CTradingControl::RefreshControlActualDatas(CPendRequest *req_obj,const CSymbol *symbol) { //--- Exit if a request object has a request type based on an error code if(req_obj.GetProperty(PEND_REQ_PROP_TYPE)==PEND_REQ_TYPE_ERROR) return; double res=EMPTY_VALUE; //--- In the loop by all request object activation conditions, uint total=req_obj.GetActivationCriterionTotal(); for(uint i=0;i<total;i++) { //--- get the activation source ENUM_PEND_REQ_ACTIVATION_SOURCE source=req_obj.GetActivationSource(i); //--- receive the current value of a controlled property double value=req_obj.GetActivationActualValue(i),actual=EMPTY_VALUE; //--- Depending on the activation source, //--- write the current value of a controlled property to the activation conditions data array switch((int)source) { //--- Account case PEND_REQ_ACTIVATION_SOURCE_ACCOUNT : actual=this.GetActualDataAccount(req_obj.GetActivationProperty(i)); req_obj.SetActivationActualValue(i,(actual!=EMPTY_VALUE ? actual : value)); break; //--- Symbol case PEND_REQ_ACTIVATION_SOURCE_SYMBOL : actual=this.GetActualDataSymbol(req_obj.GetActivationProperty(i),symbol); req_obj.SetActivationActualValue(i,(actual!=EMPTY_VALUE ? actual : value)); break; //--- Event case PEND_REQ_ACTIVATION_SOURCE_EVENT : actual=this.GetActualDataEvent(req_obj.GetActivationProperty(i)); req_obj.SetActivationActualValue(i,(actual!=EMPTY_VALUE ? actual : value)); break; //--- Default is EMPTY_VALUE default: break; } } } //+------------------------------------------------------------------+
该方法接收激活条件数据的数组大小。 此外,我们会在循环中遍历所有条件。 根据激活条件来源,我们从相应的集合中获取实际(当前)数据,然后将它们写回到延后请求对象激活条件数据的数组当中。
返回相关账户数据的方法:
//+------------------------------------------------------------------+ //| Return the relevant account data to control activation | //+------------------------------------------------------------------+ double CTradingControl::GetActualDataAccount(const int property) { switch(property) { case PEND_REQ_ACTIVATE_BY_ACCOUNT_LEVERAGE : return (double)this.m_account.Leverage(); case PEND_REQ_ACTIVATE_BY_ACCOUNT_LIMIT_ORDERS : return (double)this.m_account.LimitOrders(); case PEND_REQ_ACTIVATE_BY_ACCOUNT_TRADE_ALLOWED : return (double)this.m_account.TradeAllowed(); case PEND_REQ_ACTIVATE_BY_ACCOUNT_TRADE_EXPERT : return (double)this.m_account.TradeExpert(); case PEND_REQ_ACTIVATE_BY_ACCOUNT_BALANCE : return this.m_account.Balance(); case PEND_REQ_ACTIVATE_BY_ACCOUNT_CREDIT : return this.m_account.Credit(); case PEND_REQ_ACTIVATE_BY_ACCOUNT_PROFIT : return this.m_account.Profit(); case PEND_REQ_ACTIVATE_BY_ACCOUNT_EQUITY : return this.m_account.Equity(); case PEND_REQ_ACTIVATE_BY_ACCOUNT_MARGIN : return this.m_account.Margin(); case PEND_REQ_ACTIVATE_BY_ACCOUNT_MARGIN_FREE : return this.m_account.MarginFree(); case PEND_REQ_ACTIVATE_BY_ACCOUNT_MARGIN_LEVEL : return this.m_account.MarginLevel(); case PEND_REQ_ACTIVATE_BY_ACCOUNT_MARGIN_INITIAL : return this.m_account.MarginInitial(); case PEND_REQ_ACTIVATE_BY_ACCOUNT_MARGIN_MAINTENANCE : return this.m_account.MarginMaintenance(); case PEND_REQ_ACTIVATE_BY_ACCOUNT_ASSETS : return this.m_account.Assets(); case PEND_REQ_ACTIVATE_BY_ACCOUNT_LIABILITIES : return this.m_account.Liabilities(); case PEND_REQ_ACTIVATE_BY_ACCOUNT_COMMISSION_BLOCKED : return this.m_account.ComissionBlocked(); default: return EMPTY_VALUE; } } //+------------------------------------------------------------------+
取决于帐户类型,再依据帐户条件类型枚举返回相应的帐户对象属性的值。
返回相关品种数据的方法:
//+------------------------------------------------------------------+ //| Return the relevant symbol data to control activation | //+------------------------------------------------------------------+ double CTradingControl::GetActualDataSymbol(const int property,const CSymbol *symbol) { switch(property) { case PEND_REQ_ACTIVATE_BY_SYMBOL_BID : return symbol.Bid(); case PEND_REQ_ACTIVATE_BY_SYMBOL_ASK : return symbol.Ask(); case PEND_REQ_ACTIVATE_BY_SYMBOL_LAST : return symbol.Last(); case PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_DEALS : return (double)symbol.SessionDeals(); case PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_BUY_ORDERS : return (double)symbol.SessionBuyOrders(); case PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_SELL_ORDERS : return (double)symbol.SessionSellOrders(); case PEND_REQ_ACTIVATE_BY_SYMBOL_VOLUME : return (double)symbol.Volume(); case PEND_REQ_ACTIVATE_BY_SYMBOL_VOLUMEHIGH : return (double)symbol.VolumeHigh(); case PEND_REQ_ACTIVATE_BY_SYMBOL_VOLUMELOW : return (double)symbol.VolumeLow(); case PEND_REQ_ACTIVATE_BY_SYMBOL_TIME : return (double)symbol.Time()/1000; case PEND_REQ_ACTIVATE_BY_SYMBOL_SPREAD : return symbol.Spread(); case PEND_REQ_ACTIVATE_BY_SYMBOL_START_TIME : return (double)symbol.StartTime(); case PEND_REQ_ACTIVATE_BY_SYMBOL_EXPIRATION_TIME : return (double)symbol.ExpirationTime(); case PEND_REQ_ACTIVATE_BY_SYMBOL_TRADE_STOPS_LEVEL : return symbol.TradeStopLevel(); case PEND_REQ_ACTIVATE_BY_SYMBOL_TRADE_FREEZE_LEVEL : return symbol.TradeFreezeLevel(); case PEND_REQ_ACTIVATE_BY_SYMBOL_BIDHIGH : return symbol.BidHigh(); case PEND_REQ_ACTIVATE_BY_SYMBOL_BIDLOW : return symbol.BidLow(); case PEND_REQ_ACTIVATE_BY_SYMBOL_ASKHIGH : return symbol.AskHigh(); case PEND_REQ_ACTIVATE_BY_SYMBOL_ASKLOW : return symbol.AskLow(); case PEND_REQ_ACTIVATE_BY_SYMBOL_LASTHIGH : return symbol.LastHigh(); case PEND_REQ_ACTIVATE_BY_SYMBOL_LASTLOW : return symbol.LastLow(); case PEND_REQ_ACTIVATE_BY_SYMBOL_VOLUME_REAL : return symbol.VolumeReal(); case PEND_REQ_ACTIVATE_BY_SYMBOL_VOLUMEHIGH_REAL : return symbol.VolumeHighReal(); case PEND_REQ_ACTIVATE_BY_SYMBOL_VOLUMELOW_REAL : return symbol.VolumeLowReal(); case PEND_REQ_ACTIVATE_BY_SYMBOL_OPTION_STRIKE : return symbol.OptionStrike(); case PEND_REQ_ACTIVATE_BY_SYMBOL_TRADE_ACCRUED_INTEREST : return symbol.TradeAccuredInterest(); case PEND_REQ_ACTIVATE_BY_SYMBOL_TRADE_FACE_VALUE : return symbol.TradeFaceValue(); case PEND_REQ_ACTIVATE_BY_SYMBOL_TRADE_LIQUIDITY_RATE : return symbol.TradeLiquidityRate(); case PEND_REQ_ACTIVATE_BY_SYMBOL_SWAP_LONG : return symbol.SwapLong(); case PEND_REQ_ACTIVATE_BY_SYMBOL_SWAP_SHORT : return symbol.SwapShort(); case PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_VOLUME : return symbol.SessionVolume(); case PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_TURNOVER : return symbol.SessionTurnover(); case PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_INTEREST : return symbol.SessionInterest(); case PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_BUY_ORDERS_VOLUME : return symbol.SessionBuyOrdersVolume(); case PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_SELL_ORDERS_VOLUME : return symbol.SessionSellOrdersVolume(); case PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_OPEN : return symbol.SessionOpen(); case PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_CLOSE : return symbol.SessionClose(); case PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_AW : return symbol.SessionAW(); case PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_PRICE_SETTLEMENT : return symbol.SessionPriceSettlement(); case PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_PRICE_LIMIT_MIN : return symbol.SessionPriceLimitMin(); case PEND_REQ_ACTIVATE_BY_SYMBOL_SESSION_PRICE_LIMIT_MAX : return symbol.SessionPriceLimitMax(); default: return EMPTY_VALUE; } } //+------------------------------------------------------------------+
取决于帐户类型,再依据品种条件类型枚举,返回相应的品种对象属性的值,传递给方法的指针为品种对象。
返回相关事件数据的方法:
//+------------------------------------------------------------------+ //| Return the relevant event data to control activation | //+------------------------------------------------------------------+ double CTradingControl::GetActualDataEvent(const int property) { if(this.m_events.IsEvent()) { ENUM_TRADE_EVENT event=this.m_events.GetLastTradeEvent(); switch(property) { case PEND_REQ_ACTIVATE_BY_EVENT_POSITION_OPENED : return event==TRADE_EVENT_POSITION_OPENED; case PEND_REQ_ACTIVATE_BY_EVENT_POSITION_CLOSED : return event==TRADE_EVENT_POSITION_CLOSED; case PEND_REQ_ACTIVATE_BY_EVENT_PENDING_ORDER_PLASED : return event==TRADE_EVENT_PENDING_ORDER_PLASED; case PEND_REQ_ACTIVATE_BY_EVENT_PENDING_ORDER_REMOVED : return event==TRADE_EVENT_PENDING_ORDER_REMOVED; case PEND_REQ_ACTIVATE_BY_EVENT_ACCOUNT_CREDIT : return event==TRADE_EVENT_ACCOUNT_CREDIT; case PEND_REQ_ACTIVATE_BY_EVENT_ACCOUNT_CHARGE : return event==TRADE_EVENT_ACCOUNT_CHARGE; case PEND_REQ_ACTIVATE_BY_EVENT_ACCOUNT_CORRECTION : return event==TRADE_EVENT_ACCOUNT_CORRECTION; case PEND_REQ_ACTIVATE_BY_EVENT_ACCOUNT_BONUS : return event==TRADE_EVENT_ACCOUNT_BONUS; case PEND_REQ_ACTIVATE_BY_EVENT_ACCOUNT_COMISSION : return event==TRADE_EVENT_ACCOUNT_COMISSION; case PEND_REQ_ACTIVATE_BY_EVENT_ACCOUNT_COMISSION_DAILY : return event==TRADE_EVENT_ACCOUNT_COMISSION_DAILY; case PEND_REQ_ACTIVATE_BY_EVENT_ACCOUNT_COMISSION_MONTHLY : return event==TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY; case PEND_REQ_ACTIVATE_BY_EVENT_ACCOUNT_COMISSION_AGENT_DAILY : return event==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY; case PEND_REQ_ACTIVATE_BY_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY : return event==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY; case PEND_REQ_ACTIVATE_BY_EVENT_ACCOUNT_INTEREST : return event==TRADE_EVENT_ACCOUNT_INTEREST; case PEND_REQ_ACTIVATE_BY_EVENT_BUY_CANCELLED : return event==TRADE_EVENT_BUY_CANCELLED; case PEND_REQ_ACTIVATE_BY_EVENT_SELL_CANCELLED : return event==TRADE_EVENT_SELL_CANCELLED; case PEND_REQ_ACTIVATE_BY_EVENT_DIVIDENT : return event==TRADE_EVENT_DIVIDENT; case PEND_REQ_ACTIVATE_BY_EVENT_DIVIDENT_FRANKED : return event==TRADE_EVENT_DIVIDENT_FRANKED; case PEND_REQ_ACTIVATE_BY_EVENT_TAX : return event==TRADE_EVENT_TAX; case PEND_REQ_ACTIVATE_BY_EVENT_ACCOUNT_BALANCE_REFILL : return event==TRADE_EVENT_ACCOUNT_BALANCE_REFILL; case PEND_REQ_ACTIVATE_BY_EVENT_ACCOUNT_BALANCE_WITHDRAWAL : return event==TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL; case PEND_REQ_ACTIVATE_BY_EVENT_PENDING_ORDER_ACTIVATED : return event==TRADE_EVENT_PENDING_ORDER_ACTIVATED; case PEND_REQ_ACTIVATE_BY_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL : return event==TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL; case PEND_REQ_ACTIVATE_BY_EVENT_POSITION_OPENED_PARTIAL : return event==TRADE_EVENT_POSITION_OPENED_PARTIAL; case PEND_REQ_ACTIVATE_BY_EVENT_POSITION_CLOSED_PARTIAL : return event==TRADE_EVENT_POSITION_CLOSED_PARTIAL; case PEND_REQ_ACTIVATE_BY_EVENT_POSITION_CLOSED_BY_POS : return event==TRADE_EVENT_POSITION_CLOSED_BY_POS; case PEND_REQ_ACTIVATE_BY_EVENT_POSITION_CLOSED_PARTIAL_BY_POS : return event==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS; case PEND_REQ_ACTIVATE_BY_EVENT_POSITION_CLOSED_BY_SL : return event==TRADE_EVENT_POSITION_CLOSED_BY_SL; case PEND_REQ_ACTIVATE_BY_EVENT_POSITION_CLOSED_BY_TP : return event==TRADE_EVENT_POSITION_CLOSED_BY_TP; case PEND_REQ_ACTIVATE_BY_EVENT_POSITION_CLOSED_PARTIAL_BY_SL : return event==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL; case PEND_REQ_ACTIVATE_BY_EVENT_POSITION_CLOSED_PARTIAL_BY_TP : return event==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP; case PEND_REQ_ACTIVATE_BY_EVENT_POSITION_REVERSED_BY_MARKET : return event==TRADE_EVENT_POSITION_REVERSED_BY_MARKET; case PEND_REQ_ACTIVATE_BY_EVENT_POSITION_REVERSED_BY_PENDING : return event==TRADE_EVENT_POSITION_REVERSED_BY_PENDING; case PEND_REQ_ACTIVATE_BY_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL : return event==TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL; case PEND_REQ_ACTIVATE_BY_EVENT_POSITION_VOLUME_ADD_BY_MARKET : return event==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET; case PEND_REQ_ACTIVATE_BY_EVENT_POSITION_VOLUME_ADD_BY_PENDING : return event==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING; case PEND_REQ_ACTIVATE_BY_EVENT_MODIFY_ORDER_PRICE : return event==TRADE_EVENT_MODIFY_ORDER_PRICE; case PEND_REQ_ACTIVATE_BY_EVENT_MODIFY_ORDER_PRICE_SL : return event==TRADE_EVENT_MODIFY_ORDER_PRICE_SL; case PEND_REQ_ACTIVATE_BY_EVENT_MODIFY_ORDER_PRICE_TP : return event==TRADE_EVENT_MODIFY_ORDER_PRICE_TP; case PEND_REQ_ACTIVATE_BY_EVENT_MODIFY_ORDER_PRICE_SL_TP : return event==TRADE_EVENT_MODIFY_ORDER_PRICE_SL_TP; case PEND_REQ_ACTIVATE_BY_EVENT_MODIFY_ORDER_SL_TP : return event==TRADE_EVENT_MODIFY_ORDER_SL_TP; case PEND_REQ_ACTIVATE_BY_EVENT_MODIFY_ORDER_SL : return event==TRADE_EVENT_MODIFY_ORDER_SL; case PEND_REQ_ACTIVATE_BY_EVENT_MODIFY_ORDER_TP : return event==TRADE_EVENT_MODIFY_ORDER_TP; case PEND_REQ_ACTIVATE_BY_EVENT_MODIFY_POSITION_SL_TP : return event==TRADE_EVENT_MODIFY_POSITION_SL_TP; case PEND_REQ_ACTIVATE_BY_EVENT_MODIFY_POSITION_SL : return event==TRADE_EVENT_MODIFY_POSITION_SL; case PEND_REQ_ACTIVATE_BY_EVENT_MODIFY_POSITION_TP : return event==TRADE_EVENT_MODIFY_POSITION_TP; case PEND_REQ_ACTIVATE_BY_EVENT_POSITION_VOL_ADD_BY_MARKET_PARTIAL : return event==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL; case PEND_REQ_ACTIVATE_BY_EVENT_POSITION_VOL_ADD_BY_PENDING_PARTIAL : return event==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL; case PEND_REQ_ACTIVATE_BY_EVENT_TRIGGERED_STOP_LIMIT_ORDER : return event==TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER; case PEND_REQ_ACTIVATE_BY_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL : return event==TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL; default: return EMPTY_VALUE; } } return EMPTY_VALUE; } //+------------------------------------------------------------------+
取决于条件类型和帐户中当前存在新事件,获取帐户中的最后一个事件。 根据事件条件类型的枚举,返回 最后一个事件与延后请求对象中受控值是否相等的标志(返回所发生的受控事件的标志)。
类计时器现在变得更加紧凑:
//+------------------------------------------------------------------+ //| Timer | //+------------------------------------------------------------------+ void CTradingControl::OnTimer(void) { //--- In a loop by the list of pending requests int total=this.m_list_request.Total(); for(int i=total-1;i>WRONG_VALUE;i--) { //--- receive the next request object CPendRequest *req_obj=this.m_list_request.At(i); if(req_obj==NULL) continue; //--- If a request object is created by an error code, use the handler of objects created by the error code if(req_obj.TypeRequest()==PEND_REQ_TYPE_ERROR) this.OnPReqByErrCodeHandler(req_obj,i); //--- Otherwise, this is an object created by request - use the handler of objects created by request else this.OnPReqByRequestHandler(req_obj,i); } } //+------------------------------------------------------------------+
现在,在类计时器中,检查从延后请求对象列表中获得的请求对象的类型。 根据其类型,调用我们上面验证过的相应延后请求处理程序。
这些就是目前交易管理类的改进。
您可从附件中找到完整的代码。
我们在 CEngine 函数库基准对象类的公开部分里加以补充。
为了能够接收交易对象的日志记录级别,以便从中检索基于函数库的程序消息,添加了按品种接收交易对象日志记录级别的方法:
void TradingSetTotalTry(const uchar attempts) { this.m_trading.SetTotalTry(attempts); } //--- Return the logging level of a trading class symbol trading object ENUM_LOG_LEVEL TradingGetLogLevel(const string symbol_name) { return this.m_trading.GetTradeObjLogLevel(symbol_name); } //--- Set standard sounds (symbol==NULL) for a symbol trading object, (symbol!=NULL) for trading objects of all symbols
该方法返回 GetTradeObjLogLevel() 交易管理类方法的操作结果。
声明创建开多头持仓和开空头持仓延后请求的方法,以及为新的延后请求设置激活条件的方法,并 编写按其 ID 返回指向延后请求对象指针的方法:
//--- Remove a pending order bool DeleteOrder(const ulong ticket); //--- Create a pending request (1) to open Buy and (2) Sell positions template<typename SL,typename TP> int OpenBuyPending(const double volume, const string symbol, const ulong magic=ULONG_MAX, const SL sl=0, const TP tp=0, const uchar group_id1=0, const uchar group_id2=0, const string comment=NULL, const ulong deviation=ULONG_MAX, const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); template<typename SL,typename TP> int OpenSellPending(const double volume, const string symbol, const ulong magic=ULONG_MAX, const SL sl=0, const TP tp=0, const uchar group_id1=0, const uchar group_id2=0, const string comment=NULL, const ulong deviation=ULONG_MAX, const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE); //--- Set pending request activation criteria bool SetNewActivationProperties(const uchar id, const ENUM_PEND_REQ_ACTIVATION_SOURCE source, const int property, const double control_value, const ENUM_COMPARER_TYPE comparer_type, const double actual_value); //--- Return the pointer to the request object by its ID in the list CPendRequest *GetPendRequestByID(const uchar id) { return this.m_trading.GetPendRequestByID(id); } //--- Return event (1) milliseconds, (2) reason and (3) source from its 'long' value
GetPendRequestByID() 方法返回指向延后请求对象的指针,返回同名交易管理类方法操作的结果。
实现创建延后请求开多头持仓的方法:
//+------------------------------------------------------------------+ //| Create a pending request for opening a Buy position | //+------------------------------------------------------------------+ template<typename SL,typename TP> int CEngine::OpenBuyPending(const double volume, const string symbol, const ulong magic=ULONG_MAX, const SL sl=0, const TP tp=0, const uchar group_id1=0, const uchar group_id2=0, const string comment=NULL, const ulong deviation=ULONG_MAX, const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { return this.m_trading.OpenPositionPending(POSITION_TYPE_BUY,volume,symbol,magic,sl,tp,group_id1,group_id2,comment,deviation,type_filling); } //+------------------------------------------------------------------+
该方法调用创建延后请求,开立交易管理类持仓。 传递 POSITION_TYPE_BUY 常量作为持仓类型(开仓的其他参数一并传递给该方法)。
实现创建延后请求开空头持仓的方法:
//+------------------------------------------------------------------+ //| Create a pending request for opening a Sell position | //+------------------------------------------------------------------+ template<typename SL,typename TP> int CEngine::OpenSellPending(const double volume, const string symbol, const ulong magic=ULONG_MAX, const SL sl=0, const TP tp=0, const uchar group_id1=0, const uchar group_id2=0, const string comment=NULL, const ulong deviation=ULONG_MAX, const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { return this.m_trading.OpenPositionPending(POSITION_TYPE_SELL,volume,symbol,magic,sl,tp,group_id1,group_id2,comment,deviation,type_filling); } //+------------------------------------------------------------------+
该方法调用创建延后请求,开立交易管理类持仓。 传递 POSITION_TYPE_SELL 常量作为持仓类型(开仓的其他参数一并传递给该方法)。
实现在延后请求对象中设置新激活条件的方法:
//+------------------------------------------------------------------+ //| Set pending request activation criteria | //+------------------------------------------------------------------+ bool CEngine::SetNewActivationProperties(const uchar id, const ENUM_PEND_REQ_ACTIVATION_SOURCE source, const int property, const double control_value, const ENUM_COMPARER_TYPE comparer_type, const double actual_value) { return this.m_trading.SetNewActivationProperties(id,source,property,control_value,comparer_type,actual_value); } //+------------------------------------------------------------------+
该方法调用在交易管理类的延后请求对象里添加新的激活条件的方法。 这些都是目前针对函数库的改进。
为了测试应用延后情趣开立持仓,利用来自上一篇文章中的 EA,并将其保存在新文件夹 \MQL5\Experts\TestDoEasy\Part31\ 之下,命名为 TestDoEasyPart31.mq5。
为了按照条件检查延后请求的操作,我们将在测试 EA 的交易面板中引入其他按钮。 这些按钮分别标记为 P — 价格条件,和 T — 时间条件。 如果已按下 P 或 T(或两个同时按下),那么单击买入或卖出会创建延后请求。 如果两者都按下,则延后请求拥有两个激活条件 — 按价格和时间。
另外,我们添加两个输入来设置与当前价格的距离(用于指定受控价格),并设置在当前时间帧的柱线数量作为请求激活时间。
因此,如果按下买入和 P按钮,则根据设置中指定的点数设置从当前价格开始的距离。 设定的数值将作为触发延后请求的参考值 — 当价格等于或低于计算得出的数值时,会激活延后请求。
如果按下 T按钮,那么计算时间应是当前时间 + 当前时间帧内指定的柱线数量的时间。 将此时间设置为触发延后请求的参考时间 — 若当前时间等于或超过计算的时间时,则激活延后请求。如果同时触发了 P 和 T按钮,则同时满足两个条件时才会激活延后请求。
若要开立空头持仓,受控价格将计算为当前价格 + 设置中指定的点数。 为了激活延后请求,当前价格应超过创建延后请求时的当期价格(按下卖出按钮)。
在创建请求时添加请求激活的参考价格与当前价格的缩进距离,并设置 延后请求激活时间的延迟柱线数:
//--- input variables input ushort InpMagic = 123; // Magic number input double InpLots = 0.1; // Lots input uint InpStopLoss = 150; // StopLoss in points input uint InpTakeProfit = 150; // TakeProfit in points input uint InpDistance = 50; // Pending orders distance (points) input uint InpDistanceSL = 50; // StopLimit orders distance (points) input uint InpDistancePReq = 50; // Distance for Pending Request's activate (points) input uint InpBarsDelayPReq = 5; // Bars delay for Pending Request's activate (current timeframe) input uint InpSlippage = 5; // Slippage in points input uint InpSpreadMultiplier = 1; // Spread multiplier for adjusting stop-orders by StopLevel input uchar InpTotalAttempts = 5; // Number of trading attempts sinput double InpWithdrawal = 10; // Withdrawal funds (in tester) sinput uint InpButtShiftX = 0; // Buttons X shift sinput uint InpButtShiftY = 10; // Buttons Y shift input uint InpTrailingStop = 50; // Trailing Stop (points) input uint InpTrailingStep = 20; // Trailing Step (points) input uint InpTrailingStart = 0; // Trailing Start (points) input uint InpStopLossModify = 20; // StopLoss for modification (points) input uint InpTakeProfitModify = 60; // TakeProfit for modification (points) sinput ENUM_SYMBOLS_MODE InpModeUsedSymbols = SYMBOLS_MODE_CURRENT; // Mode of used symbols list sinput string InpUsedSymbols = "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY"; // List of used symbols (comma - separator) sinput bool InpUseSounds = true; // Use sounds
在 EA 全局变量模块中添加存储激活价格缩进量,和延迟柱线数的相应变量,从而可设置延后请求激活时间, 以及延后请求按钮状态的标志:
//--- global variables CEngine engine; SDataButt butt_data[TOTAL_BUTT]; string prefix; double lot; double withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal); ushort magic_number; uint stoploss; uint takeprofit; uint distance_pending; uint distance_stoplimit; uint distance_pending_request; uint bars_delay_pending_request; uint slippage; bool trailing_on; bool pending_buy; bool pending_buy_limit; bool pending_buy_stop; bool pending_buy_stoplimit; bool pending_close_buy; bool pending_close_buy2; bool pending_close_buy_by_sell; bool pending_sell; bool pending_sell_limit; bool pending_sell_stop; bool pending_sell_stoplimit; bool pending_close_sell; bool pending_close_sell2; bool pending_close_sell_by_buy; double trailing_stop; double trailing_step; uint trailing_start; uint stoploss_to_modify; uint takeprofit_to_modify; int used_symbols_mode; string used_symbols; string array_used_symbols[]; bool testing; uchar group1; uchar group2; //+------------------------------------------------------------------+
在 EA 的 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(); //--- Set EA global variables prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"; testing=engine.IsTester(); for(int i=0;i<TOTAL_BUTT;i++) { butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i); butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i); } lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0)); magic_number=InpMagic; stoploss=InpStopLoss; takeprofit=InpTakeProfit; distance_pending=InpDistance; distance_stoplimit=InpDistanceSL; slippage=InpSlippage; trailing_stop=InpTrailingStop*Point(); trailing_step=InpTrailingStep*Point(); trailing_start=InpTrailingStart; stoploss_to_modify=InpStopLossModify; takeprofit_to_modify=InpTakeProfitModify; distance_pending_request=(InpDistancePReq<5 ? 5 : InpDistancePReq); bars_delay_pending_request=(InpBarsDelayPReq<1 ? 1 : InpBarsDelayPReq); //--- Initialize random group numbers group1=0; group2=0; srand(GetTickCount()); //--- Initialize DoEasy library OnInitDoEasy(); //--- Check and remove remaining EA graphical objects if(IsPresentObects(prefix)) ObjectsDeleteAll(0,prefix); //--- Create the button panel if(!CreateButtons(InpButtShiftX,InpButtShiftY)) return INIT_FAILED; //--- Set trailing activation button status ButtonState(butt_data[TOTAL_BUTT-1].name,trailing_on); //--- Reset states of the buttons for working using pending requests for(int i=0;i<14;i++) { ButtonState(butt_data[i].name+"_PRICE",false); ButtonState(butt_data[i].name+"_TIME",false); } //--- Check playing a standard sound by macro substitution and a custom sound by description engine.PlaySoundByDescription(SND_OK); Sleep(600); engine.PlaySoundByDescription(TextByLanguage("Звук упавшей монетки 2","Falling coin 2")); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
对于延后请求激活价格的距离,小于五个小数点的距离取舍到五个小数点,而最小延迟应设置为当前时间帧内至少一根柱线。
简单地令启用延后请求的按钮处于未激活状态。 鉴于这只是测试 EA,因此这些按钮仅出于测试原因才需要。 我们无需跟踪它们的状态。
在创建面板按钮的函数中,添加存储新按钮宽度的变量。 此外,也要在另一个循环中创建新的按钮来启用延后请求:
//+------------------------------------------------------------------+ //| Create the buttons panel | //+------------------------------------------------------------------+ bool CreateButtons(const int shift_x=20,const int shift_y=0) { int h=18,w=82,offset=2,wpt=14; int cx=offset+shift_x+wpt*2+2,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; } } h=18; offset=2; cx=offset+shift_x; cy=offset+shift_y+(h+1)*(TOTAL_BUTT/2)+3*h+1; x=cx; y=cy; shift=0; for(int i=0;i<14;i++) { y=(cy-(i-(i>6 ? 7 : 0))*(h+1)); if(!ButtonCreate(butt_data[i].name+"_PRICE",((i>6 && i<11) || i>10 ? x+wpt*2+w*2+5 : x),y,wpt,h,"P",(i<4 ? clrGreen : i>6 && i<11 ? clrChocolate : clrBlue))) { Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),butt_data[i].text+" \"P\""); return false; } if(!ButtonCreate(butt_data[i].name+"_TIME",((i>6 && i<11) || i>10 ? x+wpt*2+w*2+5+wpt+1 : x+wpt+1),y,wpt,h,"T",(i<4 ? clrGreen : i>6 && i<11 ? clrChocolate : clrBlue))) { Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),butt_data[i].text+" \"T\""); return false; } } ChartRedraw(0); return true; } //+------------------------------------------------------------------+
在设置按钮状态(激活按钮的颜色)的函数中,添加设置激活延后请求进行交易后的按钮颜色:
//+------------------------------------------------------------------+ //| Set the button status | //+------------------------------------------------------------------+ void ButtonState(const string name,const bool state) { ObjectSetInteger(0,name,OBJPROP_STATE,state); //--- Trailing activation button 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'); } //--- Buttons enabling pending requests if(StringFind(name,"_PRICE")>0 || StringFind(name,"_TIME")>0) { if(state) ObjectSetInteger(0,name,OBJPROP_BGCOLOR,C'255,220,90'); else ObjectSetInteger(0,name,OBJPROP_BGCOLOR,C'240,240,240'); } } //+------------------------------------------------------------------+
在处理按钮按下的函数里加入按下按钮后操控延后请求的代码:
//+------------------------------------------------------------------+ //| Handle pressing the buttons | //+------------------------------------------------------------------+ void PressButtonEvents(const string button_name) { bool comp_magic=true; // Temporary variable selecting the composite magic number with random group IDs string comment=""; //--- Convert button name into its string ID string button=StringSubstr(button_name,StringLen(prefix)); //--- Random group 1 and 2 numbers within the range of 0 - 15 group1=(uchar)Rand(); group2=(uchar)Rand(); uint magic=(comp_magic ? engine.SetCompositeMagicNumber(magic_number,group1,group2) : magic_number); //--- If the button is pressed if(ButtonState(button_name)) { //--- If the BUTT_BUY button is pressed: Open Buy position if(button==EnumToString(BUTT_BUY)) { //--- If the pending request creation buttons are not pressed, open Buy if(!pending_buy) engine.OpenBuy(lot,Symbol(),magic,stoploss,takeprofit); // No comment - the default comment is to be set //--- Otherwise, create a pending request for opening a Buy position else { int id=engine.OpenBuyPending(lot,Symbol(),magic,stoploss,takeprofit); if(id>0) { //--- If the price criterion is selected if(ButtonState(prefix+EnumToString(BUTT_BUY)+"_PRICE")) { double ask=SymbolInfoDouble(NULL,SYMBOL_ASK); double control_value=NormalizeDouble(ask-distance_pending_request*SymbolInfoDouble(NULL,SYMBOL_POINT),(int)SymbolInfoInteger(NULL,SYMBOL_DIGITS)); engine.SetNewActivationProperties((uchar)id,PEND_REQ_ACTIVATION_SOURCE_SYMBOL,PEND_REQ_ACTIVATE_BY_SYMBOL_ASK,control_value,EQUAL_OR_LESS,ask); } //--- If the time criterion is selected if(ButtonState(prefix+EnumToString(BUTT_BUY)+"_TIME")) { ulong control_time=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); engine.SetNewActivationProperties((uchar)id,PEND_REQ_ACTIVATION_SOURCE_SYMBOL,PEND_REQ_ACTIVATE_BY_SYMBOL_TIME,control_time,EQUAL_OR_MORE,TimeCurrent()); } } CPendRequest *req_obj=engine.GetPendRequestByID((uchar)id); if(req_obj==NULL) return; if(engine.TradingGetLogLevel(Symbol())>LOG_LEVEL_NO_MSG) { ::Print(CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_ADD_CRITERIONS)," #",req_obj.ID(),":"); req_obj.PrintActivations(); } } } //--- If the BUTT_BUY_LIMIT button is pressed: Place BuyLimit else if(button==EnumToString(BUTT_BUY_LIMIT)) { //--- If the pending request creation buttons are not pressed, set BuyLimit if(!pending_buy_limit) engine.PlaceBuyLimit(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyLimit","Pending BuyLimit order")); //--- Otherwise, do nothing in this version else { } } //--- If the BUTT_BUY_STOP button is pressed: Set BuyStop else if(button==EnumToString(BUTT_BUY_STOP)) { //--- If the pending request creation buttons are not pressed, set BuyStop if(!pending_buy_stop) engine.PlaceBuyStop(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyStop","Pending BuyStop order")); //--- Otherwise, do nothing in this version else { } } //--- If the BUTT_BUY_STOP_LIMIT button is pressed: Set BuyStopLimit else if(button==EnumToString(BUTT_BUY_STOP_LIMIT)) { //--- If the pending request creation buttons are not pressed, set BuyStopLimit if(!pending_buy_stoplimit) engine.PlaceBuyStopLimit(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic,TextByLanguage("Отложенный BuyStopLimit","Pending order BuyStopLimit")); //--- Otherwise, do nothing in this version else { } } //--- If the BUTT_SELL button is pressed: Open Sell position else if(button==EnumToString(BUTT_SELL)) { //--- If the pending request creation buttons are not pressed, open Sell if(!pending_sell) engine.OpenSell(lot,Symbol(),magic,stoploss,takeprofit); // No comment - the default comment is to be set //--- Otherwise, create a pending request for opening a Sell position else { int id=engine.OpenSellPending(lot,Symbol(),magic,stoploss,takeprofit); if(id>0) { //--- If the price criterion is selected if(ButtonState(prefix+EnumToString(BUTT_SELL)+"_PRICE")) { double bid=SymbolInfoDouble(NULL,SYMBOL_BID); double control_value=NormalizeDouble(bid+distance_pending_request*SymbolInfoDouble(NULL,SYMBOL_POINT),(int)SymbolInfoInteger(NULL,SYMBOL_DIGITS)); engine.SetNewActivationProperties((uchar)id,PEND_REQ_ACTIVATION_SOURCE_SYMBOL,PEND_REQ_ACTIVATE_BY_SYMBOL_BID,control_value,EQUAL_OR_MORE,bid); } //--- If the time criterion is selected if(ButtonState(prefix+EnumToString(BUTT_SELL)+"_TIME")) { ulong control_time=TimeCurrent()+bars_delay_pending_request*PeriodSeconds(); engine.SetNewActivationProperties((uchar)id,PEND_REQ_ACTIVATION_SOURCE_SYMBOL,PEND_REQ_ACTIVATE_BY_SYMBOL_TIME,control_time,EQUAL_OR_MORE,TimeCurrent()); } } CPendRequest *req_obj=engine.GetPendRequestByID((uchar)id); if(req_obj==NULL) return; if(engine.TradingGetLogLevel(Symbol())>LOG_LEVEL_NO_MSG) { ::Print(CMessage::Text(MSG_LIB_TEXT_PEND_REQUEST_ADD_CRITERIONS)," #",req_obj.ID(),":"); req_obj.PrintActivations(); } } } //--- If the BUTT_SELL_LIMIT button is pressed: Set SellLimit else if(button==EnumToString(BUTT_SELL_LIMIT)) { //--- If the pending request creation buttons are not pressed, set SellLimit if(!pending_sell_limit) engine.PlaceSellLimit(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellLimit","Pending SellLimit order")); //--- Otherwise, do nothing in this version else { } } //--- If the BUTT_SELL_STOP button is pressed: Set SellStop else if(button==EnumToString(BUTT_SELL_STOP)) { //--- If the pending request creation buttons are not pressed, set SellStop if(!pending_sell_stop) engine.PlaceSellStop(lot,Symbol(),distance_pending,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellStop","Pending SellStop order")); //--- Otherwise, do nothing in this version else { } } //--- If the BUTT_SELL_STOP_LIMIT button is pressed: Set SellStopLimit else if(button==EnumToString(BUTT_SELL_STOP_LIMIT)) { //--- If the pending request creation buttons are not pressed, set SellStopLimit if(!pending_sell_stoplimit) engine.PlaceSellStopLimit(lot,Symbol(),distance_pending,distance_stoplimit,stoploss,takeprofit,magic,TextByLanguage("Отложенный SellStopLimit","Pending SellStopLimit order")); //--- Otherwise, do nothing in this version else { } } //--- 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 and for the current symbol only list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); 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) { //--- Get the Buy position object and close a position by ticket COrder* position=list.At(index); if(position!=NULL) engine.ClosePosition((ulong)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 and for the current symbol only list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); 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); //--- Close the Buy position partially if(position!=NULL) engine.ClosePositionPartially((ulong)position.Ticket(),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)) { //--- In case of a hedging account if(engine.IsHedge()) { CArrayObj *list_buy=NULL, *list_sell=NULL; //--- Get the list of all open positions CArrayObj* list=engine.GetListMarketPosition(); if(list==NULL) return; //--- Select only current symbol positions from the list list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); //--- Select only Buy positions from the list list_buy=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); if(list_buy==NULL) return; //--- 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); //--- Select only Sell positions from the list list_sell=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); if(list_sell==NULL) return; //--- 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); //--- Close the Buy position by the opposite Sell one if(position_buy!=NULL && position_sell!=NULL) engine.ClosePositionBy((ulong)position_buy.Ticket(),(ulong)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 and for the current symbol only list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); 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) { //--- Get the Sell position object and close a position by ticket COrder* position=list.At(index); if(position!=NULL) engine.ClosePosition((ulong)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 and for the current symbol only list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); 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); //--- Close the Sell position partially if(position!=NULL) engine.ClosePositionPartially((ulong)position.Ticket(),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)) { //--- In case of a hedging account if(engine.IsHedge()) { CArrayObj *list_buy=NULL, *list_sell=NULL; //--- Get the list of all open positions CArrayObj* list=engine.GetListMarketPosition(); if(list==NULL) return; //--- Select only current symbol positions from the list list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); //--- Select only Sell positions from the list list_sell=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL); if(list_sell==NULL) return; //--- 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); //--- Select only Buy positions from the list list_buy=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL); if(list_buy==NULL) return; //--- 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); //--- Close the Sell position by the opposite Buy one if(position_sell!=NULL && position_buy!=NULL) engine.ClosePositionBy((ulong)position_sell.Ticket(),(ulong)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(); //--- Select only current symbol positions from the list list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); 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 engine.ClosePosition((ulong)position.Ticket()); } } } //--- If the BUTT_DELETE_PENDING button is pressed: Remove pending orders starting from the oldest one else if(button==EnumToString(BUTT_DELETE_PENDING)) { //--- Get the list of all orders CArrayObj* list=engine.GetListMarketPendings(); //--- Select only current symbol orders from the list list=CSelect::ByOrderProperty(list,ORDER_PROP_SYMBOL,Symbol(),EQUAL); if(list!=NULL) { //--- Sort the list by placement time list.Sort(SORT_BY_ORDER_TIME_OPEN); int total=list.Total(); //--- In a loop from an order with the longest time for(int i=total-1;i>=0;i--) { COrder* order=list.At(i); if(order==NULL) continue; //--- delete the order by its ticket engine.DeleteOrder((ulong)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 neither a trailing button, nor the buttons enabling pending requests) if(button!=EnumToString(BUTT_TRAILING_ALL) && StringFind(button,"_PRICE")<0 && StringFind(button,"_TIME")<0) ButtonState(button_name,false); //--- If the BUTT_TRAILING_ALL button or the buttons enabling pending requests are pressed else { //--- Set the active button color for the button enabling trailing if(button==EnumToString(BUTT_TRAILING_ALL)) { ButtonState(button_name,true); trailing_on=true; } //--- Buying //--- Set the active button color for the button enabling pending requests for opening Buy by price or time if(button==EnumToString(BUTT_BUY)+"_PRICE" || button==EnumToString(BUTT_BUY)+"_TIME") { ButtonState(button_name,true); pending_buy=true; } //--- Set the active button color for the button enabling pending requests for placing BuyLimit by price or time if(button==EnumToString(BUTT_BUY_LIMIT)+"_PRICE" || button==EnumToString(BUTT_BUY_LIMIT)+"_TIME") { ButtonState(button_name,true); pending_buy_limit=true; } //--- Set the active button color for the button enabling pending requests for placing BuyStop by price or time if(button==EnumToString(BUTT_BUY_STOP)+"_PRICE" || button==EnumToString(BUTT_BUY_STOP)+"_TIME") { ButtonState(button_name,true); pending_buy_stop=true; } //--- Set the active button color for the button enabling pending requests for placing BuyStopLimit by price or time if(button==EnumToString(BUTT_BUY_STOP_LIMIT)+"_PRICE" || button==EnumToString(BUTT_BUY_STOP_LIMIT)+"_TIME") { ButtonState(button_name,true); pending_buy_stoplimit=true; } //--- Set the active button color for the button enabling pending requests for closing Buy by price or time if(button==EnumToString(BUTT_CLOSE_BUY)+"_PRICE" || button==EnumToString(BUTT_CLOSE_BUY)+"_TIME") { ButtonState(button_name,true); pending_close_buy=true; } //--- Set the active button color for the button enabling pending requests for closing 1/2 Buy by price or time if(button==EnumToString(BUTT_CLOSE_BUY2)+"_PRICE" || button==EnumToString(BUTT_CLOSE_BUY2)+"_TIME") { ButtonState(button_name,true); pending_close_buy2=true; } //--- Set the active button color for the button enabling pending requests for closing Buy by an opposite Sell by price or time if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL)+"_PRICE" || button==EnumToString(BUTT_CLOSE_BUY_BY_SELL)+"_TIME") { ButtonState(button_name,true); pending_close_buy_by_sell=true; } //--- Selling //--- Set the active button color for the button enabling pending requests for opening Sell by price or time if(button==EnumToString(BUTT_SELL)+"_PRICE" || button==EnumToString(BUTT_SELL)+"_TIME") { ButtonState(button_name,true); pending_sell=true; } //--- Set the active button color for the button enabling pending requests for placing SellLimit by price or time if(button==EnumToString(BUTT_SELL_LIMIT)+"_PRICE" || button==EnumToString(BUTT_SELL_LIMIT)+"_TIME") { ButtonState(button_name,true); pending_sell_limit=true; } //--- Set the active button color for the button enabling pending requests for placing SellStop by price or time if(button==EnumToString(BUTT_SELL_STOP)+"_PRICE" || button==EnumToString(BUTT_SELL_STOP)+"_TIME") { ButtonState(button_name,true); pending_sell_stop=true; } //--- Set the active button color for the button enabling pending requests for placing SellStopLimit by price or time if(button==EnumToString(BUTT_SELL_STOP_LIMIT)+"_PRICE" || button==EnumToString(BUTT_SELL_STOP_LIMIT)+"_TIME") { ButtonState(button_name,true); pending_sell_stoplimit=true; } //--- Set the active button color for the button enabling pending requests for closing Sell by price or time if(button==EnumToString(BUTT_CLOSE_SELL)+"_PRICE" || button==EnumToString(BUTT_CLOSE_SELL)+"_TIME") { ButtonState(button_name,true); pending_close_sell=true; } //--- Set the active button color for the button enabling pending requests for closing 1/2 Sell by price or time if(button==EnumToString(BUTT_CLOSE_SELL2)+"_PRICE" || button==EnumToString(BUTT_CLOSE_SELL2)+"_TIME") { ButtonState(button_name,true); pending_close_sell2=true; } //--- Set the active button color for the button enabling pending requests for closing Sell by an opposite Buy by price or time if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY)+"_PRICE" || button==EnumToString(BUTT_CLOSE_SELL_BY_BUY)+"_TIME") { ButtonState(button_name,true); pending_close_sell_by_buy=true; } } //--- re-draw the chart ChartRedraw(); } //--- Return a color for the inactive buttons else { //--- trailing button if(button==EnumToString(BUTT_TRAILING_ALL)) { ButtonState(button_name,false); trailing_on=false; } //--- Buying //--- the button enabling pending requests for opening Buy by price if(button==EnumToString(BUTT_BUY)+"_PRICE") { ButtonState(button_name,false); pending_buy=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY)+"_TIME")); } //--- the button enabling pending requests for opening Buy by time if(button==EnumToString(BUTT_BUY)+"_TIME") { ButtonState(button_name,false); pending_buy=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY)+"_PRICE")); } //--- the button enabling pending requests for placing BuyLimit by price if(button==EnumToString(BUTT_BUY_LIMIT)+"_PRICE") { ButtonState(button_name,false); pending_buy_limit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY_LIMIT)+"_TIME")); } //--- the button enabling pending requests for placing BuyLimit by time if(button==EnumToString(BUTT_BUY_LIMIT)+"_TIME") { ButtonState(button_name,false); pending_buy_limit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY_LIMIT)+"_PRICE")); } //--- the button enabling pending requests for placing BuyStop by price if(button==EnumToString(BUTT_BUY_STOP)+"_PRICE") { ButtonState(button_name,false); pending_buy_stop=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY_STOP)+"_TIME")); } //--- the button enabling pending requests for placing BuyStop by time if(button==EnumToString(BUTT_BUY_STOP)+"_TIME") { ButtonState(button_name,false); pending_buy_stop=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY_STOP)+"_PRICE")); } //--- the button enabling pending requests for placing BuyStopLimit by price if(button==EnumToString(BUTT_BUY_STOP_LIMIT)+"_PRICE") { ButtonState(button_name,false); pending_buy_stoplimit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY_STOP_LIMIT)+"_TIME")); } //--- the button enabling pending requests for placing BuyStopLimit by time if(button==EnumToString(BUTT_BUY_STOP_LIMIT)+"_TIME") { ButtonState(button_name,false); pending_buy_stoplimit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_BUY_STOP_LIMIT)+"_PRICE")); } //--- the button enabling pending requests for closing Buy by price if(button==EnumToString(BUTT_CLOSE_BUY)+"_PRICE") { ButtonState(button_name,false); pending_close_buy=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_BUY)+"_TIME")); } //--- the button enabling pending requests for closing Buy by time if(button==EnumToString(BUTT_CLOSE_BUY)+"_TIME") { ButtonState(button_name,false); pending_close_buy=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_BUY)+"_PRICE")); } //--- the button enabling pending requests for closing 1/2 Buy by price if(button==EnumToString(BUTT_CLOSE_BUY2)+"_PRICE") { ButtonState(button_name,false); pending_close_buy2=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_BUY2)+"_TIME")); } //--- the button enabling pending requests for closing 1/2 Buy by time if(button==EnumToString(BUTT_CLOSE_BUY2)+"_TIME") { ButtonState(button_name,false); pending_close_buy2=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_BUY2)+"_PRICE")); } //--- the button enabling pending requests for closing Buy by an opposite Sell by price if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL)+"_PRICE") { ButtonState(button_name,false); pending_close_buy_by_sell=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_BUY_BY_SELL)+"_TIME")); } //--- the button enabling pending requests for closing Buy by an opposite Sell by time if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL)+"_TIME") { ButtonState(button_name,false); pending_close_buy_by_sell=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_BUY_BY_SELL)+"_PRICE")); } //--- Selling //--- the button enabling pending requests for opening Sell by price if(button==EnumToString(BUTT_SELL)+"_PRICE") { ButtonState(button_name,false); pending_sell=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL)+"_TIME")); } //--- the button enabling pending requests for opening Sell by time if(button==EnumToString(BUTT_SELL)+"_TIME") { ButtonState(button_name,false); pending_sell=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL)+"_PRICE")); } //--- the button enabling pending requests for placing SellLimit by price if(button==EnumToString(BUTT_SELL_LIMIT)+"_PRICE") { ButtonState(button_name,false); pending_sell_limit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL_LIMIT)+"_TIME")); } //--- the button enabling pending requests for placing SellLimit by time if(button==EnumToString(BUTT_SELL_LIMIT)+"_TIME") { ButtonState(button_name,false); pending_sell_limit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL_LIMIT)+"_PRICE")); } //--- the button enabling pending requests for placing SellStop by price if(button==EnumToString(BUTT_SELL_STOP)+"_PRICE") { ButtonState(button_name,false); pending_sell_stop=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL_STOP)+"_TIME")); } //--- the button enabling pending requests for placing SellStop by time if(button==EnumToString(BUTT_SELL_STOP)+"_TIME") { ButtonState(button_name,false); pending_sell_stop=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL_STOP)+"_PRICE")); } //--- the button enabling pending requests for placing SellStopLimit by price if(button==EnumToString(BUTT_SELL_STOP_LIMIT)+"_PRICE") { ButtonState(button_name,false); pending_sell_stoplimit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL_STOP_LIMIT)+"_TIME")); } //--- the button enabling pending requests for placing SellStopLimit by time if(button==EnumToString(BUTT_SELL_STOP_LIMIT)+"_TIME") { ButtonState(button_name,false); pending_sell_stoplimit=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_SELL_STOP_LIMIT)+"_PRICE")); } //--- the button enabling pending requests for closing Sell by price if(button==EnumToString(BUTT_CLOSE_SELL)+"_PRICE") { ButtonState(button_name,false); pending_close_sell=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_SELL)+"_TIME")); } //--- the button enabling pending requests for closing Sell by time if(button==EnumToString(BUTT_CLOSE_SELL)+"_TIME") { ButtonState(button_name,false); pending_close_sell=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_SELL)+"_PRICE")); } //--- the button enabling pending requests for closing 1/2 Sell by price if(button==EnumToString(BUTT_CLOSE_SELL2)+"_PRICE") { ButtonState(button_name,false); pending_close_sell2=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_SELL2)+"_TIME")); } //--- the button enabling pending requests for closing 1/2 Sell by time if(button==EnumToString(BUTT_CLOSE_SELL2)+"_TIME") { ButtonState(button_name,false); pending_close_sell2=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_SELL2)+"_PRICE")); } //--- the button enabling pending requests for closing Sell by an opposite Buy by price if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY)+"_PRICE") { ButtonState(button_name,false); pending_close_sell_by_buy=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_SELL_BY_BUY)+"_TIME")); } //--- the button enabling pending requests for closing Sell by an opposite Buy by time if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY)+"_TIME") { ButtonState(button_name,false); pending_close_sell_by_buy=(ButtonState(button_name) | ButtonState(prefix+EnumToString(BUTT_CLOSE_SELL_BY_BUY)+"_PRICE")); } //--- re-draw the chart ChartRedraw(); } } //+------------------------------------------------------------------+
该函数十分冗长,但代码注释很详细,无需解释。 如果您有任何疑问,请随时在下面的评论中提问。
我们来编译 EA。 默认情况下,延后请求价格的变动等于 50 点,而延迟的柱线数量等于 5 根柱线。 保留这些设置不变,然后在策略测试器中启动 EA。
启用延后请求激活按钮,按价格和时间开立多头持仓。 然后等待激活延后请求。
之后,启用延后请求激活按钮,仅按时间开立空头持仓,并等待延后请求激活:
从日志记录中我们可以看到,生成了延后买入请求,并为其设置了激活条件。 当价格和时间触及指定条件时,两个延后请求均被激活,并由于它们已被激活,从而延后请求对象被删除。
然后,我们创建一个延后的卖出请求,该请求在五根柱线之后被激活。 开仓后,该请求因已执行而被删除。
在下一篇文章中,我们将继续开发延后交易请求概念,并按条件实现挂单。
文后附有当前版本函数库的所有文件,以及测试 EA 文件,供您测试和下载。
请在评论中留下您的问题、意见和建议。
返回目录
系列中的前几篇文章:
第一部分 概念,数据管理本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程