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.

Fills, Ticks and Seconds

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

One Response to “ Distinguishing quasi simultaneous fills - class to report last deals ”

  1. Bogdan Caramalac, MQLmagazine sr.editor on July 10, 2010 at 2:58 pm

    Moved the class code into an include file, DealHandler.mqh .