ADR-46 — Class balancing obligatoire en training binaire¶
Status: active (backfilled 2026-04-24 from prior references)
Date: 2026-04-24 (backfill) · originally implied by code changes around Sprint 10–11
Introduced by: backfill under #637 (Phase 2 of #593)
Supersedes: —
Context¶
En mission F1=0.75 binaire, la distribution empirique des labels triple-barrier sur le timeframe 15m / H4 est approximativement 70 % HOLD · 15 % BUY · 15 % SELL (cf. documentation/architecture/TRAINING_PIPELINE.md §Problem Diagnosis). Sans rééquilibrage, le modèle apprend à prédire HOLD par défaut (minimise la cross-entropy globale) et sous-pondère la classe BUY.
Résultats observés sans class balancing (CVN_CLASS_BALANCING=0) :
- BUY recall ≈ 0.25 — 75 % des opportunités ratées
- f1_buy collapse vers le baseline naïf (const_F1)
- Probabilités non calibrées pour le downstream (threshold calibrator, meta-label)
ADR-28 fixe le mode binaire ; ADR-29 impose la baseline naïve ; cet ADR complète les deux en rendant explicite l'obligation du rééquilibrage.
Decision¶
Le flag CVN_CLASS_BALANCING vaut 1 dans la base_env FTF (PostgreSQL ftf_config + config/ftf_baseline.json + Helm values-prod.yaml) — c'est la valeur effective dans tous les environnements de production, training, et tuning. L'implémentation utilise sklearn.utils.class_weight.compute_class_weight(class_weight="balanced", ...) puis passe les poids au trainer (XGBoost sample_weight, LightGBM sample_weight, CatBoost class_weights).
Note sur le défaut Python : _is_class_balancing_enabled() lit os.getenv("CVN_CLASS_BALANCING", "0") (src/training/XGBoost/cvntrade_XGBoost_trainer.py:20) — le fallback "0" n'est PAS le défaut opérationnel ; c'est un fail-safe : si un trainer s'exécute hors du contexte FTF/Airflow (où base_env est injecté), il tourne en mode plus sûr (pas de poids de classe non-désirés sur un dataset arbitraire). En production, CVN_CLASS_BALANCING=1 est toujours injecté en amont.
Opt-out explicite uniquement via factor FTF class_balancing avec la variante OFF (cf. src/commun/finetune/ablation_matrix.py:338) — pour A/B testing en ablation, jamais comme baseline.
Invariants¶
- Invariant 1 :
base_env["CVN_CLASS_BALANCING"] == "1"dansftf_configET dansconfig/ftf_baseline.jsonET dansinfra/helm/airflow/values-prod.yaml— divergence entre ces trois sources viole ADR-46. - Invariant 2 : les trois trainers (XGBoost, LightGBM, CatBoost) consomment les poids de classe quand
CVN_CLASS_BALANCING=1. Un trainer qui ignore silencieusement le flag viole ADR-25 (pas de fallback silencieux). - Invariant 3 : tout run avec
CVN_CLASS_BALANCING=0DOIT être taguéclass_balancing=disableddans MLflow + ne PEUT pas être promu en production (ADR-42).
Alternatives rejected¶
- Oversampling SMOTE : augmente la variance sur les labels triple-barrier (corrélés temporellement). Rejetée — sklearn class_weight est moins invasif et préserve la distribution temporelle.
- Focal loss : change la surface d'optimisation — non supporté nativement par XGBoost/LightGBM/CatBoost en binaire, nécessite custom objective. Gardé en backlog si class_weight insuffisant.
- Sur-échantillonnage de la classe minoritaire : biais le train set, fausse la perplexité HPO. Rejetée pour les mêmes raisons que SMOTE.
- Laisser le modèle apprendre la distribution native : cf. Context — f1_buy ≈ const_F1, mission impossible.
Consequences¶
- Positive : BUY recall remonte (+0.10 f1_buy mesuré sur Fix B du Sprint 11 diagnostic), probabilités mieux étalées → seuil calibré plus robuste.
- Negative : trois trainers à maintenir en parité sur le flag. Un nouveau trainer doit implémenter l'injection du
sample_weightéquilibré pour être éligible à la FTF (gardé par un guardrail à venir). - Neutral : le choix
balancedpar scikit-learn assume une distribution équilibrée post-poids ; d'autres schémas (carrés inverses, effective number of samples) restent explorables via un futur factor.
Rollback¶
- Toggle
CVN_CLASS_BALANCING=0dansftf_configvia Console (ADR-59). - Ou variant FTF
class_balancing=disabledpour ablation ponctuelle. - Aucune purge de cache requise ; l'impact est sur la prochaine run.
References¶
- Parent need:
CVN-N001(F1 mission, #608) - Related ADRs: ADR-25, ADR-28, ADR-29, ADR-42, ADR-59
- Code:
src/training/XGBoost/cvntrade_XGBoost_trainer.py,src/commun/finetune/ablation_matrix.py(factorclass_balancing) - Docs:
documentation/architecture/TRAINING_PIPELINE.md§Class balancing