- 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
OnTradeTransaction event
Expert Advisors and indicators can receive notifications about trading events if their code contains a special processing function OnTradeTransaction.
void OnTradeTransaction(const MqlTradeTransaction &trans,
const MqlTradeRequest &request, const MqlTradeResult &result)
The first parameter is the MqlTradeTransaction structure described in the previous section. The second and third parameters are structures MqlTradeRequest and MqlTradeResult, which were presented earlier in the relevant sections.
The MqlTradeTransaction structure that describes the trade transaction is filled in differently depending on the type of transaction specified in the type field. For example, for transactions of the TRADE_TRANSACTION_REQUEST type, all other fields are not important, and to obtain additional information, it is necessary to analyze the second and third parameters of the function (request and result). Conversely, for all other types of transactions, the last two parameters of the function should be ignored.
In case of TRADE_TRANSACTION_REQUEST, the request_id field in the result variable contains an identifier (through serial number), under which the trade request is registered in the terminal. This number has nothing to do with order and deal tickets, as well as position identifiers. During each session with the terminal, the numbering starts from the beginning (1). The presence of a request identifier allows you to associate the performed action (calling OrderSend or OrderSendAsync functions) with the result of this action passed to OnTradeTransaction. We'll look at examples later.
For trading transactions related to active orders (TRADE_TRANSACTION_ORDER_ADD, TRADE_TRANSACTION_ORDER_UPDATE and TRADE_TRANSACTION_ORDER_DELETE) and order history (TRADE_TRANSACTION_HISTORY_ADD, TRADE_TRANSACTION_HISTORY_UPDATE, TRADE_TRANSACTION_HISTORY_DELETE), the following fields are filled in the MqlTradeTransaction structure:
- order order ticket
- symbol name of the financial instrument in the order
- type trade transaction type
- order_type order type
- orders_state current order state
- time_type order expiration type
- time_expiration order expiration time (for orders with ORDER_TIME_SPECIFIED and ORDER_TIME_SPECIFIED_DAY expiration types)
- price order price specified by the client/program
- price_trigger stop price for triggering a stop-limit order (only for ORDER_TYPE_BUY_STOP_LIMIT and ORDER_TYPE_SELL_STOP_LIMIT)
- price_sl Stop Loss order price (filled if specified in the order)
- price_tp Take Profit order price (filled if specified in the order)
- volume current order volume (not executed), the initial order volume can be found from the order history
- position ticket of an open, modified, or closed position
- position_by opposite position ticket (only for orders to close with opposite position)
For trading transactions related to deals (TRADE_TRANSACTION_DEAL_ADD, TRADE_TRANSACTION_DEAL_UPDATE and TRADE_TRANSACTION_DEAL_DELETE), the following fields are filled in the MqlTradeTransaction structure:
- deal deal ticket
- order order ticket on the basis of which the deal was made
- symbol name of the financial instrument in the deal
- type trade transaction type
- deal_type deal type
- price deal price
- price_sl Stop Loss price (filled if specified in the order on the basis of which the deal was made)
- price_tp Take Profit price (filled if specified in the order on the basis of which the deal was made)
- volume deal volume
- position ticket of an open, modified, or closed position
- position_by opposite position ticket (for deals to close with opposite position)
For trading transactions related to position changes (TRADE_TRANSACTION_POSITION), the following fields are filled in the MqlTradeTransaction structure:
- symbol name of the financial instrument of the position
- type trade transaction type
- deal_type position type (DEAL_TYPE_BUY or DEAL_TYPE_SELL)
- price weighted average position opening price
- price_sl Stop Loss price
- price_tp Take Profit price
- volume position volume in lots
- position position ticket
Not all available information on orders, deals and positions (for example, a comment) is transmitted in the description of a trading transaction. For more information use the relevant functions: OrderGet, HistoryOrderGet, HistoryDealGet and PositionGet.
One trade request sent from the terminal manually or through the trading functions OrderSend/OrderSendAsync can generate several consecutive trade transactions on the trade server. At the same time, the order in which notifications about these transactions arrive at the terminal is not guaranteed, so you cannot build your trading algorithm on waiting for some trading transactions after others.
Trading events are processed asynchronously, that is, delayed (in time) relative to the moment of generation. Each trade event is sent to the queue of the MQL program, and the program sequentially picks them up in the order of the queue.
When an Expert Advisor is processing trade transactions inside the OnTradeTransaction processor, the terminal continues to accept incoming trade transactions. Thus, the state of the trading account may change while OnTradeTransaction is running. In the future, the program will be notified of all these events in the order the appear.
The length of the transaction queue is 1024 elements. If OnTradeTransaction processes the next transaction for too long, old transactions in the queue may be ousted by newer ones.
Due to parallel multi-threaded operation of the terminal with trading objects, by the time the OnTradeTransaction handler is called, all the entities mentioned in it, including orders, deals, and positions, may already be in a different state than that specified in the transaction properties. To get their current state, you should select them in the current environment or in the history and request their properties using the appropriate MQL5 functions.
Let's start with a simple Expert Advisor example TradeTransactions.mq5, which logs all OnTradeTransaction trading events. Its only parameter DetailedLog allows you to optionally use classes OrderMonitor, DealMonitor, PositionMonitor to display all properties. By default, the Expert Advisor displays only the contents of the filled fields of the MqlTradeTransaction, MqlTradeRequest and MqlTradeResult structures, coming to the handler in the form of parameters; at the same time request and result are processed only for TRADE_TRANSACTION_REQUEST transactions.
input bool DetailedLog = false; // DetailedLog ('true' shows order/deal/position details)
|
Let's run it on the EURUSD chart and perform several actions manually, and the corresponding entries will appear in the log (for the purity of the experiment, it is assumed that no one and nothing else performs operations on the trading account, in particular, no other Expert Advisors are running).
Let's open a long position with a minimum lot.
>>> 1 TRADE_TRANSACTION_ORDER_ADD, #=1296991463(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, » » @ 1.10947, V=0.01 >>> 2 TRADE_TRANSACTION_DEAL_ADD, D=1279627746(DEAL_TYPE_BUY), » » #=1296991463(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10947, V=0.01, P=1296991463 >>> 3 TRADE_TRANSACTION_ORDER_DELETE, #=1296991463(ORDER_TYPE_BUY/ORDER_STATE_FILLED), EURUSD, » » @ 1.10947, P=1296991463 >>> 4 TRADE_TRANSACTION_HISTORY_ADD, #=1296991463(ORDER_TYPE_BUY/ORDER_STATE_FILLED), EURUSD, » » @ 1.10947, P=1296991463 >>> 5 TRADE_TRANSACTION_REQUEST TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.10947, #=1296991463 DONE, D=1279627746, #=1296991463, V=0.01, @ 1.10947, Bid=1.10947, Ask=1.10947, Req=7 |
We will sell double the minimum lot.
>>> 6 TRADE_TRANSACTION_ORDER_ADD, #=1296992157(ORDER_TYPE_SELL/ORDER_STATE_STARTED), EURUSD, » » @ 1.10964, V=0.02 >>> 7 TRADE_TRANSACTION_DEAL_ADD, D=1279628463(DEAL_TYPE_SELL), » » #=1296992157(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10964, V=0.02, P=1296992157 >>> 8 TRADE_TRANSACTION_ORDER_DELETE, #=1296992157(ORDER_TYPE_SELL/ORDER_STATE_FILLED), EURUSD, » » @ 1.10964, P=1296992157 >>> 9 TRADE_TRANSACTION_HISTORY_ADD, #=1296992157(ORDER_TYPE_SELL/ORDER_STATE_FILLED), EURUSD, » » @ 1.10964, P=1296992157 >>> 10 TRADE_TRANSACTION_REQUEST TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_SELL, V=0.02, ORDER_FILLING_FOK, @ 1.10964, #=1296992157 DONE, D=1279628463, #=1296992157, V=0.02, @ 1.10964, Bid=1.10964, Ask=1.10964, Req=8 |
Let's perform the counter closing operation.
>>> 11 TRADE_TRANSACTION_ORDER_ADD, #=1296992548(ORDER_TYPE_CLOSE_BY/ORDER_STATE_STARTED), EURUSD, » » @ 1.10964, V=0.01, P=1296991463, b=1296992157 >>> 12 TRADE_TRANSACTION_DEAL_ADD, D=1279628878(DEAL_TYPE_SELL), » » #=1296992548(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10964, V=0.01, P=1296991463 >>> 13 TRADE_TRANSACTION_POSITION, EURUSD, @ 1.10947, P=1296991463 >>> 14 TRADE_TRANSACTION_DEAL_ADD, D=1279628879(DEAL_TYPE_BUY), » » #=1296992548(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10947, V=0.01, P=1296992157 >>> 15 TRADE_TRANSACTION_ORDER_DELETE, #=1296992548(ORDER_TYPE_CLOSE_BY/ORDER_STATE_FILLED), EURUSD, » » @ 1.10964, P=1296991463, b=1296992157 >>> 16 TRADE_TRANSACTION_HISTORY_ADD, #=1296992548(ORDER_TYPE_CLOSE_BY/ORDER_STATE_FILLED), EURUSD, » » @ 1.10964, P=1296991463, b=1296992157 >>> 17 TRADE_TRANSACTION_REQUEST TRADE_ACTION_CLOSE_BY, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, #=1296992548, » » P=1296991463, b=1296992157 DONE, D=1279628878, #=1296992548, V=0.01, @ 1.10964, Bid=1.10961, Ask=1.10965, Req=9 |
We still have a short position of the minimum lot. Let's close it.
>>> 18 TRADE_TRANSACTION_ORDER_ADD, #=1297002683(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, » » @ 1.10964, V=0.01, P=1296992157 >>> 19 TRADE_TRANSACTION_ORDER_DELETE, #=1297002683(ORDER_TYPE_BUY/ORDER_STATE_FILLED), EURUSD, » » @ 1.10964, P=1296992157 >>> 20 TRADE_TRANSACTION_HISTORY_ADD, #=1297002683(ORDER_TYPE_BUY/ORDER_STATE_FILLED), EURUSD, » » @ 1.10964, P=1296992157 >>> 21 TRADE_TRANSACTION_DEAL_ADD, D=1279639132(DEAL_TYPE_BUY), » » #=1297002683(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10964, V=0.01, P=1296992157 >>> 22 TRADE_TRANSACTION_REQUEST TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.10964, #=1297002683, » » P=1296992157 DONE, D=1279639132, #=1297002683, V=0.01, @ 1.10964, Bid=1.10964, Ask=1.10964, Req=10 |
If you wish, you can enable the DetailedLog option to log all properties of trading objects at the moment of event processing. In a detailed log, you can notice discrepancies between the state of objects stored in the transaction structure (at the time of its initiation) and the current state. For example, when adding an order to close a position (opposite or normal), a ticket is specified in the transaction, according to which the monitor object will no longer be able to read anything, since the position has been deleted. As a result, we will see lines like this in the log:
TRADE_TRANSACTION_ORDER_ADD, #=1297777749(ORDER_TYPE_CLOSE_BY/ORDER_STATE_STARTED), EURUSD, » » @ 1.10953, V=0.01, P=1297774881, b=1297776850 ... Error: PositionSelectByTicket(1297774881) failed: TRADE_POSITION_NOT_FOUND |
Let's restart the Expert Advisor TradeTransaction.mq5 to reset the logged events for the next test. This time we will use default settings (no details).
Now let's try to perform trading actions programmatically in the new Expert Advisor OrderSendTransaction1.mq5, and at the same time describe our OnTradeTransaction handler in it (same as in the previous example).
This Expert Advisor allows you to select the trade direction and volume: if you leave it at zero, the minimum lot of the current symbol is used by default. Also in the parameters there is a distance to the protective levels in points. The market is entered with the specified parameters, there is a 5 second pause between the setting of Stop Loss and Take Profit, and then closing the position, so that the user can intervene (for example, edit the stop loss manually), although this is not necessary, since we have already made sure that manual operations are intercepted by the program.
enum ENUM_ORDER_TYPE_MARKET
|
The strategy is launched once, for which a 1-second timer is used, which is turned off in its own handler.
int OnInit()
|
All actions are performed through an already familiar MqlTradeRequestSync structure with advanced features (MqlTradeSync.mqh): implicit initialization of fields with correct values, buy/sell methods for market orders, adjust for protective levels, and close for closing the position.
Step 1:
MqlTradeRequestSync request;
|
Step 2:
Sleep(5000); // wait 5 seconds (user can edit position)
|
Step 3:
Sleep(5000); // wait another 5 seconds
|
Intermediate waits not only make it possible to have time to consider the process, but also demonstrate an important aspect of MQL5 programming, which is single-threading. While our trading Expert Advisor is inside OnTimer, trading events generated by the terminal are accumulated in its queue and will be forwarded to the internal OnTradeTransaction handler in a deferred style, only after the exit from OnTimer.
At the same time, the TradeTransactions Expert Advisor running in parallel is not busy with any calculations and will receive trading events as quickly as possible.
The result of the execution of two Expert Advisors is presented in the following log with timing (for brevity OrderSendTransaction1 tagged as OS1, and Trade Transactions tagged as TTs).
19:09:08.078 OS1 Start trade 19:09:08.109 TTs >>> 1 19:09:08.125 TTs TRADE_TRANSACTION_ORDER_ADD, #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_STARTED), » EURUSD, @ 1.10913, V=0.01 19:09:08.125 TTs >>> 2 19:09:08.125 TTs TRADE_TRANSACTION_DEAL_ADD, D=1280661362(DEAL_TYPE_BUY), » #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10913, V=0.01, » P=1298021794 19:09:08.125 TTs >>> 3 19:09:08.125 TTs TRADE_TRANSACTION_ORDER_DELETE, #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_FILLED), » EURUSD, @ 1.10913, P=1298021794 19:09:08.125 TTs >>> 4 19:09:08.125 TTs TRADE_TRANSACTION_HISTORY_ADD, #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_FILLED), » EURUSD, @ 1.10913, P=1298021794 19:09:08.125 TTs >>> 5 19:09:08.125 TTs TRADE_TRANSACTION_REQUEST 19:09:08.125 TTs TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.10913, » D=10, #=1298021794, M=1234567890 19:09:08.125 TTs DONE, D=1280661362, #=1298021794, V=0.01, @ 1.10913, Bid=1.10913, Ask=1.10913, » Req=9 19:09:08.125 OS1 Waiting for position for deal D=1280661362 19:09:08.125 OS1 OK Open 19:09:13.133 OS1 SL/TP modification 19:09:13.164 TTs >>> 6 19:09:13.164 TTs TRADE_TRANSACTION_POSITION, EURUSD, @ 1.10913, SL=1.09913, TP=1.11913, V=0.01, » P=1298021794 19:09:13.164 OS1 OK Adjust 19:09:13.164 TTs >>> 7 19:09:13.164 TTs TRADE_TRANSACTION_REQUEST 19:09:13.164 TTs TRADE_ACTION_SLTP, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, SL=1.09913, » TP=1.11913, D=10, P=1298021794, M=1234567890 19:09:13.164 TTs DONE, Req=10 19:09:18.171 OS1 Close down 19:09:18.187 OS1 Finish 19:09:18.218 TTs >>> 8 19:09:18.218 TTs TRADE_TRANSACTION_ORDER_ADD, #=1298022443(ORDER_TYPE_SELL/ORDER_STATE_STARTED), » EURUSD, @ 1.10901, V=0.01, P=1298021794 19:09:18.218 TTs >>> 9 19:09:18.218 TTs TRADE_TRANSACTION_DEAL_ADD, D=1280661967(DEAL_TYPE_SELL), » #=1298022443(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10901, » SL=1.09913, TP=1.11913, V=0.01, P=1298021794 19:09:18.218 TTs >>> 10 19:09:18.218 TTs TRADE_TRANSACTION_ORDER_DELETE, #=1298022443(ORDER_TYPE_SELL/ORDER_STATE_FILLED), » EURUSD, @ 1.10901, P=1298021794 19:09:18.218 TTs >>> 11 19:09:18.218 TTs TRADE_TRANSACTION_HISTORY_ADD, #=1298022443(ORDER_TYPE_SELL/ORDER_STATE_FILLED), » EURUSD, @ 1.10901, P=1298021794 19:09:18.218 TTs >>> 12 19:09:18.218 TTs TRADE_TRANSACTION_REQUEST 19:09:18.218 TTs TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_SELL, V=0.01, ORDER_FILLING_FOK, @ 1.10901, » D=10, #=1298022443, P=1298021794, M=1234567890 19:09:18.218 TTs DONE, D=1280661967, #=1298022443, V=0.01, @ 1.10901, Bid=1.10901, Ask=1.10901, » Req=11 19:09:18.218 OS1 >>> 1 19:09:18.218 OS1 TRADE_TRANSACTION_ORDER_ADD, #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_STARTED), » EURUSD, @ 1.10913, V=0.01 19:09:18.218 OS1 >>> 2 19:09:18.218 OS1 TRADE_TRANSACTION_DEAL_ADD, D=1280661362(DEAL_TYPE_BUY), » #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, » @ 1.10913, V=0.01, P=1298021794 19:09:18.218 OS1 >>> 3 19:09:18.218 OS1 TRADE_TRANSACTION_ORDER_DELETE, #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_FILLED), » EURUSD, @ 1.10913, P=1298021794 19:09:18.218 OS1 >>> 4 19:09:18.218 OS1 TRADE_TRANSACTION_HISTORY_ADD, #=1298021794(ORDER_TYPE_BUY/ORDER_STATE_FILLED), » EURUSD, @ 1.10913, P=1298021794 19:09:18.218 OS1 >>> 5 19:09:18.218 OS1 TRADE_TRANSACTION_REQUEST 19:09:18.218 OS1 TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.10913, » D=10, #=1298021794, M=1234567890 19:09:18.218 OS1 DONE, D=1280661362, #=1298021794, V=0.01, @ 1.10913, Bid=1.10913, Ask=1.10913, » Req=9 19:09:18.218 OS1 >>> 6 19:09:18.218 OS1 TRADE_TRANSACTION_POSITION, EURUSD, @ 1.10913, SL=1.09913, TP=1.11913, V=0.01, » P=1298021794 19:09:18.218 OS1 >>> 7 19:09:18.218 OS1 TRADE_TRANSACTION_REQUEST 19:09:18.218 OS1 TRADE_ACTION_SLTP, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, » SL=1.09913, TP=1.11913, D=10, P=1298021794, M=1234567890 19:09:18.218 OS1 DONE, Req=10 19:09:18.218 OS1 >>> 8 19:09:18.218 OS1 TRADE_TRANSACTION_ORDER_ADD, #=1298022443(ORDER_TYPE_SELL/ORDER_STATE_STARTED), » EURUSD, @ 1.10901, V=0.01, P=1298021794 19:09:18.218 OS1 >>> 9 19:09:18.218 OS1 TRADE_TRANSACTION_DEAL_ADD, D=1280661967(DEAL_TYPE_SELL), » #=1298022443(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10901, » SL=1.09913, TP=1.11913, V=0.01, P=1298021794 19:09:18.218 OS1 >>> 10 19:09:18.218 OS1 TRADE_TRANSACTION_ORDER_DELETE, #=1298022443(ORDER_TYPE_SELL/ORDER_STATE_FILLED), » EURUSD, @ 1.10901, P=1298021794 19:09:18.218 OS1 >>> 11 19:09:18.218 OS1 TRADE_TRANSACTION_HISTORY_ADD, #=1298022443(ORDER_TYPE_SELL/ORDER_STATE_FILLED), » EURUSD, @ 1.10901, P=1298021794 19:09:18.218 OS1 >>> 12 19:09:18.218 OS1 TRADE_TRANSACTION_REQUEST 19:09:18.218 OS1 TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_SELL, V=0.01, ORDER_FILLING_FOK, @ 1.10901, » D=10, #=1298022443, P=1298021794, M=1234567890 19:09:18.218 OS1 DONE, D=1280661967, #=1298022443, V=0.01, @ 1.10901, Bid=1.10901, Ask=1.10901, » Req=11 |
The numbering of events in the programs is the same (provided that they are started cleanly, as recommended). Note that the same event is printed first from TTs immediately after the request is executed, and the second time only at the end of the test, where, in fact, all events are output from the queue to the OS1.
If we remove artificial delays, the script will, of course, run faster, but still the OnTradeTransaction handler will receive notifications (multiple times) after all three steps, not after each respective request. How critical it is?
Now the examples use our modification of the structure MqlTradeRequestSync, purposefully using the synchronous option OrderSend, which also implements a universal completed method which checks if the request completed successfully. With this control, we can set protective levels for a position, because we know how to wait for its ticket to appear. Within the framework of such a synchronous concept (adopted for the sake of convenience), we do not need to analyze query results in OnTradeTransaction. However, this is not always the case.
When an Expert Advisor needs to send many requests at once, as in the case of the example with setting a grid of orders PendingOrderGrid2.mq5 discussed in the section on position properties, waiting for each position or order to be "ready" may reduce the overall performance of the Expert Advisor. In such cases, it is recommended to use the OrderSendAsync function. But if successful, it fills only the request_id field in the MqlTradeResult, with which you then need to track the appearance of orders, deals and positions in OnTradeTransaction.
One of the most obvious but not particularly elegant tricks for implementing this scheme is to store the identifiers of requests or entire structures of the requests being sent in an array, in the global context. These identifiers can then be looked up in incoming transactions in OnTradeTransaction, the tickets can be found in the MqlTradeResult parameter and further actions can be taken. As a result, the trading logic is separated into different functions. For example, in the context of the last Expert Advisor OrderSendTransaction1.mq5 this "diversification" lies in the fact that after sending the first order, the code fragments must be transferred to OnTradeTransaction and checked for the following:
- transaction type in MqlTradeTransaction (transaction type);
- request type in MqlTradeRequest (request action);
- request id in MqlTradeResult (result.request_id);
All this should be supplemented with specific applied logic (for example, checking for the existence of a position), which provides branching by trading strategy states. A little later we will make a similar modification of the OrderSendTransaction Expert Advisor under a different number to visually show the amount of additional source code. And then we will offer a way to organize the program more linearly, but without abandoning transactional events.
For now, we only note that the developer should choose whether to build an algorithm around OnTradeTransaction or without it. In many cases, when bulk sending of orders is not needed, it is possible to stay in the synchronous programming paradigm. However, OnTradeTransaction is the most practical way to control the triggering of pending orders and protective levels, as well as other events generated by the server. After a little preparation, we will present two relevant examples: the final modification of the grid Expert Advvisor and the implementation of the popular setup of two OCO (One Cancels Other) orders (see the section On Trade).
An alternative to application of OnTradeTransaction consists in periodic analysis of the trading environment, that is, in fact, in remembering the number of orders and positions and looking for changes among them. This approach is suitable for strategies based on schedules or allowing certain time delays.
We emphasize again that the use of OnTradeTransaction does not mean that the program must necessarily switch from OrderSend on OrderSendAsync: You can use either variety or both. Recall that the OrderSend function is also not quite synchronous, as it returns, at best, the ticket of the order and the deal but not the position. Soon we will be able to measure the execution time of a batch of orders within the same grid strategy using both variants of the function: OrderSend and OrderSendAsync.
To unify the development of synchronous and asynchronous programs, it would be great to support OrderSendAsync in our structure MqlTradeRequestSync (despite its name). This can be done with just a couple of corrections. First, you need to replace all currently existing calls OrderSend to your own method orderSend, and in it switch the call to OrderSend or OrderSendAsync depending on a flag.
struct MqlTradeRequestSync: public MqlTradeRequest
|
By setting the AsyncEnabled public variable to true or false, you can switch from one mode to another, for example, in the code fragment where mass orders are sent.
Second, those methods of the structure that returned a ticket (for example, for entering the market) you should return the request_id field instead of order. For example, inside the methods _pending and _market we had the following operator:
if(OrderSend(this, result)) return result.order; |
Now it is replaced by:
if(orderSend(this, result)) return result.order ? result.order :
|
Of course, when asynchronous mode is enabled, we can no longer use the completed method to wait for the query results to be ready immediately after it is sent. But this method is, basically, optional: you can just drop it even when working through OrderSend.
So, taking into account the new modification of the MqlTradeSync.mqh file, let's create OrderSendTransaction2.mq5.
This Expert Advisor will send the initial request as before from OnTimer, while setting protective levels and closing a position in OnTradeTransaction step by step. Although we will not have an artificial delay between the stages this time, the sequence of states itself is standard for many Expert Advisors: opened a position, modified, closed (if certain market conditions are met, which are left behind the scenes here).
Two global variables will allow you to track the state: RequestID with the id of the last request sent (the result of which we expect) and Position Ticket with an open position ticket. When there the position did not appear yet, or no longer exists, the ticket is equal to 0.
uint RequestID = 0;
|
Asynchronous mode is enabled in the OnInit handler.
int OnInit()
|
The OnTimer function is now much shorter.
void OnTimer()
|
On successful completion of the request, we get only request_id and store it in the RequestID variable. The status print now contains a question mark, like "OK Open?", because the actual result is not yet known.
OnTradeTransaction became significantly more complicated due to the verification of the results and the execution of subsequent trading orders according to the conditions. Let's consider it gradually.
In this case, the entire trading logic has moved into the branch for transactions of the TRADE_TRANSACTION_REQUEST type. Of course, the developer can use other types if desired, but we use this one because it contains information in the form of a familiar structure MqlTradeResult, i.e., this sort of represents a delayed ending of an asynchronous call OrderSendAsync.
void OnTradeTransaction(const MqlTradeTransaction &transaction,
|
We should only be interested in requests with the ID we expect. So the next statement will be nested if. In its block, we describe the MqlTradeRequestSync object in advance, because it will be necessary to send regular trade requests according to the plan.
if(result.request_id == RequestID)
|
We have only two working request types, so we add one more nested if one for them.
if(request.action == TRADE_ACTION_DEAL)
|
Please note that TRADE_ACTION_DEAL is used for both opening and closing a position, and therefore one more if is required, in which we will distinguish between these two states depending on the value of the PositionTicket variable.
if(PositionTicket == 0)
|
There are no position increases (for netting) or multiple positions (for hedging) in the trading strategy under consideration, which is why this part is logically simple. Real Expert Advisors will require much more different estimates of intermediate states.
In the case of a position opening notification, the block of code looks like this:
if(PositionTicket == 0)
|
For simplicity, we have omitted error and requote checking here. You can see an example of their handling in the attached source code. Recall that all these checks have already been implemented in the methods of the MqlTradeRequestSync structure, but they only work in synchronous mode, and therefore we have to repeat them explicitly.
The next code fragment for setting protective levels has not changed much.
if(PositionTicket == 0)
|
The only difference here is: we fill the RequestID variable with ID of the new TRADE_ACTION_SLTP request.
Receiving a notification about a deal with a non-zero PositionTicket implies that the position has been closed.
if(PositionTicket == 0)
|
In case of successful deletion, the position cannot be selected using PositionSelectByTicket, so we reset RequestID and PositionTicket. The Expert Advisor then returns to its initial state and is ready to make the next buy/sell-modify-close cycle.
It remains for us to consider sending a request to close the position. In our simplified to a minimum strategy, this happens immediately after the successful modification of the protective levels.
if(request.action == TRADE_ACTION_DEAL)
|
That's the whole function OnTradeTransaction. The Expert Advisor is ready.
Let's run OrderSendTransaction2.mq5 with default settings on EURUSD. Below is an example log.
Start trade OK Open? >>> 1 TRADE_TRANSACTION_ORDER_ADD, #=1299508203(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, » » @ 1.10640, V=0.01 >>> 2 TRADE_TRANSACTION_DEAL_ADD, D=1282135720(DEAL_TYPE_BUY), » » #=1299508203(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10640, V=0.01, P=1299508203 >>> 3 TRADE_TRANSACTION_ORDER_DELETE, #=1299508203(ORDER_TYPE_BUY/ORDER_STATE_FILLED), EURUSD, » » @ 1.10640, P=1299508203 >>> 4 TRADE_TRANSACTION_HISTORY_ADD, #=1299508203(ORDER_TYPE_BUY/ORDER_STATE_FILLED), EURUSD, » » @ 1.10640, P=1299508203 >>> 5 TRADE_TRANSACTION_REQUEST TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, @ 1.10640, D=10, » » #=1299508203, M=1234567890 DONE, D=1282135720, #=1299508203, V=0.01, @ 1.1064, Bid=1.1064, Ask=1.1064, Req=7 OK Adjust? >>> 6 TRADE_TRANSACTION_POSITION, EURUSD, @ 1.10640, SL=1.09640, TP=1.11640, V=0.01, P=1299508203 >>> 7 TRADE_TRANSACTION_REQUEST TRADE_ACTION_SLTP, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, SL=1.09640, TP=1.11640, » » D=10, P=1299508203, M=1234567890 DONE, Req=8 OK Close? >>> 8 TRADE_TRANSACTION_ORDER_ADD, #=1299508215(ORDER_TYPE_SELL/ORDER_STATE_STARTED), EURUSD, » » @ 1.10638, V=0.01, P=1299508203 >>> 9 TRADE_TRANSACTION_ORDER_DELETE, #=1299508215(ORDER_TYPE_SELL/ORDER_STATE_FILLED), EURUSD, » » @ 1.10638, P=1299508203 >>> 10 TRADE_TRANSACTION_HISTORY_ADD, #=1299508215(ORDER_TYPE_SELL/ORDER_STATE_FILLED), EURUSD, » » @ 1.10638, P=1299508203 >>> 11 TRADE_TRANSACTION_DEAL_ADD, D=1282135730(DEAL_TYPE_SELL), » » #=1299508215(ORDER_TYPE_BUY/ORDER_STATE_STARTED), EURUSD, @ 1.10638, » » SL=1.09640, TP=1.11640, V=0.01, P=1299508203 >>> 12 TRADE_TRANSACTION_REQUEST TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_SELL, V=0.01, ORDER_FILLING_FOK, @ 1.10638, D=10, » » #=1299508215, P=1299508203, M=1234567890 DONE, D=1282135730, #=1299508215, V=0.01, @ 1.10638, Bid=1.10638, Ask=1.10638, Req=9 Finish |
The trading logic is working as expected, and transaction events arrive strictly after each next order is sent. If we now run our new Expert Advisor and the transactions interceptor TradeTransactions.mq5 in parallel, log messages from two Expert Advisors will appear synchronously.
However, a remake from the first straight version OrderSendTransaction1.mq5 to an asynchronous second version OrderSendTransaction2.mq5 required significantly more sophisticated code. The question arises: is it possible to somehow combine the principles of sequential description of trading logic (code transparency) and parallel processing (speed)?
In theory, this is possible, but it will require at some point to spend time to work on creating some kind of auxiliary mechanism.