Files
OpenFin/backend/services/instrument_models.py
2026-07-02 20:31:30 +02:00

670 lines
53 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 — 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