diff --git a/MyIA.AI.Notebooks/QuantConnect/projects/ML-TextClassification/quantbook.ipynb b/MyIA.AI.Notebooks/QuantConnect/projects/ML-TextClassification/quantbook.ipynb index ac4e4f09456..a30e9de2fae 100644 --- a/MyIA.AI.Notebooks/QuantConnect/projects/ML-TextClassification/quantbook.ipynb +++ b/MyIA.AI.Notebooks/QuantConnect/projects/ML-TextClassification/quantbook.ipynb @@ -33,10 +33,10 @@ "execution_count": 1, "metadata": { "execution": { - "iopub.execute_input": "2026-10-22T13:17:21.060809Z", - "iopub.status.busy": "2026-10-22T13:17:21.060554Z", - "iopub.status.idle": "2026-10-22T13:17:23.247097Z", - "shell.execute_reply": "2026-10-22T13:17:23.245577Z" + "iopub.execute_input": "2026-10-24T04:12:33.553664Z", + "iopub.status.busy": "2026-10-24T04:12:33.553380Z", + "iopub.status.idle": "2026-10-24T04:12:35.229230Z", + "shell.execute_reply": "2026-10-24T04:12:35.228121Z" } }, "outputs": [ @@ -76,10 +76,10 @@ "execution_count": 2, "metadata": { "execution": { - "iopub.execute_input": "2026-10-22T13:17:23.287006Z", - "iopub.status.busy": "2026-10-22T13:17:23.286708Z", - "iopub.status.idle": "2026-10-22T13:17:23.627537Z", - "shell.execute_reply": "2026-10-22T13:17:23.623427Z" + "iopub.execute_input": "2026-10-24T04:12:35.265950Z", + "iopub.status.busy": "2026-10-24T04:12:35.265681Z", + "iopub.status.idle": "2026-10-24T04:12:35.404440Z", + "shell.execute_reply": "2026-10-24T04:12:35.402989Z" } }, "outputs": [ @@ -114,10 +114,10 @@ "execution_count": 3, "metadata": { "execution": { - "iopub.execute_input": "2026-10-22T13:17:23.631912Z", - "iopub.status.busy": "2026-10-22T13:17:23.631578Z", - "iopub.status.idle": "2026-10-22T13:17:24.549406Z", - "shell.execute_reply": "2026-10-22T13:17:24.547933Z" + "iopub.execute_input": "2026-10-24T04:12:35.407252Z", + "iopub.status.busy": "2026-10-24T04:12:35.406810Z", + "iopub.status.idle": "2026-10-24T04:12:35.847696Z", + "shell.execute_reply": "2026-10-24T04:12:35.846442Z" } }, "outputs": [ @@ -269,10 +269,10 @@ "execution_count": 4, "metadata": { "execution": { - "iopub.execute_input": "2026-10-22T13:17:24.552907Z", - "iopub.status.busy": "2026-10-22T13:17:24.552474Z", - "iopub.status.idle": "2026-10-22T13:17:24.584673Z", - "shell.execute_reply": "2026-10-22T13:17:24.583560Z" + "iopub.execute_input": "2026-10-24T04:12:35.850233Z", + "iopub.status.busy": "2026-10-24T04:12:35.849932Z", + "iopub.status.idle": "2026-10-24T04:12:35.863796Z", + "shell.execute_reply": "2026-10-24T04:12:35.862272Z" } }, "outputs": [ @@ -363,10 +363,10 @@ "execution_count": 5, "metadata": { "execution": { - "iopub.execute_input": "2026-10-22T13:17:24.588722Z", - "iopub.status.busy": "2026-10-22T13:17:24.588454Z", - "iopub.status.idle": "2026-10-22T13:17:24.597880Z", - "shell.execute_reply": "2026-10-22T13:17:24.596186Z" + "iopub.execute_input": "2026-10-24T04:12:35.866402Z", + "iopub.status.busy": "2026-10-24T04:12:35.866192Z", + "iopub.status.idle": "2026-10-24T04:12:35.874005Z", + "shell.execute_reply": "2026-10-24T04:12:35.872359Z" } }, "outputs": [ @@ -427,10 +427,10 @@ "execution_count": 6, "metadata": { "execution": { - "iopub.execute_input": "2026-10-22T13:17:24.601591Z", - "iopub.status.busy": "2026-10-22T13:17:24.601274Z", - "iopub.status.idle": "2026-10-22T13:17:24.649139Z", - "shell.execute_reply": "2026-10-22T13:17:24.635384Z" + "iopub.execute_input": "2026-10-24T04:12:35.876820Z", + "iopub.status.busy": "2026-10-24T04:12:35.876515Z", + "iopub.status.idle": "2026-10-24T04:12:35.901072Z", + "shell.execute_reply": "2026-10-24T04:12:35.899848Z" } }, "outputs": [ @@ -501,10 +501,10 @@ "execution_count": 7, "metadata": { "execution": { - "iopub.execute_input": "2026-10-22T13:17:24.656229Z", - "iopub.status.busy": "2026-10-22T13:17:24.655979Z", - "iopub.status.idle": "2026-10-22T13:17:28.045972Z", - "shell.execute_reply": "2026-10-22T13:17:28.044232Z" + "iopub.execute_input": "2026-10-24T04:12:35.903767Z", + "iopub.status.busy": "2026-10-24T04:12:35.903533Z", + "iopub.status.idle": "2026-10-24T04:12:37.514787Z", + "shell.execute_reply": "2026-10-24T04:12:37.513118Z" } }, "outputs": [ @@ -577,10 +577,10 @@ "execution_count": 8, "metadata": { "execution": { - "iopub.execute_input": "2026-10-22T13:17:28.049888Z", - "iopub.status.busy": "2026-10-22T13:17:28.049619Z", - "iopub.status.idle": "2026-10-22T13:17:30.168995Z", - "shell.execute_reply": "2026-10-22T13:17:30.167141Z" + "iopub.execute_input": "2026-10-24T04:12:37.517748Z", + "iopub.status.busy": "2026-10-24T04:12:37.517531Z", + "iopub.status.idle": "2026-10-24T04:12:39.073017Z", + "shell.execute_reply": "2026-10-24T04:12:39.071557Z" } }, "outputs": [ @@ -629,10 +629,10 @@ "execution_count": 9, "metadata": { "execution": { - "iopub.execute_input": "2026-10-22T13:17:30.179388Z", - "iopub.status.busy": "2026-10-22T13:17:30.178912Z", - "iopub.status.idle": "2026-10-22T13:17:30.216234Z", - "shell.execute_reply": "2026-10-22T13:17:30.207410Z" + "iopub.execute_input": "2026-10-24T04:12:39.076003Z", + "iopub.status.busy": "2026-10-24T04:12:39.075535Z", + "iopub.status.idle": "2026-10-24T04:12:39.099475Z", + "shell.execute_reply": "2026-10-24T04:12:39.098012Z" } }, "outputs": [ @@ -684,10 +684,10 @@ "execution_count": 10, "metadata": { "execution": { - "iopub.execute_input": "2026-10-22T13:17:30.219641Z", - "iopub.status.busy": "2026-10-22T13:17:30.219308Z", - "iopub.status.idle": "2026-10-22T13:17:34.907964Z", - "shell.execute_reply": "2026-10-22T13:17:34.906856Z" + "iopub.execute_input": "2026-10-24T04:12:39.102496Z", + "iopub.status.busy": "2026-10-24T04:12:39.102278Z", + "iopub.status.idle": "2026-10-24T04:12:42.844931Z", + "shell.execute_reply": "2026-10-24T04:12:42.842380Z" } }, "outputs": [ @@ -735,10 +735,10 @@ "execution_count": 11, "metadata": { "execution": { - "iopub.execute_input": "2026-10-22T13:17:34.922090Z", - "iopub.status.busy": "2026-10-22T13:17:34.921668Z", - "iopub.status.idle": "2026-10-22T13:17:35.011262Z", - "shell.execute_reply": "2026-10-22T13:17:35.010467Z" + "iopub.execute_input": "2026-10-24T04:12:42.860161Z", + "iopub.status.busy": "2026-10-24T04:12:42.858975Z", + "iopub.status.idle": "2026-10-24T04:12:42.928299Z", + "shell.execute_reply": "2026-10-24T04:12:42.923480Z" } }, "outputs": [ @@ -774,10 +774,10 @@ "execution_count": 12, "metadata": { "execution": { - "iopub.execute_input": "2026-10-22T13:17:35.068968Z", - "iopub.status.busy": "2026-10-22T13:17:35.068435Z", - "iopub.status.idle": "2026-10-22T13:17:58.591408Z", - "shell.execute_reply": "2026-10-22T13:17:58.577776Z" + "iopub.execute_input": "2026-10-24T04:12:42.932960Z", + "iopub.status.busy": "2026-10-24T04:12:42.932619Z", + "iopub.status.idle": "2026-10-24T04:12:58.660250Z", + "shell.execute_reply": "2026-10-24T04:12:58.648033Z" } }, "outputs": [ @@ -831,7 +831,7 @@ "\n", "La validation croisée temporelle (5 découpes entraînées sur le passé, évaluées sur le futur immédiat) confirme la tendance : Naive Bayes 50,78 % en moyenne (47,43 % à 54,73 % selon la découpe), régression logistique 52,57 % (50,08 % à 57,71 %) — autour du hasard, jamais nettement au-dessus de la classe dominante.\n", "\n", - "Cette lecture est **cohérente avec le backtest de la section 9** : la stratégie retire **77,19 %** sur la fenêtre, mais le buy & hold équipondéré du même univers en retire **111,67 %** (écart **-34,48 %**). Le rendement positif vient du panier tech 2017-2019 (le beta du panier), pas du signal — le signal, lui, reste sous la baseline. C'est l'attente d'un pipeline où les « news » sont fabriquées depuis les prix passés (section 2) : le classifieur ré-apprend la transformation prix → texte → prix, une circularité sans bord prédictif. Notons enfin que le backtest balaye toute la fenêtre, période d'entraînement comprise : il illustre le pipeline, il n'évalue pas le classifieur hors échantillon.\n", + "Cette lecture est **cohérente avec le backtest de la section 9** : la stratégie retire **77,19 %** sur la fenêtre, mais le buy & hold équipondéré du même univers et de la même fenêtre — ancré sur la première barre où la stratégie peut trader, du 2017-06-30 au 2019-12-31 — en retire **86,08 %**, soit un écart de **-8,89 %**. Le rendement positif vient du panier tech 2017-2019 (le beta du panier), pas du signal — le signal, lui, reste sous la baseline. C'est l'attente d'un pipeline où les « news » sont fabriquées depuis les prix passés (section 2) : le classifieur ré-apprend la transformation prix → texte → prix, une circularité sans bord prédictif. Notons enfin que le backtest balaye toute la fenêtre, période d'entraînement comprise : il illustre le pipeline, il n'évalue pas le classifieur hors échantillon.\n", "\n", "Trois leçons à retenir :\n", "1. **Sur données temporelles, le split aléatoire surestime l'accuracy** : il met des jours voisins (fortement autocorrélés) des deux côtés de la coupure et mesure un effet de voisinage, pas une prédiction.\n", @@ -851,10 +851,10 @@ "execution_count": 13, "metadata": { "execution": { - "iopub.execute_input": "2026-10-22T13:17:58.599277Z", - "iopub.status.busy": "2026-10-22T13:17:58.598930Z", - "iopub.status.idle": "2026-10-22T13:17:58.736341Z", - "shell.execute_reply": "2026-10-22T13:17:58.734878Z" + "iopub.execute_input": "2026-10-24T04:12:58.703593Z", + "iopub.status.busy": "2026-10-24T04:12:58.703292Z", + "iopub.status.idle": "2026-10-24T04:12:58.832520Z", + "shell.execute_reply": "2026-10-24T04:12:58.830997Z" } }, "outputs": [ @@ -919,10 +919,10 @@ "execution_count": 14, "metadata": { "execution": { - "iopub.execute_input": "2026-10-22T13:17:58.739173Z", - "iopub.status.busy": "2026-10-22T13:17:58.738930Z", - "iopub.status.idle": "2026-10-22T13:17:58.778426Z", - "shell.execute_reply": "2026-10-22T13:17:58.776680Z" + "iopub.execute_input": "2026-10-24T04:12:58.835620Z", + "iopub.status.busy": "2026-10-24T04:12:58.835323Z", + "iopub.status.idle": "2026-10-24T04:12:58.889045Z", + "shell.execute_reply": "2026-10-24T04:12:58.886922Z" } }, "outputs": [ @@ -1020,10 +1020,10 @@ "execution_count": 15, "metadata": { "execution": { - "iopub.execute_input": "2026-10-22T13:17:58.781608Z", - "iopub.status.busy": "2026-10-22T13:17:58.781300Z", - "iopub.status.idle": "2026-10-22T13:18:02.290030Z", - "shell.execute_reply": "2026-10-22T13:18:02.288565Z" + "iopub.execute_input": "2026-10-24T04:12:58.894756Z", + "iopub.status.busy": "2026-10-24T04:12:58.894429Z", + "iopub.status.idle": "2026-10-24T04:12:46.494682Z", + "shell.execute_reply": "2026-10-24T04:12:46.493210Z" } }, "outputs": [ @@ -1037,8 +1037,9 @@ "Valeur finale: $177,190.59\n", "Return total: 77.19%\n", "Nombre de trades: 404\n", - "Buy & hold (équipondéré, même univers/fenêtre) : 111.67%\n", - "Écart stratégie - buy&hold : -34.48%\n" + "Fenêtre de la stratégie : 2017-06-30 au 2019-12-31\n", + "Buy & hold (équipondéré, même univers et même fenêtre) : 86.08%\n", + "Écart stratégie - buy&hold : -8.89%\n" ] } ], @@ -1130,13 +1131,22 @@ "print(f\"Valeur finale: ${backtest_results['final_value']:,.2f}\")\n", "print(f\"Return total: {backtest_results['return']:.2%}\")\n", "print(f\"Nombre de trades: {len(backtest_results['trades'])}\")\n", - "# Baseline buy-and-hold équipondéré sur la même fenêtre / le même univers\n", - "# (recette ai-01 c.45, #8777 CHANGES_REQUESTED) : sans ce comparateur, le +77% isolé\n", - "# laisse croire que le classifieur de sentiment \"marche\", alors que l'accuracy est\n", - "# ~52% (≈ hasard). Le rendement vient de la direction cumulative + du beta du panier\n", - "# tech sur la fenêtre, pas du signal de sentiment.\n", - "bh = (closes.ffill().iloc[-1] / closes.ffill().iloc[0] - 1).mean()\n", - "print(f\"Buy & hold (équipondéré, même univers/fenêtre) : {bh:.2%}\")\n", + "# Baseline buy-and-hold équipondéré sur le même univers ET la même fenêtre que la\n", + "# stratégie (recette ai-01 c.45, #8777 CHANGES_REQUESTED) : sans ce comparateur, le\n", + "# +77% isolé laisse croire que le classifieur de sentiment \"marche\", alors que\n", + "# l'accuracy est ~52% (≈ hasard). Le rendement vient de la direction cumulative + du\n", + "# beta du panier tech sur la fenêtre, pas du signal de sentiment.\n", + "#\n", + "# Ancre = première barre où la stratégie peut trader (start_idx = 100), jamais\n", + "# l'indice 0 : ancré à 0, le buy & hold couvre 100 barres de plus que la stratégie et\n", + "# l'écart mélange écart de performance et écart de période (réserve mesurée par la\n", + "# revue Hermes du head 5e039ed1, corrigée ici).\n", + "bh_start_idx = 100\n", + "bh_entry = closes.ffill().iloc[bh_start_idx]\n", + "bh_exit = closes.ffill().iloc[-1]\n", + "bh = (bh_exit / bh_entry - 1).mean()\n", + "print(f\"Fenêtre de la stratégie : {closes.index[bh_start_idx].date()} au {closes.index[-1].date()}\")\n", + "print(f\"Buy & hold (équipondéré, même univers et même fenêtre) : {bh:.2%}\")\n", "print(f\"Écart stratégie - buy&hold : {backtest_results['return'] - bh:+.2%}\")" ] }, @@ -1148,6 +1158,11 @@ "\n", "**Rappel d'objectif** : ce carnet ne teste pas le chemin « vraies news ». Les extensions ci-dessous changent la source des textes autant que le modèle — aucune n'est validée par les mesures précédentes, qui portent exclusivement sur des titres simulés depuis les prix.\n", "\n", + "### Protocole d'évaluation\n", + "- **Flux de titres réel** : remplacer `simulate_news_headlines` par une API de news — tant que le texte dérive du prix, aucun modèle ne peut battre le hasard\n", + "- **Validation purgée** : blocs croissants avec purge autour de la coupure, pour couper l'autocorrélation résiduelle des fenêtres glissantes\n", + "- **Référence passive systématique** : tout backtest se lit contre un buy & hold équipondéré du même univers et de la même fenêtre\n", + "\n", "### NLP Avancé\n", "- **TF-IDF**: Pondération par importance des mots\n", "- **Word Embeddings**: Word2Vec, GloVe pour capturer le contexte\n",