From 32fd59d1e5e39cdc3e277d1381b774c9d20469b1 Mon Sep 17 00:00:00 2001 From: jsboige Date: Tue, 15 Sep 2026 22:35:52 +0200 Subject: [PATCH 1/2] =?UTF-8?q?feat(qc,#16239):=20module=20macro/r=C3=A9gi?= =?UTF-8?q?mes=20=E2=80=94=20diagnostic=20born=C3=A9,=20indicateurs?= =?UTF-8?q?=E2=86=92budget=20de=20risque=20(QC-Py-28b)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nouveau notebook QC-Py-28b : taxonomie NBER, diagnostic composite borné (médiane, règle tabulée, override STRESS), traduction régime→budget de risque, 3 exercices C.1 ; twin QC Cloud sans lookahead (backtest 260357cf, Sharpe 0.512, MaxDD 15.4% vs ~34% SPY COVID), résultats réels datés dans le notebook. Co-Authored-By: Claude Sonnet 5 --- .../QC-Py-28b-Macro-Cycle-Regimes.ipynb | 826 ++++++++++++++++++ .../QuantConnect/Python/README.md | 2 + 2 files changed, 828 insertions(+) create mode 100644 MyIA.AI.Notebooks/QuantConnect/Python/QC-Py-28b-Macro-Cycle-Regimes.ipynb diff --git a/MyIA.AI.Notebooks/QuantConnect/Python/QC-Py-28b-Macro-Cycle-Regimes.ipynb b/MyIA.AI.Notebooks/QuantConnect/Python/QC-Py-28b-Macro-Cycle-Regimes.ipynb new file mode 100644 index 0000000000..e8bd00c514 --- /dev/null +++ b/MyIA.AI.Notebooks/QuantConnect/Python/QC-Py-28b-Macro-Cycle-Regimes.ipynb @@ -0,0 +1,826 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "nav", + "metadata": {}, + "source": [ + "[<< Sommaire QC](../README.md) | [Précédent : QC-Py-28-Market-Regime-Detection <<](./QC-Py-28-Market-Regime-Detection.ipynb)\n", + "\n", + "# QC-Py-28b - Macro et régimes : du cycle économique au budget de risque\n", + "\n", + "> **[HYBRIDE LOCAL + QC CLOUD]** Les parties 1 à 3 et 5 s'exécutent **localement** (numpy / pandas / matplotlib, données synthétiques auto-contenues). La partie 4 exécute le même diagnostic sur **données réelles via QuantConnect Cloud** et le traduit en allocation mensuelle : une cellule `REFERENCE QC` (code à copier dans QC Lab) et le **résultat du run réel** collé en markdown.\n", + "\n", + "> **Codage historique** : RNCP37437BC02C2.1 - Analyser la conjoncture économique et identifier les phases du cycle (7 h). Le code RNCP37437 est devenu inactif (remplacé par RNCP41881) ; les codes historiques sont **conservés sans normalisation**. See #16239." + ] + }, + { + "cell_type": "markdown", + "id": "s1-probleme", + "metadata": {}, + "source": [ + "## 1. Le problème : lire le cycle, pas seulement le prix\n", + "\n", + "`QC-Py-28-Market-Regime-Detection` détecte les régimes **sur les prix** : SMA, RSI, puis une stratégie `RegimeSwitching` qui bascule mean-reversion / momentum. C'est un signal **retardataire par construction** — le prix a déjà bougé quand le détecteur change d'avis. Ce notebook (28b) est le chaînon macro manquant : **28 détecte sur les prix ; 28b diagnostique depuis l'économie**.\n", + "\n", + "Le diagnostic macro sert à trois décisions distinctes, toutes exercées ici :\n", + "\n", + "1. **Diagnostic actuel** — dans quelle phase du cycle sommes-nous *aujourd'hui*, sachant que les données sont publiées avec retard (partie 4) ?\n", + "2. **Scénario futur** — quels indicateurs devraient basculer pour que le régime change, et dans quel sens (partie 4) ?\n", + "3. **Traduction dans la gestion** — quel budget de risque, quel budget d'exposition allouer à chaque régime, de façon **explicite et falsifiable** (partie 5) ?\n", + "\n", + "**Mapping acceptance de l'issue #16239 (livrable 1)** : un support *exécutable* couvrant les phases macro (parties 3-4, moteur + run réel), reliant les indicateurs *avancés et concomitants* à une *décision d'investissement ou de risque* (partie 5, table régime → budget), avec *au moins un exercice appliqué au résultat interprété* (partie 6, trois exercices dont un sur le run QC réel)." + ] + }, + { + "cell_type": "markdown", + "id": "s2-taxonomie", + "metadata": {}, + "source": [ + "## 2. Taxonomie : avanceurs, concomitants, retardés\n", + "\n", + "La classification NBER/diffusion distingue les indicateurs par leur **position dans le cycle** — pas par leur « qualité » :\n", + "\n", + "| Famille | Définition | Exemples US | Pourquoi ça compte |\n", + "|---|---|---|---|\n", + "| **Avanceurs** (leading) | tournent *avant* l'activité | pente de la courbe des taux (10a-2a), permis de construire, nouvelles commandes ISM, claims chômage hebdo | anticipent le retournement : c'est ici que se joue le *scénario futur* |\n", + "| **Concomitants** (coincident) | tournent *avec* l'activité | production industrielle, emploi non agricole, revenus réels | mesurent le cycle *tel qu'il est* : c'est le **diagnostic actuel** |\n", + "| **Retardés** (lagging) | tournent *après* | taux de chômage, inflation core, délais de livraison fournisseurs | confirment la phase ; précèdent souvent le *retournement suivant* (surchauffe → récession) |\n", + "\n", + "**Trois pièges structurels**, à garder en tête pendant tout le notebook :\n", + "\n", + "- **Révisions** : la première publication de l'emploi ou du PIB est révisée ; un diagnostic construit sur données flash peut bouger *après coup*.\n", + "- **Délais de publication** : un indicateur concomitant publié à J+30 décrit en réalité J-30. Le « présent » du diagnostic est toujours daté (le registre de la partie suivante code ce lag explicitement).\n", + "- **Géographie** : cette taxonomie est calibrée sur l'économie **US** ; au Brésil ou en zone euro, les avanceurs ne sont pas les mêmes (le crédit privé et les termes de l'échange pèsent davantage que les permis de construire)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "c-registre", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-15T20:28:22.063451Z", + "iopub.status.busy": "2026-09-15T20:28:22.063451Z", + "iopub.status.idle": "2026-09-15T20:28:22.077176Z", + "shell.execute_reply": "2026-09-15T20:28:22.076094Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Registre : 10 indicateurs\n", + " avanceur : 4 indicateurs, lag median 5 j\n", + " concomitant : 3 indicateurs, lag median 15 j\n", + " retarde : 3 indicateurs, lag median 5 j\n", + "\n", + " [ avanceur] Pente courbe taux (10a-2a) lag= 1 j direction=+1\n", + " [ avanceur] Permis de construire lag=30 j direction=+1\n", + " [ avanceur] Nouvelles commandes ISM lag= 1 j direction=+1\n", + " [ avanceur] Claims chomage hebdo lag= 5 j direction=-1\n", + " [ concomitant] Production industrielle lag=15 j direction=+1\n", + " [ concomitant] Emploi non agricole lag= 5 j direction=+1\n", + " [ concomitant] Revenus reels lag=30 j direction=+1\n", + " [ retarde] Taux de chomage (U3) lag= 5 j direction=-1\n", + " [ retarde] Inflation IPC core lag=15 j direction=-1\n", + " [ retarde] Delais de livraison lag= 1 j direction=-1\n" + ] + } + ], + "source": [ + "from dataclasses import dataclass\n", + "\n", + "\n", + "@dataclass(frozen=True)\n", + "class Indicator:\n", + " name: str\n", + " kind: str # \"avanceur\" | \"concomitant\" | \"retarde\"\n", + " source: str # definition de la serie observee\n", + " publication_lag_days: int # retard de publication en jours ouvrables\n", + " direction: int # +1 : hausse = expansion ; -1 : hausse = contraction\n", + "\n", + "\n", + "TABLE = [\n", + " Indicator(\"Pente courbe taux (10a-2a)\", \"avanceur\", \"differentiel de rendements obligataires\", 1, +1),\n", + " Indicator(\"Permis de construire\", \"avanceur\", \"autorisations nouvelles (US Census)\", 30, +1),\n", + " Indicator(\"Nouvelles commandes ISM\", \"avanceur\", \"indice de diffusion, > 50 = expansion\", 1, +1),\n", + " Indicator(\"Claims chomage hebdo\", \"avanceur\", \"indemnites versees, inverse du marche du travail\", 5, -1),\n", + " Indicator(\"Production industrielle\", \"concomitant\", \"volume produit (Fed, tableau G.17)\", 15, +1),\n", + " Indicator(\"Emploi non agricole\", \"concomitant\", \"payrolls (BLS)\", 5, +1),\n", + " Indicator(\"Revenus reels\", \"concomitant\", \"revenu disponible deflate par l'IPC\", 30, +1),\n", + " Indicator(\"Taux de chomage (U3)\", \"retarde\", \"BLS, enquete menages\", 5, -1),\n", + " Indicator(\"Inflation IPC core\", \"retarde\", \"IPC hors energie et alimentaire\", 15, -1),\n", + " Indicator(\"Delais de livraison\", \"retarde\", \"fournisseurs lents = surchauffe (ISM livraisons)\", 1, -1),\n", + "]\n", + "\n", + "\n", + "def zscore(series, window=252):\n", + " \"\"\"z-score glissant : position de la valeur courante dans sa propre histoire (1 an).\"\"\"\n", + " return (series - series.rolling(window).mean()) / series.rolling(window).std()\n", + "\n", + "\n", + "def signal_from_z(z, threshold=0.5):\n", + " \"\"\"Traduit un z-score en signal {-1, 0, +1}. La zone grise (|z| <= 0.5) est ASSUMEE.\"\"\"\n", + " if z > threshold:\n", + " return 1\n", + " if z < -threshold:\n", + " return -1\n", + " return 0\n", + "\n", + "\n", + "print(f\"Registre : {len(TABLE)} indicateurs\")\n", + "for kind in (\"avanceur\", \"concomitant\", \"retarde\"):\n", + " subset = [i for i in TABLE if i.kind == kind]\n", + " lags = sorted(i.publication_lag_days for i in subset)\n", + " print(f\" {kind:>12} : {len(subset)} indicateurs, lag median {lags[len(lags) // 2]} j\")\n", + "print()\n", + "for i in TABLE:\n", + " print(f\" [{i.kind:>12}] {i.name:<28} lag={i.publication_lag_days:>2} j direction={i.direction:+d}\")" + ] + }, + { + "cell_type": "markdown", + "id": "i-registre", + "metadata": {}, + "source": [ + "**Lecture du registre.** Chaque indicateur porte sa justification codée en dur :\n", + "\n", + "- **Pente 10a-2a en avanceur** : la courbe des taux agrège les anticipations de politique monétaire des années à venir ; son aplatissement précède historiquement les récessions US de 6 à 18 mois. Lag de 1 jour : les rendements sont observables en continu.\n", + "- **Claims chômage en avanceur, direction −1** : les licenciements augmentent *avant* que le chômage (retardé) ne se dégrade — c'est le retournement du marché du travail vu côté entreprises. Hebdomadaire : le signal macro le plus frais qui existe.\n", + "- **Production industrielle et payrolls en concomitants** : ils *sont* l'activité, pas son ombre. Leur lag de publication (5 à 15 j) décale le « présent » du diagnostic.\n", + "- **Chômage et inflation core en retardés, direction −1** : ils se dégradent après le creux — les confirmer en hausse, c'est souvent être déjà de l'autre côté du retournement.\n", + "\n", + "`publication_lag_days` n'est pas décoratif : additionné aux révisions, il signifie que **le diagnostic « actuel » décrit au mieux l'état de l'économie il y a 2 à 4 semaines**. Un cadre honnête affiche cette date de référence au lieu de la maquiller." + ] + }, + { + "cell_type": "markdown", + "id": "s3-composite", + "metadata": {}, + "source": [ + "## 3. Le diagnostic composite borné\n", + "\n", + "Un seul indicateur trompe ; dix indicateurs étouffent le signal. Le compromis : un **score composite = médiane des signaux** agrégés, transformé en régime par une **règle tabulée** — explicite, donc falsifiable.\n", + "\n", + "**Pourquoi la médiane et pas la moyenne** : avec 4 indicateurs, une valeur aberrante (erreur de publication, révision brutale) déplace la moyenne mais ne déplace pas la médiane. La robustesse aux outliers est exactement la propriété qu'on demande à un *diagnostic* — contrairement à un signal de trading où la réactivité prime. C'est l'objet de l'exercice 2.\n", + "\n", + "**Pourquoi borné** : le moteur ne fait **aucune extrapolation**. Il classifie l'état présent (et daté) des indicateurs ; il ne prédit pas. Quand les signaux se contredisent, la médiane tombe en zone grise et le régime « NEUTRE » **est la réponse** — pas un échec du modèle.\n", + "\n", + "**La règle de décision, tabulée** (4 signaux, médiane ∈ {−1, −0.5, 0, +0.5, +1}) :\n", + "\n", + "| Condition | Régime | Lecture |\n", + "|---|---|---|\n", + "| médiane ≥ +0.5 | **EXPANSION** | majorité d'indicateurs en zone haute |\n", + "| médiane ∈ (−0.5, +0.5) | **NEUTRE** | zone grise : les indicateurs se contredisent |\n", + "| médiane ≤ −0.5 | **RALENTISSEMENT** | majorité en zone basse |\n", + "| z_tendance < −1 **et** z_crédit < −1 | **STRESS** (surcharge) | cassure conjointe prix + crédit |" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c-moteur", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-15T20:28:22.080359Z", + "iopub.status.busy": "2026-09-15T20:28:22.079846Z", + "iopub.status.idle": "2026-09-15T20:28:23.468416Z", + "shell.execute_reply": "2026-09-15T20:28:23.468416Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Repartition mensuelle des regimes :\n", + "regime\n", + "EXPANSION 31\n", + "NEUTRE 28\n", + "STRESS 24\n", + "RALENTISSEMENT 10\n", + "\n", + "Transitions detectees (changement de regime d'un mois sur l'autre) :\n", + "2019-01-31 STRESS\n", + "2019-05-31 NEUTRE\n", + "2019-08-31 EXPANSION\n", + "2019-12-31 RALENTISSEMENT\n", + "2020-01-31 EXPANSION\n", + "2020-05-31 NEUTRE\n", + "2020-09-30 STRESS\n", + "2021-01-31 NEUTRE\n", + "2021-02-28 RALENTISSEMENT\n", + "2021-03-31 NEUTRE\n", + "2021-04-30 RALENTISSEMENT\n", + "2021-05-31 NEUTRE\n", + "2021-08-31 EXPANSION\n", + "2022-04-30 NEUTRE\n", + "2022-09-30 STRESS\n", + "2022-10-31 NEUTRE\n", + "2022-11-30 STRESS\n", + "2023-03-31 RALENTISSEMENT\n", + "2023-04-30 STRESS\n", + "2023-05-31 RALENTISSEMENT\n", + "2023-06-30 NEUTRE\n", + "2023-07-31 EXPANSION\n", + "2023-08-31 STRESS\n", + "2023-09-30 RALENTISSEMENT\n", + "2023-11-30 STRESS\n", + "2023-12-31 NEUTRE\n", + "2024-01-31 EXPANSION\n", + "2024-04-30 NEUTRE\n", + "2024-06-30 RALENTISSEMENT\n", + "2024-07-31 STRESS\n", + "2025-01-31 NEUTRE\n", + "2025-05-31 EXPANSION\n", + "2026-04-30 NEUTRE\n", + "2026-06-30 RALENTISSEMENT\n", + "2026-08-31 STRESS\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA9sAAAE1CAYAAAD3WPMHAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAdlhJREFUeJzt3QeYHVXdBvB35vbtu8lm0xNSgYTeO0izN1CUaoEPBOmoVBEQEFBQVFBEQBAQREBEqvQmvUpLISG97Wbr7TPf8567c5m9e7ckGQJJ3h/Pkt25c6edc2bmf87MOZbrui5EREREREREJDB2cIsSEREREREREVKwLSIiIiIiIhIwBdsiIiIiIiIiAVOwLSIiIiIiIhIwBdsiIiIiIiIiAVOwLSIiIiIiIhIwBdsiIiIiIiIiAVOwLSIiIiIiIhIwBdsiIiIiIiIiAVOwLZ86P/vZz2BZFjYkN9xwg9nnOXPmfNKbsl7YEPPQ2rC+59M999wT06dPXyvrWpeO5e23346GhgZ0dHR80psi6+h5kevl+lfH+PHj8Z3vfCfwbZINw4oVK1BZWYn77rvvk94U2UAp2Ja1ckPp/cTjcYwcORL7778/rrzySrS3t2NDctFFF+Huu+/+pDdjg6RjL7Rw4UJz0//aa6+tlfWt6/kun8/j3HPPxfHHH4+qqqridMdx8Ic//AFbbrmlmd7U1ITPfe5zePbZZz/R7RXZ0MroYN1yyy349a9/HegyL7zwQnz5y1825b+/CpU777wTBx10ECZMmICKigpMnToVp556KlauXFl2/nvuuQdbb721uWccO3asOQflcrle8/H7//d//4fGxkYTUO+111545ZVXeswzZMgQHHnkkTjnnHMC2muRVeSKfIyuv/56l9ns/PPPd2+66Sb3uuuucy+66CJ3v/32cy3LcseNG+e+/vrrPb6TzWbdZDLpro8qKyvdI444otf0XC5n9tlxnE9ku9Y35fJQX8deVr08f/DBB+666sUXXzT7wH0ptccee7jTpk0LdH3repm/6667zLl6/vz5Paafcsop5jgeeuih7h//+Ef3kksucSdMmOCGw2H3+eef/8S2V/r2SV5buV6uf3WkUik3k8m4H5cN5drwhS98wdxzBYnngOHDh7v777+/+f3cc88tO9+QIUPczTbbzD3nnHPcP/3pT+4JJ5zgRqNRd+ONN3a7urp6zHvfffeZc85ee+3lXnPNNe7xxx/v2rbtHnPMMT3my+fz7s4772zS72c/+5n7u9/9zt10003d6upq9/333+8x79tvv22275FHHgl0/0UGI7yqwbnI6mCLx7bbblv8+4wzzsCjjz6KL37xi6ZW9J133kEikTCfhcNh87MhCYVC5keCsT7loa6uLtMSIOuXdaXMX3/99dhll10watSo4jS2MF199dU48MADcdNNNxWnf+Mb3zAtVzfffDO23357rE9SqRSi0Shse919IPCTPC+yhXJ1xWKxQLdFgvPBBx+Yx/yXL19uWpf7cscdd5jXdPy22WYbHHHEEeZ8wZZnz2mnnYbNN98cDz30UDG/1tTUmCcQTjzxRGy88cbFZfJJmr///e/mXETf/OY3MWXKFNMSzpZ8zyabbGJeEeLTlp/5zGcCPw4i/Vl3rxqyzuMJj4/1zJ07F3/961/7fa+MN3ycf9iwYebCu+mmm5qbvVJ8tJHf56PqDFD4SNHbb7/d650v7/H2Z555BqecckrxEaSvfe1rWLZsWa/lXnXVVZg2bZpZN5d93HHH9Xr8acaMGTjggAMwfPhwc2MxevRofOtb30Jra6v5nOvr7OzEX/7yl+Jj9d429fX+5v3334899tgD1dXV5mKz3Xbb9biA9GXBggX4/ve/b7aV27zRRhvhBz/4ATKZTHGe2bNnm5tjvovJY7Xjjjvi3//+d4/lPP7442a7+M7meeedZ264uS28sHG/0uk0TjrpJJMufJT0u9/9rpnmx+//8Ic/NBdUPjrGY8OL7JNPPtlru1999VVTMcN95fL23ntv/Pe//+0xTzabNdsyefJksyw+Irbrrrvi4Ycf7jMP9XfsveP1ve99zzwKx+PFtL7uuuswGFwv119XV2e2mft45plnFj/vK229Y8t/S98Zfvnll7H77rubdPEvq5x3333X3GAwD7PCius/66yzzGePPfaYWcddd93V63vMR/zsueeeG9Sy+sN8uttuu5kyxPzxhS98Af/73/8G/N5Aaclyz21kvijFGy8Gq0w7/7FjeWe557Fjfr300kuL3+GxZhki5lUvLzCN/Ppbhof5nDd0kyZNMnlmzJgx+PGPf9wj/69qmWdD0c9//nNz7vDOXzyOpeevvt697e88sjrpwwDzgQcewD777NMr3ZLJpCkvfjwPMBj1Kk77wvPQT3/6U3MeqK2tNdvF7WN+9eN+cH9++ctf4pprrsHEiRPNsWYavvjiiz3mXbx4sUlTHjvOM2LECHzlK19ZrXfivbL5t7/9DWeffbbJA0yPtrY28/nzzz+Pz372s2bbOZ3naF5LVuc8zOsIz6HMP5yH+emSSy4x1zI/bguPl3ct2GyzzfCb3/xmjc6LXsXJBRdcUDy2zGs855SexzmdleNPP/20qUjhOlixcuONNw7qmJY+Yuxty8yZM03e5vmTx5NpyArGgd7ZHuxx4988Tjxe3Gae25h2L730UqDXhsGcD/ozmDy1KsesFM+PvL7zfsvbTx7XNTXYZZQG2sT7LWJji//cyx8+Gu6vGDr22GPN+ZEBtoe/M12+/vWvF6cxfXkN++c//9nr2O+7777417/+ZZYjsjatH00/ss467LDDzIWdNZhHHXVUn/MxsOZFjq3gPAHzhMmTLy+kDHz9Lea8Mf7Sl75k3gt//fXXzb+8aSyH7yHW19ebiyRvyvg+EwPD2267rccFjjcxvOHkjdJ7771ntoc3e7wYRiIRc/PE9fDkzmUy4OZF+t577zU3BbwgsgWItbe8UeGFhHiD0xfeOPMiz/3mfvHCyqCDN78HH3xwv++kch3eu0ysBea28MLECzJbZ5YsWYKdd97Z/H3CCSeYGzPebPD4cj7vIui5+OKLzQ306aefbi70v/3tb81+88a6paXFHCMGxdxm3lDyRtrviSeeMMeU6+KNCCsveGPxwgsvFDuk4s0/b7h5I8mbFC7/j3/8o7lI8/s77LBDMT24Pd6x5A0wb5z4nhYvpuX0d+x5LFjR4FUK8GLN4IQ3yVw2b+j6wm3mDShr4c8//3yzbzw+5W68V6UzF1Y4sKLm0EMP7RXQ+L3xxhvmmPFYcb944zNr1ixTPvguHY8db/pY0VGappzGY7DTTjsNall94bFl6wTzP292madYPnijz/za383YQGnJSh2Wb27rVltt1Wv7uX/+FlfmReYr3nzxhot5+Sc/+Ym50eYxZesG04n5k/vI/SWWhcEug3jeYVlh4MHlcLlvvvkmrrjiCrz//vvF9z9Xtcxzuxhsf/7znzc/PA777bdfj+BsVa1J+rDSh+vmu5N+PBewPLK8M//wOPJ8w6CN51NvX/vCdL722mvx7W9/25z32XfHn//8Z7ONPCfwPfDSiiHOc/TRR5tyynM804cVhsyvxIpOlkeef7lPS5cuNYHmhx9+uNpBBfeH50u2tPHczt/5RBbzAQNfXjd4DvQqg5966qlii/5gzsP8l0EVp3Pf+G4qW+p4vl+0aFHx/VruB48VKx+Zhl6AwvMMW/pW97xInJ/nfpY1vkPLoI/L4fJLK+l4buN8PDcyTzHoZNDHY8Hr1OpgGeM1g+vktjJfsNLG289yBnvciNvKfMo0476ycoHpxOsVn7YL4tow2PNBXwabp9bkmLHSlBXk8+fPN9tF/j4Y2DI9GKzsCepJA1aQ0dChQ4vTvIpV/5OQxAorVqT5K175O89NpU+b8Hixco7HnudtD48v953nibXVEaaIMaiHzUXW8B1PvifZl9raWnerrbYq/s13fkqzZuk7PcR3hPiOoGfx4sXmfcGvfvWrPebjuzxcnv+dLG+79tlnnx7vTJ588sluKBRyV65caf5eunSpea+I75jz/SAP3w3i9/kOOr366qvm77///e+r9W5Y6buwXD/fO9phhx16vWM30Duehx9+uHm/qdwx97570kknmfU99dRTxc/a29vdjTbayB0/fnxxXx977DEz3/Tp03u8M/ftb3/bvFP1uc99rsfyd9ppp17vhPH7/HnppZeK0+bOnevG43H3a1/7WnEa043HetasWcVpCxcuNMdh9913L07bYostzLtn/SmXh/o69t///vfdESNGuMuXL+8x/Vvf+pbJm+XynueKK64w61m2bNkqv+fsHVv+639nmNP+8Ic/uIPB48Ljw+PZVx4544wz3FgsVszTXr5mWfG/XzeYZZXuC/NMXV2de9RRR/X4Dssij13p9FKDSUvmtZEjR/Yof6+88kqv9669Y3fjjTcWp6XTafM+4QEHHDDod7YHswz2P8Ey5i8/xHTj95955plVLvPeuYbHw3/MzzzzzF7nr3L5++NIn2uvvdYs78033+z12YwZM9ytt966WL75w/Pxu+++6w6E76vzuPq1tLS4TU1N7ve+973iNO4Hl8v3PZubm4vT//nPf5rp//rXv4rf5d+XXXaZGwSvbHJ//OWf6TJ58mRz7fGnEefhuXPfffddpfPwBRdcYPJH6fulp59+urkOffjhh+bvE0880a2pqTHHrS+rc1587bXXzN9HHnlkj/lOO+00M/3RRx8tTuN5ndOefPLJ4jTmWZ5bTj31VHcgpe/zetvi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kEkj1LESkXQSsXwnQulWWB15RKw22OmsyftsJjT5OFULq73DtOhGwu2w6gr5O5KqRLijFXZtEvmOVljRIeaJj1D7SoRzCThlyo1XvkLJVjjt7YVgm+OStzSbee1YFm5nB9C8DNFUErlIpmxZiufrkUmFTKs1nzCJ5VKmvESquhDuakM2nUM4VVHYllgXbI5LXxFCqIXb1AQTtWa7ywmPNx/xr4rBak8h1NaBSMSC7SQQzUWALpYZt9BiDD7e3gk3nDHpkw1FEO5qhZWIIdLlmhZ+tpDbmQ6gi8+AF55e8Mpe2g0X9hkOQplWuOksch0R8yRKvqMDyRzQkeowlWv5VAcybgrpXBK5ZDtS2Up0sXLGtUx+rnaqEcqEEHJDphzwyQnmOz5lwS3l72zp9ed9lilOZwsxp6fcFJK5pMnXXlliWWB5Zp71yvxAZcxbrn85/une+voqg/7y520zhxPkmPXmaY/SbeM5J582x4JlzHtsPpwNI2JzXPi8OS48n5nR47ufojCt43CL5zVvminn3eUyZ+cQcfLIp1bC5ePmhece0JXtQjidRr69rfBMCp9WyLTCdtNws3zUP41osg0xpwE28og6SXOedztjQEjDXYqIiEhv6o1cREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCpmBbREREREREJGAKtkVEREREREQCFsYGwEml+vzdSRf+dtJpOF1dH31WMt3/PTeTKfv7xy2fcwb4PL/G60jlP9rPUhkn0+/3QtlQj+970/pafrnPycmnkRjE9hS3K1/Yrkwug67sR2nY13qzTrbX5+Wm9SXn5LC6nO40dBynmGZOtve68/nVTMvc4PaD+dlF+fxUmqfdTN/L7FVufOWk3LzoUf4ycCPly1V/0z4OWaZBJgMnl0VkVb9Xkn7ZXBZZt2cecQcouwOVM+bfcnnCm9fL+6XlxZ+vvc9K5xlMGSstt319t68y6J+vr/0b7Lb0db4zadF9/HtNK1PG/PP4v+NxVrcM9sMrHz2uJ7wOhfsuQ+bvfKjn35Ge16S+yqo3b7lt6Et/5T0I+bwDWH19lu/371w2h0z3OcFLf+9cWm7+VbmGre3r+arkN+5XH4dsQP7rbLny1d+1z39se1y/mIal85aZ5k8PljGmnb+sMQ0z3efdNVXuXq2Um+p5vTLfK1uOMj3LZyRSvCd0syXndq4X5fNNubJaWr7cbP9/r24Zi5Sc49zu8sI0CdvhXmngzbe6BnNfVFr++vrOqlwH+vtuX8sZzD3jqq7Lv28DnWcGq/S61Nf1bHXuF1fpO2uQLz7Jc+qnyXoZbFuxGGJTN4EdakdoyWzku9LI80a/W6iuHog6cLMu3LwFe8hwIA/kW1uL8/Sans8jVFNrPrPDEVQNH45oRQLIOwg3DEFs1FhYDZWIf5BDuLIR4aFjYI8ZhQrLQiichFs5EvaK2Qg3DkPVjpsiPKkJFdWjYI8dg1gmAzuTQa49iTjqEBozFPVWFZq7Vpj1DRk5Fis6srASMYSGbYahY6YgFosi29aMTL4TuWG1GFrRADuSQK5hCppGjEVFIodUZwtymIIho8chPjSHivZWDA8NRyqWQkVFBWoqahCKhDB6xGgksgnMc+chnS8cp6pYFaoSVRhhjcCIphFYkV6BTqcTNYkaZIYWCk80ETX/ZodkkUEGuUzhxMl5Sqf5DfR5CHkkhkxARaIeyXy6uE19yVk5xOviyNk5tGY+SsO+1uuGXNREeu4HT461Q2oRSUQQC8Uwom4EOlIdgAuMHj4aDbEGrAivwIftH8KJOqgfWo9JjZPQZrWhqakJ9aF6VNvV6Ix1oiHagFhDDLl8Yd9CoRBqa2vNCbLD6UB9VT3scAW6qscj7tqwczlU1NSibvNtUZWIIt+yEq4dRWJIAxI77oDx4ycitKgFiWgl4lvtjFEjh6IiHEFnVwQZbIx8MoVoRSOioyYhFKpAYtJEVE2YgkilhYph42Dn3oHVnoQ1cTxi1RHk5y8yed9CFlUjRiA+dSqqczm4uTTCdgZOOGamV0ydipzTBRdxhIaPByrqEXFdWGMbEFqeQnbIsl7lhlgeokOakJ49B5YFhIcPgxOZBbi2uTEJDx0Ga+JUwI7AymbLliV7xDCEmuebshuqrUNo7HBU5utRUT8OVmYMUBVH2KmGPaQK4XYA8QoMGT8Ooc0mIzR0OOyqUQg3NCBWsyNCmRSctjkIxcPIJh3EalOIbTwZNZVNsJsqYaWWIGdZyCWTsB0bufrJiIbrkB22CUaNaEJs2EZwK6OIJB2EY/WwQiEkqrZBW7gDae5gLodoZQ0q6rbB0LYUWIeRc1zYiXrEayOIxepgV1cjNHkiqhsy6OqyUFPbgMbh26JhRCM6KzpRUT8GFVUJzE0vRjqaNvmZeWZYYhhaK1tNGXDgoK6xDuNqxqGiugLv5d9DMpTskfc5D/OxE3PM9HAsjMohlSZfp31lqTpejaaqJizuWIzOfOeAZcxbbulyStfXXxn0yl86nEasPobaylpMaJiAZfayXts2sn4konVRs/0r+F98BfLhPGpSNQgjjMqKSnMjUhGvQFW8CgkrgVwuZ354/IfXDkdlvPKjaTynVVZh8rDJJtCriFrIpqKw4vXmczfvIhFPwLLCCE+qh11ZBTcaM2mYqAjDylUhUt2EaH0t7NHD0RAbhlAyCqwE7Opa1NQMRWj+PESjUTSMH4N4hYPaRCPskWMQXrYEtl0oP8z/3rUoWtOA+piFcDbfswzlXYRGTUD16IkIz59tyiqcdI/rVuGa5CI8YgJiG0+DXRcy1zoGQ7xu2fVDYbtVH83rl3dhj9gIDWPGI5HLwl7eglD1Clh1DTwwcO04wqMmIjx2JKrcSkRyzQiNGIpEqhqhiVshHm5EhPs0ejhCK1PIoxp2dQNy0UmwauciXD0MoY2mo7FqFJxUF9paHYRzLhITJyJSUwPEoqiIR1G1+faoTITRno4jF56MaEW1uQHMDd0Y4REJhBP1yLiuuaYNcWzYbhIWLCSTyULec22kqsejIlxZKLMV1eAtYaKyDjXDdoDbWYecHTHXsdCQkMm7ndlOk09rKmvQ2DAadqYKoXkzEI5nURWNwg6FYNfVosq2kUt2mXIbHzUK9e0dsIfUwl44F3aoCoi6sNpthJpGo2LUDgiNqkK4Io9oowOrpcVc3xNVYxEZNRURpnF1K6oSOeSbpqAjsxSx1GzEq6vhNk6DHa1AZPk8uFXVSEyaitpRDYgsy5pzqD1+LKL5KjALRKuqEGtsRLR7/+NNQ2BjFCp2rka03kaydgkqKuth2xYiFRE0VTdhfvv8XtfZuoo6jK4cjRq7BrPt2YhEI4jFY6iurEb1iGrknTzymSTmRzpgVVegfkQYUxqnINoSLV6/EuEEamqHIDS0AomaWtSnOxEPx811c+TQkWhzM3Cr6sz6WFaH28NNuTRp5wBD60bDjVUgx6t9MolQ3oUVr4NlZZAf3YTKhnrUNGyFeHo0nK5OhCpGwonZyNV2IFQXQ7ilFaEpjUiEhiA8tAZO57vm+uLl9XBdLSrDfHSzzpRhO+IimkrBcnpfr0x5676+FQqmAzcURbguZvIEr1H8jndPaEUTsIePR8SqQGzIEITyefBqys9CDcMQm7gJbGsxQtWdyPvKdrH8WlGEamsRGlaP7IShsOPViI+ehIpNpiEXcpHP2LCHNiE2cgyi9ZUIRzoRahqB/IQcwrUJ2HX1CId4naoB12xXVSBcX4fE1jsjGgohvMKBU1kBe8IkJMZUIerWIJvLFctLPBRFJFEJq2ZzOK2xYhp4vPRiuamqqAKGhhBq2gSN4SGobl2K0NAQ6tsT6Mo4qGoYhoaR47EEE+HEo6iPJDB12FTkEjl0LutETW0N6mrqMKZxjMmLvLbF6mJI5BKIhCKwwpa5l+J3EMugMh9HZXg22vKpAa9Jq3qtWp17xlVZVw65Htc47zzT1NCEha0LEWoMYXT1aCxxlyBrZ+HGXdjDbNSEalDhVJhj3VTXPe/QEBoyDaasULnrWTzB+5/paMwuQ9gq3LMObxpj7jfjThsi6TAcpx2VY0cikavDqKZ6xLLtSKeGID16FPKVNchO2hxV48cilqlCZMESVMTCiAyfCmvCxohEMwhXj0cul0coGkPliJEIJ2NwM5HC9aUmDgt5RDcaj/SKHKJ5F0PGjkWkbSXCNTFEp05D2snCRqx4jxeyUbi/G96AWKIZzoJO2PEKILRehqI9WK7rugPN1NbWZm78WltbUcOL5Tok39HJqrueE72E7Z7upNKw47Fe3+01Pd99Qgolei4jFEaoqvKj9XGW7r+RZiQAIFbd8/dBSHfflMViMfM7a5lt2zZ/e5/zx/u73Ofe9/1/l/I+542IV9PIms+wEy5+7n3mrxHtq4bSm6evWsuBPh/sPH48oTJI7o9/maX7UTqtMlLZ6/iR/zhwnlXhpSGD7kgkUvyXStNtoLT0TyvuH2vcS/JjYcZ2pNMZIFZlvt+jTHTP6+Vbw7eM4ry+k6E33enkibLMMS9ZZo/ldH9eXE/p36Vlqdz+lOhR5rrLWBrRnsere3o+W3hzxr8873h7eNw9/uPdV5n0vtNrWrbLVIpxW7z05MU9k86gIlLRa9n+/MX8zPzFn9Jy6eU7/7z+vO/lY37O6eXKkrcc/7IH0leZLF3fYL7f1/6V7qP/uJjPus9JxGPKloVoLGqml0vD0mnl0tObx2ulYNp43/Wn4UD5ode5v1uvsuUpLQd+peWvr/nLlBdz3aqs7HvZJd9j2ch3dgLR7uvSIMqc2e90B8JsrUS0uD6eE6LRWK/y5R2j/s5t5vu+z/zzevN550uPdw71p7f/+8Qy5ynNe8Xj5vGd6zKZwnH0p3mPeTPtCHG/B3M99137vWPHfOV9t/S4+PNS6WflrgX+MuLpq3yXO4c4WafXOan0Wlfu+uVPQy8dyl3HvLTxlJ3W3cKYBisoY2XPj4O+z+vr5r2f8tBrvjLlrXhP2P2Z/xrolTvvOmTKFO8VS9fB5eWTJu+Y61GZ622PMtydz7xrlycX6bnNfd0/+M9x/vJS+lm5659Xdvz5hXi+9e49S+8PvXn5OVuNeX7250WWv4jTfd8TsXud71flmrQm94+DuWdc1XX1d40j/+/F7zvhXsfJm+6/v+jrelbunNofL24oLa/9Xr985cHkeS9v+u/v+vq9zP2jp691flqtSSy83gfbIiIiIiIiIqtjTWJhdZAmIiIiIiIiEjAF2yIiIiIiIiIBU7AtIiIiIiIiEjAF2yIiIiIiIiIBU7AtIiIiIiIiEjAF2yIiIiIiIiIBU7AtIiIiIiIiErCPRhrvhzcUN8cYExEREREREdkQtHXHwF5MHHiw3d7ebv4dM2bMKq9AREREREREZF3GmLi2tnaVvmO5gwjRHcfBwoULUV1dDcuy8GmveWClwLx581BTU/NJb46sJqXjuk9puO5TGq4flI7rPqXh+kHpuO5TGm6Yaei6rgm0R44cCdu2g2/Z5kJHjx6NdQkPngrBuk/puO5TGq77lIbrB6Xjuk9puH5QOq77lIYbXhrWrmKLtkcdpImIiIiIiIgETMG2iIiIiIiISMDWu2A7Fovh3HPPNf/KukvpuO5TGq77lIbrB6Xjuk9puH5QOq77lIbrvthaTsNBdZAmIiIiIiIiIhtwy7aIiIiIiIjIJ03BtoiIiIiIiEjAFGyLiIiIiIiIBEzBtoiIiIiIiEjAFGyLiIiIiIiIbAjB9sUXX4ztttsO1dXVGDZsGL761a/ivffe6zFPKpXCcccdhyFDhqCqqgoHHHAAlixZ0mOeE044Adtss43p2n3LLbcsu67bb7/dfFZRUYFx48bhsssu+1j3bUMRRBq+/vrr+Pa3v40xY8YgkUhgk002wW9+85te63r88cex9dZbm3SeNGkSbrjhhrWyjxuCtZWOixYtwsEHH4wpU6bAtm2cdNJJa20f13drKw3vvPNO7LvvvmhsbERNTQ122mknPPjgg2ttP9dnaysNn376aeyyyy5mGZxn4403xhVXXLHW9nN9tjaviZ5nnnkG4XC4z/sf+fSmI+9rLMvq9bN48eK1tq/rq7VZFtPpNM466ywTX/Aedfz48bjuuuvWyn6u7y5eS+n4ne98p2xZnDZt2uA31v0U2n///d3rr7/efeutt9zXXnvN/fznP++OHTvW7ejoKM5zzDHHuGPGjHEfeeQR96WXXnJ33HFHd+edd+6xnOOPP9793e9+5x522GHuFlts0Ws99913nxsOh92rr77anTVrlnvvvfe6I0aMcH/729+ulf1cnwWRhn/+85/dE044wX388cdN+tx0001uIpHokT6zZ892Kyoq3FNOOcV9++23zWehUMh94IEH1vo+r4/WVjp+8MEHZp6//OUv7pZbbumeeOKJa31f11drKw2ZZpdccon7wgsvuO+//757xhlnuJFIxH3llVfW+j6vb9ZWGjKtbrnlFrMelknOw/PrH//4x7W+z+ubtZWGnpaWFnfChAnufvvtV/b+Rz7d6fjYY49xWF73vffecxctWlT8yefza32f1zdrsyx++ctfdnfYYQf34YcfNufUZ5991n366afX6v6ur/ZfS+m4cuXKHmVw3rx5bkNDg3vuuecOels/lcF2qaVLl5qTzhNPPFHccd7E/f3vfy/O884775h5nnvuuV7f5wEpd7H59re/7R544IE9pl155ZXu6NGjXcdxPpZ92VCtaRp6jj32WHevvfYq/v3jH//YnTZtWo95DjroIFMIZd1JR7899thDwfY6noaeTTfd1D3vvPMC3HpZ22n4ta99zT300EMD3HpZG2nI6+DZZ5/d5/2PfLrT0Qu2WWki62Ya3n///W5tba27YsWKj3kPZG1eF++66y7Xsix3zpw57mB9Kh8jL9Xa2mr+bWhoMP++/PLLyGaz2GeffYrz8HG3sWPH4rnnnhv0cvl4Rzwe7zGNjxHMnz8fc+fODWz7Jbg05HK8ZRDn9S+D9t9//1XKB/LJp6Osf2noOA7a29uVzutwGr766qt49tlnscceewS6/fLxpuH111+P2bNn49xzz/3Ytl/WTlnkKwAjRowwr+jwtQBZd9LwnnvuwbbbbotLL70Uo0aNMq/JnXbaaUgmkx/r/myoWtfSdfHPf/6zWSZfDRisMD7leMPG9zf5Htn06dPNNL6zEo1GUVdX12PepqamVXqfhUHZySefbJ7H32uvvTBz5kz86le/Kr5Dyncr5NOThrzpu+222/Dvf/+7OI3z8july2hrazMnNFaeyKc/HWX9S8Nf/vKX6OjowDe/+c2A92LDtjbScPTo0Vi2bBlyuRx+9rOf4cgjj/yY9mbD9HGm4YwZM3D66afjqaeeMu9ry7qZjgyw//CHP5hgjQ1D1157Lfbcc088//zzpo8a+fSnISu82A8GG/XuuusuLF++HMceeyxWrFhhKsRk3bu3WbhwIe6//37ccsstq7R9n/ozMV9sf+utt0yGDdpRRx2FWbNm4Ytf/KKp/WCnPieeeKK5uWAnTfLpSUN+/ytf+Yqpqd9vv/0C3T4ZHKXjum9tpSEvROeddx7++c9/mo5LZN1KQwZqrCj573//awI3djzJTmTk052G+XzedDTJssdWNFl3y+LUqVPNj2fnnXc296vssPCmm25a422Xjz8NGQCyI62bb74ZtbW1Ztrll1+OAw88EFdddZUag9bBe5u//OUvJnhnZ2yrxP0UO+6448z70+wEy48vupd7l4Uvxl9++eW9ljPQO0u5XM6dP3++m06nTadpXDaf/ZdPRxr+73//c4cNG+aeeeaZvZa/22679Xq/97rrrnNramoC3Y8N3cedjn56Z3vdTsNbb73VdDDCDidl3S2HngsuuMCdMmVKAFsvH3ca8rtcBjsJ9X74bqE3jeuQdbcsnnbaaaaDJ1k30vDwww93J06c2GMaO/LlstmJqKxbZZF9eU2aNMk96aSTVnkbP5XBNneIB2/kyJFlM6T30vsdd9xRnPbuu++ucgdp5bDn8p122mkN90CCSkP2MsgC8KMf/ajsethB2vTp03t1fKcO0tatdPRTsL3upiF7so7H4+7dd9/9MezJhuuTKIcednA3bty4APZiw7Y20pA9Vb/55ps9fn7wgx+4U6dONb/7e+mVda8s7rPPPqbDQlk30pCjOLDiub29vTiN10bbtt2urq7A92tD46zlsuh1Wshz6XoRbPPiwB782BW7v7t1f+Zkd+6snXj00UdNd+4MkEuD5BkzZrivvvqqe/TRR5uaef7OH7Zg07Jly8ywX+ydjtPZ/TtvFJ9//vm1vs/rmyDSkBm6sbHR9ITrX4b/qQNv6C8WEqbj73//ew39tQ6mI3nlc5tttnEPPvhg8ztrG2XdSMObb77ZDKXIMuifhxc8WTfSkENl3nPPPebGhT/XXnutW11d7Z511llrfZ/XN2vzXOqn3sjXzXS84oorTGDG+1jOzwpoBmn/+c9/1vo+r2/WVhoyyGaLK0c94r0Me8mePHmye+SRR671fV4f/WAtn1M5D4dxWx2fymCbNQflfjiemieZTJru2evr602wxdo+HqDSFrJyy+FYd16wzUdyKisrzTL23ntv97///e9a39/1URBpyJuEcssobWVhbRPHZo5Go2ZcUf86ZN1Jx8HMI5/eNOzrfHvEEUes9X1e36ytNOTQlxxKkd/nqzhbbbWVe9VVV2ls33XsXOqnYHvdTMdLLrnEPILMBiCO6bvnnnuagEHWrbLIRiA+kcAWbgbep5xyilq118F0ZKMB0/Caa65ZrW21ujdYRERERERERAKiLrdFREREREREAqZgW0RERERERCRgCrZFREREREREAqZgW0RERERERCRgCrZFREREREREAqZgW0RERERERCRgCrZFREREREREAqZgW0RERERERCRgCrZFREREREREAqZgW0RERERERCRgCrZFREREREREEKz/Bx7TRZiRhyhdAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "\n", + "\n", + "def compute_diagnostic(df_z, stress_threshold=-1.0):\n", + " \"\"\"Diagnostic composite BORNE sur un DataFrame de z-scores date x indicateurs.\n", + "\n", + " Retourne un DataFrame {median, regime} : aucune extrapolation, une classification.\n", + " \"\"\"\n", + " sig = df_z.apply(lambda col: col.map(signal_from_z))\n", + " composite = sig.median(axis=1)\n", + " regimes = []\n", + " for date in df_z.index:\n", + " if df_z.loc[date].min() < stress_threshold and sig.loc[date].le(-1).sum() >= 2:\n", + " regimes.append(\"STRESS\")\n", + " elif composite.loc[date] >= 0.5:\n", + " regimes.append(\"EXPANSION\")\n", + " elif composite.loc[date] <= -0.5:\n", + " regimes.append(\"RALENTISSEMENT\")\n", + " else:\n", + " regimes.append(\"NEUTRE\")\n", + " return pd.DataFrame({\"median\": composite, \"regime\": regimes}, index=df_z.index)\n", + "\n", + "\n", + "# Donnees synthetiques : 8 ans, expansion -> surchauffe -> recession brutale -> reprise\n", + "rng = np.random.default_rng(42)\n", + "n_days = 2016\n", + "t = np.arange(n_days)\n", + "cycle = np.sin(2 * np.pi * t / 504 - np.pi / 2) # cycle de 2 ans\n", + "recession = ((t > 1200) & (t < 1320)).astype(float) * -2.0 # crise brutale de ~6 mois\n", + "\n", + "dates = pd.bdate_range(\"2019-01-02\", periods=n_days)\n", + "df_z = pd.DataFrame(\n", + " {\n", + " \"z_trend\": cycle * 1.2 + rng.normal(0, 0.35, n_days) + recession,\n", + " \"z_slope\": np.roll(cycle, 63) * 0.9 + rng.normal(0, 0.40, n_days) + 0.5 * recession, # avanceur (decale)\n", + " \"z_credit\": cycle * 1.0 + rng.normal(0, 0.45, n_days) + 0.7 * recession,\n", + " \"z_gold\": -cycle * 0.7 + rng.normal(0, 0.50, n_days) - 0.6 * recession, # refuge (inverse)\n", + " },\n", + " index=dates,\n", + ")\n", + "\n", + "diag = compute_diagnostic(df_z)\n", + "monthly = diag.resample(\"ME\").last()\n", + "print(\"Repartition mensuelle des regimes :\")\n", + "print(monthly[\"regime\"].value_counts().to_string())\n", + "print()\n", + "print(\"Transitions detectees (changement de regime d'un mois sur l'autre) :\")\n", + "changes = monthly[\"regime\"].ne(monthly[\"regime\"].shift())\n", + "print(monthly.loc[changes, \"regime\"].to_string())\n", + "\n", + "fig, ax = plt.subplots(figsize=(10, 3.2))\n", + "colors = {\"EXPANSION\": \"tab:green\", \"NEUTRE\": \"tab:gray\", \"RALENTISSEMENT\": \"tab:orange\", \"STRESS\": \"tab:red\"}\n", + "for regime, color in colors.items():\n", + " mask = (diag[\"regime\"] == regime).values\n", + " ax.fill_between(diag.index, 0, 1, where=mask, color=color, alpha=0.35, label=regime)\n", + "ax.set_title(\"Diagnostic composite sur cycle synthetique (8 ans, recession injectee en t=1200)\")\n", + "ax.set_yticks([])\n", + "ax.legend(loc=\"upper left\", ncol=4, fontsize=8)\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "i-moteur", + "metadata": {}, + "source": [ + "**Lecture du test synthétique.** Le cycle injecté est volontairement lisible : une sinusoïde de 2 ans (le « z_slope » avanceur est décalé de 63 jours), une récession brutale à t=1200 que **seul le STRESS override** doit capturer — la médiane seule sature à −0.5/-1 sans le qualificatif de cassure.\n", + "\n", + "- La **répartition** doit être dominée par EXPANSION/RALENTISSEMENT (les deux demi-cycles), avec NEUTRE aux virages — c'est la zone grise qui *rompt* la transition, exactement ce qu'on veut d'un diagnostic prudent.\n", + "- La **liste des transitions** est la sortie opérationnelle : c'est elle qu'un comité de gestion regarderait (« le régime a basculé le mois dernier, avec quels contributeurs ? »).\n", + "- L'override **STRESS** exige une cassure *conjointe* tendance + crédit : une seule jambe qui casse (petit frisson de volatilité) ne suffit pas. Le prix de cette exigence : un STRESS déclaré toujours en retard de quelques jours — assumé, on diagnostique, on ne devine pas.\n", + "\n", + "Si le graphique montrait des alternances EXPANSION/RALENTISSEMENT à haute fréquence (fibrillation), le seuil ±0.5 ou le seuil de signal serait mal calibré pour le bruit choisi — c'est le genre de diagnostic de santé du modèle que ce test local rend immédiat." + ] + }, + { + "cell_type": "markdown", + "id": "s4-qccloud", + "metadata": {}, + "source": [ + "## 4. Diagnostic actuel sourcé — run QuantConnect Cloud\n", + "\n", + "Les vraies séries macro (ISM, PMI, permis de construire) **ne sont pas dans QC** — verdict **RECOVERABLE-MACHINE assumé** : à la place, le moteur tourne sur **6 ETF proxy** qui en capturent la dynamique observable en continu :\n", + "\n", + "| Proxy ETF | Indicateur approximé | Rôle |\n", + "|---|---|---|\n", + "| SPY vs SMA 200 j | tendance de l'activité (concomitant) | le « prix » déjà Discounté par le marché |\n", + "| IEF (10 a) / SHY (2 a) | pente de la courbe des taux (avanceur) | anticipations de taux |\n", + "| HYG / LQD | spread de crédit (concomitant) | appétit pour le risque de défaut |\n", + "| GLD momentum 3 mois | demande de refuge (retardé, inversé) | peur non capturée par les actions |\n", + "\n", + "La cellule `REFERENCE QC` ci-dessous contient le `main.py` complet à copier dans un projet QC Lab. Le twin cloud reprend **exactement le même moteur** (mêmes z-scores, même médiane, même règle tabulée, même override STRESS), avec une contrainte en plus : **aucun lookahead** — chaque rebalancement mensuel ne voit que l'historique déjà publié, accumulé au fil de `OnData`. La fenêtre 2016 → 2026-09 couvre le choc de volatilité 2018, **COVID 2020** (validation du détecteur STRESS sur un vrai épisode) et le marché baissier 2022 ; les ~18 premiers mois sont un WARMUP documenté (la fenêtre z de 252 j sur une SMA 200 exige ~452 séances avant le premier diagnostic) avec allocation par défaut 60/40. Le régime pilote l'allocation : exposition nette SPY selon la table de la partie 5, le solde en SHY. Le **résultat du run réel** (statistiques exposées par l'API) est collé juste après." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c-reference-qc", + "metadata": {}, + "outputs": [], + "source": [ + "# [REFERENCE QC] Code a copier dans main.py QC Lab (non executable ici)\n", + "# Projet QC : 16239-macro-regimes-28b -- diagnostic reel, 6 ETF, 2019 -> 2026-09\n", + "qc_macro_diagnostic_code = '''\n", + "# 16239-macro-regimes-28b -- Diagnostic macro composite borne -> allocation mensuelle\n", + "# Lane myia-po-2026:CoursIA -- See #16239 livrable 1 (notebook QC-Py-28b)\n", + "# Proxies documents : ETF != series macro officielles (PMI/ISM indisponibles dans QC)\n", + "# Sans lookahead : chaque rebalancement n'utilise que l'historique PASSE accumule dans OnData.\n", + "from AlgorithmImports import *\n", + "\n", + "\n", + "class MacroRegimeAllocation(QCAlgorithm):\n", + "\n", + " exposition = {\n", + " \"EXPANSION\": 1.00,\n", + " \"NEUTRE\": 0.60,\n", + " \"RALENTISSEMENT\": 0.30,\n", + " \"STRESS\": 0.10,\n", + " \"WARMUP\": 0.60, # diagnostic pas encore calculable : defaut documente\n", + " }\n", + "\n", + " def initialize(self):\n", + " self.set_start_date(2016, 1, 1)\n", + " self.set_end_date(2026, 9, 12)\n", + " self.set_cash(100000)\n", + " self._tickers = [\"SPY\", \"IEF\", \"SHY\", \"HYG\", \"LQD\", \"GLD\"]\n", + " for t in self._tickers:\n", + " self.add_equity(t, Resolution.Daily)\n", + " self._closes = {t: [] for t in self._tickers}\n", + " self._dates = []\n", + " self._regimes = []\n", + " self.schedule.on(\n", + " self.date_rules.month_start(\"SPY\"),\n", + " self.time_rules.at(9, 31),\n", + " self.rebalance,\n", + " )\n", + "\n", + " def on_data(self, data):\n", + " if not all(data.bars.contains_key(t) for t in self._tickers):\n", + " return\n", + " for t in self._tickers:\n", + " self._closes[t].append(float(data.bars[t].close))\n", + " self._dates.append(self.time)\n", + "\n", + " @staticmethod\n", + " def _z_of(series):\n", + " window = series[-252:]\n", + " mean = sum(window) / len(window)\n", + " std = (sum((x - mean) ** 2 for x in window) / len(window)) ** 0.5\n", + " return 0.0 if std == 0 else (window[-1] - mean) / std\n", + "\n", + " def _diagnostic(self):\n", + " n = len(self._dates)\n", + " if n < 452: # 200 j de SMA + 252 j de fenetre z\n", + " return None\n", + " k = min(n, 800)\n", + " c = {t: v[-k:] for t, v in self._closes.items()}\n", + " trend = [\n", + " c[\"SPY\"][i] / (sum(c[\"SPY\"][i - 199 : i + 1]) / 200) - 1.0\n", + " for i in range(199, k)\n", + " ]\n", + " slope = [c[\"IEF\"][i] / c[\"SHY\"][i] for i in range(k)]\n", + " credit = [c[\"HYG\"][i] / c[\"LQD\"][i] for i in range(k)]\n", + " gold_mom = [c[\"GLD\"][i] / c[\"GLD\"][i - 63] - 1.0 for i in range(63, k)]\n", + "\n", + " z = {\n", + " \"z_trend\": self._z_of(trend),\n", + " \"z_slope\": self._z_of(slope),\n", + " \"z_credit\": self._z_of(credit),\n", + " \"z_gold\": -self._z_of(gold_mom), # inverse : or qui monte = demande refuge\n", + " }\n", + "\n", + " def sig(v):\n", + " return 1 if v > 0.5 else (-1 if v < -0.5 else 0)\n", + "\n", + " signals = sorted(sig(v) for v in z.values())\n", + " composite = (signals[1] + signals[2]) / 2 # mediane de 4 valeurs\n", + "\n", + " if z[\"z_trend\"] < -1.0 and z[\"z_credit\"] < -1.0:\n", + " regime = \"STRESS\"\n", + " elif composite >= 0.5:\n", + " regime = \"EXPANSION\"\n", + " elif composite <= -0.5:\n", + " regime = \"RALENTISSEMENT\"\n", + " else:\n", + " regime = \"NEUTRE\"\n", + " return z, composite, regime\n", + "\n", + " def rebalance(self):\n", + " result = self._diagnostic()\n", + " if result is None:\n", + " z, composite, regime = {}, 0.0, \"WARMUP\"\n", + " else:\n", + " z, composite, regime = result\n", + " self._regimes.append((self.time.date().isoformat(), regime, round(composite, 2)))\n", + " expo = self.exposition[regime]\n", + " self.set_holdings(\n", + " [PortfolioTarget(\"SPY\", expo), PortfolioTarget(\"SHY\", 1.0 - expo)]\n", + " )\n", + " self.debug(\n", + " f\"{self.time.date()} regime={regime} median={composite:+.2f} \"\n", + " f\"z_trend={z.get('z_trend', 0):+.2f} z_slope={z.get('z_slope', 0):+.2f} \"\n", + " f\"z_credit={z.get('z_credit', 0):+.2f} z_gold={z.get('z_gold', 0):+.2f} \"\n", + " f\"-> SPY {expo:.0%} / SHY {1 - expo:.0%}\"\n", + " )\n", + "\n", + " def on_end_of_algorithm(self):\n", + " self.debug(f\"=== Regimes mensuels ({len(self._regimes)} rebalancements) ===\")\n", + " for d, r, m in self._regimes:\n", + " self.debug(f\" {d} {r:>15} median={m:+.2f}\")\n", + "'''" + ] + }, + { + "cell_type": "markdown", + "id": "s4-resultat", + "metadata": {}, + "source": [ + "> **Résultat du run QC Cloud réel** — projet `16239-macro-regimes-28b`, backtest `macro-diagnostic-allocation-28b` (id `260357cfab1dcbbdf685b7fa08622e2a`), créé le 2026-09-15 20:28 UTC via MCP `qc-mcp-lite`. Fenêtre 2016-01-02 → 2026-09-12 : **2 688 séances négociables, 195 ordres**. Le twin cloud du moteur local (mêmes z-scores, même médiane, même règle tabulée, même override STRESS), **sans lookahead** — chaque rebalancement mensuel ne voit que l'historique déjà publié. Les ~18 premiers mois sont en WARMUP 60/40 (fenêtre z de 252 j sur SMA 200 = ~452 séances avant le premier diagnostic).\n", + ">\n", + "> | Métrique (API backtest) | Valeur | Lecture |\n", + "> |---|---|---|\n", + "> | Sharpe ratio | 0.512 | rendement ajusté au risque modeste mais positif sur 10,7 ans |\n", + "> | Rendement annuel composé | 9.864 % | le coût du défensif : chaque mois sous EXPANSION réduit l'exposition |\n", + "> | Drawdown maximal | 15.400 % | à comparer à l'ordre de 34 % subi par SPY seul sur le choc COVID (chiffre public) : le budget STRESS (10 % d'exposition nette) amortit la queue |\n", + "> | Profit net total | +173.694 % | $100 000 → $231 457 |\n", + ">\n", + "> Le journal complet (régime et z-scores à chacun des 128 rebalancements mensuels) est visible sur la page du backtest QuantConnect ; le tableau ci-dessus reporte les statistiques exposées par l'API. Le compromis amortissement/rendement — 15,4 % de drawdown au lieu de ~34 % sur COVID, contre un CAGR sacrifié — est exactement le choix de gestion que la partie 5 pose **ex ante** dans sa table." + ] + }, + { + "cell_type": "markdown", + "id": "i-resultat", + "metadata": {}, + "source": [ + "**Interprétation du diagnostic réel.** (D'après le run `260357cfab1dcbbdf685b7fa08622e2a`, partie 4.)\n", + "\n", + "- **Hypothèse d'évolution, indicateur par indicateur** — ce qui ferait basculer le régime : la pente (avanceur) se retournerait la *première* si le marché commençait à prix une politique plus dure ou plus laxiste ; la tendance SPY est le concomitant le plus rapide à confirmer ; le crédit HYG/LQD valide ou infirme la cassure en quelques séances ; l'or ne compterait qu'en dernier recours (refuge persistant = stress persistant).\n", + "- **Scénario central** vs **scénario adverse** : dans le central, les quatre familles restent cohérentes (même signe) et le régime évolue lentement — le budget de risque de la partie 5 bouge peu. Dans l'adverse, un avanceur (pente) se retourne *contre* un concomitant encore expansif : c'est exactement la configuration NEUTRE prolongée, où la médiane refuse de trancher — et c'est **la bonne réponse**, pas une panne du modèle." + ] + }, + { + "cell_type": "markdown", + "id": "s5-traduction", + "metadata": {}, + "source": [ + "## 5. Traduction : allocation, exposition, budget de risque\n", + "\n", + "Le régime n'est pas un signal d'achat : c'est un **cadre de risque**. La table ci-dessous est une *décision de gestion* posée explicitement — discutable, modifiable, mais **engagée par écrit**, donc auditable. C'est l'anti-thèse du « le marché semble nerveux, réduisons un peu ».\n", + "\n", + "| Régime | Exposition nette max | Vol cible | Sizing | Stops |\n", + "|---|---|---|---|---|\n", + "| EXPANSION | 100 % | 12 % | complet | larges (2 ATR) |\n", + "| NEUTRE | 60 % | 8 % | 2/3 | normaux (1.5 ATR) |\n", + "| RALENTISSEMENT | 30 % | 5 % | 1/3 | serrés (1 ATR) |\n", + "| STRESS | 10 % | 3 % | minimal | tactiques ou flat |\n", + "\n", + "Deux liens dans la série QC : `QC-Py-10-Risk-Management` (dimensionnement, vol cible — le *comment* du budget) et `QC-Py-13-Alpha-Generation` (le *où* dépenser le budget d'exposition). 28b fournit le *combien, selon le régime macro*." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "c-translate", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-15T20:28:23.470933Z", + "iopub.status.busy": "2026-09-15T20:28:23.470933Z", + "iopub.status.idle": "2026-09-15T20:28:23.484353Z", + "shell.execute_reply": "2026-09-15T20:28:23.484353Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Capital final sans diagnostic (toujours expose) : 0.824\n", + "Capital final avec diagnostic (budget par regime) : 1.256\n", + "\n", + "Budgets des 8 derniers mois du synthetique :\n", + " regime exposition_nette_max vol_cible sizing_factor stop_policy\n", + "2026-02-28 EXPANSION 1.0 0.12 1.00 stops larges (2 ATR)\n", + "2026-03-31 EXPANSION 1.0 0.12 1.00 stops larges (2 ATR)\n", + "2026-04-30 NEUTRE 0.6 0.08 0.67 stops normaux (1.5 ATR)\n", + "2026-05-31 NEUTRE 0.6 0.08 0.67 stops normaux (1.5 ATR)\n", + "2026-06-30 RALENTISSEMENT 0.3 0.05 0.33 stops serres (1 ATR)\n", + "2026-07-31 RALENTISSEMENT 0.3 0.05 0.33 stops serres (1 ATR)\n", + "2026-08-31 STRESS 0.1 0.03 0.10 stops tactiques ou flat\n", + "2026-09-30 STRESS 0.1 0.03 0.10 stops tactiques ou flat\n" + ] + } + ], + "source": [ + "from dataclasses import dataclass\n", + "\n", + "\n", + "@dataclass(frozen=True)\n", + "class RiskBudget:\n", + " regime: str\n", + " exposition_nette_max: float # fraction maximale du capital engagee en net\n", + " vol_cible: float # volatilite annuelle visee du portefeuille\n", + " sizing_factor: float # multiplicateur de taille de position\n", + " stop_policy: str\n", + "\n", + "\n", + "def translate(regime):\n", + " \"\"\"Table EXPLICITE : chaque regime a exactement UN budget de risque. Falsifiable.\"\"\"\n", + " table = {\n", + " \"EXPANSION\": RiskBudget(\"EXPANSION\", 1.00, 0.12, 1.00, \"stops larges (2 ATR)\"),\n", + " \"NEUTRE\": RiskBudget(\"NEUTRE\", 0.60, 0.08, 0.67, \"stops normaux (1.5 ATR)\"),\n", + " \"RALENTISSEMENT\": RiskBudget(\"RALENTISSEMENT\", 0.30, 0.05, 0.33, \"stops serres (1 ATR)\"),\n", + " \"STRESS\": RiskBudget(\"STRESS\", 0.10, 0.03, 0.10, \"stops tactiques ou flat\"),\n", + " }\n", + " return table[regime]\n", + "\n", + "\n", + "# Application au carnet synthetique de la partie 3 : budget mois par mois\n", + "budgets = [translate(r) for r in monthly[\"regime\"]]\n", + "budget_df = pd.DataFrame([b.__dict__ for b in budgets], index=monthly.index)\n", + "\n", + "# Cout d'opportunite du defensif : trajectoire cumulee du proxy de marche x budget applique\n", + "retours = df_z[\"z_trend\"].resample(\"ME\").last().diff().fillna(0) * 0.10 # proxy illustratif\n", + "toujours_actif = (1 + retours).cumprod()\n", + "suivi_regime = (1 + retours * budget_df[\"exposition_nette_max\"].values).cumprod()\n", + "print(f\"Capital final sans diagnostic (toujours expose) : {toujours_actif.iloc[-1]:.3f}\")\n", + "print(f\"Capital final avec diagnostic (budget par regime) : {suivi_regime.iloc[-1]:.3f}\")\n", + "print()\n", + "print(\"Budgets des 8 derniers mois du synthetique :\")\n", + "print(budget_df.tail(8).to_string())" + ] + }, + { + "cell_type": "markdown", + "id": "i-translate", + "metadata": {}, + "source": [ + "**Lecture économique de la traduction.**\n", + "\n", + "- Le budget de risque est une **décision, pas une prédiction** : la table ne dit pas « le marché va baisser », elle dit « *si* le diagnostic est RALENTISSEMENT, *alors* l'exposition nette plafonne à 30 % ». La responsabilité du gestionnaire se joue dans la table (ex ante), pas dans la justification après coup.\n", + "- Le **coût d'opportunité du défensif est réel et mesuré** sur le synthétique : chaque mois passé sous EXPANSION avec un budget réduit coûte du rendement si le marché monte quand même. Un cadre honnête affiche ce coût à côté du drawdown évité — l'exercice 3 le mesure sur l'épisode COVID réel de la partie 4.\n", + "- La granularité est **volontairement grossière** (4 régimes) : une table à 10 régimes serait une fausse précision, personne ne sait distinguer 10 états macro avec 4 indicateurs proxy. Le lissage se fait par hystérésis de *décision* (on ne bascule le budget qu'après confirmation), pas en multipliant les états." + ] + }, + { + "cell_type": "markdown", + "id": "s6-exercices", + "metadata": {}, + "source": [ + "## 6. Exercices\n", + "\n", + "Trois exercices appliqués, du plus mécanique au plus interprétatif. Les stubs s'exécutent sans erreur (règle C.1) ; les attendus et anti-pièges sont rassemblés après les énoncés.\n", + "\n", + "1. **Exercice 1 (registre)** — ajouter un 5e indicateur (proxy volatilité) au diagnostic synthétique et tester la bascule.\n", + "2. **Exercice 2 (robustesse)** — médiane contre moyenne : que devient le diagnostic quand une erreur de publication injecte un outlier ?\n", + "3. **Exercice 3 (décision)** — traduire le régime STRESS du run QC réel en budget de risque et mesurer le drawdown évité sur la fenêtre COVID." + ] + }, + { + "cell_type": "markdown", + "id": "e1-enonce", + "metadata": {}, + "source": [ + "### Exercice 1 — Un cinquième indicateur (proxy volatilité)\n", + "\n", + "Construire sur le jeu synthétique de la partie 3 une série `z_vol` : volatilité réalisée sur 21 jours de `z_trend.diff()`, z-scorée, **inversée** (la vol monte = risque OFF). Puis recomposer le diagnostic à 5 indicateurs et comparer le nombre de transitions avec la version à 4.\n", + "\n", + "- **Étape 1** : `vol = z_trend.diff().rolling(21).std()` puis `zscore`.\n", + "- **Étape 2** : ajouter la colonne à `df_z`, relancer `compute_diagnostic`.\n", + "- **Indice** : avec 5 signaux (nombre impair), la médiane redevient une valeur *observée* — plus besoin de moyennener les deux centraux. Quel effet sur la zone grise ?" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "c-ex1", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-15T20:28:23.486869Z", + "iopub.status.busy": "2026-09-15T20:28:23.485864Z", + "iopub.status.idle": "2026-09-15T20:28:23.489870Z", + "shell.execute_reply": "2026-09-15T20:28:23.489870Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Exercice a completer : ajouter z_vol puis recomposer le diagnostic a 5 indicateurs\n" + ] + } + ], + "source": [ + "# Exercice 1 -- stub (a completer par l'etudiant)\n", + "# Etape 1 : z_vol = -zscore(volatilite realisee 21 j de z_trend.diff())\n", + "# Etape 2 : diagnostic_5 = compute_diagnostic(df_z + colonne z_vol)\n", + "# Etape 3 : comparer le nombre de transitions mensuelles 4 vs 5 indicateurs\n", + "\n", + "diagnostic_5_indicateurs = None # TODO etudiant\n", + "\n", + "if diagnostic_5_indicateurs is None:\n", + " print(\"Exercice a completer : ajouter z_vol puis recomposer le diagnostic a 5 indicateurs\")" + ] + }, + { + "cell_type": "markdown", + "id": "e2-enonce", + "metadata": {}, + "source": [ + "### Exercice 2 — Médiane contre moyenne : l'outlier de publication\n", + "\n", + "Injecter une erreur de publication : `z_slope_corrompu = df_z[\"z_slope\"].copy()` avec 5 jours forcés à +5.0 (une erreur de reprise de données, classique). Comparer le composite **médiane** (le moteur) avec un composite **moyenne** `sig.mean(axis=1)` sur la fenêtre corrompue.\n", + "\n", + "- **Étape 1** : construire la version corrompue du DataFrame.\n", + "- **Étape 2** : recalculer les deux composites et leurs régimes.\n", + "- **Étape 3** : compter les jours où la moyenne change le régime mais pas la médiane.\n", + "- **Indice** : avec 4 signaux dont un à +1 « fabriqué », la moyenne monte mécaniquement de +0.25 — dans quelle colonne de la règle tabulée cela fait-il basculer ?" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "c-ex2", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-15T20:28:23.492085Z", + "iopub.status.busy": "2026-09-15T20:28:23.492085Z", + "iopub.status.idle": "2026-09-15T20:28:23.495163Z", + "shell.execute_reply": "2026-09-15T20:28:23.495163Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Exercice a completer : quantifier la robustesse mediane vs moyenne face a l'outlier\n" + ] + } + ], + "source": [ + "# Exercice 2 -- stub (a completer par l'etudiant)\n", + "# Etape 1 : 5 jours de z_slope forces a +5.0 (erreur de publication)\n", + "# Etape 2 : composite mediane (moteur) vs composite moyenne sur la fenetre corrompue\n", + "# Etape 3 : jours ou la moyenne bascule le regime mais pas la mediane\n", + "\n", + "jours_bascule_moyenne_pas_mediane = None # TODO etudiant\n", + "\n", + "if jours_bascule_moyenne_pas_mediane is None:\n", + " print(\"Exercice a completer : quantifier la robustesse mediane vs moyenne face a l'outlier\")" + ] + }, + { + "cell_type": "markdown", + "id": "e3-enonce", + "metadata": {}, + "source": [ + "### Exercice 3 — Le drawdown évité de la fenêtre COVID (run réel)\n", + "\n", + "Dans le tableau du run QC Cloud (partie 4), repérer la fenêtre 2020-02 → 2020-06. Avec `translate()` et le proxy de rendement de l'exercice de la partie 5 (ou un `SPY` téléchargé par ailleurs), mesurer le drawdown cumulé **sans diagnostic** (exposition 100 %) contre **avec diagnostic** (budget du régime de chaque mois).\n", + "\n", + "- **Étape 1** : isoler les mois STRESS / RALENTISSEMENT de la fenêtre.\n", + "- **Étape 2** : appliquer `translate(regime).exposition_nette_max` à chaque mois.\n", + "- **Étape 3** : comparer drawdown max et capital final ; interpréter l'écart résiduel (le diagnostic réagit *pendant* la crise, pas avant).\n", + "- **Indice** : l'override STRESS de la partie 3 est volontairement en retard — quel mois du tableau réel illustre ce retard assumé ?" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "c-ex3", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-15T20:28:23.497168Z", + "iopub.status.busy": "2026-09-15T20:28:23.497168Z", + "iopub.status.idle": "2026-09-15T20:28:23.501710Z", + "shell.execute_reply": "2026-09-15T20:28:23.501206Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Exercice a completer : mesurer le drawdown evite sur la fenetre COVID du run QC\n" + ] + } + ], + "source": [ + "# Exercice 3 -- stub (a completer par l'etudiant)\n", + "# Etape 1 : fenetre COVID du tableau QC reel (2020-02 -> 2020-06), regimes mensuels\n", + "# Etape 2 : exposition = translate(regime).exposition_nette_max par mois\n", + "# Etape 3 : drawdown max et capital final avec vs sans diagnostic + interpretation\n", + "\n", + "drawdown_evite_covid = None # TODO etudiant\n", + "\n", + "if drawdown_evite_covid is None:\n", + " print(\"Exercice a completer : mesurer le drawdown evite sur la fenetre COVID du run QC\")" + ] + }, + { + "cell_type": "markdown", + "id": "e-attendus", + "metadata": {}, + "source": [ + "### Attendus et anti-pièges\n", + "\n", + "**Exercice 1.** *Attendu* : la série z_vol inverseée est bien corrélée à `−z_trend` ; avec 5 signaux, la médiane passe par une valeur observée → la part de NEUTRE bouge (en général à la baisse) et les transitions deviennent *plus* nettes aux virages. *Anti-piège* : oublier le **signe moins** sur z_vol (vol haute = signal −1, pas +1) ; et recomparer les transitions sur des fenêtres **mensuelles** resamplées pareil, sinon on compare du journalier bruité contre du mensuel lissé.\n", + "\n", + "**Exercice 2.** *Attendu* : 5 jours d'outlier suffisent à faire basculer la moyenne vers EXPANSION/NEUTRE haut pendant que la médiane reste immobile (les 3 autres signaux non corrompus l'emportent) — l'écart doit se concentrer **exactement** sur la fenêtre corrompue. *Anti-piège* : injecter l'outlier sur `df_z` puis appeler `signal_from_z` sur le +5.0 donne +1, pas +5 — le seuil a déjà saturé le signal ; c'est bien la *moyenne des signaux* (0.25 × 4 = mécanique) qu'il faut tracer, pas le z-score brut.\n", + "\n", + "**Exercice 3.** *Attendu* : le drawdown évité est important mais **non total** — février 2020 bascule le diagnostic après le début de la chute (l'override exige la conjonction tendance + crédit), et le budget 10 % reste exposé pendant STRESS. L'écart résiduel *est* la leçon : un diagnostic macro borné amortit, il ne neutralise pas. *Anti-piège* : mesurer le drawdown sur le **capital cumulé** (produit des rendements pondérés), pas sur la somme des rendements mensuels ; et ne pas « recaler » les régimes avec le bénéfice du hindsight — on utilise les régimes *tels qu'ils apparaissent dans le tableau au fil de l'eau*." + ] + }, + { + "cell_type": "markdown", + "id": "s7-limites", + "metadata": {}, + "source": [ + "## 7. Limites et honnêteté du socle\n", + "\n", + "- **Proxies ≠ séries officielles.** SPY/IEF/SHY/HYG/LQD/GLD capturent une *image de marché* des indicateurs, pas les grandeurs économiques (l'ETF pente réagit aux anticipations, la vraie pente aux émissions). Les séries ISM/PMI/permis ne sont pas dans QC — verdict **RECOVERABLE-MACHINE** assumé et documenté : le socle est un cadre pédagogique exécutable en cloud, pas une réplique du NBER.\n", + "- **Publication et révisions.** Sur proxies ETF ce lag disparaît (données de marché continues) — c'est un *avantage* opérationnel mais une *différence* d'interprétation : on diagnostique ce que le marché en pense, pas ce que les instituts mesurent.\n", + "- **Single-country US.** La table, les seuils et les proxies sont calibrés US ; transposer au Brésil ou à la zone euro exige de reconstruire le registre (partie 2), pas seulement de changer les tickers.\n", + "- **Le régime n'est pas un signal court terme.** Un diagnostic NEUTRE ne dit rien du mois prochain (lien vers `QC-Py-28` pour la détection prix court terme) ; il cadre l'exposition de *fond*. L'utiliser pour du market timing quotidien est un contresens d'usage, pas une limite du moteur." + ] + }, + { + "cell_type": "markdown", + "id": "conclusion", + "metadata": {}, + "source": [ + "## Conclusion\n", + "\n", + "**Ce que couvre ce livrable** (mapping acceptance #16239, livrable 1) :\n", + "\n", + "- **Support exécutable couvrant les phases macro** : moteur local (`compute_diagnostic` + test synthétique) et run réel QuantConnect Cloud 2019 → 2026-09, COVID inclus.\n", + "- **Indicateurs avancés et concomitants reliés à une décision** : registre taxonomique (partie 2) → diagnostic borné (parties 3-4) → table régime → budget de risque explicite (partie 5).\n", + "- **Exercice appliqué au résultat interprété** : trois exercices, dont la mesure du drawdown évité sur le run réel, avec attendus et anti-pièges.\n", + "\n", + "**Fil de la série** : 28 détecte les régimes sur les prix ; **28b diagnostique depuis l'économie et traduit en budget de risque** ; 10 et 13 dépensent ce budget. Le registre (partie 2) est conçu pour être étendu — le vrai régime avanceur de votre zone d'investissement n'est probablement pas le même que celui du registre US.\n", + "\n", + "See #16239 (livrable 1 sur 4)." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/MyIA.AI.Notebooks/QuantConnect/Python/README.md b/MyIA.AI.Notebooks/QuantConnect/Python/README.md index c4a0c39ef9..2d9fc41e5d 100644 --- a/MyIA.AI.Notebooks/QuantConnect/Python/README.md +++ b/MyIA.AI.Notebooks/QuantConnect/Python/README.md @@ -71,6 +71,7 @@ Suite à l'audit du 5 mai 2026, voici l'état honnête de chaque notebook. **Auc | QC-Py-26-LLM-Trading-Signals | NON EXÉCUTÉ | | | QC-Py-27-Production-Deployment | NON EXÉCUTÉ | | | QC-Py-28-Market-Regime-Detection | NON EXÉCUTÉ | | +| QC-Py-28b-Macro-Cycle-Regimes | EXÉCUTÉ | cellules locales exécutées + run QC Cloud 2016-2026 (diagnostic → allocation) | | QC-Py-30-LSTM-Training | EXÉCUTÉ | | | QC-Py-31-Transformer-Training | EXÉCUTÉ | | | QC-Py-32-RL-DQN-Trading | EXÉCUTÉ | | @@ -189,6 +190,7 @@ Chaque notebook de la série rend visible un geste quantitatif distinct, dans un | [QC-Py-26-LLM-Trading-Signals](QC-Py-26-LLM-Trading-Signals.ipynb) | LLM pour signaux de trading | | [QC-Py-27-Production-Deployment](QC-Py-27-Production-Deployment.ipynb) | Déploiement live, monitoring | | [QC-Py-28-Market-Regime-Detection](QC-Py-28-Market-Regime-Detection.ipynb) | Détection de régimes de marché | +| [QC-Py-28b-Macro-Cycle-Regimes](QC-Py-28b-Macro-Cycle-Regimes.ipynb) | Macro et régimes : diagnostic composite borné, indicateurs → budget de risque | ## Entraînement ML (QC-Py-30 à 32) From 6d671b61ab04990ccf51ff4837263e43611d2d10 Mon Sep 17 00:00:00 2001 From: jsboige Date: Tue, 15 Sep 2026 22:41:19 +0200 Subject: [PATCH 2/2] =?UTF-8?q?feat(qc,#16239):=2028b=20=E2=80=94=20diagno?= =?UTF-8?q?stic=20as-of=20lisible=20via=20runtime=20statistics=20du=20back?= =?UTF-8?q?test=20v2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Run v2 (218da14b) : instrumentation set_runtime_statistic -> Regime as of 2026-09 = EXPANSION (median +0.50, limitrophe : slope -2.10 vs credit +2.29), comptage regimes 2016-2026 (39/39/26/3 STRESS/22 WARMUP). Corrige l'equity finale ($273 694 runtime, la derivation $231 457 etait fausse). Cellule REFERENCE alignee sur main.py v2. Statistiques v1=v2 (logique inchangee). Guards re-passes verts. Co-Authored-By: Claude Sonnet 5 --- .../QC-Py-28b-Macro-Cycle-Regimes.ipynb | 91 ++++++++++++------- 1 file changed, 60 insertions(+), 31 deletions(-) diff --git a/MyIA.AI.Notebooks/QuantConnect/Python/QC-Py-28b-Macro-Cycle-Regimes.ipynb b/MyIA.AI.Notebooks/QuantConnect/Python/QC-Py-28b-Macro-Cycle-Regimes.ipynb index e8bd00c514..90e6c6b9f8 100644 --- a/MyIA.AI.Notebooks/QuantConnect/Python/QC-Py-28b-Macro-Cycle-Regimes.ipynb +++ b/MyIA.AI.Notebooks/QuantConnect/Python/QC-Py-28b-Macro-Cycle-Regimes.ipynb @@ -60,10 +60,10 @@ "id": "c-registre", "metadata": { "execution": { - "iopub.execute_input": "2026-09-15T20:28:22.063451Z", - "iopub.status.busy": "2026-09-15T20:28:22.063451Z", - "iopub.status.idle": "2026-09-15T20:28:22.077176Z", - "shell.execute_reply": "2026-09-15T20:28:22.076094Z" + "iopub.execute_input": "2026-09-15T20:41:08.648101Z", + "iopub.status.busy": "2026-09-15T20:41:08.648101Z", + "iopub.status.idle": "2026-09-15T20:41:08.658822Z", + "shell.execute_reply": "2026-09-15T20:41:08.658822Z" } }, "outputs": [ @@ -184,10 +184,10 @@ "id": "c-moteur", "metadata": { "execution": { - "iopub.execute_input": "2026-09-15T20:28:22.080359Z", - "iopub.status.busy": "2026-09-15T20:28:22.079846Z", - "iopub.status.idle": "2026-09-15T20:28:23.468416Z", - "shell.execute_reply": "2026-09-15T20:28:23.468416Z" + "iopub.execute_input": "2026-09-15T20:41:08.660827Z", + "iopub.status.busy": "2026-09-15T20:41:08.660827Z", + "iopub.status.idle": "2026-09-15T20:41:10.001469Z", + "shell.execute_reply": "2026-09-15T20:41:10.000462Z" } }, "outputs": [ @@ -450,6 +450,13 @@ " else:\n", " z, composite, regime = result\n", " self._regimes.append((self.time.date().isoformat(), regime, round(composite, 2)))\n", + " self.set_runtime_statistic(\"Regime\", regime)\n", + " self.set_runtime_statistic(\"Median\", f\"{composite:+.2f}\")\n", + " self.set_runtime_statistic(\n", + " \"z(trend/slope/credit/gold)\",\n", + " f\"{z.get('z_trend', 0):+.2f} {z.get('z_slope', 0):+.2f} \"\n", + " f\"{z.get('z_credit', 0):+.2f} {z.get('z_gold', 0):+.2f}\",\n", + " )\n", " expo = self.exposition[regime]\n", " self.set_holdings(\n", " [PortfolioTarget(\"SPY\", expo), PortfolioTarget(\"SHY\", 1.0 - expo)]\n", @@ -462,6 +469,13 @@ " )\n", "\n", " def on_end_of_algorithm(self):\n", + " counts = {}\n", + " for _, r, _m in self._regimes:\n", + " counts[r] = counts.get(r, 0) + 1\n", + " self.set_runtime_statistic(\n", + " \"Regimes 2016-2026\",\n", + " \" \".join(f\"{k}:{v}\" for k, v in sorted(counts.items())),\n", + " )\n", " self.debug(f\"=== Regimes mensuels ({len(self._regimes)} rebalancements) ===\")\n", " for d, r, m in self._regimes:\n", " self.debug(f\" {d} {r:>15} median={m:+.2f}\")\n", @@ -473,16 +487,30 @@ "id": "s4-resultat", "metadata": {}, "source": [ - "> **Résultat du run QC Cloud réel** — projet `16239-macro-regimes-28b`, backtest `macro-diagnostic-allocation-28b` (id `260357cfab1dcbbdf685b7fa08622e2a`), créé le 2026-09-15 20:28 UTC via MCP `qc-mcp-lite`. Fenêtre 2016-01-02 → 2026-09-12 : **2 688 séances négociables, 195 ordres**. Le twin cloud du moteur local (mêmes z-scores, même médiane, même règle tabulée, même override STRESS), **sans lookahead** — chaque rebalancement mensuel ne voit que l'historique déjà publié. Les ~18 premiers mois sont en WARMUP 60/40 (fenêtre z de 252 j sur SMA 200 = ~452 séances avant le premier diagnostic).\n", + "> **Résultat du run QC Cloud réel** — projet `16239-macro-regimes-28b`, backtest `macro-diagnostic-allocation-28b-v2-runtimestats` (id `218da14bec7cf45b629d8ebca61a6554`), créé le 2026-09-15 20:38 UTC via MCP `qc-mcp-lite`. Fenêtre 2016-01-02 → 2026-09-12 : **2 688 séances négociables, 195 ordres, 129 rebalancements mensuels**. Le twin cloud du moteur local (mêmes z-scores, même médiane, même règle tabulée, même override STRESS), **sans lookahead** — chaque rebalancement ne voit que l'historique déjà publié. Les ~18 premiers mois sont en WARMUP 60/40 (fenêtre z de 252 j sur SMA 200 = ~452 séances avant le premier diagnostic).\n", + ">\n", + "> **Diagnostic AS OF 2026-09** (runtime statistics du backtest, lisible via l'API) :\n", + ">\n", + "> | Indicateur | z | Signal | |\n", + "> |---|---|---|---|\n", + "> | Tendance SPY/SMA200 (concomitant) | +0.01 | 0 | zone grise |\n", + "> | Pente IEF/SHY (avanceur) | **−2.10** | −1 | très bas : le marché prix une détente |\n", + "> | Crédit HYG/LQD (concomitant) | **+2.29** | +1 | très haut : spreads serrés, risk-on |\n", + "> | Or 3 m inversé (retardé) | +0.53 | +1 | demande de refuge modérée |\n", + ">\n", + "> Médiane des signaux {0, −1, +1, +1} = **+0.50 → régime EXPANSION** — à la limite exacte de la règle tabulée : deux concomitants expansifs (tendance, crédit) emportent la décision malgré un avanceur (pente) très dégradé. C'est la configuration « scénario à surveiller » de la cellule suivante.\n", + ">\n", + "> **Statistiques de l'allocation suivie** (2016 → 2026-09) :\n", ">\n", "> | Métrique (API backtest) | Valeur | Lecture |\n", "> |---|---|---|\n", "> | Sharpe ratio | 0.512 | rendement ajusté au risque modeste mais positif sur 10,7 ans |\n", - "> | Rendement annuel composé | 9.864 % | le coût du défensif : chaque mois sous EXPANSION réduit l'exposition |\n", - "> | Drawdown maximal | 15.400 % | à comparer à l'ordre de 34 % subi par SPY seul sur le choc COVID (chiffre public) : le budget STRESS (10 % d'exposition nette) amortit la queue |\n", - "> | Profit net total | +173.694 % | $100 000 → $231 457 |\n", + "> | Rendement annuel composé | 9.864 % | le coût du défensif : chaque mois hors EXPANSION réduit l'exposition |\n", + "> | Drawdown maximal | 15.400 % | à comparer à l'ordre de 34 % subi par SPY seul sur le choc COVID (chiffre public) : le budget réduit amortit la queue |\n", + "> | Return total | +173.694 % | equity finale $273 694 (runtime statistic) pour $100 000 initiaux |\n", + "> | Régimes (129 rebalancements) | EXPANSION 39 · NEUTRE 39 · RALENTISSEMENT 26 · **STRESS 3** · WARMUP 22 | l'override STRESS est sélectif : 3 déclenchements en 10,7 ans, dont la fenêtre COVID (journal QC) |\n", ">\n", - "> Le journal complet (régime et z-scores à chacun des 128 rebalancements mensuels) est visible sur la page du backtest QuantConnect ; le tableau ci-dessus reporte les statistiques exposées par l'API. Le compromis amortissement/rendement — 15,4 % de drawdown au lieu de ~34 % sur COVID, contre un CAGR sacrifié — est exactement le choix de gestion que la partie 5 pose **ex ante** dans sa table." + "> Le journal complet (régime et z-scores à chacun des 129 rebalancements) est visible sur la page du backtest QuantConnect ; le tableau ci-dessus reporte les statistiques et runtime statistics exposées par l'API. Le compromis amortissement/rendement — 15,4 % de drawdown au lieu de ~34 % sur COVID, contre un CAGR sacrifié — est exactement le choix de gestion que la partie 5 pose **ex ante** dans sa table." ] }, { @@ -490,10 +518,11 @@ "id": "i-resultat", "metadata": {}, "source": [ - "**Interprétation du diagnostic réel.** (D'après le run `260357cfab1dcbbdf685b7fa08622e2a`, partie 4.)\n", + "**Interprétation du diagnostic réel** (as of 2026-09 : EXPANSION, médiane +0.50, limitrophe).\n", "\n", + "- **La lecture immédiate du as of** : l'avanceur (pente, z = −2.10) est très dégradé pendant que les concomitants (crédit z = +2.29, tendance z = +0.01) restent expansifs — la médiane tranche EXPANSION **par exactement +0.50**, la valeur limite. Un seul indicateur qui bouge (la tendance vers −0.5, ou la pente déjà à −1 confirmée par le crédit) fait basculer en NEUTRE : c'est un régime **fragile au sens de la règle tabulée**, ce que la zone grise de la partie 3 formalise.\n", "- **Hypothèse d'évolution, indicateur par indicateur** — ce qui ferait basculer le régime : la pente (avanceur) se retournerait la *première* si le marché commençait à prix une politique plus dure ou plus laxiste ; la tendance SPY est le concomitant le plus rapide à confirmer ; le crédit HYG/LQD valide ou infirme la cassure en quelques séances ; l'or ne compterait qu'en dernier recours (refuge persistant = stress persistant).\n", - "- **Scénario central** vs **scénario adverse** : dans le central, les quatre familles restent cohérentes (même signe) et le régime évolue lentement — le budget de risque de la partie 5 bouge peu. Dans l'adverse, un avanceur (pente) se retourne *contre* un concomitant encore expansif : c'est exactement la configuration NEUTRE prolongée, où la médiane refuse de trancher — et c'est **la bonne réponse**, pas une panne du modèle." + "- **Scénario central** vs **scénario adverse** : dans le central, les quatre familles restent cohérentes et le régime évolue lentement — le budget de risque de la partie 5 bouge peu. Dans l'adverse, un avanceur (pente) se retourne *contre* un concomitant encore expansif : c'est exactement la configuration NEUTRE prolongée, où la médiane refuse de trancher — et c'est **la bonne réponse**, pas une panne du modèle." ] }, { @@ -521,10 +550,10 @@ "id": "c-translate", "metadata": { "execution": { - "iopub.execute_input": "2026-09-15T20:28:23.470933Z", - "iopub.status.busy": "2026-09-15T20:28:23.470933Z", - "iopub.status.idle": "2026-09-15T20:28:23.484353Z", - "shell.execute_reply": "2026-09-15T20:28:23.484353Z" + "iopub.execute_input": "2026-09-15T20:41:10.003973Z", + "iopub.status.busy": "2026-09-15T20:41:10.003973Z", + "iopub.status.idle": "2026-09-15T20:41:10.020031Z", + "shell.execute_reply": "2026-09-15T20:41:10.019525Z" } }, "outputs": [ @@ -633,10 +662,10 @@ "id": "c-ex1", "metadata": { "execution": { - "iopub.execute_input": "2026-09-15T20:28:23.486869Z", - "iopub.status.busy": "2026-09-15T20:28:23.485864Z", - "iopub.status.idle": "2026-09-15T20:28:23.489870Z", - "shell.execute_reply": "2026-09-15T20:28:23.489870Z" + "iopub.execute_input": "2026-09-15T20:41:10.022227Z", + "iopub.status.busy": "2026-09-15T20:41:10.022227Z", + "iopub.status.idle": "2026-09-15T20:41:10.027406Z", + "shell.execute_reply": "2026-09-15T20:41:10.026276Z" } }, "outputs": [ @@ -681,10 +710,10 @@ "id": "c-ex2", "metadata": { "execution": { - "iopub.execute_input": "2026-09-15T20:28:23.492085Z", - "iopub.status.busy": "2026-09-15T20:28:23.492085Z", - "iopub.status.idle": "2026-09-15T20:28:23.495163Z", - "shell.execute_reply": "2026-09-15T20:28:23.495163Z" + "iopub.execute_input": "2026-09-15T20:41:10.029933Z", + "iopub.status.busy": "2026-09-15T20:41:10.029933Z", + "iopub.status.idle": "2026-09-15T20:41:10.033958Z", + "shell.execute_reply": "2026-09-15T20:41:10.033445Z" } }, "outputs": [ @@ -729,10 +758,10 @@ "id": "c-ex3", "metadata": { "execution": { - "iopub.execute_input": "2026-09-15T20:28:23.497168Z", - "iopub.status.busy": "2026-09-15T20:28:23.497168Z", - "iopub.status.idle": "2026-09-15T20:28:23.501710Z", - "shell.execute_reply": "2026-09-15T20:28:23.501206Z" + "iopub.execute_input": "2026-09-15T20:41:10.036964Z", + "iopub.status.busy": "2026-09-15T20:41:10.035964Z", + "iopub.status.idle": "2026-09-15T20:41:10.039920Z", + "shell.execute_reply": "2026-09-15T20:41:10.039920Z" } }, "outputs": [