- 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
Functions for reading deal properties from history
To read deal properties, there are groups of functions organized by property type: integer, real and string. Before calling functions, you need to select the desired period of history and thus ensure the availability of deals with tickets that are passed in the first parameter (ticket) of all functions.
There are two forms for each type of property: returning a value directly and writing to a variable by reference. The second form returns true to indicate success. The first form will simply return 0 on error. The error code is in the _LastError variable.
Integer and compatible property types (datetime, enumerations) can be obtained using the HistoryDealGetInteger function.
long HistoryDealGetInteger(ulong ticket, ENUM_DEAL_PROPERTY_INTEGER property)
bool HistoryDealGetInteger(ulong ticket, ENUM_DEAL_PROPERTY_INTEGER property,
long &value)
Real properties are read by the HistoryDealGetDouble function.
double HistoryDealGetDouble(ulong ticket, ENUM_DEAL_PROPERTY_DOUBLE property)
bool HistoryDealGetDouble(ulong ticket, ENUM_DEAL_PROPERTY_DOUBLE property,
double &value)
For string properties there is the HistoryDealGetString function.
string HistoryDealGetString(ulong ticket, ENUM_DEAL_PROPERTY_STRING property)
bool HistoryDealGetString(ulong ticket, ENUM_DEAL_PROPERTY_STRING property,
string &value)
A unified reading of deal properties will be provided by the DealMonitor class (DealMonitor.mqh), organized in exactly the same way as OrderMonitor and PositionMonitor. The base class is DealMonitorInterface, inherited from the template MonitorInterface (we described it in the section Functions for reading the properties of active orders). It is at this level that the specific types of ENUM_DEAL_PROPERTY enumerations are specified as template parameters and the specific implementation of the stringify method.
#include <MQL5Book/TradeBaseMonitor.mqh>
|
The DealMonitor class below is somewhat similar to a class recently modified to work with history OrderMonitor. In addition to the application of HistoryDeal functions instead of HistoryOrder functions, it should be noted that for deals there is no need to check the ticket in the online environment because deals exist only in history.
class DealMonitor: public DealMonitorInterface
|
Based on DealMonitor and TradeFilter it is easy to create a deal filter (DealFilter.mqh). Recall that TradeFilter, as the base class for many entities, was described in the section Selecting orders by properties.
#include <MQL5Book/DealMonitor.mqh>
|
As a generalized example of working with histories, consider the position history recovery script TradeHistoryPrint.mq5.
TradeHistoryPrint
The script will build a history for the current chart symbol.
We first need filters for deals and orders.
#include <MQL5Book/OrderFilter.mqh>
|
From the deals, we will extract the position IDs and, based on them, we will request details about the orders.
The history can be viewed in its entirety or for a specific position, for which we will provide a mode selection and an input field for the identifier in the input variables.
enum SELECTOR_TYPE
|
It should be remembered that sampling a long account history can be an overhead, so it is desirable to provide for caching of the obtained results of history processing in working Expert Advisors, along with the last processing timestamp. With each subsequent analysis of history, you can start the process not from the very beginning, but from a remembered moment.
To display information about history records with column alignment in a visually attractive way, it makes sense to represent it as an array of structures. However, our filters already support querying data stored in special structures - tuples. Therefore, we will apply a trick: we will describe our application structures, observing the rules of tuples:
- The first field must have the name _1; it is optionally used in the sorting algorithm.
- The size function returning the number of fields must be described in the structure.
- The structure should have a template method assign to populate fields from the properties of the passed monitor object derived from MonitorInterface.
In standard tuples, the method assign is described like this:
template<typename M>
|
As the first parameter, it receives an array with the property IDs corresponding to the fields we are interested in. In fact, this is the array that is passed by the calling code to the select method of the filter (TradeFilter::select), and then by reference it gets to assign. But since we will now create not some standard tuples but our own structures that "know" about the applied nature of their fields, we can leave the array with property identifiers inside the structure itself and not "drive" it into the filter and back to the assign method of the same structure.
In particular, to request deals, we describe the DealTuple structure with 8 fields. Their identifiers will be specified in the fields static array.
struct DealTuple
|
This approach brings together identifiers and fields to store the corresponding values in a single place, which makes it easier to understand and maintain the source code.
Filling fields with property values will require a slightly modified (simplified) version of the assign method which takes the IDs from the fields array and not from the input parameter.
struct DealTuple
|
At the same time, we convert the numeric elements of the ENUM_DEAL_TYPE and ENUM_DEAL_ENTRY enumerations into user-friendly strings. Of course, this is only needed for logging. For programmatic analysis, the types should be left as they are.
Since we have invented a new version of the assign method in their tuples, you need to add a new version of the select method for it in the TradeFilter class. The innovation will certainly be useful for other programs, and therefore we will introduce it directly into TradeFilter, not into some new derived class.
template<typename T,typename I,typename D,typename S>
|
Recall that all template methods are not implemented by the compiler until they are called in code with a specific type. Therefore, the presence of such patterns in TradeFilter does not oblige you to include any tuple header files or describe similar structures if you don't use them.
So, if earlier, to select transactions using a standard tuple, we would have to write like this:
#include <MQL5Book/Tuples.mqh>
|
Then with a customized structure, everything is much simpler:
DealFilter filter;
|
Similar to the DealTuple structure, let's describe the 10-field structure for orders OrderTuple.
struct OrderTuple
|
Now everything is ready to implement the main function of the script OnStart. At the very beginning, we will describe the objects of filters for deals and orders.
void OnStart()
|
Depending on the input variables, we choose either the entire history or a specific position.
if(PositionID == 0 || Type == TOTAL)
|
Next, we will collect all position identifiers in an array, or leave one specified by the user.
ulong positions[];
|
The helper function ArrayUnique leaves non-repeating elements in the array. It requires the source array to be sorted for it to work.
Further, in a loop through positions, we request deals and orders related to each of them. Deals are sorted by the first field of the DealTuple structure, i.e., by time. Perhaps the most interesting is the calculation of profit/loss on a position. To do this, we sum the values of the profit field of all deals.
for(int i = 0; i < n; ++i)
|
This code does not analyze commissions (DEAL_COMMISSION), swaps (DEAL_SWAP), and fees (DEAL_FEE) in deal properties. In real Expert Advisors, this should probably be done (depending on the requirements of the strategy). We will look at another example of trading history analysis in the section on testing multicurrency Expert Advisors, and there we will take into account this moment.
You can compare the results of the script with the table on the History tab in the terminal: its Profit column shows the net profit for each position (swaps, commissions, and fees are in adjacent columns, but they need to be included).
It is important to note that an order of the ORDER_TYPE_CLOSE_BY type will be displayed in both positions only if the entire history is selected in the settings. If a specific position was selected, the system will include such an order only in one of them (the one that was specified in the trade request first, in the position field) but not the second one (which was specified in position_by).
Below is an example of the result of the script for a symbol with a small history.
Positions total: 3 Position: 1 1253500309 Profit:238.150000 [_1] [deal] [order] [type] [in_out] [volume] [price] [profit] [0] 2022.02.04 17:34:57 1236049891 1253500309 "BUY" "IN" 1.00000 76.23900 0.00000 [1] 2022.02.14 16:28:41 1242295527 1259788704 "SELL" "OUT" 1.00000 76.42100 238.15000 Order details: [_1] [setup] [done] [type] [volume] [open] [current] » » [sl] [tp] [comment] [0] 1253500309 2022.02.04 17:34:57 2022.02.04 17:34:57 "BUY" 1.00000 76.23900 76.23900 » » 0.00 0.00 "" [1] 1259788704 2022.02.14 16:28:41 2022.02.14 16:28:41 "SELL" 1.00000 76.42100 76.42100 » » 0.00 0.00 "" Position: 2 1253526613 Profit:878.030000 [_1] [deal] [order] [type] [in_out] [volume] [price] [profit] [0] 2022.02.07 10:00:00 1236611994 1253526613 "BUY" "IN" 1.00000 75.75000 0.00000 [1] 2022.02.14 16:28:40 1242295517 1259788693 "SELL" "OUT" 1.00000 76.42100 878.03000 Order details: [_1] [setup] [done] [type] [volume] [open] [current] » » [sl] [tp] [comment] [0] 1253526613 2022.02.04 17:55:18 2022.02.07 10:00:00 "BUY_LIMIT" 1.00000 75.75000 75.67000 » » 0.00 0.00 "" [1] 1259788693 2022.02.14 16:28:40 2022.02.14 16:28:40 "SELL" 1.00000 76.42100 76.42100 » » 0.00 0.00 "" Position: 3 1256280710 Profit:4449.040000 [_1] [deal] [order] [type] [in_out] [volume] [price] [profit] [0] 2022.02.09 13:17:52 1238797056 1256280710 "BUY" "IN" 2.00000 74.72100 0.00000 [1] 2022.02.14 16:28:39 1242295509 1259788685 "SELL" "OUT" 2.00000 76.42100 4449.04000 Order details: [_1] [setup] [done] [type] [volume] [open] [current] » » [sl] [tp] [comment] [0] 1256280710 2022.02.09 13:17:52 2022.02.09 13:17:52 "BUY" 2.00000 74.72100 74.72100 » » 0.00 0.00 "" [1] 1259788685 2022.02.14 16:28:39 2022.02.14 16:28:39 "SELL" 2.00000 76.42100 76.42100 » » 0.00 0.00 "" |
The case of increasing a position (two "IN" deals) and its reversal (an "INOUT" deal of a larger volume) on a netting account is shown in the following fragment.
Position: 5 219087383 Profit:0.170000 [_1] [deal] [order] [type] [in_out] [volume] [price] [profit] [0] 2022.03.29 08:03:33 215612450 219087383 "BUY" "IN" 0.01000 1.10011 0.00000 [1] 2022.03.29 08:04:05 215612451 219087393 "BUY" "IN" 0.01000 1.10009 0.00000 [2] 2022.03.29 08:04:29 215612457 219087400 "SELL" "INOUT" 0.03000 1.10018 0.16000 [3] 2022.03.29 08:04:34 215612460 219087403 "BUY" "OUT" 0.01000 1.10017 0.01000 Order details: [_1] [setup] [done] [type] [volume] [open] [current] » » [sl] [tp] [comment] [0] 219087383 2022.03.29 08:03:33 2022.03.29 08:03:33 "BUY" 0.01000 0.0000 1.10011 » » 0.00 0.00 "" [1] 219087393 2022.03.29 08:04:05 2022.03.29 08:04:05 "BUY" 0.01000 0.0000 1.10009 » » 0.00 0.00 "" [2] 219087400 2022.03.29 08:04:29 2022.03.29 08:04:29 "SELL" 0.03000 0.0000 1.10018 » » 0.00 0.00 "" [3] 219087403 2022.03.29 08:04:34 2022.03.29 08:04:34 "BUY" 0.01000 0.0000 1.10017 » » 0.00 0.00 "" |
We will consider a partial history using the example of specific positions for the case of an opposite closure on a hedging account. First, you can view the first position separately: PositionID=1276109280. It will be shown in full regardless of the input parameter Type.
Positions total: 1 Position: 1 1276109280 Profit:-0.040000 [_1] [deal] [order] [type] [in_out] [volume] [price] [profit] [0] 2022.03.07 12:20:53 1258725455 1276109280 "BUY" "IN" 0.01000 1.08344 0.00000 [1] 2022.03.07 12:20:58 1258725503 1276109328 "SELL" "OUT_BY" 0.01000 1.08340 -0.04000 Order details: [_1] [setup] [done] [type] [volume] [open] [current] » » [sl] [tp] [comment] [0] 1276109280 2022.03.07 12:20:53 2022.03.07 12:20:53 "BUY" 0.01000 1.08344 1.08344 » » 0.00 0.00 "" [1] 1276109328 2022.03.07 12:20:58 2022.03.07 12:20:58 "CLOSE_BY" 0.01000 1.08340 1.08340 » » 0.00 0.00 "#1276109280 by #1276109283" |
You can also see the second one: PositionID=1276109283. However, if Type equals "position", to select a fragment of history, the function HistorySelectByPosition is used, and as a result there will be only one exit order (despite the fact that there are two deals).
Positions total: 1 Position: 1 1276109283 Profit:0.000000 [_1] [deal] [order] [type] [in_out] [volume] [price] [profit] [0] 2022.03.07 12:20:53 1258725458 1276109283 "SELL" "IN" 0.01000 1.08340 0.00000 [1] 2022.03.07 12:20:58 1258725504 1276109328 "BUY" "OUT_BY" 0.01000 1.08344 0.00000 Order details: [_1] [setup] [done] [type] [volume] [open] [current] » » [sl] [tp] [comment] [0] 1276109283 2022.03.07 12:20:53 2022.03.07 12:20:53 "SELL" 0.01000 1.08340 1.08340 » » 0.00 0.00 "" |
If we set Type to the "whole history", a "CLOSE_BY" order will appear.
Positions total: 1 Position: 1 1276109283 Profit:0.000000 [_1] [deal] [order] [type] [in_out] [volume] [price] [profit] [0] 2022.03.07 12:20:53 1258725458 1276109283 "SELL" "IN" 0.01000 1.08340 0.00000 [1] 2022.03.07 12:20:58 1258725504 1276109328 "BUY" "OUT_BY" 0.01000 1.08344 0.00000 Order details: [_1] [setup] [done] [type] [volume] [open] [current] » » [sl] [tp] [comment] [0] 1276109283 2022.03.07 12:20:53 2022.03.07 12:20:53 "SELL" 0.01000 1.08340 1.08340 » » 0.00 0.00 "" [1] 1276109328 2022.03.07 12:20:58 2022.03.07 12:20:58 "CLOSE_BY" 0.01000 1.08340 1.08340 » » 0.00 0.00 "#1276109280 by #1276109283" |
With such settings, the history is selected completely, but the filter leaves only those orders, in which the identifier of the specified position is found in the ORDER_POSITION_ID or ORDER_POSITION_BY_ID properties. For composing conditions with a logical OR, the IS::OR_EQUAL element has been added to the TradeFilter class. You can additionally study it.