← Voltar aos artigos
June 27, 2026
5 min read

Look-Ahead Bias: como um erro de uma vela fabrica um Sharpe de 15 a partir de puro ruído

Look-Ahead Bias: como um erro de uma vela fabrica um Sharpe de 15 a partir de puro ruído
#algotrading
#backtest
#look-ahead bias
#data leakage
#overfitting
#validation

Parte da série "Backtests Without Illusions".

📄 Este artigo se transformou em um paper de pesquisa. Três fugas sutis de look-ahead são submetidas a um teste controlado contra uma verdade fundamental conhecida (4.000 históricos simulados). Leia o paper online (versão interativa + PDF) em lookahead.marketmaker.cc, código e dados em github.com/suenot/lookahead-inflation.

Há algumas semanas, nosso benchmark de busca de parâmetros estava mentindo para nós, e quase não percebemos.

O motor parecia limpo. Lógica de vela fechada, uma divisão walk-forward rolante honesta, uma busca Sobol/QMC sobre o espaço de parâmetros, uma janela de teste reservada. A busca encontrou configurações que pareciam boas in-sample. O único problema: fora da amostra, quase tudo era negativo. Assumimos que a estratégia era simplesmente fraca.

Então encontramos uma linha. O sinal era decidido no fechamento da vela i, mas a execução era registrada na mesma vela i em vez de na abertura da vela seguinte. Um erro de índice de um (off-by-one) no índice de execução. Movemos a execução para open[i+1] — o único preço pelo qual você poderia realmente transacionar depois de ver o fechamento da vela i — e o resultado fora da amostra inverteu o sinal. A busca Sobol passou de prejuízo para lucro. Nada na estratégia havia mudado. Simplesmente paramos de negociar no passado.

Isso é look-ahead bias, e a parte inquietante é o quão pequeno foi o erro e o quão grande foi a consequência. Este artigo é uma autoauditoria controlada: construímos um simulador em que a verdade fundamental é conhecida por construção, injetamos as fugas sutis uma de cada vez e medimos exatamente o quanto cada uma infla o backtest. A conclusão principal: com nenhum edge real, uma execução na mesma vela fabrica um Sharpe anualizado de +14,8 a partir de puro ruído.

O que é realmente o look-ahead bias

The three places look-ahead hides — execution, normalization, and indicators — as channels of unequal danger feeding one trading decision

Look-ahead bias é qualquer ponto no seu pipeline em que uma decisão ou medição usa informação que não estaria disponível, em tempo real, no momento em que é usada. Os exemplos de livro-texto são grosseiros — usar o lucro anual completo de uma ação em janeiro, ou uma reformulação que ainda não havia sido publicada. Esses são fáceis de identificar. Os que sobrevivem a uma revisão de código são sutis, e se escondem em três lugares:

  1. Execução — você decide na vela i e executa na vela i (ou usa a máxima/mínima da vela i para stops na mesma vela que gerou o sinal). Você negocia a um preço que está correlacionado com o que te acionou.
  2. Normalização — você calcula z-score, min-max, ou de outra forma escala uma feature usando estatísticas calculadas sobre toda a série, incluindo o futuro. O scaler "conhece" o conjunto de teste.
  3. Indicadores / features — você suaviza ou filtra com uma janela que é centralizada (ou que de alguma forma olha para frente), de modo que o valor na vela i já contém um pedaço da vela i+1.

Todas as três são formas do que a literatura de machine learning chama de leakage: a contaminação de treino/avaliação com informação do futuro do alvo (Kaufman et al., 2012; Kapoor & Narayanan, 2023). Em finanças, o tratamento canônico é Advances in Financial Machine Learning de López de Prado (2018) — validação cruzada purgada, embargo, os perigos do backtesting. A disciplina point-in-time remonta pelo menos a Fama & French (1992), que deliberadamente atrasam os dados contábeis em seis meses para que a variável seja conhecida antes do retorno que ela explica.

A pergunta que este artigo responde é quantitativa: não "o leakage é ruim" (todos concordam), mas "quantos pontos de Sharpe cada forma proporciona, e quais são perigosas?" Sem um número, você não consegue raciocinar sobre isso. Você não consegue saber se uma inflação de +0,3 é ruído ou se uma inflação de +14 é uma prova irrefutável.

Um simulador com verdade fundamental conhecida

A controlled synthetic market with a known edge dial: a null world with no real edge beside an edge world whose equity genuinely rises

Para medir a inflação, você precisa conhecer a verdade. Dados reais nunca revelam a verdade — eles dão uma realização e nenhum oráculo. Então construímos um mercado sintético em que nós definimos o edge.

O processo gerador de dados é estritamente causal e não explosivo:

gt=ϕgt1+1ϕ2  ut,utN(0,1)g_t = \phi\, g_{t-1} + \sqrt{1-\phi^2}\; u_t, \qquad u_t \sim \mathcal{N}(0,1)

rt=agt1+σεt,εtN(0,1)r_t = a\, g_{t-1} + \sigma\, \varepsilon_t, \qquad \varepsilon_t \sim \mathcal{N}(0,1)

Aqui gtg_t é uma deriva latente persistente exógena (um AR(1) com ϕ=0.95\phi = 0.95), e o retorno da vela rtr_t tem uma pequena deriva agt1a\,g_{t-1} que é conhecida uma vela à frente. Como gg não depende de retornos passados, não há realimentação e nada explode. O parâmetro aa é o botão que controla quanto edge real existe:

  • a=0a = 0 — o nulo: nenhum edge. Qualquer Sharpe positivo do backtest é 100% artefato.
  • a>0a > 0 — um edge real e negociável: uma regra de momentum honesta realmente ganha dinheiro.

A estratégia é deliberadamente simples — uma regra de sinal de momentum. A feature é a soma dos retornos das últimas LL velas (L=24L = 24 velas), e a posição é seu sinal:

csum = np.concatenate(([0.0], np.cumsum(r)))      # csum[k] = sum r[0..k-1]
mom = np.full(n, np.nan)
tt = np.arange(L - 1, n)
mom[tt] = csum[tt + 1] - csum[tt - L + 1]

signal = np.sign(mom)                              # position for the next bar

Essa feature de momentum é o veículo perfeito para estudar a fuga de mesma vela, porque tem uma propriedade que os indicadores reais compartilham: ela contém mecanicamente a vela atual. mom[t] inclui r[t]. Então, se você registra r[t] como sua operação, está apostando em parte em uma quantidade que já está dentro do seu próprio sinal. Essa é a fuga, tornada concreta.

Configuração: σ=0.01\sigma = 0.01 (1% de volatilidade por vela), uma taxa unidirecional de 0,00045 (ida e volta 0,09%, correspondendo ao nosso motor), Sharpe anualizado por 8760\sqrt{8760} (velas horárias), 4.000 históricos independentes de 4.000 velas cada. Tudo é semeado (seeded) e determinístico.

O pipeline honesto (o único negociável)

Decidir no fechamento da vela t, ganhar o retorno da vela seguinte, pagar taxas nas mudanças de posição:

def sharpe(sig, ret_booked):
    dpos = np.abs(np.diff(np.concatenate(([0.0], sig))))
    pnl  = sig * ret_booked - FEE_ONEWAY * dpos
    return pnl.mean() / pnl.std() * np.sqrt(8760)

honest = sharpe(signal[idx], r[idx + 1])           # earn r[t+1]: tradable

As três fugas, cada uma uma única mudança cirúrgica

same_bar  = sharpe(signal[idx], r[idx])

z_full    = (mom - mom[valid].mean()) / mom[valid].std()
norm_full = sharpe(np.sign(z_full[idx]), r[idx + 1])

z_sm      = (mom[:-2] + mom[1:-1] + mom[2:]) / 3.0   # uses t-1, t, t+1
indicator = sharpe(np.sign(z_sm[idx]), r[idx + 1])

Cada fuga está a uma linha de distância do pipeline honesto. Esse é o ponto central: não são erros exóticos, são o tipo de coisa que passa por uma revisão de código.

Resultados: a magnitude de cada fuga

Pure market noise funneled through a same-bar leak into a soaring, fake equity curve and a performance gauge pinned near its maximum

Executado em 4.000 sementes (seeds), aqui está o Sharpe anualizado que cada pipeline reporta, sob o nulo (sem edge) e sob um edge real (a=0,0011a = 0,0011, ajustado para que o Sharpe honesto seja um crível +1,57):

Pipeline Nulo (sem edge) Edge real
Honesto (a verdade) −0,74 +1,57
Execução na mesma vela +14,79 +15,85
Espiada de indicador (1 vela) +4,76 +6,62
Normalização de toda a série −0,84 +1,46

Os intervalos de confiança de 95% entre as sementes são de ±0,05 ou mais estreitos em cada célula; os testes t pareados sobre a inflação são astronomicamente significativos onde o efeito é real (t > 400, p ≈ 0).

Leia primeiro a coluna nula, porque é o experimento mais limpo possível: não há edge, então o pipeline honesto perde dinheiro corretamente (−0,74, o atrito de pagar taxas para negociar ruído). Agora veja o que as fugas fazem com esse mesmo nada:

  • Execução na mesma vela: −0,74 → +14,79. Uma estratégia sem nenhum poder preditivo, negociando ruído aleatório, reporta um Sharpe anualizado de quase 15. Isso não é um viés sutil; é uma fabricação. O mecanismo é exatamente o que construímos: a feature de momentum contém r[t], então registrar r[t] é apostar no próprio sinal.
  • Espiada de indicador: −0,74 → +4,76. Deixar o suavizador ver uma vela à frente no futuro fabrica um Sharpe próximo de 5 a partir de ruído, porque o valor suavizado em t agora se correlaciona com o r[t+1] que você está prestes a ganhar.
  • Normalização de toda a série: −0,74 → −0,84. Praticamente nenhuma inflação. Esse é o resultado honesto e não óbvio (mais sobre isso abaixo).

A coluna de edge entrega a mensagem mais insidiosa. Quando um edge real existe (honesto +1,57), as fugas não apenas somam uma constante — elas empurram o Sharpe medido para +15,85 e +6,62, muito acima do +1,57 que você realmente poderia negociar. Então o número medido não consegue distinguir habilidade de fuga. Um +6 vazado e um +6 honesto parecem idênticos no relatório. Você só descobre qual dos dois tinha depois de ter alocado capital.

A fuga é um gradiente, não um interruptor

The same-bar leak as a smooth dose-response: capturing a larger fraction of the signal bar lifts the equity curve monotonically across the deployable threshold

Uma objeção natural: "registrar a vela de sinal inteira é um erro extremo e irrealista." Então varremos a dose — a fração ff da vela de sinal capturada pela fuga, de 0 (honesto) a 1 (fuga completa na mesma vela):

Fração capturada ff Sharpe nulo Sharpe com edge
0,00 (honesto) −0,74 +1,57
0,25 +3,90 +6,41
0,50 +9,86 +12,20
1,00 (fuga total) +14,79 +15,85

Capturar apenas um quarto da vela de sinal leva uma estratégia sem edge de −0,74 para +3,90. Você não precisa do erro completo de um índice para ser enganado; uma execução ligeiramente favorável demais — um toque de slippage otimista na vela de sinal, um stop intrabar verificado contra a mesma vela que o acionou — é suficiente para superar a maioria dos limiares "implantáveis". A inflação é suave e monotônica em relação a quanto do presente você se permite negociar.

Com que frequência isso coloca uma estratégia perdedora em produção?

O número que deveria preocupar um praticante é a taxa de implantação falsa: com que frequência uma fuga faz uma configuração verdadeiramente perdedora superar o limiar que você usaria para aprová-la. Usando "Sharpe anualizado ≥ 1,0" como critério de implantação, sob o nulo:

  • Execução na mesma vela: 68% das estratégias sem edge parecem implantáveis e realmente perdem dinheiro. Duas em cada três configurações de puro ruído passariam por um portão de Sharpe-≥-1 e perderiam dinheiro ao vivo. (Essa taxa é claramente definida aqui porque a fuga está puramente na execução — o equivalente honesto é o mesmo sinal com uma execução honesta.)
  • Espiada de indicador: empurra praticamente toda configuração sem edge acima do limiar de implantação também (99,9% superam Sharpe ≥ 1) — levaria ruído diretamente para produção.
  • Normalização de toda a série: 12% superam o limiar — essencialmente a taxa base de ruído, sem prêmio de leakage.

A taxonomia, e como detectar cada uma

As três fugas não são igualmente perigosas, e as diferenças são instrutivas.

1. Leakage de execução (a cara)

The same-bar fill off-by-one: an execution arrow curling back into the bar that generated the signal, versus the honest fill on the next bar's open

Sintoma: o preço de execução está correlacionado com o sinal porque vêm da mesma vela. Magnitude: enorme (+15 a partir de ruído em dose completa, +3,9 em um quarto de dose). Por que é a pior: seu sinal é, quase por definição, construído a partir da ação de preço recente, então o retorno da vela de sinal é exatamente aquilo com que sua feature está mais correlacionada. Registrá-lo é quase o mesmo que consultar a resposta.

Detecção — o teste de deslocamento de uma vela. Este é o diagnóstico mais valioso deste artigo. Pegue seu backtest e desloque cada execução uma vela mais tarde (decida em i, execute em open[i+1]). Se o resultado mal se mover, sua execução era honesta. Se o resultado desmoronar ou inverter o sinal, você estava negociando no passado. Foi exatamente isso que aconteceu com nossa busca Sobol: desloque as execuções, e um OOS "lucrativo" se revelou uma perda — ou melhor, a relação real emergiu assim que a fuga foi removida.

entry_price = open_[i + 1]      # NOT close[i], NOT open[i]

2. Leakage de indicador / feature (a silenciosa)

Sintoma: um indicador na vela i depende de dados de i+1 ou posteriores — uma média móvel centralizada, um filtro sem atraso causal, um rótulo de pico/vale que precisa de velas futuras para confirmar, uma transformação tipo Heikin-Ashi alimentada com candles futuros. Magnitude: grande (+4,8 a partir de ruído). Por que se esconde: a fuga está enterrada dentro de uma chamada de biblioteca. scipy.signal.filtfilt é de fase zero — e fase zero significa não causal. Uma feature "esta vela é um máximo local" é incognoscível até que a próxima vela seja impressa.

Detecção: para cada indicador, pergunte: qual é o maior índice que ele lê? Se calcular o valor em t alguma vez tocar t+1, ele é não causal. Calcule indicadores sobre uma janela causal expansiva/rolante e verifique que o valor na vela t é idêntico independentemente de existirem ou não velas após t no array. (Nossas implementações de HMA/ADX passam neste teste: cada saída em t lê apenas entradas em ≤ t.)

3. Leakage de normalização (a específica do canal)

Sintoma: um scaler (StandardScaler, min-max, um z-score global) é ajustado em todo o conjunto de dados, teste incluído. Os avisos canônicos de ML são explícitos sobre isso — o §7.10.2 de Elements of Statistical Learning de Hastie, Tibshirani & Friedman ("the wrong and right way to do cross-validation"), e o próprio guia de armadilhas comuns do scikit-learn: "the average should be the average of the train subset, not the average of all the data."

Magnitude em nosso teste:zero (−0,74 → −0,84). Este é o resultado surpreendente e honesto, e vale a pena entender em vez de decorar.

Por que não inflou? Porque nossa estratégia usa a feature apenas através do seu sinal (um limiar zero). O escalonamento por desvio padrão nunca muda um sinal, e a centralização pela média global apenas desloca ligeiramente o cruzamento por zero. Então a padronização de toda a série de uma regra de sinal pura é quase inócua.

Não generalize demais isso. O leakage de normalização é específico do canal. No momento em que sua estratégia usa a magnitude da feature — dimensionamento de posição proporcional a um z-score, um limiar de entrada diferente de zero escolhido observando a distribuição escalonada, uma rede neural que consome entradas padronizadas —, o scaler ciente do futuro começa a importar, e importa mais quanto mais as estatísticas globais diferirem das causais. Nosso resultado não é "o leakage de normalização é seguro." É "a magnitude do leakage depende do canal pelo qual a quantidade vazada entra na decisão, e você deve medi-la em vez de presumi-la." Uma regra de sinal é o único caso em que essa fuga específica é barata.

Onde isso se conecta

Look-ahead bias é o primeiro elo de uma cadeia que esta série vem documentando:

  • Corrompe a entrada da validação. Um backtest vazado navegará facilmente por uma divisão walk-forward e parecerá um platô amplo em vez de um pico de overfitting — a fuga é consistente entre os folds, então a validação cruzada não consegue capturá-la. O leakage é o modo de falha anterior ao overfitting, e nenhuma validação honesta posterior vai te salvar.
  • Interage com a busca de parâmetros: uma busca de milhares de trials sobre dados vazados encontrará a configuração que explora a fuga mais agressivamente. O "vencedor" é o pior infrator.
  • É por isso que a paridade backtest-live diverge. Uma fuga é a explicação mais clara para uma lacuna de 30–50% entre o backtest e o bot, porque o trading ao vivo é, mecanicamente, o único lugar onde você não pode espiar.

A disciplina que captura tudo isso é a mesma que a literatura acadêmica vem defendendo há anos: trate um backtest como um experimento estatístico com uma fronteira de informação estrita. Bailey, Borwein, López de Prado & Zhu mostraram com que facilidade o overfitting fabrica desempenho falso (2014); o protocolo de backtesting de Arnott, Harvey & Markowitz (2019) codifica essa higiene. Look-ahead bias é a fronteira mais básica de todas — a fronteira no tempo — e a mais barata de violar por acidente.

Conclusões

The one-bar shift diagnostic: nudging every fill one bar later deflates a fake soaring curve back down to the honest truth

  1. O look-ahead bias é quantitativamente enorme e qualitativamente invisível. Um único erro de execução de uma vela transformou um Sharpe de −0,74 (puro ruído, perdendo corretamente) em +14,79. O erro é uma linha; a consequência é um histórico fabricado.
  2. É um gradiente. Capturar apenas 25% da vela de sinal já produz +3,90 do nada. Você não precisa de um bug flagrante — um pouco de otimismo demais em suas execuções basta.
  3. O número medido não consegue distinguir habilidade de fuga. Quando um edge real existe, as fugas inflam o relatório muito além da verdade negociável. A única defesa é o processo, não a métrica.
  4. O teste de deslocamento de uma vela é seu diagnóstico mais rápido. Desloque cada execução uma vela mais tarde. Se o desempenho desmoronar, você estava negociando no passado.
  5. A magnitude do leakage é específica do canal. As espiadas de execução e indicador são devastadoras; a normalização de toda a série de uma regra de sinal é quase gratuita. Meça a fuga através do canal pelo qual ela realmente entra — não presuma.

O estudo controlado completo — as três fugas, a varredura de dose, a análise de implantação falsa, os métodos formais e cada número reproduzível a partir de um único script determinístico — está no paper complementar em lookahead.marketmaker.cc, com código e dados em github.com/suenot/lookahead-inflation.

A estratégia em nosso experimento nulo não tinha nenhum edge. Ainda assim, mostrou um Sharpe de 15. Se seu backtest parece bom demais, a primeira coisa a suspeitar não é do seu gênio — é do seu relógio.

blog.disclaimer

Authors

Eugen Soloviov
Eugen Soloviov

Trading-systems engineer

Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.

Newsletter

Fique à frente do mercado

Assine nossa newsletter para insights exclusivos sobre trading com IA, análises de mercado e atualizações da plataforma.

Respeitamos sua privacidade. Cancele a inscrição a qualquer momento.