""" Instrument Models — graphe causal exhaustif par instrument. Deux types de nœuds : - structural : facteurs éditables manuellement (carry, taux réels, croissance…) - event_driven: pressions auto depuis causal_event_analyses, groupées par catégorie template - output : nœud résultat (somme en pips) Valeur d'un nœud structural = override utilisateur × coefficient → pips Valeur d'un nœud event_driven = sum(pips × decay) depuis les analyses actives Output = sum(tous les nœuds en pips) """ import json import math from datetime import datetime, timedelta, date as date_type from typing import Optional # ── Helpers ──────────────────────────────────────────────────────────────────── 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) # ── Comprehensive node definitions ───────────────────────────────────────────── # coefficient (structural only) : native_unit × coefficient = pips impact INSTRUMENT_MODELS: dict[str, dict] = { # ═══════════════════════════════════════════════════════════════════════════════ "EURUSD": { "name": "EUR/USD", "output_node": "eurusd", "description": "Taux de change Euro / Dollar — pair G10 la plus liquide au monde", "nodes": [ # ── Monétaire ────────────────────────────────────────────────────────────── {"id":"rate_diff_ois_2y","label":"Spread OIS 2Y USD-EUR","type":"structural","category":"monetary","unit":"bps","coefficient":1.5, "description":"Différentiel taux swap OIS 2 ans USD vs EUR. Principal déterminant court terme. Positif = USD plus rémunérateur → EUR/USD ↓"}, {"id":"fed_path_12m","label":"Anticipation Fed 12m","type":"structural","category":"monetary","unit":"bps","coefficient":0.8, "description":"Variation cumulée taux Fed attendue sur 12m (négatif = cuts → USD ↓ → EUR/USD ↑)"}, {"id":"ecb_path_12m","label":"Anticipation BCE 12m","type":"structural","category":"monetary","unit":"bps","coefficient":-0.8, "description":"Variation cumulée taux BCE attendue sur 12m (négatif = cuts → EUR ↓ → EUR/USD ↓)"}, {"id":"us_real_rate","label":"Taux réel US 10Y (TIPS)","type":"structural","category":"monetary","unit":"bps","coefficient":-0.5, "description":"Rendement TIPS 10Y. Hausse = USD attractif pour capitaux → EUR/USD ↓"}, {"id":"eu_real_rate","label":"Taux réel EU 10Y","type":"structural","category":"monetary","unit":"bps","coefficient":0.5, "description":"Taux réel zone euro 10Y. Hausse = EUR attractif → EUR/USD ↑"}, {"id":"carry_attractiveness","label":"Attractivité carry EUR/USD","type":"structural","category":"monetary","unit":"score","coefficient":0.6, "description":"Score synthétique carry trade EUR vs USD (+= EUR avantageux, -=USD avantageux)"}, # ── Macro ────────────────────────────────────────────────────────────────── {"id":"us_growth_advantage","label":"Avantage croissance US/EU","type":"structural","category":"macro","unit":"pts","coefficient":-15.0, "description":"Différentiel PIB US - EU (annualisé). US surperformance → USD fort → pair ↓"}, {"id":"us_labor_market","label":"Marché emploi US","type":"structural","category":"macro","unit":"score","coefficient":-0.4, "description":"Score santé marché emploi US (NFP, chômage, salaires). Fort = USD ↑"}, {"id":"eu_pmi_composite","label":"PMI composite Eurozone","type":"structural","category":"macro","unit":"pts","coefficient":0.3, "description":"PMI composite zone euro (>50 = expansion). Hausse = EUR ↑"}, # ── Inflation ────────────────────────────────────────────────────────────── {"id":"us_cpi_yoy","label":"CPI US YoY","type":"structural","category":"inflation","unit":"%","coefficient":-0.8, "description":"Inflation américaine. Hausse surprise → Fed plus hawkish → USD ↑ → pair ↓"}, {"id":"eu_cpi_yoy","label":"HICP Eurozone YoY","type":"structural","category":"inflation","unit":"%","coefficient":0.8, "description":"Inflation zone euro. Hausse surprise → BCE plus hawkish → EUR ↑"}, # ── Géopolitique & Politique ──────────────────────────────────────────────── {"id":"eu_fragmentation_risk","label":"Risque fragmentation UE","type":"structural","category":"political","unit":"score","coefficient":-0.5, "description":"Risque politique/fragmentation zone euro (0=stable, 100=crise). Hausse → EUR ↓"}, {"id":"us_political_risk","label":"Incertitude politique US","type":"structural","category":"political","unit":"score","coefficient":0.3, "description":"Incertitude politique américaine (debt ceiling, shutdown, élections). Hausse → USD ↓"}, {"id":"energy_price_impact","label":"Prix énergie (termes échanges EU)","type":"structural","category":"macro","unit":"$/bbl","coefficient":-0.25, "description":"Prix pétrole/gaz. Hausse élargit déficit commercial EU → EUR ↓ structurellement"}, # ── Sentiment & Risque ───────────────────────────────────────────────────── {"id":"risk_appetite","label":"Appétit risque mondial","type":"structural","category":"sentiment","unit":"score","coefficient":0.5, "description":"Score risk-on/off global (+100=risk-on max). Risk-on → sorties USD → EUR/USD ↑"}, {"id":"vix_level","label":"Niveau VIX","type":"structural","category":"sentiment","unit":"pts","coefficient":-0.4, "description":"VIX. Spike → flight to USD safe haven → EUR/USD ↓"}, # ── Positionnement & Flux ───────────────────────────────────────────────── {"id":"cftc_eur_net","label":"Positions nettes EUR (CoT)","type":"structural","category":"positioning","unit":"k contrats","coefficient":0.1, "description":"Positions spéculatives nettes EUR sur CME (CoT). Long extrême → risque retournement."}, {"id":"dollar_reserve_demand","label":"Demande réserves USD","type":"structural","category":"flows","unit":"score","coefficient":-0.4, "description":"Demande globale de réserves en USD (score). Fort = USD structurellement demandé → pair ↓"}, # ── Event-driven (auto depuis causal_event_analyses) ──────────────────────── {"id":"ev_central_bank","label":"Pression Banques Centrales","type":"event_driven","category":"central_bank","unit":"pips", "description":"Contributions cumulées des événements banques centrales actifs (décisions, minutes, guidance). Décroissance exp."}, {"id":"ev_macro_surprise","label":"Surprise Données Macro","type":"event_driven","category":"monetary_shock","unit":"pips", "description":"Surprises publications économiques (CPI, NFP, PIB, PMI, ventes détail). Décroissance rapide ~12j."}, {"id":"ev_geopolitical","label":"Risque Géopolitique","type":"event_driven","category":"geopolitical","unit":"pips", "description":"Chocs géopolitiques actifs (conflits, sanctions, tensions). Décroissance linéaire ~21j."}, {"id":"ev_trade_policy","label":"Choc Commercial / Tarifs","type":"event_driven","category":"trade_policy","unit":"pips", "description":"Annonces commerciales (tarifs, accords, menaces). Décroissance lente."}, {"id":"ev_growth_shock","label":"Choc de Croissance","type":"event_driven","category":"growth_shock","unit":"pips", "description":"Chocs perspectives croissance (récession, rebond inattendu)."}, {"id":"ev_commodity","label":"Choc Commodités","type":"event_driven","category":"commodity","unit":"pips", "description":"Chocs matières premières (pétrole, gaz, métaux) impactant USD ou EUR."}, {"id":"ev_credit_stress","label":"Stress Crédit / Liquidité","type":"event_driven","category":"credit_stress","unit":"pips", "description":"Événements stress crédit/liquidité (banking stress, spreads IG/HY)."}, {"id":"ev_sentiment","label":"Sentiment & Positionnement","type":"event_driven","category":"sentiment","unit":"pips", "description":"Changements sentiment et flux positionnement institutionnel."}, {"id":"ev_technical","label":"Momentum Technique","type":"event_driven","category":"technical","unit":"pips", "description":"Signaux techniques (cassures, croisements MA, niveaux clés)."}, # ── Output ───────────────────────────────────────────────────────────────── {"id":"eurusd","label":"EUR/USD Impact Net","type":"output","category":"output","unit":"pips", "description":"Pression nette = Σ(structurels × coefficient) + Σ(event-driven en pips)"}, ] }, # ═══════════════════════════════════════════════════════════════════════════════ "USDJPY": { "name": "USD/JPY", "output_node": "usdjpy", "description": "Taux de change Dollar / Yen — pair carry & safe haven par excellence", "nodes": [ {"id":"yield_diff_10y","label":"Différentiel rendement 10Y US-JP","type":"structural","category":"monetary","unit":"bps","coefficient":1.2, "description":"Spread rendement UST10Y - JGB10Y. Principal moteur USD/JPY. Hausse = USD/JPY ↑"}, {"id":"fed_path_12m","label":"Anticipation Fed 12m","type":"structural","category":"monetary","unit":"bps","coefficient":0.6, "description":"Variation cumulée taux Fed 12m. Hausse = USD ↑ → USD/JPY ↑"}, {"id":"boj_policy_stance","label":"Biais BoJ (hawkish/dovish)","type":"structural","category":"monetary","unit":"score","coefficient":-8.0, "description":"Score posture BoJ (+= hawkish). Hawkish BoJ → JPY apprécie → USD/JPY ↓"}, {"id":"jgb_yield_10y","label":"Rendement JGB 10Y","type":"structural","category":"monetary","unit":"bps","coefficient":-0.8, "description":"Rendement JGB 10Y. Hausse = JPY attractif → USD/JPY ↓"}, {"id":"us_real_rate","label":"Taux réel US 10Y","type":"structural","category":"monetary","unit":"bps","coefficient":0.6, "description":"TIPS 10Y. Hausse = USD attractif vs actifs risqués → USD/JPY ↑"}, {"id":"risk_appetite","label":"Appétit risque mondial","type":"structural","category":"sentiment","unit":"score","coefficient":-1.2, "description":"Score risk-on. Risk-off → fuite vers JPY safe haven → USD/JPY ↓"}, {"id":"vix_level","label":"Niveau VIX","type":"structural","category":"sentiment","unit":"pts","coefficient":-0.8, "description":"VIX. Spike → demande JPY refuge → USD/JPY ↓"}, {"id":"carry_trade_momentum","label":"Momentum carry USD/JPY","type":"structural","category":"positioning","unit":"score","coefficient":0.4, "description":"Force du carry trade. Positif = flux acheteurs USD/JPY (emprunter JPY, investir USD)."}, {"id":"cftc_jpy_net_short","label":"Positions nettes JPY short (CoT)","type":"structural","category":"positioning","unit":"k contrats","coefficient":0.15, "description":"Positions short JPY spéculatifs CoT CFTC. Extrême = risque short squeeze → USD/JPY ↓ brutal."}, {"id":"mof_intervention_risk","label":"Risque intervention MoF","type":"structural","category":"political","unit":"score","coefficient":-0.8, "description":"Probabilité intervention verbale/physique du Trésor japonais (0=aucune, 100=imminente). Hausse → USD/JPY ↓ préventif."}, {"id":"japan_current_account","label":"Balance courante Japon","type":"structural","category":"flows","unit":"Mds¥","coefficient":-0.05, "description":"Excédent courant japonais. Large surplus = rapatriements YEN → USD/JPY ↓ structurel."}, {"id":"us_japan_trade_tension","label":"Tensions commerciales US-Japon","type":"structural","category":"political","unit":"score","coefficient":0.3, "description":"Tensions bilatérales US-Japon (tarifs, pressions). Hausse → USD/JPY incertain."}, {"id":"ev_central_bank","label":"Pression Banques Centrales","type":"event_driven","category":"central_bank","unit":"pips","description":"Fed, BoJ decisions/minutes actifs."}, {"id":"ev_macro_surprise","label":"Surprise Macro","type":"event_driven","category":"monetary_shock","unit":"pips","description":"NFP, CPI US, Tankan, CPI Japon surprises."}, {"id":"ev_geopolitical","label":"Risque Géopolitique","type":"event_driven","category":"geopolitical","unit":"pips","description":"Tensions NK, conflits régionaux → JPY safe haven."}, {"id":"ev_trade_policy","label":"Choc Commercial","type":"event_driven","category":"trade_policy","unit":"pips","description":"Tarifs US-Japon, accords commerciaux."}, {"id":"ev_growth_shock","label":"Choc Croissance","type":"event_driven","category":"growth_shock","unit":"pips","description":"Chocs PIB/récession → risk-off → JPY."}, {"id":"ev_credit_stress","label":"Stress Crédit","type":"event_driven","category":"credit_stress","unit":"pips","description":"Stress bancaire → flight to JPY."}, {"id":"ev_sentiment","label":"Sentiment & Flux","type":"event_driven","category":"sentiment","unit":"pips","description":"Positionnement spéculatif, flux risk-on/off."}, {"id":"ev_technical","label":"Momentum Technique","type":"event_driven","category":"technical","unit":"pips","description":"Niveaux clés, tendances USD/JPY."}, {"id":"usdjpy","label":"USD/JPY Impact Net","type":"output","category":"output","unit":"pips","description":"Pression nette cumulée USD/JPY"}, ] }, # ═══════════════════════════════════════════════════════════════════════════════ "XAUUSD": { "name": "XAU/USD (Or)", "output_node": "xauusd", "description": "Or vs Dollar — actif refuge, couverture inflation et géopolitique", "nodes": [ {"id":"us_real_rate_10y","label":"Taux réel US 10Y (TIPS)","type":"structural","category":"monetary","unit":"bps","coefficient":-2.5, "description":"PLUS IMPORTANT pour l'or. Taux réel US négatif/en baisse → or ↑. Chaque -10bps ≈ +25 pips or."}, {"id":"dxy_level","label":"Indice Dollar (DXY)","type":"structural","category":"monetary","unit":"pts","coefficient":-3.0, "description":"Niveau DXY. Or libellé en USD → DXY ↑ = or ↓ mécaniquement (corrélation ~-0.75)."}, {"id":"inflation_breakeven_10y","label":"Breakeven inflation 10Y US","type":"structural","category":"inflation","unit":"bps","coefficient":1.5, "description":"Anticipations inflation 10Y US (TIPS). Hausse = valeur réelle USD ↓ → or demandé comme couverture."}, {"id":"fed_path_12m","label":"Anticipation Fed 12m","type":"structural","category":"monetary","unit":"bps","coefficient":-1.0, "description":"Variation taux Fed attendue. Cuts attendus → taux réels ↓ → or ↑."}, {"id":"global_uncertainty_score","label":"Indice incertitude mondiale","type":"structural","category":"sentiment","unit":"score","coefficient":0.3, "description":"Score incertitude géopolitique/économique globale. Hausse → demande refuge or."}, {"id":"vix_level","label":"Niveau VIX","type":"structural","category":"sentiment","unit":"pts","coefficient":0.4, "description":"VIX. Spike → or comme couverture risque extrême."}, {"id":"central_bank_gold_buying","label":"Achats CB (or)","type":"structural","category":"flows","unit":"tonnes/mois","coefficient":2.0, "description":"Achats nets d'or par les banques centrales mondiales. Driver structurel majeur depuis 2022."}, {"id":"etf_gold_flows","label":"Flux ETF or (GLD, IAU)","type":"structural","category":"flows","unit":"tonnes","coefficient":1.5, "description":"Flux net dans les ETF adossés à l'or. Entrées → demande physique → or ↑."}, {"id":"cftc_gold_net_long","label":"Positions nettes or (CoT)","type":"structural","category":"positioning","unit":"k contrats","coefficient":0.15, "description":"Positions spéculatives nettes or COMEX. Extrême long → risque liquidation → or ↓."}, {"id":"us_fiscal_deficit","label":"Déficit fiscal US (%PIB)","type":"structural","category":"macro","unit":"%","coefficient":0.5, "description":"Inquiétudes sur la trajectoire fiscale US → doutes sur USD → or refuge."}, {"id":"gold_mine_cost","label":"Coût d'extraction (AISC)","type":"structural","category":"supply","unit":"$/oz","coefficient":0.05, "description":"All-in sustaining cost mines d'or. Support fondamental pour le prix."}, {"id":"india_china_demand","label":"Demande physique Inde/Chine","type":"structural","category":"flows","unit":"score","coefficient":0.4, "description":"Demande physique joaillerie/investment Inde et Chine. Saisonnalité (Diwali, Nouvel An chinois)."}, {"id":"ev_central_bank","label":"Pression Banques Centrales","type":"event_driven","category":"central_bank","unit":"pips","description":"Décisions Fed/BoE/BCE → impact taux réels → or."}, {"id":"ev_macro_surprise","label":"Surprise Macro","type":"event_driven","category":"monetary_shock","unit":"pips","description":"CPI/PCE surprises, NFP → réévaluation taux réels → or."}, {"id":"ev_geopolitical","label":"Risque Géopolitique","type":"event_driven","category":"geopolitical","unit":"pips","description":"Conflits, tensions → demande refuge or maximale."}, {"id":"ev_trade_policy","label":"Choc Commercial","type":"event_driven","category":"trade_policy","unit":"pips","description":"Tarifs/guerres commerciales → incertitude → or."}, {"id":"ev_growth_shock","label":"Choc Croissance","type":"event_driven","category":"growth_shock","unit":"pips","description":"Récession → easing monétaire attendu → or ↑."}, {"id":"ev_credit_stress","label":"Stress Crédit / Systémique","type":"event_driven","category":"credit_stress","unit":"pips","description":"Crises bancaires, stress liquidité → or refuge absolu."}, {"id":"ev_commodity","label":"Choc Commodités","type":"event_driven","category":"commodity","unit":"pips","description":"Chocs matières premières (inflation des inputs)."}, {"id":"ev_sentiment","label":"Sentiment & Flux","type":"event_driven","category":"sentiment","unit":"pips","description":"Sentiment risk-off, flux vers or."}, {"id":"ev_technical","label":"Momentum Technique","type":"event_driven","category":"technical","unit":"pips","description":"Cassures ATH, niveaux clés or."}, {"id":"xauusd","label":"XAU/USD Impact Net","type":"output","category":"output","unit":"pips","description":"Pression nette cumulée sur or/USD"}, ] }, # ═══════════════════════════════════════════════════════════════════════════════ "SP500": { "name": "S&P 500", "output_node": "sp500", "description": "Indice actions US large cap — baromètre risque global", "nodes": [ {"id":"fed_path_12m","label":"Anticipation Fed 12m","type":"structural","category":"monetary","unit":"bps","coefficient":-0.6, "description":"Cuts attendus → taux discount ↓ → PE expansion → SP500 ↑. Chaque -25bps ≈ +15 pts SP500."}, {"id":"us_real_rate_10y","label":"Taux réel US 10Y","type":"structural","category":"monetary","unit":"bps","coefficient":-1.5, "description":"Taux réel = taux d'actualisation des bénéfices futurs. Hausse → PE compression → SP500 ↓."}, {"id":"eps_growth_12m","label":"Croissance BPA attendue 12m","type":"structural","category":"earnings","unit":"%","coefficient":8.0, "description":"Révisions croissance bénéfices S&P 500. +1% révision haussière ≈ +8 pts SP500."}, {"id":"eps_revision_ratio","label":"Ratio révisions BPA","type":"structural","category":"earnings","unit":"ratio","coefficient":2.0, "description":"Ratio révisions haussières/baissières (>1 = majorité haussière). Fort driver momentum."}, {"id":"pe_expansion","label":"Expansion multiple PE","type":"structural","category":"valuation","unit":"x","coefficient":12.0, "description":"Variation multiple PE du S&P 500. +1x PE ≈ +12 pts SP500 (base ~4500)."}, {"id":"financial_conditions","label":"Indice conditions financières","type":"structural","category":"monetary","unit":"score","coefficient":1.5, "description":"Chicago Fed NFCI ou Goldman GSFCI. Desserrement = SP500 ↑."}, {"id":"credit_spread_ig","label":"Spread crédit IG (bps)","type":"structural","category":"credit","unit":"bps","coefficient":-0.8, "description":"Spread obligations IG. Hausse = conditions crédit durcissent → SP500 ↓."}, {"id":"credit_spread_hy","label":"Spread crédit HY (bps)","type":"structural","category":"credit","unit":"bps","coefficient":-0.5, "description":"Spread high yield. Baromètre stress financier. Spike → SP500 ↓."}, {"id":"buyback_volume","label":"Volume rachats actions","type":"structural","category":"flows","unit":"Mds$/sem","coefficient":0.5, "description":"Flux de rachats d'actions S&P 500 en cours. Support technique majeur."}, {"id":"retail_flow","label":"Flux retail (FOMO)","type":"structural","category":"flows","unit":"score","coefficient":0.3, "description":"Score flux acheteurs retail (ETF, options call). Momentum-driven."}, {"id":"consumer_confidence","label":"Confiance consommateur US","type":"structural","category":"macro","unit":"pts","coefficient":0.3, "description":"Conference Board ou Michigan. Signal dépenses → bénéfices → SP500."}, {"id":"us_gdp_growth","label":"Croissance PIB US","type":"structural","category":"macro","unit":"%","coefficient":5.0, "description":"Croissance réelle US annualisée. Surprise haussière → SP500 ↑."}, {"id":"vix_level","label":"Niveau VIX","type":"structural","category":"sentiment","unit":"pts","coefficient":-0.8, "description":"VIX. Spike → risk-off → SP500 ↓. Normalisation VIX → SP500 ↑."}, {"id":"geopolitical_risk_premium","label":"Prime risque géopolitique","type":"structural","category":"political","unit":"score","coefficient":-0.5, "description":"Tensions géopolitiques majeures → incertitude → prime de risque → SP500 ↓."}, {"id":"ev_central_bank","label":"Pression Banques Centrales","type":"event_driven","category":"central_bank","unit":"pips","description":"Fed pivot, pause, hawkish surprise → SP500 directement."}, {"id":"ev_macro_surprise","label":"Surprise Macro","type":"event_driven","category":"monetary_shock","unit":"pips","description":"NFP, CPI, PIB, ISM surprises."}, {"id":"ev_geopolitical","label":"Risque Géopolitique","type":"event_driven","category":"geopolitical","unit":"pips","description":"Conflits, sanctions → risk-off SP500."}, {"id":"ev_trade_policy","label":"Choc Commercial","type":"event_driven","category":"trade_policy","unit":"pips","description":"Tarifs → chaînes supply, marges bénéficiaires SP500."}, {"id":"ev_growth_shock","label":"Choc Croissance","type":"event_driven","category":"growth_shock","unit":"pips","description":"Récession/reprise choc."}, {"id":"ev_credit_stress","label":"Stress Crédit","type":"event_driven","category":"credit_stress","unit":"pips","description":"Banking stress → SP500 ↓ brutal."}, {"id":"ev_commodity","label":"Choc Commodités","type":"event_driven","category":"commodity","unit":"pips","description":"Choc énergie → marges → SP500."}, {"id":"ev_sentiment","label":"Sentiment & Positionnement","type":"event_driven","category":"sentiment","unit":"pips","description":"Flux institutionnels, CTA positioning."}, {"id":"ev_technical","label":"Momentum Technique","type":"event_driven","category":"technical","unit":"pips","description":"Niveaux clés SP500, MA200, cassures."}, {"id":"sp500","label":"S&P 500 Impact Net","type":"output","category":"output","unit":"pips","description":"Pression nette cumulée S&P 500"}, ] }, # ═══════════════════════════════════════════════════════════════════════════════ "TLT": { "name": "TLT (US Long Bonds)", "output_node": "tlt", "description": "ETF obligations US 20Y+ — duration longue, sensible aux taux et récession", "nodes": [ {"id":"fed_terminal_rate","label":"Taux terminal Fed anticipé","type":"structural","category":"monetary","unit":"bps","coefficient":-0.4, "description":"Taux Fed terminal pricé par les marchés. Hausse → taux longs up → TLT ↓ (duration ~18)."}, {"id":"us_10y_yield","label":"Rendement UST 10Y","type":"structural","category":"monetary","unit":"bps","coefficient":-0.3, "description":"Rendement 10Y direct. Chaque +1bps ≈ -$0.18 sur $100 TLT (duration modifiée ~18)."}, {"id":"us_30y_yield","label":"Rendement UST 30Y","type":"structural","category":"monetary","unit":"bps","coefficient":-0.25, "description":"Rendement 30Y. TLT détient principalement des 20-30Y."}, {"id":"inflation_breakeven_10y","label":"Breakeven inflation 10Y","type":"structural","category":"inflation","unit":"bps","coefficient":-0.2, "description":"Anticipations inflation 10Y. Hausse → taux nominaux ↑ → TLT ↓."}, {"id":"recession_probability","label":"Probabilité récession 12m","type":"structural","category":"macro","unit":"%","coefficient":0.3, "description":"Probabilité récession 12m (modèle yield curve ou Fed NY). Hausse → flight to safety → TLT ↑."}, {"id":"fed_qt_pace","label":"Rythme QT Fed (Mds$/mois)","type":"structural","category":"monetary","unit":"Mds$","coefficient":-0.1, "description":"Réduction bilan Fed (quantitative tightening). Plus vite = pression vendeur sur Treasuries → TLT ↓."}, {"id":"us_fiscal_deficit","label":"Déficit fiscal US (%PIB)","type":"structural","category":"macro","unit":"%","coefficient":-0.4, "description":"Déficit croissant → supply massive de Treasuries → pression vendeuse → TLT ↓."}, {"id":"term_premium","label":"Prime de terme (ACM)","type":"structural","category":"monetary","unit":"bps","coefficient":-0.3, "description":"Prime de terme (Adrian-Crump-Moench). Hausse = extra rendement exigé → TLT ↓."}, {"id":"foreign_treasury_demand","label":"Demande étrangère Treasuries","type":"structural","category":"flows","unit":"Mds$","coefficient":0.05, "description":"Achats Treasuries par banques centrales étrangères (Chine, Japon). Fort = TLT ↑."}, {"id":"yield_curve_slope","label":"Pente courbe 2Y-10Y","type":"structural","category":"monetary","unit":"bps","coefficient":0.1, "description":"Steepening (2Y-10Y ↑) = TLT ↑ relatif (marché price fin de hausse Fed)."}, {"id":"vix_level","label":"Niveau VIX","type":"structural","category":"sentiment","unit":"pts","coefficient":0.5, "description":"VIX spike → flight to quality Treasuries → TLT ↑ (sauf si crise stagflationniste)."}, {"id":"sp500_correlation","label":"Corrélation SP500 (inverse)","type":"structural","category":"sentiment","unit":"score","coefficient":-0.3, "description":"En régime normal : SP500 ↑ → TLT ↓ (rotation risque). Score + = corrélation positive anormale."}, {"id":"ev_central_bank","label":"Pression Banques Centrales","type":"event_driven","category":"central_bank","unit":"pips","description":"FOMC décisions, minutes → impact direct TLT."}, {"id":"ev_macro_surprise","label":"Surprise Macro","type":"event_driven","category":"monetary_shock","unit":"pips","description":"CPI, PCE, NFP → réévaluation taux → TLT."}, {"id":"ev_geopolitical","label":"Risque Géopolitique","type":"event_driven","category":"geopolitical","unit":"pips","description":"Crises → flight to safety Treasuries."}, {"id":"ev_growth_shock","label":"Choc Croissance","type":"event_driven","category":"growth_shock","unit":"pips","description":"Récession → TLT ↑ massif."}, {"id":"ev_credit_stress","label":"Stress Crédit","type":"event_driven","category":"credit_stress","unit":"pips","description":"Banking stress → fuite vers Treasuries."}, {"id":"ev_trade_policy","label":"Choc Commercial","type":"event_driven","category":"trade_policy","unit":"pips","description":"Tarifs → incertitude → Treasuries demandés ou vendus."}, {"id":"ev_sentiment","label":"Sentiment & Flux","type":"event_driven","category":"sentiment","unit":"pips","description":"Flux institutionnels bonds."}, {"id":"ev_technical","label":"Momentum Technique","type":"event_driven","category":"technical","unit":"pips","description":"Niveaux clés TLT, moyennes mobiles."}, {"id":"tlt","label":"TLT Impact Net","type":"output","category":"output","unit":"pips","description":"Pression nette cumulée TLT"}, ] }, # ═══════════════════════════════════════════════════════════════════════════════ "GBPUSD": { "name": "GBP/USD", "output_node": "gbpusd", "description": "Taux de change Livre sterling / Dollar — sensible aux données UK et BoE", "nodes": [ {"id":"boe_path_12m","label":"Anticipation BoE 12m","type":"structural","category":"monetary","unit":"bps","coefficient":0.7, "description":"Variation cumulée taux BoE attendue 12m. Hikes → GBP ↑ → GBP/USD ↑."}, {"id":"fed_path_12m","label":"Anticipation Fed 12m","type":"structural","category":"monetary","unit":"bps","coefficient":-0.7, "description":"Variation cumulée taux Fed attendue 12m. Hikes → USD ↑ → GBP/USD ↓."}, {"id":"uk_us_rate_differential","label":"Différentiel taux BoE-Fed","type":"structural","category":"monetary","unit":"bps","coefficient":1.0, "description":"Spread taux directeurs BoE vs Fed. Principal driver EUR/USD."}, {"id":"uk_cpi_yoy","label":"CPI UK YoY","type":"structural","category":"inflation","unit":"%","coefficient":0.7, "description":"Inflation UK. Persistance → BoE forcé de rester hawkish → GBP ↑."}, {"id":"uk_gdp_growth","label":"Croissance PIB UK","type":"structural","category":"macro","unit":"%","coefficient":3.0, "description":"PIB UK annualisé. Surprise haussière → GBP ↑."}, {"id":"uk_pmi_composite","label":"PMI composite UK","type":"structural","category":"macro","unit":"pts","coefficient":0.25, "description":"PMI composite UK. >50 = expansion → GBP ↑."}, {"id":"uk_labor_market","label":"Marché emploi UK","type":"structural","category":"macro","unit":"score","coefficient":0.4, "description":"Score santé emploi UK (chômage, salaires, emplois). Fort = BoE hawkish → GBP ↑."}, {"id":"uk_political_risk","label":"Risque politique UK","type":"structural","category":"political","unit":"score","coefficient":-0.4, "description":"Incertitude politique UK (snap election, budget, Brexit séquelles). Hausse → GBP ↓."}, {"id":"uk_current_account","label":"Balance courante UK (%PIB)","type":"structural","category":"flows","unit":"%","coefficient":0.3, "description":"Déficit courant UK chronique. Amélioration → moins de pression vendeuse GBP."}, {"id":"risk_appetite","label":"Appétit risque mondial","type":"structural","category":"sentiment","unit":"score","coefficient":0.4, "description":"Risk-on → GBP comme devise cyclique/risquée apprécie vs USD."}, {"id":"cftc_gbp_net","label":"Positions nettes GBP (CoT)","type":"structural","category":"positioning","unit":"k contrats","coefficient":0.08, "description":"Positions spéculatives nettes GBP CME CoT."}, {"id":"ev_central_bank","label":"Pression Banques Centrales","type":"event_driven","category":"central_bank","unit":"pips","description":"BoE, Fed décisions/minutes."}, {"id":"ev_macro_surprise","label":"Surprise Macro","type":"event_driven","category":"monetary_shock","unit":"pips","description":"CPI UK/US, NFP, GDP UK surprises."}, {"id":"ev_geopolitical","label":"Risque Géopolitique","type":"event_driven","category":"geopolitical","unit":"pips","description":"Conflits → risk-off → GBP vendu."}, {"id":"ev_trade_policy","label":"Choc Commercial","type":"event_driven","category":"trade_policy","unit":"pips","description":"Tarifs US → UK exposé."}, {"id":"ev_growth_shock","label":"Choc Croissance","type":"event_driven","category":"growth_shock","unit":"pips","description":"Récession UK/US."}, {"id":"ev_credit_stress","label":"Stress Crédit","type":"event_driven","category":"credit_stress","unit":"pips","description":"Stress Gilts, banking UK."}, {"id":"ev_sentiment","label":"Sentiment & Flux","type":"event_driven","category":"sentiment","unit":"pips","description":"Positionnement GBP."}, {"id":"ev_technical","label":"Momentum Technique","type":"event_driven","category":"technical","unit":"pips","description":"Niveaux clés cable."}, {"id":"gbpusd","label":"GBP/USD Impact Net","type":"output","category":"output","unit":"pips","description":"Pression nette cumulée GBP/USD"}, ] }, # ═══════════════════════════════════════════════════════════════════════════════ "EEM": { "name": "EEM (Marchés Émergents)", "output_node": "eem", "description": "ETF actions marchés émergents — sensible au dollar, Chine et commodités", "nodes": [ {"id":"dxy_inverse","label":"Indice Dollar (DXY) — impact inverse","type":"structural","category":"monetary","unit":"pts","coefficient":-0.8, "description":"USD fort → pression sur dettes EM en USD, sorties capitaux EM → EEM ↓. DXY ↑ = EEM ↓."}, {"id":"us_real_rate","label":"Taux réel US 10Y","type":"structural","category":"monetary","unit":"bps","coefficient":-0.6, "description":"Taux réel US élevé → capitaux retournent aux US → sorties EM → EEM ↓."}, {"id":"fed_path_12m","label":"Anticipation Fed 12m","type":"structural","category":"monetary","unit":"bps","coefficient":-0.5, "description":"Cuts Fed → dollar faible + taux attractifs EM → capitaux entrent EM → EEM ↑."}, {"id":"china_pmi","label":"PMI manufacturier Chine","type":"structural","category":"macro","unit":"pts","coefficient":0.6, "description":"PMI Caixin/NBS Chine. Expansion = croissance EM → EEM ↑ (Chine ~28% de l'indice)."}, {"id":"china_growth_stimulus","label":"Stimulus croissance Chine","type":"structural","category":"macro","unit":"score","coefficient":0.8, "description":"Score stimulus PBOC/gouvernement. Annonces majeures → EEM spike haussier."}, {"id":"commodity_complex","label":"Indice commodités (export EM)","type":"structural","category":"commodity","unit":"score","coefficient":0.4, "description":"Commodités elevés → exportateurs EM (Brésil, Russie, Afrique du Sud) profitent → EEM ↑."}, {"id":"em_credit_spread","label":"Spread crédit souverain EM","type":"structural","category":"credit","unit":"bps","coefficient":-0.5, "description":"EMBI spread. Hausse = stress EM → sorties → EEM ↓."}, {"id":"em_capital_flows","label":"Flux capitaux EM nets","type":"structural","category":"flows","unit":"Mds$","coefficient":0.3, "description":"IIF flux nets vers EM. Entrées soutenues = EEM ↑ structurel."}, {"id":"risk_appetite","label":"Appétit risque mondial","type":"structural","category":"sentiment","unit":"score","coefficient":0.6, "description":"Risk-on → recherche de rendement EM → EEM ↑."}, {"id":"vix_level","label":"Niveau VIX","type":"structural","category":"sentiment","unit":"pts","coefficient":-0.5, "description":"VIX spike → sorties EM (flight to quality) → EEM ↓."}, {"id":"us_china_tension","label":"Tensions US-Chine","type":"structural","category":"political","unit":"score","coefficient":-0.5, "description":"Tensions US-Chine (tarifs, sanctions, tech). Hausse → risque EM → EEM ↓."}, {"id":"ev_central_bank","label":"Pression Banques Centrales","type":"event_driven","category":"central_bank","unit":"pips","description":"Fed pivot, PBOC mesures."}, {"id":"ev_macro_surprise","label":"Surprise Macro","type":"event_driven","category":"monetary_shock","unit":"pips","description":"Données Chine, US macro."}, {"id":"ev_geopolitical","label":"Risque Géopolitique","type":"event_driven","category":"geopolitical","unit":"pips","description":"Tensions régionales EM."}, {"id":"ev_trade_policy","label":"Choc Commercial","type":"event_driven","category":"trade_policy","unit":"pips","description":"Tarifs US-Chine, sanctions."}, {"id":"ev_growth_shock","label":"Choc Croissance","type":"event_driven","category":"growth_shock","unit":"pips","description":"Récession US/Chine → EEM ↓."}, {"id":"ev_commodity","label":"Choc Commodités","type":"event_driven","category":"commodity","unit":"pips","description":"Chocs matières premières → exportateurs EM."}, {"id":"ev_credit_stress","label":"Stress Crédit EM","type":"event_driven","category":"credit_stress","unit":"pips","description":"Stress souverain EM, crise devises EM."}, {"id":"ev_sentiment","label":"Sentiment & Flux","type":"event_driven","category":"sentiment","unit":"pips","description":"Flux ETF EM, risk-on/off."}, {"id":"ev_technical","label":"Momentum Technique","type":"event_driven","category":"technical","unit":"pips","description":"Niveaux EEM, tendances."}, {"id":"eem","label":"EEM Impact Net","type":"output","category":"output","unit":"pips","description":"Pression nette cumulée EEM"}, ] }, # ═══════════════════════════════════════════════════════════════════════════════ "QQQ": { "name": "QQQ (NASDAQ-100 Tech)", "output_node": "qqq", "description": "ETF NASDAQ-100 — tech et croissance US, sensible aux taux réels et bénéfices tech", "nodes": [ {"id":"us_real_rate_10y","label":"Taux réel US 10Y (duration tech)","type":"structural","category":"monetary","unit":"bps","coefficient":-1.5, "description":"PRINCIPAL driver : tech = duration longue (flux futurs). Taux réels ↑ → actualisation ↑ → PE tech ↓ → QQQ ↓."}, {"id":"fed_path_12m","label":"Anticipation Fed 12m","type":"structural","category":"monetary","unit":"bps","coefficient":-0.8, "description":"Cuts attendus → taux discount ↓ → PE tech expansion → QQQ ↑."}, {"id":"big_tech_eps_revision","label":"Révisions BPA Big Tech (M7)","type":"structural","category":"earnings","unit":"%","coefficient":15.0, "description":"Révisions bénéfices Magnificent 7 (AAPL, MSFT, NVDA, GOOGL, AMZN, META, TSLA). +1% ≈ +15 pts QQQ."}, {"id":"ai_capex_cycle","label":"Cycle capex IA","type":"structural","category":"tech","unit":"score","coefficient":0.8, "description":"Score momentum investissements IA (Hyperscalers capex, NVDA demande GPU). Positif = QQQ ↑."}, {"id":"semiconductor_cycle","label":"Cycle semi-conducteurs","type":"structural","category":"tech","unit":"score","coefficient":0.6, "description":"Phase cycle semis (book-to-bill, inventaires, commandes). Upcycle = QQQ ↑."}, {"id":"tech_pe_multiple","label":"Multiple PE tech (NTM)","type":"structural","category":"valuation","unit":"x","coefficient":12.0, "description":"PE forward NASDAQ-100. Expansion = QQQ ↑. Actuellement élevé → sensible aux déceptions."}, {"id":"regulation_risk_tech","label":"Risque réglementaire tech","type":"structural","category":"political","unit":"score","coefficient":-0.5, "description":"Risque antitrust, régulation IA, vie privée (EU AI Act, FTC actions). Hausse → QQQ ↓."}, {"id":"us_consumer_spending","label":"Dépenses consommateur US","type":"structural","category":"macro","unit":"score","coefficient":0.4, "description":"Dépenses techno consommateur (iPhone, cloud, publicité). Solide = revenus tech → QQQ ↑."}, {"id":"cloud_growth_enterprise","label":"Croissance cloud enterprise","type":"structural","category":"tech","unit":"%","coefficient":0.6, "description":"Croissance AWS/Azure/GCP. Moteur marges operating → QQQ."}, {"id":"vix_level","label":"Niveau VIX","type":"structural","category":"sentiment","unit":"pts","coefficient":-0.8, "description":"VIX. Spike → vente tech en premier (beta élevé) → QQQ ↓ plus que SP500."}, {"id":"retail_options_flow","label":"Flux options retail (call buying)","type":"structural","category":"flows","unit":"score","coefficient":0.4, "description":"Momentum gamma/delta flows retail sur options tech. FOMO = QQQ squeeze haussier."}, {"id":"china_tech_risk","label":"Risque restrictions tech US-Chine","type":"structural","category":"political","unit":"score","coefficient":-0.4, "description":"Restrictions export chips, sanctions tech US-Chine. Hausse → revenus tech ↓ → QQQ ↓."}, {"id":"ev_central_bank","label":"Pression Banques Centrales","type":"event_driven","category":"central_bank","unit":"pips","description":"Fed pivot → QQQ amplificateur."}, {"id":"ev_macro_surprise","label":"Surprise Macro","type":"event_driven","category":"monetary_shock","unit":"pips","description":"CPI, NFP → réévaluation Fed → QQQ."}, {"id":"ev_geopolitical","label":"Risque Géopolitique","type":"event_driven","category":"geopolitical","unit":"pips","description":"Tensions → risk-off → sell tech."}, {"id":"ev_trade_policy","label":"Choc Commercial","type":"event_driven","category":"trade_policy","unit":"pips","description":"Tarifs tech, restrictions exports."}, {"id":"ev_growth_shock","label":"Choc Croissance","type":"event_driven","category":"growth_shock","unit":"pips","description":"Récession → dépenses IT coupées → QQQ."}, {"id":"ev_credit_stress","label":"Stress Crédit","type":"event_driven","category":"credit_stress","unit":"pips","description":"Conditions financières → tech financement."}, {"id":"ev_commodity","label":"Choc Commodités","type":"event_driven","category":"commodity","unit":"pips","description":"Énergie → data centers coûts."}, {"id":"ev_sentiment","label":"Sentiment & Positionnement","type":"event_driven","category":"sentiment","unit":"pips","description":"Flux CTAs, hedge funds tech."}, {"id":"ev_technical","label":"Momentum Technique","type":"event_driven","category":"technical","unit":"pips","description":"Niveaux QQQ, MA200, RSI."}, {"id":"qqq","label":"QQQ Impact Net","type":"output","category":"output","unit":"pips","description":"Pression nette cumulée QQQ"}, ] }, } # end INSTRUMENT_MODELS # ── Category labels (French) ──────────────────────────────────────────────────── 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", # event-driven categories (from causal_graph_templates.category) "central_bank": "Banques Centrales", "monetary_shock": "Surprise Macro", "geopolitical": "Géopolitique", "trade_policy": "Commerce / Tarifs", "growth_shock": "Choc Croissance", "commodity": "Commodités", "credit_stress": "Stress Crédit", "sentiment": "Sentiment", "technical": "Technique", "positioning": "Positionnement", "unclassified": "Non Classifié", } # ── DB init ───────────────────────────────────────────────────────────────────── 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): """Insert/update instrument models on startup. Never overwrites user overrides.""" init_instrument_model_tables(conn) for inst, model in INSTRUMENT_MODELS.items(): existing = conn.execute( "SELECT id FROM instrument_models WHERE instrument=?", (inst,) ).fetchone() graph_json = json.dumps({ "name": model["name"], "description": model["description"], "output_node": model["output_node"], "nodes": model["nodes"], }) if existing: conn.execute( "UPDATE instrument_models SET graph_json=?, updated_at=datetime('now') WHERE instrument=?", (graph_json, inst) ) else: conn.execute( "INSERT INTO instrument_models (instrument, graph_json) VALUES (?,?)", (inst, graph_json) ) conn.commit() # ── Value computation ─────────────────────────────────────────────────────────── def _compute_event_driven_values(conn, instrument: str, ref_date: date_type) -> dict[str, float]: """ Pour chaque catégorie event-driven : somme des pips × decay sur toutes les analyses actives. Retourne {category: float} en pips. """ extended_from = ref_date - timedelta(days=180) inst_upper = instrument.upper() rows = conn.execute(""" SELECT a.prediction_json, e.start_date, e.end_date AS event_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] = {} inst_lower = inst_upper.lower() 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")) # Guidance events: dynamic absorption until meeting date ev_end = r.get("event_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" except ValueError: pass try: ev_date = date_type.fromisoformat(r["start_date"][:10]) except ValueError: continue days_elapsed = (ref_date - ev_date).days df = _decay(days_elapsed, absorption, dtype) if df < 0.01: continue cat = r["category"] by_cat[cat] = by_cat.get(cat, 0.0) + round(pips * df, 2) return by_cat def get_model_state(conn, instrument: str, at_date: Optional[str] = None) -> Optional[dict]: """ Retourne le graphe instrument avec les valeurs courantes de chaque nœud. """ inst_upper = instrument.upper() row = conn.execute( "SELECT graph_json FROM instrument_models WHERE instrument=?", (inst_upper,) ).fetchone() if not row: return None graph = 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() # Load user overrides override_rows = conn.execute( "SELECT node_id, value, note, set_at FROM instrument_node_overrides WHERE instrument=?", (inst_upper,) ).fetchall() overrides: dict[str, dict] = {r["node_id"]: dict(r) for r in override_rows} # Compute event-driven values by category ev_by_cat = _compute_event_driven_values(conn, inst_upper, ref_date) # Build node states net_pips = 0.0 nodes_out = [] for node in graph["nodes"]: nid = node["id"] ntype = node["type"] coeff = node.get("coefficient", 1.0) cat = node["category"] state = dict(node) if ntype == "structural": ov = overrides.get(nid) if ov: state["current_value"] = ov["value"] state["pip_contribution"] = round(ov["value"] * coeff, 1) state["source"] = "manual" state["override_note"] = ov.get("note", "") state["override_set_at"] = ov.get("set_at", "") else: state["current_value"] = 0.0 state["pip_contribution"] = 0.0 state["source"] = "neutral" net_pips += state["pip_contribution"] elif ntype == "event_driven": pips = ev_by_cat.get(cat, 0.0) # manual override possible too ov = overrides.get(nid) if ov: pips = ov["value"] state["source"] = "manual" state["override_note"] = ov.get("note", "") else: state["source"] = "events" if pips != 0 else "neutral" state["current_value"] = round(pips, 1) state["pip_contribution"] = round(pips, 1) net_pips += pips elif ntype == "output": state["current_value"] = None # filled after loop state["pip_contribution"] = None state["source"] = "computed" nodes_out.append(state) net_pips = round(net_pips, 1) # Fill output node for n in nodes_out: if n["type"] == "output": n["current_value"] = net_pips n["pip_contribution"] = net_pips direction = "bullish" if net_pips > 5 else "bearish" if net_pips < -5 else "neutral" return { "instrument": inst_upper, "name": graph["name"], "description": graph.get("description", ""), "at_date": str(ref_date), "net_pips": net_pips, "direction": direction, "nodes": nodes_out, "output_node": graph["output_node"], } def set_node_override(conn, instrument: str, node_id: str, value: float, note: str = "") -> bool: inst_upper = instrument.upper() 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') """, (inst_upper, node_id, value, note)) conn.commit() return True def clear_node_override(conn, instrument: str, node_id: str) -> bool: inst_upper = instrument.upper() conn.execute( "DELETE FROM instrument_node_overrides WHERE instrument=? AND node_id=?", (inst_upper, node_id) ) conn.commit() return True