Me passaram essa sugestão para avaliar a confiança das FLAGS:
O que sse código faz:
-> Verifica a % de ticks TRADE que vêm com BUY/SELL flags vs sem flag
-> Consistência preço vs flag: distância do last para bid/ask e se o lado “faz sentido”
-> Consolidação: quantos ticks TRADE têm mesmo last seguido (indício de agregação)
//+------------------------------------------------------------------+ //| TickFlagAudit.mq5 | //| Audita flags BUY/SELL em COPY_TICKS_TRADE no MT5 | //| - Grava summary no CSV (sempre) | //| - Heartbeat 60s | //+------------------------------------------------------------------+ #property strict #property version "1.10" // ======================= // INPUTS // ======================= input int FlushEveryN = 50; // a cada N ticks TRADE, grava summary input bool LogEachTick = false; // true = grava linha por tick (arquivo cresce rápido) input double EpsPoints = 2.0; // tolerância (pts) p/ checar consistência input int MaxCopy = 32; // ticks por chamada (pega só o último) // ======================= // GLOBALS // ======================= int h = INVALID_HANDLE; string csvName; long n_trade = 0; long n_buy = 0, n_sell = 0, n_none = 0, n_both = 0; double sum_dAsk_buy = 0.0, sum_dBid_buy = 0.0; double sum_dAsk_sell = 0.0, sum_dBid_sell = 0.0; long n_buy_consistent = 0, n_buy_inconsistent = 0; long n_sell_consistent = 0, n_sell_inconsistent = 0; long n_same_last = 0; double last_trade_price = 0.0; bool has_last_trade = false; int ticks_since_flush = 0; datetime lastHeartbeat = 0; string DateStamp(datetime t) { MqlDateTime dt; TimeToStruct(t, dt); return StringFormat("%04d%02d%02d", dt.year, dt.mon, dt.day); } bool OpenCsv() { csvName = StringFormat("TickFlagAudit_%s_%s.csv", _Symbol, DateStamp(TimeCurrent())); bool existed = FileIsExist(csvName, FILE_COMMON); h = FileOpen(csvName, FILE_READ|FILE_WRITE|FILE_CSV|FILE_COMMON); if(h == INVALID_HANDLE) { PrintFormat("ERRO FileOpen [%s] err=%d", csvName, GetLastError()); return false; } FileSeek(h, 0, SEEK_END); if(!existed) { // Header SUMMARY (sempre terá linhas) FileWrite(h, "time","symbol", "n_trade","n_buy","n_sell","n_none","n_both","n_same_last", "pct_buy","pct_sell","pct_none","pct_both", "buy_consistent_pct","sell_consistent_pct", "avg_dAsk_buy_pts","avg_dBid_buy_pts", "avg_dBid_sell_pts","avg_dAsk_sell_pts" ); if(LogEachTick) { // separador/marker (opcional) FileWrite(h, "----", "----", "TICK_LOG_ENABLED"); FileWrite(h, "tick_time","last","bid","ask", "flags","is_buy","is_sell", "d_to_ask_pts","d_to_bid_pts", "side_consistent","same_last" ); } FileFlush(h); } string commonPath = TerminalInfoString(TERMINAL_COMMONDATA_PATH); PrintFormat("TickFlagAudit iniciado. Arquivo em Common\\Files\\%s | CommonPath=%s", csvName, commonPath); return true; } void WriteSummaryLine() { if(h == INVALID_HANDLE) return; double pct_buy = (n_trade > 0 ? 100.0 * (double)n_buy / (double)n_trade : 0.0); double pct_sell = (n_trade > 0 ? 100.0 * (double)n_sell / (double)n_trade : 0.0); double pct_none = (n_trade > 0 ? 100.0 * (double)n_none / (double)n_trade : 0.0); double pct_both = (n_trade > 0 ? 100.0 * (double)n_both / (double)n_trade : 0.0); double buy_cons = (n_buy > 0 ? 100.0 * (double)n_buy_consistent / (double)n_buy : 0.0); double sell_cons= (n_sell> 0 ? 100.0 * (double)n_sell_consistent/ (double)n_sell: 0.0); double avg_dAsk_buy = (n_buy > 0 ? (sum_dAsk_buy / (double)n_buy) : 0.0); double avg_dBid_buy = (n_buy > 0 ? (sum_dBid_buy / (double)n_buy) : 0.0); double avg_dBid_sell = (n_sell > 0 ? (sum_dBid_sell / (double)n_sell) : 0.0); double avg_dAsk_sell = (n_sell > 0 ? (sum_dAsk_sell / (double)n_sell) : 0.0); // grava linha SUMMARY no CSV FileWrite( h, TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS), _Symbol, n_trade, n_buy, n_sell, n_none, n_both, n_same_last, pct_buy, pct_sell, pct_none, pct_both, buy_cons, sell_cons, avg_dAsk_buy, avg_dBid_buy, avg_dBid_sell, avg_dAsk_sell ); FileFlush(h); // também printa no Journal PrintFormat( "TickFlagAudit | TRADE=%lld | BUY=%lld (%.1f%%) | SELL=%lld (%.1f%%) | NONE=%lld (%.1f%%) | BOTH=%lld (%.1f%%) | same_last=%lld | BUYcons=%.1f%% | SELLcons=%.1f%%", n_trade, n_buy, pct_buy, n_sell, pct_sell, n_none, pct_none, n_both, pct_both, n_same_last, buy_cons, sell_cons ); } int OnInit() { if(!OpenCsv()) return INIT_FAILED; lastHeartbeat = TimeCurrent(); return INIT_SUCCEEDED; } void OnDeinit(const int reason) { WriteSummaryLine(); if(h != INVALID_HANDLE) FileClose(h); } void OnTick() { // ===== pega ticks TRADE ===== MqlTick ticks[]; int copied = CopyTicks(_Symbol, ticks, COPY_TICKS_TRADE, 0, MaxCopy); if(copied <= 0) { // heartbeat mesmo sem trades if(TimeCurrent() - lastHeartbeat >= 60) { WriteSummaryLine(); lastHeartbeat = TimeCurrent(); } return; } // Processa o mais recente MqlTick t = ticks[0]; if(t.last <= 0.0) { if(TimeCurrent() - lastHeartbeat >= 60) { WriteSummaryLine(); lastHeartbeat = TimeCurrent(); } return; } double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double last = t.last; bool is_buy = ((t.flags & TICK_FLAG_BUY) == TICK_FLAG_BUY); bool is_sell = ((t.flags & TICK_FLAG_SELL) == TICK_FLAG_SELL); n_trade++; if(is_buy && is_sell) { n_both++; } else if(is_buy) { n_buy++; } else if(is_sell) { n_sell++; } else { n_none++; } double dAsk = (ask > 0 ? MathAbs(ask - last) / _Point : 0.0); double dBid = (bid > 0 ? MathAbs(last - bid) / _Point : 0.0); int side_consistent = -1; // -1 n/a, 0 inconsistente, 1 consistente if(is_buy && !is_sell) { sum_dAsk_buy += dAsk; sum_dBid_buy += dBid; if(dAsk <= dBid + EpsPoints) { n_buy_consistent++; side_consistent = 1; } else { n_buy_inconsistent++; side_consistent = 0; } } else if(is_sell && !is_buy) { sum_dAsk_sell += dAsk; sum_dBid_sell += dBid; if(dBid <= dAsk + EpsPoints) { n_sell_consistent++; side_consistent = 1; } else { n_sell_inconsistent++; side_consistent = 0; } } int same_last = 0; if(has_last_trade && MathAbs(last - last_trade_price) < 0.5*_Point) { n_same_last++; same_last = 1; } last_trade_price = last; has_last_trade = true; // ===== log por tick (opcional) ===== if(LogEachTick && h != INVALID_HANDLE) { FileWrite( h, TimeToString((datetime)t.time, TIME_DATE|TIME_SECONDS), last, bid, ask, (long)t.flags, (is_buy?1:0), (is_sell?1:0), dAsk, dBid, side_consistent, same_last ); // não flush toda hora pra não travar } // ===== summary por contagem ===== ticks_since_flush++; if(ticks_since_flush >= FlushEveryN) { WriteSummaryLine(); ticks_since_flush = 0; } // ===== heartbeat por tempo ===== if(TimeCurrent() - lastHeartbeat >= 60) { WriteSummaryLine(); lastHeartbeat = TimeCurrent(); } }
Resultados que coletei com o código acima estão no arquivo anexado. Dados obtidos no WINJ26 operando pela corretora Genial.
Conclusões da análise do código:
Resultado acumulado em 10 min (15:22:29 → 15:32:29)
n_trade: 64.150 ticks TRADE
pct_buy: 49,64%
pct_sell: 47,76%
pct_none: 0,00% (quase todos vêm com algum flag)
pct_both: 2,60% (BUY e SELL ao mesmo tempo)
Consistência “flag vs bid/ask”
BUY consistent: 59,19%
SELL consistent: 59,31%
Isso é moderado. Se fosse “agressor confiável”, seria algo do tipo 75–90% (dependendo do ativo/servidor). ~59% significa: dá pra usar como proxy grosseira, mas não para microestrutura fina (absorção/reversão por agressor).
Sinal forte de consolidação/agregação
n_same_last: 46.715
same_last / n_trade: 72,82%
Ou seja: em ~73% dos ticks TRADE seguidos, o last repetiu. Isso é compatível com:
muitas execuções no mesmo preço ou servidor consolidando/achatando a sequência (o que prejudica “micro timing”).
SOBRE A CONFIABILIDADE:
Flags existem (NONE=0%)
Mas o “lado agressor” é fraco/moderado (≈59%)
E há forte agregação (same_last≈73%)
ENTÃO: Usar OFI/persistência apenas como sinal macro estatístico, com smoothing e filtros, mas não como “order flow real” para detecção de absorção e reversão curta. Por isso que quando tentei encontrar padrões não batia com os dados coletados.
Alguém usa microestrutura no MT5? Conseguem ter bons resultados? Vou tentar encontrar alguma correlação com a minha OFI calculada versus Retornos futuros, se não achar vou abandonar esse projeto com muito amargor, porque levei um temão para aprender microestrutura, crie um código de mais de 3000 linhas. Estou frustrado mesmo. Se alguém tiver alguma experiência com isso por favor me ajudem.
Obrigado,
Teste de correlação entre OFI (Order Flow Imbalance) versus Retornos futuros
OFI está pior que aleatório. E a correlação é ligeiramente negativa. Isso significa que no feed do MT5: O OFI calculado por flags
NÃO representa fluxo real, ou até pior, ele pode estar invertido ou distorcido. Isso casa exatamente com o que a corretora disse: "os ticks são consolidados e não refletem o fluxo real da B3."
Com esse feed, o OFI baseado em TICK_FLAG_BUY/SELL não possui edge estatístico
Logo estratégias baseadas em:
-> Absorção
-> Agressão institucional
-> Footprint
-> Imbalance
não podem funcionar corretamente no MT5. Foi a conclusão que chegei. O que eu preciso é acesso a esse tipo de dados de forma confiável, porque ficar com robots só no OHLC é complicado.
Código para realização do teste:
//+------------------------------------------------------------------+ //| OFI_CorrAudit.mq5 | //| Correlação OFI (por candle) vs retorno futuro (1/3/5) | //| Compatível com MQL5 (sem auto/lambda) | //+------------------------------------------------------------------+ #property strict #property version "1.10" input int CorrWindow = 300; // número de candles M1 usados input int FlushEveryBars = 10; // flush do arquivo a cada N barras input bool PrintEachBar = true; // print no Journal int h = INVALID_HANDLE; string csvName; datetime lastBarTime = 0; int bars_since_flush = 0; string DateStamp(datetime t) { MqlDateTime dt; TimeToStruct(t, dt); return StringFormat("%04d%02d%02d", dt.year, dt.mon, dt.day); } int Sign(double x) { if(x > 0) return 1; if(x < 0) return -1; return 0; } double Corr(const double &x[], const double &y[], int n) { if(n < 10) return 0.0; double sx=0, sy=0; for(int i=0;i<n;i++){ sx += x[i]; sy += y[i]; } double mx = sx/n, my = sy/n; double num=0, vx=0, vy=0; for(int i=0;i<n;i++) { double dx = x[i]-mx; double dy = y[i]-my; num += dx*dy; vx += dx*dx; vy += dy*dy; } if(vx <= 1e-12 || vy <= 1e-12) return 0.0; return num / MathSqrt(vx*vy); } double HitRate(const double &x[], const double &y[], int n) { if(n < 10) return 0.0; int hit=0, valid=0; for(int i=0;i<n;i++) { int sx = Sign(x[i]); int sy = Sign(y[i]); if(sx==0 || sy==0) continue; valid++; if(sx==sy) hit++; } if(valid == 0) return 0.0; return (double)hit / (double)valid; } bool OpenCsv() { csvName = StringFormat("OFI_CorrAudit_%s_%s.csv", _Symbol, DateStamp(TimeCurrent())); bool existed = FileIsExist(csvName, FILE_COMMON); h = FileOpen(csvName, FILE_READ|FILE_WRITE|FILE_CSV|FILE_COMMON); if(h == INVALID_HANDLE) { PrintFormat("ERRO FileOpen [%s] err=%d", csvName, GetLastError()); return false; } FileSeek(h, 0, SEEK_END); if(!existed) { FileWrite(h, "time","symbol", "win","n_used", "corr_r1","corr_r3","corr_r5", "hit_r1","hit_r3","hit_r5", "pct_nonzero_r1","pct_nonzero_r3","pct_nonzero_r5" ); FileFlush(h); } PrintFormat("OFI_CorrAudit iniciado. Arquivo: Common\\Files\\%s", csvName); return true; } bool IsNewBarM1() { datetime t = iTime(_Symbol, PERIOD_M1, 0); if(t != lastBarTime) { lastBarTime = t; return true; } return false; } // Up/Down via ticks TRADE no intervalo [start,end) void GetMarketAggressions(datetime start, datetime end, long &up, long &down) { up = 0; down = 0; MqlTick ticks[]; int copied = CopyTicksRange(_Symbol, ticks, COPY_TICKS_TRADE, (ulong)start*1000, (ulong)end*1000); if(copied <= 0) return; for(int i=0; i<copied; i++) { long vol = (long)ticks[i].volume; if(vol <= 0) continue; bool b = ((ticks[i].flags & TICK_FLAG_BUY) == TICK_FLAG_BUY); bool s = ((ticks[i].flags & TICK_FLAG_SELL) == TICK_FLAG_SELL); // BOTH: divide volume para não enviesar if(b && s) { up += vol/2; down += vol - vol/2; } else if(b) up += vol; else if(s) down += vol; } } // Calcula OFI de um candle fechado (shift) double CalcOFI_ForClosedBar(int shift) { datetime start = iTime(_Symbol, PERIOD_M1, shift); datetime end = iTime(_Symbol, PERIOD_M1, shift-1); // candle mais recente if(start <= 0 || end <= 0 || end <= start) return 0.0; long up=0, down=0; GetMarketAggressions(start, end, up, down); long tot = up + down; if(tot <= 0) return 0.0; return (double)(up - down) / (double)tot; } int OnInit() { lastBarTime = iTime(_Symbol, PERIOD_M1, 0); if(!OpenCsv()) return INIT_FAILED; return INIT_SUCCEEDED; } void OnDeinit(const int reason) { if(h != INVALID_HANDLE) FileClose(h); } void OnTick() { if(!IsNewBarM1()) return; // Precisamos de barras suficientes: shift até (CorrWindow + 5 + 1) int bars = Bars(_Symbol, PERIOD_M1); if(bars < CorrWindow + 10) return; int win = CorrWindow; double x[]; double y1[], y3[], y5[]; ArrayResize(x, win); ArrayResize(y1, win); ArrayResize(y3, win); ArrayResize(y5, win); // Vamos construir pares (OFI_t, ret_{t+k}) usando candles fechados // Usamos shifts: // t = shift (k+1 .. k+win) // ret_{t+k} = Close(shift-k) - Close(shift) // Ex: k=1: ret = Close(shift-1) - Close(shift) int n_used1=0, n_used3=0, n_used5=0; int nz1=0, nz3=0, nz5=0; for(int i=0; i<win; i++) { int shift = (win + 5) - i; // garante que shift-5 >= 1 double ofi = CalcOFI_ForClosedBar(shift); x[i] = ofi; double c0 = iClose(_Symbol, PERIOD_M1, shift); double r1 = iClose(_Symbol, PERIOD_M1, shift-1) - c0; double r3 = iClose(_Symbol, PERIOD_M1, shift-3) - c0; double r5 = iClose(_Symbol, PERIOD_M1, shift-5) - c0; // em pontos (WIN: tick=5 pontos, _Point=1 no índice) r1 /= _Point; r3 /= _Point; r5 /= _Point; y1[i] = r1; y3[i] = r3; y5[i] = r5; // contagem de não-zero (retorno realmente se moveu) if(MathAbs(r1) > 0.0001) nz1++; if(MathAbs(r3) > 0.0001) nz3++; if(MathAbs(r5) > 0.0001) nz5++; } double corr1 = Corr(x, y1, win); double corr3 = Corr(x, y3, win); double corr5 = Corr(x, y5, win); double hit1 = HitRate(x, y1, win); double hit3 = HitRate(x, y3, win); double hit5 = HitRate(x, y5, win); double pct_nz1 = 100.0 * (double)nz1 / (double)win; double pct_nz3 = 100.0 * (double)nz3 / (double)win; double pct_nz5 = 100.0 * (double)nz5 / (double)win; // log if(h != INVALID_HANDLE) { FileWrite( h, TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS), _Symbol, win, win, corr1, corr3, corr5, hit1, hit3, hit5, pct_nz1, pct_nz3, pct_nz5 ); bars_since_flush++; if(bars_since_flush >= FlushEveryBars) { FileFlush(h); bars_since_flush = 0; } } if(PrintEachBar) { PrintFormat("OFI_CorrAudit | win=%d | corr: r1=%.4f r3=%.4f r5=%.4f | hit: r1=%.3f r3=%.3f r5=%.3f | nz%%: %.1f %.1f %.1f", win, corr1, corr3, corr5, hit1, hit3, hit5, pct_nz1, pct_nz3, pct_nz5); } }
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso
Eu quero um robot nível institucional e não um bot de varejo só baseado em OHLC. Perdi um tempão desenvolvendo um robot todo em microestrutura. Mas hoje chegou a resposta da corretora sobre a confiança dos dados :(
Bom dia Andre,Em relação aos questionamentos sobre a integridade e granularidade do feed disponibilizado no ambiente MetaTrader 5, esclarecemos que a plataforma opera com dados consolidados recebidos do servidor da corretora, e não com o fluxo bruto nativo da B3.
É importante destacar que, no MT5, o conceito de tick não é equivalente ao conceito de negócio divulgado pela B3. Enquanto a B3 contabiliza exclusivamente os negócios efetivamente executados, o MT5 registra todas as atualizações recebidas do servidor, incluindo alterações de BID, ASK, LAST, variações de preço e eventos consolidados. Dessa forma, divergências entre a quantidade de ticks observada na plataforma e o número de negócios oficiais da B3 são esperadas.
Adicionalmente, especialmente em ativos de alta frequência como o WDO e o WIN, é prática comum que o servidor consolide múltiplos negócios executados em um único evento antes de transmiti-los à plataforma, com o objetivo de otimizar o tráfego de dados e preservar a estabilidade e performance do ambiente. Nesses casos, um único tick no MT5 pode representar mais de um negócio ocorrido no mercado. Esse comportamento é estrutural e inerente ao modelo de distribuição de dados utilizado.
Da mesma forma, o ambiente não disponibiliza o fluxo microestrutural bruto da B3 (tick-by-tick individualizado com identificação nativa de agressor), mas sim dados tratados e consolidados conforme o padrão de integração da plataforma. Assim, estratégias que dependam da reconstrução fiel do order flow original da bolsa devem considerar essa limitação estrutural.
Em resumo, o ambiente MT5 é plenamente adequado para estratégias baseadas em variações de preço, volume e dinâmica de mercado dentro do modelo de dados consolidados disponibilizado. Entretanto, por definição técnica da arquitetura da plataforma e do modelo de distribuição de dados, ele não replica integralmente o fluxo bruto e individualizado de negócios da B3.
Qualquer dúvida, estamos à disposição.
Atenciosamente,
Antes, já estava analisando os dados do dataset que o EA gerou, mas não consigo obter padrões que se repetem devido aos dados não confiáveis.
Estou para desistir de vez, a frustração é enorme. Alguém tem alguma solução para isso ou a plataforma definitivamente não permite esse tipo de abordagem?