Você me mandou o notebook na semana passada: mesmo sinal, mesmo universo, os mesmos 14 meses de dados do perp BTCUSDT. A única mudança foi na execução. Você parou de cruzar o spread e passou a deixar bids passivos 1 tick dentro do spread. O Sharpe saltou de 0.42 para 2.14. Você perguntou se isso era real ou se tinha quebrado alguma coisa.
Você quebrou alguma coisa. Quero mostrar exatamente onde, porque o bug está em uma linha, mas a lição por trás dela é bem maior.
Aqui está sua regra de execução, copiada do seu motor:
if bar.low <= limit_price: fill(limit_price)
Isso diz: se houve uma negociação no mercado a um preço igual ou inferior ao meu durante esse minuto, minha ordem foi executada no meu preço. Na prática, o que isso codifica é que o preço tocou seu nível. Tocar não é executar. Entre as duas coisas há uma fila, e você modelou a fila como se estivesse vazia.
O que está na sua frente
Quando você coloca um bid em 84,120.0 no perp BTCUSDT, entra no fim de tudo que já está descansando naquele preço. No melhor nível do book desse contrato, isso costuma ficar entre 4 e 30 BTC, dependendo do horário; a mediana na janela da sua amostra é de cerca de 12 BTC. Sua ordem é de 0.4 BTC. Para negociar, os vendedores precisam bater nesse nível com volume agregado suficiente para tirar da frente todo mundo que chegou antes de você, e precisam fazer isso antes que o nível seja cancelado ou que o mercado suba e se afaste.
Então a pergunta que seu backtest deveria fazer não é “o preço chegou a 84,120.0?”, mas “foram executados pelo menos 12.4 BTC em vendas a mercado a 84,120.0 enquanto minha ordem estava lá?”. São eventos muito diferentes. Nos seus dados, a diferença é de cerca de 3 vezes.
3 regras de execução, 3 estratégias diferentes
Rodei seu sinal de novo com as mesmas entradas e 3 modelos de execução. Mesmo alpha, mesmas taxas, mesmo funding. Só a lógica de execução mudou.
| Regra de execução | Execuções | Vantagem média em 60s após a execução | Sharpe |
|---|---|---|---|
Toque: low <= limit | 4,180 | +2.6 bp | 2.14 |
Penetração estrita: low < limit - 1 tick | 1,712 | +0.4 bp | 0.61 |
| Simulação de fila com volume no nível | 1,306 | +0.9 bp | 0.77 |
A linha do meio é a correção simplista que todo mundo tenta primeiro: só contabilizar uma execução se o mercado negociou estritamente além do seu preço, partindo da ideia de que, se passou do seu preço, deve ter consumido sua ordem. A direção está certa e elimina a maior parte da fantasia. Mas também introduz um viés pesado, que já explico.
A linha de baixo é o modelo que você deveria construir. Não precisa de feed L3 nem de reconstrução ordem por ordem. Você já tem quase tudo de que precisa.
Uma simulação de fila que você consegue montar com aggTrades
Use o fluxo de negócios agregados em vez das klines. Cada negócio traz preço, quantidade, timestamp e a flag do lado maker, que indica se o agressor estava comprando ou vendendo. Isso basta para uma simulação razoável da sua própria ordem passiva:
- Quando sua ordem entrar no book, registre o volume parado no seu preço. Se você só tiver um feed do book a cada 100ms, use o último snapshot; o erro é pequeno perto do que você está corrigindo.
- Defina
queue_ahead = resting_size. Seja pessimista e presuma que você está no fim da fila. É onde você fica, a menos que seja você quem criou o nível. - Percorra o fluxo de negócios. Cada negócio com agressor vendedor, a um preço igual ou inferior ao seu, reduz
queue_aheadpelo volume negociado. Quando o valor ficar negativo, sua ordem é executada no seu preço naquele timestamp. - Se o preço se afastar 1 tick, não zere a fila. Reduza-a gradualmente. Algumas ordens à sua frente são canceladas quando o nível fica defasado; outras permanecem. Uma redução de 30-40% por cada segundo inteiro fora do melhor preço se ajustou melhor às minhas reconstruções do que qualquer um dos extremos.
- Se sua estratégia cancelaria a ordem e a enviaria de novo, modele o reenvio como uma ordem nova no fim da fila do novo nível. É essa etapa que as pessoas pulam, e é aí que se esconde o resto da fantasia.
É na etapa 2 que você vai querer contestar o que estou dizendo. Sim, às vezes você fica perto do início da fila, porque enviou a ordem assim que o nível se formou. Tudo bem: meça isso, não presuma. Registre o volume parado no momento do envio e deixe os dados mostrarem qual fração das suas ordens realmente chega cedo. Na sua estratégia, foram 11%, porque o sinal dispara depois de um movimento. Isso significa que o nível em que você entra já existe e já tem gente na fila.
As execuções que você consegue são justamente as que preferia não ter tido
Agora vem a parte que realmente importa, e o motivo pelo qual a regra de penetração estrita tem viés.
Pense em quando seu bid é totalmente consumido. Isso acontece quando a pressão vendedora é forte o bastante para consumir o nível inteiro. Por definição, é o momento em que o mercado está caindo e atravessando seu preço. As execuções de que você tem mais certeza são aquelas em que sua posição fica imediatamente no prejuízo.
Separe as execuções pelo modo como aconteceram e calcule o mark-out em 60 segundos:
| Tipo de execução | Parcela das execuções | Mark-out em 60s |
|---|---|---|
| Negociou no nível, preço subiu em seguida | 38% | +3.1 bp |
| Negociou no nível, preço ficou estável | 21% | +0.2 bp |
| O preço passou 2+ ticks | 41% | −2.4 bp |
Seu backtest ingênuo incluiu os 3 grupos e tratou todos como se fossem de graça. A regra de penetração estrita dá quase exclusivamente o terceiro grupo, por isso sua vantagem caiu ainda mais do que na simulação de fila. Nenhuma das duas está certa. A simulação de fila dá uma combinação realista, e a combinação é o que importa: a execução passiva captura o spread, mas devolve parte disso em seleção adversa. A proporção entre os dois é a sua estratégia de verdade.
A velha perspectiva da mesa de ações ainda se aplica: uma ordem maker é uma opção gratuita que você vendeu ao mercado. Alguém a exerce quando vale a pena. Seu backtest embolsava o prêmio e esquecia que a opção também podia gerar uma perda.
As negociações que você não conseguiu mudam a estratégia, não apenas o custo
Este é o ponto que mais quero que você considere com calma. Quando você modela mal a execução taker, consegue as negociações certas pelo preço errado, e corrigir as taxas resolve a maior parte do problema. Quando modela mal a execução maker, consegue um conjunto de negociações totalmente diferente. Cerca de 2,900 das suas 4,180 entradas nunca aconteceram. Algumas eram seus melhores sinais, em candles que dispararam e depois reverteram — exatamente o tipo de movimento em que o mercado foi embora sem você.
Então a lógica para os casos sem execução precisa ser realista. O que a estratégia faz quando a entrada não é executada antes de o sinal perder a validade? Corre atrás com uma ordem taker e paga o spread mais o impacto? Envia outra ordem mais abaixo e aceita uma base de entrada diferente? Pula a negociação e fica de fora? Cada escolha produz uma curva de patrimônio bem diferente, e nenhuma delas equivale a “presuma que a ordem foi executada”. Nas nossas simulações, incluir uma regra honesta para correr atrás do preço — cruzar o spread após 20 segundos sem execução, com slippage limitado a 3 bp — recuperou cerca de 1/3 das negociações perdidas e aproximadamente metade da diferença entre os Sharpes ingênuo e da simulação de fila. Um resultado realmente interessante, que só aparece quando o modelo de execução é realista o bastante para que a pergunta faça sentido.
Teste rápido, 10 minutos: pegue seu registro de paper trading ao vivo e compare com o backtest na mesma janela. Compare a taxa de execução, não o PnL. Se o backtest executa 100% das ordens passivas e o paper trading, 34%, você não está comparando estratégias: está diante de um bug no modelo de execução. Corrija isso antes de olhar para qualquer número de retorno.
Mais 2 coisas rápidas antes de terminar
Rejeições post-only. Se você usa post-only para garantir a faixa de taxas maker e o book se move entre sua decisão e a confirmação da corretora, a ordem é rejeitada em vez de ficar no book. Nos nossos registros de paper trading, isso acontece em 3-6% das tentativas no BTCUSDT em horários normais e passa de 15% no minuto seguinte à divulgação do CPI dos EUA. Uma ordem rejeitada não foi executada, mas também não é uma ordem passiva à espera de execução: é uma negociação que nunca existiu. Se o seu backtest não representa esse estado, a contagem de negociações fica inflada justamente nos cenários que mais importam para você.
Prevenção de self-trade e sua própria pegada. Com 0.4 BTC, você não mexe no BTCUSDT, então pode ignorar isso. Mas você comentou que queria testar a estratégia em um perp de altcoin de capitalização média, em que o valor nocional no melhor nível do book costuma ser inferior a $15k. Nesse mercado, sua ordem é uma parcela relevante da fila, e o volume que você registra à frente já inclui o efeito da presença da sua última ordem. Quando seu tamanho ultrapassa cerca de 10% do volume parado no nível, a simulação precisa levar em conta que os outros participantes reagem a você. E, falando com franqueza, a partir daí eu confiaria mais no paper trading do que em qualquer simulação que você ou eu possamos escrever.
Rode tudo de novo com a simulação de fila e me envie a tabela de mark-out dividida por tipo de execução. Se o grupo de repique ainda concentrar a maior parte da vantagem e o grupo de penetração não consumir tudo, talvez você tenha algo que valha a pena testar em paper trading. Se tudo dependia das 2,900 execuções que você nunca teria conseguido, melhor descobrir agora do que passar 4 semanas vendo a conta de paper trading não fazer o que o notebook prometeu.
← Todos os artigos


