From daea33355516f105a8f0abbf19e6fc7766c845e2 Mon Sep 17 00:00:00 2001 From: Tika Date: Sun, 8 Mar 2026 22:22:27 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20LabelGenerator=20=5Fclassify=5Fbar=20?= =?UTF-8?q?=E2=80=94=20simulation=20LONG/SHORT=20ind=C3=A9pendante?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug : quand le SL LONG était touché, la fonction retournait 0 (NEUTRAL) immédiatement sans évaluer les conditions SHORT. Résultat : 0 labels SHORT sur 12230 barres, modèle inutilisable pour signaux SHORT. Fix : deux boucles indépendantes (LONG et SHORT) qui évaluent chacune leur propre TP/SL. Si les deux gagnent, priorité au premier résolu. Co-Authored-By: Claude Sonnet 4.6 --- src/ml/features/label_generator.py | 71 +++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 21 deletions(-) diff --git a/src/ml/features/label_generator.py b/src/ml/features/label_generator.py index 3bee559..bb6b05c 100644 --- a/src/ml/features/label_generator.py +++ b/src/ml/features/label_generator.py @@ -140,30 +140,59 @@ class LabelGenerator: sl_short: float, ) -> int: """ - Parcourt les barres futures bar par bar et retourne le label. - Vérifie HIGH pour TP LONG et LOW pour SL LONG (et inversement pour SHORT). + Simule LONG et SHORT de façon indépendante sur les barres futures. + + LONG et SHORT sont deux trades hypothétiques distincts : le SL du LONG + (prix baisse) ne signifie pas que le SL du SHORT (prix monte) est touché. + Les deux simulations sont donc parcourues séparément pour éviter de + manquer les signaux SHORT quand le prix descend. + + Retourne le label du trade gagnant qui se résout en premier : + 1 (LONG), -1 (SHORT) ou 0 (NEUTRAL). """ - for _, bar in future.iterrows(): - # LONG : TP atteint ? - if bar['high'] >= tp_long and bar['low'] > sl_long: - return 1 - # LONG : SL atteint en premier ? - if bar['low'] <= sl_long: - # Vérifie si TP atteint le même bar (candle ambiguë) - if bar['high'] >= tp_long: - return 0 # Ambigu → neutre - return 0 # SL touché → pas de LONG + # --- Simulation LONG indépendante --- + long_win_idx = None + long_lose_idx = None + for idx, (_, bar) in enumerate(future.iterrows()): + tp_hit = bar['high'] >= tp_long + sl_hit = bar['low'] <= sl_long + if tp_hit and sl_hit: + long_lose_idx = idx # Barre ambiguë → perte + break + if tp_hit: + long_win_idx = idx + break + if sl_hit: + long_lose_idx = idx + break - # SHORT : TP atteint ? - if bar['low'] <= tp_short and bar['high'] < sl_short: - return -1 - # SHORT : SL atteint en premier ? - if bar['high'] >= sl_short: - if bar['low'] <= tp_short: - return 0 - return 0 + # --- Simulation SHORT indépendante --- + short_win_idx = None + short_lose_idx = None + for idx, (_, bar) in enumerate(future.iterrows()): + tp_hit = bar['low'] <= tp_short + sl_hit = bar['high'] >= sl_short + if tp_hit and sl_hit: + short_lose_idx = idx # Barre ambiguë → perte + break + if tp_hit: + short_win_idx = idx + break + if sl_hit: + short_lose_idx = idx + break - return 0 # Ni TP ni SL atteint dans l'horizon + long_won = long_win_idx is not None + short_won = short_win_idx is not None + + if long_won and not short_won: + return 1 + if short_won and not long_won: + return -1 + if long_won and short_won: + # Les deux trades seraient gagnants : prendre celui qui se résout en premier + return 1 if long_win_idx <= short_win_idx else -1 + return 0 # Aucun TP atteint dans l'horizon @staticmethod def _log_distribution(labels: pd.Series) -> None: