diff --git a/SIGNALS_FUTURES.md b/SIGNALS_FUTURES.md new file mode 100644 index 0000000..d1e5a0d --- /dev/null +++ b/SIGNALS_FUTURES.md @@ -0,0 +1,78 @@ +# Signaux non disponibles actuellement — Feuille de route + +Ce document liste les signaux macro et de marché que nous ne pouvons pas encore +récupérer gratuitement via yfinance, et les sources qui les rendraient accessibles. + +--- + +## A. Disponibles gratuitement mais non encore intégrés + +Ces sources sont accessibles sans abonnement — implémentation possible. + +| Signal | Source | Délai | Intérêt pour le trade | +|--------|---------|-------|----------------------| +| **CFTC Commitments of Traders (COT)** | cftc.gov (CSV hebdomadaire) | 3 jours ouvrés | Positionnement net des spéculateurs sur or, pétrole, blé, forex — signal de retournement contrarian fort | +| **Baltic Dry Index (BDI)** | Scrapage web (Investing.com) | J+1 | Leading indicator du commerce mondial — baisse = ralentissement industriel global | +| **Fed H.4.1 — Bilan de la Fed (weekly)** | federalreserve.gov (CSV) | 1 semaine | Expansion/contraction du bilan Fed = signal quantitative easing/tightening | +| **US Money Supply M2** | FRED API (gratuit) | ~6 semaines | Croissance M2 → inflation future ; contraction M2 → récession (2022-2023 signal) | +| **CNN Fear & Greed Index** | Scrapage CNN | J | Sentiment extrême (0 = peur max, 100 = avidité max) — contrarian signal timing | +| **AAII Investor Sentiment Survey** | aaii.com (hebdo) | 1 semaine | % bulls/bears retail — extrêmes signalent retournement | +| **FRED Economic Data (40+ séries)** | api.stlouisfed.org (gratuit) | Variable | CPI, PCE, taux chômage U6, leading indicators Conference Board, etc. | +| **US 2Y Treasury yield** | yfinance `^IRX` proxy ou FRED | J | Segment le plus Fed-sensitive — différentiel 2Y/10Y = meilleur signal récession | +| **Breakeven inflation 5Y (TIPS)** | FRED: T5YIE | J+1 | Taux réels implicites = ce que le marché anticipe pour l'inflation | +| **Indice PMI manufacturier (ISM)** | Scrapage ou FRED | Mensuel | PMI < 50 = contraction manufacturière ; PMI > 55 = expansion forte | +| **Google Trends (mots-clés macro)** | pytrends (gratuit) | J | "Recession", "unemployment", "inflation" — sentiment populaire précède souvent les données | +| **Données OPEC production** | opec.org (PDF/RSS) | Mensuel | Production réelle vs quota = signal énergie avant les chiffres EIA | +| **EIA Crude Oil Inventories** | eia.gov (CSV) | Hebdo (mercredi) | Stocks pétrole US = driver court terme du WTI | + +--- + +## B. Nécessitent un abonnement payant (prix modéré) + +| Signal | Source | Coût estimé | Intérêt | +|--------|---------|-------------|---------| +| **CBOE Put/Call Ratio (temps réel)** | CBOE DataShop | ~$50/mois | Sentiment options en temps réel — P/C > 1.2 = panique = timing long | +| **Gamma Exposure (GEX) SPX** | SpotGamma / SqueezeMetrics | ~$50-150/mois | Exposition gamma des market makers = niveaux magnétiques prix, vol des options | +| **Options Flow (dark pools)** | Unusual Whales / FlowAlgo | ~$50-200/mois | Gros blocs institutionnels sur options = signal directionnel fort | +| **Short Interest Ratio** | Ortex / S3 Partners | ~$100/mois | % float vendu à découvert = squeeze potential ou bearish conviction institutionnel | +| **Credit Default Swaps (CDS IG/HY)** | Bloomberg / Markit | $500+/mois | Spread CDS direct = meilleur indicateur crédit (vs HYG qui est proxy) | +| **VIX Term Structure (VX Futures)** | CBOE / IVolatility | ~$30/mois | Contango/backwardation exact de la courbe VIX futures — clé pour stratégies vol | +| **Skew par sous-jacent (GLD, USO, etc.)** | IVolatility / OptionMetrics | ~$50/mois | Skew spécifique par ETF = évalue si les puts/calls sont chers sur TON sous-jacent | +| **Insider Trading en temps réel** | OpenInsider (partiellement gratuit) | ~$50/mois pour alertes | Achats/ventes dirigeants = signal fondamental avant résultats | +| **Earnings Revision Score** | Factset / Bloomberg | $500+/mois | Révisions de bénéfices = meilleur driver des actions sur 3-6 mois | + +--- + +## C. Signaux structurellement difficiles à obtenir + +Ces données sont soit OTC, soit publiées avec un délai important, soit agrégées uniquement +par des institutions qui ne les partagent pas publiquement. + +| Signal | Problème d'accès | Intérêt si disponible | +|--------|-----------------|----------------------| +| **Réserves d'or des banques centrales** | FMI publie avec 2-3 mois de délai ; achats réels souvent cachés | Demande physique institutionnelle = pression prix or moyen terme | +| **Bilans détaillés BCE/PBoC/BoJ** | Données disponibles mais complexes à parser (formats hétérogènes, langues) | Expansion monétaire mondiale = signal reflation/inflation globale | +| **Positionnement hedge funds (13F)** | SEC 13F = 45 jours de délai après fin du trimestre | Trop tardif pour le trading — anecdotique | +| **Dettes souveraines EM (libellées en USD)** | Banque Mondiale, FMI — trimestriels/annuels | Signal de crise de dette EM avant que ça se voit sur EEM/EMB | +| **Prix physiques vs prix futures (basis)** | OTC / brokers spécialisés matières premières | Basis pétrole = signal supply/demand physique réel vs spéculatif | +| **Dark pool prints (actions)** | FINRA publie avec délai ; temps réel = ~$500+/mois | Flow institutionnel avant les mouvements visibles | +| **Inventaires métaux LME (Cu, Al, Zn, Ni)** | LME.com — partiellement gratuit | Stocks physiques cuivre/aluminium = confirmation Dr Copper signal | +| **Taux SOFR / repo overnight** | NY Fed publie J+1 — utilisable | Stress du marché repo = signal crise liquidité avant VIX | +| **Global PMI composite (JP Morgan)** | IHS Markit / S&P — abonnement | PMI monde = leading indicator croissance globale avant EEM | +| **Inflation swap rates (5Y5Y forward)** | Bloomberg uniquement | Anticipations inflation long terme des pros — différent du breakeven TIPS | + +--- + +## D. Priorités d'implémentation suggérées + +Par ordre de valeur/effort : + +1. **FRED API (gratuit)** — M2, breakeven 5Y, US 2Y yield, leading indicators → ±2h de dev +2. **CFTC COT (gratuit, hebdo)** — COT or + pétrole + blé → signal contrarian puissant → ±4h de dev +3. **EIA inventories pétrole (gratuit)** — driver court terme WTI → ±2h de dev +4. **CNN Fear & Greed (scrapage)** — sentiment composite → ±1h de dev +5. **Baltic Dry Index (scrapage)** — commerce mondial → ±1h de dev +6. **LME stocks cuivre** — confirmation signal cuivre → ±2h de dev +7. **SpotGamma GEX** (~$50/mois) — niveaux gamma SPX → impact direct stratégie +8. **CBOE P/C ratio** (~$50/mois) — timing options → impact direct +9. **VIX term structure** (~$30/mois) → améliore pilier 3e diff --git a/backend/services/ai_analyzer.py b/backend/services/ai_analyzer.py index 1b7d5a3..f1b2633 100644 --- a/backend/services/ai_analyzer.py +++ b/backend/services/ai_analyzer.py @@ -286,7 +286,13 @@ PILIER 3 — SIGNAUX DE PRIX (35 pts max) 3b. Énergie & Matières prem. (0-7): or, pétrole, gaz, cuivre, blé — direction et momentum 3c. Forex (0-7): USD index, EUR/USD, paires émergentes — cohérence avec pattern 3d. Actions & Indices (0-7): SPX, NDX, rotation sectorielle, breadth, sentiment - 3e. Volatilité (VIX/IV) (0-7): régime de vol, coût options, skew — favorable à la stratégie ? + 3e. Volatilité & Surface de vol (0-7): + - Régime VIX (niveau absolu et tendance) + - SKEW Index: si >135 → queues chères → PRÉFÉRER spreads (budget = max loss limité) + si <115 → vol bon marché → envisager straddles/strangles si catalyseur binaire + - VVIX: si >100 → straddles/strangles trop chers → préférer directionnels + - OVX (>40) / GVZ (>22): vol sectorielle élevée sur le sous-jacent → primes gonflées, spreads + - Régime surface: contango_calm (idéal vendre vol) vs backwardation_panic (primes explosées) PILIER 4 — RISQUE / RÉCOMPENSE (15 pts max) 4a. Asymétrie R/R (0-10): ratio gain potentiel / prime payée / perte max pour ~1000€ @@ -419,11 +425,42 @@ def score_patterns_with_context( "dominant_scenario": dominant, "scenario_scores": scenarios.get("scores", {}), "asset_class_bias": bias_for_class, + # ── Signaux historiques (phase 1) ───────────────────────────── "vix": gauges.get("vix", {}).get("value"), "yield_slope_pct": gauges.get("slope_10y3m", {}).get("value"), "gold_copper_ratio": gauges.get("gold_copper_ratio", {}).get("value"), "brent_1d_pct": gauges.get("brent", {}).get("change_pct"), "spx_vs_200d_pct": gauges.get("spx_vs_200d", {}).get("value"), + # ── Surface de volatilité (phase 2) ────────────────────────── + # SKEW >135 = queues chères → spreads; <115 = vol bon marché → straddles + "skew_index": gauges.get("skew", {}).get("value"), + # VVIX >100 = straddles/strangles trop chers → préférer directionnels + "vvix": gauges.get("vvix", {}).get("value"), + # Vol sectorielle spécifique au sous-jacent + "oil_vol_ovx": gauges.get("ovx", {}).get("value"), + "gold_vol_gvz": gauges.get("gvz", {}).get("value"), + # Régime composite: contango_calm|normal|tail_risk_elevated|backwardation_panic|complacency_hedged + "vol_surface_regime": gauges.get("vol_surface_regime", {}).get("note"), + # ── Rotation sectorielle (phase 2) ──────────────────────────── + # Positif = tech > défensifs = risk-on; négatif = rotation défensive + "tech_vs_staples_pct": gauges.get("xlk_xlp_momentum", {}).get("value"), + # Négatif = banques < marché = stress économique anticipé + "financials_vs_spx_pct": gauges.get("xlf_spx_ratio", {}).get("value"), + # ── Global / Marchés émergents (phase 2) ───────────────────── + "em_equity_1d_pct": gauges.get("eem", {}).get("change_pct"), + "em_bonds_1d_pct": gauges.get("emb", {}).get("change_pct"), + "china_equity_1d_pct": gauges.get("fxi", {}).get("change_pct"), + # Ratio EM vs SPX: positif = croissance globale; négatif = fuite vers US + "em_vs_us_divergence_pct": gauges.get("eem_spx_ratio", {}).get("value"), + # ── Carry / Risk-off (phase 2) ──────────────────────────────── + # Négatif = JPY s'apprécie = carry unwind = risk-off global + "usdjpy_1d_pct": gauges.get("usdjpy", {}).get("change_pct"), + # ── Long bonds / Qualité (phase 2) ──────────────────────────── + # Positif = flight to quality = risk-off; négatif = taux longs remontent + "long_bond_tlt_1d_pct": gauges.get("tlt", {}).get("change_pct"), + # ── Métaux (phase 2) ────────────────────────────────────────── + # Ratio Ag/Au: >0.016 = argent > or = risk-on industriel; <0.012 = defensive metals + "silver_gold_ratio": gauges.get("silver_gold_ratio", {}).get("value"), } pattern_blocks.append({ @@ -451,17 +488,50 @@ def score_patterns_with_context( macro_section = "" if macro_regime: sc = macro_regime.get("scenarios", {}) + gauges_g = macro_regime.get("gauges", {}) dom = sc.get("dominant", "incertain") sc_scores = sc.get("scores", {}) + # Extract key new signals for global macro summary + _skew = gauges_g.get("skew", {}).get("value") + _vvix = gauges_g.get("vvix", {}).get("value") + _ovx = gauges_g.get("ovx", {}).get("value") + _vol_r = gauges_g.get("vol_surface_regime", {}).get("note", "normal") + _tech_st = gauges_g.get("xlk_xlp_momentum", {}).get("value") + _xlf_spx = gauges_g.get("xlf_spx_ratio", {}).get("value") + _eem_c = gauges_g.get("eem", {}).get("change_pct") + _usdjpy_c = gauges_g.get("usdjpy", {}).get("change_pct") + _tlt_c = gauges_g.get("tlt", {}).get("change_pct") + _sgr = gauges_g.get("silver_gold_ratio", {}).get("value") + + def _fmt(v, unit="", decimals=1): + return f"{v:.{decimals}f}{unit}" if v is not None else "n/d" + macro_section = f""" -RÉGIME MACRO ACTUEL (30 compteurs agrégés): +RÉGIME MACRO ACTUEL (50 compteurs — 29 tickers + 9 métriques dérivées): - Scénario dominant: {dom.upper()} | Scores: {json.dumps(sc_scores, ensure_ascii=False)} -- Instruction: Intègre ce régime dans les piliers prix (3a taux, 3b énergie, 3d indices, 3e VIX). - Chaque pattern reçoit un champ "macro_regime.asset_class_bias" indiquant la compatibilité - (bullish+/bullish/neutral/bearish/bearish+/defensive) de sa classe d'actif avec le scénario dominant. - → "bullish+" = conditions très favorables pour ce pattern → majore 3b ou 3d selon la classe - → "bearish" ou "bearish+" = conditions défavorables → minore 3b ou 3d - Indique dans "summary": [GOLDILOCKS|STAGFLATION|RÉCESSION|DÉSINFLATION|CRISE] + [SUPPORTING|NEUTRAL|CONTRA] + +SURFACE DE VOLATILITÉ (pilier 3e — impact direct stratégie options): +- SKEW Index: {_fmt(_skew, '', 0)} | Régime vol: {_vol_r} | VVIX: {_fmt(_vvix, '', 0)} | OVX: {_fmt(_ovx, '', 0)} + → SKEW >135 = queues chères → SPREADS (pas de straddles). VVIX >100 = primes gonflées → directionnels. + → {_vol_r} = {"contango calme: idéal spreads bon marché" if _vol_r == "contango_calm" else "backwardation/panique: options très chères, primes à vendre" if _vol_r == "backwardation_panic" else "tail risk élevé: protection queues = faveur spreads définis" if _vol_r == "tail_risk_elevated" else "environnement normal"} + +ROTATION SECTORIELLE & RISQUE (pilier 3d + 3e): +- Tech vs Défensifs (XLK-XLP): {_fmt(_tech_st, '%pts')} | Financières vs S&P: {_fmt(_xlf_spx, '%pts')} + → {"Risk-on sectoriel fort" if _tech_st and _tech_st > 0.5 else "Rotation défensive ⚠️" if _tech_st and _tech_st < -0.5 else "Neutre sectoriel"} + → {"Banques saines = pas de récession" if _xlf_spx and _xlf_spx > 0.3 else "Stress bancaire anticipé ⚠️" if _xlf_spx and _xlf_spx < -0.3 else ""} + +GLOBAL / CARRY / QUALITÉ (pilier 3a + 3d): +- EM Actions J+1: {_fmt(_eem_c, '%')} | USD/JPY J+1: {_fmt(_usdjpy_c, '%')} | TLT (20Y bonds) J+1: {_fmt(_tlt_c, '%')} +- Ratio Ag/Or: {_fmt(_sgr, '', 5)} + → {"EM surperforme = croissance globale" if _eem_c and _eem_c > 0.5 else "EM stress = fuite vers US ⚠️" if _eem_c and _eem_c < -1.0 else ""} + → {"JPY s'apprécie = carry unwind = risk-off ⚠️" if _usdjpy_c and _usdjpy_c < -0.8 else "JPY faible = risk-on carry actif" if _usdjpy_c and _usdjpy_c > 0.5 else ""} + → {"TLT monte = flight to quality = bonds longs demandés" if _tlt_c and _tlt_c > 0.3 else "TLT baisse = taux longs remontent = inflation/risk-on" if _tlt_c and _tlt_c < -0.3 else ""} + +Instructions de notation: +- Intègre le régime dans 3a (taux: slope+TLT), 3b (énergie+OVX), 3d (indices+XLF+EM), 3e (SKEW+VVIX+régime) +- "macro_regime.asset_class_bias" dans chaque pattern → majore/minore 3b ou 3d +- "macro_regime.skew_index" + "vol_surface_regime" → influence directe sur le choix de stratégie dans "recommended_trade" +- Indique dans "summary": [GOLDILOCKS|STAGFLATION|RÉCESSION|DÉSINFLATION|CRISE] + [SUPPORTING|NEUTRAL|CONTRA] """ user = f"""CONTEXTE GLOBAL: diff --git a/backend/services/data_fetcher.py b/backend/services/data_fetcher.py index 92bdd3a..d9d2d6b 100644 --- a/backend/services/data_fetcher.py +++ b/backend/services/data_fetcher.py @@ -292,21 +292,44 @@ def get_economic_calendar() -> List[Dict[str, Any]]: MACRO_GAUGE_CONFIG = [ # (id, label, ticker, unit, bloc) + # ── Liquidité / Taux ───────────────────────────────────────────────────── ("dxy", "Dollar DXY", "DX-Y.NYB", "index", "liquidite"), ("us10y", "UST 10Y", "^TNX", "%", "liquidite"), ("us3m", "UST 3M", "^IRX", "%", "liquidite"), ("tips", "TIPS ETF", "TIP", "$", "liquidite"), + ("tlt", "Obligations 20Y+ (TLT)", "TLT", "$", "liquidite"), + # ── Crédit ─────────────────────────────────────────────────────────────── ("vix", "VIX", "^VIX", "pts", "credit"), ("hyg", "HY Bonds (HYG)", "HYG", "$", "credit"), ("lqd", "IG Bonds (LQD)", "LQD", "$", "credit"), ("ief", "Trésor 7-10Y (IEF)", "IEF", "$", "credit"), + # ── Énergie ────────────────────────────────────────────────────────────── ("brent", "Brent", "BZ=F", "$", "energie"), ("ng", "Gaz naturel", "NG=F", "$", "energie"), + # ── Métaux ─────────────────────────────────────────────────────────────── ("gold", "Or", "GC=F", "$", "metaux"), + ("silver", "Argent (Silver)", "SI=F", "$/oz", "metaux"), ("copper", "Cuivre", "HG=F", "$/lb", "metaux"), + # ── Croissance US ───────────────────────────────────────────────────────── ("spx", "S&P 500", "^GSPC", "pts", "croissance"), ("iwm", "Russell 2000", "IWM", "$", "croissance"), ("xli", "Industriels XLI", "XLI", "$", "croissance"), + # ── Secteurs US (rotation) ──────────────────────────────────────────────── + ("xlk", "Tech (XLK)", "XLK", "$", "secteurs"), + ("xlf", "Financières (XLF)", "XLF", "$", "secteurs"), + ("xlp", "Conso. défensif (XLP)", "XLP", "$", "secteurs"), + ("xlu", "Utilities (XLU)", "XLU", "$", "secteurs"), + # ── Volatilité de surface ───────────────────────────────────────────────── + ("vvix", "VVIX (vol-of-vol)", "^VVIX", "pts", "volatilite"), + ("skew", "CBOE SKEW", "^SKEW", "pts", "volatilite"), + ("ovx", "Pétrole Vol (OVX)", "^OVX", "pts", "volatilite"), + ("gvz", "Or Vol (GVZ)", "^GVZ", "pts", "volatilite"), + # ── Global / EM ─────────────────────────────────────────────────────────── + ("eem", "EM Actions (EEM)", "EEM", "$", "global"), + ("emb", "EM Bonds (EMB)", "EMB", "$", "global"), + ("fxi", "Chine Large Cap (FXI)", "FXI", "$", "global"), + # ── Forex risk-off ──────────────────────────────────────────────────────── + ("usdjpy", "USD/JPY", "USDJPY=X", "pts", "forex_ro"), ] SCENARIO_META = { @@ -337,7 +360,7 @@ def get_macro_gauges() -> Dict[str, Any]: from concurrent.futures import ThreadPoolExecutor, as_completed raw: Dict[str, Any] = {} - with ThreadPoolExecutor(max_workers=min(len(MACRO_GAUGE_CONFIG), 12)) as exe: + with ThreadPoolExecutor(max_workers=min(len(MACRO_GAUGE_CONFIG), 20)) as exe: futures = { exe.submit(get_quote, ticker): (gid, label, ticker, unit, bloc) for gid, label, ticker, unit, bloc in MACRO_GAUGE_CONFIG @@ -414,6 +437,72 @@ def get_macro_gauges() -> Dict[str, Any]: else ("parité" if rel_perf > -0.2 else "large caps dominants (défensif)")), } + # Derived: Silver / Gold ratio (Silver oz / Gold oz — proxy risk-on metals) + # <0.012 = or surperforme = risk-off; >0.016 = argent surperforme = risk-on industrie + sv = raw.get("silver", {}).get("value") + gv_ = raw.get("gold", {}).get("value") + sgr = round(sv / gv_, 5) if (sv and gv_) else None + raw["silver_gold_ratio"] = { + "id": "silver_gold_ratio", "label": "Ratio Argent/Or", + "ticker": None, "value": sgr, "change_pct": None, "unit": "ratio", "bloc": "derive", + "note": ("argent surperforme (risk-on industriel)" if sgr and sgr > 0.016 + else ("neutre" if sgr and sgr > 0.012 else ("or surperforme (risk-off)" if sgr else None))), + } + + # Derived: Tech vs Consumer Staples relative performance (XLK vs XLP) + # Positive = tech > défensifs = risk-on; negative = rotation défensive = ralentissement + xlk_c = raw.get("xlk", {}).get("change_pct") or 0.0 + xlp_c = raw.get("xlp", {}).get("change_pct") or 0.0 + tech_vs_staples = round(xlk_c - xlp_c, 2) + raw["xlk_xlp_momentum"] = { + "id": "xlk_xlp_momentum", "label": "Tech vs Défensifs (XLK-XLP)", + "ticker": None, "value": tech_vs_staples, "change_pct": None, "unit": "pts%", "bloc": "derive", + "note": ("tech > défensifs (risk-on fort)" if tech_vs_staples > 0.5 + else ("parité" if tech_vs_staples > -0.5 else "rotation défensive ⚠️")), + } + + # Derived: Financials vs S&P relative performance (XLF vs SPX) + # XLF is a leading indicator — underperformance signals credit/economic stress ahead + xlf_c = raw.get("xlf", {}).get("change_pct") or 0.0 + xlf_vs_spx = round(xlf_c - spx_c_val, 2) + raw["xlf_spx_ratio"] = { + "id": "xlf_spx_ratio", "label": "Financières vs S&P (XLF-SPX)", + "ticker": None, "value": xlf_vs_spx, "change_pct": None, "unit": "pts%", "bloc": "derive", + "note": ("banques > marché (expansion crédit)" if xlf_vs_spx > 0.3 + else ("parité" if xlf_vs_spx > -0.3 else "banques < marché ⚠️ (stress crédit)")), + } + + # Derived: EM vs US equity relative performance (EEM vs SPX) + # Positive = global risk-on; negative = fuite vers US (dollar strength, EM stress) + eem_c = raw.get("eem", {}).get("change_pct") or 0.0 + eem_vs_spx = round(eem_c - spx_c_val, 2) + raw["eem_spx_ratio"] = { + "id": "eem_spx_ratio", "label": "EM vs S&P (EEM-SPX)", + "ticker": None, "value": eem_vs_spx, "change_pct": None, "unit": "pts%", "bloc": "derive", + "note": ("EM > US (croissance globale)" if eem_vs_spx > 0.3 + else ("parité" if eem_vs_spx > -0.5 else "fuite vers US ⚠️ (EM stress/dollar fort)")), + } + + # Derived: Vol surface regime — composite classification from VIX + VVIX + SKEW + vix_v = raw.get("vix", {}).get("value") or 20.0 + vvix_v = raw.get("vvix", {}).get("value") or 85.0 + skew_v = raw.get("skew", {}).get("value") or 115.0 + if vix_v < 15 and vvix_v < 90 and skew_v < 120: + vol_regime = "contango_calm" # ideal pour vendre vol ou spreads bon marché + elif vix_v > 30 or vvix_v > 110: + vol_regime = "backwardation_panic" # stress aigu — options chères, spreads larges + elif skew_v > 140 and vix_v > 20: + vol_regime = "tail_risk_elevated" # marché achète protection queue = méfiance + elif vix_v < 20 and skew_v > 130: + vol_regime = "complacency_hedged" # calme apparent mais queues protégées + else: + vol_regime = "normal" + raw["vol_surface_regime"] = { + "id": "vol_surface_regime", "label": "Régime Surface de Vol", + "ticker": None, "value": None, "change_pct": None, "unit": "regime", "bloc": "derive", + "note": vol_regime, + } + return _sanitize_floats(raw) @@ -449,6 +538,22 @@ def score_macro_scenarios(gauges: Dict[str, Any]) -> Dict[str, Any]: iwm_c = gc("iwm") xli_c = gc("xli") rel_perf = gv("iwm_spx_ratio") or 0.0 # Russell vs S&P relative perf + # ── Nouveaux signaux (phase 2 — 50 compteurs) ──────────────────────────── + skew_v = gv("skew") or 115.0 # CBOE SKEW: normal ~115, élevé >130, extrême >145 + vvix_v = gv("vvix") or 85.0 # Vol-of-vol: normal ~85, élevé >100, panique >115 + ovx_v = gv("ovx") or 25.0 # Oil vol implicite + gvz_v = gv("gvz") or 17.0 # Gold vol implicite + tlt_c = gc("tlt") # Long bonds 20Y+ (hausse = flight to quality) + xlk_c = gc("xlk") # Tech (hausse = risk-on sectoriel) + xlf_c = gc("xlf") # Financières (baisse = stress crédit) + xlp_c = gc("xlp") # Conso. défensif (hausse = rotation défensive) + xlu_c = gc("xlu") # Utilities (hausse = rotation défensive) + eem_c = gc("eem") # EM actions (hausse = croissance globale) + emb_c = gc("emb") # EM bonds (baisse = crise liquidité EM) + fxi_c = gc("fxi") # Chine equities + usdjpy_c = gc("usdjpy") # USD/JPY (baisse = JPY s'apprécie = risk-off/panique carry) + silver_c = gc("silver") # Argent (surperf or = risk-on industriel) + tech_vs_staples = gv("xlk_xlp_momentum") or 0.0 scores: Dict[str, int] = {} reasons: Dict[str, List[str]] = {} @@ -470,6 +575,12 @@ def score_macro_scenarios(gauges: Dict[str, Any]) -> Dict[str, Any]: if vs200 > 5: s += 15; r.append(f"S&P+{vs200}% vs 200j") elif vs200 > 0: s += 7 if copper_c > 0.5: s += 10; r.append("Cuivre↑") + # Signaux phase 2 + if skew_v < 115: s += 6; r.append(f"SKEW {skew_v:.0f} (no tail hedge = complacency)") + if vvix_v < 85: s += 5; r.append(f"VVIX {vvix_v:.0f} (vol stable)") + if tech_vs_staples > 0.5: s += 7; r.append("Tech > Défensifs (risk-on sectoriel)") + if eem_c > 0.3: s += 6; r.append("EM↑ (croissance globale)") + if usdjpy_c > 0.2: s += 4; r.append("JPY↓ (carry actif = risk-on)") scores["goldilocks"] = min(100, s); reasons["goldilocks"] = r # DÉSINFLATION / BAISSE DE TAUX @@ -483,6 +594,10 @@ def score_macro_scenarios(gauges: Dict[str, Any]) -> Dict[str, Any]: if vs200 is not None and vs200 > 0: s += 20; r.append("S&P au-dessus 200j") if hyg_c > 0: s += 10; r.append("HYG↑") if gold_c > 0 and brent_c < 0: s += 10; r.append("Or↑+Brent↓ (taux réels ↓)") + # Signaux phase 2 + if tlt_c > 0.5: s += 12; r.append("TLT↑↑ (taux 20Y baissent = désinflation confirmée)") + elif tlt_c > 0.2: s += 6; r.append("TLT↑ (bonds longs soutiennent)") + if xlf_c > 0: s += 5; r.append("XLF↑ (banques = anticipent baisse taux)") scores["desinflation"] = min(100, s); reasons["desinflation"] = r # STAGFLATION — inflation + croissance faible @@ -497,6 +612,11 @@ def score_macro_scenarios(gauges: Dict[str, Any]) -> Dict[str, Any]: if gold_c > 0.5: s += 15; r.append("Or↑ (protection inflation)") if copper_c < 0: s += 15; r.append("Cuivre↓ (demande faible)") if vix > 18: s += 10; r.append("VIX élevé") + # Signaux phase 2 + if xlp_c > xlk_c + 0.5: s += 8; r.append("Défensifs > Tech (rotation stagflationniste)") + if xlu_c > 0.4: s += 6; r.append("Utilities↑ (rotation vers revenus stables)") + if skew_v > 130: s += 5; r.append(f"SKEW {skew_v:.0f} (tail risk croissant)") + if tlt_c < -0.3: s += 5; r.append("TLT↓ (taux longs remontent = inflation persistante)") scores["stagflation"] = min(100, s); reasons["stagflation"] = r # RÉCESSION @@ -514,6 +634,15 @@ def score_macro_scenarios(gauges: Dict[str, Any]) -> Dict[str, Any]: if hyg_c < -0.5: s += 15; r.append("HYG↓ (spreads s'écartent)") elif hyg_c < 0: s += 5 if gold_c > 0.3: s += 10; r.append("Or↑ (refuge)") + # Signaux phase 2 + if tlt_c > 0.5: s += 15; r.append("TLT↑↑ (fuite vers bonds longs = signal recessionnaire fort)") + elif tlt_c > 0.2: s += 7; r.append("TLT↑ (obligations soutenues)") + if xlf_c < -1.0: s += 12; r.append("Financières↓↓ (banques = leading indicator récession)") + elif xlf_c < -0.3: s += 5 + if eem_c < -1.0: s += 8; r.append("EM↓ (global slowdown)") + if usdjpy_c < -1.0: s += 10; r.append("JPY↑↑ (carry unwind = risk-off global)") + elif usdjpy_c < -0.5: s += 5 + if skew_v > 135: s += 8; r.append(f"SKEW {skew_v:.0f} (tail risk extrême)") scores["recession"] = min(100, s); reasons["recession"] = r # CRISE DE LIQUIDITÉ @@ -530,6 +659,16 @@ def score_macro_scenarios(gauges: Dict[str, Any]) -> Dict[str, Any]: if gold_c > 1.0 and copper_c < -1.0: s += 20; r.append("Or↑+Cuivre↓ (fuite sécurité)") if dxy_c > 1.0: s += 15; r.append("Dollar↑↑") if ief_c > 0.5: s += 10; r.append("Obligations souveraines↑↑") + # Signaux phase 2 — les meilleurs indicateurs de crise liquide + if skew_v > 145: s += 15; r.append(f"SKEW {skew_v:.0f} — extrême tail risk (panique protection)") + elif skew_v > 135: s += 8 + if vvix_v > 115: s += 15; r.append(f"VVIX {vvix_v:.0f} — vol-of-vol panique") + elif vvix_v > 100: s += 8; r.append(f"VVIX {vvix_v:.0f} — vol élevée") + if usdjpy_c < -1.5: s += 15; r.append("JPY↑↑↑ (carry trade unwind = panique globale)") + elif usdjpy_c < -0.8: s += 7; r.append("JPY↑ (risk-off carry)") + if xlf_c < -2.0: s += 15; r.append("Banques↓↓ (stress bancaire systémique)") + elif xlf_c < -1.0: s += 7 + if emb_c < -1.0: s += 10; r.append("EM Bonds↓ (fuite liquidité EM)") scores["crise_liquidite"] = min(100, s); reasons["crise_liquidite"] = r # REFLATION — croissance accélère + inflation remonte (cuivre, énergie, small caps explosent) @@ -547,6 +686,12 @@ def score_macro_scenarios(gauges: Dict[str, Any]) -> Dict[str, Any]: if rel_perf > 0.3: s += 10; r.append("Small caps > large (risk-on large)") elif rel_perf > 0: s += 4 if vix < 18: s += 5 + # Signaux phase 2 + if eem_c > 1.0: s += 10; r.append("EM↑↑ (croissance mondiale = reflation globale)") + elif eem_c > 0.3: s += 5; r.append("EM↑ (global risk-on)") + if xlk_c > 1.0: s += 8; r.append("Tech↑↑ (croissance+momentum)") + if silver_c > 1.5: s += 8; r.append("Argent↑↑ (industrial metals = reflation industrielle)") + if usdjpy_c > 0.5: s += 6; r.append("JPY↓ (carry trades actifs = risk-on global)") scores["reflation"] = min(100, s); reasons["reflation"] = r # SOFT LANDING — croissance positive + inflation en repli, pas encore basse @@ -562,6 +707,10 @@ def score_macro_scenarios(gauges: Dict[str, Any]) -> Dict[str, Any]: if xli_c > 0: s += 8; r.append("Industriels positifs") if copper_c > 0: s += 5; r.append("Cuivre stable") if ief_c > 0 and brent_c < 0: s += 7; r.append("Taux baissent + énergie recule") + # Signaux phase 2 + if xlf_c > 0: s += 8; r.append("Financières↑ (banques = économie saine, no recession)") + if eem_c > 0: s += 5; r.append("EM stable (croissance globale intacte)") + if skew_v < 130: s += 4; r.append(f"SKEW {skew_v:.0f} (tail risk non-extrême)") scores["soft_landing"] = min(100, s); reasons["soft_landing"] = r # CHOC INFLATIONNISTE — spike énergie/supply soudain (guerre, OPEC, sécheresse) @@ -578,6 +727,12 @@ def score_macro_scenarios(gauges: Dict[str, Any]) -> Dict[str, Any]: elif vix > 18: s += 5 if copper_c < -0.5: s += 8; r.append("Cuivre↓ (demand destruction)") if ief_c < -0.2: s += 8; r.append("Trésor↓ (taux longs remontent)") + # Signaux phase 2 + if ovx_v > 45: s += 15; r.append(f"OVX {ovx_v:.0f} — vol pétrole extreme (choc supply aigu)") + elif ovx_v > 35: s += 8; r.append(f"OVX {ovx_v:.0f} — vol pétrole élevée") + if gvz_v > 22: s += 8; r.append(f"GVZ {gvz_v:.0f} — vol or élevée (inflation/géo)") + if xlp_c > 0.5: s += 6; r.append("Défensifs↑ (rotation anti-inflation)") + if tlt_c < -0.5: s += 8; r.append("TLT↓↓ (taux 20Y montent = anticipation inflation)") scores["inflation_shock"] = min(100, s); reasons["inflation_shock"] = r ranked = sorted(scores.items(), key=lambda x: x[1], reverse=True)