Distinguishing quasi simultaneous fills – class to report last deals
[Versiunea romaneasca] [MQLmagazine.com in romana] [English edition]
It’s said that you’re a magician if you can make a woman laugh. I think you should try this with Irene Aldridge. Tell her that time in MT5 is seconds based. And if you don’t get but a smile, tell her that OnTrade() doesn’t tell you which order was filled! Trust me, that should make her roll laughing!
The issue is that timestamps in MT5 are seconds based. Now latency is measured in microseconds, which is a millionth of a second, as opposed to what it was about two years ago, when latency was measured in milliseconds (a thousandth of a second). Needless to say, even if MT5 time would be standard, including hundredths of seconds, would still be too impractical for at least attemtping higher frequency trading.

An important issue is that you don’t know quote latency, meaning the time distance between two ticks. In the level II forex there are hundredths of executions per second, and many ticks that are lacking in retail stations. So you could expect a from a few ticks per second, to a tick at a few seconds or more (asian session). For instance, if you look up the chart, you can see that both fills 2 and 3 are within second 2. However, until second 2 happens, fills 2 and 3 will be reported as within second 1. At the same time, both ticks 1 and 2 will have second 1 as TimeCurrent(). A second seems to be a moment, but’s actually an interval of time.
Worst solution to address the problem of distinguishing fills is the usage of HistorySelect() with tight timings. Because these timings will be wrong – either too large or too small. Best solution to do it, is selecting the entire history, but processing only what was done since last processing time. The time of the last deal is a good method to know where to stop scanning, but for the same reasons as before, many deals per second, the ticket is an ideal way to store last reported deal. Actually the two components have to be stored: the ticket and the index. Time is not compulsory, but it can be added if you wish to make changes to the class.
We have the following scenarios:
a. Last deal index for real < Last stored deal index. Something has happened with the history. Search for last ticket. If found, report trades from last ticket until present time, otherwise no reports.
b. Last deal index for real = Last stored deal index. As you see now, OnTrade() triggers twice for a deal, so this case has to be treated. Also, this may happen if history has a limited number of deals, thus the last index is always the same. As in the previous case, it is necessary as search for the last ticket.
c. Last deal index for real > Last stored deal index. Select and report deals until latest deal in history.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | //+------------------------------------------------------------------+ //| DealHandler.mq5 | //| Copyright Bogdan Caramalac | //| http:\\mqlmagazine.com | //+------------------------------------------------------------------+ #property copyright "Bogdan Caramalac" #property link "http:\\mqlmagazine.com" #property version "1.00" //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ class DealHandler { private: int LastDealIndex; ulong LastDealTicket; datetime LastDealTime; bool setup; public: bool IsSetup(); void Setup(); void ReportDeals(); //sadly, virtual methods must reside in the class body virtual void EnumDealsCallback(int fromindex,int toindex) { for (int i=fromindex;i<=toindex;i++) { Print("Deal ",i," has ticket ",HistoryDealGetTicket(i)); } return; } DealHandler() { setup=false; Setup(); return; } };//DealHandler class end bool DealHandler::IsSetup() { return(setup); } void DealHandler::Setup() { HistorySelect(0,TimeCurrent()+5*60); LastDealIndex=HistoryDealsTotal()-1; if (LastDealIndex!=-1) { LastDealTicket=HistoryDealGetTicket(LastDealIndex); LastDealTime=HistoryDealGetInteger(LastDealTicket,DEAL_TIME); } else { LastDealTicket=0; LastDealTime=0; } if (TerminalInfoInteger(TERMINAL_CONNECTED)==true) setup=true; return; } void DealHandler::ReportDeals() { datetime dtime; ulong ticket; int lasti; if (setup==false) { Setup(); return; } lasti=0; HistorySelect(0,TimeCurrent()+5*60); if (HistoryDealsTotal()==0) return; if (HistoryDealsTotal()-1<LastDealIndex) //damn, they deleted deals from history { for (int i=HistoryDealsTotal()-1;i>=0;i--) { ticket=HistoryDealGetTicket(i); if (ticket==LastDealTicket) { lasti=i+1; break; } } if (lasti!=0) { if (lasti<HistoryDealsTotal()) { EnumDealsCallback(lasti,HistoryDealsTotal()-1); LastDealIndex=HistoryDealsTotal()-1; LastDealTicket=HistoryDealGetTicket(LastDealIndex); LastDealTime=HistoryDealGetInteger(LastDealTicket,DEAL_TIME); } } } else { if (HistoryDealsTotal()-1==LastDealIndex) {//possible limited history to a fixed number of deals //or a multiple OnTrade() triggers per deal for (int i=HistoryDealsTotal()-1;i>=0;i--) { ticket=HistoryDealGetTicket(i); if (ticket==LastDealTicket) { lasti=i+1; break; } } if (lasti!=0) { if (lasti<HistoryDealsTotal()) { EnumDealsCallback(lasti,HistoryDealsTotal()-1); LastDealIndex=HistoryDealsTotal()-1; LastDealTicket=HistoryDealGetTicket(LastDealIndex); LastDealTime=HistoryDealGetInteger(LastDealTicket,DEAL_TIME); } } } else { if (HistoryDealsTotal()-1>LastDealIndex) //current index is larger; simple selection; { EnumDealsCallback(LastDealIndex+1,HistoryDealsTotal()-1); LastDealIndex=HistoryDealsTotal()-1; LastDealTicket=HistoryDealGetTicket(LastDealIndex); LastDealTime=HistoryDealGetInteger(LastDealTicket,DEAL_TIME); } }//else if (HistoryDealsTotal()-1==LastDealIndex) }//if (HistoryDealsTotal()-1<LastDealIndex) return; }; DealHandler DealHandlerDevice; void OnTrade() { DealHandlerDevice.ReportDeals(); } int OnInit() { if (DealHandlerDevice.IsSetup()==false) DealHandlerDevice.Setup(); return(0); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if (DealHandlerDevice.IsSetup()==false) DealHandlerDevice.Setup(); return; } //+------------------------------------------------------------------+ |
As you can see, the DealHandler class is used straight as it is, by the object DealHandlerDevice. The OnInit() and OnTick() check if the object is properly set up (it contains valid info), because only in this case the deals can be reported. It is mandatory that your OnTrade() calls the ReportDeals() method of the object. The ReportDeals() method will just look to see what fills happened since last report, and forwards them to EnumDealsCallback(), which is a virtual method; so you can just inherit DealHandler in a new class and define your own EnumDealsCallback(), or just write over my example. This is why I also left blancs in OnInit() and OnTick(). What follows is your choice.
To test, simply activate the EA on an instrument, then activate the Experts tab within the Toolbox. Then just play with some manual trades.
File link:
DealHandler.mqh
Moved the class code into an include file, DealHandler.mqh .