Às 10:07 de uma terça-feira, nosso agente de pesquisa sinalizou uma configuração de alta de curto prazo em um ETF de índice. O sinal do ativo-objeto era comum: o preço havia recuperado sua média de 20 barras, a volatilidade realizada estava diminuindo e havia uma divulgação macroeconômica programada para o pregão seguinte. A operação proposta era uma trava de alta com calls e cerca de duas semanas até o vencimento.
O backtest gostou da operação. A estratégia em paper não a executou. Quando comparamos as ordens, os horários dos sinais coincidiam e os preços do ativo-objeto diferiam por poucos centavos. Os contratos eram diferentes: o backtest havia comprado o vencimento de sexta-feira que parecia melhor na cadeia histórica; o processo em paper selecionou o próximo vencimento listado, uma semana depois.
Essa diferença veio de uma junção de calendário. Tratamos “dias até o vencimento” como uma propriedade de uma data e, em seguida, selecionamos o contrato cujo vencimento ficava mais perto da meta. Nos dados históricos, o calendário de vencimentos incluía uma listagem semanal que havia sido adicionada depois da data que estávamos testando. O backtest usou um contrato do futuro de um jeito que parecia uma conveniência inofensiva.
Primeiro, suspeitamos de um erro de fuso horário. Datas de vencimento de opções são especialmente propensas a isso: o pregão termina em Nova York, os horários das bolsas podem ser armazenados em UTC e uma data à meia-noite pode mudar quando um pipeline faz a conversão de tipo sem cuidado. Conferimos. Os horários estavam corretos. O contrato simplesmente não existia no universo histórico naquela terça-feira.
Essa foi a primeira lição: uma cadeia histórica de opções precisa de um universo de listagens point-in-time. A data de vencimento de um contrato informa quando ele termina. Não informa quando foi listado, quando passou a ser negociável nem se o sistema poderia tê-lo selecionado naquele momento.
Reconstruímos a etapa de seleção com base nas informações disponíveis no horário da decisão. Para cada contrato candidato, exigimos um horário de listagem anterior ou igual ao da decisão, uma cotação válida com idade dentro do nosso limite de atualização e um pregão no qual a estratégia pudesse enviar uma ordem. Também registramos o conjunto de candidatos antes de classificá-los. Esse último ponto fez diferença, porque o backtest antigo guardava apenas o vencedor; não podíamos saber se havia comparado cinco contratos elegíveis ou escolhido discretamente em um calendário que ainda nem existia.
Em seguida, examinamos a cotação usada para precificar a trava. O backtest havia usado a cotação mais recente de cada perna de forma independente. O ask da call era das 10:06:58; o bid da call vendida era das 10:04:11. Ambas ainda eram recentes de acordo com a política geral de dados, mas o mercado havia se movido nesse intervalo. Um preço médio da trava calculado com pernas assíncronas pode descrever um mercado que nunca existiu.
Mudamos a regra de cotação para exigir que os horários das duas pernas estivessem separados por no máximo 2 segundos e para rejeitar mercados cruzados ou com tamanho zero. Isso reduziu o número de observações históricas utilizáveis. Também tornou as marcações restantes mais confiáveis. Quando não havia uma cotação sincronizada, marcávamos o candidato como indisponível, em vez de preencher a lacuna com um preço defasado.
Depois veio a regra de classificação. O agente havia escolhido o vencimento com o menor custo de spread cotado. Parece razoável até a trava ter apenas 1 contrato de tamanho e a cotação estar defasada. Explicitamos o objetivo: maximizar o retorno esperado por unidade de risco definido entre os contratos que passam pelos critérios mínimos de tamanho da cotação, atualização e listagem. Neste exemplo, o vencimento elegível mais próximo tinha uma largura cotada de $0.42 entre as duas pernas, enquanto o vencimento posterior tinha uma largura de $0.31. A opção aparentemente mais barata também tinha apenas 1 contrato exibido na perna vendida.
| Candidato | Vencimento disponível naquele momento? | Idade da cotação | Tamanho exibido na perna vendida | Decisão |
|---|---|---|---|---|
| Sexta-feira semanal | Não | Não se aplica | Não se aplica | Excluir |
| Sexta-feira seguinte | Sim | 1.4 segundos | 18 contratos | Elegível |
| Terceira sexta-feira | Sim | 9.8 segundos | 1 contrato | Excluir por cotação defasada e baixa liquidez |
Reproduzimos o incidente com essas regras. Agora o backtest escolheu o mesmo vencimento que a estratégia em paper e registrou por que os outros contratos foram rejeitados. A entrada na trava ficou pior que a execução histórica original em $0.11. Isso não significava que a estratégia não tinha valor; significava que o número de desempenho antigo incluía uma seleção de operação que o processo em produção não conseguiria reproduzir.
Um pequeno inconveniente: os formatos de símbolos das bolsas fazem erros de vencimento parecerem problemas de qualidade de dados. Encontramos um código semanal de um fornecedor que diferia do código mensal por um caractere. Ainda mantenho o símbolo bruto ao lado da chave de contrato normalizada nos logs de auditoria. Não é elegante, mas já nos impediu de confiar depressa demais em uma junção arrumadinha.
Se você está montando um backtest de opções, salve o conjunto completo de candidatos em cada decisão, junto com o status de listagem, os horários das cotações, os tamanhos e o motivo da rejeição de cada contrato. Depois, faça da seleção de contratos uma função determinística desse snapshot. A escolha do vencimento faz parte da estratégia. Se o backtest consegue enxergar um contrato que a estratégia em paper não conseguia, a comparação dos sinais começa depois que a resposta já mudou.
← Todos os artigos


