1242 lines
94 KiB
Python
1242 lines
94 KiB
Python
"""
|
|
Bibliothèque de graphes causaux — templates pré-peuplés et helpers.
|
|
|
|
Chaque template définit une chaîne causale entre un type d'événement de marché
|
|
et un ou plusieurs instruments (EURUSD, XAUUSD, SP500, BRENT...).
|
|
|
|
Structure graph_json :
|
|
nodes[] — nœuds avec position (x,y) pour le rendu SVG
|
|
edges[] — arêtes directionnelles entre nœuds
|
|
coefficients — valeurs heuristiques + calibrées
|
|
instruments[] — instruments cibles du template
|
|
input_mapping — comment extraire les inputs depuis un market_event
|
|
"""
|
|
import ast
|
|
import json
|
|
import logging
|
|
import operator
|
|
from typing import Optional
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# ── Helpers de construction ────────────────────────────────────────────────────
|
|
|
|
def _n(id_, label, type_, x, y, formula=None, unit="", instrument=None, description=""):
|
|
n = {"id": id_, "label": label, "type": type_, "x": x, "y": y}
|
|
if formula: n["formula"] = formula
|
|
if unit: n["unit"] = unit
|
|
if instrument: n["instrument"] = instrument
|
|
if description: n["description"] = description
|
|
return n
|
|
|
|
def _e(from_, to_, style="solid", type_="causal", strength=2, sign="neutral", label=""):
|
|
e = {"from": from_, "to": to_, "style": style, "type": type_,
|
|
"strength": strength, "sign": sign}
|
|
if label: e["label"] = label
|
|
return e
|
|
|
|
def _c(value, description=""):
|
|
return {"value": value, "calibrated": None, "description": description}
|
|
|
|
|
|
# ── Slugs régimes (12 templates exhaustifs — liste fermée pour auto-assign) ───
|
|
REGIME_SLUGS = [
|
|
"MACRO_DATA_SURPRISE",
|
|
"CENTRAL_BANK_DECISION",
|
|
"GROWTH_CORPORATE_SIGNAL",
|
|
"GEOPOLITICAL_RISK_OFF",
|
|
"COMMODITY_SUPPLY_SHOCK",
|
|
"TRADE_POLICY_SHOCK",
|
|
"CREDIT_SYSTEMIC_EVENT",
|
|
"TECHNICAL_MOMENTUM_BREAKOUT",
|
|
"SENTIMENT_POSITIONING_EXTREME",
|
|
"COMMODITY_INVENTORY_REPORT",
|
|
"INSTITUTIONAL_FLOW",
|
|
"UNCLASSIFIED_IMPACT",
|
|
]
|
|
|
|
BUILT_IN_TEMPLATES = [
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# 1. MACRO_DATA_SURPRISE
|
|
# CPI, PCE, NFP, ADP, GDP, PMI, ISM, Retail Sales, Jobless Claims, JOLTS...
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
{
|
|
"name": "Macro Data Surprise",
|
|
"slug": "MACRO_DATA_SURPRISE",
|
|
"category": "monetary_shock",
|
|
"heuristic_ver": 2,
|
|
"calibration_json": {"lag_days": 0, "half_life_days": 4, "absorption_days": 12, "decay_type": "exp"},
|
|
"instruments": ["EURUSD", "SP500", "XAUUSD", "QQQ", "TLT", "USDJPY", "EEM", "GDX", "SLV"],
|
|
"description": "Scheduled economic release vs consensus -> OIS repricing -> full asset repricing",
|
|
"ai_rationale": "Hawkish surprise: OIS up -> 2Y up -> USD up -> EURUSD/USDJPY/EEM react; 10Y up -> TLT down, GDX/XAUUSD down. Growth channel -> SP500/QQQ up. Dovish miss: all reversed.",
|
|
"graph_json": {
|
|
"nodes": [
|
|
_n("macro_surprise", "Macro Surprise", "input", 400, 50, unit="sigma"),
|
|
_n("ois_repricing", "Fed OIS Repricing", "observable", 180, 180, formula="macro_surprise * {{coef_surp_ois}}", unit="bps"),
|
|
_n("growth_exp", "Growth Expectation", "latent", 580, 180, formula="macro_surprise * {{coef_surp_growth}}"),
|
|
_n("inflation_risk", "Inflation Risk", "latent", 820, 180, formula="macro_surprise * {{coef_surp_infl}}"),
|
|
_n("us_2y", "Delta US 2Y", "observable", 80, 330, formula="ois_repricing * {{coef_ois_2y}}", unit="%"),
|
|
_n("us_10y", "Delta US 10Y", "observable", 340, 330, formula="ois_repricing * {{coef_ois_10y}} + inflation_risk * {{coef_infl_10y}}", unit="%"),
|
|
_n("fin_conditions", "Financial Conditions", "latent", 620, 330, formula="ois_repricing * {{coef_ois_fc}} + us_10y * {{coef_10y_fc}}"),
|
|
_n("usd_strength", "USD Strength", "observable", 80, 470, formula="us_2y * {{coef_2y_usd}}", unit="%"),
|
|
# ── Primary outputs row 1 ──────────────────────────────────────────
|
|
_n("eurusd", "EUR/USD", "market_asset", 60, 620, formula="-usd_strength * {{coef_usd_eurusd}}", unit="pips", instrument="EURUSD"),
|
|
_n("sp500", "S&P 500", "market_asset", 260, 620, formula="growth_exp * {{coef_growth_sp}} - fin_conditions * {{coef_fc_sp}}", unit="pts", instrument="SP500"),
|
|
_n("xauusd", "Gold XAU/USD", "market_asset", 460, 620, formula="-us_10y * {{coef_10y_gold}} - usd_strength * {{coef_usd_gold}}", unit="$/oz", instrument="XAUUSD"),
|
|
# ── Additional outputs row 2 ──────────────────────────────────────
|
|
_n("qqq", "QQQ Nasdaq", "market_asset", 60, 790, formula="growth_exp * {{coef_growth_qqq}} - fin_conditions * {{coef_fc_qqq}}", unit="pts", instrument="QQQ"),
|
|
_n("tlt", "TLT 20Y Bond", "market_asset", 230, 790, formula="-us_10y * {{coef_10y_tlt}}", unit="pts", instrument="TLT"),
|
|
_n("usdjpy", "USD/JPY", "market_asset", 400, 790, formula="usd_strength * {{coef_usd_usdjpy}}", unit="pips", instrument="USDJPY"),
|
|
_n("eem", "EEM Emerg Mkt", "market_asset", 570, 790, formula="-usd_strength * {{coef_usd_eem}} + growth_exp * {{coef_growth_eem}}", unit="pts", instrument="EEM"),
|
|
_n("gdx", "GDX Gold Mine", "market_asset", 740, 790, formula="-us_10y * {{coef_10y_gdx}} - usd_strength * {{coef_usd_gdx}}", unit="pts", instrument="GDX"),
|
|
_n("slv", "SLV Silver", "market_asset", 910, 790, formula="-us_10y * {{coef_10y_slv}} - usd_strength * {{coef_usd_slv}}", unit="pts", instrument="SLV"),
|
|
],
|
|
"edges": [
|
|
_e("macro_surprise", "ois_repricing", "solid", "rate_repricing", strength=3, sign="positive"),
|
|
_e("macro_surprise", "growth_exp", "dashed", "growth_channel", strength=2, sign="positive"),
|
|
_e("macro_surprise", "inflation_risk", "dashed", "inflation_ch", strength=2, sign="positive"),
|
|
_e("ois_repricing", "us_2y", "solid", "short_end", strength=3, sign="positive"),
|
|
_e("ois_repricing", "us_10y", "dashed", "long_end", strength=1, sign="positive"),
|
|
_e("inflation_risk", "us_10y", "solid", "term_premium", strength=2, sign="positive"),
|
|
_e("ois_repricing", "fin_conditions", "solid", "policy_tighten", strength=2, sign="positive"),
|
|
_e("us_10y", "fin_conditions", "solid", "discount_rate", strength=2, sign="positive"),
|
|
_e("us_2y", "usd_strength", "solid", "carry_diff", strength=3, sign="positive"),
|
|
# row 1 outputs
|
|
_e("usd_strength", "eurusd", "solid", "fx_channel", strength=3, sign="negative"),
|
|
_e("growth_exp", "sp500", "solid", "earnings_ch", strength=2, sign="positive"),
|
|
_e("fin_conditions", "sp500", "solid", "valuation_ch", strength=3, sign="negative"),
|
|
_e("us_10y", "xauusd", "solid", "real_rate_gold",strength=2, sign="negative"),
|
|
_e("usd_strength", "xauusd", "dashed", "usd_gold", strength=2, sign="negative"),
|
|
# row 2 outputs
|
|
_e("growth_exp", "qqq", "solid", "tech_growth", strength=3, sign="positive", label="tech/growth beta"),
|
|
_e("fin_conditions", "qqq", "solid", "rate_compress", strength=3, sign="negative", label="high duration sensitivity"),
|
|
_e("us_10y", "tlt", "solid", "bond_price", strength=3, sign="negative", label="price = inverse yield"),
|
|
_e("usd_strength", "usdjpy", "solid", "carry_ch", strength=2, sign="positive", label="USD carry -> JPY weakens"),
|
|
_e("usd_strength", "eem", "solid", "usd_eem", strength=2, sign="negative", label="USD -> EM outflows"),
|
|
_e("growth_exp", "eem", "dashed", "growth_eem", strength=2, sign="positive"),
|
|
_e("us_10y", "gdx", "solid", "gold_miners", strength=2, sign="negative"),
|
|
_e("usd_strength", "gdx", "dashed", "usd_miners", strength=2, sign="negative"),
|
|
_e("us_10y", "slv", "solid", "silver_rates", strength=2, sign="negative"),
|
|
_e("usd_strength", "slv", "dashed", "silver_usd", strength=1, sign="negative"),
|
|
],
|
|
"coefficients": {
|
|
"coef_surp_ois": _c(8.0, "1sigma -> OIS bps"),
|
|
"coef_surp_growth": _c(0.30, "1sigma -> growth exp"),
|
|
"coef_surp_infl": _c(0.15, "1sigma -> inflation risk"),
|
|
"coef_ois_2y": _c(0.028, "OIS bps -> 2Y yield"),
|
|
"coef_ois_10y": _c(0.008, "OIS bps -> 10Y yield"),
|
|
"coef_infl_10y": _c(0.12, "Inflation -> 10Y premium"),
|
|
"coef_ois_fc": _c(0.40, "OIS -> financial conditions"),
|
|
"coef_10y_fc": _c(0.60, "10Y -> financial conditions"),
|
|
"coef_2y_usd": _c(0.50, "2Y -> USD strength"),
|
|
"coef_usd_eurusd": _c(80, "USD -> EURUSD pips fall"),
|
|
"coef_growth_sp": _c(60, "Growth -> SP500 pts"),
|
|
"coef_fc_sp": _c(50, "Fin cond -> SP500 pts fall"),
|
|
"coef_10y_gold": _c(15, "1% 10Y -> Gold fall $/oz"),
|
|
"coef_usd_gold": _c(10, "USD -> Gold fall $/oz"),
|
|
"coef_growth_qqq": _c(90, "Growth -> QQQ pts (higher beta)"),
|
|
"coef_fc_qqq": _c(75, "Fin cond -> QQQ pts fall (duration)"),
|
|
"coef_10y_tlt": _c(16, "1% 10Y -> TLT pts fall (~16y duration)"),
|
|
"coef_usd_usdjpy": _c(100, "USD strength -> USDJPY pips up"),
|
|
"coef_usd_eem": _c(25, "USD -> EEM pts fall"),
|
|
"coef_growth_eem": _c(12, "Growth -> EEM pts up"),
|
|
"coef_10y_gdx": _c(30, "1% 10Y -> GDX pts fall (2x gold beta)"),
|
|
"coef_usd_gdx": _c(20, "USD -> GDX pts fall"),
|
|
"coef_10y_slv": _c(8, "1% 10Y -> SLV pts fall (less monetary than gold)"),
|
|
"coef_usd_slv": _c(5, "USD -> SLV pts fall"),
|
|
},
|
|
"instruments": ["EURUSD", "SP500", "XAUUSD", "QQQ", "TLT", "USDJPY", "EEM", "GDX", "SLV"],
|
|
"input_mapping": {
|
|
"macro_surprise": {"source": "surprise", "unit": "sigma", "range": [-3, 3]}
|
|
},
|
|
},
|
|
},
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# 2. CENTRAL_BANK_DECISION
|
|
# FOMC, ECB, BoJ, BoE, BoC, RBA, SNB, PBOC
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
{
|
|
"name": "Central Bank Decision",
|
|
"slug": "CENTRAL_BANK_DECISION",
|
|
"category": "central_bank",
|
|
"heuristic_ver": 2,
|
|
"calibration_json": {"lag_days": 0, "half_life_days": 5, "absorption_days": 14, "decay_type": "exp"},
|
|
"instruments": ["EURUSD", "SP500", "XAUUSD", "QQQ", "TLT", "USDJPY", "GBPUSD", "XLF", "EEM"],
|
|
"description": "CB rate decision + forward guidance -> full curve repricing -> carry FX, bonds, equities",
|
|
"ai_rationale": "Rate hike -> 2Y/10Y up -> carry differential -> EURUSD/USDJPY/GBPUSD down; TLT down; QQQ hardest hit (duration); XLF benefits from steeper curve. Dovish: all reversed. EEM suffers from USD rate rises (capital outflows).",
|
|
"graph_json": {
|
|
"nodes": [
|
|
_n("rate_surprise_bps", "Rate Surprise bps", "input", 140, 50, unit="bps"),
|
|
_n("tone_score", "Guidance Tone", "input", 500, 50, unit="score"),
|
|
_n("rate_channel", "Rate Path Revision", "observable", 140, 190, formula="rate_surprise_bps / 25 * {{coef_rate_mult}}"),
|
|
_n("guidance_channel", "Forward Guidance", "latent", 500, 190, formula="tone_score * {{coef_tone}}"),
|
|
_n("us_2y", "Delta 2Y Yield", "observable", 80, 340, formula="rate_channel * {{coef_rate_2y}} + guidance_channel * {{coef_tone_2y}}", unit="%"),
|
|
_n("us_10y", "Delta 10Y Yield", "observable", 320, 340, formula="rate_channel * {{coef_rate_10y}} + guidance_channel * {{coef_tone_10y}}", unit="%"),
|
|
_n("risk_appetite", "Risk Appetite", "latent", 600, 340, formula="-rate_channel * {{coef_rate_risk}} + guidance_channel * {{coef_tone_risk}}"),
|
|
_n("carry_diff", "Carry Differential", "observable", 80, 470, formula="us_2y * {{coef_2y_carry}}", unit="%"),
|
|
# ── Row 1 outputs ─────────────────────────────────────────────────
|
|
_n("eurusd", "EUR/USD", "market_asset", 60, 620, formula="-carry_diff * {{coef_carry_eurusd}}", unit="pips", instrument="EURUSD"),
|
|
_n("sp500", "S&P 500", "market_asset", 270, 620, formula="risk_appetite * {{coef_risk_sp}} - us_10y * {{coef_10y_sp}}", unit="pts", instrument="SP500"),
|
|
_n("xauusd", "Gold XAU/USD", "market_asset", 480, 620, formula="-us_10y * {{coef_10y_gold}} - carry_diff * {{coef_carry_gold}}", unit="$/oz", instrument="XAUUSD"),
|
|
# ── Row 2 outputs ─────────────────────────────────────────────────
|
|
_n("qqq", "QQQ Nasdaq", "market_asset", 60, 790, formula="risk_appetite * {{coef_risk_qqq}} - us_10y * {{coef_10y_qqq}}", unit="pts", instrument="QQQ"),
|
|
_n("tlt", "TLT 20Y Bond", "market_asset", 230, 790, formula="-us_10y * {{coef_10y_tlt}}", unit="pts", instrument="TLT"),
|
|
_n("usdjpy", "USD/JPY", "market_asset", 400, 790, formula="-carry_diff * {{coef_carry_usdjpy}}", unit="pips", instrument="USDJPY"),
|
|
_n("gbpusd", "GBP/USD", "market_asset", 570, 790, formula="-carry_diff * {{coef_carry_gbpusd}}", unit="pips", instrument="GBPUSD"),
|
|
_n("xlf", "XLF Financials","market_asset", 740, 790, formula="us_10y * {{coef_10y_xlf}} + risk_appetite * {{coef_risk_xlf}}", unit="pts", instrument="XLF"),
|
|
_n("eem", "EEM Emerg Mkt", "market_asset", 910, 790, formula="-carry_diff * {{coef_carry_eem}}", unit="pts", instrument="EEM"),
|
|
],
|
|
"edges": [
|
|
_e("rate_surprise_bps", "rate_channel", "solid", "rate_reprice", strength=3, sign="positive"),
|
|
_e("tone_score", "guidance_channel","dashed","fwd_guidance", strength=3, sign="positive"),
|
|
_e("rate_channel", "us_2y", "solid", "short_anchor", strength=3, sign="positive"),
|
|
_e("guidance_channel","us_2y", "dashed", "bleed_2y", strength=1, sign="positive"),
|
|
_e("rate_channel", "us_10y", "solid", "level_shift", strength=2, sign="positive"),
|
|
_e("guidance_channel","us_10y", "dashed", "long_end_guid", strength=2, sign="positive"),
|
|
_e("rate_channel", "risk_appetite","dashed", "cost_capital", strength=2, sign="negative"),
|
|
_e("guidance_channel","risk_appetite","dashed","confidence", strength=2, sign="positive"),
|
|
_e("us_2y", "carry_diff", "solid", "carry", strength=3, sign="positive"),
|
|
# row 1
|
|
_e("carry_diff", "eurusd", "solid", "fx_carry", strength=3, sign="negative"),
|
|
_e("risk_appetite", "sp500", "solid", "risk_on_sp", strength=2, sign="positive"),
|
|
_e("us_10y", "sp500", "solid", "discount_sp", strength=3, sign="negative"),
|
|
_e("us_10y", "xauusd", "solid", "real_rate_gold",strength=3, sign="negative"),
|
|
_e("carry_diff", "xauusd", "dashed", "opp_cost_gold", strength=2, sign="negative"),
|
|
# row 2
|
|
_e("risk_appetite", "qqq", "solid", "risk_on_qqq", strength=2, sign="positive"),
|
|
_e("us_10y", "qqq", "solid", "duration_qqq", strength=3, sign="negative", label="tech=long duration"),
|
|
_e("us_10y", "tlt", "solid", "bond_price", strength=3, sign="negative"),
|
|
_e("carry_diff", "usdjpy", "solid", "carry_jpy", strength=2, sign="negative", label="USD carry -> JPY weakens"),
|
|
_e("carry_diff", "gbpusd", "solid", "carry_gbp", strength=2, sign="negative"),
|
|
_e("us_10y", "xlf", "solid", "nim_steepen", strength=2, sign="positive", label="steeper curve -> NIM"),
|
|
_e("risk_appetite", "xlf", "dashed", "risk_banks", strength=2, sign="positive"),
|
|
_e("carry_diff", "eem", "solid", "em_outflows", strength=2, sign="negative", label="USD hike -> EM outflows"),
|
|
],
|
|
"coefficients": {
|
|
"coef_rate_mult": _c(1.5, "Decision -> rate path revision"),
|
|
"coef_tone": _c(0.8, "Tone -> guidance signal"),
|
|
"coef_rate_2y": _c(0.70, "Rate path -> 2Y"),
|
|
"coef_tone_2y": _c(0.03, "Guidance -> 2Y bleed"),
|
|
"coef_rate_10y": _c(0.30, "Rate path -> 10Y"),
|
|
"coef_tone_10y": _c(0.07, "Guidance -> 10Y long end"),
|
|
"coef_rate_risk": _c(0.30, "Rate hike -> risk appetite negative"),
|
|
"coef_tone_risk": _c(0.40, "Positive guidance -> risk appetite"),
|
|
"coef_2y_carry": _c(1.0, "2Y -> carry differential"),
|
|
"coef_carry_eurusd": _c(120, "1% carry -> EURUSD pips fall"),
|
|
"coef_risk_sp": _c(40, "Risk appetite -> SP500 pts"),
|
|
"coef_10y_sp": _c(80, "1% 10Y -> SP500 pts fall"),
|
|
"coef_10y_gold": _c(20, "1% 10Y -> Gold fall $/oz"),
|
|
"coef_carry_gold": _c(15, "Carry -> Gold fall"),
|
|
"coef_risk_qqq": _c(50, "Risk appetite -> QQQ pts"),
|
|
"coef_10y_qqq": _c(110, "1% 10Y -> QQQ pts fall (high duration)"),
|
|
"coef_10y_tlt": _c(16, "1% 10Y -> TLT pts fall"),
|
|
"coef_carry_usdjpy": _c(150, "1% carry -> USDJPY pips (USD up -> JPY down)"),
|
|
"coef_carry_gbpusd": _c(80, "1% carry -> GBPUSD pips fall"),
|
|
"coef_10y_xlf": _c(15, "1% 10Y -> XLF pts (curve steepens -> NIM up)"),
|
|
"coef_risk_xlf": _c(20, "Risk appetite -> XLF pts"),
|
|
"coef_carry_eem": _c(20, "1% carry -> EEM pts fall"),
|
|
},
|
|
"instruments": ["EURUSD", "SP500", "XAUUSD", "QQQ", "TLT", "USDJPY", "GBPUSD", "XLF", "EEM"],
|
|
"input_mapping": {
|
|
"rate_surprise_bps": {"source": "surprise_bps", "unit": "bps"},
|
|
"tone_score": {"source": "user_input", "unit": "score", "range": [-3, 3]},
|
|
},
|
|
},
|
|
},
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# 3. GROWTH_CORPORATE_SIGNAL
|
|
# Earnings, M&A, layoffs, guidance, analyst revisions
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
{
|
|
"name": "Growth & Corporate Signal",
|
|
"slug": "GROWTH_CORPORATE_SIGNAL",
|
|
"category": "growth_shock",
|
|
"heuristic_ver": 2,
|
|
"calibration_json": {"lag_days": 0, "half_life_days": 4, "absorption_days": 10, "decay_type": "exp"},
|
|
"instruments": ["SP500", "QQQ", "IWM", "EURUSD", "XAUUSD", "HYG"],
|
|
"description": "Corporate/macro growth signal -> earnings revision + risk appetite -> equities, credit, FX, Gold",
|
|
"ai_rationale": "Earnings beats revise EPS upward and compress credit spreads (HYG up). QQQ > SP500 > IWM in tech-driven beats; IWM > SP500 in domestic growth signals. EURUSD follows risk appetite. Gold falls as safe haven demand recedes.",
|
|
"graph_json": {
|
|
"nodes": [
|
|
_n("growth_trigger", "Growth Signal", "input", 350, 50, unit="%"),
|
|
_n("earnings_revision", "Earnings Revision", "latent", 180, 200, formula="growth_trigger * {{coef_trig_earn}}"),
|
|
_n("econ_activity", "Economic Activity", "observable", 520, 200, formula="growth_trigger * {{coef_trig_activity}}"),
|
|
_n("risk_appetite", "Risk Appetite", "latent", 350, 360, formula="earnings_revision * {{coef_earn_risk}} + econ_activity * {{coef_act_risk}}"),
|
|
_n("us_10y", "Delta US 10Y", "observable", 100, 360, formula="econ_activity * {{coef_act_10y}}", unit="%"),
|
|
# ── Row 1 outputs ─────────────────────────────────────────────────
|
|
_n("sp500", "S&P 500", "market_asset", 60, 540, formula="earnings_revision * {{coef_earn_sp}} + risk_appetite * {{coef_risk_sp}} - us_10y * {{coef_10y_sp}}", unit="pts", instrument="SP500"),
|
|
_n("qqq", "QQQ Nasdaq", "market_asset", 250, 540, formula="earnings_revision * {{coef_earn_qqq}} + risk_appetite * {{coef_risk_qqq}} - us_10y * {{coef_10y_qqq}}", unit="pts", instrument="QQQ"),
|
|
_n("iwm", "IWM Russell", "market_asset", 440, 540, formula="econ_activity * {{coef_act_iwm}} + risk_appetite * {{coef_risk_iwm}}", unit="pts", instrument="IWM"),
|
|
# ── Row 2 outputs ─────────────────────────────────────────────────
|
|
_n("eurusd", "EUR/USD", "market_asset", 60, 710, formula="risk_appetite * {{coef_risk_eurusd}}", unit="pips", instrument="EURUSD"),
|
|
_n("xauusd", "Gold XAU/USD", "market_asset", 280, 710, formula="-risk_appetite * {{coef_risk_gold}}", unit="$/oz", instrument="XAUUSD"),
|
|
_n("hyg", "HYG HY Bond", "market_asset", 500, 710, formula="risk_appetite * {{coef_risk_hyg}}", unit="pts", instrument="HYG"),
|
|
],
|
|
"edges": [
|
|
_e("growth_trigger", "earnings_revision","solid", "eps_channel", strength=3, sign="positive"),
|
|
_e("growth_trigger", "econ_activity", "dashed", "activity_proxy",strength=2, sign="positive"),
|
|
_e("growth_trigger", "us_10y", "dashed", "growth_premium",strength=1, sign="positive"),
|
|
_e("earnings_revision", "risk_appetite", "solid", "confidence", strength=3, sign="positive"),
|
|
_e("econ_activity", "risk_appetite", "solid", "sentiment", strength=2, sign="positive"),
|
|
_e("earnings_revision", "sp500", "solid", "eps_sp", strength=3, sign="positive"),
|
|
_e("risk_appetite", "sp500", "solid", "risk_sp", strength=2, sign="positive"),
|
|
_e("us_10y", "sp500", "dashed", "discount_sp", strength=1, sign="negative"),
|
|
_e("earnings_revision", "qqq", "solid", "eps_tech", strength=3, sign="positive", label="tech earnings beta"),
|
|
_e("risk_appetite", "qqq", "solid", "risk_qqq", strength=2, sign="positive"),
|
|
_e("us_10y", "qqq", "dashed", "duration_qqq",strength=2, sign="negative"),
|
|
_e("econ_activity", "iwm", "solid", "domestic_act",strength=3, sign="positive", label="domestic growth"),
|
|
_e("risk_appetite", "iwm", "solid", "risk_iwm", strength=2, sign="positive"),
|
|
_e("risk_appetite", "eurusd","solid", "risk_on_fx", strength=2, sign="positive"),
|
|
_e("risk_appetite", "xauusd","dashed", "safe_exit", strength=2, sign="negative"),
|
|
_e("risk_appetite", "hyg", "solid", "spread_compress",strength=3, sign="positive", label="spreads compress"),
|
|
],
|
|
"coefficients": {
|
|
"coef_trig_earn": _c(0.50, "Growth signal -> earnings revision"),
|
|
"coef_trig_activity":_c(0.40,"Growth signal -> activity proxy"),
|
|
"coef_act_10y": _c(0.05, "Activity -> 10Y growth premium"),
|
|
"coef_earn_risk": _c(0.60, "Earnings -> risk appetite"),
|
|
"coef_act_risk": _c(0.40, "Activity -> risk appetite"),
|
|
"coef_earn_sp": _c(80, "Earnings -> SP500 pts"),
|
|
"coef_risk_sp": _c(40, "Risk appetite -> SP500 pts"),
|
|
"coef_10y_sp": _c(30, "10Y -> SP500 pts fall"),
|
|
"coef_earn_qqq": _c(110, "Earnings -> QQQ pts (higher beta)"),
|
|
"coef_risk_qqq": _c(55, "Risk appetite -> QQQ pts"),
|
|
"coef_10y_qqq": _c(50, "10Y -> QQQ pts fall"),
|
|
"coef_act_iwm": _c(60, "Activity -> IWM pts (domestic)"),
|
|
"coef_risk_iwm": _c(35, "Risk appetite -> IWM pts"),
|
|
"coef_risk_eurusd": _c(30, "Risk appetite -> EURUSD pips"),
|
|
"coef_risk_gold": _c(15, "Risk appetite up -> Gold falls"),
|
|
"coef_risk_hyg": _c(0.50, "Risk appetite -> HYG pts (spread compress)"),
|
|
},
|
|
"instruments": ["SP500", "QQQ", "IWM", "EURUSD", "XAUUSD", "HYG"],
|
|
"input_mapping": {
|
|
"growth_trigger": {"source": "user_input", "unit": "%", "range": [-5, 5]}
|
|
},
|
|
},
|
|
},
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# 4. GEOPOLITICAL_RISK_OFF
|
|
# Military conflicts, sanctions, coups, election shocks
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
{
|
|
"name": "Geopolitical Risk-Off",
|
|
"slug": "GEOPOLITICAL_RISK_OFF",
|
|
"category": "geopolitical",
|
|
"heuristic_ver": 2,
|
|
"calibration_json": {"lag_days": 0, "half_life_days": 7, "absorption_days": 21, "decay_type": "linear"},
|
|
"instruments": ["EURUSD", "XAUUSD", "SP500", "BRENT", "USDJPY", "VXX", "TLT", "HYG", "UNG", "SLV"],
|
|
"description": "Geopolitical shock -> risk premium + safe haven demand -> multi-asset risk-off repricing",
|
|
"ai_rationale": "Gold/TLT/JPY safe havens rise; EUR/SP500/HYG fall. VXX spikes. USDJPY falls (JPY strengthens as safe haven). BRENT and UNG spike if energy-producing region. EU equities most exposed to European conflicts.",
|
|
"graph_json": {
|
|
"nodes": [
|
|
_n("geo_shock", "Geopolitical Shock", "input", 350, 50, unit="score"),
|
|
_n("risk_premium", "Risk Premium", "latent", 160, 200, formula="geo_shock * {{coef_shock_rp}}"),
|
|
_n("safe_haven_dem", "Safe Haven Demand", "latent", 520, 200, formula="geo_shock * {{coef_shock_shd}}"),
|
|
_n("oil_supply_risk", "Oil/Gas Supply Risk", "latent", 820, 200, formula="geo_shock * {{coef_shock_oil}}"),
|
|
_n("vix_spike", "Delta VIX", "observable", 160, 360, formula="risk_premium * {{coef_rp_vix}}", unit="pts"),
|
|
_n("usd_safe_haven", "USD Safe Haven", "observable", 440, 360, formula="safe_haven_dem * {{coef_shd_usd}}"),
|
|
_n("gold_demand", "Gold Safe Haven", "observable", 700, 360, formula="safe_haven_dem * {{coef_shd_gold}}"),
|
|
# ── Row 1 outputs ─────────────────────────────────────────────────
|
|
_n("eurusd", "EUR/USD", "market_asset", 60, 550, formula="-usd_safe_haven * {{coef_usd_eurusd}} - risk_premium * {{coef_rp_eurusd}}", unit="pips", instrument="EURUSD"),
|
|
_n("sp500", "S&P 500", "market_asset", 280, 550, formula="-risk_premium * {{coef_rp_sp}} - vix_spike * {{coef_vix_sp}}", unit="pts", instrument="SP500"),
|
|
_n("xauusd", "Gold XAU/USD", "market_asset", 500, 550, formula="gold_demand * {{coef_gold_xau}}", unit="$/oz", instrument="XAUUSD"),
|
|
_n("brent", "Brent Crude", "market_asset", 720, 550, formula="oil_supply_risk * {{coef_oilrisk_brent}}", unit="$/bbl", instrument="BRENT"),
|
|
# ── Row 2 outputs ─────────────────────────────────────────────────
|
|
_n("usdjpy", "USD/JPY", "market_asset", 60, 730, formula="-safe_haven_dem * {{coef_shd_usdjpy}}", unit="pips", instrument="USDJPY"),
|
|
_n("vxx", "VXX Vol ETN", "market_asset", 240, 730, formula="vix_spike * {{coef_vix_vxx}}", unit="pts", instrument="VXX"),
|
|
_n("tlt", "TLT 20Y Bond", "market_asset", 420, 730, formula="safe_haven_dem * {{coef_shd_tlt}}", unit="pts", instrument="TLT"),
|
|
_n("hyg", "HYG HY Bond", "market_asset", 600, 730, formula="-risk_premium * {{coef_rp_hyg}}", unit="pts", instrument="HYG"),
|
|
_n("ung", "UNG Nat Gas", "market_asset", 780, 730, formula="oil_supply_risk * {{coef_oilrisk_ung}}", unit="pts", instrument="UNG"),
|
|
_n("slv", "SLV Silver", "market_asset", 960, 730, formula="safe_haven_dem * {{coef_shd_slv}}", unit="pts", instrument="SLV"),
|
|
],
|
|
"edges": [
|
|
_e("geo_shock", "risk_premium", "solid", "uncertainty", strength=3, sign="positive"),
|
|
_e("geo_shock", "safe_haven_dem", "solid", "flight_quality",strength=3, sign="positive"),
|
|
_e("geo_shock", "oil_supply_risk","dashed", "supply_threat", strength=2, sign="positive"),
|
|
_e("risk_premium", "vix_spike", "solid", "fear_gauge", strength=3, sign="positive"),
|
|
_e("safe_haven_dem", "usd_safe_haven", "solid", "usd_flight", strength=2, sign="positive"),
|
|
_e("safe_haven_dem", "gold_demand", "solid", "gold_flight", strength=3, sign="positive"),
|
|
# row 1
|
|
_e("usd_safe_haven", "eurusd", "solid", "usd_apprec", strength=3, sign="negative"),
|
|
_e("risk_premium", "eurusd", "dashed", "eu_exposure", strength=2, sign="negative"),
|
|
_e("risk_premium", "sp500", "solid", "risk_reprice", strength=3, sign="negative"),
|
|
_e("vix_spike", "sp500", "solid", "vol_crush_eq", strength=2, sign="negative"),
|
|
_e("gold_demand", "xauusd", "solid", "gold_move", strength=3, sign="positive"),
|
|
_e("oil_supply_risk","brent", "solid", "supply_brent", strength=3, sign="positive"),
|
|
# row 2
|
|
_e("safe_haven_dem", "usdjpy", "solid", "jpy_safe_haven",strength=3, sign="negative", label="JPY safe haven -> USDJPY falls"),
|
|
_e("vix_spike", "vxx", "solid", "vix_vxx", strength=3, sign="positive", label="VIX up -> VXX up"),
|
|
_e("safe_haven_dem", "tlt", "solid", "tsy_flight", strength=3, sign="positive", label="flight to treasuries"),
|
|
_e("risk_premium", "hyg", "solid", "spread_widen", strength=2, sign="negative", label="spreads widen"),
|
|
_e("oil_supply_risk","ung", "solid", "gas_supply", strength=2, sign="positive", label="EU energy supply"),
|
|
_e("safe_haven_dem", "slv", "dashed", "silver_partial",strength=1, sign="positive"),
|
|
],
|
|
"coefficients": {
|
|
"coef_shock_rp": _c(0.30, "Geo shock -> risk premium"),
|
|
"coef_shock_shd": _c(0.40, "Geo shock -> safe haven demand"),
|
|
"coef_shock_oil": _c(0.20, "Geo shock -> oil/gas supply risk"),
|
|
"coef_rp_vix": _c(2.0, "Risk premium -> Delta VIX pts"),
|
|
"coef_shd_usd": _c(0.50, "Safe haven -> USD flow"),
|
|
"coef_shd_gold": _c(0.80, "Safe haven -> gold demand"),
|
|
"coef_usd_eurusd": _c(60, "USD safe haven -> EURUSD pips fall"),
|
|
"coef_rp_eurusd": _c(40, "Risk premium -> EURUSD fall"),
|
|
"coef_rp_sp": _c(80, "Risk premium -> SP500 pts fall"),
|
|
"coef_vix_sp": _c(30, "VIX spike -> SP500 pts fall"),
|
|
"coef_gold_xau": _c(25, "Gold demand -> XAU/USD rise"),
|
|
"coef_oilrisk_brent": _c(5.0, "Oil risk -> Brent $/bbl"),
|
|
"coef_shd_usdjpy": _c(80, "Safe haven -> USDJPY pips fall (JPY up)"),
|
|
"coef_vix_vxx": _c(2.5, "1pt VIX -> VXX pts (futures basis)"),
|
|
"coef_shd_tlt": _c(3.0, "Safe haven -> TLT pts up"),
|
|
"coef_rp_hyg": _c(0.80, "Risk premium -> HYG pts fall"),
|
|
"coef_oilrisk_ung": _c(3.0, "Gas supply risk -> UNG pts"),
|
|
"coef_shd_slv": _c(0.30, "Safe haven -> SLV pts (partial)"),
|
|
},
|
|
"instruments": ["EURUSD", "XAUUSD", "SP500", "BRENT", "USDJPY", "VXX", "TLT", "HYG", "UNG", "SLV"],
|
|
"input_mapping": {
|
|
"geo_shock": {"source": "impact_score_scaled", "unit": "score", "range": [1, 10]}
|
|
},
|
|
},
|
|
},
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# 5. COMMODITY_SUPPLY_SHOCK
|
|
# OPEC, sanctions on producers, pipeline disruptions, WASDE
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
{
|
|
"name": "Commodity Supply Shock",
|
|
"slug": "COMMODITY_SUPPLY_SHOCK",
|
|
"category": "commodity",
|
|
"heuristic_ver": 2,
|
|
"calibration_json": {"lag_days": 1, "half_life_days": 5, "absorption_days": 14, "decay_type": "exp"},
|
|
"instruments": ["BRENT", "XAUUSD", "EURUSD", "SP500", "UNG", "XLE", "SLV"],
|
|
"description": "Supply disruption/glut -> Brent + energy inflation -> EU trade deficit -> multi-asset",
|
|
"ai_rationale": "OPEC cuts hit BRENT and XLE (energy sector stocks) first. Energy inflation pressures EU trade balance (EURUSD down) and inflates Gold/SLV. UNG follows as cross-commodity supply chain linkage. S&P suffers from margin pressure.",
|
|
"graph_json": {
|
|
"nodes": [
|
|
_n("supply_shock", "Supply Shock", "input", 350, 50, unit="mbpd"),
|
|
_n("supply_deficit", "Supply Deficit", "observable", 160, 200, formula="supply_shock * {{coef_shock_deficit}}"),
|
|
_n("energy_inflation","Energy Inflation", "latent", 600, 200, formula="supply_shock * {{coef_shock_einfl}}"),
|
|
_n("eu_import_cost", "EU Import Cost", "latent", 440, 370, formula="energy_inflation * {{coef_einfl_import}}"),
|
|
_n("infl_breakeven", "Inflation Expect", "latent", 780, 370, formula="energy_inflation * {{coef_einfl_breakeven}}"),
|
|
# ── Row 1 outputs ─────────────────────────────────────────────────
|
|
_n("brent", "Brent Crude", "market_asset", 60, 560, formula="supply_deficit * {{coef_deficit_brent}}", unit="$/bbl", instrument="BRENT"),
|
|
_n("eurusd", "EUR/USD", "market_asset", 260, 560, formula="-eu_import_cost * {{coef_import_eurusd}}", unit="pips", instrument="EURUSD"),
|
|
_n("sp500", "S&P 500", "market_asset", 460, 560, formula="-eu_import_cost * {{coef_import_sp}} - infl_breakeven * {{coef_infl_sp}}", unit="pts", instrument="SP500"),
|
|
_n("xauusd", "Gold XAU/USD", "market_asset", 660, 560, formula="infl_breakeven * {{coef_infl_gold}}", unit="$/oz", instrument="XAUUSD"),
|
|
# ── Row 2 outputs ─────────────────────────────────────────────────
|
|
_n("xle", "XLE Energy", "market_asset", 60, 740, formula="supply_deficit * {{coef_deficit_xle}}", unit="pts", instrument="XLE"),
|
|
_n("ung", "UNG Nat Gas", "market_asset", 260, 740, formula="energy_inflation * {{coef_einfl_ung}}", unit="pts", instrument="UNG"),
|
|
_n("slv", "SLV Silver", "market_asset", 460, 740, formula="infl_breakeven * {{coef_infl_slv}}", unit="pts", instrument="SLV"),
|
|
],
|
|
"edges": [
|
|
_e("supply_shock", "supply_deficit", "solid", "production_gap", strength=3, sign="positive"),
|
|
_e("supply_shock", "energy_inflation", "dashed", "cost_push", strength=2, sign="positive"),
|
|
_e("supply_deficit", "brent", "solid", "spot_premium", strength=3, sign="positive"),
|
|
_e("energy_inflation","eu_import_cost", "solid", "trade_balance", strength=2, sign="positive"),
|
|
_e("energy_inflation","infl_breakeven", "solid", "breakeven_reprice",strength=2, sign="positive"),
|
|
_e("eu_import_cost", "eurusd", "solid", "terms_of_trade", strength=2, sign="negative"),
|
|
_e("eu_import_cost", "sp500", "dashed", "margin_squeeze", strength=2, sign="negative"),
|
|
_e("infl_breakeven", "sp500", "solid", "multiple_compress",strength=2, sign="negative"),
|
|
_e("infl_breakeven", "xauusd", "solid", "infl_hedge", strength=3, sign="positive"),
|
|
_e("supply_deficit", "xle", "solid", "energy_stocks", strength=3, sign="positive", label="oil price -> energy stocks"),
|
|
_e("energy_inflation","ung", "dashed", "energy_chain", strength=2, sign="positive", label="cross-commodity link"),
|
|
_e("infl_breakeven", "slv", "solid", "industrial_infl", strength=2, sign="positive", label="silver: industrial + inflation"),
|
|
],
|
|
"coefficients": {
|
|
"coef_shock_deficit": _c(1.0, "Supply shock mb/d -> deficit"),
|
|
"coef_shock_einfl": _c(0.10, "Supply shock -> energy inflation"),
|
|
"coef_deficit_brent": _c(3.0, "1mb/d deficit -> Brent $/bbl"),
|
|
"coef_einfl_import": _c(0.60, "Energy inflation -> EU import cost"),
|
|
"coef_einfl_breakeven": _c(0.40, "Energy inflation -> breakeven"),
|
|
"coef_import_eurusd": _c(50, "EU import cost -> EURUSD pips fall"),
|
|
"coef_import_sp": _c(40, "Import cost -> SP500 pts fall"),
|
|
"coef_infl_sp": _c(30, "Inflation exp -> SP500 pts fall"),
|
|
"coef_infl_gold": _c(20, "Inflation exp -> Gold $/oz"),
|
|
"coef_deficit_xle": _c(1.5, "1mb/d deficit -> XLE pts"),
|
|
"coef_einfl_ung": _c(1.0, "Energy inflation -> UNG pts"),
|
|
"coef_infl_slv": _c(5, "Inflation exp -> SLV pts"),
|
|
},
|
|
"instruments": ["BRENT", "XAUUSD", "EURUSD", "SP500", "UNG", "XLE", "SLV"],
|
|
"input_mapping": {
|
|
"supply_shock": {"source": "user_input", "unit": "mbpd", "range": [-3, 3]}
|
|
},
|
|
},
|
|
},
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# 6. TRADE_POLICY_SHOCK
|
|
# Tariffs, protectionism, export bans
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
{
|
|
"name": "Trade Policy Shock",
|
|
"slug": "TRADE_POLICY_SHOCK",
|
|
"category": "trade_policy",
|
|
"heuristic_ver": 2,
|
|
"calibration_json": {"lag_days": 1, "half_life_days": 10, "absorption_days": 21, "decay_type": "linear"},
|
|
"instruments": ["EURUSD", "SP500", "XAUUSD", "EEM", "QQQ", "GBPUSD", "IWM"],
|
|
"description": "Tariff announcement -> stagflationary shock -> EUR/GBP down SP500/QQQ down EEM down Gold up",
|
|
"ai_rationale": "Tariffs create stagflation: import cost rise (inflation) + trade volume drop (growth). EEM is hit hardest via global trade contraction and USD strength. QQQ suffers from tech supply chain disruption (AAPL/NVDA exposed). GBPUSD exposed as UK trade-dependent. IWM domestic plays can benefit initially but input cost inflation hurts margins.",
|
|
"graph_json": {
|
|
"nodes": [
|
|
_n("tariff_shock", "Trade Restriction", "input", 350, 50, unit="%"),
|
|
_n("import_cost_rise", "Import Cost Rise", "observable", 160, 200, formula="tariff_shock * {{coef_tariff_cost}}"),
|
|
_n("trade_volume_drop","Trade Volume Drop", "observable", 600, 200, formula="-tariff_shock * {{coef_tariff_vol}}"),
|
|
_n("policy_uncertainty","Policy Uncertainty", "latent", 350, 200, formula="tariff_shock * {{coef_tariff_unc}}"),
|
|
_n("stagflation_risk", "Stagflation Risk", "latent", 350, 380, formula="import_cost_rise * {{coef_cost_stag}} - trade_volume_drop * {{coef_vol_stag}}"),
|
|
_n("corp_margin", "Corp Margin Pressure", "latent", 650, 380, formula="import_cost_rise * {{coef_cost_margin}}"),
|
|
# ── Row 1 outputs ─────────────────────────────────────────────────
|
|
_n("eurusd", "EUR/USD", "market_asset", 60, 570, formula="-stagflation_risk * {{coef_stag_eurusd}} - trade_volume_drop * {{coef_vol_eurusd}}", unit="pips", instrument="EURUSD"),
|
|
_n("sp500", "S&P 500", "market_asset", 300, 570, formula="-corp_margin * {{coef_margin_sp}} - policy_uncertainty * {{coef_unc_sp}}", unit="pts", instrument="SP500"),
|
|
_n("xauusd", "Gold XAU/USD", "market_asset", 540, 570, formula="stagflation_risk * {{coef_stag_gold}} + policy_uncertainty * {{coef_unc_gold}}", unit="$/oz", instrument="XAUUSD"),
|
|
# ── Row 2 outputs ─────────────────────────────────────────────────
|
|
_n("eem", "EEM Emerg Mkt", "market_asset", 60, 750, formula="-trade_volume_drop * {{coef_vol_eem}} - stagflation_risk * {{coef_stag_eem}}", unit="pts", instrument="EEM"),
|
|
_n("qqq", "QQQ Nasdaq", "market_asset", 260, 750, formula="-corp_margin * {{coef_margin_qqq}} - policy_uncertainty * {{coef_unc_qqq}}", unit="pts", instrument="QQQ"),
|
|
_n("gbpusd", "GBP/USD", "market_asset", 460, 750, formula="-stagflation_risk * {{coef_stag_gbpusd}} - trade_volume_drop * {{coef_vol_gbpusd}}", unit="pips", instrument="GBPUSD"),
|
|
_n("iwm", "IWM Russell", "market_asset", 660, 750, formula="-policy_uncertainty * {{coef_unc_iwm}}", unit="pts", instrument="IWM"),
|
|
],
|
|
"edges": [
|
|
_e("tariff_shock", "import_cost_rise", "solid", "price_passthru", strength=3, sign="positive"),
|
|
_e("tariff_shock", "trade_volume_drop", "solid", "trade_contract", strength=3, sign="negative"),
|
|
_e("tariff_shock", "policy_uncertainty", "solid", "capex_freeze", strength=3, sign="positive"),
|
|
_e("import_cost_rise", "stagflation_risk", "solid", "cost_stag", strength=2, sign="positive"),
|
|
_e("trade_volume_drop", "stagflation_risk", "solid", "growth_stag", strength=2, sign="negative"),
|
|
_e("import_cost_rise", "corp_margin", "solid", "supply_chain", strength=3, sign="positive"),
|
|
_e("stagflation_risk", "eurusd", "solid", "eu_stag", strength=2, sign="negative"),
|
|
_e("trade_volume_drop", "eurusd", "dashed", "export_shock", strength=2, sign="negative"),
|
|
_e("corp_margin", "sp500", "solid", "eps_compress", strength=3, sign="negative"),
|
|
_e("policy_uncertainty","sp500", "solid", "capex_sentiment",strength=2, sign="negative"),
|
|
_e("stagflation_risk", "xauusd", "solid", "stag_hedge", strength=3, sign="positive"),
|
|
_e("policy_uncertainty","xauusd", "dashed", "unc_gold", strength=2, sign="positive"),
|
|
_e("trade_volume_drop", "eem", "solid", "global_trade_eem",strength=3, sign="negative", label="EM most exposed"),
|
|
_e("stagflation_risk", "eem", "dashed", "usd_eem", strength=2, sign="negative"),
|
|
_e("corp_margin", "qqq", "solid", "tech_supply_ch", strength=3, sign="negative", label="AAPL/NVDA supply chain"),
|
|
_e("policy_uncertainty","qqq", "dashed", "tech_capex", strength=2, sign="negative"),
|
|
_e("stagflation_risk", "gbpusd", "solid", "uk_trade", strength=2, sign="negative"),
|
|
_e("trade_volume_drop", "gbpusd", "dashed", "uk_exports", strength=2, sign="negative"),
|
|
_e("policy_uncertainty","iwm", "dashed", "domestic_capex", strength=2, sign="negative"),
|
|
],
|
|
"coefficients": {
|
|
"coef_tariff_cost": _c(0.30, "10% tariff -> ~3% import cost"),
|
|
"coef_tariff_vol": _c(0.50, "Tariff -> trade volume drop"),
|
|
"coef_tariff_unc": _c(0.40, "Tariff -> policy uncertainty"),
|
|
"coef_cost_stag": _c(0.50, "Cost -> stagflation index"),
|
|
"coef_vol_stag": _c(0.50, "Vol drop -> growth component"),
|
|
"coef_cost_margin": _c(0.60, "Cost -> margin pressure"),
|
|
"coef_stag_eurusd": _c(60, "Stagflation -> EURUSD pips fall"),
|
|
"coef_vol_eurusd": _c(30, "Trade drop -> EURUSD pips fall"),
|
|
"coef_margin_sp": _c(60, "Margin -> SP500 pts fall"),
|
|
"coef_unc_sp": _c(40, "Uncertainty -> SP500 pts fall"),
|
|
"coef_stag_gold": _c(20, "Stagflation -> Gold $/oz"),
|
|
"coef_unc_gold": _c(15, "Uncertainty -> Gold $/oz"),
|
|
"coef_vol_eem": _c(25, "Trade drop -> EEM pts fall"),
|
|
"coef_stag_eem": _c(20, "Stagflation -> EEM pts fall (USD up)"),
|
|
"coef_margin_qqq": _c(80, "Margin -> QQQ pts fall"),
|
|
"coef_unc_qqq": _c(50, "Uncertainty -> QQQ pts fall"),
|
|
"coef_stag_gbpusd": _c(40, "Stagflation -> GBPUSD pips fall"),
|
|
"coef_vol_gbpusd": _c(30, "Trade drop -> GBPUSD pips fall"),
|
|
"coef_unc_iwm": _c(30, "Uncertainty -> IWM pts fall"),
|
|
},
|
|
"instruments": ["EURUSD", "SP500", "XAUUSD", "EEM", "QQQ", "GBPUSD", "IWM"],
|
|
"input_mapping": {
|
|
"tariff_shock": {"source": "user_input", "unit": "%", "range": [0, 50]}
|
|
},
|
|
},
|
|
},
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# 7. CREDIT_SYSTEMIC_EVENT
|
|
# Bank failure, sovereign debt crisis, HY blowout, financial contagion
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
{
|
|
"name": "Credit & Systemic Stress",
|
|
"slug": "CREDIT_SYSTEMIC_EVENT",
|
|
"category": "credit_stress",
|
|
"heuristic_ver": 2,
|
|
"calibration_json": {"lag_days": 0, "half_life_days": 7, "absorption_days": 21, "decay_type": "exp"},
|
|
"instruments": ["EURUSD", "SP500", "XAUUSD", "HYG", "TLT", "VXX", "XLF", "USDJPY", "BTC"],
|
|
"description": "Credit event -> spread widening + deleveraging -> systemic risk-off + USD liquidity flight",
|
|
"ai_rationale": "HYG is the primary instrument (spreads widen = HYG down). TLT initially up (flight to quality) then volatile (sold for cash). VXX spikes hard. XLF (financials) crushed. USDJPY falls (JPY safe haven). BTC complex: initially up as alternative store of value, then down on margin calls.",
|
|
"graph_json": {
|
|
"nodes": [
|
|
_n("credit_trigger", "Credit Event", "input", 350, 50, unit="bps"),
|
|
_n("spread_widening", "Spread Widening", "observable", 160, 200, formula="credit_trigger * {{coef_trig_spread}}", unit="bps"),
|
|
_n("counterparty_risk","Counterparty Risk", "latent", 600, 200, formula="credit_trigger * {{coef_trig_cp}}"),
|
|
_n("fin_conditions", "Financial Conditions", "observable", 160, 370, formula="spread_widening * {{coef_spread_fc}}"),
|
|
_n("liq_premium", "Liquidity Premium", "latent", 440, 370, formula="spread_widening * {{coef_spread_liq}} + counterparty_risk * {{coef_cp_liq}}"),
|
|
_n("deleveraging", "Forced Deleveraging", "latent", 750, 370, formula="counterparty_risk * {{coef_cp_delev}}"),
|
|
# ── Row 1 outputs ─────────────────────────────────────────────────
|
|
_n("eurusd", "EUR/USD", "market_asset", 60, 570, formula="-liq_premium * {{coef_liq_eurusd}} - fin_conditions * {{coef_fc_eurusd}}", unit="pips", instrument="EURUSD"),
|
|
_n("sp500", "S&P 500", "market_asset", 300, 570, formula="-fin_conditions * {{coef_fc_sp}} - deleveraging * {{coef_delev_sp}}", unit="pts", instrument="SP500"),
|
|
_n("xauusd", "Gold XAU/USD", "market_asset", 540, 570, formula="liq_premium * {{coef_liq_gold}} - deleveraging * {{coef_delev_gold}}", unit="$/oz", instrument="XAUUSD"),
|
|
# ── Row 2 outputs ─────────────────────────────────────────────────
|
|
_n("hyg", "HYG HY Bond", "market_asset", 60, 760, formula="-spread_widening * {{coef_spread_hyg}} - deleveraging * {{coef_delev_hyg}}", unit="pts", instrument="HYG"),
|
|
_n("tlt", "TLT 20Y Bond", "market_asset", 240, 760, formula="liq_premium * {{coef_liq_tlt}} - deleveraging * {{coef_delev_tlt}}", unit="pts", instrument="TLT"),
|
|
_n("vxx", "VXX Vol ETN", "market_asset", 420, 760, formula="spread_widening * {{coef_spread_vxx}}", unit="pts", instrument="VXX"),
|
|
_n("xlf", "XLF Financials","market_asset", 600, 760, formula="-spread_widening * {{coef_spread_xlf}} - fin_conditions * {{coef_fc_xlf}}", unit="pts", instrument="XLF"),
|
|
_n("usdjpy", "USD/JPY", "market_asset", 780, 760, formula="-liq_premium * {{coef_liq_usdjpy}}", unit="pips", instrument="USDJPY"),
|
|
_n("btc", "BTC-USD", "market_asset", 960, 760, formula="liq_premium * {{coef_liq_btc}} - deleveraging * {{coef_delev_btc}}", unit="pts", instrument="BTC"),
|
|
],
|
|
"edges": [
|
|
_e("credit_trigger", "spread_widening", "solid", "contagion", strength=3, sign="positive"),
|
|
_e("credit_trigger", "counterparty_risk","solid", "systemic_fear", strength=3, sign="positive"),
|
|
_e("spread_widening", "fin_conditions", "solid", "tightening", strength=3, sign="positive"),
|
|
_e("spread_widening", "liq_premium", "solid", "cash_demand", strength=2, sign="positive"),
|
|
_e("counterparty_risk","liq_premium", "solid", "cash_hoarding", strength=3, sign="positive"),
|
|
_e("counterparty_risk","deleveraging", "solid", "margin_calls", strength=3, sign="positive"),
|
|
_e("liq_premium", "eurusd", "solid", "usd_flight", strength=3, sign="negative"),
|
|
_e("fin_conditions", "eurusd", "dashed", "fin_cond_fx", strength=2, sign="negative"),
|
|
_e("fin_conditions", "sp500", "solid", "valuation", strength=3, sign="negative"),
|
|
_e("deleveraging", "sp500", "solid", "forced_sell", strength=3, sign="negative"),
|
|
_e("liq_premium", "xauusd", "dashed", "safe_initial", strength=2, sign="positive"),
|
|
_e("deleveraging", "xauusd", "dashed", "sold_margin", strength=2, sign="negative"),
|
|
_e("spread_widening", "hyg", "solid", "direct_spread", strength=3, sign="negative", label="HY spreads -> HYG falls"),
|
|
_e("deleveraging", "hyg", "solid", "forced_selling_hy",strength=2, sign="negative"),
|
|
_e("liq_premium", "tlt", "solid", "tsy_safe", strength=3, sign="positive", label="flight to treasuries"),
|
|
_e("deleveraging", "tlt", "dashed", "sold_tsy", strength=2, sign="negative", label="sold for cash"),
|
|
_e("spread_widening", "vxx", "solid", "vol_spike", strength=3, sign="positive"),
|
|
_e("spread_widening", "xlf", "solid", "bank_stress", strength=3, sign="negative", label="banks = credit exposure"),
|
|
_e("fin_conditions", "xlf", "solid", "npa_risk", strength=2, sign="negative"),
|
|
_e("liq_premium", "usdjpy", "solid", "jpy_safe", strength=2, sign="negative", label="JPY safe haven"),
|
|
_e("liq_premium", "btc", "dashed", "alt_store", strength=2, sign="positive", label="alt store of value (initial)"),
|
|
_e("deleveraging", "btc", "dashed", "btc_margin", strength=2, sign="negative", label="margin call selling"),
|
|
],
|
|
"coefficients": {
|
|
"coef_trig_spread": _c(1.0, "Credit event -> spread widening"),
|
|
"coef_trig_cp": _c(0.60, "Credit event -> counterparty risk"),
|
|
"coef_spread_fc": _c(0.50, "Spread -> financial conditions"),
|
|
"coef_spread_liq": _c(0.40, "Spread -> liquidity premium"),
|
|
"coef_cp_liq": _c(0.60, "Counterparty -> liquidity premium"),
|
|
"coef_cp_delev": _c(0.50, "Counterparty -> deleveraging"),
|
|
"coef_liq_eurusd": _c(80, "Liquidity -> EURUSD pips fall"),
|
|
"coef_fc_eurusd": _c(40, "Fin cond -> EURUSD fall"),
|
|
"coef_fc_sp": _c(100, "Fin cond -> SP500 pts fall"),
|
|
"coef_delev_sp": _c(80, "Deleveraging -> SP500 pts fall"),
|
|
"coef_liq_gold": _c(10, "Liquidity -> Gold rise (initial)"),
|
|
"coef_delev_gold": _c(15, "Deleveraging -> Gold fall (sold)"),
|
|
"coef_spread_hyg": _c(0.05, "1bps spread -> HYG pts fall"),
|
|
"coef_delev_hyg": _c(0.30, "Deleveraging -> HYG pts fall"),
|
|
"coef_liq_tlt": _c(3.0, "Liquidity -> TLT pts up (initial)"),
|
|
"coef_delev_tlt": _c(1.5, "Deleveraging -> TLT pts fall"),
|
|
"coef_spread_vxx": _c(0.05, "1bps spread -> VXX pts up"),
|
|
"coef_spread_xlf": _c(0.04, "1bps spread -> XLF pts fall"),
|
|
"coef_fc_xlf": _c(0.60, "Fin cond -> XLF pts fall"),
|
|
"coef_liq_usdjpy": _c(60, "Liquidity -> USDJPY pips fall (JPY up)"),
|
|
"coef_liq_btc": _c(200, "Liquidity -> BTC pts up (alt store)"),
|
|
"coef_delev_btc": _c(400, "Deleveraging -> BTC pts fall (margin calls)"),
|
|
},
|
|
"instruments": ["EURUSD", "SP500", "XAUUSD", "HYG", "TLT", "VXX", "XLF", "USDJPY", "BTC"],
|
|
"input_mapping": {
|
|
"credit_trigger": {"source": "user_input", "unit": "bps", "range": [50, 500]}
|
|
},
|
|
},
|
|
},
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# 8. TECHNICAL_MOMENTUM_BREAKOUT
|
|
# MA crosses, RSI extremes, key level breaks, 52w H/L
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
{
|
|
"name": "Technical Momentum Breakout",
|
|
"slug": "TECHNICAL_MOMENTUM_BREAKOUT",
|
|
"category": "technical",
|
|
"heuristic_ver": 2,
|
|
"calibration_json": {"lag_days": 0, "half_life_days": 3, "absorption_days": 7, "decay_type": "exp"},
|
|
"instruments": ["EURUSD", "SP500", "QQQ"],
|
|
"description": "Technical breakout/breakdown -> stop cascade + momentum -> instrument price action",
|
|
"ai_rationale": "Technical events operate via microstructure: breakout triggers stops, amplifying the move. No fundamental anchor. The correlated asset move is weaker. Applies to any instrument — the primary and correlated move fields should be overridden per event.",
|
|
"graph_json": {
|
|
"nodes": [
|
|
_n("tech_signal", "Technical Signal", "input", 300, 50, unit="sigma"),
|
|
_n("momentum_score", "Momentum Score", "observable", 160, 220, formula="tech_signal * {{coef_sig_mom}}"),
|
|
_n("stop_cascade", "Stop Order Cascade","latent", 500, 220, formula="tech_signal * {{coef_sig_stop}}"),
|
|
_n("trend_strength", "Trend Strength", "latent", 300, 400, formula="momentum_score * {{coef_mom_trend}} + stop_cascade * {{coef_stop_trend}}"),
|
|
_n("eurusd", "Primary Instrument", "market_asset", 160, 600, formula="trend_strength * {{coef_trend_inst}} + stop_cascade * {{coef_stop_inst}}", unit="pips", instrument="EURUSD"),
|
|
_n("sp500", "Correlated Asset A", "market_asset", 400, 600, formula="trend_strength * {{coef_trend_corr}}", unit="pts", instrument="SP500"),
|
|
_n("qqq", "Correlated Asset B", "market_asset", 620, 600, formula="trend_strength * {{coef_trend_corr2}}", unit="pts", instrument="QQQ"),
|
|
],
|
|
"edges": [
|
|
_e("tech_signal", "momentum_score", "solid", "signal_strength",strength=3, sign="positive"),
|
|
_e("tech_signal", "stop_cascade", "solid", "stop_trigger", strength=3, sign="positive"),
|
|
_e("momentum_score", "trend_strength", "solid", "trend_confirm", strength=3, sign="positive"),
|
|
_e("stop_cascade", "trend_strength", "solid", "amplification", strength=2, sign="positive"),
|
|
_e("trend_strength", "eurusd", "solid", "price_action", strength=3, sign="positive"),
|
|
_e("stop_cascade", "eurusd", "solid", "liquidity_grab", strength=2, sign="positive"),
|
|
_e("trend_strength", "sp500", "dashed", "spillover_a", strength=2, sign="positive"),
|
|
_e("trend_strength", "qqq", "dashed", "spillover_b", strength=2, sign="positive"),
|
|
],
|
|
"coefficients": {
|
|
"coef_sig_mom": _c(1.0, "Signal -> momentum score"),
|
|
"coef_sig_stop": _c(0.60, "Signal -> stop cascade"),
|
|
"coef_mom_trend": _c(0.70, "Momentum -> trend strength"),
|
|
"coef_stop_trend": _c(0.50, "Stop cascade -> amplification"),
|
|
"coef_trend_inst": _c(50, "Trend -> primary instrument (pips)"),
|
|
"coef_stop_inst": _c(30, "Stop cascade -> extra move (pips)"),
|
|
"coef_trend_corr": _c(20, "Trend -> correlated A (pts)"),
|
|
"coef_trend_corr2":_c(25, "Trend -> correlated B (pts)"),
|
|
},
|
|
"instruments": ["EURUSD", "SP500", "QQQ"],
|
|
"input_mapping": {
|
|
"tech_signal": {"source": "user_input", "unit": "sigma", "range": [-5, 5]}
|
|
},
|
|
},
|
|
},
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# 9. SENTIMENT_POSITIONING_EXTREME
|
|
# VIX extremes, COT crowded, put/call skew extremes — contrarian setup
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
{
|
|
"name": "Sentiment & Positioning Extreme",
|
|
"slug": "SENTIMENT_POSITIONING_EXTREME",
|
|
"category": "sentiment",
|
|
"heuristic_ver": 2,
|
|
"calibration_json": {"lag_days": 1, "half_life_days": 5, "absorption_days": 10, "decay_type": "linear"},
|
|
"instruments": ["SP500", "QQQ", "EURUSD", "XAUUSD", "VXX", "HYG", "BTC"],
|
|
"description": "Extreme fear / greed -> crowded trade unwind -> contrarian mean reversion",
|
|
"ai_rationale": "+sigma = extreme fear/oversold (contrarian buy signal). VXX collapses as fear recedes. HYG recovers as credit spreads compress. BTC follows the risk appetite extreme. Mean reversion typically takes 3-10 days to materialize once the flow exhausts.",
|
|
"graph_json": {
|
|
"nodes": [
|
|
_n("sent_trigger", "Sentiment Extreme", "input", 350, 50, unit="sigma"),
|
|
_n("pos_imbalance", "Positioning Imbalance","observable", 160, 210, formula="sent_trigger * {{coef_sent_pos}}"),
|
|
_n("vol_regime", "Volatility Regime", "observable", 600, 210, formula="-sent_trigger * {{coef_sent_vol}}"),
|
|
_n("crowd_risk", "Crowded Trade Risk", "latent", 160, 380, formula="pos_imbalance * {{coef_pos_crowd}}"),
|
|
_n("mr_force", "Mean Reversion Force","latent", 400, 380, formula="crowd_risk * {{coef_crowd_mr}}"),
|
|
# ── Row 1 outputs ─────────────────────────────────────────────────
|
|
_n("sp500", "S&P 500", "market_asset", 60, 580, formula="mr_force * {{coef_mr_sp}} + vol_regime * {{coef_vol_sp}}", unit="pts", instrument="SP500"),
|
|
_n("qqq", "QQQ Nasdaq", "market_asset", 260, 580, formula="mr_force * {{coef_mr_qqq}} + vol_regime * {{coef_vol_qqq}}", unit="pts", instrument="QQQ"),
|
|
_n("eurusd", "EUR/USD", "market_asset", 460, 580, formula="mr_force * {{coef_mr_eurusd}}", unit="pips", instrument="EURUSD"),
|
|
_n("xauusd", "Gold XAU/USD", "market_asset", 660, 580, formula="-mr_force * {{coef_mr_gold}}", unit="$/oz", instrument="XAUUSD"),
|
|
# ── Row 2 outputs ─────────────────────────────────────────────────
|
|
_n("vxx", "VXX Vol ETN", "market_asset", 60, 760, formula="-mr_force * {{coef_mr_vxx}} - vol_regime * {{coef_vol_vxx}}", unit="pts", instrument="VXX"),
|
|
_n("hyg", "HYG HY Bond", "market_asset", 280, 760, formula="mr_force * {{coef_mr_hyg}}", unit="pts", instrument="HYG"),
|
|
_n("btc", "BTC-USD", "market_asset", 500, 760, formula="mr_force * {{coef_mr_btc}}", unit="pts", instrument="BTC"),
|
|
],
|
|
"edges": [
|
|
_e("sent_trigger", "pos_imbalance","solid", "flow_data", strength=3, sign="positive"),
|
|
_e("sent_trigger", "vol_regime", "dashed", "vol_signal", strength=2, sign="negative"),
|
|
_e("pos_imbalance", "crowd_risk", "solid", "crowd_build", strength=3, sign="positive"),
|
|
_e("crowd_risk", "mr_force", "solid", "squeeze", strength=3, sign="positive"),
|
|
_e("mr_force", "sp500", "solid", "contrarian_sp", strength=3, sign="positive"),
|
|
_e("vol_regime", "sp500", "dashed", "vol_compress", strength=2, sign="positive"),
|
|
_e("mr_force", "qqq", "solid", "contrarian_qqq", strength=3, sign="positive"),
|
|
_e("vol_regime", "qqq", "dashed", "vol_qqq", strength=2, sign="positive"),
|
|
_e("mr_force", "eurusd", "solid", "unwind_fx", strength=2, sign="positive"),
|
|
_e("mr_force", "xauusd", "dashed", "safe_exit", strength=2, sign="negative"),
|
|
_e("mr_force", "vxx", "solid", "vol_crush", strength=3, sign="negative", label="fear recedes -> VXX falls"),
|
|
_e("vol_regime", "vxx", "solid", "vol_collapse", strength=3, sign="negative"),
|
|
_e("mr_force", "hyg", "solid", "spread_recovery",strength=2, sign="positive"),
|
|
_e("mr_force", "btc", "dashed", "btc_recovery", strength=2, sign="positive"),
|
|
],
|
|
"coefficients": {
|
|
"coef_sent_pos": _c(1.0, "Sentiment -> positioning imbalance"),
|
|
"coef_sent_vol": _c(0.50, "Fear extreme -> vol regime"),
|
|
"coef_pos_crowd": _c(0.80, "Positioning -> crowded trade risk"),
|
|
"coef_crowd_mr": _c(0.70, "Crowded trade -> mean reversion force"),
|
|
"coef_mr_sp": _c(60, "MR force -> SP500 pts"),
|
|
"coef_vol_sp": _c(30, "Vol compression -> SP500 pts"),
|
|
"coef_mr_qqq": _c(80, "MR force -> QQQ pts (higher beta)"),
|
|
"coef_vol_qqq": _c(40, "Vol compression -> QQQ pts"),
|
|
"coef_mr_eurusd": _c(40, "MR force -> EURUSD pips"),
|
|
"coef_mr_gold": _c(15, "Fear recedes -> Gold falls"),
|
|
"coef_mr_vxx": _c(3.0, "MR force -> VXX pts fall"),
|
|
"coef_vol_vxx": _c(4.0, "Vol collapse -> VXX pts fall"),
|
|
"coef_mr_hyg": _c(0.40, "MR force -> HYG pts up"),
|
|
"coef_mr_btc": _c(300, "MR force -> BTC pts"),
|
|
},
|
|
"instruments": ["SP500", "QQQ", "EURUSD", "XAUUSD", "VXX", "HYG", "BTC"],
|
|
"input_mapping": {
|
|
"sent_trigger": {"source": "user_input", "unit": "sigma", "range": [-4, 4]}
|
|
},
|
|
},
|
|
},
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# 10. COMMODITY_INVENTORY_REPORT
|
|
# EIA crude weekly, EIA gas storage, API crude, DOE
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
{
|
|
"name": "Commodity Inventory Report",
|
|
"slug": "COMMODITY_INVENTORY_REPORT",
|
|
"category": "commodity",
|
|
"heuristic_ver": 2,
|
|
"calibration_json": {"lag_days": 0, "half_life_days": 2, "absorption_days": 5, "decay_type": "exp"},
|
|
"instruments": ["BRENT", "EURUSD", "XAUUSD", "UNG", "XLE"],
|
|
"description": "Weekly inventory surprise -> supply/demand balance -> Brent + XLE + UNG",
|
|
"ai_rationale": "EIA crude draw vs consensus -> Brent spot up + backwardation. XLE (energy sector ETF) follows Brent with some lag. EIA gas storage report separately drives UNG. Secondary inflation channel hits EURUSD and gold.",
|
|
"graph_json": {
|
|
"nodes": [
|
|
_n("inv_surprise", "Inventory Surprise", "input", 350, 50, unit="mb", description="+mb = draw (bullish), -mb = build"),
|
|
_n("sd_balance", "Supply/Demand Balance","observable", 200, 220, formula="inv_surprise * {{coef_surp_balance}}"),
|
|
_n("price_pressure", "Spot Price Pressure", "latent", 200, 390, formula="sd_balance * {{coef_balance_pressure}}"),
|
|
_n("fwd_curve_shift", "Forward Curve Shift", "observable", 560, 390, formula="sd_balance * {{coef_balance_fwd}}"),
|
|
_n("energy_cost_fwd", "Energy Cost Outlook", "latent", 740, 390, formula="price_pressure * {{coef_pressure_cost}}"),
|
|
# ── Row 1 outputs ─────────────────────────────────────────────────
|
|
_n("brent", "Brent Crude", "market_asset", 60, 580, formula="price_pressure * {{coef_pressure_brent}} + fwd_curve_shift * {{coef_fwd_brent}}", unit="$/bbl", instrument="BRENT"),
|
|
_n("eurusd", "EUR/USD", "market_asset", 310, 580, formula="-energy_cost_fwd * {{coef_energy_eurusd}}", unit="pips", instrument="EURUSD"),
|
|
_n("xauusd", "Gold XAU/USD", "market_asset", 560, 580, formula="energy_cost_fwd * {{coef_energy_gold}}", unit="$/oz", instrument="XAUUSD"),
|
|
# ── Row 2 outputs ─────────────────────────────────────────────────
|
|
_n("xle", "XLE Energy ETF","market_asset", 60, 760, formula="price_pressure * {{coef_pressure_xle}}", unit="pts", instrument="XLE"),
|
|
_n("ung", "UNG Nat Gas", "market_asset", 310, 760, formula="sd_balance * {{coef_balance_ung}}", unit="pts", instrument="UNG"),
|
|
],
|
|
"edges": [
|
|
_e("inv_surprise", "sd_balance", "solid", "tightness", strength=3, sign="positive"),
|
|
_e("sd_balance", "price_pressure", "solid", "spot_impact", strength=3, sign="positive"),
|
|
_e("sd_balance", "fwd_curve_shift","solid", "curve_structure",strength=2, sign="positive"),
|
|
_e("price_pressure", "energy_cost_fwd","dashed", "cost_outlook", strength=2, sign="positive"),
|
|
_e("price_pressure", "brent", "solid", "spot_move", strength=3, sign="positive"),
|
|
_e("fwd_curve_shift","brent", "solid", "backwardation", strength=2, sign="positive"),
|
|
_e("energy_cost_fwd","eurusd", "dashed", "trade_deficit", strength=2, sign="negative"),
|
|
_e("energy_cost_fwd","xauusd", "dashed", "infl_hedge", strength=2, sign="positive"),
|
|
_e("price_pressure", "xle", "solid", "energy_stocks", strength=3, sign="positive", label="oil -> energy sector"),
|
|
_e("sd_balance", "ung", "dashed", "gas_correlation",strength=2, sign="positive", label="EIA gas storage parallel"),
|
|
],
|
|
"coefficients": {
|
|
"coef_surp_balance": _c(1.0, "1mb surprise -> balance signal"),
|
|
"coef_balance_pressure":_c(0.8, "Balance -> spot pressure"),
|
|
"coef_balance_fwd": _c(0.4, "Balance -> forward curve"),
|
|
"coef_pressure_cost": _c(0.3, "Pressure -> energy cost outlook"),
|
|
"coef_pressure_brent": _c(0.5, "1mb -> Brent $/bbl"),
|
|
"coef_fwd_brent": _c(0.2, "Curve shift -> Brent $/bbl"),
|
|
"coef_energy_eurusd": _c(20, "Energy cost -> EURUSD pips fall"),
|
|
"coef_energy_gold": _c(5, "Energy cost -> Gold $/oz"),
|
|
"coef_pressure_xle": _c(0.4, "Oil pressure -> XLE pts"),
|
|
"coef_balance_ung": _c(0.3, "Balance -> UNG pts (gas proxy)"),
|
|
},
|
|
"instruments": ["BRENT", "EURUSD", "XAUUSD", "UNG", "XLE"],
|
|
"input_mapping": {
|
|
"inv_surprise": {"source": "surprise", "unit": "mb", "range": [-10, 10]}
|
|
},
|
|
},
|
|
},
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# 11. INSTITUTIONAL_FLOW
|
|
# COT repositioning, FX intervention, end-of-month rebalancing
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
{
|
|
"name": "Institutional Flow & Repositioning",
|
|
"slug": "INSTITUTIONAL_FLOW",
|
|
"category": "positioning",
|
|
"heuristic_ver": 2,
|
|
"calibration_json": {"lag_days": 1, "half_life_days": 7, "absorption_days": 14, "decay_type": "linear"},
|
|
"instruments": ["EURUSD", "SP500", "XAUUSD"],
|
|
"description": "Large institutional flow -> order flow imbalance -> momentum + mean reversion risk",
|
|
"ai_rationale": "COT repositioning or FX intervention moves prices via order flow mechanics (not fundamentals). Move is self-limiting — creates a mean reversion setup once the flow exhausts. Low confidence, short-lived signal.",
|
|
"graph_json": {
|
|
"nodes": [
|
|
_n("flow_trigger", "Institutional Flow", "input", 300, 50, unit="$B"),
|
|
_n("order_imbalance", "Order Flow Imbalance", "observable", 150, 220, formula="flow_trigger * {{coef_flow_imbalance}}"),
|
|
_n("market_impact", "Market Impact", "latent", 150, 390, formula="order_imbalance * {{coef_imbalance_impact}}"),
|
|
_n("momentum_sig", "Flow Momentum", "latent", 450, 390, formula="market_impact * {{coef_impact_mom}} + order_imbalance * {{coef_imbalance_mom}}"),
|
|
_n("eurusd", "EUR/USD", "market_asset", 100, 590, formula="momentum_sig * {{coef_mom_eurusd}}", unit="pips", instrument="EURUSD"),
|
|
_n("sp500", "S&P 500", "market_asset", 350, 590, formula="momentum_sig * {{coef_mom_sp}}", unit="pts", instrument="SP500"),
|
|
_n("xauusd", "Gold XAU/USD", "market_asset", 600, 590, formula="momentum_sig * {{coef_mom_gold}}", unit="$/oz", instrument="XAUUSD"),
|
|
],
|
|
"edges": [
|
|
_e("flow_trigger", "order_imbalance","solid", "execution", strength=3, sign="positive"),
|
|
_e("order_imbalance", "market_impact", "solid", "price_impact",strength=3, sign="positive"),
|
|
_e("market_impact", "momentum_sig", "solid", "momentum", strength=2, sign="positive"),
|
|
_e("order_imbalance", "momentum_sig", "solid", "order_flow", strength=2, sign="positive"),
|
|
_e("momentum_sig", "eurusd", "solid", "fx_flow", strength=3, sign="positive"),
|
|
_e("momentum_sig", "sp500", "solid", "eq_flow", strength=2, sign="positive"),
|
|
_e("momentum_sig", "xauusd", "dashed", "gold_flow",strength=2, sign="positive"),
|
|
],
|
|
"coefficients": {
|
|
"coef_flow_imbalance": _c(0.50, "$1B -> order imbalance"),
|
|
"coef_imbalance_impact": _c(0.60, "Imbalance -> market impact"),
|
|
"coef_impact_mom": _c(0.70, "Impact -> momentum"),
|
|
"coef_imbalance_mom": _c(0.40, "Imbalance -> momentum direct"),
|
|
"coef_mom_eurusd": _c(30, "Momentum -> EURUSD pips"),
|
|
"coef_mom_sp": _c(15, "Momentum -> SP500 pts"),
|
|
"coef_mom_gold": _c(8, "Momentum -> Gold $/oz"),
|
|
},
|
|
"instruments": ["EURUSD", "SP500", "XAUUSD"],
|
|
"input_mapping": {
|
|
"flow_trigger": {"source": "cot_report", "field": "net_non_commercial", "unit": "$B", "range": [-20, 20]}
|
|
},
|
|
},
|
|
},
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# 12. UNCLASSIFIED_IMPACT — fallback (confidence < 0.4)
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
{
|
|
"name": "Unclassified Market Impact",
|
|
"slug": "UNCLASSIFIED_IMPACT",
|
|
"category": "unclassified",
|
|
"heuristic_ver": 2,
|
|
"calibration_json": {"lag_days": 0, "half_life_days": 5, "absorption_days": 12, "decay_type": "exp"},
|
|
"instruments": ["EURUSD", "SP500", "XAUUSD"],
|
|
"description": "Fallback — event doesn't fit established regimes. Low confidence, no causal chain.",
|
|
"ai_rationale": "Direct trigger to output with no intermediate nodes. Precision scores from this template are marked low-confidence and excluded from calibration stats.",
|
|
"graph_json": {
|
|
"nodes": [
|
|
_n("unknown_trigger","Market Trigger", "input", 300, 80, unit="score"),
|
|
_n("market_reaction","Market Reaction", "latent", 300, 300, formula="unknown_trigger * {{coef_trig_react}}"),
|
|
_n("eurusd", "EUR/USD", "market_asset", 100, 520, formula="market_reaction * {{coef_react_eurusd}}", unit="pips", instrument="EURUSD"),
|
|
_n("sp500", "S&P 500", "market_asset", 300, 520, formula="market_reaction * {{coef_react_sp}}", unit="pts", instrument="SP500"),
|
|
_n("xauusd", "Gold XAU/USD", "market_asset", 520, 520, formula="market_reaction * {{coef_react_gold}}", unit="$/oz", instrument="XAUUSD"),
|
|
],
|
|
"edges": [
|
|
_e("unknown_trigger","market_reaction","dashed","direct_impact",strength=1, sign="positive", label="low confidence"),
|
|
_e("market_reaction","eurusd", "dashed","output_fx", strength=1, sign="positive"),
|
|
_e("market_reaction","sp500", "dashed","output_eq", strength=1, sign="positive"),
|
|
_e("market_reaction","xauusd", "dashed","output_gold",strength=1, sign="positive"),
|
|
],
|
|
"coefficients": {
|
|
"coef_trig_react": _c(0.50, "Trigger -> market reaction"),
|
|
"coef_react_eurusd": _c(20, "Reaction -> EURUSD pips"),
|
|
"coef_react_sp": _c(15, "Reaction -> SP500 pts"),
|
|
"coef_react_gold": _c(5, "Reaction -> Gold $/oz"),
|
|
},
|
|
"instruments": ["EURUSD", "SP500", "XAUUSD"],
|
|
"input_mapping": {
|
|
"unknown_trigger": {"source": "user_input", "unit": "score", "range": [-5, 5]}
|
|
},
|
|
},
|
|
},
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# 13. MACRO_RATE_GUIDANCE (auto-créé par guidance_sync — pas dans REGIME_SLUGS)
|
|
# Anticipation d'une décision de taux avant l'event : repricing progressif OIS
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
{
|
|
"name": "Macro Rate Guidance",
|
|
"slug": "MACRO_RATE_GUIDANCE",
|
|
"category": "central_bank",
|
|
"sub_type": "guidance",
|
|
"heuristic_ver": 2,
|
|
"calibration_json": {"lag_days": 0, "half_life_days": 30, "absorption_days": 60, "decay_type": "linear"},
|
|
"instruments": ["EURUSD", "TLT", "USDJPY", "SP500", "XAUUSD", "GBPUSD", "EEM", "QQQ"],
|
|
"description": "Anticipation marché d'une décision de taux — pricing-in progressif avant l'event (OIS forwards, carry FX, duration bonds)",
|
|
"ai_rationale": "Quand le consensus se forme autour d'un cut/hike, les OIS forwards repricing graduellement. Le carry FX shift avant la décision. TLT monte si cut attendu. Signal = forecast - previous en bps. Signal=0 = marché neutre (status quo).",
|
|
"graph_json": {
|
|
"nodes": [
|
|
_n("guidance_signal", "Expected Δ Rate", "input", 400, 50, unit="bps",
|
|
description="Positif=hike attendu, Négatif=cut attendu (bps)"),
|
|
_n("ois_forward", "OIS Fwd Shift", "observable", 180, 200,
|
|
formula="guidance_signal * {{coef_signal_ois}}", unit="bps"),
|
|
_n("usd_carry", "USD Carry Shift", "observable", 60, 350,
|
|
formula="ois_forward * {{coef_ois_carry}}", unit="%"),
|
|
_n("rate_expectation", "Rate Path Exp.", "latent", 400, 350,
|
|
formula="ois_forward * {{coef_ois_re}}"),
|
|
_n("risk_premium", "Risk Premium", "latent", 700, 350,
|
|
formula="guidance_signal * {{coef_signal_rp}}",
|
|
description="Négatif si cut (risk-on), positif si hike (risk-off)"),
|
|
# Outputs row 1
|
|
_n("eurusd", "EUR/USD", "market_asset", 60, 530, formula="-usd_carry * {{coef_carry_eurusd}}", unit="pips", instrument="EURUSD"),
|
|
_n("tlt", "TLT Bond", "market_asset", 240, 530, formula="-rate_expectation * {{coef_re_tlt}}", unit="pts", instrument="TLT"),
|
|
_n("usdjpy", "USD/JPY", "market_asset", 420, 530, formula="usd_carry * {{coef_carry_usdjpy}}", unit="pips", instrument="USDJPY"),
|
|
_n("sp500", "S&P 500", "market_asset", 600, 530, formula="-risk_premium * {{coef_rp_sp}} + rate_expectation * {{coef_re_sp}}", unit="pts", instrument="SP500"),
|
|
# Outputs row 2
|
|
_n("xauusd", "Gold", "market_asset", 60, 680, formula="-usd_carry * {{coef_carry_gold}} - rate_expectation * {{coef_re_gold}}", unit="$/oz", instrument="XAUUSD"),
|
|
_n("gbpusd", "GBP/USD", "market_asset", 240, 680, formula="-usd_carry * {{coef_carry_gbpusd}}", unit="pips", instrument="GBPUSD"),
|
|
_n("eem", "EEM Emerg", "market_asset", 420, 680, formula="-usd_carry * {{coef_carry_eem}} - risk_premium * {{coef_rp_eem}}", unit="pts", instrument="EEM"),
|
|
_n("qqq", "QQQ Nasdaq", "market_asset", 600, 680, formula="-rate_expectation * {{coef_re_qqq}} - risk_premium * {{coef_rp_qqq}}", unit="pts", instrument="QQQ"),
|
|
],
|
|
"edges": [
|
|
_e("guidance_signal", "ois_forward", "solid", "policy_fwd_pricing", strength=3, sign="positive", label="hike→OIS up / cut→OIS dn"),
|
|
_e("guidance_signal", "risk_premium", "dashed", "risk_channel", strength=2, sign="positive", label="hike=risk-off / cut=risk-on"),
|
|
_e("ois_forward", "usd_carry", "solid", "carry_diff", strength=3, sign="positive"),
|
|
_e("ois_forward", "rate_expectation", "solid", "fwd_path", strength=3, sign="positive"),
|
|
# FX
|
|
_e("usd_carry", "eurusd", "solid", "fx_eurusd", strength=3, sign="negative", label="USD up → EUR/USD dn"),
|
|
_e("usd_carry", "usdjpy", "solid", "fx_usdjpy", strength=2, sign="positive", label="USD up → USD/JPY up"),
|
|
_e("usd_carry", "gbpusd", "solid", "fx_gbpusd", strength=2, sign="negative"),
|
|
_e("usd_carry", "eem", "solid", "usd_eem", strength=2, sign="negative"),
|
|
# Bonds
|
|
_e("rate_expectation","tlt", "solid", "bond_dur", strength=3, sign="negative", label="hike exp → TLT dn"),
|
|
# Equities
|
|
_e("risk_premium", "sp500", "solid", "equity_rp", strength=2, sign="negative"),
|
|
_e("rate_expectation","sp500", "dashed", "disc_rate", strength=2, sign="negative", label="hike → discount rate up → SP dn"),
|
|
_e("risk_premium", "qqq", "dashed", "tech_rp", strength=2, sign="negative"),
|
|
_e("rate_expectation","qqq", "solid", "tech_dur", strength=3, sign="negative", label="QQQ = long duration"),
|
|
_e("risk_premium", "eem", "dashed", "em_risk", strength=2, sign="negative"),
|
|
# Gold
|
|
_e("usd_carry", "xauusd", "solid", "usd_gold", strength=2, sign="negative"),
|
|
_e("rate_expectation","xauusd", "dashed", "real_rate", strength=2, sign="negative", label="hike exp → real rates up → gold dn"),
|
|
],
|
|
"coefficients": {
|
|
"coef_signal_ois": _c(0.8, "bps signal → OIS shift (same sign: hike=up, cut=down)"),
|
|
"coef_ois_carry": _c(0.4, "OIS shift → USD carry %"),
|
|
"coef_ois_re": _c(0.9, "OIS shift → rate expectation index"),
|
|
"coef_signal_rp": _c(0.3, "signal bps → risk premium (hike=risk-off positive)"),
|
|
"coef_carry_eurusd": _c(60, "1% USD carry → EUR/USD pips fall"),
|
|
"coef_carry_usdjpy": _c(80, "1% USD carry → USD/JPY pips rise"),
|
|
"coef_carry_gbpusd": _c(50, "1% USD carry → GBP/USD pips fall"),
|
|
"coef_carry_eem": _c(20, "1% USD carry → EEM pts fall"),
|
|
"coef_carry_gold": _c(50, "1% USD carry → Gold $/oz fall"),
|
|
"coef_re_tlt": _c(12, "rate exp index → TLT pts fall (~12y duration)"),
|
|
"coef_rp_sp": _c(30, "risk premium → SP500 pts fall"),
|
|
"coef_re_sp": _c(20, "rate expectation → SP500 pts fall (discount rate)"),
|
|
"coef_re_qqq": _c(45, "rate exp → QQQ pts fall (high duration)"),
|
|
"coef_rp_qqq": _c(25, "risk premium → QQQ pts fall"),
|
|
"coef_rp_eem": _c(15, "risk premium → EEM pts fall"),
|
|
"coef_re_gold": _c(15, "rate exp → Gold $/oz fall (real rate)"),
|
|
},
|
|
"instruments": ["EURUSD", "TLT", "USDJPY", "SP500", "XAUUSD", "GBPUSD", "EEM", "QQQ"],
|
|
"input_mapping": {
|
|
"guidance_signal": {"source": "guidance_bps", "unit": "bps", "range": [-200, 200]}
|
|
},
|
|
},
|
|
},
|
|
]
|
|
|
|
|
|
# ── DB Helpers ─────────────────────────────────────────────────────────────────
|
|
|
|
def init_tables(conn):
|
|
conn.execute("""
|
|
CREATE TABLE IF NOT EXISTS causal_graph_templates (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT NOT NULL UNIQUE,
|
|
category TEXT NOT NULL,
|
|
sub_type TEXT,
|
|
instruments TEXT NOT NULL DEFAULT '[]',
|
|
description TEXT DEFAULT '',
|
|
graph_json TEXT NOT NULL,
|
|
ai_rationale TEXT DEFAULT '',
|
|
calibration_json TEXT DEFAULT '{}',
|
|
created_by TEXT DEFAULT 'system',
|
|
heuristic_ver INTEGER DEFAULT 1,
|
|
created_at TEXT DEFAULT (datetime('now')),
|
|
updated_at TEXT DEFAULT (datetime('now'))
|
|
)
|
|
""")
|
|
conn.execute("""
|
|
CREATE TABLE IF NOT EXISTS causal_event_analyses (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
market_event_id INTEGER,
|
|
template_id INTEGER,
|
|
instrument TEXT NOT NULL DEFAULT 'EURUSD',
|
|
inputs_json TEXT DEFAULT '{}',
|
|
override_params TEXT DEFAULT '{}',
|
|
prediction_json TEXT,
|
|
actual_json TEXT,
|
|
activation_score REAL,
|
|
drift_json TEXT,
|
|
ai_recommendation TEXT,
|
|
analyzed_at TEXT DEFAULT (datetime('now')),
|
|
FOREIGN KEY (template_id) REFERENCES causal_graph_templates(id)
|
|
)
|
|
""")
|
|
conn.commit()
|
|
|
|
|
|
def seed_templates(conn):
|
|
"""Insert/update built-in templates. One-shot migration: removes old non-regime templates."""
|
|
existing = conn.execute(
|
|
"SELECT name FROM causal_graph_templates WHERE created_by='system'"
|
|
).fetchall()
|
|
new_names = {t["name"] for t in BUILT_IN_TEMPLATES}
|
|
for row in existing:
|
|
if row["name"] not in new_names:
|
|
conn.execute("DELETE FROM causal_graph_templates WHERE name=? AND created_by='system'", (row["name"],))
|
|
|
|
for t in BUILT_IN_TEMPLATES:
|
|
ver = t.get("heuristic_ver", 1)
|
|
existing_row = conn.execute(
|
|
"SELECT id, heuristic_ver FROM causal_graph_templates WHERE name = ?", (t["name"],)
|
|
).fetchone()
|
|
if existing_row:
|
|
if (existing_row["heuristic_ver"] or 1) < ver:
|
|
conn.execute("""
|
|
UPDATE causal_graph_templates
|
|
SET graph_json=?, description=?, ai_rationale=?, instruments=?,
|
|
heuristic_ver=?, updated_at=datetime('now')
|
|
WHERE name=?
|
|
""", (
|
|
json.dumps(t["graph_json"]),
|
|
t.get("description", ""),
|
|
t.get("ai_rationale", ""),
|
|
json.dumps(t.get("instruments", [])),
|
|
ver, t["name"],
|
|
))
|
|
continue
|
|
conn.execute("""
|
|
INSERT INTO causal_graph_templates
|
|
(name, category, sub_type, instruments, description, graph_json,
|
|
calibration_json, ai_rationale, heuristic_ver, created_by)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'system')
|
|
""", (
|
|
t["name"], t["category"], t.get("sub_type", ""),
|
|
json.dumps(t.get("instruments", [])),
|
|
t.get("description", ""),
|
|
json.dumps(t["graph_json"]),
|
|
json.dumps(t.get("calibration_json", {})),
|
|
t.get("ai_rationale", ""),
|
|
ver,
|
|
))
|
|
|
|
# Migration: fill missing temporal params in calibration_json for all built-in templates.
|
|
# Only sets keys that are absent — never overwrites existing calibration stats.
|
|
for t in BUILT_IN_TEMPLATES:
|
|
defaults = t.get("calibration_json", {})
|
|
if not defaults:
|
|
continue
|
|
row = conn.execute(
|
|
"SELECT id, calibration_json FROM causal_graph_templates WHERE name=?", (t["name"],)
|
|
).fetchone()
|
|
if not row:
|
|
continue
|
|
calib = json.loads(row["calibration_json"] or "{}")
|
|
dirty = False
|
|
for key in ("lag_days", "half_life_days", "absorption_days", "decay_type"):
|
|
if calib.get(key) is None and key in defaults:
|
|
calib[key] = defaults[key]
|
|
dirty = True
|
|
if dirty:
|
|
conn.execute(
|
|
"UPDATE causal_graph_templates SET calibration_json=?, updated_at=datetime('now') WHERE id=?",
|
|
(json.dumps(calib), row["id"])
|
|
)
|
|
|
|
conn.commit()
|
|
|
|
|
|
def get_templates(conn, category: str = "") -> list:
|
|
where = f"WHERE category = '{category}'" if category else ""
|
|
rows = conn.execute(
|
|
f"SELECT * FROM causal_graph_templates {where} ORDER BY category, name"
|
|
).fetchall()
|
|
result = []
|
|
for r in rows:
|
|
d = dict(r)
|
|
d["graph_json"] = json.loads(d["graph_json"] or "{}")
|
|
d["instruments"] = json.loads(d["instruments"] or "[]")
|
|
d["calibration_json"] = json.loads(d["calibration_json"] or "{}")
|
|
result.append(d)
|
|
return result
|
|
|
|
|
|
def get_template(conn, template_id: int) -> Optional[dict]:
|
|
row = conn.execute(
|
|
"SELECT * FROM causal_graph_templates WHERE id = ?", (template_id,)
|
|
).fetchone()
|
|
if not row:
|
|
return None
|
|
d = dict(row)
|
|
d["graph_json"] = json.loads(d["graph_json"] or "{}")
|
|
d["instruments"] = json.loads(d["instruments"] or "[]")
|
|
d["calibration_json"] = json.loads(d["calibration_json"] or "{}")
|
|
return d
|
|
|
|
|
|
def update_coefficients(conn, template_id: int, coef_updates: dict):
|
|
tmpl = get_template(conn, template_id)
|
|
if not tmpl:
|
|
return False
|
|
graph = tmpl["graph_json"]
|
|
for k, v in coef_updates.items():
|
|
if k in graph.get("coefficients", {}):
|
|
graph["coefficients"][k]["value"] = float(v)
|
|
conn.execute(
|
|
"UPDATE causal_graph_templates SET graph_json = ?, updated_at = datetime('now') WHERE id = ?",
|
|
(json.dumps(graph), template_id)
|
|
)
|
|
conn.commit()
|
|
return True
|
|
|
|
|
|
def update_calibration(conn, template_id: int, analysis_result: dict):
|
|
"""Update calibration stats after a new analysis."""
|
|
tmpl = get_template(conn, template_id)
|
|
if not tmpl:
|
|
return
|
|
calib = tmpl.get("calibration_json") or {}
|
|
n = calib.get("n_events", 0) + 1
|
|
prev_act = calib.get("avg_activation", 0) or 0
|
|
new_act = analysis_result.get("activation_score") or 0
|
|
instrument = analysis_result.get("instrument", "EURUSD")
|
|
pred_pips = analysis_result.get("pred_pips", 0) or 0
|
|
act_pips = analysis_result.get("actual_pips", 0) or 0
|
|
|
|
calib["n_events"] = n
|
|
calib["avg_activation"] = round((prev_act * (n - 1) + new_act) / n, 3)
|
|
calib["last_analyzed"] = analysis_result.get("analyzed_at", "")
|
|
|
|
if instrument not in calib:
|
|
calib[instrument] = {"n": 0, "avg_pred": 0, "avg_actual": 0}
|
|
ci = calib[instrument]
|
|
ni = ci["n"] + 1
|
|
ci["n"] = ni
|
|
ci["avg_pred"] = round((ci["avg_pred"] * (ni - 1) + pred_pips) / ni, 1)
|
|
ci["avg_actual"] = round((ci["avg_actual"] * (ni - 1) + act_pips) / ni, 1)
|
|
if ci["avg_pred"] != 0:
|
|
ci["coef_ratio"] = round(ci["avg_actual"] / ci["avg_pred"], 2)
|
|
|
|
conn.execute(
|
|
"UPDATE causal_graph_templates SET calibration_json = ?, updated_at = datetime('now') WHERE id = ?",
|
|
(json.dumps(calib), template_id)
|
|
)
|
|
conn.commit()
|
|
|
|
|
|
# ── Graph evaluator ────────────────────────────────────────────────────────────
|
|
|
|
def _safe_eval(expr: str, context: dict) -> float:
|
|
_ops = {
|
|
ast.Add: operator.add,
|
|
ast.Sub: operator.sub,
|
|
ast.Mult: operator.mul,
|
|
ast.Div: operator.truediv,
|
|
ast.USub: operator.neg,
|
|
}
|
|
|
|
def _eval(node):
|
|
if isinstance(node, ast.Constant):
|
|
return float(node.value)
|
|
if isinstance(node, ast.Name):
|
|
if node.id not in context:
|
|
raise ValueError(f"Unknown variable: {node.id}")
|
|
return float(context[node.id])
|
|
if isinstance(node, ast.BinOp):
|
|
op = _ops.get(type(node.op))
|
|
if op is None:
|
|
raise ValueError(f"Unsupported op: {type(node.op)}")
|
|
return op(_eval(node.left), _eval(node.right))
|
|
if isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.USub):
|
|
return -_eval(node.operand)
|
|
raise ValueError(f"Unsupported AST node: {type(node)}")
|
|
|
|
tree = ast.parse(expr.strip(), mode="eval")
|
|
return _eval(tree.body)
|
|
|
|
|
|
def evaluate_graph(graph_json: dict, inputs: dict, coef_overrides: Optional[dict] = None) -> dict:
|
|
"""
|
|
Evaluate a causal graph from inputs. Returns {node_id: value}.
|
|
{{coef_xxx}} placeholders are substituted from graph_json["coefficients"].
|
|
"""
|
|
coefs = {k: v["value"] for k, v in graph_json.get("coefficients", {}).items()}
|
|
if coef_overrides:
|
|
coefs.update({k: float(v) for k, v in coef_overrides.items()})
|
|
|
|
def sub(formula: str) -> str:
|
|
for k, v in coefs.items():
|
|
formula = formula.replace(f"{{{{{k}}}}}", str(v))
|
|
return formula
|
|
|
|
values = dict(inputs)
|
|
nodes = graph_json.get("nodes", [])
|
|
for _ in range(len(nodes) + 2):
|
|
changed = False
|
|
for node in nodes:
|
|
if node["id"] in values or not node.get("formula"):
|
|
continue
|
|
try:
|
|
result = _safe_eval(sub(node["formula"]), values)
|
|
values[node["id"]] = round(result, 4)
|
|
changed = True
|
|
except Exception:
|
|
pass
|
|
if not changed:
|
|
break
|
|
|
|
return values
|