Backtests - italostatonato/stock-screener-automation GitHub Wiki

Histórico retroativo e backtest

Revisado em 15/09/2026. Os comandos deste guia são executados separadamente; não integram o workflow semanal de coleta. Faça backup de data/ antes de reprocessar. As curvas teóricas do dashboard têm premissas próprias, descritas no guia do dashboard.

Separação obrigatória

O histórico observado e o reconstruído têm naturezas diferentes e nunca são gravados no mesmo lake como se fossem equivalentes.

  • OBSERVADO: ranking realmente salvo/publicado pelo robô.
  • SIMULADO_POINT_IN_TIME: ranking reconstituído posteriormente com dados cuja data de disponibilização era anterior ou igual à data do sinal.

Essa distinção evita transformar um backfill em evidência de uma decisão que não foi realmente tomada no passado.

Frequência dos registros

A rotina atual coleta semanalmente e aceita execuções manuais. O histórico observado reúne as datas efetivamente registradas; não pressupõe um snapshot por dia. O backtest legado rebalanceia a cada snapshot disponível. Séries de preços podem conter pregões diários, e o point-in-time de FIIs usa sinais mensais. Essas frequências não alteram o cron semanal do screener.

Histórico observado

python scripts/build_observed_history.py
python scripts/run_observed_backtest.py

Fontes, em ordem de precedência por data e classe: snapshots do lake, JSONs já publicados no dashboard e Excels locais. O manifesto registra completude e origem.

Saídas principais:

data/backtest/observed_portfolios.parquet
data/backtest/observed_history_manifest.json
data/backtest/observed_backtest_periods.parquet
data/backtest/observed_backtest_summary.json
data/backtest/observed_curve_fii.parquet
data/backtest/observed_curve_acao.parquet

Backfill point-in-time de FIIs

python scripts/build_point_in_time_history.py --start 2021-01-01
python scripts/run_point_in_time_backtest.py

O sinal mensal usa o último pregão do mês. Fundamentos vêm do Informe Mensal de FII da CVM, preservando data de referência, data de entrega e versão. Preço, liquidez e volatilidade de formação do sinal vêm do COTAHIST oficial da B3. Os arquivos brutos e a fundação pesada são cache local ignorado pelo Git; hashes e metadados ficam no manifesto versionável.

O manifesto identifica a versão fii_v1_legacy_selection. A construção chama as funções de score e seleção disponíveis no código atual, mas não dispõe de todos os sete indicadores da coleta atual. Indicadores ausentes recebem score neutro e o filtro de DY pode usar o dividendo mensal. Portanto, não apresente esse backfill como reprodução exata da estratégia atual com todos os fundamentos históricos disponíveis.

Os snapshots ficam em data/point_in_time/snapshots/YYYY-MM-DD/. Carteiras com menos de 20 ativos são marcadas como parciais e ficam fora do backtest principal. Elas podem ser incluídas apenas para sensibilidade com --include-partial.

Regras do motor

  • sinal calculado no fechamento de D e entrada no próximo pregão;
  • carteira equal weight;
  • preço ajustado para retorno e benchmark;
  • custo de 10 bps multiplicado pelo turnover, configurável por CLI;
  • curva inicia em 100 antes do primeiro retorno, incluindo a primeira perda no drawdown;
  • benchmark ausente em qualquer período deixa seu acumulado e alpha indisponíveis;
  • ativo sem preço vira caixa, sem ser removido silenciosamente da média;
  • cobertura, tickers ausentes, preços defasados e turnover são salvos por período;
  • turnover usa a troca de pesos-alvo entre composições; não inclui ajuste por deriva dos pesos causada pela oscilação dos preços dentro de um período;
  • datas com carteira incompleta são excluídas por padrão.

O benchmark de FIIs usa XFIX11 como proxy de IFIX. O custo de 10 bps corresponde a 0,10% multiplicado pelo turnover; não é uma cobrança fixa de 10% nem inclui toda a tributação de uma carteira real.

Execução offline e parâmetros

--offline existe em build_point_in_time_history.py, run_observed_backtest.py e run_point_in_time_backtest.py. Ele reutiliza arquivos/cache existentes; uma primeira execução sem os dados necessários precisa obtê-los antes. O consolidado observado não exige esse parâmetro.

python scripts/build_point_in_time_history.py --start 2021-01-01 --offline
python scripts/run_observed_backtest.py --offline --end-date 2026-09-06 --transaction-cost-bps 10
python scripts/run_point_in_time_backtest.py --offline --end-date 2026-09-06 --transaction-cost-bps 10

A data acima é um exemplo reproduzível, não um corte atualizado automaticamente. --include-partial pertence ao backtest point-in-time e deve ficar identificado como análise de sensibilidade.

Limitações atuais

O backtest point-in-time de FIIs usa Yahoo Finance para retorno ajustado. Tickers renomeados ou extintos não têm cobertura uniforme, portanto o resumo deve ser tratado como preliminar quando a cobertura média estiver baixa. O COTAHIST da B3 é completo para negociação, mas seus preços não são ajustados por proventos. Por isso o script também gera uma curva diagnóstica fii_b3_price_only_curve.parquet: ela mede cobertura e retorno de preço, mas não substitui uma série de retorno total.

O retroativo de ações ainda não é publicado. Os demonstrativos ITR/DFP da CVM têm data de recebimento adequada para point-in-time, mas falta fechar um cadastro histórico auditável ticker–CNPJ, eventos societários e métricas equivalentes às da estratégia atual (incluindo EBITDA e dividendos). Até essa fundação existir, criar rankings históricos de ações produziria viés de sobrevivência e falsa precisão.

Leitura dos resultados

data/point_in_time/backtest/fii_summary.json traz lado a lado a visão ajustada do Yahoo e o diagnóstico de preço da B3. Para auditoria, use fii_periods.parquet e fii_b3_price_only_periods.parquet: cobertura e tickers ausentes estão explícitos. Não compare o resultado sintético com o histórico observado como se ambos fossem decisões reais; use o primeiro para pesquisa e o segundo para acompanhamento fora da amostra.