- Expert Advisors main event: OnTick
- Basic principles and concepts: order, deal, and position
- Types of trading operations
- Order types
- Order execution modes by price and volume
- Pending order expiration dates
- Margin calculation for a future order: OrderCalcMargin
- Estimating the profit of a trading operation: OrderCalcProfit
- MqlTradeRequest structure
- MqlTradeCheckResult structure
- Request validation: OrderCheck
- Request sending result: MqlTradeResult structure
- Sending a trade request: OrderSend and OrderSendAsync
- Buying and selling operations
- Modying Stop Loss and/or Take Profit levels of a position
- Trailing stop
- Closing a position: full and partial
- Closing opposite positions: fill and partial
- Placing a pending order
- Modifying a pending order
- Deleting a pending order
- Getting a list of active orders
- Order properties (active and historical)
- Functions for reading properties of active orders
- Selecting orders by properties
- Getting the list of positions
- Position properties
- Functions for reading position properties
- Deal properties
- Selecting orders and deals from history
- Functions for reading order properties from history
- Functions for reading deal properties from history
- Types of trading transactions
- OnTradeTransaction event
- Synchronous and asynchronous requests
- OnTrade event
- Monitoring trading environment changes
- Creating multi-symbol Expert Advisors
- Limitations and benefits of Expert Advisors
- Creating Expert Advisors in the MQL Wizard
Modifying a pending order
MetaTrader 5 allows you to modify certain properties of a pending order, including the activation price, protection levels, and expiration date. The main properties such as order type or volume cannot be changed. In such cases, you should delete the order and replace it with another one. The only case where the order type can be changed by the server itself is the activation of a stop limit order, which turns into the corresponding limit order.
Programmatic modification of orders is performed by the TRADE_ACTION_MODIFY operation: it is this constant that needs to be written in the field action of the structure MqlTradeRequest before sending to the server by the function OrderSend or OrderSendAsync. The ticket of the modified order is indicated in the field order. Taking into account action and order, the full list of required fields for this operation includes:
- action
- order
- price
- type_time (default value 0 corresponds to ORDER_TIME_GTC)
- expiration (default 0, not important for ORDER_TIME_GTC)
- type_filling (default 0 corresponds to ORDER_FILLING_FOK)
- stoplimit (only for orders of types ORDER_TYPE_BUY_STOP_LIMIT and ORDER_TYPE_SELL_STOP_LIMIT)
Optional fields:
- sl
- tp
If protective levels have already been set for the order, they should be specified so they can be saved. Zero values indicate deletion of Stop Loss and/or Take Profit.
In the MqlTradeRequestSync structure (MqlTradeSync.mqh), the implementation of order modification is placed in the modify method.
struct MqlTradeRequestSync: public MqlTradeRequest
|
The actual execution of the request is again done in the completed method, in the dedicated branch of the if operator.
bool completed()
|
For the MqlTradeResultSync structure to know the new values of the properties of the edited order and to be able to compare them with the result, we write them in free fields (they are not filled by the server in this type of request). Further in the modified method, the result structure is waiting for the modification to be applied.
struct MqlTradeResultSync: public MqlTradeResult
|
Here we see how the order properties are read using the OrderGetDouble function and compared with the specified values. All this happens according to the already familiar procedure, in a loop inside the wait function, within a certain timeout of msc (1000 milliseconds by default).
As an example, let's use the Expert Advisor PendingOrderModify.mq5, while inheriting some code fragments from PendingOrderSend.mq5. In particular, a set of input parameters and the PlaceOrder function to create a new order. It is used at the first launch if there is no order for the given combination of the symbol and Magic number, thus ensuring that the Expert Advisor has something to modify.
A new function was required to find a suitable order: GetMyOrder. It is very similar to the GetMyPosition function, which was used in the example with position tracking (TrailingStop.mq5) to find a suitable position. The purpose of the built-in MQL5 API functions used inside GetMyOrder should be generally clear from their names, and the technical description will be presented in separate sections.
ulong GetMyOrder(const string name, const ulong magic)
|
The input parameter Distance2SLTP is now missing. Instead, the new Expert Advisor will automatically calculate the daily range of prices and place protective levels at a distance of half of this range. At the beginning of each day, the range and the new levels in the sl and tp fields will be recalculated. Order modification requests will be generated based on the new values.
Those pending orders that trigger and turn into positions will be closed upon reaching Stop Loss or Take Profit. The terminal can inform the MQL program about the activation of pending orders and the closing of positions if you describe trading event handlers in it. This would allow, for example, to avoid the creation of a new order if there is an open position. However, the current strategy can also be used. So, we will deal with events later.
The main logic of the Expert Advisor is implemented in the OnTick handler.
void OnTick()
|
Two lines at the beginning of the function ensure that the algorithm runs once at the beginning of each day. To do this, we calculate the current date without time and compare it with the value of the lastDay variable which contains the last successful date. The success or error status of course becomes clear at the end of the function, so we'll come back to it later.
Next, the price range for the previous day is calculated.
const string symbol = StringLen(Symbol) == 0 ? _Symbol : Symbol;
|
Depending on whether there is an order or not in the GetMyOrder function, we will either create a new order via PlaceOrder or edit the existing one using ModifyOrder.
uint retcode = 0;
|
Both functions, PlaceOrder and ModifyOrder, work on the basis of the Expert Advisor's input parameters and the found price range. They return the status of the request, which will need to be analyzed in some way to decide which action to take:
- Update the lastDay variable if the request is successful (the order has been updated and the Expert Advisor sleeps until the beginning of the next day)
- Leave the old day in lastDay for some time to try again on the next ticks if there are temporary problems (for example, the trading session has not started yet)
- Stop the Expert Advisor if serious problems are detected (for example, the selected order type or trade direction is not allowed on the symbol)
...
|
In the section Closing a position: full and partial, we used a simplified analysis with the IS_TANGIBLE macro which gave an answer in the categories of "yes" and "no" to indicate whether there was an error or not. Obviously, this approach needs to be improved, and we will return to this issue soon. For now, we will focus on the main functionality of the Expert Advisor.
The source code of the PlaceOrder function remained virtually unchanged from the previous example. ModifyOrder is shown below.
Recall that we determined the location of orders based on the daily range, to which the table of coefficients was applied. The principle has not changed, however, since we now have two functions that work with orders, PlaceOrder and ModifyOrder, the Coefficients table is placed in a global context. We will not repeat it here and will go straight to the ModifyOrder function.
uint ModifyOrder(const ulong ticket, const double range,
|
Price levels are calculated depending on the order type and the passed range.
const ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
|
After calculating all the values, we create an object of the MqlTradeRequestSync structure and execute the request.
MqlTradeRequestSync request(symbol);
|
To analyze retcode which we have to execute in the calling block inside OnTick, a new mechanism was developed that supplemented the file TradeRetcode.mqh. All server return codes are divided into several "severity" groups, described by the elements of the TRADE_RETCODE_SEVERITY enumeration.
enum TRADE_RETCODE_SEVERITY
|
In a simplistic way, the first half corresponds to recoverable errors: it is usually enough to wait a while and retry the request. The second half requires you to change the content of the request, check the account or symbol settings, the permissions for the program, and in the worst case, stop trading. Those who wish can draw a conditional separator line not after SEVERITY_REJECT, as it is visually highlighted now, but before it.
The division of all codes into groups is performed by the TradeCodeSeverity function (given with abbreviations).
TRADE_RETCODE_SEVERITY TradeCodeSeverity(const uint retcode)
|
Thanks to this functionality, the OnTick handler can be supplemented with "smart" error handling. A static variable RetryFrequency stores the frequency with which the program will try to repeat the request in case of non-critical errors. The last time such an attempt was made is stored in the RetryRecordTime variable.
void OnTick()
|
Once the PlaceOrder or ModifyOrder function returns the value of retcode, we learn how severe it is and, based on the severity, we choose one of three alternatives: stopping the Expert Advisor, waiting for a timeout, or regular operation (marking the successful modification of the order by the current day in lastDay).
const TRADE_RETCODE_SEVERITY severity = TradeCodeSeverity(retcode);
|
In case of repeated problems that are classified as solvable, the RetryFrequency timeout gradually increases with each subsequent error but resets to 1 second when the request is successfully processed.
It should be noted that the methods of the applied structure MqlTradeRequestSync check a large number of combinations of parameters for correctness and, if problems are found, interrupt the process prior to the SendRequest call. This behavior is enabled by default, but it can be disabled by defining an empty RETURN(X) macro before the directive #include with MqlTradeSync.mqh.
#define RETURN(X)
|
With this macro definition, checks will only print warnings to the log but will continue to execute methods until the SendRequest call.
In any case, after calling one or another method of the MqlTradeResultSync structure, the error code will be added to retcode. This will be done either by the server or by the MqlTradeRequestSync structure's checking algorithms (here we utilize the fact that the MqlTradeResultSync instance is included inside MqlTradeRequestSync). I do not provide here the description of the return of error codes and the use of the RETURN macro in the MqlTradeRequestSync methods for the sake of brevity. Those interested can see the full source code in the MqlTradeSync.mqh file.
Let's run the Expert Advisor PendingOrderModify.mq5 in the tester, with the visual mode enabled, using the data of XAUUSD, H1 (all ticks or real ticks mode). With the default settings, the Expert Advisor will place orders of the ORDER_TYPE_BUY_STOP type with a minimum lot. Let's make sure from the log and trading history that the program places pending orders and modifies them at the beginning of each day.
2022.01.03 01:05:00 Autodetected daily range: 14.37 2022.01.03 01:05:00 buy stop 0.01 XAUUSD at 1845.73 sl: 1838.55 tp: 1852.91 (1830.63 / 1831.36) 2022.01.03 01:05:00 OK order placed: #=2 2022.01.03 01:05:00 TRADE_ACTION_PENDING, XAUUSD, ORDER_TYPE_BUY_STOP, V=0.01, ORDER_FILLING_FOK, » » @ 1845.73, SL=1838.55, TP=1852.91, ORDER_TIME_GTC, M=1234567890 2022.01.03 01:05:00 DONE, #=2, V=0.01, Bid=1830.63, Ask=1831.36, Request executed 2022.01.04 01:05:00 Autodetected daily range: 33.5 2022.01.04 01:05:00 order modified [#2 buy stop 0.01 XAUUSD at 1836.56] 2022.01.04 01:05:00 OK order modified: #=2 2022.01.04 01:05:00 TRADE_ACTION_MODIFY, XAUUSD, ORDER_TYPE_BUY_STOP, V=0.01, ORDER_FILLING_FOK, » » @ 1836.56, SL=1819.81, TP=1853.31, ORDER_TIME_GTC, #=2 2022.01.04 01:05:00 DONE, #=2, @ 1836.56, Bid=1819.81, Ask=1853.31, Request executed, Req=1 2022.01.05 01:05:00 Autodetected daily range: 18.23 2022.01.05 01:05:00 order modified [#2 buy stop 0.01 XAUUSD at 1832.56] 2022.01.05 01:05:00 OK order modified: #=2 2022.01.05 01:05:00 TRADE_ACTION_MODIFY, XAUUSD, ORDER_TYPE_BUY_STOP, V=0.01, ORDER_FILLING_FOK, » » @ 1832.56, SL=1823.45, TP=1841.67, ORDER_TIME_GTC, #=2 2022.01.05 01:05:00 DONE, #=2, @ 1832.56, Bid=1823.45, Ask=1841.67, Request executed, Req=2 ... 2022.01.11 01:05:00 Autodetected daily range: 11.96 2022.01.11 01:05:00 order modified [#2 buy stop 0.01 XAUUSD at 1812.91] 2022.01.11 01:05:00 OK order modified: #=2 2022.01.11 01:05:00 TRADE_ACTION_MODIFY, XAUUSD, ORDER_TYPE_BUY_STOP, V=0.01, ORDER_FILLING_FOK, » » @ 1812.91, SL=1806.93, TP=1818.89, ORDER_TIME_GTC, #=2 2022.01.11 01:05:00 DONE, #=2, @ 1812.91, Bid=1806.93, Ask=1818.89, Request executed, Req=6 2022.01.11 18:10:58 order [#2 buy stop 0.01 XAUUSD at 1812.91] triggered 2022.01.11 18:10:58 deal #2 buy 0.01 XAUUSD at 1812.91 done (based on order #2) 2022.01.11 18:10:58 deal performed [#2 buy 0.01 XAUUSD at 1812.91] 2022.01.11 18:10:58 order performed buy 0.01 at 1812.91 [#2 buy stop 0.01 XAUUSD at 1812.91] 2022.01.11 20:28:59 take profit triggered #2 buy 0.01 XAUUSD 1812.91 sl: 1806.93 tp: 1818.89 » » [#3 sell 0.01 XAUUSD at 1818.89] 2022.01.11 20:28:59 deal #3 sell 0.01 XAUUSD at 1818.91 done (based on order #3) 2022.01.11 20:28:59 deal performed [#3 sell 0.01 XAUUSD at 1818.91] 2022.01.11 20:28:59 order performed sell 0.01 at 1818.91 [#3 sell 0.01 XAUUSD at 1818.89] 2022.01.12 01:05:00 Autodetected daily range: 23.28 2022.01.12 01:05:00 buy stop 0.01 XAUUSD at 1843.77 sl: 1832.14 tp: 1855.40 (1820.14 / 1820.49) 2022.01.12 01:05:00 OK order placed: #=4 2022.01.12 01:05:00 TRADE_ACTION_PENDING, XAUUSD, ORDER_TYPE_BUY_STOP, V=0.01, ORDER_FILLING_FOK, » » @ 1843.77, SL=1832.14, TP=1855.40, ORDER_TIME_GTC, M=1234567890 2022.01.12 01:05:00 DONE, #=4, V=0.01, Bid=1820.14, Ask=1820.49, Request executed, Req=7 |
The order can be triggered at any moment, after which the position is closed after some time by the stop loss or take profit (as in the code above).
In some cases, a situation may arise when the position still exists at the beginning of the next day, and then a new order will be created in addition to it, as in the screenshot below.
The Expert Advisor with a trading strategy based on pending orders in the tester
Please note that due to the fact that we request quotes of the PERIOD_D1 timeframe to calculate the daily range, the visual tester opens the corresponding chart, in addition to the current working one. Such a service works not only for timeframes other than the working one but also for other symbols. This will be useful, in particular, when developing multicurrency Expert Advisors.
To check how error handling works, try disabling trading for the Expert Advisor. The log will contain the following:
Autodetected daily range: 34.48
|
This error is critical, and the Expert Advisor stops working.
To demonstrate one of the easier errors, we could use the OnTimer handler instead of OnTick. Then launching the same Expert Advisor on symbols where trading sessions take only a part of a day would periodically generate a sequence of non-critical errors about a closed market ("Market closed"). In this case, the Expert Advisor would keep trying to start trading, constantly increasing the waiting time.
This, in particular, is easy to check in the tester, which allows you to set up arbitrary trading sessions for any symbol. On the Settings tab, to the right of the Delays dropdown list, there is a button that opens the Trade setup dialog. There, you should include the option Use your settings and on the Trade tab add at least one record to the table Non-trading periods.
Setting up non-trading periods in the tester
Please note that it is non-trading periods that are set here, not trading sessions, i.e., this setting acts exactly the opposite in comparison with the symbol specification.
Many potential errors related to trade restrictions can be eliminated by preliminary analysis of the environment using a class like Permissions presented in the section Restrictions and permissions for account transactions.