Pular para conteúdo

ADR-001: CPCV em vez de Walk-Forward simples

Status: ✅ Accepted (2026-04)

Contexto

Na época da v1.x usávamos Walk-Forward sequencial (WF):

[train 1500 bars][gap 10][val 300 bars]  →  fold 1
                [train 1500 bars][gap 10][val 300 bars]  →  fold 2
                ...

Total: 5 folds → 5 estimativas de AUC por modelo.

Problemas:

  • Variância alta em 5 pontos não permite calcular percentil confiável
  • Overfit ao último fold (mais recente, viés temporal)
  • Underutilização dos dados (cada barra é vista em 1 train, 1 val)
  • Não captura distribuição de performance sob diferentes ordens

Decisão

Adotar CPCV — Combinatorial Purged Cross-Validation (Lopez de Prado, AFML 2018, cap. 12).

# configs/model.yaml
walk_forward:
  use_cpcv: true
  cpcv_n_splits: 6    # divide série em 6 grupos
  cpcv_n_test:   2    # 2 grupos viram teste em cada combinação

Com 6 splits e 2 grupos de teste:

$$ \binom{6}{2} = 15 \text{ combinações de paths} $$

Cada modelo tem 15 estimativas AUC/PF em vez de 5. Permite:

  • AUC mediana + AUC p5 (5º percentil) — robust statistics
  • Purge automático entre train e test (evita lookahead)
  • Embargo após cada bloco (evita autocorrelation contamination)

Gate na promoção

promotion:
  min_auc:    0.58        # mediana das 15 paths
  min_auc_p5: 0.55        # 5º percentil das 15 paths (robusto)

Um modelo só passa se a mediana for forte E o pior caso for aceitável.

Alternativas consideradas

A. Walk-Forward com mais folds (10+)

Por que não: sequencial = ainda 1 estimativa por fold = mesmo problema. Mais folds só dispersa dados.

B. K-Fold tradicional shuffled

Por que não: quebra série temporal. Look-ahead severo.

C. Time Series Split (sklearn)

Por que não: equivalente a WF sequencial. Mesmas limitações.

D. CPCV completo (Lopez de Prado original)

Considerei cpcv_n_splits: 10 com cpcv_n_test: 4 → 210 paths. Trade-off:

  • Pros: estatística super-robusta
  • Cons: treino 14× mais lento (210/15)

Escolhi balance: 15 paths é bom e treina em tempo razoável (~4h para 56 modelos).

Consequências

Positivas

  • 71/72 modelos aprovados com gates de v2.27 (98.6%)
  • Distribuição de AUC visível (média 0.61, p5 0.57)
  • Auto-rollback CUSUM funciona melhor com baseline rico
  • Permite ECE percentil também

Negativas

  • Treino mais lento (3x walk-forward simples)
  • Implementação mais complexa em app/models/cv.py
  • Memory footprint maior durante CV (mantém 15 estados)

Mitigações

  • Paralelização: cada combinação em thread separada
  • Cache de features (computadas 1× e reusadas)
  • LightGBM early_stopping_rounds: 50 corta CV cedo se overfit

Referências