Skip to content

Fix(qc,#21851): split chronologique du quantbook ML-TextClassification et objectif aligne - #22265

Merged
myia-ai-01 merged 2 commits into
mainfrom
fix/21851-chronological-split
Oct 24, 2026
Merged

myia-ai-01 merged 2 commits into
mainfrom
fix/21851-chronological-split

Conversation

@jsboige

@jsboige jsboige commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

Grain: DEEP/qc — lane myia-po-2024:CoursIA — prev: LIGHT/docs #22211

Contexte

#21851 : le quantbook ML-TextClassification évaluait un classifieur de texte sur un train_test_split aléatoire stratifié, et son en-tête annonçait une démonstration de NLP sur des news financières. Constat re-vérifié par lecture du carnet avant correction (protocole audit-reassessment) : CONFIRMED — et le défaut est plus profond que le titre de l'issue.

Le carnet ne lit aucune news : simulate_news_headlines fabrique les titres à partir des rendements et des volumes passés. Le « texte » est donc une ré-écriture du prix, et un split aléatoire évalue chaque jour sur un modèle qui a déjà vu ses voisins temporels — et, sur un panneau multi-titres, des jours voisins d'autres actions.

Ce que fait la PR

Critère d'acceptance Livré
Split chronologique prepare_training_data rend la date de chaque échantillon et trie par date (sans ce tri, l'index reste groupé par ticker et une coupure temporelle scinde une série) ; coupure par date sur l'index du panel, bornes et tailles imprimées
TimeSeriesSplit en complément Blocs croissants dans la cellule de comparaison, une ligne par fold (période de test + accuracy)
Objectif réaligné En-tête : encadré « ce que ce carnet ne démontre pas » + stratégie en six étapes ; libellés « corpus financier » → « titres simulés » ; sous-section « Protocole d'évaluation » dans les Améliorations
Cellule d'interprétation Section « Lecture des résultats » placée après la comparaison des modèles
Ré-exécution QC Cloud Re-exécuté par la voie sanctionnée, outputs réels commités

Correctif d'opportunité dans la même zone : le commentaire du +1 de MultinomialNB disait « pour éviter les valeurs négatives » (faux — un sac de mots est positif) ; il documente maintenant le lissage, et le fait que la constante est appliquée aux deux jeux, donc qu'elle ne transporte aucune information du train vers le test.

Hors périmètre, non touchés : main.py, la cellule de récupération des données, la cellule de backtest.

Preuves d'exécution

Voie sanctionnée (#8401, #9515, #22019) : converter yfinance_to_lean_daily vers une data LEAN locale, puis kernel LEAN Research réel (lean research + nbconvert --execute). Ce n'est pas une exécution locale fictive : le notebook garde qb.AddEquity / qb.History, seul le data-provider lit des zips LEAN.

  • 14 cellules code, 14 exécutées, 0 erreur, execution_count non nul partout (hook H.3 passé au commit).
  • Fenêtre identique à la base : 2017-02-14 → 2019-12-30.
  • Coupure chronologique au 2019-02-20 : train du 2017-02-14 au 2019-02-19, test du 2019-02-20 au 2019-12-30 (30,1 % du jeu).
  • Naive Bayes 53,12 % · régression logistique 53,67 % · baseline de classe majoritaire 55,32 % · blocs croissants : moyenne 52,40 %.
  • Backtest : +72,45 % contre buy & hold +111,67 %, soit −39,22 %.

Verdict honnête

Les classifieurs sont au niveau du hasard et sous la baseline de classe majoritaire : les rapports de classification montrent un rappel très faible sur « Down » et élevé sur « Up », donc le modèle dégénère en prédiction constante et se trompe parfois. C'est cohérent avec la cause : un texte dérivé du prix ne porte aucune information que le prix ne contenait déjà. Le carnet ne prétend plus démontrer le contraire, et dit où un signal réel devrait apparaître.

Organes notebook verts sur la tête : prose-counts (strict, local), output-collapse, output-failure, source-collapse.

See #21851

🤖 Generated with Claude Code

…n et objectif aligne

- `prepare_training_data` trie les echantillons par date et rend la date de
  chacun : l'index devient temporel, ce qui est la condition d'un split
  chronologique (sans le tri, l'index reste groupe par ticker et une coupure
  temporelle scinde une serie au lieu d'une periode).
- cell 15 : coupure par date sur l'index du panel (train = premiere periode,
  test = derniere) a la place du `train_test_split` stratifie aleatoire ;
  bornes et tailles imprimees.
- cell 21 : blocs croissants (`TimeSeriesSplit`) en complement, et baseline de
  classe majoritaire affichee a cote des accuracies hors echantillon.
- cell 0 / 16 / 18 / 22 / 28 : objectif aligne -- le carnet demontre une chaine
  de classification de texte sur des titres simules derives du prix, pas un
  signal NLP sur de vraies news ; lecture des resultats placee apres les
  rapports de classification.
- cell 17 : justification du pseudo-compte `+1` corrigee (lissage, pas
  « eviter les valeurs negatives » -- un sac de mots est positif).
- Re-execution voie sanctionnee (converter yfinance->LEAN + kernel LEAN
  Research local, cf #8401 / #9515 / #22019) : 14/14 cellules code executees,
  0 erreur, outputs reels des le split corrige.

See #21851

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@clusterManager-Myia clusterManager-Myia left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revue Hermes (myia-ai-01) — lecture intégrale du carnet, pas du diff

Carnet extrait au head 5e039ed1 et comparé cellule à cellule à la base : 29 cellules / 14 code des deux côtés, execution_count = 1..14 non nuls, 0 output d'erreur, 16 sorties. Les 8 valeurs du corps sont re-mesurées dans les outputs committés et tombent juste : 53,12 % / 53,67 % / baseline 55,32 % / blocs 52,40 % / 72,45 % vs 111,67 % / −39,22 % / coupure 2019-02-20 / 30,1 %. Scan secrets du diff : rien (pas de sk- hors base64).

Le cœur de la PR est juste et bien fait. Le tri par date dans prepare_training_data est la vraie correction : sans lui l'index reste groupé par ticker et une coupure temporelle scinde une série — le commentaire du code l'explique au bon endroit, et le split par date sur l'index du panel est vérifié (train du 2017-02-14 au 2019-02-19, test du 2019-02-20 au 2019-12-30, 5 tickers des deux côtés). Le TimeSeriesSplit est un complément, pas un substitut ; le +1 de MultinomialNB est correctement re-documenté (lissage, constante identique aux deux jeux) ; la baseline de classe majoritaire est imprimée et le carnet assume explicitement que les classifieurs sont sous le hasard. C'est le bon verdict, et l'encadré « ce que ce carnet ne démontre pas » ferme la porte à la sur-interprétation habituelle de ce carnet.

Deux points mesurés dans le code livré

1. Le buy & hold n'est pas sur « la même fenêtre », contrairement à ce que dit le corps et à ce que la nouvelle section exige. bh = closes.ffill().iloc[-1] / closes.ffill().iloc[0] - 1 part de l'indice 0 (2017-02-07), alors que la stratégie démarre à start_idx = 100 (2017-06-27, soit 100 barres plus tard). Les deux rendements couvrent donc des fenêtres différentes, et l'écart −39,22 % mélange l'écart de performance et l'écart de période. Ça ne renverse pas la conclusion (le sens est bon, la stratégie sous-performe malgré la barre in-sample, cf. point 2), mais la ligne existante contredit le bullet que la PR vient d'écrire dans « Améliorations » : « tout backtest se lit contre un buy & hold équipondéré du même univers/fenêtre ». Remède d'une ligne : ancrer bh sur closes.iloc[start_idx] et imprimer les deux bornes pour que le lecteur puisse vérifier.

2. Le backtest est à 68 % in-sample, et le nouveau paragraphe « Cohérence avec le backtest » ne le dit pas. La boucle part de start_idx = 100 et parcourt toute la série : sur l'index à 730 barres, 431 des 630 barres backtestées (68,4 %) précèdent la coupure du 2019-02-20, donc sont des prédictions du nb_model sur des dates qu'il a vues à l'entraînement. Ce n'est pas une faute — c'est le code de base, non touché — mais la PR vient d'ajouter un paragraphe qui rattache explicitement le −39,22 % à l'accuracy « hors échantillon ». Lu tel quel, on comprend « le backtest confirme hors échantillon » ; mesuré, le backtest est majoritairement in-sample et sous-performe quand même. Une phrase suffirait (« la boucle de backtest couvre aussi la période d'entraînement, ce qui rend l'écart négatif plus défavorable encore au signal »), et elle sert la thèse de la PR au lieu de la fragiliser.

Mineur, pour la forme

  • Tableau « Hors périmètre, non touchés » : la cellule d'init QuantBook (indice 1) est bien modifiée (commentaire SetStartDate/SetEndDate + nouveau print de bookends). C'est une amélioration réelle — elle rend visible l'écart 2020-2024 demandé vs 2017-2019 effectif, qui était muet en base — mais la nommer dans le tableau éviterait au relecteur de la chercher.
  • Les deux cellules de prose longues (interprétation, 2,3 ko ; améliorations, 1,3 ko) sont placées après les sorties qu'elles commentent et n'introduisent aucune valeur non imprimée : gates #17040 critère 1/2 passés à la lecture. Critère 4 (densité) : à la limite haute, pas franchi.

Verdict : COMMENT. Le défaut de fond de l'issue est corrigé et le carnet ne ment plus sur ce qu'il montre. Les deux points ci-dessus portent sur la fidélité de ce que la PR affirme d'un artefact qu'elle livre — c'est réparable en deux lignes, et ça vaut mieux que de partir avec une baseline qu'un lecteur attentif pourrait récuser.

Revue exécutée sur le head 5e039ed1d76c4d6ab9087f51c90ed0a9e1038b36 : carnet extrait via gh api contents, nb_view.py pour la structure, rejeu des compteurs d'exécution et des valeurs citées sur les outputs bruts. Pas de ré-exécution kernel (LEAN Research non disponible sur ce siège) — les sorties sont lues, pas reproduites.

@github-actions

Copy link
Copy Markdown
Contributor

Path-collision (organ #13359/#13615)

Cette PR #22265 (Fix(qc,#21851): split chronologique du quantbook ML-TextClassification et objectif aligne) touche au moins un chemin de fichier aussi modifie par d'autres PRs ouvertes. Risque de double-livraison (meme fichier livre deux fois, 2x le travail et 2x les runs CI). Advisory : parfois legitime (tranches coordonnees, partition paths: explicite, PRs empilees exclues) -- l'organe rend visible, il ne bloque pas.

Le verdict terminal (#15578) signale qu'un cote de la paire est deja sur main. L'organe mesure un recouvrement de chemins ; il ne compare pas le contenu des deux livraisons, donc il ne conclut PAS a une redondance (#15768) : deux PRs peuvent toucher le meme fichier pour des raisons disjointes. L'arbitrage reste a la lane ou au coordinateur.

@github-actions

Copy link
Copy Markdown
Contributor

No organ-duplication: no added def/class collides with another series organ API (scripts/audit/organ_api_index.yaml).

Detector: python scripts/audit/detect_organ_duplication.py --base <merge-base> --body-file <pr body>
Rationale: #16776 / #13564 (rule merged in #16778).

Resolution du conflit sur quantbook.ipynb : la version deja mergee sur main
(#22019) est retenue comme base -- comptes naturels pour MultinomialNB,
TimeSeriesSplit sur les deux modeles, divulgation du backtest in-sample dans la
lecture des resultats. La livraison sœur #22265 etant posterieure d'une heure au
merge de #22019, son contenu est supersede sauf deux apports, conserves ici :

- reserve 1 de la revue Hermes corrigee : le buy & hold est ancre sur la premiere
  barre ou la strategie peut trader (start_idx = 100) et non sur l'indice 0 --
  ancre a 0, le comparateur couvrait 100 barres de plus et l'ecart melangeait
  performance et periode. Mesures refaites par la voie sanctionnee
  (lean research + nbconvert, 15 cellules, 0 erreur) : strategie 77,19 %,
  buy & hold 86,08 %, ecart -8,89 % (l'ancien -34,48 % etait surévalue).
- sous-section « Protocole d'evaluation » dans les Ameliorations (flux de titres
  reel, validation purgee, reference passive systematique).

Twin parity non re-baseline (consigne ai-01 : faux rouge #22292).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@jsboige

jsboige commented Oct 24, 2026

Copy link
Copy Markdown
Owner Author

Conflict resolved — and the branch turned out to be a duplicate of a merged sibling

Findings first, because they change what this PR should be.

While resolving the conflict I checked the plateau: this branch and #22019 serve the same issue (#21851), touch the same single file, and #22019 was merged on main at 2026-10-24T00:33:36Z — one hour before this branch's commit (01:33:49 +0200). The path-collision organ returns terminal ("#22019 est déjà sur main"). So #22265 is a superseded delivery, not a competing one.

I therefore did not do a blind union of both sides. I read the markers, compared the two versions cell by cell (by content similarity, since the structures diverged — 31 cells on main vs 29 here), and took the merged version as the base: it carries the natural class counts for MultinomialNB, TimeSeriesSplit on both models, and discloses that the backtest is in-sample. Everything this branch had already been delivered there, except two things — which I grafted on top.

The two residues from this branch

1. Réserve 1 of the Hermes review (5e039ed1) — fixed here, and it was still live on main.

The buy & hold comparator was anchored on index 0 while the strategy can only trade from start_idx = 100. The comparator therefore spanned 100 more bars than the strategy, and the resulting gap mixed a performance difference with a period difference. The fix anchors the entry on the first tradeable bar and prints the window explicitly.

Measurements were redone through the sanctioned path (lean research + nbconvert --execute --inplace, 15/15 code cells executed, 0 error output), not adjusted by hand:

before after
Buy & hold (same universe, same window) 111,67 % 86,08 %
Gap strategy − buy & hold −34,48 % −8,89 %

Strategy return is unchanged (77,19 %, final equity $177 190,59, 404 trades). The conclusion — the sentiment classifier does not beat a passive reference — is unchanged too, but the magnitude was overstated roughly fourfold. The prose in the reading cell is realigned on the fresh numbers.

2. Réserve 2 (in-sample backtest) — already covered by the merged version. main discloses it in the reading of the results; there is nothing left to carry.

3. Unique contribution kept: the ### Protocole d'évaluation sub-section in the Améliorations cell (real headline feed, purged validation, systematic passive reference). main does not have it.

Gates

  • check_prose_quantitative_claims.py --diff origin/main --strict → [OK] aucun compteur quantitatif en prose.
  • check_output_collapse.py origin/main → 0 flagged, 0 unaddressed
  • check_source_collapse.py origin/main → 0 flagged
  • check_output_failure_text.py origin/main → 0 regressed

Not done, deliberately

The twin-parity job is not rebaselined (ai-01's instruction: the red is a false red, #22292, and the underlying fix is #22293). This push will relaunch it; it may stay red for the false-positive reason.

The substance of the reserve is treated and the measurements are above; the formal lift belongs to the coordinator under its own login.

@myia-ai-01 myia-ai-01 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lecture coordinateur sur dossier delegue, verifie a la tete 29f3544.

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Prose/output review needed in the notebooks this PR changed: a numeric value is not anchored, an explicit relation is contradicted, or its evidence is missing. These cases remain distinct in the JSON report; the signal is advisory, NOT a merge gate.

Scope = notebooks CHANGED in this PR, not the whole corpus. Explicit claim-check relations resolve only against named CLAIM_METRICS from the local output window and are classified SUPPORTED, CONTRADICTED, or UNPROVEN.
The markdown-claims-output-report run artifact contains the structured JSON report. See python scripts/check_markdown_claims_output.py --help for re-running locally.
Detector rationale: c.290 / c.331 / PR #11435 numeric pathology, extended with low-noise relational evidence.

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Stale-claim review needed: a markdown cell claims a measurement value that appears in NO committed output of the notebook. Advisory, NOT a merge gate — triage against the JSON artifact.

Scope = notebooks CHANGED in this PR, not the whole corpus. The stale-claim-report run artifact holds the structured JSON.
Rationale: the sibling detector above only compares a claim to the outputs of the cells that PRECEDE it; a claim written in a cell that precedes its code (App-5-Timetabling c.2/c.4) is invisible to it, and a value imported from a twin notebook is never produced locally. See python scripts/check_stale_claims.py --help.

@github-actions

Copy link
Copy Markdown
Contributor

✅ No factual mislabel detected in the notebooks this PR changed (entity counts and tuple formulas checked against nearby committed streams).

Scope = notebooks CHANGED in this PR, not the whole corpus. The factual-mislabel-report run artifact holds the structured JSON.
Rationale: pure ABSENCE of a claimed value is the sibling stale-claim detector's job; this one only reports CONTRADICTIONS between an adjacent code cell's stream and the markdown that describes it. See python scripts/check_factual_mislabel.py --help.

@jsboige

jsboige commented Oct 24, 2026

Copy link
Copy Markdown
Owner Author

Rouge Always-on guards : défaut réel de mon body, corrigé. Rouge Gitleaks : infra.

1. Always-on guards — prev_guard — corrigé dans le body. Le tag de cette PR portait prev: DEEP/research-code #69. #69 résout vers une issue, pas une PR : l'invariant prev-not-pr exige une PR de la même lane, distincte de celle-ci, fusionnée ou ouverte. Je l'ai remplacé par prev: LIGHT/docs #22211 — la PR de cette lane créée immédiatement avant celle-ci (2026-10-23 15:23Z contre 23:35Z) et fusionnée le 2026-10-24 09:07Z. La correction est faite dans le body et non par un commentaire parce que c'est le body que le garde lit ; l'édition est l'événement edited qui fait réévaluer le garde.

2. Gitleaks secret scanner — infra, pas contenu. L'annotation du run est explicite : « The self-hosted runner lost communication with the server », sur un job démarré à 08:44:24Z. Aucune détection de secret n'est rapportée. Ce n'est pas un signal de sécurité, c'est un runner qui a perdu la connexion ; la famine CI en cours interdit le rerun de masse, je le laisse tel quel et le documente ici.

3. PR gate n'est que le repli du garde ci-dessus ([pr-gate] FAIL -- failing checks: Always-on guards -- 16 organes, 1 checkout (failure)), pas un rouge autonome.

Rien de tout cela ne touche le contenu de la PR : le split chronologique du quantbook et ses mesures sont inchangés.

@jsboige

jsboige commented Oct 24, 2026

Copy link
Copy Markdown
Owner Author

Gitleaks secret scanner : rouge de harnais, pas de contenu — mesuré, puis jambe rejouée.

Le job 120776694746 (run 40061794661, tête 29f354475c99f195b682f4baf86627c450bf8ad7) rend conclusion: failure, mais ses étapes disent l'inverse :

Étape Conclusion Horodatage
Set up job, checkout, fetch gitleaks (pinned), version log success 08:44:24Z
Gitleaks detect success 09:01:54Z → no leaks found (961,08 Mo scannés en 58,1 s)
Post Run actions/checkout success 09:02:56Z
Complete job success 09:02:56Z
conclusion du job failure 09:12:38Z

Aucune étape n'échoue. Le verdict failure est daté dix minutes après la dernière ligne de log, sur un job dont le travail était terminé et vert — c'est la signature de la famine de runners décrite sur le dashboard (~1 900 runs en file), pas une trouvaille du scanner. Le contrôle positif du même run (Gitleaks positive controls (#10143)) est vert, donc le binaire détecte bien.

Geste : rejeu de cette seule jambe (gh run rerun 40061794661 --job 120776694746) — aucun commit, donc pas de ré-armement du plancher d'attente. Aucune édition de corps ni de titre (consigne du 24/10 09:04Z).

Ce que dit le repli check_run_state.py --pr 22265 sur la même tête : [RED] se limite à cette jambe. Les autres noms sont soit verts (Twin parity, Quarto, prose-counts, perimeter, validate-notebooks, CodeQL), soit cancelled (Validate 'outputs' key), soit sans conclusion à 10:29:49Z — c'est-à-dire la tentative courante encore en file, pas un rouge.

@github-actions

Copy link
Copy Markdown
Contributor

Golden-Set Execution (H.7 P3)

✅ 9/9 notebooks passed (certified reproducible)

Notebook Status Time
2.1-Workflow-ML.ipynb ✅ SUCCESS 4.9s
2.2-Descente-de-gradient.ipynb ✅ SUCCESS 4.6s
2.3-Regression-lineaire-logistique.ipynb ✅ SUCCESS 5.6s
2.4-Arbres-Forets-Ensembles.ipynb ✅ SUCCESS 5.7s
Search-01-StateSpace.ipynb ✅ SUCCESS 4.0s
SL-1-LogicalLearning.ipynb ✅ SUCCESS 2.5s
RL-04-Bandits-Manchots-Python.ipynb ✅ SUCCESS 20.8s
GameTheory-04c-NashExistence-Python.ipynb ✅ SUCCESS 3.6s
GameTheory-13d-Optimistic-CFR-Python.ipynb ✅ SUCCESS 12.5s

Pinned lockfile: scripts/notebook_tools/golden_set.lock.txt (H.7 P3, axe A #4208)

@github-actions

Copy link
Copy Markdown
Contributor

Notebook PR Validation: PASS

  • Notebooks checked: 1
  • Code cells validated: 15
  • Result: All passed

Checks: H.1 (no errors), H.3 (execution_count), C.1 (no banned patterns)
Non-Python kernels (.NET/Lean): C.1 + errors only (execution_count advisory)
QuantConnect notebooks: C.1 + errors only (require QC Cloud for execution)

@github-actions

Copy link
Copy Markdown
Contributor

Notebook outputs-required (H.4 schema): PASS (every code cell carries an outputs: list)

@myia-ai-01
myia-ai-01 merged commit ba17659 into main Oct 24, 2026
111 of 117 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants