Diferença de CopyTicks em diferentes corretoras.

 

Tenho um sistema para copiar ticks em tempo real que funciona perfeitamente na corretora Genial e grupo XP. Já faz algum tempo que uso esse sistema.

Como tenho verificado a questão de disponibilidade de dados no book em diferentes corretoras. acabei me deparando com o fato de CopyTicks não funciona corretamente no BTG, uma vez que somente quando uso COPY_TICKS_TRADE consigo o resultado esperado. Quando uso COPY_TICKS_ALL ou uma combinação COPY_TICKS_TRADE | COPY_TICKS_INFO, perde-se muitos ticks (as vezes parece até que tem um delay, mas na verdade o aparente delay é ticks faltantes), mas se copio os ticks posteriomente (segundos ou minutos depois) os dados vem corretamente.

Alguém já experenciou esse problema?

Fiz uma versão mais simplificada do sistema e dessa vez coloquei numa tabela igual times and sales pra comparar. Aí coloco lado a lado pra comparar. Testando em INDJ25 que é mais lento e fica mais fácil de acompanhar.

//+------------------------------------------------------------------+
//|                                                TicksHandling.mq5 |
//|                           Copyright 2025, Samuel Manoel De Souza |
//|                          https://www.mql5.com/en/users/samuelmnl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Samuel Manoel De Souza"
#property link      "https://www.mql5.com/en/users/samuelmnl"
#property version   "1.00"
#include <Canvas/Canvas.mqh>
CCanvas icanvas;
int market_book_handler = 0;
ulong last_tick_time_msc = 0;
int last_tick_time_msc_count = 0;
MqlTick ticks_list[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   if(!icanvas.CreateBitmapLabel(0, 0, "icanvas", 0, 0, 100, 100, COLOR_FORMAT_ARGB_NORMALIZE))
      return(INIT_FAILED);
   icanvas.FontSet("Tahoma", 16);
   if(market_book_handler == 0 && MarketBookAdd(_Symbol))
      market_book_handler++;

   last_tick_time_msc = 0;
   datetime time_current = TimeCurrent();
   if(time_current > 0)
      last_tick_time_msc = (time_current + 1) * 1000L;
   last_tick_time_msc_count = 0;
   ArrayFree(ticks_list);

   EventSetMillisecondTimer(50);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(market_book_handler > 0 && MarketBookRelease(_Symbol))
      market_book_handler--;
   icanvas.Destroy();
   EventKillTimer();
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
void OnTick(void)
  {

  }
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
   if(last_tick_time_msc == 0)
     {
      datetime time_current = TimeCurrent();
      if(time_current == 0)
         return;
      if(time_current > 0)
         last_tick_time_msc = (time_current + 1) * 1000L;
     }

   ulong symbol_time_msc = SymbolInfoInteger(_Symbol, SYMBOL_TIME_MSC);
   if(symbol_time_msc == 0 || symbol_time_msc <= last_tick_time_msc_count)
      return;
//---
   MqlTick ticks[];
   ulong from = last_tick_time_msc;
   int copied = CopyTicks(_Symbol, ticks, COPY_TICKS_ALL, from, 2000);
   if(copied < 0)
     {
      Print(__FUNCTION__, ", copy ticks failed with error ", (string)GetLastError());
      return;
     }

   int new_ticks = copied - last_tick_time_msc_count;
   if(new_ticks < 0)
     {
      Print(__FUNCTION__, ", less ticks copied that the previous call");
      return;
     }

   if(new_ticks == 0)
      return;

   int updates = 0;
   for(int i = last_tick_time_msc_count; i < copied; i++)
     {
      // filter
      if((ticks[i].flags & TICK_FLAG_BUY) == TICK_FLAG_BUY) {}
      else
         if((ticks[i].flags & TICK_FLAG_SELL) == TICK_FLAG_SELL) {}
         else
            continue;
      // add tick
      int size = ArraySize(ticks_list) + 1;
      ArrayResize(ticks_list, size, new_ticks);
      ticks_list[size - 1] = ticks[i];
      updates++;
     }

   last_tick_time_msc = ticks[copied - 1].time_msc;
   last_tick_time_msc_count = 0;
   for(int i = copied - 1; i >= 0; i--)
     {
      if(ticks[i].time_msc == last_tick_time_msc)
        {
         last_tick_time_msc_count++;
         continue;
        }
      break;
     }

   if(updates > 0)
      Redraw();
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   if(id == CHARTEVENT_CHART_CHANGE)
     {
      int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
      int chart_height = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
      icanvas.Resize(chart_width, chart_height);
      Redraw();
     }
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnBookEvent(const string& symbol)
  {

  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void Redraw(void)
  {
   icanvas.Erase();
   int total = ArraySize(ticks_list);
   CSize text_size;
   icanvas.TextSize("gÉ", text_size.cx, text_size.cy);
   int cell_height = text_size.cy + 9, cell_width = icanvas.Width() / 4;
   int rows = icanvas.Height() / cell_height;
   for(int i = 0; i < rows && i < total; i++)
     {
      MqlTick data = ticks_list[total - 1 - i];
      string text = "Invalid";
      color bg_clr = clrGray;
      if((data.flags & TICK_FLAG_BUY) == TICK_FLAG_BUY && (data.flags & TICK_FLAG_SELL) == TICK_FLAG_SELL)
        {
         text = "N/A";
         bg_clr = C'0xc0,0xff,0xc0';
        }
      else
         if((data.flags & TICK_FLAG_BUY) == TICK_FLAG_BUY)
           {
            text = "Buy";
            bg_clr = C'0xd7,0xe4,0xff';
           }
         else
            if((data.flags & TICK_FLAG_SELL) == TICK_FLAG_SELL)
              {
               text = "Sell";
               bg_clr = C'0xff,0xe4,0xff';
              }

      string cell_values[] =
        {
         TimeMscToString(data.time_msc),
         text,
         DoubleToString(data.last, _Digits),
         (string)data.volume
        };

      for(int j = 0; j < ArraySize(cell_values); j++)
        {
         int left = j * cell_width, right = left + cell_width, top = i * cell_height, bottom = top + cell_height;
         icanvas.FillRectangle(left, top + 1, right, bottom - 1, ColorToARGB(bg_clr));
         icanvas.TextOut((left + right) / 2, (top + bottom) / 2, cell_values[j], ColorToARGB(clrBlack), TA_CENTER | TA_VCENTER);
        }
     }
   icanvas.Update();
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
string TimeMscToString(ulong time)
  {
   string result = TimeToString((datetime)(time / 1000), TIME_MINUTES | TIME_SECONDS) + ".";
   ulong msc = time % 1000;
   string msc_text = (string)msc;
   while(StringLen(msc_text) < 3)
      msc_text = "0" + msc_text;
   result += msc_text;
   return(result);
  }
//+------------------------------------------------------------------+

Esse é o resultado na XP.


Esse é no BTG


No BTG usando apenas COPY_TICKS_TRADE


 
Samuel Manoel De Souza:
Alguém já experenciou esse problema?
Não é problema, é decisão da arquitetura do software, esta até na ajuda dizendo que ticks poderão ser perdidos.
 
Ricardo Rodrigues Lucca #:
Não é problema, é decisão da arquitetura do software, esta até na ajuda dizendo que ticks poderão ser perdidos.

Qual frase na documentação diz que ticks poderão ser perdidos?

 
Samuel Manoel De Souza:

Tenho um sistema para copiar ticks em tempo real que funciona perfeitamente na corretora Genial e grupo XP. Já faz algum tempo que uso esse sistema.

Como tenho verificado a questão de disponibilidade de dados no book em diferentes corretoras. acabei me deparando com o fato de CopyTicks não funciona corretamente no BTG, uma vez que somente quando uso COPY_TICKS_TRADE consigo o resultado esperado. Quando uso COPY_TICKS_ALL ou uma combinação COPY_TICKS_TRADE | COPY_TICKS_INFO, perde-se muitos ticks (as vezes parece até que tem um delay, mas na verdade o aparente delay é ticks faltantes), mas se copio os ticks posteriomente (segundos ou minutos depois) os dados vem corretamente.


Acredito que o problema seja causado durante a sincronização dos dados do servidor MT5 da corretora com o cache/memória do cliente MT5, a função CopyTicks utiilza (segundo a documentação) um cache onde são armazenados os últimos 4096 ticks dos ativos gerais e 65536 dos ticks para os ativos que estejam listados no Market Depth do cliente, caso haja mais ticks que o tamanho do cache então a memória é usada, e caso a solicitação do "from" seja superior ao existente na memória então seria usado os arquivos da pasta de ticks do ativo no cliente, ou seja, a chamada da função não interage diretamente com o servidor. Além disso, todas essas estruturas são sincronizadas automaticamente entre cliente/servidor, então imagino que algum problema deve ocorrer na organização/transferencia dos ticks feitas pelo servidor da corretora, mas mesmo com uma sincronização incompleta, novos ticks são enviados para as estruturas, por isso, a chamada da função retorna como sucesso mesmo com os ticks faltando. E como o cliente MT5 não tem como saber que estão faltando ticks, uma vez que são gerados pelo servidor, então é o servidor que deve identificar o problema e solicitar uma atualização dos ticks faltantes nas estruturas, por isso que eles aparecerem corretamente depois de um tempo na chamada da função. Bom, essa é a minha hipótese.