ADR-002: Conformal Prediction como gate final¶
Status: ✅ Accepted (2026-04)
Contexto¶
Em v1.x as predições eram emitidas apenas com base na probabilidade do modelo:
if max(probs) > 0.55:
signal = ["BUY", "SELL", "NO_TRADE"][np.argmax(probs)]
else:
signal = "NO_TRADE"
Problemas:
- Calibração ruim:
P=0.62não significa "62% de chance" se o modelo é mal calibrado (ECE alto) - Sem garantia estatística de cobertura
- Threshold absoluto (0.55) é ad-hoc e varia por símbolo
Decisão¶
Implementar Inductive Conformal Prediction (ICP) com α = 0.15 → cobertura ≥ 85%.
Mecânica¶
- Após treinar modelo, separa subset de calibração (~20% do training)
- Calcula "non-conformity score" para cada amostra de calibração:
$$s_i = 1 - P(y_i | x_i)$$
- Em predição, gera conjunto de classes possíveis (não apenas argmax):
$$\text{set}(x) = { y : 1 - P(y|x) \leq q_{1-\alpha}(s) }$$
- Se set tem 1 classe (singleton) → predição confiável → emite sinal
- Se set tem 2+ classes (incerteza) → emite NO_TRADE
Cobertura garantida¶
Teorema do ICP: P(y_true ∈ set(x)) ≥ 1 - α = 85%.
Isso é distribution-free — não depende do modelo estar bem calibrado.
Alternativas consideradas¶
A. Apenas calibrar com Platt Scaling¶
Por que não: corrige calibração mas não dá garantia de cobertura. Ainda emite predições incertas como se fossem confiáveis.
B. Threshold dinâmico por regime¶
Por que não: ainda dependente do modelo, sem garantia estatística. Foi usado em v1.x e era frágil.
C. Bayesian credible intervals¶
Por que não: modelo gradient-boosted não tem distribuição posterior nativa. Bootstrapping seria oneroso.
D. Conformal Mondrian (α por classe)¶
Considerado: α diferente para BUY/SELL/NO_TRADE. Complica configuração sem ganho prático em backtests.
Consequências¶
Positivas¶
- Cobertura empírica em produção ≈ 87% (validamos com 2 meses de forward data)
- Reduz falsos positivos sem sacrificar muito recall
- Independente de qualidade de calibração do modelo
- Auditável (qualquer um pode verificar cobertura)
Negativas¶
- ~15% de sinais viram NO_TRADE que antes eram BUY/SELL (perdas potenciais)
- Aumenta tempo de inferência (~20%) por causa do quantile lookup
- Calibration set "rouba" data de treino
Mitigação¶
- α = 0.15 (não 0.05 ou 0.10) — balance entre cobertura e taxa de sinais
- Calibration set reciclado a cada retrain (não fica obsoleto)
- Em modelos novos com pouco data, fall-back para threshold absoluto
Por que α = 0.15 (não 0.10)?¶
Backtested 0.05, 0.10, 0.15, 0.20:
| α | Cobertura | Sinais BUY/SELL | PF médio |
|---|---|---|---|
| 0.05 | ~95% | <5% sinais | 1.18 |
| 0.10 | ~91% | 12% sinais | 1.29 |
| 0.15 | ~87% | ~30% sinais | 1.42 |
| 0.20 | ~82% | ~45% sinais | 1.31 (mais ruído) |
0.15 é o sweet spot: cobertura ainda forte + sinais suficientes para o EA operar.
Referências¶
- Shafer & Vovk, A Tutorial on Conformal Prediction, JMLR 2008
- Angelopoulos & Bates, A Gentle Introduction to Conformal Prediction, 2022
- Implementação em
app/models/conformal.py