Skip to content

QC-Py-31 : le « split temporel » est un split par actif, et l'axe du backtest empile 30 actions (mesures non calendaires) #17571

Closed

Description

@jsboige

Constat

QC-Py-31-Transformer-Training.ipynb presente un « split temporel » qui n'en est pas un, et un backtest dont l'axe de temps empile 30 historiques d'actions. Les deux sont pre-existants sur origin/main (bloc identique verifie a la source, pas deduit) ; #17521 est la premiere PR a publier les chiffres du backtest comme des mesures, c'est ce qui les rend visibles.

1. Le « split temporel » est un split par actif

build_sequences (cellule 15) concatene dans l'ordre d'insertion du dictionnaire :

for ticker, df in all_features.items():
    ...
    all_X.append(features[i - seq_len:i])

L'ordre obtenu est donc (actif, date) — jamais (date, actif) — et la fonction ne rend que X, y_reg, y_cls, y_plain : ni le ticker ni la date ne remontent. Le bloc qui suit coupe des tranches contiguës en annoncant l'inverse :

# Split temporel strict (les sequences sont deja ordonnees par date)
X_train = X_all[:train_end]
X_test  = X_all[val_end:]

Mesure (sortie de la cellule 15) : Sequences totales: 81090, Shape X: (81090, 60, 12), soit 30 actions × 2 703 sequences (2 768 seances 2014-2025 moins seq_len + pred_len). Le test (Test: 12164 samples, 15 %) est donc la queue de la concatenation : environ les 4,5 dernieres actions, sur toute la periode, pas une fenetre temporelle sur l'univers.

Consequence : le modele est entraine sur ~25,5 actions et evalue sur ~4,5 autres actions, sur la meme periode. Ce n'est pas un holdout temporel — c'est un holdout d'actifs, et il est annonce comme temporel.

Lecture alternative ecartee, avec la mesure : « 30 actions x ~405 seances » (soit ~1,6 an) ne decrit pas la structure du code — 405 x 30 = 12 150, c'est-a-dire la taille du test, pas une grille. Le perimetre par actif est de 2 703 sequences.

2. Le backtest trie des jours d'une action, pas des actions entre elles

Cellule 40 :

batch_preds = preds_reg[i:i+rebalance_freq]     # 20 echantillons CONSECUTIFS
sorted_indices = np.argsort(batch_preds)
top_indices = sorted_indices[-top_n:]
port_return = batch_realized[top_indices].mean()

Sur un tableau ordonne (actif, date), 20 echantillons consecutifs sont 20 fenetres du meme actif. Le tri porte donc sur des jours d'une action, alors que la docstring annonce « Trier les actions par prediction de rendement ». Le backtest est un timing intra-actif ; la selection transversale decrite n'existe pas.

3. Les metriques sont sur un axe empile, pas calendaire

n_days = max(len(port_returns) * REBALANCE_FREQ, 1)   # 607 x 20 = 12 140
annualized_return = (1 + total_return) ** (252 / n_days) - 1

12 140 « jours » ≈ 48 ans, obtenus en mettant bout a bout les 30 historiques, alors que la fenetre reelle de chaque actif est ~11 ans. Sortie publiee a la tete 23853f70db :

Rendement total:    3852.05%
Rendement annualise: 7.92%
Volatilite:         16.20%
Sharpe Ratio:       0.489
Max Drawdown:       -37.59%

Ces quatre chiffres sont coherents entre eux et faux en echelle : ils decrivent un backtest de 48 ans qui n'a pas eu lieu. Le total (3852 %) est d'ailleurs le genre de nombre qu'un etudiant lit comme un resultat.

Pourquoi c'est un sujet separe, pas une retouche de #17521

#17516 nomme trois defauts (inf/nan, entrainement saute, prose 14 vs 12 features) ; celui-ci n'y figure pas. Le corriger demande de faire remonter (ticker, date) a travers build_sequences puis le split, donc de toucher trois cellules (15, split, 40) et de re-entrainer completement — un second sujet, pas un correctif du premier. En attendant, #17521 declare la limite en prose la ou les chiffres sont imprimes.

Ce qui est attendu

  1. Propager (ticker, date) dans la construction du jeu de sequences, et decouper par date (globale, tous actifs confondus) : le test doit etre une fenetre temporelle sur l'univers, pas une queue d'actifs sous un nom qui dit le contraire.
  2. Backtest transversal : a chaque date de rebalancement, trier les actifs entre eux (cross-section), puis composer les rendements ; metriques sur un axe calendaire.
  3. Si l'intention pedagogique est de rester volontairement simplifie : l'ecrire noir sur blanc a l'endroit du chiffre (axe en echantillons empiles, pas de selection transversale), et cesser de publier un « rendement annualise » calcule sur cet axe.

Critere de fermeture

Une PR mergée dont la tête : (a) ne présente plus un split par actif comme un split temporel ; (b) n'imprime plus de rendement annualisé dérivé d'un axe empilé ; (c) exécute son backtest sur une cross-section par date, ou déclare explicitement la limite là où le chiffre est imprimé.

See #17516 · See #17482 · See #16795

Activity

  1. added
    bugSomething isn't working
    quantconnectQuantConnect strategy development
    on Sep 23, 2026
  2. added a commit that references this issue on Sep 23, 2026
  3. jsboige commented on Sep 23, 2026

    @jsboige
    OwnerAuthor

    [CLAIMED] lane myia-po-2023:CoursIA — corriger le split (par date globale, univers entier) et le backtest (cross-section par date, metriques calendaires) de QC-Py-31, re-entrainement complet -- paths: MyIA.AI.Notebooks/QuantConnect/Python/QC-Py-31-Transformer-Training.ipynb

  4. jsboige commented on Sep 23, 2026

    @jsboige
    OwnerAuthor

    Livraison : PR #17583 (Grain: DEEP/notebook-python — lane myia-po-2023:CoursIA).

    Acceptance : (a) split temporel global par date de cible — train 2014-03→2021-10 (56 790), val →2023-05 (12 150), test 2023-05-16→2024-12-23 (12 150, fenêtre commune aux 30 actifs), assert anti-fuite ; (b) backtest cross-sectionnel (top quintile trié entre actifs à chaque rebalancement, rendements forward 5 j non chevauchants) avec métriques sur axe calendaire et benchmark equal-weight imprimé ; (c) re-entraînement complet from scratch — le .pt committé du 2026-07-02 était entraîné sous l'ancien split fuyard et sautait l'entraînement au chargement ; supprimé et régénéré (10 epochs, early stopping, val_loss 2.1165), papermill 52/52 exit 0.

    Verdict honnête : NO BEATS ajusté du risque — corrélation prédiction/réalisation −0,014 (signal nul), stratégie 116,09 % total / Sharpe 3,008 vs benchmark 61,82 % / Sharpe 3,495 : l'excès de +54 % est du risque de concentration en bull market, pas de la prédiction. Exactement ce que l'ancien protocole maquillait.

  5. added 4 commits that reference this issue on Sep 23, 2026
  6. added a commit that references this issue on Sep 25, 2026
  7. added 3 commits that reference this issue on Sep 25, 2026
  8. added a commit that references this issue on Sep 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingqcquantconnectQuantConnect strategy development

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions