Files
OpenFin/backend/services/instrument_models.py
2026-07-02 22:34:16 +02:00

772 lines
66 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Instrument Models — Phase 1 : propagation en chaîne réelle.
Architecture DAG (3 couches) :
Layer 0 — Inputs :
- input_event : valeur auto depuis causal_event_analyses (par catégorie template)
- input_manual : valeur utilisateur (unité native → pips via coefficient_to_pips)
Layer 1 — Intermediate : formules agrégeant les inputs par domaine
Layer 2 — Output : formule sommant les nœuds intermédiaires
Évaluation : evaluate_graph() de causal_graphs.py (formula-based DAG).
Lifecycle : montée (rise_days) → plateau → décroissance (absorption_days).
Timeline : simulation jour par jour via simulate_timeline().
"""
import json
import math
from datetime import datetime, timedelta, date as date_type
from typing import Optional
# ── Lifecycle & decay ──────────────────────────────────────────────────────────
def _decay(days: int, absorption: int, dtype: str) -> float:
if days < 0: return 0.0
if dtype == "step": return 1.0 if days <= absorption else 0.0
if dtype == "linear": return max(0.0, 1.0 - days / max(absorption, 1))
lam = 3.0 / max(absorption, 1)
return math.exp(-lam * days)
def _lifecycle(days: int, rise: int, plateau: int, absorption: int, dtype: str) -> float:
"""Montée linéaire → plateau → décroissance (exp/linear/step)."""
if days < 0: return 0.0
if days < rise: return days / max(rise, 1)
if days < rise + plateau: return 1.0
return _decay(days - rise - plateau, absorption, dtype)
# ── Category labels ────────────────────────────────────────────────────────────
CAT_LABELS: dict[str, str] = {
"monetary":"Monétaire","inflation":"Inflation","macro":"Macro/Croissance",
"political":"Politique","flows":"Flux & Réserves","positioning":"Positionnement",
"sentiment":"Sentiment & Risque","credit":"Crédit","earnings":"Bénéfices",
"valuation":"Valorisation","tech":"Technologie","commodity":"Commodités",
"supply":"Offre","output":"Résultat","intermediate":"Couche intermédiaire",
# event categories
"central_bank":"Banques Centrales","monetary_shock":"Surprise Macro",
"geopolitical":"Géopolitique","trade_policy":"Commerce / Tarifs",
"growth_shock":"Choc Croissance","credit_stress":"Stress Crédit",
"technical":"Technique","sentiment":"Sentiment",
"positioning":"Positionnement","unclassified":"Non Classifié",
}
# ── Helper to build formula from list of node IDs ──────────────────────────────
def _sum_formula(*node_ids: str) -> str:
return " + ".join(node_ids)
# ── Built-in instrument model definitions ──────────────────────────────────────
# Each model defines:
# nodes : list of node dicts
# output_node : id of the output node
#
# Node schema:
# id, label, node_type (input_event|input_manual|intermediate|output),
# category, unit (for manual), coefficient_to_pips (for manual),
# event_category (for event nodes), formula (for intermediate/output),
# description, display_col (0=inputs-event, 1=inputs-manual, 2=intermediate, 3=output)
#
# All intermediate/output values are in pips.
# For input_manual : value_in_native_unit * coefficient_to_pips = pips injected into graph.
INSTRUMENT_MODELS: dict[str, dict] = {
# ══════════════════════════════════════════════════════════════════════════════
"EURUSD": {
"name": "EUR/USD",
"description": "Taux de change Euro/Dollar — modèle causal 3 couches avec 4 domaines d'influence",
"output_node": "eurusd",
"nodes": [
# ── Layer 0a : event inputs ───────────────────────────────────────────────
{"id":"in_cb", "label":"Banques Centrales", "node_type":"input_event", "category":"central_bank", "unit":"pips","display_col":0,"description":"Décisions Fed/BCE, minutes, forward guidance. Décroissance exp ~14j.","event_category":"central_bank"},
{"id":"in_macro", "label":"Surprises Macro (données)","node_type":"input_event","category":"monetary_shock","unit":"pips","display_col":0,"description":"CPI, NFP, PIB, PMI US/EU. Décroissance rapide ~12j.","event_category":"monetary_shock"},
{"id":"in_geo", "label":"Risque Géopolitique", "node_type":"input_event", "category":"geopolitical", "unit":"pips","display_col":0,"description":"Conflits, sanctions, tensions. Décroissance linéaire ~21j.","event_category":"geopolitical"},
{"id":"in_trade", "label":"Choc Commercial/Tarifs", "node_type":"input_event", "category":"trade_policy", "unit":"pips","display_col":0,"description":"Tarifs US-UE, accords commerciaux.","event_category":"trade_policy"},
{"id":"in_growth", "label":"Choc Croissance", "node_type":"input_event", "category":"growth_shock", "unit":"pips","display_col":0,"description":"Chocs perspectives croissance.","event_category":"growth_shock"},
{"id":"in_credit", "label":"Stress Crédit/Liquidité", "node_type":"input_event", "category":"credit_stress", "unit":"pips","display_col":0,"description":"Banking stress, spreads IG/HY.","event_category":"credit_stress"},
{"id":"in_technical", "label":"Momentum Technique", "node_type":"input_event", "category":"technical", "unit":"pips","display_col":0,"description":"Cassures niveaux clés, tendances.","event_category":"technical"},
{"id":"in_commodity", "label":"Choc Commodités", "node_type":"input_event", "category":"commodity", "unit":"pips","display_col":0,"description":"Pétrole, gaz, métaux → inflation EU.","event_category":"commodity"},
{"id":"in_sentiment", "label":"Sentiment/Positionnement","node_type":"input_event", "category":"sentiment", "unit":"pips","display_col":0,"description":"Flux institutionnels, risk-on/off.","event_category":"sentiment"},
# ── Layer 0b : manual structural inputs ──────────────────────────────────
{"id":"m_rate_diff_2y", "label":"Spread OIS 2Y USD-EUR", "node_type":"input_manual","category":"monetary", "unit":"bps", "coefficient_to_pips": 1.5, "display_col":1,"description":"Principal driver court terme. Positif = USD plus rémunérateur → pair ↓"},
{"id":"m_fed_path", "label":"Anticipation Fed 12m", "node_type":"input_manual","category":"monetary", "unit":"bps", "coefficient_to_pips": 0.8, "display_col":1,"description":"Cuts attendus → USD ↓ → pair ↑. Hikes → USD ↑ → pair ↓"},
{"id":"m_ecb_path", "label":"Anticipation BCE 12m", "node_type":"input_manual","category":"monetary", "unit":"bps", "coefficient_to_pips":-0.8, "display_col":1,"description":"Cuts BCE → EUR ↓. Hikes → EUR ↑"},
{"id":"m_us_real_rate", "label":"Taux réel US 10Y (TIPS)", "node_type":"input_manual","category":"monetary", "unit":"bps", "coefficient_to_pips":-0.5, "display_col":1,"description":"Hausse → USD attractif → pair ↓"},
{"id":"m_eu_real_rate", "label":"Taux réel EU 10Y", "node_type":"input_manual","category":"monetary", "unit":"bps", "coefficient_to_pips": 0.5, "display_col":1,"description":"Hausse → EUR attractif → pair ↑"},
{"id":"m_carry", "label":"Score carry EUR/USD", "node_type":"input_manual","category":"monetary", "unit":"score", "coefficient_to_pips": 0.6, "display_col":1,"description":"Score attractivité carry (+= EUR avantageux)"},
{"id":"m_us_growth_adv", "label":"Avantage croissance US vs EU","node_type":"input_manual","category":"macro", "unit":"pts%", "coefficient_to_pips":-15.0,"display_col":1,"description":"Différentiel PIB US-EU. US surperform → USD ↑ → pair ↓"},
{"id":"m_eu_pmi", "label":"PMI composite Eurozone", "node_type":"input_manual","category":"macro", "unit":"pts", "coefficient_to_pips": 0.3, "display_col":1,"description":"Expansion EU → EUR ↑"},
{"id":"m_energy_price", "label":"Prix énergie ($/bbl delta)", "node_type":"input_manual","category":"macro", "unit":"$/bbl", "coefficient_to_pips":-0.25,"display_col":1,"description":"Énergie chère → déficit commercial EU → EUR ↓"},
{"id":"m_us_cpi", "label":"CPI US YoY", "node_type":"input_manual","category":"inflation", "unit":"%", "coefficient_to_pips":-0.8, "display_col":1,"description":"Inflation US → Fed hawkish → USD ↑ → pair ↓"},
{"id":"m_eu_cpi", "label":"HICP Eurozone YoY", "node_type":"input_manual","category":"inflation", "unit":"%", "coefficient_to_pips": 0.8, "display_col":1,"description":"Inflation EU → BCE hawkish → EUR ↑"},
{"id":"m_risk_appetite", "label":"Appétit risque mondial", "node_type":"input_manual","category":"sentiment", "unit":"score", "coefficient_to_pips": 0.5, "display_col":1,"description":"Risk-on → sorties USD → pair ↑"},
{"id":"m_vix", "label":"Niveau VIX", "node_type":"input_manual","category":"sentiment", "unit":"pts", "coefficient_to_pips":-0.4, "display_col":1,"description":"VIX spike → flight to USD → pair ↓"},
{"id":"m_eu_fragmentation","label":"Risque fragmentation EU", "node_type":"input_manual","category":"political", "unit":"score", "coefficient_to_pips":-0.5, "display_col":1,"description":"Risque politique EU → EUR ↓"},
{"id":"m_us_political", "label":"Incertitude politique US", "node_type":"input_manual","category":"political", "unit":"score", "coefficient_to_pips": 0.3, "display_col":1,"description":"Incertitude US → USD ↓ → pair ↑"},
{"id":"m_cftc_eur", "label":"Positions nettes EUR (CoT)", "node_type":"input_manual","category":"positioning","unit":"k lots","coefficient_to_pips": 0.1, "display_col":1,"description":"Net long EUR CFTC. Extrême → risque retournement"},
{"id":"m_dollar_reserve", "label":"Demande réserves USD", "node_type":"input_manual","category":"flows", "unit":"score", "coefficient_to_pips":-0.4, "display_col":1,"description":"Demande réserves → USD structurellement fort → pair ↓"},
# ── Layer 1 : intermediate (4 domaines) ──────────────────────────────────
{"id":"layer_monetary", "label":"▶ Pression Monétaire & Taux", "node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"in_cb + in_macro + m_rate_diff_2y + m_fed_path + m_ecb_path + m_us_real_rate + m_eu_real_rate + m_carry",
"description":"Domaine monétaire : taux directeurs, OIS, anticipations Fed/BCE, carry. Driver n°1 de l'EURUSD."},
{"id":"layer_growth", "label":"▶ Pression Macro & Croissance", "node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"in_growth + in_commodity + m_us_growth_adv + m_eu_pmi + m_energy_price + m_us_cpi + m_eu_cpi",
"description":"Domaine macro : croissance relative, inflation, énergie. Impact via anticipations BC."},
{"id":"layer_risk", "label":"▶ Pression Risque & Géopolitique", "node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"in_geo + in_credit + in_sentiment + m_risk_appetite + m_vix + m_eu_fragmentation",
"description":"Domaine risque : géopolitique, stress crédit, aversion au risque (USD safe haven)."},
{"id":"layer_positioning","label":"▶ Pression Positionnement & Flux", "node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"in_trade + in_technical + m_cftc_eur + m_dollar_reserve + m_us_political",
"description":"Domaine positionnement : flux spéculatifs, technicalités, réserves, politique."},
# ── Layer 2 : output ──────────────────────────────────────────────────────
{"id":"eurusd","label":"EUR/USD — Impact Net","node_type":"output","category":"output","unit":"pips","display_col":3,
"formula":"layer_monetary + layer_growth + layer_risk + layer_positioning",
"description":"Pression nette cumulée = Σ(4 domaines). Positif = biais haussier EUR/USD."},
]
},
# ══════════════════════════════════════════════════════════════════════════════
"USDJPY": {
"name": "USD/JPY", "output_node": "usdjpy",
"description": "Carry & safe haven — yield diff 10Y + BoJ + risk appetite",
"nodes": [
{"id":"in_cb", "label":"Banques Centrales", "node_type":"input_event","category":"central_bank", "unit":"pips","display_col":0,"event_category":"central_bank","description":"Fed/BoJ décisions."},
{"id":"in_macro", "label":"Surprises Macro", "node_type":"input_event","category":"monetary_shock","unit":"pips","display_col":0,"event_category":"monetary_shock","description":"NFP, CPI US, Tankan, CPI Japon."},
{"id":"in_geo", "label":"Risque Géopolitique", "node_type":"input_event","category":"geopolitical", "unit":"pips","display_col":0,"event_category":"geopolitical","description":"NK tensions → JPY safe haven demandé."},
{"id":"in_trade", "label":"Choc Commercial", "node_type":"input_event","category":"trade_policy", "unit":"pips","display_col":0,"event_category":"trade_policy","description":"Tarifs US-Japon."},
{"id":"in_growth", "label":"Choc Croissance", "node_type":"input_event","category":"growth_shock", "unit":"pips","display_col":0,"event_category":"growth_shock","description":"Récession → risk-off → JPY."},
{"id":"in_credit", "label":"Stress Crédit", "node_type":"input_event","category":"credit_stress","unit":"pips","display_col":0,"event_category":"credit_stress","description":"Stress → JPY refuge."},
{"id":"in_sentiment", "label":"Sentiment/Flux", "node_type":"input_event","category":"sentiment", "unit":"pips","display_col":0,"event_category":"sentiment","description":"Risk-on/off."},
{"id":"in_technical", "label":"Momentum Technique", "node_type":"input_event","category":"technical", "unit":"pips","display_col":0,"event_category":"technical","description":"Niveaux clés USD/JPY."},
{"id":"m_yield_diff", "label":"Diff. rendement 10Y US-JP","node_type":"input_manual","category":"monetary","unit":"bps","coefficient_to_pips": 1.2,"display_col":1,"description":"Principal driver. Hausse → USD/JPY ↑"},
{"id":"m_fed_path", "label":"Anticipation Fed 12m", "node_type":"input_manual","category":"monetary","unit":"bps","coefficient_to_pips": 0.6,"display_col":1,"description":"Fed hike → USD/JPY ↑"},
{"id":"m_boj_stance", "label":"Biais BoJ (score)", "node_type":"input_manual","category":"monetary","unit":"score","coefficient_to_pips":-8.0,"display_col":1,"description":"Hawkish → JPY ↑ → USD/JPY ↓"},
{"id":"m_jgb_10y", "label":"Rendement JGB 10Y", "node_type":"input_manual","category":"monetary","unit":"bps","coefficient_to_pips":-0.8,"display_col":1,"description":"JGB yield ↑ → JPY attractif → paire ↓"},
{"id":"m_us_real_rate","label":"Taux réel US 10Y", "node_type":"input_manual","category":"monetary","unit":"bps","coefficient_to_pips": 0.6,"display_col":1,"description":"Taux réel US ↑ → USD ↑ vs JPY"},
{"id":"m_risk_appetite","label":"Appétit risque", "node_type":"input_manual","category":"sentiment","unit":"score","coefficient_to_pips":-1.2,"display_col":1,"description":"Risk-off → JPY safe haven → paire ↓"},
{"id":"m_vix", "label":"Niveau VIX", "node_type":"input_manual","category":"sentiment","unit":"pts","coefficient_to_pips":-0.8,"display_col":1,"description":"VIX spike → JPY refuge → paire ↓"},
{"id":"m_carry_momentum","label":"Momentum carry", "node_type":"input_manual","category":"positioning","unit":"score","coefficient_to_pips": 0.4,"display_col":1,"description":"Carry actif → acheteurs USD/JPY"},
{"id":"m_mof_risk", "label":"Risque intervention MoF","node_type":"input_manual","category":"political","unit":"score","coefficient_to_pips":-0.8,"display_col":1,"description":"Probabilité intervention MoF. Hausse → paire ↓ préventif"},
{"id":"m_cftc_jpy", "label":"Net short JPY (CoT)", "node_type":"input_manual","category":"positioning","unit":"k lots","coefficient_to_pips": 0.15,"display_col":1,"description":"Extreme short JPY → risque short squeeze → paire ↓"},
{"id":"layer_rates", "label":"▶ Différentiel Taux/Carry","node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"in_cb + in_macro + m_yield_diff + m_fed_path + m_boj_stance + m_jgb_10y + m_us_real_rate + m_carry_momentum",
"description":"Yield differential, Fed vs BoJ, carry trade momentum."},
{"id":"layer_risk", "label":"▶ Risque & Safe Haven JPY","node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"in_geo + in_credit + in_sentiment + m_risk_appetite + m_vix",
"description":"JPY comme refuge : risk-off, géopolitique, stress crédit."},
{"id":"layer_policy", "label":"▶ Politique & Positionnement","node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"in_trade + in_technical + in_growth + m_mof_risk + m_cftc_jpy",
"description":"Intervention MoF, tarifs US-Japon, technique, CFTC."},
{"id":"usdjpy","label":"USD/JPY — Impact Net","node_type":"output","category":"output","unit":"pips","display_col":3,
"formula":"layer_rates + layer_risk + layer_policy","description":"Pression nette cumulée USD/JPY"},
]
},
# ══════════════════════════════════════════════════════════════════════════════
"XAUUSD": {
"name": "XAU/USD (Or)", "output_node": "xauusd",
"description": "Or/Dollar — taux réels, dollar, géopolitique, banques centrales",
"nodes": [
{"id":"in_cb", "label":"Banques Centrales", "node_type":"input_event","category":"central_bank", "unit":"pips","display_col":0,"event_category":"central_bank","description":"Fed (taux réels) → or."},
{"id":"in_macro", "label":"Surprises Macro", "node_type":"input_event","category":"monetary_shock","unit":"pips","display_col":0,"event_category":"monetary_shock","description":"CPI, PCE → anticipations taux réels → or."},
{"id":"in_geo", "label":"Risque Géopolitique", "node_type":"input_event","category":"geopolitical", "unit":"pips","display_col":0,"event_category":"geopolitical","description":"Conflits → refuge or maximal."},
{"id":"in_credit", "label":"Stress Crédit/Systémique","node_type":"input_event","category":"credit_stress","unit":"pips","display_col":0,"event_category":"credit_stress","description":"Crises bancaires → or refuge absolu."},
{"id":"in_growth", "label":"Choc Croissance", "node_type":"input_event","category":"growth_shock", "unit":"pips","display_col":0,"event_category":"growth_shock","description":"Récession → easing → or ↑."},
{"id":"in_trade", "label":"Choc Commercial", "node_type":"input_event","category":"trade_policy", "unit":"pips","display_col":0,"event_category":"trade_policy","description":"Incertitude → or refuge."},
{"id":"in_commodity","label":"Choc Commodités", "node_type":"input_event","category":"commodity", "unit":"pips","display_col":0,"event_category":"commodity","description":"Inflation inputs (énergie)."},
{"id":"in_sentiment","label":"Sentiment/Flux", "node_type":"input_event","category":"sentiment", "unit":"pips","display_col":0,"event_category":"sentiment","description":"Risk-off flows vers or."},
{"id":"in_technical","label":"Momentum Technique", "node_type":"input_event","category":"technical", "unit":"pips","display_col":0,"event_category":"technical","description":"Cassures ATH, niveaux or."},
{"id":"m_us_real_rate","label":"Taux réel US 10Y (TIPS)","node_type":"input_manual","category":"monetary","unit":"bps","coefficient_to_pips":-2.5,"display_col":1,"description":"DRIVER PRINCIPAL. Chaque -10bps ≈ +25 pips or."},
{"id":"m_dxy", "label":"Indice Dollar (DXY)", "node_type":"input_manual","category":"monetary","unit":"pts","coefficient_to_pips":-3.0,"display_col":1,"description":"Or libellé USD → DXY ↑ = or ↓ mécaniquement."},
{"id":"m_fed_path", "label":"Anticipation Fed 12m","node_type":"input_manual","category":"monetary","unit":"bps","coefficient_to_pips":-1.0,"display_col":1,"description":"Cuts attendus → taux réels ↓ → or ↑."},
{"id":"m_breakeven", "label":"Breakeven inflation 10Y","node_type":"input_manual","category":"inflation","unit":"bps","coefficient_to_pips": 1.5,"display_col":1,"description":"Anticipations inflation → or comme couverture ↑."},
{"id":"m_vix", "label":"Niveau VIX", "node_type":"input_manual","category":"sentiment","unit":"pts","coefficient_to_pips": 0.4,"display_col":1,"description":"VIX spike → or refuge."},
{"id":"m_cb_buying", "label":"Achats CB (tonnes/mois)","node_type":"input_manual","category":"flows","unit":"t","coefficient_to_pips": 2.0,"display_col":1,"description":"Achats banques centrales. Driver structurel majeur depuis 2022."},
{"id":"m_etf_flows", "label":"Flux ETF or (GLD, IAU)","node_type":"input_manual","category":"flows","unit":"tonnes","coefficient_to_pips": 1.5,"display_col":1,"description":"Entrées ETF → demande physique → or ↑."},
{"id":"m_cftc_gold", "label":"Net long or (CoT)", "node_type":"input_manual","category":"positioning","unit":"k lots","coefficient_to_pips": 0.15,"display_col":1,"description":"Extrême long → risque liquidation."},
{"id":"m_fiscal_risk","label":"Risque fiscal US (score)","node_type":"input_manual","category":"macro","unit":"score","coefficient_to_pips": 0.5,"display_col":1,"description":"Déficit/dette US → doutes USD → or refuge."},
{"id":"m_india_china","label":"Demande physique Inde/Chine","node_type":"input_manual","category":"flows","unit":"score","coefficient_to_pips": 0.4,"display_col":1,"description":"Joaillerie, investment. Saisonnalité."},
{"id":"layer_rates","label":"▶ Taux Réels & Dollar","node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"in_cb + in_macro + m_us_real_rate + m_dxy + m_fed_path + m_breakeven",
"description":"Taux réels US = driver fondamental or. DXY = mécanisme de transmission."},
{"id":"layer_demand","label":"▶ Demande & Flux", "node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"m_cb_buying + m_etf_flows + m_cftc_gold + m_india_china + m_fiscal_risk",
"description":"Acheteurs physiques et financiers : CB, ETF, CFTC, Asie."},
{"id":"layer_refuge","label":"▶ Valeur Refuge", "node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"in_geo + in_credit + in_growth + in_trade + in_sentiment + m_vix",
"description":"Or comme couverture : géopolitique, stress systémique, récession."},
{"id":"layer_technical","label":"▶ Technique & Momentum","node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"in_technical + in_commodity",
"description":"Signaux techniques et commodités (coûts extraction)."},
{"id":"xauusd","label":"XAU/USD — Impact Net","node_type":"output","category":"output","unit":"pips","display_col":3,
"formula":"layer_rates + layer_demand + layer_refuge + layer_technical","description":"Pression nette cumulée or/USD"},
]
},
# ══════════════════════════════════════════════════════════════════════════════
"SP500": {
"name": "S&P 500", "output_node": "sp500",
"description": "Indice actions US — taux, bénéfices, risque, liquidités",
"nodes": [
{"id":"in_cb", "label":"Banques Centrales", "node_type":"input_event","category":"central_bank","unit":"pips","display_col":0,"event_category":"central_bank","description":"Fed pivot/hike → SP500 directement."},
{"id":"in_macro", "label":"Surprises Macro", "node_type":"input_event","category":"monetary_shock","unit":"pips","display_col":0,"event_category":"monetary_shock","description":"NFP, CPI, PIB US."},
{"id":"in_geo", "label":"Risque Géopolitique", "node_type":"input_event","category":"geopolitical","unit":"pips","display_col":0,"event_category":"geopolitical","description":"Risk-off → SP500 vendu."},
{"id":"in_trade", "label":"Choc Commercial", "node_type":"input_event","category":"trade_policy","unit":"pips","display_col":0,"event_category":"trade_policy","description":"Tarifs → marges bénéficiaires SP500."},
{"id":"in_growth", "label":"Choc Croissance", "node_type":"input_event","category":"growth_shock","unit":"pips","display_col":0,"event_category":"growth_shock","description":"Récession → SP500 ↓ massif."},
{"id":"in_credit", "label":"Stress Crédit", "node_type":"input_event","category":"credit_stress","unit":"pips","display_col":0,"event_category":"credit_stress","description":"Banking stress → SP500 ↓ brutal."},
{"id":"in_commodity","label":"Choc Commodités", "node_type":"input_event","category":"commodity","unit":"pips","display_col":0,"event_category":"commodity","description":"Énergie → marges opérationnelles."},
{"id":"in_sentiment","label":"Sentiment/Positionnement","node_type":"input_event","category":"sentiment","unit":"pips","display_col":0,"event_category":"sentiment","description":"CTA, hedge fund flows."},
{"id":"in_technical","label":"Momentum Technique", "node_type":"input_event","category":"technical","unit":"pips","display_col":0,"event_category":"technical","description":"MA200, cassures."},
{"id":"m_fed_path", "label":"Anticipation Fed 12m", "node_type":"input_manual","category":"monetary","unit":"bps","coefficient_to_pips":-0.6,"display_col":1,"description":"Cuts → taux discount ↓ → PE expansion → SP500 ↑"},
{"id":"m_real_rate","label":"Taux réel US 10Y", "node_type":"input_manual","category":"monetary","unit":"bps","coefficient_to_pips":-1.5,"display_col":1,"description":"Taux réel = taux d'actualisation. Hausse → PE compression → SP500 ↓"},
{"id":"m_fin_cond", "label":"Conditions financières", "node_type":"input_manual","category":"monetary","unit":"score","coefficient_to_pips": 1.5,"display_col":1,"description":"Desserrement → SP500 ↑"},
{"id":"m_eps_growth","label":"Croissance BPA attendue","node_type":"input_manual","category":"earnings","unit":"%","coefficient_to_pips": 8.0,"display_col":1,"description":"+1% BPA revision ≈ +8 pts SP500"},
{"id":"m_eps_revision","label":"Ratio révisions BPA","node_type":"input_manual","category":"earnings","unit":"ratio","coefficient_to_pips": 2.0,"display_col":1,"description":"Ratio haussier/baissier. Fort driver momentum."},
{"id":"m_pe", "label":"Multiple PE forward", "node_type":"input_manual","category":"valuation","unit":"x","coefficient_to_pips":12.0,"display_col":1,"description":"+1x PE ≈ +12 pts SP500"},
{"id":"m_ig_spread","label":"Spread crédit IG (bps)","node_type":"input_manual","category":"credit","unit":"bps","coefficient_to_pips":-0.8,"display_col":1,"description":"Spread IG hausse → conditions crédit durcissent → SP500 ↓"},
{"id":"m_buybacks", "label":"Volume rachats actions","node_type":"input_manual","category":"flows","unit":"Mds$/sem","coefficient_to_pips": 0.5,"display_col":1,"description":"Flux rachats = support technique majeur"},
{"id":"m_vix", "label":"Niveau VIX", "node_type":"input_manual","category":"sentiment","unit":"pts","coefficient_to_pips":-0.8,"display_col":1,"description":"VIX spike → risk-off → SP500 ↓"},
{"id":"m_geo_risk_prem","label":"Prime risque géopolitique","node_type":"input_manual","category":"political","unit":"score","coefficient_to_pips":-0.5,"display_col":1,"description":"Tensions géopolitiques → incertitude → SP500 ↓"},
{"id":"m_gdp", "label":"Croissance PIB US", "node_type":"input_manual","category":"macro","unit":"%","coefficient_to_pips": 5.0,"display_col":1,"description":"Surprise haussière → SP500 ↑"},
{"id":"layer_monetary","label":"▶ Politique Monétaire","node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"in_cb + in_macro + m_fed_path + m_real_rate + m_fin_cond",
"description":"Fed, taux réels, conditions financières — transmission directe sur PE."},
{"id":"layer_earnings","label":"▶ Bénéfices & Valorisation","node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"m_eps_growth + m_eps_revision + m_pe + m_gdp",
"description":"BPA, révisions, multiple. Fondamental long terme."},
{"id":"layer_risk_credit","label":"▶ Risque & Crédit","node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"in_geo + in_credit + in_growth + in_trade + in_commodity + m_ig_spread + m_vix + m_geo_risk_prem",
"description":"Risque systémique, géopolitique, crédit — driver de la prime de risque."},
{"id":"layer_flows","label":"▶ Flux & Positionnement","node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"in_sentiment + in_technical + m_buybacks",
"description":"Flux institutionnels, retail, rachats, momentum."},
{"id":"sp500","label":"S&P 500 — Impact Net","node_type":"output","category":"output","unit":"pips","display_col":3,
"formula":"layer_monetary + layer_earnings + layer_risk_credit + layer_flows","description":"Pression nette cumulée S&P 500"},
]
},
# ══════════════════════════════════════════════════════════════════════════════
"TLT": {
"name": "TLT (US Long Bonds)", "output_node": "tlt",
"description": "ETF obligations US 20Y+ — duration, inflation, récession, supply",
"nodes": [
{"id":"in_cb", "label":"Banques Centrales","node_type":"input_event","category":"central_bank","unit":"pips","display_col":0,"event_category":"central_bank","description":"FOMC décisions/minutes → impact direct TLT."},
{"id":"in_macro", "label":"Surprises Macro", "node_type":"input_event","category":"monetary_shock","unit":"pips","display_col":0,"event_category":"monetary_shock","description":"CPI, PCE, NFP → réévaluation taux."},
{"id":"in_geo", "label":"Géopolitique", "node_type":"input_event","category":"geopolitical","unit":"pips","display_col":0,"event_category":"geopolitical","description":"Crises → flight to safety Treasuries."},
{"id":"in_growth", "label":"Choc Croissance", "node_type":"input_event","category":"growth_shock","unit":"pips","display_col":0,"event_category":"growth_shock","description":"Récession → TLT ↑ massif."},
{"id":"in_credit", "label":"Stress Crédit", "node_type":"input_event","category":"credit_stress","unit":"pips","display_col":0,"event_category":"credit_stress","description":"Banking stress → Treasuries demandés."},
{"id":"in_trade", "label":"Choc Commercial", "node_type":"input_event","category":"trade_policy","unit":"pips","display_col":0,"event_category":"trade_policy","description":"Tarifs → incertitude → Treasuries."},
{"id":"in_technical","label":"Technique", "node_type":"input_event","category":"technical","unit":"pips","display_col":0,"event_category":"technical","description":"Niveaux TLT, tendances."},
{"id":"in_sentiment","label":"Sentiment/Flux", "node_type":"input_event","category":"sentiment","unit":"pips","display_col":0,"event_category":"sentiment","description":"Flux obligataires institutionnels."},
{"id":"m_terminal_rate","label":"Taux terminal Fed","node_type":"input_manual","category":"monetary","unit":"bps","coefficient_to_pips":-0.4,"display_col":1,"description":"Taux terminal ↑ → taux longs ↑ → TLT ↓ (duration ~18)"},
{"id":"m_us_10y", "label":"Rendement UST 10Y","node_type":"input_manual","category":"monetary","unit":"bps","coefficient_to_pips":-0.3,"display_col":1,"description":"Chaque +1bps ≈ -$0.18 sur $100 TLT"},
{"id":"m_us_30y", "label":"Rendement UST 30Y","node_type":"input_manual","category":"monetary","unit":"bps","coefficient_to_pips":-0.25,"display_col":1,"description":"TLT détient principalement des 20-30Y"},
{"id":"m_breakeven","label":"Breakeven inflation 10Y","node_type":"input_manual","category":"inflation","unit":"bps","coefficient_to_pips":-0.2,"display_col":1,"description":"Inflation anticipée ↑ → taux nominaux ↑ → TLT ↓"},
{"id":"m_recession_prob","label":"Probabilité récession 12m","node_type":"input_manual","category":"macro","unit":"%","coefficient_to_pips": 0.3,"display_col":1,"description":"Récession → flight to bonds → TLT ↑"},
{"id":"m_qt_pace", "label":"Rythme QT Fed (Mds$/mois)","node_type":"input_manual","category":"monetary","unit":"Mds$","coefficient_to_pips":-0.1,"display_col":1,"description":"QT = pression vendeuse sur Treasuries → TLT ↓"},
{"id":"m_deficit", "label":"Déficit fiscal US (%PIB)","node_type":"input_manual","category":"macro","unit":"%","coefficient_to_pips":-0.4,"display_col":1,"description":"Déficit → supply massive → pression vendeuse → TLT ↓"},
{"id":"m_term_prem","label":"Prime de terme (ACM)","node_type":"input_manual","category":"monetary","unit":"bps","coefficient_to_pips":-0.3,"display_col":1,"description":"Prime de terme ↑ = extra rendement exigé → TLT ↓"},
{"id":"m_foreign_demand","label":"Demande étrangère Treasuries","node_type":"input_manual","category":"flows","unit":"Mds$","coefficient_to_pips": 0.05,"display_col":1,"description":"Achats CB étrangères (Chine, Japon) → TLT ↑"},
{"id":"m_vix", "label":"Niveau VIX", "node_type":"input_manual","category":"sentiment","unit":"pts","coefficient_to_pips": 0.5,"display_col":1,"description":"VIX spike → flight to quality → TLT ↑"},
{"id":"layer_rates","label":"▶ Taux & Politique Monétaire","node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"in_cb + in_macro + m_terminal_rate + m_us_10y + m_us_30y + m_breakeven + m_qt_pace + m_term_prem",
"description":"Taux directeurs, duration, QT, inflation anticipée."},
{"id":"layer_supply","label":"▶ Supply & Demande Treasuries","node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"m_deficit + m_foreign_demand",
"description":"Émission nette, achats étrangers — équilibre offre/demande marché obligataire."},
{"id":"layer_macro","label":"▶ Macro & Récession","node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"in_growth + in_credit + in_trade + m_recession_prob",
"description":"Risque récession → flight to safety → TLT ↑."},
{"id":"layer_risk","label":"▶ Risque & Refuge","node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"in_geo + in_sentiment + in_technical + m_vix",
"description":"Géopolitique, sentiment, VIX → demande refuge Treasuries."},
{"id":"tlt","label":"TLT — Impact Net","node_type":"output","category":"output","unit":"pips","display_col":3,
"formula":"layer_rates + layer_supply + layer_macro + layer_risk","description":"Pression nette cumulée TLT"},
]
},
# ══════════════════════════════════════════════════════════════════════════════
"GBPUSD": {
"name": "GBP/USD", "output_node": "gbpusd",
"description": "Livre sterling/Dollar — BoE, données UK, risque politique",
"nodes": [
{"id":"in_cb", "label":"Banques Centrales","node_type":"input_event","category":"central_bank","unit":"pips","display_col":0,"event_category":"central_bank","description":"BoE, Fed."},
{"id":"in_macro", "label":"Surprises Macro", "node_type":"input_event","category":"monetary_shock","unit":"pips","display_col":0,"event_category":"monetary_shock","description":"CPI UK/US, NFP, GDP UK."},
{"id":"in_geo", "label":"Géopolitique", "node_type":"input_event","category":"geopolitical","unit":"pips","display_col":0,"event_category":"geopolitical","description":"GBP comme devise cyclique, vendue en risk-off."},
{"id":"in_trade", "label":"Choc Commercial", "node_type":"input_event","category":"trade_policy","unit":"pips","display_col":0,"event_category":"trade_policy","description":"Tarifs US → UK exposé."},
{"id":"in_growth", "label":"Choc Croissance", "node_type":"input_event","category":"growth_shock","unit":"pips","display_col":0,"event_category":"growth_shock","description":"Récession UK."},
{"id":"in_credit", "label":"Stress Crédit", "node_type":"input_event","category":"credit_stress","unit":"pips","display_col":0,"event_category":"credit_stress","description":"Stress Gilts, banking UK."},
{"id":"in_technical","label":"Technique", "node_type":"input_event","category":"technical","unit":"pips","display_col":0,"event_category":"technical","description":"Niveaux clés cable."},
{"id":"in_sentiment","label":"Sentiment/Flux", "node_type":"input_event","category":"sentiment","unit":"pips","display_col":0,"event_category":"sentiment","description":"Positionnement GBP."},
{"id":"m_boe_path", "label":"Anticipation BoE 12m","node_type":"input_manual","category":"monetary","unit":"bps","coefficient_to_pips": 0.7,"display_col":1,"description":"Hikes BoE → GBP ↑"},
{"id":"m_fed_path", "label":"Anticipation Fed 12m","node_type":"input_manual","category":"monetary","unit":"bps","coefficient_to_pips":-0.7,"display_col":1,"description":"Hikes Fed → USD ↑ → paire ↓"},
{"id":"m_uk_cpi", "label":"CPI UK YoY", "node_type":"input_manual","category":"inflation","unit":"%","coefficient_to_pips": 0.7,"display_col":1,"description":"Persistance inflation → BoE hawkish → GBP ↑"},
{"id":"m_uk_gdp", "label":"Croissance PIB UK", "node_type":"input_manual","category":"macro","unit":"%","coefficient_to_pips": 3.0,"display_col":1,"description":"Surprise PIB UK → GBP ↑"},
{"id":"m_uk_pmi", "label":"PMI composite UK", "node_type":"input_manual","category":"macro","unit":"pts","coefficient_to_pips": 0.25,"display_col":1,"description":">50 = expansion UK → GBP ↑"},
{"id":"m_uk_pol_risk","label":"Risque politique UK","node_type":"input_manual","category":"political","unit":"score","coefficient_to_pips":-0.4,"display_col":1,"description":"Incertitude UK → GBP ↓"},
{"id":"m_risk_appetite","label":"Appétit risque","node_type":"input_manual","category":"sentiment","unit":"score","coefficient_to_pips": 0.4,"display_col":1,"description":"Risk-on → GBP comme devise cyclique ↑"},
{"id":"m_cftc_gbp", "label":"Positions nettes GBP","node_type":"input_manual","category":"positioning","unit":"k lots","coefficient_to_pips": 0.08,"display_col":1,"description":"Net long GBP CoT."},
{"id":"layer_monetary","label":"▶ Différentiel Monétaire","node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"in_cb + in_macro + m_boe_path + m_fed_path + m_uk_cpi",
"description":"BoE vs Fed, inflation UK — driver principal GBP/USD."},
{"id":"layer_uk_macro","label":"▶ Fondamentaux UK","node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"in_growth + m_uk_gdp + m_uk_pmi + m_uk_pol_risk",
"description":"Croissance, PMI, risque politique UK."},
{"id":"layer_global","label":"▶ Risque & Flux Globaux","node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"in_geo + in_credit + in_trade + in_technical + in_sentiment + m_risk_appetite + m_cftc_gbp",
"description":"GBP exposé aux chocs globaux (devise cyclique)."},
{"id":"gbpusd","label":"GBP/USD — Impact Net","node_type":"output","category":"output","unit":"pips","display_col":3,
"formula":"layer_monetary + layer_uk_macro + layer_global","description":"Pression nette cumulée GBP/USD"},
]
},
# ══════════════════════════════════════════════════════════════════════════════
"EEM": {
"name": "EEM (Marchés Émergents)", "output_node": "eem",
"description": "ETF EM — dollar, Chine, commodités, risk appetite",
"nodes": [
{"id":"in_cb", "label":"Banques Centrales","node_type":"input_event","category":"central_bank","unit":"pips","display_col":0,"event_category":"central_bank","description":"Fed pivot → EM bénéficient du dollar faible."},
{"id":"in_macro", "label":"Surprises Macro", "node_type":"input_event","category":"monetary_shock","unit":"pips","display_col":0,"event_category":"monetary_shock","description":"Données Chine, US macro."},
{"id":"in_geo", "label":"Géopolitique", "node_type":"input_event","category":"geopolitical","unit":"pips","display_col":0,"event_category":"geopolitical","description":"Tensions régionales EM."},
{"id":"in_trade", "label":"Choc Commercial", "node_type":"input_event","category":"trade_policy","unit":"pips","display_col":0,"event_category":"trade_policy","description":"Tarifs US-Chine, sanctions."},
{"id":"in_growth", "label":"Choc Croissance", "node_type":"input_event","category":"growth_shock","unit":"pips","display_col":0,"event_category":"growth_shock","description":"Récession US/Chine → EEM ↓."},
{"id":"in_credit", "label":"Stress Crédit EM", "node_type":"input_event","category":"credit_stress","unit":"pips","display_col":0,"event_category":"credit_stress","description":"Stress souverain EM, crise devises."},
{"id":"in_commodity","label":"Choc Commodités", "node_type":"input_event","category":"commodity","unit":"pips","display_col":0,"event_category":"commodity","description":"Exportateurs EM profitent des commodités élevées."},
{"id":"in_sentiment","label":"Sentiment/Flux", "node_type":"input_event","category":"sentiment","unit":"pips","display_col":0,"event_category":"sentiment","description":"Flux ETF EM, risk-on/off."},
{"id":"in_technical","label":"Technique", "node_type":"input_event","category":"technical","unit":"pips","display_col":0,"event_category":"technical","description":"Niveaux EEM."},
{"id":"m_dxy_inv", "label":"Dollar (DXY) — impact inverse","node_type":"input_manual","category":"monetary","unit":"pts","coefficient_to_pips":-0.8,"display_col":1,"description":"USD fort → pression dettes EM → EEM ↓. DXY ↑ = EEM ↓"},
{"id":"m_us_real_rate","label":"Taux réel US 10Y","node_type":"input_manual","category":"monetary","unit":"bps","coefficient_to_pips":-0.6,"display_col":1,"description":"Taux réel US ↑ → capitaux retournent US → EEM ↓"},
{"id":"m_fed_path", "label":"Anticipation Fed 12m","node_type":"input_manual","category":"monetary","unit":"bps","coefficient_to_pips":-0.5,"display_col":1,"description":"Cuts Fed → dollar faible → EM avantageux → EEM ↑"},
{"id":"m_china_pmi","label":"PMI manufacturier Chine","node_type":"input_manual","category":"macro","unit":"pts","coefficient_to_pips": 0.6,"display_col":1,"description":"Chine ~28% index. Expansion → EEM ↑"},
{"id":"m_china_stimulus","label":"Stimulus Chine","node_type":"input_manual","category":"macro","unit":"score","coefficient_to_pips": 0.8,"display_col":1,"description":"PBOC/fiscal. Annonces majeures → EEM spike"},
{"id":"m_commodity_index","label":"Indice commodités","node_type":"input_manual","category":"commodity","unit":"score","coefficient_to_pips": 0.4,"display_col":1,"description":"Exportateurs EM (Brésil, Afrique du Sud) profitent."},
{"id":"m_em_spread","label":"Spread souverain EM (EMBI)","node_type":"input_manual","category":"credit","unit":"bps","coefficient_to_pips":-0.5,"display_col":1,"description":"Spread ↑ = stress EM → sorties → EEM ↓"},
{"id":"m_risk_appetite","label":"Appétit risque","node_type":"input_manual","category":"sentiment","unit":"score","coefficient_to_pips": 0.6,"display_col":1,"description":"Risk-on → search for yield EM → EEM ↑"},
{"id":"m_vix", "label":"Niveau VIX", "node_type":"input_manual","category":"sentiment","unit":"pts","coefficient_to_pips":-0.5,"display_col":1,"description":"VIX spike → sorties EM → EEM ↓"},
{"id":"layer_dollar","label":"▶ Dollar & Taux US","node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"in_cb + in_macro + m_dxy_inv + m_us_real_rate + m_fed_path",
"description":"Dollar et taux réels = driver macro principal des EM."},
{"id":"layer_china","label":"▶ Chine & Croissance EM","node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"m_china_pmi + m_china_stimulus + in_growth + m_commodity_index + in_commodity",
"description":"Moteur Chine + exportateurs commodités."},
{"id":"layer_risk","label":"▶ Risque & Flux EM","node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"in_geo + in_credit + in_trade + m_em_spread + m_risk_appetite + m_vix + in_sentiment + in_technical",
"description":"Prime de risque EM, flux capitaux, sentiment global."},
{"id":"eem","label":"EEM — Impact Net","node_type":"output","category":"output","unit":"pips","display_col":3,
"formula":"layer_dollar + layer_china + layer_risk","description":"Pression nette cumulée EEM"},
]
},
# ══════════════════════════════════════════════════════════════════════════════
"QQQ": {
"name": "QQQ (NASDAQ-100 Tech)", "output_node": "qqq",
"description": "Tech US — taux réels, bénéfices big tech, IA, réglementation",
"nodes": [
{"id":"in_cb", "label":"Banques Centrales","node_type":"input_event","category":"central_bank","unit":"pips","display_col":0,"event_category":"central_bank","description":"Fed pivot → QQQ amplificateur (duration longue)."},
{"id":"in_macro", "label":"Surprises Macro", "node_type":"input_event","category":"monetary_shock","unit":"pips","display_col":0,"event_category":"monetary_shock","description":"CPI, NFP → réévaluation Fed → QQQ."},
{"id":"in_geo", "label":"Géopolitique", "node_type":"input_event","category":"geopolitical","unit":"pips","display_col":0,"event_category":"geopolitical","description":"Risk-off → vente tech en premier."},
{"id":"in_trade", "label":"Choc Commercial", "node_type":"input_event","category":"trade_policy","unit":"pips","display_col":0,"event_category":"trade_policy","description":"Restrictions export chips US-Chine."},
{"id":"in_growth", "label":"Choc Croissance", "node_type":"input_event","category":"growth_shock","unit":"pips","display_col":0,"event_category":"growth_shock","description":"Récession → dépenses IT coupées."},
{"id":"in_credit", "label":"Stress Crédit", "node_type":"input_event","category":"credit_stress","unit":"pips","display_col":0,"event_category":"credit_stress","description":"Conditions financières → financement tech."},
{"id":"in_commodity","label":"Choc Commodités", "node_type":"input_event","category":"commodity","unit":"pips","display_col":0,"event_category":"commodity","description":"Énergie → data centers coûts."},
{"id":"in_sentiment","label":"Sentiment/Flux", "node_type":"input_event","category":"sentiment","unit":"pips","display_col":0,"event_category":"sentiment","description":"CTA, hedge fund tech positions."},
{"id":"in_technical","label":"Technique", "node_type":"input_event","category":"technical","unit":"pips","display_col":0,"event_category":"technical","description":"QQQ niveaux, MA200, cassures."},
{"id":"m_real_rate","label":"Taux réel US 10Y (duration)","node_type":"input_manual","category":"monetary","unit":"bps","coefficient_to_pips":-1.5,"display_col":1,"description":"PRINCIPAL DRIVER : tech = duration longue. Taux réels ↑ → PE tech ↓ → QQQ ↓"},
{"id":"m_fed_path", "label":"Anticipation Fed 12m","node_type":"input_manual","category":"monetary","unit":"bps","coefficient_to_pips":-0.8,"display_col":1,"description":"Cuts → taux discount ↓ → PE tech expansion → QQQ ↑"},
{"id":"m_m7_eps", "label":"Révisions BPA Magnificent 7","node_type":"input_manual","category":"earnings","unit":"%","coefficient_to_pips":15.0,"display_col":1,"description":"+1% révision M7 ≈ +15 pts QQQ. AAPL, MSFT, NVDA, GOOGL, AMZN, META, TSLA"},
{"id":"m_ai_capex", "label":"Cycle capex IA","node_type":"input_manual","category":"tech","unit":"score","coefficient_to_pips": 0.8,"display_col":1,"description":"Hyperscalers capex, NVDA GPU demand. Momentum = QQQ ↑"},
{"id":"m_semi_cycle","label":"Cycle semi-conducteurs","node_type":"input_manual","category":"tech","unit":"score","coefficient_to_pips": 0.6,"display_col":1,"description":"Upcycle (book-to-bill, inventaires) = QQQ ↑"},
{"id":"m_tech_pe", "label":"Multiple PE tech (NTM)","node_type":"input_manual","category":"valuation","unit":"x","coefficient_to_pips":12.0,"display_col":1,"description":"PE forward NASDAQ-100. Expansion = QQQ ↑"},
{"id":"m_reg_risk", "label":"Risque réglementaire tech","node_type":"input_manual","category":"political","unit":"score","coefficient_to_pips":-0.5,"display_col":1,"description":"Antitrust, EU AI Act, FTC. Hausse → QQQ ↓"},
{"id":"m_vix", "label":"Niveau VIX", "node_type":"input_manual","category":"sentiment","unit":"pts","coefficient_to_pips":-0.8,"display_col":1,"description":"VIX spike → vente tech (beta élevé) → QQQ ↓ plus que SP500"},
{"id":"m_retail_options","label":"Flux options retail","node_type":"input_manual","category":"flows","unit":"score","coefficient_to_pips": 0.4,"display_col":1,"description":"FOMO gamma squeeze. Momentum = QQQ ↑ explosif"},
{"id":"m_cloud_growth","label":"Croissance cloud enterprise","node_type":"input_manual","category":"tech","unit":"%","coefficient_to_pips": 0.6,"display_col":1,"description":"AWS/Azure/GCP. Marges operating tech → QQQ"},
{"id":"m_china_tech_risk","label":"Restrictions tech US-Chine","node_type":"input_manual","category":"political","unit":"score","coefficient_to_pips":-0.4,"display_col":1,"description":"Export bans, sanctions. Revenus tech ↓ → QQQ ↓"},
{"id":"layer_rates","label":"▶ Taux & Valorisation","node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"in_cb + in_macro + m_real_rate + m_fed_path + m_tech_pe",
"description":"Taux réels = principal driver de la valorisation tech via taux d'actualisation."},
{"id":"layer_tech_fundamental","label":"▶ Fondamentaux Tech","node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"m_m7_eps + m_ai_capex + m_semi_cycle + m_cloud_growth",
"description":"BPA Magnificent 7, cycle IA, semis, cloud. Foundation long terme."},
{"id":"layer_risk","label":"▶ Risque & Réglementation","node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"in_geo + in_credit + in_growth + in_trade + in_commodity + m_reg_risk + m_china_tech_risk + m_vix",
"description":"Risques systémiques, réglementaires, géopolitiques tech."},
{"id":"layer_flows","label":"▶ Flux & Momentum","node_type":"intermediate","category":"intermediate","unit":"pips","display_col":2,
"formula":"in_sentiment + in_technical + m_retail_options",
"description":"Flux spéculatifs, options retail, momentum technique."},
{"id":"qqq","label":"QQQ — Impact Net","node_type":"output","category":"output","unit":"pips","display_col":3,
"formula":"layer_rates + layer_tech_fundamental + layer_risk + layer_flows","description":"Pression nette cumulée QQQ"},
]
},
} # end INSTRUMENT_MODELS
# ── DB ─────────────────────────────────────────────────────────────────────────
def init_instrument_model_tables(conn):
conn.executescript("""
CREATE TABLE IF NOT EXISTS instrument_models (
id INTEGER PRIMARY KEY,
instrument TEXT UNIQUE NOT NULL,
graph_json TEXT NOT NULL,
updated_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS instrument_node_overrides (
id INTEGER PRIMARY KEY,
instrument TEXT NOT NULL,
node_id TEXT NOT NULL,
value REAL NOT NULL,
note TEXT,
set_at TEXT DEFAULT (datetime('now')),
UNIQUE(instrument, node_id)
);
""")
conn.commit()
def seed_instrument_models(conn):
"""Seed/update models on startup. Safe to call repeatedly."""
init_instrument_model_tables(conn)
for inst, model in INSTRUMENT_MODELS.items():
graph_json = json.dumps({
"name": model["name"],
"description": model["description"],
"output_node": model["output_node"],
"nodes": model["nodes"],
})
conn.execute("""
INSERT INTO instrument_models (instrument, graph_json)
VALUES (?,?)
ON CONFLICT(instrument) DO UPDATE SET graph_json=excluded.graph_json, updated_at=datetime('now')
""", (inst, graph_json))
conn.commit()
# ── Event contribution by category (with lifecycle) ────────────────────────────
def _compute_event_by_category(conn, instrument: str, ref_date: date_type) -> dict[str, float]:
"""Sum active event contributions per template category, applying lifecycle."""
inst_upper = instrument.upper()
inst_lower = inst_upper.lower()
extended_from = ref_date - timedelta(days=365)
rows = conn.execute("""
SELECT a.prediction_json,
e.start_date, e.end_date, e.sub_type,
t.category, t.calibration_json
FROM causal_event_analyses a
JOIN market_events e ON e.id = a.market_event_id
JOIN causal_graph_templates t ON t.id = a.template_id
WHERE a.instrument = ?
AND e.start_date >= ?
AND e.start_date <= ?
""", (inst_upper, str(extended_from), str(ref_date))).fetchall()
by_cat: dict[str, float] = {}
for row in rows:
r = dict(row)
try:
predictions = json.loads(r["prediction_json"] or "{}")
calib = json.loads(r["calibration_json"] or "{}")
except Exception:
continue
pips: Optional[float] = None
if inst_lower in predictions:
pips = float(predictions[inst_lower])
else:
for k, v in predictions.items():
if inst_lower in k.lower():
try: pips = float(v); break
except (TypeError, ValueError): pass
if pips is None or pips == 0:
continue
absorption = max(1, int(calib.get("absorption_days", 7)))
dtype = str(calib.get("decay_type", "exp"))
rise = max(0, int(calib.get("rise_days", 0)))
plateau = max(0, int(calib.get("plateau_days", 0)))
# Guidance events: dynamic absorption until meeting
ev_end = r.get("end_date")
if ev_end and str(r.get("sub_type", "")).startswith("rate_guidance"):
try:
meeting = date_type.fromisoformat(ev_end[:10])
ev_start = date_type.fromisoformat(r["start_date"][:10])
absorption = max(1, (meeting - ev_start).days)
dtype = "linear"; rise = 0; plateau = 0
except ValueError:
pass
try:
ev_date = date_type.fromisoformat(r["start_date"][:10])
except ValueError:
continue
days = (ref_date - ev_date).days
df = _lifecycle(days, rise, plateau, absorption, dtype)
if df < 0.01:
continue
cat = r["category"]
by_cat[cat] = round(by_cat.get(cat, 0.0) + pips * df, 2)
return by_cat
# ── Graph evaluation ───────────────────────────────────────────────────────────
def _build_inputs(graph_def: dict, overrides: dict, ev_by_cat: dict) -> dict:
"""
Build the inputs dict for evaluate_graph():
- input_event nodes → value from event contributions (category mapping)
- input_manual nodes → user_value × coefficient_to_pips
Any node with an override uses that value directly (already in pips for events;
for manual nodes the override IS the native-unit value → converted to pips).
"""
inputs: dict[str, float] = {}
for node in graph_def["nodes"]:
nid = node["id"]
ntype = node.get("node_type", "")
ov = overrides.get(nid)
if ntype == "input_event":
if ov:
inputs[nid] = float(ov["value"]) # override IS pips
else:
cat = node.get("event_category", "")
inputs[nid] = ev_by_cat.get(cat, 0.0)
elif ntype == "input_manual":
if ov:
coeff = float(node.get("coefficient_to_pips", 1.0))
inputs[nid] = float(ov["value"]) * coeff
else:
inputs[nid] = 0.0 # neutral
return inputs
def _graph_json_for_eval(graph_def: dict) -> dict:
"""Convert our model graph_def to the format expected by evaluate_graph()."""
nodes = []
for n in graph_def["nodes"]:
entry: dict = {"id": n["id"]}
if n.get("formula"):
entry["formula"] = n["formula"]
nodes.append(entry)
return {"nodes": nodes, "coefficients": {}}
# ── Public API ─────────────────────────────────────────────────────────────────
def get_model_state(conn, instrument: str, at_date: Optional[str] = None) -> Optional[dict]:
"""Full model state: all node values computed via DAG evaluation."""
inst_upper = instrument.upper()
row = conn.execute(
"SELECT graph_json FROM instrument_models WHERE instrument=?", (inst_upper,)
).fetchone()
if not row:
return None
graph_def = json.loads(row["graph_json"])
try:
ref_date = date_type.fromisoformat(at_date) if at_date else datetime.utcnow().date()
except ValueError:
ref_date = datetime.utcnow().date()
overrides = {r["node_id"]: dict(r) for r in conn.execute(
"SELECT node_id, value, note, set_at FROM instrument_node_overrides WHERE instrument=?",
(inst_upper,)
).fetchall()}
ev_by_cat = _compute_event_by_category(conn, inst_upper, ref_date)
inputs = _build_inputs(graph_def, overrides, ev_by_cat)
# DAG evaluation (propagates through intermediate nodes via formulas)
from services.causal_graphs import evaluate_graph
gj = _graph_json_for_eval(graph_def)
all_vals = evaluate_graph(gj, inputs)
output_id = graph_def["output_node"]
net_pips = round(float(all_vals.get(output_id, 0.0)), 1)
nodes_out = []
for node in graph_def["nodes"]:
nid = node["id"]
ntype = node.get("node_type", "")
val = round(float(all_vals.get(nid, 0.0)), 1)
ov = overrides.get(nid)
state = dict(node)
state["computed_value"] = val
state["pip_contribution"] = val
if ntype == "input_event":
cat = node.get("event_category", "")
state["source"] = "manual" if ov else ("events" if val != 0.0 else "neutral")
state["raw_value"] = ov["value"] if ov else round(ev_by_cat.get(cat, 0.0), 1)
if ov:
state["override_note"] = ov.get("note", "")
state["override_set_at"] = ov.get("set_at", "")
elif ntype == "input_manual":
state["source"] = "manual" if ov else "neutral"
state["raw_value"] = ov["value"] if ov else 0.0 # in native unit
if ov:
state["override_note"] = ov.get("note", "")
state["override_set_at"] = ov.get("set_at", "")
elif ntype in ("intermediate", "output"):
state["source"] = "computed"
nodes_out.append(state)
direction = "bullish" if net_pips > 5 else "bearish" if net_pips < -5 else "neutral"
return {
"instrument": inst_upper,
"name": graph_def["name"],
"description": graph_def.get("description", ""),
"at_date": str(ref_date),
"net_pips": net_pips,
"direction": direction,
"nodes": nodes_out,
"output_node": output_id,
}
def simulate_timeline(
conn, instrument: str, period: str = "1y"
) -> list[dict]:
"""
Simulate all node values day by day over the period.
Returns [{date, nodes: {id: value}, net_pips}].
Uses lifecycle (rise/plateau/decay) for event contributions.
Manual overrides are static (applied uniformly across the period).
"""
inst_upper = instrument.upper()
row = conn.execute(
"SELECT graph_json FROM instrument_models WHERE instrument=?", (inst_upper,)
).fetchone()
if not row:
return []
graph_def = json.loads(row["graph_json"])
output_id = graph_def["output_node"]
period_days = {"5d":7,"1mo":35,"3mo":95,"6mo":190,"1y":370,"2y":740}
lookback = period_days.get(period, 370)
today = datetime.utcnow().date()
date_from = today - timedelta(days=lookback)
# Load overrides (static)
overrides = {r["node_id"]: dict(r) for r in conn.execute(
"SELECT node_id, value, note, set_at FROM instrument_node_overrides WHERE instrument=?",
(inst_upper,)
).fetchall()}
# Load ALL relevant events (extended lookback for decay)
inst_lower = inst_upper.lower()
extended = date_from - timedelta(days=365)
rows = conn.execute("""
SELECT a.prediction_json, e.start_date, e.end_date, e.sub_type,
t.category, t.calibration_json
FROM causal_event_analyses a
JOIN market_events e ON e.id = a.market_event_id
JOIN causal_graph_templates t ON t.id = a.template_id
WHERE a.instrument=? AND e.start_date>=? AND e.start_date<=?
""", (inst_upper, str(extended), str(today))).fetchall()
# Pre-parse events
events = []
for r in rows:
r = dict(r)
try:
preds = json.loads(r["prediction_json"] or "{}")
calib = json.loads(r["calibration_json"] or "{}")
except Exception:
continue
pips: Optional[float] = None
if inst_lower in preds:
pips = float(preds[inst_lower])
else:
for k, v in preds.items():
if inst_lower in k.lower():
try: pips = float(v); break
except (TypeError, ValueError): pass
if not pips:
continue
absorption = max(1, int(calib.get("absorption_days", 7)))
dtype = str(calib.get("decay_type", "exp"))
rise = max(0, int(calib.get("rise_days", 0)))
plateau = max(0, int(calib.get("plateau_days", 0)))
ev_end = r.get("end_date")
if ev_end and str(r.get("sub_type","")).startswith("rate_guidance"):
try:
meeting = date_type.fromisoformat(ev_end[:10])
ev_start = date_type.fromisoformat(r["start_date"][:10])
absorption = max(1, (meeting - ev_start).days)
dtype = "linear"; rise = 0; plateau = 0
except ValueError:
pass
try:
ev_date = date_type.fromisoformat(r["start_date"][:10])
except ValueError:
continue
events.append({
"ev_date": ev_date, "category": r["category"], "pips": pips,
"rise": rise, "plateau": plateau, "absorption": absorption, "dtype": dtype,
})
from services.causal_graphs import evaluate_graph
gj = _graph_json_for_eval(graph_def)
timeline = []
cur = date_from
while cur <= today:
# Event contributions for this day
ev_by_cat: dict[str, float] = {}
for ev in events:
if ev["ev_date"] > cur:
continue
days = (cur - ev["ev_date"]).days
df = _lifecycle(days, ev["rise"], ev["plateau"], ev["absorption"], ev["dtype"])
if df < 0.01:
continue
cat = ev["category"]
ev_by_cat[cat] = ev_by_cat.get(cat, 0.0) + round(ev["pips"] * df, 2)
inputs = _build_inputs(graph_def, overrides, ev_by_cat)
vals = evaluate_graph(gj, inputs)
net = round(float(vals.get(output_id, 0.0)), 1)
timeline.append({
"date": str(cur),
"net_pips": net,
"nodes": {k: round(float(v), 1) for k, v in vals.items()},
})
cur += timedelta(days=1)
return timeline
def set_node_override(conn, instrument: str, node_id: str, value: float, note: str = "") -> bool:
conn.execute("""
INSERT INTO instrument_node_overrides (instrument, node_id, value, note, set_at)
VALUES (?,?,?,?,datetime('now'))
ON CONFLICT(instrument, node_id) DO UPDATE SET value=excluded.value, note=excluded.note, set_at=datetime('now')
""", (instrument.upper(), node_id, value, note))
conn.commit()
return True
def clear_node_override(conn, instrument: str, node_id: str) -> bool:
conn.execute(
"DELETE FROM instrument_node_overrides WHERE instrument=? AND node_id=?",
(instrument.upper(), node_id)
)
conn.commit()
return True