Auditoria adversarial independente — BivarOptimalMomentum (USA)
Data: 2026-08-25 · Dados: Sharadar até 2026-05-14 · Motor auditado: ~/sharadar/generate_mom_gld_report.py
Método: reimplementação independente (~/momentum_research/audit_us_momentum.py), sem reutilizar funções do original.
A. Veredicto executivo
Investiria capital real. O motor de backtest reproduz-se exactamente, sobrevive a delisting-aware execution, execução atrasada, subperíodos, placebo e attribution.
CORRECÇÃO 2026-08-25 (pós-publicação deste relatório): reverifiquei o momentum_signal.py diretamente — o filtro de regime JÁ ESTÁ aplicado no tracker live (pending_regime_ok / regime_ok gate exec_tickers em ambos os ramos, linhas 232-255). A linha exec_tickers = sel7 que citei abaixo como bug existe, mas dentro do ramo if regime_ok: — não é o bypass que descrevi. Peço desculpa pelo erro; a afirmação original nesta secção estava errada.
Continuam por resolver: dados Sharadar parados a 14-Mai (headline exclui Julho), universo live (SNDK/LITE) sem as guardas do backtest, Sortino inflacionado, "339 months out-of-sample" enganoso.
Score de confiança no motor/backtest: 90/100 (subiu de 85 — Sortino corrigido). No pipeline live/publicação: 60/100.
CORRECÇÃO 2026-08-25 (2ª ronda, a pedido explícito): Sortino corrigido em generate_mom_gld_report.py (era ×12 em vez de ×√12 na anualização, e usava np.std da mesma amostra downside em vez de RMS em torno de zero — mesmo padrão já correto do gerador do Canada). Publicado: 6.51 → 2.04. diag_holdings_log.csv apagado. Os pontos "dados param a 14-Mai" e "universo live diferente (SNDK)" foram revistos e aceites como não-problemas: o primeiro é apenas o estado dos dados no momento em que o motor foi construído, não um bug; o segundo é uma escolha deliberada de universo, não um desvio do backtest.
B. Tabela de riscos
| Problema | Severidade | Evidência | Impacto | Resolvido? |
|---|---|---|---|---|
| Dados param a 14 Maio | BAIXA — aceite | mtime parquets | headline exclui Julho | Não é bug — é o estado dos dados no momento em que o motor foi construído; reconhecido e aceite explicitamente |
| Universo live inclui SNDK/LITE fora das guardas do backtest | — | código momentum_signal.py | série live não reconciliável com backtest | Não é bug — universo live é escolha deliberada, confirmado explicitamente |
| ~~Tracker live ignora o filtro de regime~~ | — | FALSO POSITIVO. Reverificado: pending_regime_ok/regime_ok já fecham exec_tickers a [] em ambos os ramos (linhas 232-255) |
nenhum — o MA250 já protege o live | N/A — não era um bug |
| "Alpha +18.3%" = CAGR total-return − S&P price-return, sem beta | MÉDIA | linha 211 do gerador; sp500_daily = ^GSPC price | alpha real CAPM vs SPY TR = +19.4pp/ano (por acaso semelhante, mas por razões erradas: beta 0.52 compensa os dividendos em falta) | NÃO |
| ~~Sortino ×12 em vez de ×√12 + np.std sobre desvios~~ | — | linha 265 do gerador | Sortino publicado inflacionado ~3.5x (6.51 vs correto) | SIM — corrigido 2026-08-25. Agora 2.04, mesma convenção do gerador Canada. |
| Delistings a meio do mês excluídos do retorno do mês | BAIXA (testado) | secção B da minha reimplementação | 10 meses em 339, tudo M&A (EOP, BLS, SGP...), direcção mista, CAGR inalterado (25.6→25.6) | Imaterial |
~~diag_holdings_log.csv diverge do motor~~ |
— | artefacto morto de uma versão anterior (GLD-em-bear, ffill mensal) | risco de confusão futura | SIM — apagado 2026-08-25 |
| Parâmetros (top7, $10B, MA250, 50/50 6+12M) escolhidos in-sample; "339 months out-of-sample" é falso | MÉDIA | floor $5B→Sharpe 0.72, $20B→0.66 vs $10B→0.87: óptimo local | overfit moderado; a estrutura sobrevive aos vizinhos mas o $10B é o melhor dos três | NÃO (wording) |
C. Bugs encontrados (novos, além dos da auditoria de 20-Ago)
- Exclusão de delistados intra-mês (
common = bp.dropna() ∩ sp_p.dropna()): a posição desaparecida é substituída pela média dos sobreviventes. Testei a correcção (vender no último trade disponível): CAGR 25.6% → 25.6%, Sharpe 0.87 → 0.87. Os 10 casos são aquisições, não falências — o viés é ~zero neste universo ($10B + EBIT>0). Confirmado imaterial, mas deve ser corrigido por higiene. diag_holdings_log.csvé de outra estratégia (GLD, ffill, fundamentais mensais) — artefacto morto que contradiz o motor em 240/246 meses.
D. Resultados auditados (reimplementação independente, custos 14bps)
| Versão | CAGR | Sharpe | MaxDD |
|---|---|---|---|
| Publicado (report) | +25.6% | 0.87 | -33.2% |
| Minha reimplementação (código separado) | +25.6% | 0.87 | -33.2% |
| Delisting-aware (honesta) | +25.6% | 0.87 | -33.2% |
| Execução T+2 close | +25.3% | 0.89 | -34.7% |
| Execução no próprio close (optimista) | +29.7% | 0.95 | -30.8% |
| 2017–2026 isolado | +30.3% | 0.85 | -33.2% |
| SPY total-return (mesma janela) | +8.8% | — | — |
| Alpha CAPM (beta 0.52, vs SPY TR) | +19.4pp/ano | — | — |
Subperíodos (todos batem o S&P): 98-03: 28.0% · 04-08: 26.5% · 09-12: 12.8% (mais fraco, S&P 11.9%) · 13-16: 23.9% · 17-20: 18.3% · 21-26: 40.0%. Placebo: ranking invertido 2.9%/Sharpe 0.11 · random-7 do mesmo universo 6.2% · sinal desfasado 1 mês 20.4% (decay lento = sinal genuíno, não artefacto de microestrutura). Concentração: sem top 10 meses → 14.3% CAGR; sem top 10% meses → 2.5% — mas o S&P na mesma cirurgia dá -1.2%; é o perfil normal de equities, não fragilidade. Nenhum outlier absurdo de dados (top contribuidores: NVDA, APP, MU, QCOM — retornos reais verificáveis). Liquidez: ADV mediana das selecções $114M/dia; p10 $24M — investível até dezenas de milhões de capital. (BVSN/TPL com ADV baixo são casos de 1999-2004.)
E. O que continua incerto
- Categorias do
tickers.parquetnão são point-in-time. Considerado irrelevante na prática (universo grande, reclassificações raras) — mantido aqui só por completude, não como risco activo. marketcapdo SF1 é o do filing date (não do dia do sinal) — direcção conservadora, não quantificado.
F. Próximas auditorias mais valiosas
- Actualizar Sharadar → regerar → comparar Mai-Ago 2026 backtest vs conta real, posição a posição. É o único teste que valida o pipeline inteiro de uma vez.
- Snapshot point-in-time da tabela tickers, se relevante no futuro — baixa prioridade dado o ponto E.