Compra a 84,120. Stop em 84,036, objetivo em 84,271. Chega a vela do minuto seguinte: abertura 84,118, máximo 84,290, mínimo 84,010, fecho 84,240.
Os dois níveis estão dentro dessa vela. O stop foi tocado e o objetivo foi tocado, mas os quatro valores OHLC não contêm absolutamente nenhuma informação sobre qual foi atingido primeiro. Ainda assim, o teu backtest devolveu um número. Algures no ciclo, uma linha decidiu — provavelmente uma linha que não consideraste uma suposição de modelação quando a escreveste.
Esta é a maior fonte isolada de desempenho fictício que vejo em estratégias de horizonte curto, à frente das comissões e do slippage, porque não parece uma suposição. Parece apenas código de suporte.
Primeira forma de errar: deixar a cadeia de if decidir
A estrutura habitual:
if bar.high >= target:
exit(target, "tp")
elif bar.low <= stop:
exit(stop, "sl")
Ninguém escolheu «resolver primeiro os objetivos». A verificação do objetivo foi escrita primeiro porque esse é o caminho feliz e era nisso que estavas a pensar. Troca a ordem dos dois ramos e a curva de capital muda; isso, por si só, devia dizer-te que o P&L da estratégia depende em parte do teu editor.
Testei um scalper de reversão à média deliberadamente banal no perp BTCUSDT, com três meses de velas de 1 minuto, 4,812 operações, stop a 0.10% e objetivo a 0.18% da entrada. Os mesmos sinais, as mesmas comissões; só mudou a convenção para desempatar.
Um oitavo das operações sustenta o resultado inteiro. É essa a matemática de um par apertado de stop e objetivo: as operações ambíguas são aquelas em que o preço foi nos dois sentidos, ou seja, a maioria das interessantes, e cada uma vale a distância total entre o stop e o objetivo, dependendo do lançamento da moeda. 12.6% das operações × 0.28% de amplitude correspondem a 3.5% do volume nominal bruto por unidade da amostra, muito mais do que a verdadeira vantagem da estratégia.
A taxa depende do tamanho da vela em relação ao espaçamento entre os níveis, e piora rapidamente à medida que aumentas a duração das velas. Mesmo stop e objetivo, mesmos sinais, reamostrados:
| Intervalo da vela | Operações com os dois níveis dentro de uma vela | Sharpe (objetivo primeiro) |
|---|---|---|
| 1s | 0.3% | 0.91 |
| 1m | 12.6% | 2.31 |
| 5m | 34% | 3.60 |
| 15m | 49% | 4.42 |
| 1h | 71% | 5.88 |
Repara no que essa tabela está realmente a dizer. Velas mais grosseiras melhoraram o backtest. O instinto de qualquer investigador é achar que as velas horárias são a opção conservadora: menos ruído, menos sobreajuste à microestrutura. Mas, com uma convenção intrabar que resolve a teu favor, uma vela mais grosseira é apenas uma caixa maior dentro da qual podes presumir que tiveste sorte. Com velas de 1h, sete em cada dez operações dependem inteiramente da convenção. Esse backtest não está a testar uma estratégia; está a testar a ordem de duas instruções `if` 3,400 vezes.
Segunda forma de errar: presumir que o stop foi executado ao preço do stop
Digamos que corriges a ordem. O stop é resolvido primeiro, registas uma perda de exatamente 0.10% mais a comissão taker e sentes que fizeste tudo com rigor. Ainda há duas coisas erradas.
A primeira é que um stop é um gatilho, não uma execução. Na Binance USDⓈ-M, uma ordem STOP_MARKET transforma-se numa ordem de mercado assim que a condição de ativação é satisfeita, e depois consome o que houver no livro de ordens. Num minuto calmo, isso significa um ou dois ticks de slippage. No minuto que efetivamente ativou o teu stop — aquele com uma vela de 40 pontos e uma cascata de liquidações por baixo — o livro está escasso precisamente do lado em que estás a executar. Na minha amostra, ao comparar as ativações dos stops com os dados tick a tick, a execução mediana ficou 1.4 bps além do gatilho e o percentil 95 ficou a 11 bps. Num stop de 10 bps, a cauda custa-te mais um décimo do risco que julgavas ter definido.
A segunda é mais subtil e específica dos perps: qual é o preço que ativa a ordem. Por predefinição, a Binance usa o mark price para as ordens stop, e o mark price é calculado a partir do índice mais uma base suavizada, não a partir da última transação nessa plataforma. A tua série OHLCV usa o último preço. São séries diferentes e divergem mais precisamente durante os eventos que ativam os stops.
| Último preço (as tuas klines) | Mark price (gatilho predefinido) | |
|---|---|---|
| Origem | transações nesta plataforma | índice de várias plataformas + base |
| Comportamento das mechas | excursão completa | fortemente atenuado |
| Divergência típica | 1–3 bps em períodos calmos, 20–35 bps num minuto de cascata | |
| Consequência para o backtest | stops ativados que não deviam ter sido, e vice-versa | |
Assim, uma mecha de 25 bps na série do último preço faz o backtest fechar a posição pelo stop, embora o mark price em tempo real nunca se tenha aproximado a menos de 10 bps do teu gatilho. Ou acontece o inverso, no dia em que o índice se move e a tua plataforma fica para trás. Se definires workingType como CONTRACT_PRICE, pelo menos alinhas o comportamento em tempo real com os teus dados, o que costuma ser a escolha certa para quem faz investigação, já que simular corretamente um gatilho baseado no mark price implica manter uma segunda série em todo o teu motor de execução.
Lembro-me melhor deste caso: alguém da nossa equipa «melhorou» uma estratégia ao passar o take-profit de 0.18% para 0.21%. O Sharpe subiu de 2.3 para 3.1. Não surgiu nenhuma vantagem nova. O objetivo limitou-se a sair da parte mais densa da distribuição das mechas de 1 minuto, pelo que menos operações caíram na categoria ambígua em que o código lhes atribuía discretamente a vitória. Tinham otimizado o critério de desempate.
Terceira forma de errar: presumir sempre o pior e chamar-lhe conservadorismo
A reação instintiva é optar pelo pessimismo. Se os dois níveis forem tocados, assume o stop. Feito, já não há otimismo; siga.
Eu costumava fazer isto. É melhor do que a alternativa e continua errado, por duas razões.
Elimina estratégias que estão bem. Resolver pessimisticamente 12.6% das operações custou a esta estratégia 2.1 pontos de Sharpe em comparação com os 0.94 resolvidos com dados tick a tick. Se o valor verdadeiro for 0.94 e a tua convenção indicar 0.18, deitas fora a ideia e vais trabalhar em algo pior. Um conservadorismo que falha por dois pontos de Sharpe não é conservadorismo; é ruído com pose moral.
Pior ainda, distorce a otimização. Dá a um varrimento de parâmetros um critério de desempate pessimista e o otimizador aprende a evitar a ambiguidade, porque agora a ambiguidade é apenas uma penalização. Vai procurar stops largos e objetivos próximos, ou velas mais lentas em que os dois níveis raramente coincidem, e apresenta-te parâmetros escolhidos em função da tua convenção de execução, em vez do mercado. É a mesma falha da versão generosa, com sinal oposto, e igualmente invisível no relatório de desempenho.
Regra prática que usamos antes de tudo: se stop_distance + target_distance for inferior à amplitude do percentil 75 do teu intervalo de velas, a tua suposição intrabar pesa mais no P&L do que o teu sinal. Calcula os dois valores. Leva quatro linhas de código e já encerrou mais revisões de estratégias do que qualquer outra verificação isolada.
O que funciona na prática
O percurso dentro da vela é um dado. Vai buscá-lo ou delimita o que não consegues obter.
- Resolve com a série mais granular de que dispões. Os aggTrades da Binance para os minutos relevantes são algumas centenas de linhas e resolvem a questão de vez: qual foi o primeiro nível tocado e a que preço foi executada a varrida. Não precisas de dados tick a tick para todo o backtest, apenas para as velas ambíguas. Na minha amostra, isso correspondeu a 606 minutos em 129,600. É um pequeno volume de dados para descarregar, não um projeto de infraestrutura.
- Se não tiveres dados tick a tick, desce um ou dois níveis de periodicidade apenas para resolver as saídas. Sinais em 15m, saídas resolvidas com velas de 1s ou 1m. A ambiguidade cai de 49% para uma fração de um por cento, e o que resta é suficientemente pouco para ser ignorado com honestidade.
- Apresenta sempre o intervalo de resultados. Executa cada backtest duas vezes, com resolução otimista e pessimista, e mostra ambos os valores de Sharpe ao lado do valor resolvido. Essa amplitude representa a tua incerteza intrabar e deve constar do relatório de desempenho, junto do intervalo de confiança que apresentarias para o próprio Sharpe. Quando o intervalo vai de 0.2 a 2.3, nenhuma conclusão dentro dele é real.
- Acompanha a taxa de ambiguidade como métrica de primeira linha. A nossa aparece no topo de cada ficha de estratégia, junto do número de operações e do volume transacionado. Uma taxa superior a cerca de 5% significa que o que está a ser testado é a lógica de saída, não a lógica de entrada.
- Modela o gatilho separadamente da execução. Usa como gatilho a série de preços que a plataforma realmente utiliza; executa ao preço do gatilho mais um valor de slippage sorteado e calibrado com os dados de mercado, não ao preço do gatilho.
Nas ações, o mesmo problema aparece com outra roupagem. Um stop a 62.00 numa ação que abre em gap a 58.40 durante a noite não é executado a 62.00; é executado algures abaixo do preço de abertura. Um backtest com velas diárias que regista −$0.00 de slippage nos gaps vai dizer-te, sem hesitar, que uma camada de stop-loss melhorou a tua redução máxima. Não melhorou. Simplesmente nunca foi testada nos dias que contam. As suspensões de negociação também causam isto: é na leilão de reabertura que o stop é efetivamente executado, a um preço que o mínimo da vela nunca mostra.
Nada disto é exótico. É reconhecer que uma vela é um resumo, e que uma estratégia de stop e objetivo aposta na ordem dos acontecimentos que o resumo descartou. Quando o motor de paper trading finalmente executa a estratégia com dados de mercado em tempo real, esses dados têm uma opinião sobre a ordem dos acontecimentos e nunca se importaram com o ramo da instrução if que escreveste primeiro.
← Todos os artigos


