feat: simulateur EUR/USD — canaux taux/ton séparés + saisie directe sliders

Modèle causal refactorisé en deux canaux distincts :
- Canal taux (solid) : variations taux directeurs → principalement 2Y (ancrage court terme)
- Canal ton/anticipations (tirets) : discours CB + surprises CPI/NFP → principalement 10Y (anticipations long terme)

Slider : clic sur la valeur affichée → input éditable (Enter/Blur pour valider, Escape pour annuler)

Backend : endpoint /api/simulator/baseline expose us_10y et eu_10y (yfinance ^TNX + GE10YT=RR)

SVG mis à jour : 4 nœuds de taux (2Y/10Y US+EU), 2 nœuds différentiels, flèches solides/tirets
Strip métrique : 6 cellules (US 2Y, US 10Y, Bund 2Y, Bund 10Y, Δ 2Y, Δ 10Y)
Décomposition : séparation "Δ 2Y — taux directeurs" / "Δ 10Y — anticipations/ton"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-26 23:08:08 +02:00
parent fe86cc1994
commit e27ff72c1c
2 changed files with 311 additions and 160 deletions

View File

@@ -15,7 +15,9 @@ _FALLBACK = {
"fed_rate": 4.25,
"ecb_rate": 3.65,
"us_2y": 4.50,
"us_10y": 4.30,
"eu_2y": 2.80,
"eu_10y": 2.60,
"eurusd": 1.1450,
"vix": 18.0,
"oil": 80.0,
@@ -79,10 +81,11 @@ def simulator_baseline():
result["oil"] = round(v, 1)
sources["oil"] = f"snapshot {snap_date}"
# us10y raw value is the yield in % → keep as reference
# US 10Y from gauge snapshot (yfinance will override if available)
v = _gauge_val(gauges, "us10y")
if v:
result["_us10y_gauge"] = round(v, 2)
result["us_10y"] = round(v, 2)
sources["us_10y"] = f"snapshot {snap_date}"
except Exception as e:
logger.debug(f"[simulator/baseline] gauge query: {e}")
@@ -135,8 +138,12 @@ def simulator_baseline():
result["us_2y"] = round(tnx * 0.92, 2) # rough proxy
sources["us_2y"] = "yfinance ^TNX proxy"
# US 10Y (^TNX)
if tnx:
result["us_10y"] = round(tnx, 2)
sources["us_10y"] = "yfinance ^TNX"
# Real yield: 10Y nominal minus 10Y breakeven inflation via TIPS
# ^TNX = 10Y nominal; ^FVX gives us a rough real yield proxy
if tnx and "real_yield_us" not in sources:
# TIPS yield approximation: nominal - 2.3% (rough breakeven)
result["real_yield_us"] = round(tnx - 2.3, 2)
@@ -152,6 +159,16 @@ def simulator_baseline():
result["eu_2y"] = round(result["ecb_rate"] + 0.15, 2)
sources["eu_2y"] = "ECB rate +15bps approx"
# EU 10Y — German Bund 10Y
eu10y = _yf_last("GE10YT=RR")
if eu10y:
result["eu_10y"] = round(eu10y, 2)
sources["eu_10y"] = "yfinance GE10YT=RR"
else:
# Approximation: ECB rate + term premium ~200bps
result["eu_10y"] = round(result["ecb_rate"] + 2.0, 2)
sources["eu_10y"] = "ECB rate +200bps approx"
# VIX — override gauge if not already set from DB
if "vix" not in sources:
v = _yf_last("^VIX")
@@ -170,7 +187,6 @@ def simulator_baseline():
logger.debug(f"[simulator/baseline] yfinance block: {e}")
# ── Cleanup & return ────────────────────────────────────────────────────
result.pop("_us10y_gauge", None)
result["sources"] = sources
result["fetched_at"] = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
return result