Library for easy and quick development of MetaTrader programs (part XXXI): Pending trading requests - opening positions under certain conditions
Contents
Concept
While developing the library functionality, I have introduced the concept of trading using pending requests. The concept features two operation options — handling trade server errors and usual sending of trading orders under programmatically set conditions. Starting with the article 26, I have been gradually implementing handling trade server errors using pending requests allowing to process resending trading orders to the server in case fixing of an error requires resending the order to the server after correcting error parameters and waiting.
Starting with this article, we are going to develop a functionality allowing to trade using pending requests under certain conditions.
This
library functionality allows users to programmatically create conditions, under which a trading order is sent to the server.
For
example:
- Open Buy upon the occurrence or exceeding a certain time provided that the price has dropped below a specified value (two conditions
related to symbol property values).
- Close a position partially if a specified profit is exceeded (one condition related to an account property value).
- If a position is closed by stop loss, open an opposite position (one condition related to an account event property).
The examples are simple but there may be plenty of conditions and their combinations. At this stage, we are going to develop control over
changes in properties of accounts, symbols and events occurring on the current account. The conditions from these three lists can be set in
any combination.
We will start from a simple thing — controlling changes of symbol and account property values. This will be followed by
controlling account events and reacting to them.
To let a pending request object work as part of a trading logic (sending trading orders under conditions), we need to implement additional data into this object to store pending request activation conditions and methods of their control and handling. The data storage is to be made in the form of a two-dimensional array. The first dimension is to store a condition number (there may be as many conditions as necessary), while the second one is to contain all data of the condition whose number is specified in the first dimension — condition source type (symbol, account or event), condition itself (create enumerations for each of the sources), comparison method (>,<,==,!=,>=,<=), reference value of a tracked property and its current value.
Conditions set in pending request objects are to be controlled in the timer of the class managing pending requests. Activated pending requests are sent to the server from the same class immediately after meeting all the conditions set in a pending request object.
In the current article, we are going to create and check trading using pending requests — opening positions under certain conditions. We are
going to track only two conditions in the test EA — price and time. Conditions can be set either separately (by price or by time), or jointly (by
price and time).
Preparing data
As usual, we start by adding indices of new library messages and appropriate texts.
Write all the necessary message indices in the Datas.mqh file:
//--- 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 }; //+------------------------------------------------------------------+
and add message texts corresponding to newly added indices:
//--- 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"}, }; //+---------------------------------------------------------------------+
Since a single pending request object handles controlled conditions from completely different sources (in this case, these are account, symbol and account events, then we can add something else), we need a data source whose parameters we track in order to track the activation of a specified pending request activation condition. When tracking account and symbol parameters, they may have matching property indices while the properties themselves are completely different. To avoid confusion, we will specify the data source, in which its property values are tracked.
In the Defines.mqh file, write the enumeration of pending request activation sources:
//+------------------------------------------------------------------+ //| 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 {
Also, add enumerations of possible criteria used to activate pending requests.
For the criteria of activating
by account, symbol
and event properties, use separate enumerations:
//+------------------------------------------------------------------+ //| 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) }; //+------------------------------------------------------------------+
The values of enumeration constants are equal to the constant values of text messages of the appropriate symbol, account and event
properties. This relieves us from the necessity to additionally identify a described constant as belonging to a symbol, account or event
when displaying messages in the journal. Instead, we are going to simply use the index of a constant itself to display a message.
Using three different enumerations of activation conditions, we can finally set any combination of constants out of three enumerations for compiling a required pending request activation criteria.
Add the function returning the description of a comparison type to the DELib.mqh service function file:
//+------------------------------------------------------------------+ //| 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 " != "; } } //+------------------------------------------------------------------+
Names of enumeration constants featuring "STOP_LOSS" and "TAKE_PROFIT" strings have been changed in many library files. The occurrences of
these strings have been replaced with "SL" and "TP", respectively.
Pending request object created on demand
The basic object of the abstract pending request is now
inherited from the base object of all library objects.
Include
the base object file of all library objects to the CPendRequest class file and make
the class inherit the base object:
//+------------------------------------------------------------------+ //| 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 {
In the private section of the class, declare the array for storing data using tracked pending request activation criterion:
//+------------------------------------------------------------------+ //| 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);
In the same private section, add the methods returning a magic number
and ID set in the EA settings, as well as IDs
of the first and second groups.
Also, declare the method for
returning the flag of the successful check of a controlled property with its actual value, the
method of comparing two controlled property values and the method
returning the number of decimal places for the tracked property for correct display of values in the journal:
//--- 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:
The methods of returning magic number and
group IDs use same-name methods of the CBaseObj parent object we have inherited the base abstract pending request object from.
In the block of methods for a simplified access to request object properties of the public class section, add declarations of all the necessary public methods we are going to consider further:
//+------------------------------------------------------------------+ //| 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;
The SetTypeRequest() method sets the "pending request type" property passed to the type method. "A pending request based on an
error code" or "a pending request created by request" can be used as a type. A pending request type is set in an object automatically within the
class constructor depending on the "error code" parameter. If the code is equal to zero, this is a pending request created by a program
request. Thus, the method is not used anywhere now. It is created in case you suddenly need to quickly change a pending request type from the
outside (personally, I have not been able to come up with a need for this yet).
Add declarations of the methods returning descriptions of controlled
properties to the appropriate block of methods:
//+------------------------------------------------------------------+ //| 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;} }; //+------------------------------------------------------------------+
The GetActivationCriterionTotal() method returns the size of the first dimension of the activation conditions data array, in
other words, the number of activation conditions in the pending request object.
In the class constructor, set the zero size for the activation conditions data array in the first dimension:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
The size of the activation conditions data array changes automatically when adding each successive activation condition.
In the method displaying the full list of pending request object data, add display of the activation condition list after displaying all its properties:
//+------------------------------------------------------------------+ //| 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"); } //+------------------------------------------------------------------+
Implementing the method displaying pending request activation conditions to the journal:
//+------------------------------------------------------------------+ //| 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(""); } //+------------------------------------------------------------------+
The method for creating a new condition for activating a pending request in the activation conditions data array:
//+------------------------------------------------------------------+ //| 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; } //+---------------------------------------------------------------------+
Activation data source, activation
condition, controlled and actual
activation condition values, as well as comparison method are
passed to the method.
The
size of the activation conditions data array is increased by 1 and all the
necessary data in the array are filled with values passed in the method inputs. The method should be used only to add a new activation
condition.
The following methods are used to correct activation conditions already existing in the request object:
//+---------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
The method of setting the SetActivationProperty() controlled property receives the index and two condition parameters —
condition source (symbol, account or event) and the activation condition itself (from the appropriate enumerations considered above)
since the activation condition consists of the two parameters — property change source and type. Other methods of setting activation
values receive only index and value.
Activation condition number is used as an index. If there is only one condition, the index
should be equal to zero. In case of two conditions, the index should be equal to 0 or 1 depending on what condition we want to change, etc. When
passing the index going beyond the array first dimension, the invalid index entry appears in the journal and false
is returned.
The methods returning activation conditions parameters:
//+------------------------------------------------------------------+ //| 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]; } //+------------------------------------------------------------------+
Here all is similar to setting the activation properties except that all condition properties are returned one at a time therefore passing a
requested activation condition index in each method is sufficient. In case an invalid index is passed, the invalid index entry appears in
the journal. The value of -1 is returned for the methods returning integer values, while EMPTY_VALUE
is returned for methods returning real values.
The method of comparing two values by a specified comparison type:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
The method receives the current value of a compared property, the
reference value the current value is compared with and the
comparison type.
Depending on the comparison type,
the current property values are compared with its reference value returning the comparison result.
The method returning the flag of successful comparison of an activation condition by its index in the activation conditions data array:
//+----------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
The method returns the activation flag of one of the pending request activation conditions. The method input passes the index of a checked
condition in the activation conditions data array. The comparison is performed using the IsCompared() method considered above,
and the comparison result is returned.
The method returning the flag of successful check of all activation conditions created for a request object:
//+------------------------------------------------------------------+ //| 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; } //+-------------------------------------------------------------------+
This is a universal method allowing to check any pending request object for its activation time.
Here, in
the array's first dimension loop of the activation conditions data, the IsComparisonCompleted() method is used
to add the successful check flag to the check result (res variable).
The check defines whether the controlled property loop matches the index. The result of checking all conditions is returned upon the loop
completion. If at least one of the conditions is not met or the data array is of zero size in the first dimension, the result is false.
The method returning the number of decimal places for the correct display of an activation condition description in the journal:
//+-------------------------------------------------------------------+ //|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; } //+------------------------------------------------------------------+
The method checks the activation source and the activation condition depending on the source. Depending on the activation condition, the
system returns either a number of decimal places in a symbol quote value, or a number of decimal places in the current account currency, or a
number of decimal places in the symbol lot value, or zero.
The method returning a text description of a controlled property:
//+------------------------------------------------------------------+ //| 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 : "" ); } //+------------------------------------------------------------------+
The method receives the condition index in the activation conditions data array. Get the activation source by the index and obtain the
remaining text messages depending on it. These messages are to be used to arrange and return the final text.
The method returning the comparison type description:
//+------------------------------------------------------------------+ //| Return the comparison type description | //+------------------------------------------------------------------+ string CPendRequest::GetActivationComparerTypeDescription(const uint index) const { return ComparisonTypeDescription((ENUM_COMPARER_TYPE)this.m_activated_control[index][2]); } //+------------------------------------------------------------------+
The method simply returns the text description of a comparison type set in the data array by the activation condition index passed by the
parameter to the method.
The method returning a controlled property value description in a request object:
//+------------------------------------------------------------------+ //| 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)) ) : "" ); } //+------------------------------------------------------------------+
The method receives the condition index.
A controlled property
value set to the array by a specified index is checked. If it is equal to a specified index and not equal to an "empty value" (EMPTY_VALUE),
condition and
its type are checked. If a symbol time is checked as a result,
the time text description is returned, otherwise,
the text description of an integer or real value is
returned with the correct number of decimal places.
The method returning an actual controlled property value description in a request object:
//+------------------------------------------------------------------+ //|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)) ) : "" ); } //+------------------------------------------------------------------+
The method is identical to the previous one, except that the data is obtained by index 4 in the second dimension of the activation conditions data array. These are all the changes of the abstract pending request's base object.
Now let's make some improvements in the classes of the descendant objects of the abstract request base object.
Since we have implemented two types of pending requests — by error code and by request, the second type of objects does not imply the presence of some properties — such as the server return code (it is always equal to zero here), request activation time (the time in requests of the second type can be specified as one of the request activation conditions and is located in the activation conditions data array of a pending trading request), waiting time (not used here at all) and the current attempt index (one attempt is given here, a standard trading order is sent afterwards and handled by the trade server return code).
In this regard, let's supplement all descendant objects of the pending request base object, namely their methods returning support for integer properties by the object and add calling the method displaying the list of pending request activation conditions in the journal to the PrintShort() method of each of the descendant objects.
Add the following changes to the PendReqOpen.mqh, PendReqClose.mqh, PendReqSLTP.mqh, PendReqPlace.mqh, PendReqRemove.mqh and PendReqModify.mqh files of the abstract pending request base object (the CPendReqOpen class is used as an example)://+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
The system makes sure this is the object created by request. If yes, the properties mentioned above are excluded.
//+------------------------------------------------------------------+ //| 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(); } //+------------------------------------------------------------------+
After the "+end" string, we removed adding the text wrapping code (+"\n"), as well as added calling the method displaying the list of activation
conditions after the ::Print(message); string. If the condition array is of zero size (in the objects created by the error code), the
PrintActivations() prints nothing except for the text wrapping code ("\n"). Otherwise, the method displays the full list of all
conditions written in the data array.
Some classes have undergone minor changes related to the journal display. There is no point in
dwelling on them here. You can find them in the attachments.
Now let's have a look at the trading classes.
In the CTrading base trading class, move
the three class member variables and the GetFreeID() method from the private section to the protected one:
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
These variables and methods are necessary in the descendant class. Therefore, they
should be located in the protected section so that the descendant class is able to access them (they are not needed in the public
section — the outside access to them is disabled). In the protected section of the
class, add the method returning the flag of a market order/position with a pending request ID.
As a result, the protected class
section looks as follows:
//+------------------------------------------------------------------+ //| 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:
The variables and methods moved from the private section
and the new method definition are highlighted in color here.
In the public section of the class, add the declaration of the method
returning the pointer to the request object by its ID in the list and the
declaration of the method returning the level of logging a symbol trading object:
//--- 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); }; //+------------------------------------------------------------------+
Let's implement these methods outside the class body.
Implementing the method returning the logging level of a symbol trading object:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
The method receives a name of a symbol whose trading object logging level should be received. Get
a trading object from a symbol object. If the object has been received,
return the object's logging level, otherwise return
the logging disabled status.
Implementing the method returning the pointer to the request object by its ID in the list:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
The method receives the request ID, then obtains the pending request object
index in the list by its ID. If there is no object in the list, return NULL.
Otherwise, return
the object from the list by its obtained index in the list.
Implementing the method returning the flag of a market order/position with a pending request 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); } //+------------------------------------------------------------------+
The method receives a pending request ID. Next, receive the list of market
orders/positions sorted by a pending request ID and its value. If the list is not received or is empty (no orders/positions with the
desired ID), return false, otherwise return true.
Let's add yet another check to the method returning the unoccupied 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; } //+------------------------------------------------------------------+
Why do we need yet another check for an order/position having the
appropriate ID? If we have an activated pending request and a position is opened based on it, the request is removed from the lists of
pending requests and its ID becomes available for use when creating new pending requests.
When creating a new pending request, its ID
will be equal to the one used by a currently opened position. When the new pending request activation conditions are met, the presence of a
position with the same ID is checked (it should be present as it has been opened using the previous ID) and the new pending request is simply
removed. Since the position with the same ID exists, the request is considered executed. In other words, the request is removed instead of
sending a trading order to the server.
There are a couple of solutions for avoiding such situations: introduce an additional identification defining whether this is another
request with the same ID the open position has or simply check the presence of a position with the same ID if there is no pending request with the
same ID in the list.
The second option seems less resource-intensive to me, although it has a limitation since it is impossible to use an
unoccupied ID till a position with the same ID is closed. In other words, we have a strict limitation of 255 positions with different pending
request IDs.
This concludes the improvements of the main trading class.
Now let's finalize the CTradingControl trading management class which is a descendant of the CTrading main trading class.
While developing the pending requests management class in the
previous article, we introduced handling pending request objects in the class timer.
Since we handled a single type
of pending requests created using the server return code, it is sufficient to place the entire handling code in the class timer.
Today we will add handling the second type of pending requests created by a program request.
This means we need to
make two handlers — the first one is for requests created by an error code, while the second one is for the ones created by request.
Therefore, we will introduce two pending requests object handlers divided by the type of handled requests, while the identical code for
both handlers is made in a separate method. In this case, we only need to check a request type in the timer and call the appropriate handler to
handle both types of pending requests.
Let's make all the necessary additions in the class body and analyze them:
//+------------------------------------------------------------------+ //| 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); }; //+------------------------------------------------------------------+
Since now we need to set data on controlled parameters to a pending request object (while we have previously also added relevant data on the order
the request is based on), rename the SetActualProperties()
method for setting relevant order data to a request object to SetOrderActualProperties() to avoid confusion.
In this article, we are dealing only with opening positions using pending requests, therefore the method of creating a pending request
remains out of the scope of the current article.
Let's consider the method of creating a pending request for opening a position:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
The method is a truncated version of the method for opening a position from
the article 26 (and subsequent ones) with the creation of a pending request in case of a trade server error. It is commented in detail so
there is no point in dwelling on it here.
The method receives all the necessary data for opening a position. The trading request
structure fields are filled in and sent to the pending request creation method.
If a pending request is created successfully, the ID of a
newly created pending request is returned, otherwise -1 is returned.
The calculated maximum possible waiting time is used here as
a difference between the maximum possible time in the terminal and the current time as a delay between repeated attempts. Thus, the
maximum possible lifetime is used for a pending request (up to 31.12.3000).
The method setting pending request activation criteria:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
The method receives an ID of a pending request a new activation condition should be added to, a request activation source (symbol, account or
event), activation condition, reference value, comparison type and actual value of a property controlled for activating the request.
Next, receive a pending request object by the ID passed to the method
and create a new activation condition for it with the parameters
passed to the method.
The method of checking the pending request relevance:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
The method checks the execution of a pending request and removes it after the execution is confirmed. We have already considered this code within
the code of the trading management class timer. Since we have divided handling pending request objects into two handlers by pending
request types and that code is similar for both handlers, we have put it into a separate method. It is to be called in each handler.
The handler of pending requests created by error code:
//+------------------------------------------------------------------+ //| 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; } } //+------------------------------------------------------------------+
We have also considered this code within the trading management class timer.
Its only difference is that handling a request activation check is moved to calling an
appropriate method.
The handler of pending requests created by request:
//+------------------------------------------------------------------+ //| 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; } } } //+------------------------------------------------------------------+
This method is slightly simpler than the previous one, since it only checks the activation and the moment of sending a trading order (meeting
pending request activation conditions).
The method of checking the request
activation is also called in it. Also, the system tracks whether pending request activation conditions set in its parameters are
triggered. If all conditions are triggered, a trading order is sent
to the server.
The method updating relevant values of controlled properties in pending request objects:
//+------------------------------------------------------------------+ //| 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; } } } //+------------------------------------------------------------------+
The method receives the array size of the activation conditions data. Besides, we move along all the conditions in the loop. Depending on the activation conditions source, we obtain the actual (current) data from the appropriate collections and write them back to the activation conditions data array of the pending request object.
The method returning the relevant account data:
//+------------------------------------------------------------------+ //| 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; } } //+------------------------------------------------------------------+
Depending on an account type, return the value of the appropriate account object property according to the account condition types enumeration.
The method returning the relevant symbol data:
//+------------------------------------------------------------------+ //| 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; } } //+------------------------------------------------------------------+
Depending on an account type and according to the symbol condition types enumeration, return the value of the appropriate symbol object property, the pointer to which is passed to the method.
The method returning the relevant event data:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Depending on a condition type and the current presence of a new event on an account, obtain the last event on the account. According to the enumeration of event condition types, return the flag of equality of the last event to the value controlled in the pending request object (return the flag of an occurred controlled event).
The class timer has now become much more compact:
//+------------------------------------------------------------------+ //| 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); } } //+------------------------------------------------------------------+
Now, in the class timer, check a type of a request object obtained from the list of pending request objects. Depending on its type, call the corresponding handler of pending requests we examined above.
These are all the improvements of the trading management class at the moment.
You can find the full code in the
attachments.
Let's make additions to the public section of the CEngine library base object class.
To be able to receive a logging level of trading objects to retrieve library-based program messages from them, add
the method of receiving a trading object logging level by symbol:
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
The method returns the operation result of the GetTradeObjLogLevel() trading management class method.
Declare the methods of creating a pending request for opening Buy
and Sell positions, as well as the method of setting a new pending
request activation condition and write the method returning the pointer
to a pending request object by its 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
The GetPendRequestByID() method returning the pointer to a pending request object returns the result of the same-name trading
management class method operation.
Implementing the method of creating a pending request for opening a Buy position:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
The method calls the method of creating a pending request for opening a trading management class position. Pass the POSITION_TYPE_BUY
constant as an opened position type (together with other parameters of a future position passed to the method).
Implementing the method of creating a pending request for opening a Sell position:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
The method calls the method of creating a pending request for opening a trading management class position. Pass the POSITION_TYPE_SELL constant as an opened position type (together with other parameters of a future position passed to the method).
Implementing the method of setting a new activation condition in a pending request object:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
The method calls the method of adding a new activation condition to the pending request object of the trading management class. These are all
the library improvements for now.
Testing
To test applying pending requests for opening positions, use the EA from the previous article and save it in the new folder \MQL5\Experts\TestDoEasy\ Part31\ under the name TestDoEasyPart31.mq5.
To check operation of pending requests by conditions, we will introduce additional buttons in the trading panel of the test EA. The buttons are marked as P — price condition and T — time condition. Pending requests are created when clicking Buy or Sell provided that P or T (or both) are pressed. If both are pressed, the pending request has two activation conditions — by price and time.
Also, let's add two inputs to set the distance from the current price for specifying a controlled price and a number of bars of the current
timeframe for setting the request activation time.
Thus, if Buy and P buttons are pressed, the distance below the current price is set from it for the number of points specified in the settings. This value is set as a reference one for triggering a pending request — when the price is equal or below the calculated one, the pending request is activated.
If the T button is pressed, the time calculated as the current time + the time of a specified number of bars of the current timeframe is added to the current time. This time is set as a reference one for triggering the pending request — when the current time becomes equal to or exceeds the calculated one, the pending request is activated.If both P and T buttons are triggered, both conditions should be met at once for the pending request activation.
To open a Sell position, the controlled price is calculated as the current price + the specified number of points in the
settings. For activating the pending request, the current price should exceed the one present at the moment of creating the pending request
(pressing the Sell button).
Add the indent distance of the reference price of the request activation
from the current price at the moment of creating the request and the
number of delay bars for setting the pending request activation time:
//--- 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
Add the appropriate variables for storing the activation price indent and delays in bars to the block of EA global variables to set the pending request activation time, as well as the flags of pending request button states:
//--- 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; //+------------------------------------------------------------------+
In the EA's OnInit() handler, assign correct input values to variables
and reset states of pending request buttons:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
For the distance of the pending request activation price, the distance of less than five points is equal to five points, while the minimum
delay of at least one bar of the current timeframe is set for the delay in bars.
The buttons for enabling pending requests are simply made
inactive. Since this is a test EA, these buttons are needed for test reasons only. We do not need to track their status.
In the function of creating the panel buttons, add the variable storing the width of new buttons. Also, create the new buttons enabling pending requests in yet another loop:
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
In the function setting the states of buttons (active button color), add
setting the color of active buttons for trading using pending requests:
//+------------------------------------------------------------------+ //| 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'); } } //+------------------------------------------------------------------+
Add the codes handling pressing the buttons for working with pending requests to the function handling button pressing:
//+------------------------------------------------------------------+ //| 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(); } } //+------------------------------------------------------------------+
The function is quite large but the code is commented in detail and needs no explanation. If you have any questions, feel free to ask them in the comments below.
Let's compile the EA. By default, the shift of the pending request price is equal to 50 points while delay in bars is equal to five bars. Leave these
settings unchanged and launch the EA in the strategy tester.
Enable the pending request activation buttons for opening a Buy position
by price and time. Then wait for activation of pending requests.
After that, enable the pending request activation button for opening a
Sell position by time only and wait for the pending request activation:
As we can see from the journal entries, buy pending requests are generated and activation conditions are set for them. When the price and time
reach the specified conditions, both pending requests are activated and pending request objects are removed due to their activation.
Then we create a sell pending request which is activated after five bars. The request is removed as an executed one after a position is opened.
What's next?
In the next article, we will continue the development of the pending trading request concept and implement placing pending orders by
condition.
All files of the current version of the library are attached below together with the test EA files for you to test and download.
Leave
your questions, comments and suggestions in the comments.
Previous articles within the series:
Part 1. Concept, data managementPart 2. Collection of historical orders and deals
Part 3. Collection of market orders and positions, arranging the search
Part 4. Trading events. Concept
Part 5. Classes and collection of trading events. Sending events to the program
Part 6. Netting account events
Part 7. StopLimit order activation events, preparing the functionality for order and position modification events
Part 8. Order and position modification events
Part 9. Compatibility with MQL4 — Preparing data
Part 10. Compatibility with MQL4 - Events of opening a position and activating pending orders
Part 11. Compatibility with MQL4 - Position closure events
Part 12. Account object class and account object collection
Part 13. Account object events
Part 14. Symbol object
Part 15. Symbol object collection
Part 16. Symbol collection events
Part 17. Interactivity of library objects
Part 18. Interactivity of account and any other library objects
Part 19. Class of library messages
Part 20. Creating and storing program resources
Part 21. Trading classes - Base cross-platform trading object
Part 22. Trading classes - Base trading class, verification of limitations
Part 23. Trading classes - Base trading class, verification of valid parameters
Part 24. Trading classes - Base trading class, auto correction of invalid parameters
Part 25. Trading classes - Base trading class, handling errors returned by the trade server
Part 26. Working with pending trading requests - First implementation (opening positions)
Part 27. Working with pending trading requests - Placing pending orders
Part 28. Working with pending trading requests - Closure, removal and modification
Part 29. Working with pending trading requests - request object classes
Part 30. Pending trading requests - managing request objects
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/7521
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use