feat: eco calendar bootstrap + driver types + timeline cursor

Backend:
- eco_calendar_bootstrap.py: 75 historical events 2020-2026 (FOMC, NFP,
  CPI, GDP, ISM, BOJ, ECB, BOE) with expected_value/actual_value/
  surprise_pct/unit/absorption_pct fields
- database.py: 5 new columns on market_events (expected_value,
  actual_value, surprise_pct, unit, sub_type) + updated save_market_event
- timeline.py: POST /api/timeline/bootstrap-eco endpoint
- instrument_service.py: _get_relevant_events now returns eco fields
- instruments.json: type field on all 90 drivers across 20 instruments
  (event_calendar | report | geopolitical | fundamental | sentiment | technical)

Frontend (InstrumentDashboard):
- EventTimelineStrip: vertical cursor line tracking crosshair selectedDate
- EventsCard: active-event highlight at hovered date + expected/actual/
  surprise_pct display + absorption progress bar
- DriversPanel: type selector dropdown per driver
- DriverTypeBadge: colored micro-badge on driver labels in strip

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-25 00:07:41 +02:00
parent aec9ced74f
commit 3470205d54
6 changed files with 878 additions and 160 deletions

View File

@@ -11,11 +11,11 @@
"description": "US large-cap equity benchmark — 500 plus grandes capitalisations américaines",
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
"drivers": [
{"key": "fed_policy", "label": "Fed Policy", "weight": 0.9, "keywords": ["FOMC","Fed","Federal Reserve","Powell","taux directeur","pivot","dot plot","QT","QE","taux Fed"]},
{"key": "earnings_growth", "label": "Earnings Growth", "weight": 0.85, "keywords": ["Earnings","EPS","bénéfices","résultats","Earnings Season","reporting","profits"]},
{"key": "real_rates", "label": "Real Rates", "weight": 0.75, "keywords": ["taux réels","TIPS","real yields","breakeven","inflation","CPI","PCE"]},
{"key": "liquidity", "label": "Global Liquidity", "weight": 0.7, "keywords": ["liquidité","liquidity","QE","QT","bilan Fed","repo","M2","conditions financières"]},
{"key": "vix", "label": "VIX / Fear", "weight": 0.6, "keywords": ["VIX","volatilité","vol spike","hedging","protection","skew","put","peur","fear"]}
{"key": "fed_policy", "label": "Fed Policy", "type": "event_calendar", "weight": 0.9, "keywords": ["FOMC","Fed","Federal Reserve","Powell","taux directeur","pivot","dot plot","QT","QE","taux Fed"]},
{"key": "earnings_growth", "label": "Earnings Growth", "type": "report", "weight": 0.85, "keywords": ["Earnings","EPS","bénéfices","résultats","Earnings Season","reporting","profits"]},
{"key": "real_rates", "label": "Real Rates", "type": "fundamental", "weight": 0.75, "keywords": ["taux réels","TIPS","real yields","breakeven","inflation","CPI","PCE"]},
{"key": "liquidity", "label": "Global Liquidity", "type": "fundamental", "weight": 0.7, "keywords": ["liquidité","liquidity","QE","QT","bilan Fed","repo","M2","conditions financières"]},
{"key": "vix", "label": "VIX / Fear", "type": "sentiment", "weight": 0.6, "keywords": ["VIX","volatilité","vol spike","hedging","protection","skew","put","peur","fear"]}
],
"regime_labels": ["Risk-On Bull", "Risk-Off Bear", "Stagflation", "Rate Shock", "Recovery"],
"event_keywords": ["FOMC","CPI","NFP","GDP","Earnings Season","Fed Chair","VIX"],
@@ -34,11 +34,11 @@
"description": "US large-cap tech and growth benchmark — 100 plus grandes valeurs Nasdaq",
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
"drivers": [
{"key": "real_rates", "label": "Real Rates", "weight": 0.9, "keywords": ["taux réels","TIPS","real yields","FOMC","Fed","CPI","breakeven"]},
{"key": "ai_capex", "label": "AI Capex", "weight": 0.85, "keywords": ["IA","AI","Nvidia","Microsoft","capex tech","data center","GPU","intelligence artificielle","ChatGPT"]},
{"key": "megacaps", "label": "Mégacaps GAFAM", "weight": 0.8, "keywords": ["GAFAM","Apple","Meta","Alphabet","Amazon","Microsoft","earnings tech","Big Tech","résultats tech"]},
{"key": "growth", "label": "Croissance US", "weight": 0.75, "keywords": ["croissance","GDP","récession","NFP","emploi","PMI","croissance économique"]},
{"key": "dollar", "label": "Dollar Index", "weight": 0.5, "keywords": ["Dollar","DXY","dollar index","EUR/USD","devise"]}
{"key": "real_rates", "label": "Real Rates", "type": "fundamental", "weight": 0.9, "keywords": ["taux réels","TIPS","real yields","FOMC","Fed","CPI","breakeven"]},
{"key": "ai_capex", "label": "AI Capex", "type": "fundamental", "weight": 0.85, "keywords": ["IA","AI","Nvidia","Microsoft","capex tech","data center","GPU","intelligence artificielle","ChatGPT"]},
{"key": "megacaps", "label": "Mégacaps GAFAM", "type": "report", "weight": 0.8, "keywords": ["GAFAM","Apple","Meta","Alphabet","Amazon","Microsoft","earnings tech","Big Tech","résultats tech"]},
{"key": "growth", "label": "Croissance US", "type": "event_calendar", "weight": 0.75, "keywords": ["croissance","GDP","récession","NFP","emploi","PMI","croissance économique"]},
{"key": "dollar", "label": "Dollar Index", "type": "technical", "weight": 0.5, "keywords": ["Dollar","DXY","dollar index","EUR/USD","devise"]}
],
"regime_labels": ["Tech Bull (AI-Driven)", "Tech Bear", "Rate Compression", "Earnings Crash", "Recovery"],
"event_keywords": ["FOMC","Big Tech Earnings","CPI","AI","Rate","NVDA","AAPL"],
@@ -57,10 +57,10 @@
"description": "US small-cap equity benchmark — 2000 petites capitalisations américaines",
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
"drivers": [
{"key": "gdp_growth", "label": "Croissance US", "weight": 0.85, "keywords": ["GDP","PIB","croissance US","NFP","emploi","récession","PMI","domestic growth"]},
{"key": "credit", "label": "Crédit & Spreads", "weight": 0.8, "keywords": ["crédit","credit","HY","high yield","spreads","LBO","conditions de crédit","credit crunch"]},
{"key": "regional_banks", "label": "Banques Régionales","weight": 0.75, "keywords": ["banques régionales","SVB","FDIC","banque","banking","bank failure","crédit bancaire"]},
{"key": "dollar", "label": "Dollar Index", "weight": 0.6, "keywords": ["Dollar","DXY","dollar fort","USD strength"]}
{"key": "gdp_growth", "label": "Croissance US", "type": "event_calendar", "weight": 0.85, "keywords": ["GDP","PIB","croissance US","NFP","emploi","récession","PMI","domestic growth"]},
{"key": "credit", "label": "Crédit & Spreads", "type": "fundamental", "weight": 0.8, "keywords": ["crédit","credit","HY","high yield","spreads","LBO","conditions de crédit","credit crunch"]},
{"key": "regional_banks", "label": "Banques Régionales","type": "fundamental", "weight": 0.75, "keywords": ["banques régionales","SVB","FDIC","banque","banking","bank failure","crédit bancaire"]},
{"key": "dollar", "label": "Dollar Index", "type": "technical", "weight": 0.6, "keywords": ["Dollar","DXY","dollar fort","USD strength"]}
],
"regime_labels": ["Small Cap Expansion", "Credit Crunch Squeeze", "Risk-Off", "Recovery", "Neutral"],
"event_keywords": ["FOMC","Credit","GDP","NFP","Rates","Regional Banks"],
@@ -79,10 +79,10 @@
"description": "MSCI Emerging Markets — grandes capitalisations des marchés émergents",
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
"drivers": [
{"key": "dollar_index", "label": "Dollar Index", "weight": 0.9, "keywords": ["Dollar","DXY","USD","dollar index","dollar fort","faible dollar"]},
{"key": "china_growth", "label": "Chine / PBoC", "weight": 0.85, "keywords": ["China","Chine","PBOC","PBoC","PIB chinois","Xi","immobilier chinois","stimulus Chine","relance Chine"]},
{"key": "em_flows", "label": "Flux EM", "weight": 0.75, "keywords": ["EM","émergents","emerging","flows","flux","capitaux émergents"]},
{"key": "commodities", "label": "Matières Premières","weight": 0.7, "keywords": ["matières premières","commodities","minerai","pétrole","métaux","commodité"]}
{"key": "dollar_index", "label": "Dollar Index", "type": "technical", "weight": 0.9, "keywords": ["Dollar","DXY","USD","dollar index","dollar fort","faible dollar"]},
{"key": "china_growth", "label": "Chine / PBoC", "type": "event_calendar", "weight": 0.85, "keywords": ["China","Chine","PBOC","PBoC","PIB chinois","Xi","immobilier chinois","stimulus Chine","relance Chine"]},
{"key": "em_flows", "label": "Flux EM", "type": "sentiment", "weight": 0.75, "keywords": ["EM","émergents","emerging","flows","flux","capitaux émergents"]},
{"key": "commodities", "label": "Matières Premières","type": "fundamental", "weight": 0.7, "keywords": ["matières premières","commodities","minerai","pétrole","métaux","commodité"]}
],
"regime_labels": ["EM Growth Rally", "Dollar Squeeze", "China Risk-Off", "Commodity Lift", "Neutral"],
"event_keywords": ["China GDP","Dollar","EM","PBOC","Commodities","stimulus Chine"],
@@ -101,10 +101,10 @@
"description": "MSCI EAFE — Europe, Australasie, Extrême-Orient (hors US/Canada)",
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
"drivers": [
{"key": "ecb_policy", "label": "BCE / ECB", "weight": 0.85, "keywords": ["BCE","ECB","Lagarde","taux BCE","zone euro","politique BCE","ECB rate"]},
{"key": "energy", "label": "Énergie Europe", "weight": 0.8, "keywords": ["énergie","gaz européen","pétrole","LNG","crisis énergétique","energy","energy price"]},
{"key": "eurusd", "label": "EUR/USD", "weight": 0.75, "keywords": ["EUR/USD","euro dollar","eurusd","parité euro","euro fort","euro faible"]},
{"key": "european_growth", "label": "Croissance Européenne", "weight": 0.7, "keywords": ["Europe","zone euro","Allemagne","PIB européen","PMI Europe","récession Europe","croissance européenne"]}
{"key": "ecb_policy", "label": "BCE / ECB", "type": "event_calendar", "weight": 0.85, "keywords": ["BCE","ECB","Lagarde","taux BCE","zone euro","politique BCE","ECB rate"]},
{"key": "energy", "label": "Énergie Europe", "type": "fundamental", "weight": 0.8, "keywords": ["énergie","gaz européen","pétrole","LNG","crisis énergétique","energy","energy price"]},
{"key": "eurusd", "label": "EUR/USD", "type": "technical", "weight": 0.75, "keywords": ["EUR/USD","euro dollar","eurusd","parité euro","euro fort","euro faible"]},
{"key": "european_growth", "label": "Croissance Européenne", "type": "event_calendar", "weight": 0.7, "keywords": ["Europe","zone euro","Allemagne","PIB européen","PMI Europe","récession Europe","croissance européenne"]}
],
"regime_labels": ["Global Risk-On", "USD Strength Drag", "Europe Recession", "Recovery", "Neutral"],
"event_keywords": ["ECB","EU GDP","Europe","Germany","BOJ","Japan","EUR"],
@@ -123,11 +123,11 @@
"description": "SPDR Gold Shares — proxy physique de l'or",
"chart": { "ma_periods": [50, 100, 200], "bollinger_period": 50, "bollinger_std": 2, "show_volume": true },
"drivers": [
{"key": "real_rates_us", "label": "Taux Réels US", "weight": 0.95, "keywords": ["taux réels","TIPS","real yields","Fed","FOMC","CPI","breakeven","inflation réelle"]},
{"key": "dollar_index", "label": "Dollar Index", "weight": 0.85, "keywords": ["Dollar","DXY","dollar index","USD","faible dollar","dollar fort"]},
{"key": "inflation_expectations", "label": "Anticipations Inflation", "weight": 0.75, "keywords": ["inflation","CPI","PCE","breakeven","attentes d'inflation","stagflation"]},
{"key": "geopolitical_risk", "label": "Risque Géopolitique", "weight": 0.7, "keywords": ["géopolitique","guerre","Moyen-Orient","Iran","Russie","conflit","Chine","tension","crise","war"]},
{"key": "cb_buying", "label": "Achats Banques Centrales","weight": 0.5, "keywords": ["banque centrale","central bank","réserves","gold reserves","achats d'or","PBOC gold","dédollarisation"]}
{"key": "real_rates_us", "label": "Taux Réels US", "type": "fundamental", "weight": 0.95, "keywords": ["taux réels","TIPS","real yields","Fed","FOMC","CPI","breakeven","inflation réelle"]},
{"key": "dollar_index", "label": "Dollar Index", "type": "technical", "weight": 0.85, "keywords": ["Dollar","DXY","dollar index","USD","faible dollar","dollar fort"]},
{"key": "inflation_expectations", "label": "Anticipations Inflation", "type": "fundamental", "weight": 0.75, "keywords": ["inflation","CPI","PCE","breakeven","attentes d'inflation","stagflation"]},
{"key": "geopolitical_risk", "label": "Risque Géopolitique", "type": "geopolitical", "weight": 0.7, "keywords": ["géopolitique","guerre","Moyen-Orient","Iran","Russie","conflit","Chine","tension","crise","war"]},
{"key": "cb_buying", "label": "Achats Banques Centrales","type": "fundamental", "weight": 0.5, "keywords": ["banque centrale","central bank","réserves","gold reserves","achats d'or","PBOC gold","dédollarisation"]}
],
"regime_labels": ["Real Rates Falling", "Safe Haven Rally", "Inflation Hedge", "Dollar Weakness", "Range"],
"event_keywords": ["CPI","Fed","FOMC","Dollar","Geopolitical","Iran","Gold","guerre","inflation"],
@@ -146,11 +146,11 @@
"description": "iShares Silver Trust — proxy physique de l'argent métal",
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
"drivers": [
{"key": "industrial_demand", "label": "Demande Industrielle", "weight": 0.8, "keywords": ["industrie","manufacturing","PMI","production industrielle","fabrique","industry"]},
{"key": "real_rates", "label": "Taux Réels", "weight": 0.75, "keywords": ["taux réels","TIPS","Fed","FOMC","real yields"]},
{"key": "dollar", "label": "Dollar Index", "weight": 0.7, "keywords": ["Dollar","DXY","USD"]},
{"key": "solar_ev_demand", "label": "Solaire / Véhicule EV","weight": 0.65, "keywords": ["solaire","solar","EV","véhicule électrique","énergie verte","green energy","photovoltaïque","battery"]},
{"key": "gold_ratio", "label": "Ratio Or/Argent", "weight": 0.6, "keywords": ["Gold","or","ratio Or/Argent","gold silver","argent","silver"]}
{"key": "industrial_demand", "label": "Demande Industrielle", "type": "fundamental", "weight": 0.8, "keywords": ["industrie","manufacturing","PMI","production industrielle","fabrique","industry"]},
{"key": "real_rates", "label": "Taux Réels", "type": "fundamental", "weight": 0.75, "keywords": ["taux réels","TIPS","Fed","FOMC","real yields"]},
{"key": "dollar", "label": "Dollar Index", "type": "technical", "weight": 0.7, "keywords": ["Dollar","DXY","USD"]},
{"key": "solar_ev_demand", "label": "Solaire / Véhicule EV","type": "fundamental", "weight": 0.65, "keywords": ["solaire","solar","EV","véhicule électrique","énergie verte","green energy","photovoltaïque","battery"]},
{"key": "gold_ratio", "label": "Ratio Or/Argent", "type": "technical", "weight": 0.6, "keywords": ["Gold","or","ratio Or/Argent","gold silver","argent","silver"]}
],
"regime_labels": ["Industrial Demand", "Safe Haven Flow", "Dollar Weakness", "Risk-Off", "Range"],
"event_keywords": ["Manufacturing","PMI","CPI","Dollar","Silver","EV","Solar","taux réels"],
@@ -169,11 +169,11 @@
"description": "United States Oil Fund — proxy WTI (pétrole brut américain)",
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
"drivers": [
{"key": "opec_policy", "label": "OPEP+ / Production", "weight": 0.9, "keywords": ["OPEC","OPEP","production pétrolière","quotas","Arabie Saoudite","Saudi","réduction","coupes","OPEC meeting"]},
{"key": "inventories", "label": "Stocks EIA", "weight": 0.8, "keywords": ["EIA","stocks pétroliers","inventaires","inventories","crude stocks","rapport EIA","crude supply"]},
{"key": "geopolitical_risk","label": "Risque Géopolitique","weight": 0.75, "keywords": ["géopolitique","Moyen-Orient","Iran","Russie","conflit","guerre","war","Golfe Persique","tension"]},
{"key": "global_demand", "label": "Demande Mondiale", "weight": 0.85, "keywords": ["demande mondiale","China demand","IEA","croissance mondiale","global demand","reprise économique"]},
{"key": "dollar", "label": "Dollar Index", "weight": 0.7, "keywords": ["Dollar","DXY","USD"]}
{"key": "opec_policy", "label": "OPEP+ / Production", "type": "event_calendar", "weight": 0.9, "keywords": ["OPEC","OPEP","production pétrolière","quotas","Arabie Saoudite","Saudi","réduction","coupes","OPEC meeting"]},
{"key": "inventories", "label": "Stocks EIA", "type": "report", "weight": 0.8, "keywords": ["EIA","stocks pétroliers","inventaires","inventories","crude stocks","rapport EIA","crude supply"]},
{"key": "geopolitical_risk","label": "Risque Géopolitique","type": "geopolitical", "weight": 0.75, "keywords": ["géopolitique","Moyen-Orient","Iran","Russie","conflit","guerre","war","Golfe Persique","tension"]},
{"key": "global_demand", "label": "Demande Mondiale", "type": "fundamental", "weight": 0.85, "keywords": ["demande mondiale","China demand","IEA","croissance mondiale","global demand","reprise économique"]},
{"key": "dollar", "label": "Dollar Index", "type": "technical", "weight": 0.7, "keywords": ["Dollar","DXY","USD"]}
],
"regime_labels": ["Supply Shock", "Demand Recovery", "Inventory Draw", "Geopolitical Premium", "Neutral"],
"event_keywords": ["OPEC","EIA","Inventories","Brent","WTI","Oil","Middle East","Saudi"],
@@ -192,10 +192,10 @@
"description": "United States Natural Gas Fund — proxy Henry Hub (gaz naturel)",
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
"drivers": [
{"key": "weather_seasonality", "label": "Météo / Saisonnalité","weight": 0.9, "keywords": ["météo","weather","hiver","été","froid","chaleur","El Niño","La Niña","saison","vague de froid","heat wave"]},
{"key": "storage", "label": "Stockage EIA Gaz", "weight": 0.85, "keywords": ["EIA Gas Storage","stockage gaz","gas storage","inventaires gaz","rapport stockage gaz"]},
{"key": "lng_exports", "label": "Exports LNG", "weight": 0.75, "keywords": ["LNG","GNL","exportations gaz","terminal LNG","gazoduc","liquefied natural gas"]},
{"key": "production", "label": "Production US", "weight": 0.7, "keywords": ["production gaz","shale gas","fracking","puits gaz","Marcellus","Appalachian","US gas output"]}
{"key": "weather_seasonality", "label": "Météo / Saisonnalité","type": "fundamental", "weight": 0.9, "keywords": ["météo","weather","hiver","été","froid","chaleur","El Niño","La Niña","saison","vague de froid","heat wave"]},
{"key": "storage", "label": "Stockage EIA Gaz", "type": "report", "weight": 0.85, "keywords": ["EIA Gas Storage","stockage gaz","gas storage","inventaires gaz","rapport stockage gaz"]},
{"key": "lng_exports", "label": "Exports LNG", "type": "fundamental", "weight": 0.75, "keywords": ["LNG","GNL","exportations gaz","terminal LNG","gazoduc","liquefied natural gas"]},
{"key": "production", "label": "Production US", "type": "fundamental", "weight": 0.7, "keywords": ["production gaz","shale gas","fracking","puits gaz","Marcellus","Appalachian","US gas output"]}
],
"regime_labels": ["Winter Storage Fill", "Supply Glut", "Demand Surge", "Export LNG Lift", "Neutral"],
"event_keywords": ["EIA Gas Storage","LNG","Gas","Natural Gas","Weather","stockage gaz"],
@@ -214,11 +214,11 @@
"description": "iShares 20+ Year Treasury — proxy du rendement US 10Y/30Y",
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
"drivers": [
{"key": "fed_path", "label": "Trajectoire Fed", "weight": 0.9, "keywords": ["FOMC","Fed","taux directeur","Fed hike","Fed cut","pivot","dot plot","Federal Reserve"]},
{"key": "inflation_expectations", "label": "Inflation / CPI", "weight": 0.85, "keywords": ["CPI","PCE","inflation","déflation","attentes","breakeven","inflation US"]},
{"key": "growth", "label": "Croissance / NFP", "weight": 0.75, "keywords": ["GDP","NFP","récession","croissance","emploi","PMI","chômage","payrolls"]},
{"key": "deficit_supply", "label": "Déficit / Offre Treasury","weight": 0.7, "keywords": ["déficit","dette publique","Treasury auction","enchères","budget","fiscal","dette fédérale"]},
{"key": "risk_aversion", "label": "Fuite Vers Qualité", "weight": 0.65, "keywords": ["risk-off","crise","fuite vers la qualité","safe haven","vol spike","contagion","crise bancaire"]}
{"key": "fed_path", "label": "Trajectoire Fed", "type": "event_calendar", "weight": 0.9, "keywords": ["FOMC","Fed","taux directeur","Fed hike","Fed cut","pivot","dot plot","Federal Reserve"]},
{"key": "inflation_expectations", "label": "Inflation / CPI", "type": "fundamental", "weight": 0.85, "keywords": ["CPI","PCE","inflation","déflation","attentes","breakeven","inflation US"]},
{"key": "growth", "label": "Croissance / NFP", "type": "event_calendar", "weight": 0.75, "keywords": ["GDP","NFP","récession","croissance","emploi","PMI","chômage","payrolls"]},
{"key": "deficit_supply", "label": "Déficit / Offre Treasury","type": "fundamental", "weight": 0.7, "keywords": ["déficit","dette publique","Treasury auction","enchères","budget","fiscal","dette fédérale"]},
{"key": "risk_aversion", "label": "Fuite Vers Qualité", "type": "sentiment", "weight": 0.65, "keywords": ["risk-off","crise","fuite vers la qualité","safe haven","vol spike","contagion","crise bancaire"]}
],
"regime_labels": ["Safe Haven Bid", "Rate Shock Selloff", "QE Expectations", "Inflation Selloff", "Range"],
"event_keywords": ["FOMC","CPI","Fed","Treasury","Yield","Deficit","NFP"],
@@ -237,10 +237,10 @@
"description": "iShares iBoxx USD High Yield Corporate Bond — baromètre du crédit HY",
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
"drivers": [
{"key": "default_rate", "label": "Taux de Défaut", "weight": 0.85, "keywords": ["défauts","default","faillite","bankruptcy","spreads HY","taux de défaut","HY spreads"]},
{"key": "growth", "label": "Croissance Économique", "weight": 0.8, "keywords": ["GDP","récession","croissance","PMI","emploi","payrolls","activité"]},
{"key": "liquidity", "label": "Conditions de Liquidité","weight": 0.8, "keywords": ["liquidité","crédit","QT","QE","conditions financières","FCO","financial conditions"]},
{"key": "risk_appetite", "label": "Appétit pour le Risque", "weight": 0.75, "keywords": ["risk-on","risk-off","appétit pour le risque","flux crédit","sentiment marché"]}
{"key": "default_rate", "label": "Taux de Défaut", "type": "fundamental", "weight": 0.85, "keywords": ["défauts","default","faillite","bankruptcy","spreads HY","taux de défaut","HY spreads"]},
{"key": "growth", "label": "Croissance Économique", "type": "event_calendar", "weight": 0.8, "keywords": ["GDP","récession","croissance","PMI","emploi","payrolls","activité"]},
{"key": "liquidity", "label": "Conditions de Liquidité","type": "fundamental", "weight": 0.8, "keywords": ["liquidité","crédit","QT","QE","conditions financières","FCO","financial conditions"]},
{"key": "risk_appetite", "label": "Appétit pour le Risque", "type": "sentiment", "weight": 0.75, "keywords": ["risk-on","risk-off","appétit pour le risque","flux crédit","sentiment marché"]}
],
"regime_labels": ["Credit Expansion", "Credit Crunch", "Spread Compression", "Recession Fear", "Neutral"],
"event_keywords": ["Credit","Default","GDP","FOMC","Recession","HY","spreads"],
@@ -259,10 +259,10 @@
"description": "Taux spot Euro / Dollar américain",
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": false },
"drivers": [
{"key": "fed_ecb_differential", "label": "Différentiel Fed/BCE", "weight": 0.95, "keywords": ["BCE","ECB","Fed","FOMC","différentiel","taux directeurs","politique monétaire","rate differential"]},
{"key": "rate_differential_2y", "label": "Taux 2 ans US-EUR", "weight": 0.85, "keywords": ["taux 2 ans","Bund 2Y","Treasury 2Y","swap","différentiel de taux","2Y spread"]},
{"key": "inflation_differential", "label": "Inflation US vs EUR", "weight": 0.75, "keywords": ["CPI","inflation","PCE","HICP","zone euro","inflation différentiel"]},
{"key": "dxy", "label": "Dollar Index DXY", "weight": 0.7, "keywords": ["Dollar","DXY","dollar index","USD","dollar fort","faible dollar"]}
{"key": "fed_ecb_differential", "label": "Différentiel Fed/BCE", "type": "event_calendar", "weight": 0.95, "keywords": ["BCE","ECB","Fed","FOMC","différentiel","taux directeurs","politique monétaire","rate differential"]},
{"key": "rate_differential_2y", "label": "Taux 2 ans US-EUR", "type": "technical", "weight": 0.85, "keywords": ["taux 2 ans","Bund 2Y","Treasury 2Y","swap","différentiel de taux","2Y spread"]},
{"key": "inflation_differential", "label": "Inflation US vs EUR", "type": "fundamental", "weight": 0.75, "keywords": ["CPI","inflation","PCE","HICP","zone euro","inflation différentiel"]},
{"key": "dxy", "label": "Dollar Index DXY", "type": "technical", "weight": 0.7, "keywords": ["Dollar","DXY","dollar index","USD","dollar fort","faible dollar"]}
],
"regime_labels": ["EUR Strength (ECB Hawkish)", "USD Strength (Fed Hawkish)", "Risk-On EUR Rally", "Risk-Off USD Safe", "Range"],
"event_keywords": ["ECB","FOMC","CPI","EUR","Dollar","Fed","Eurozone","BCE"],
@@ -281,10 +281,10 @@
"description": "Taux spot Dollar américain / Yen japonais",
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": false },
"drivers": [
{"key": "boj_policy", "label": "BOJ Policy", "weight": 0.95, "keywords": ["BOJ","Banque du Japon","Ueda","YCC","yield curve control","politique monétaire japonaise","BOJ rate","JGB"]},
{"key": "rate_diff_us_jp","label": "Différentiel US-JP","weight": 0.9, "keywords": ["différentiel US-Japon","JGB","UST","rendements US-JP","US Japan spread","10Y spread"]},
{"key": "fed_policy", "label": "Fed Policy", "weight": 0.85, "keywords": ["Fed","FOMC","taux directeur US","hawkish Fed","Fed hike","Federal Reserve"]},
{"key": "risk_appetite", "label": "Risk Appetite / Carry","weight": 0.7, "keywords": ["risk-off","carry trade","yen safe haven","crise","carry unwind","yen refuge"]}
{"key": "boj_policy", "label": "BOJ Policy", "type": "event_calendar", "weight": 0.95, "keywords": ["BOJ","Banque du Japon","Ueda","YCC","yield curve control","politique monétaire japonaise","BOJ rate","JGB"]},
{"key": "rate_diff_us_jp","label": "Différentiel US-JP","type": "technical", "weight": 0.9, "keywords": ["différentiel US-Japon","JGB","UST","rendements US-JP","US Japan spread","10Y spread"]},
{"key": "fed_policy", "label": "Fed Policy", "type": "event_calendar", "weight": 0.85, "keywords": ["Fed","FOMC","taux directeur US","hawkish Fed","Fed hike","Federal Reserve"]},
{"key": "risk_appetite", "label": "Risk Appetite / Carry","type": "sentiment", "weight": 0.7, "keywords": ["risk-off","carry trade","yen safe haven","crise","carry unwind","yen refuge"]}
],
"regime_labels": ["Carry Trade On", "BOJ Tightening Surprise", "Risk-Off JPY Safe Haven", "Dollar Bull", "YCC Adjustment"],
"event_keywords": ["BOJ","Fed","Japan","YCC","Carry","JPY","Yen","taux Japon"],
@@ -303,10 +303,10 @@
"description": "Taux spot Livre Sterling / Dollar américain",
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": false },
"drivers": [
{"key": "boe_policy", "label": "BOE Policy", "weight": 0.9, "keywords": ["BOE","Bank of England","Bailey","taux UK","Royaume-Uni","Angleterre","UK rate","BOE meeting"]},
{"key": "uk_inflation", "label": "Inflation UK", "weight": 0.8, "keywords": ["inflation UK","CPI UK","CPIH","RPI","inflation Angleterre"]},
{"key": "uk_growth", "label": "Croissance UK", "weight": 0.75, "keywords": ["GDP UK","PMI UK","croissance UK","récession UK","UK economy","UK GDP"]},
{"key": "political_risk","label": "Risque Politique UK","weight": 0.65, "keywords": ["politique UK","budget UK","Labour","Tories","fiscal UK","gilt","UK budget","Reeves"]}
{"key": "boe_policy", "label": "BOE Policy", "type": "event_calendar", "weight": 0.9, "keywords": ["BOE","Bank of England","Bailey","taux UK","Royaume-Uni","Angleterre","UK rate","BOE meeting"]},
{"key": "uk_inflation", "label": "Inflation UK", "type": "event_calendar", "weight": 0.8, "keywords": ["inflation UK","CPI UK","CPIH","RPI","inflation Angleterre"]},
{"key": "uk_growth", "label": "Croissance UK", "type": "event_calendar", "weight": 0.75, "keywords": ["GDP UK","PMI UK","croissance UK","récession UK","UK economy","UK GDP"]},
{"key": "political_risk","label": "Risque Politique UK","type": "geopolitical", "weight": 0.65, "keywords": ["politique UK","budget UK","Labour","Tories","fiscal UK","gilt","UK budget","Reeves"]}
],
"regime_labels": ["GBP Strength (BOE Hawkish)", "USD Dominance", "Political Risk GBP", "UK Recession Pressure", "Range"],
"event_keywords": ["BOE","Fed","UK","Britain","GBP","Pound","UK CPI"],
@@ -325,10 +325,10 @@
"description": "iPath VIX Short-Term Futures ETN — proxy de la peur et de la volatilité implicite",
"chart": { "ma_periods": [10, 20, 50], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
"drivers": [
{"key": "market_stress", "label": "Stress de Marché", "weight": 0.9, "keywords": ["krach","crash","crise","choc","panic","volatilité","vol spike","selloff","capitulation","contagion"]},
{"key": "risk_appetite", "label": "Peur / Risk-Off", "weight": 0.85, "keywords": ["risk-off","risk-on","sentiment","fear","peur","greed","appétit risque","VIX spike"]},
{"key": "options_demand", "label": "Demande Options (Puts)", "weight": 0.75, "keywords": ["options","puts","hedging","protection","skew","IV","couverture","implied vol","volatilité implicite"]},
{"key": "macro_uncertainty", "label": "Incertitude Macro", "weight": 0.7, "keywords": ["FOMC","surprise","incertitude","géopolitique","macro","inflation choc","data surprise"]}
{"key": "market_stress", "label": "Stress de Marché", "type": "geopolitical", "weight": 0.9, "keywords": ["krach","crash","crise","choc","panic","volatilité","vol spike","selloff","capitulation","contagion"]},
{"key": "risk_appetite", "label": "Peur / Risk-Off", "type": "sentiment", "weight": 0.85, "keywords": ["risk-off","risk-on","sentiment","fear","peur","greed","appétit risque","VIX spike"]},
{"key": "options_demand", "label": "Demande Options (Puts)", "type": "technical", "weight": 0.75, "keywords": ["options","puts","hedging","protection","skew","IV","couverture","implied vol","volatilité implicite"]},
{"key": "macro_uncertainty", "label": "Incertitude Macro", "type": "event_calendar", "weight": 0.7, "keywords": ["FOMC","surprise","incertitude","géopolitique","macro","inflation choc","data surprise"]}
],
"regime_labels": ["Low Vol Complacency", "Vol Spike Crisis", "Vol Expansion", "Vol Compression", "Neutral"],
"event_keywords": ["VIX","Volatility","Crisis","FOMC","Market Stress","peur","vol spike"],
@@ -347,11 +347,11 @@
"description": "Apple Inc. — ecosystème consommateur tech et services",
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
"drivers": [
{"key": "iphone_cycle", "label": "Cycle iPhone", "weight": 0.85, "keywords": ["iPhone","Apple","AAPL earnings","supercycle","launch","iPhone 16","hardware"]},
{"key": "china_sales", "label": "Ventes Chine", "weight": 0.8, "keywords": ["Chine","China","Huawei","ban Apple","restrictions Apple","China sales","marché chinois"]},
{"key": "ai_integration","label": "Apple Intelligence / IA","weight": 0.75, "keywords": ["Apple Intelligence","AI","IA","Siri","machine learning","IA Apple","generative AI"]},
{"key": "margins", "label": "Services & Marges", "weight": 0.7, "keywords": ["services","App Store","iCloud","marge Apple","services revenue","abonnements"]},
{"key": "fed_policy", "label": "Valorisation / Taux", "weight": 0.65, "keywords": ["Fed","taux","valorisation","taux d'intérêt","discount rate"]}
{"key": "iphone_cycle", "label": "Cycle iPhone", "type": "report", "weight": 0.85, "keywords": ["iPhone","Apple","AAPL earnings","supercycle","launch","iPhone 16","hardware"]},
{"key": "china_sales", "label": "Ventes Chine", "type": "geopolitical", "weight": 0.8, "keywords": ["Chine","China","Huawei","ban Apple","restrictions Apple","China sales","marché chinois"]},
{"key": "ai_integration","label": "Apple Intelligence / IA","type": "fundamental", "weight": 0.75, "keywords": ["Apple Intelligence","AI","IA","Siri","machine learning","IA Apple","generative AI"]},
{"key": "margins", "label": "Services & Marges", "type": "report", "weight": 0.7, "keywords": ["services","App Store","iCloud","marge Apple","services revenue","abonnements"]},
{"key": "fed_policy", "label": "Valorisation / Taux", "type": "event_calendar", "weight": 0.65, "keywords": ["Fed","taux","valorisation","taux d'intérêt","discount rate"]}
],
"regime_labels": ["Growth Bull", "Rate Squeeze", "Earnings Beat", "Tech Selloff", "Neutral"],
"event_keywords": ["Apple Earnings","iPhone","Services","China","Fed","AI","AAPL"],
@@ -370,11 +370,11 @@
"description": "NVIDIA Corporation — leader mondial des GPU pour IA et data centers",
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
"drivers": [
{"key": "ai_capex_cycle", "label": "Capex IA / Hyperscalers","weight": 0.95, "keywords": ["IA","AI","data center","capex","Nvidia","GPU","H100","Blackwell","NVLink","hyperscalers","infrastructure IA"]},
{"key": "earnings_growth", "label": "Croissance Bénéfices", "weight": 0.9, "keywords": ["NVDA earnings","résultats Nvidia","revenues","data center revenue","beat","guidance"]},
{"key": "data_center_demand","label": "Demande Data Center", "weight": 0.85, "keywords": ["Amazon AWS","Microsoft Azure","Google Cloud","data center","cloud","computing","GPU demand"]},
{"key": "export_restrictions","label": "Restrictions Export", "weight": 0.7, "keywords": ["export control","restrictions export","Chine","China ban","BIS","CISA","H20","chip ban"]},
{"key": "rates", "label": "Taux / Valorisation", "weight": 0.6, "keywords": ["taux","valorisation","growth stock","multiples","discount rate","Fed"]}
{"key": "ai_capex_cycle", "label": "Capex IA / Hyperscalers","type": "fundamental", "weight": 0.95, "keywords": ["IA","AI","data center","capex","Nvidia","GPU","H100","Blackwell","NVLink","hyperscalers","infrastructure IA"]},
{"key": "earnings_growth", "label": "Croissance Bénéfices", "type": "report", "weight": 0.9, "keywords": ["NVDA earnings","résultats Nvidia","revenues","data center revenue","beat","guidance"]},
{"key": "data_center_demand","label": "Demande Data Center", "type": "fundamental", "weight": 0.85, "keywords": ["Amazon AWS","Microsoft Azure","Google Cloud","data center","cloud","computing","GPU demand"]},
{"key": "export_restrictions","label": "Restrictions Export", "type": "geopolitical", "weight": 0.7, "keywords": ["export control","restrictions export","Chine","China ban","BIS","CISA","H20","chip ban"]},
{"key": "rates", "label": "Taux / Valorisation", "type": "event_calendar", "weight": 0.6, "keywords": ["taux","valorisation","growth stock","multiples","discount rate","Fed"]}
],
"regime_labels": ["AI Bull Run", "AI Hype Correction", "Earnings Catalyst", "Rate Compression", "Consolidation"],
"event_keywords": ["NVIDIA Earnings","AI","Data Center","Export","GPU","NVDA","Blackwell"],
@@ -393,10 +393,10 @@
"description": "The Goldman Sachs Group — banque d'investissement globale",
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
"drivers": [
{"key": "yield_curve", "label": "Courbe des Taux", "weight": 0.85, "keywords": ["courbe des taux","2Y-10Y","steepening","inversion","yield curve","10Y-2Y","pente"]},
{"key": "ma_activity", "label": "M&A / Activité Deals", "weight": 0.8, "keywords": ["M&A","fusion-acquisition","LBO","IPO","deal","advisory","boutique","transaction"]},
{"key": "rates", "label": "Taux d'Intérêt", "weight": 0.8, "keywords": ["taux","Fed","intérêts","NIM","net interest margin","Fed rate","interest rates"]},
{"key": "credit_conditions","label": "Conditions de Crédit", "weight": 0.75, "keywords": ["crédit","spreads","conditions financières","leverage loans","credit tightening"]}
{"key": "yield_curve", "label": "Courbe des Taux", "type": "technical", "weight": 0.85, "keywords": ["courbe des taux","2Y-10Y","steepening","inversion","yield curve","10Y-2Y","pente"]},
{"key": "ma_activity", "label": "M&A / Activité Deals", "type": "fundamental", "weight": 0.8, "keywords": ["M&A","fusion-acquisition","LBO","IPO","deal","advisory","boutique","transaction"]},
{"key": "rates", "label": "Taux d'Intérêt", "type": "event_calendar", "weight": 0.8, "keywords": ["taux","Fed","intérêts","NIM","net interest margin","Fed rate","interest rates"]},
{"key": "credit_conditions","label": "Conditions de Crédit", "type": "fundamental", "weight": 0.75, "keywords": ["crédit","spreads","conditions financières","leverage loans","credit tightening"]}
],
"regime_labels": ["Finance Bull", "Recession Fear", "Yield Curve Steepen", "M&A Boom", "Range"],
"event_keywords": ["Goldman","Financials","Yield Curve","M&A","Banking","Fed","Credit"],
@@ -415,11 +415,11 @@
"description": "Exxon Mobil Corporation — major pétrolier intégré mondial",
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
"drivers": [
{"key": "oil_price", "label": "Prix du Pétrole", "weight": 0.9, "keywords": ["WTI","Brent","pétrole","crude","oil price","prix pétrole","cours pétrole"]},
{"key": "global_demand", "label": "Demande Mondiale", "weight": 0.8, "keywords": ["demande mondiale","China demand","IEA","croissance","global demand","reprise"]},
{"key": "opec_policy", "label": "OPEP+ Production", "weight": 0.75, "keywords": ["OPEC","OPEP","quotas","production","Saudi","Arabie Saoudite","réduction"]},
{"key": "capex", "label": "Capex / Investissement","weight": 0.6, "keywords": ["capex","investissement","forage","upstream","exploration","rig count"]},
{"key": "dollar", "label": "Dollar Index", "weight": 0.6, "keywords": ["Dollar","DXY","USD"]}
{"key": "oil_price", "label": "Prix du Pétrole", "type": "fundamental", "weight": 0.9, "keywords": ["WTI","Brent","pétrole","crude","oil price","prix pétrole","cours pétrole"]},
{"key": "global_demand", "label": "Demande Mondiale", "type": "fundamental", "weight": 0.8, "keywords": ["demande mondiale","China demand","IEA","croissance","global demand","reprise"]},
{"key": "opec_policy", "label": "OPEP+ Production", "type": "event_calendar", "weight": 0.75, "keywords": ["OPEC","OPEP","quotas","production","Saudi","Arabie Saoudite","réduction"]},
{"key": "capex", "label": "Capex / Investissement","type": "fundamental", "weight": 0.6, "keywords": ["capex","investissement","forage","upstream","exploration","rig count"]},
{"key": "dollar", "label": "Dollar Index", "type": "technical", "weight": 0.6, "keywords": ["Dollar","DXY","USD"]}
],
"regime_labels": ["Energy Bull", "Demand Slowdown", "OPEC Benefit", "Green Transition Drag", "Range"],
"event_keywords": ["Oil","OPEC","Energy","XOM","EIA","Crude","Exxon","WTI"],
@@ -438,11 +438,11 @@
"description": "Bitcoin — premier actif décentralisé, indicateur de liquidité et Risk-On",
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
"drivers": [
{"key": "global_liquidity","label": "Liquidité Mondiale", "weight": 0.85, "keywords": ["liquidité mondiale","M2","Fed","QE","QT","conditions financières","liquidity","money supply"]},
{"key": "etf_flows", "label": "Flux ETF Bitcoin", "weight": 0.85, "keywords": ["ETF Bitcoin","spot BTC ETF","BlackRock","Fidelity","flux ETF","ETF approval","IBIT","FBTC"]},
{"key": "risk_appetite", "label": "Sentiment Risk-On", "weight": 0.8, "keywords": ["risk-on","risk-off","crypto","sentiment","appétit pour le risque","halving","cycle crypto"]},
{"key": "regulatory", "label": "Réglementation Crypto", "weight": 0.7, "keywords": ["réglementation crypto","SEC","CFTC","crypto ban","ETF approval","régulation","MiCA"]},
{"key": "dollar", "label": "Dollar Index", "weight": 0.65, "keywords": ["Dollar","DXY","USD","dédollarisation"]}
{"key": "global_liquidity","label": "Liquidité Mondiale", "type": "fundamental", "weight": 0.85, "keywords": ["liquidité mondiale","M2","Fed","QE","QT","conditions financières","liquidity","money supply"]},
{"key": "etf_flows", "label": "Flux ETF Bitcoin", "type": "report", "weight": 0.85, "keywords": ["ETF Bitcoin","spot BTC ETF","BlackRock","Fidelity","flux ETF","ETF approval","IBIT","FBTC"]},
{"key": "risk_appetite", "label": "Sentiment Risk-On", "type": "sentiment", "weight": 0.8, "keywords": ["risk-on","risk-off","crypto","sentiment","appétit pour le risque","halving","cycle crypto"]},
{"key": "regulatory", "label": "Réglementation Crypto", "type": "geopolitical", "weight": 0.7, "keywords": ["réglementation crypto","SEC","CFTC","crypto ban","ETF approval","régulation","MiCA"]},
{"key": "dollar", "label": "Dollar Index", "type": "technical", "weight": 0.65, "keywords": ["Dollar","DXY","USD","dédollarisation"]}
],
"regime_labels": ["Crypto Bull (ETF Inflows)", "Risk-Off Crypto Selloff", "Liquidity Expansion", "Regulatory Fear", "Consolidation"],
"event_keywords": ["Bitcoin","BTC","Crypto","ETF","Fed","Liquidity","Regulation","halving"],

View File

@@ -226,6 +226,22 @@ def bootstrap_events() -> Dict[str, Any]:
return {"seeded": count, "status": "ok" if count > 0 else "already_seeded"}
@router.post("/bootstrap-eco")
def bootstrap_eco(force: bool = False) -> Dict[str, Any]:
"""
Seed the market_events table with economic calendar events (2020-2026).
Includes FOMC, NFP, CPI, GDP, ISM, BOJ, ECB, BOE with expected/actual/surprise fields.
Pass ?force=true to delete existing eco events and re-insert.
"""
try:
from services.eco_calendar_bootstrap import bootstrap_eco_events
result = bootstrap_eco_events(force=force)
return result
except Exception as e:
logger.error(f"[Timeline] bootstrap_eco failed: {e}")
raise HTTPException(500, str(e))
@router.post("/bootstrap-ma")
async def bootstrap_ma() -> Dict[str, Any]:
"""Detect MA ruptures on 5 key underlyings and generate historical market events via GPT."""

View File

@@ -753,8 +753,13 @@ def init_db():
)""")
# Add columns to existing DBs (idempotent via catch)
for _col, _def in [
("absorption_pct", "REAL DEFAULT NULL"),
("relevant_indicators", "TEXT DEFAULT '[]'"),
("absorption_pct", "REAL DEFAULT NULL"),
("relevant_indicators", "TEXT DEFAULT '[]'"),
("expected_value", "TEXT DEFAULT NULL"),
("actual_value", "TEXT DEFAULT NULL"),
("surprise_pct", "REAL DEFAULT NULL"),
("unit", "TEXT DEFAULT NULL"),
("sub_type", "TEXT DEFAULT NULL"),
]:
try:
c.execute(f"ALTER TABLE market_events ADD COLUMN {_col} {_def}")
@@ -4480,12 +4485,16 @@ def save_market_event(ev: Dict[str, Any]) -> int:
try:
cur = conn.execute("""INSERT INTO market_events
(name, start_date, end_date, level, category, description, market_impact,
affected_assets, impact_score, parent_event_id)
VALUES (?,?,?,?,?,?,?,?,?,?)""",
affected_assets, impact_score, parent_event_id,
expected_value, actual_value, surprise_pct, unit, sub_type, absorption_pct)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
(ev["name"], ev["start_date"], ev.get("end_date"),
ev["level"], ev.get("category", "macro"), ev.get("description", ""),
ev.get("market_impact", ""), json.dumps(ev.get("affected_assets", [])),
ev.get("impact_score", 0.5), ev.get("parent_event_id")))
ev.get("impact_score", 0.5), ev.get("parent_event_id"),
ev.get("expected_value"), ev.get("actual_value"),
ev.get("surprise_pct"), ev.get("unit"), ev.get("sub_type"),
ev.get("absorption_pct")))
conn.commit()
return cur.lastrowid
finally:

View File

@@ -0,0 +1,586 @@
"""
Bootstrap historical economic calendar events into market_events DB.
Covers 2020-2026 with actual vs expected values and surprise metrics.
Categories: FOMC, NFP, CPI, GDP, ISM/PMI, BOJ, ECB, BOE.
Convention: surprise_pct positive = bullish surprise, negative = bearish.
"""
import logging
from typing import List, Dict, Any
logger = logging.getLogger(__name__)
# Shared asset groups
_EQ = ["SPY", "QQQ", "IWM"]
_RATES = ["TLT", "HYG"]
_FX = ["EURUSD=X", "USDJPY=X", "GBPUSD=X"]
_SAFE = ["GLD", "VXX"]
_ALL = _EQ + _RATES + ["EURUSD=X", "GLD", "BTC-USD"]
ECO_EVENTS: List[Dict[str, Any]] = [
# ══════════════════════════════════════════════════════════
# FOMC — DECISIONS DE TAUX (2020-2025)
# ══════════════════════════════════════════════════════════
{"name": "FOMC — Urgence COVID : coupe -50pb (entre-séances)",
"start_date": "2020-03-03", "level": "long", "category": "calendar", "sub_type": "FOMC",
"expected_value": "-25pb", "actual_value": "-50pb", "surprise_pct": -12,
"unit": "pb", "impact_score": 0.95, "absorption_pct": 100,
"affected_assets": _ALL,
"description": "Fed coupe -50pb en urgence hors-séance (premier depuis 2008). COVID commence à impacter les marchés. S&P500 avait perdu -10% en une semaine. Marché attendait -25pb."},
{"name": "FOMC — Urgence COVID : taux à 0% + QE illimité",
"start_date": "2020-03-16", "level": "long", "category": "calendar", "sub_type": "FOMC",
"expected_value": "-50pb", "actual_value": "-100pb + QE∞", "surprise_pct": -20,
"unit": "pb", "impact_score": 0.98, "absorption_pct": 100,
"affected_assets": _ALL + ["USO", "EEM"],
"description": "FOMC coupe -100pb en urgence, amène taux à 0-0.25%. Lance QE illimité (Treasuries + MBS). Bazooka monétaire COVID. Circuit breakers activés ce matin. Le tout en un seul dimanche soir."},
{"name": "FOMC — Taper talk (dot plot hawkish inattendu)",
"start_date": "2021-06-16", "level": "medium", "category": "calendar", "sub_type": "FOMC",
"expected_value": "stable", "actual_value": "2 hausses en 2023 signalées", "surprise_pct": -18,
"unit": "signal", "impact_score": 0.82, "absorption_pct": 100,
"affected_assets": _EQ + _RATES + ["GLD", "EURUSD=X"],
"description": "Dot plot de juin 2021 révèle que 13 des 18 membres FOMC prévoient 2+ hausses d'ici 2023. Surprise hawkish. Gold -5% en 2 jours. DXY +1%. Prémices du pivot narratif 2022."},
{"name": "FOMC — Début réduction QE (tapering annoncé)",
"start_date": "2021-11-03", "level": "medium", "category": "calendar", "sub_type": "FOMC",
"expected_value": "taper annoncé", "actual_value": "$15Md/mois taper", "surprise_pct": 0,
"unit": "signal", "impact_score": 0.78, "absorption_pct": 100,
"affected_assets": _EQ + _RATES + ["GLD", "EURUSD=X"],
"description": "Fed annonce début du tapering à -$15Md/mois (Nov-Déc 2021). Largement attendu. Marché absorbe sans choc majeur. SPY au plus haut. Signale la fin de l'ère QE COVID."},
{"name": "FOMC — Accélération taper + 3 hausses 2022 signalées",
"start_date": "2021-12-15", "level": "long", "category": "calendar", "sub_type": "FOMC",
"expected_value": "taper accéléré", "actual_value": "-$30Md/mois + 3 hausses 2022", "surprise_pct": -15,
"unit": "signal", "impact_score": 0.85, "absorption_pct": 100,
"affected_assets": _ALL,
"description": "Fed double la vitesse du tapering (-$30Md/mois) et signale 3 hausses en 2022 vs 1 attendue. Inflation à 6.8% force Powell à pivoter hawkish. Dollar se renforce. Gold résiste."},
{"name": "FOMC — Premier hike cycle : +25pb (premier depuis 2018)",
"start_date": "2022-03-16", "level": "long", "category": "calendar", "sub_type": "FOMC",
"expected_value": "+25pb", "actual_value": "+25pb (cibles 0.25-0.50%)", "surprise_pct": 0,
"unit": "pb", "impact_score": 0.88, "absorption_pct": 100,
"affected_assets": _ALL,
"description": "Premier hike depuis déc 2018. Taux cible 0.25-0.50%. Ukraine envahie 3 semaines avant. Dot plot : 7 hausses totales en 2022. Powell dit 'l'économie est forte'. Début du cycle de resserrement le plus agressif depuis 40 ans."},
{"name": "FOMC — +50pb (premier double hike depuis 2000)",
"start_date": "2022-05-04", "level": "medium", "category": "calendar", "sub_type": "FOMC",
"expected_value": "+50pb", "actual_value": "+50pb + QT annoncé", "surprise_pct": -5,
"unit": "pb", "impact_score": 0.82, "absorption_pct": 100,
"affected_assets": _ALL,
"description": "+50pb, plus forte hausse depuis 2000. Taux 0.75-1.00%. Annonce début QT en juin (-$95Md/mois). CPI à 8.3%. Conférence de presse : Powell exclut +75pb pour la prochaine réunion (retournement de veste en juin)."},
{"name": "FOMC — Choc +75pb : surprise hawkish (attendu +50pb)",
"start_date": "2022-06-15", "level": "long", "category": "calendar", "sub_type": "FOMC",
"expected_value": "+50pb", "actual_value": "+75pb", "surprise_pct": -25,
"unit": "pb", "impact_score": 0.96, "absorption_pct": 100,
"affected_assets": _ALL + ["EEM", "USO"],
"description": "Fed remonte +75pb, plus fort depuis 1994. FOMC dit +50pb en mai, mais CPI jun 9.1% force la main. Dot plot : taux terminal à 3.4% fin 2022. SPY -3.5%, TLT -2%. Début du bear market officiel."},
{"name": "FOMC — +75pb (2e consécutif)",
"start_date": "2022-07-27", "level": "medium", "category": "calendar", "sub_type": "FOMC",
"expected_value": "+75pb", "actual_value": "+75pb (taux 2.25-2.50%)", "surprise_pct": 0,
"unit": "pb", "impact_score": 0.82, "absorption_pct": 100,
"affected_assets": _EQ + _RATES + ["GLD", "EURUSD=X"],
"description": "2e +75pb consécutif. Taux 2.25-2.50%. Powell dit 'probablement approprié de ralentir'. Marché interprète comme dovish → rally court terme. GDP Q2 -0.9% annoncé le même jour (récession technique)."},
{"name": "FOMC — +75pb + dot plot terminal 4.6% (hawkish peak)",
"start_date": "2022-09-21", "level": "long", "category": "calendar", "sub_type": "FOMC",
"expected_value": "+75pb", "actual_value": "+75pb + terminal 4.6%", "surprise_pct": -20,
"unit": "pb", "impact_score": 0.93, "absorption_pct": 100,
"affected_assets": _ALL,
"description": "3e +75pb. Dot plot choque le marché: taux terminal révisé à 4.6% pour 2023. 'Higher for longer' officiel. Dollar index au plus haut 20 ans. SPY perd -5% semaine. Tous les actifs se vendent."},
{"name": "FOMC — +75pb (4e consécutif)",
"start_date": "2022-11-02", "level": "medium", "category": "calendar", "sub_type": "FOMC",
"expected_value": "+75pb", "actual_value": "+75pb (taux 3.75-4.00%)", "surprise_pct": 0,
"unit": "pb", "impact_score": 0.85, "absorption_pct": 100,
"affected_assets": _ALL,
"description": "4e +75pb consécutif. Mais communication laisse entrevoir un ralentissement à venir. 'Timing et ampleur des hausses futures' → signal de pivot. Marché commence à pricer pivot 2023."},
{"name": "FOMC — Décélération +50pb (fin des gros moves)",
"start_date": "2022-12-14", "level": "medium", "category": "calendar", "sub_type": "FOMC",
"expected_value": "+50pb", "actual_value": "+50pb + terminal 5.1%", "surprise_pct": -8,
"unit": "pb", "impact_score": 0.80, "absorption_pct": 100,
"affected_assets": _EQ + _RATES + ["GLD", "EURUSD=X"],
"description": "Décélération à +50pb mais dot plot hawkish : terminal révisé à 5.1% (vs 4.6% en sept). SPY -2.5% sur la semaine. Marché était trop optimiste sur le pivot. Gold résistant."},
{"name": "FOMC — +25pb (déceleration confirmée)",
"start_date": "2023-02-01", "level": "medium", "category": "calendar", "sub_type": "FOMC",
"expected_value": "+25pb", "actual_value": "+25pb (taux 4.50-4.75%)", "surprise_pct": +5,
"unit": "pb", "impact_score": 0.75, "absorption_pct": 100,
"affected_assets": _EQ + _RATES + ["GLD", "EURUSD=X"],
"description": "Premier +25pb depuis mars 2022. Powell signale 'quelques hausses supplémentaires'. Ton moins hawkish qu'attendu. SPY +1.5%. BTC rally. Début du discours désinflation 2023."},
{"name": "FOMC — +25pb (avant crise SVB)",
"start_date": "2023-03-22", "level": "medium", "category": "calendar", "sub_type": "FOMC",
"expected_value": "+25pb", "actual_value": "+25pb + doutes sur la suite", "surprise_pct": +5,
"unit": "pb", "impact_score": 0.78, "absorption_pct": 100,
"affected_assets": _EQ + _RATES + ["GLD", "EURUSD=X", "HYG"],
"description": "Réunion post-SVB (faillite 10 mars). Powell dit la crise bancaire remplace 'une ou deux hausses' de taux. Signal de pause proche. Taux 4.75-5.00%. GLD fort sur les craintes bancaires."},
{"name": "FOMC — Dernière hausse du cycle : +25pb",
"start_date": "2023-05-03", "level": "long", "category": "calendar", "sub_type": "FOMC",
"expected_value": "+25pb", "actual_value": "+25pb (taux 5.00-5.25%)", "surprise_pct": 0,
"unit": "pb", "impact_score": 0.82, "absorption_pct": 100,
"affected_assets": _ALL,
"description": "Dernière hausse du cycle de resserrement. Taux au plus haut depuis 2007 : 5.00-5.25%. Powell retire la phrase 'additional firming may be appropriate'. Signal de pause officieux. Marché voit le pic."},
{"name": "FOMC — Première pause du cycle (skip)",
"start_date": "2023-06-14", "level": "long", "category": "calendar", "sub_type": "FOMC",
"expected_value": "+25pb ou pause", "actual_value": "pause (hold)", "surprise_pct": +8,
"unit": "signal", "impact_score": 0.80, "absorption_pct": 100,
"affected_assets": _ALL,
"description": "Première pause depuis mars 2022. Taux stable à 5.00-5.25%. Mais dot plot révise à 5.6% le terminal : une hausse de plus attendue en 2023. SPY tient les gains. 'Higher for longer' s'installe."},
{"name": "FOMC — Pivot historique : première baisse -50pb (surprise)",
"start_date": "2024-09-18", "level": "long", "category": "calendar", "sub_type": "FOMC",
"expected_value": "-25pb", "actual_value": "-50pb", "surprise_pct": +20,
"unit": "pb", "impact_score": 0.94, "absorption_pct": 100,
"affected_assets": _ALL + ["EEM", "USO"],
"description": "Premier cut depuis mars 2020. La Fed surprend avec -50pb alors que le consensus était -25pb. Taux 4.75-5.00%. Powell: 'Cette décision témoigne de notre confiance croissante'. SPY +1%, GLD +1.2%, BTC +3%. Début du cycle d'assouplissement 2024."},
{"name": "FOMC — Deuxième baisse -25pb du cycle",
"start_date": "2024-11-07", "level": "medium", "category": "calendar", "sub_type": "FOMC",
"expected_value": "-25pb", "actual_value": "-25pb (taux 4.50-4.75%)", "surprise_pct": 0,
"unit": "pb", "impact_score": 0.72, "absorption_pct": 100,
"affected_assets": _EQ + _RATES + ["GLD", "EURUSD=X"],
"description": "2e coupe du cycle. Lendemain des élections Trump → incertitude tarifaire. Fed ignore les résultats d'élection. Taux 4.50-4.75%. Marché commence à se questionner sur 2025."},
{"name": "FOMC — Dot plot hawkish : 2 cuts 2025 seulement",
"start_date": "2024-12-18", "level": "long", "category": "calendar", "sub_type": "FOMC",
"expected_value": "-25pb + 4 cuts 2025", "actual_value": "-25pb + 2 cuts 2025", "surprise_pct": -18,
"unit": "pb", "impact_score": 0.87, "absorption_pct": 100,
"affected_assets": _ALL,
"description": "3e coupe mais dot plot choc : révision de 4 à 2 baisses prévues en 2025. 'Higher for longer' resurgit. SPY -3% sur la séance. TLT -1.5%. Dollar index au plus haut annuel. Correction de fin d'année violente."},
{"name": "FOMC — Hold (incertitude tarifs Trump)",
"start_date": "2025-01-29", "level": "medium", "category": "calendar", "sub_type": "FOMC",
"expected_value": "hold", "actual_value": "hold (taux 4.25-4.50%)", "surprise_pct": 0,
"unit": "pb", "impact_score": 0.70, "absorption_pct": 100,
"affected_assets": _EQ + _RATES + ["GLD", "EURUSD=X"],
"description": "Première réunion de l'ère Trump 2. Fed en pause. Incertitude tarifaire force la prudence. CPI encore au-dessus de 2%. Marché commence à réviser les anticipations de baisses 2025 à la baisse."},
{"name": "FOMC — Hold (choc tarifaire Liberation Day)",
"start_date": "2025-05-07", "level": "medium", "category": "calendar", "sub_type": "FOMC",
"expected_value": "hold", "actual_value": "hold — 'wait and see' tarifaire", "surprise_pct": 0,
"unit": "pb", "impact_score": 0.72, "absorption_pct": 100,
"affected_assets": _EQ + _RATES + ["GLD", "EURUSD=X"],
"description": "Fed en mode attentiste post Liberation Day (tarifs réciproques Trump). Stagflation imminente : inflation tarifaire vs ralentissement croissance. Marché incertain sur timing prochain move."},
# ══════════════════════════════════════════════════════════
# NFP — NON-FARM PAYROLLS (2020-2025)
# ══════════════════════════════════════════════════════════
{"name": "NFP Mars 2020 — Premier choc COVID (-701K)",
"start_date": "2020-04-03", "level": "medium", "category": "calendar", "sub_type": "NFP",
"expected_value": "-100K", "actual_value": "-701K", "surprise_pct": -85,
"unit": "K emplois", "impact_score": 0.88, "absorption_pct": 100,
"affected_assets": _EQ + ["GLD", "TLT", "VXX"],
"description": "NFP mars 2020 : -701K (premier mois plein COVID). Attendu -100K. Début de la destruction d'emplois. Le pire viendra en avril. Chômage : 4.4% vs 3.5% en février."},
{"name": "NFP Avril 2020 — Record absolu destruction emplois (-20.5M)",
"start_date": "2020-05-08", "level": "long", "category": "calendar", "sub_type": "NFP",
"expected_value": "-22000K", "actual_value": "-20537K", "surprise_pct": +8,
"unit": "K emplois", "impact_score": 0.98, "absorption_pct": 100,
"affected_assets": _ALL,
"description": "NFP avril 2020 : -20.537M emplois détruits en un seul mois. Record absolu dans l'histoire moderne. Chômage 14.7%. Légèrement mieux qu'attendu (-22M) = paradoxal rallye. Début du V-shape recovery."},
{"name": "NFP Mai 2020 — Surprise massive de reprise (+2.5M)",
"start_date": "2020-06-05", "level": "long", "category": "calendar", "sub_type": "NFP",
"expected_value": "-8000K", "actual_value": "+2509K", "surprise_pct": +100,
"unit": "K emplois", "impact_score": 0.92, "absorption_pct": 100,
"affected_assets": _EQ + ["GLD", "EURUSD=X", "VXX"],
"description": "NFP mai 2020 : +2.509M ! Attendu -8M. Surprise positive historique. Début de la reprise. SPY +2.6% sur la séance. Fed et marchés reprennent confiance. Mais le taux de chômage mal calculé (mislabeling)."},
{"name": "NFP Avril 2021 — Miss catastrophique (+266K vs +1000K attendu)",
"start_date": "2021-05-07", "level": "medium", "category": "calendar", "sub_type": "NFP",
"expected_value": "+1000K", "actual_value": "+266K", "surprise_pct": -75,
"unit": "K emplois", "impact_score": 0.85, "absorption_pct": 100,
"affected_assets": _EQ + ["TLT", "GLD", "EURUSD=X"],
"description": "NFP avril 2021 : +266K vs +1000K attendu. Miss le plus large de l'histoire récente. Fed et marchés ont du mal à lire le post-COVID. Dollar chute. Or monte. Fed repousse toute discussion de taper."},
{"name": "NFP Octobre 2021 — Forte reprise (+531K)",
"start_date": "2021-11-05", "level": "short", "category": "calendar", "sub_type": "NFP",
"expected_value": "+450K", "actual_value": "+531K", "surprise_pct": +18,
"unit": "K emplois", "impact_score": 0.72, "absorption_pct": 100,
"affected_assets": _EQ + ["TLT", "EURUSD=X"],
"description": "NFP oct 2021 : +531K, révisions haussières. Marché du travail se resserre. Pression salariale commence. Confirme le taper annoncé en novembre. SPY vers ATH."},
{"name": "NFP Janvier 2022 — Beat surprise (+467K vs +150K)",
"start_date": "2022-02-04", "level": "medium", "category": "calendar", "sub_type": "NFP",
"expected_value": "+150K", "actual_value": "+467K", "surprise_pct": +58,
"unit": "K emplois", "impact_score": 0.80, "absorption_pct": 100,
"affected_assets": _EQ + ["TLT", "EURUSD=X", "GLD"],
"description": "NFP janvier 2022 : +467K avec révisions massives à la hausse (+708K révisé pour déc). Marché du travail ultra-chaud. Renforce les arguments hawkish pour mars. TLT -1.5%."},
{"name": "NFP Juin 2022 — Solide malgré craintes récession (+372K)",
"start_date": "2022-07-08", "level": "short", "category": "calendar", "sub_type": "NFP",
"expected_value": "+268K", "actual_value": "+372K", "surprise_pct": +39,
"unit": "K emplois", "impact_score": 0.72, "absorption_pct": 100,
"affected_assets": _EQ + ["TLT", "EURUSD=X"],
"description": "NFP juin 2022 : +372K malgré les craintes de récession (GDP Q1 -1.6%, Q2 estimé négatif). Donne à la Fed les munitions pour continuer les hausses agressives."},
{"name": "NFP Janvier 2023 — Méga-beat choc (+517K vs +185K)",
"start_date": "2023-02-03", "level": "long", "category": "calendar", "sub_type": "NFP",
"expected_value": "+185K", "actual_value": "+517K", "surprise_pct": +79,
"unit": "K emplois", "impact_score": 0.90, "absorption_pct": 100,
"affected_assets": _ALL,
"description": "NFP janvier 2023 : +517K ! 2.8x le consensus. Chômage 3.4% (plus bas depuis 1969). Donne à la Fed l'argument pour des hausses supplémentaires. TLT -1.5%. Or -1%. Dollar +0.8%. Remet en question le 'pivot'."},
{"name": "NFP Juillet 2024 — Choc à la baisse (+114K vs +175K) — Récession panic",
"start_date": "2024-08-02", "level": "long", "category": "calendar", "sub_type": "NFP",
"expected_value": "+175K", "actual_value": "+114K", "surprise_pct": -38,
"unit": "K emplois", "impact_score": 0.92, "absorption_pct": 100,
"affected_assets": _ALL,
"description": "NFP juillet 2024 : +114K, massive miss. Chômage 4.3% (Sahm Rule activée → récession signal). Catalyseur du crash août 2024 (SPY -6%, Nikkei -12%). VIX explose à 65. BOJ avait haussé les taux 2 jours avant → double choc."},
{"name": "NFP Décembre 2024 — Beat fort (+256K vs +155K attendu)",
"start_date": "2025-01-10", "level": "medium", "category": "calendar", "sub_type": "NFP",
"expected_value": "+155K", "actual_value": "+256K", "surprise_pct": +65,
"unit": "K emplois", "impact_score": 0.80, "absorption_pct": 100,
"affected_assets": _EQ + ["TLT", "EURUSD=X", "GLD"],
"description": "NFP décembre 2024 : +256K, fort beat. Chômage 4.1%. Donne à la Fed un argument supplémentaire pour un 'patient approach'. TLT -1.2%. Repousse les attentes de baisses de taux 2025."},
# ══════════════════════════════════════════════════════════
# CPI — INFLATION US (2021-2025)
# ══════════════════════════════════════════════════════════
{"name": "CPI Mai 2021 — Premier saut inflationniste majeur (5.0%)",
"start_date": "2021-06-10", "level": "long", "category": "calendar", "sub_type": "CPI",
"expected_value": "4.7%", "actual_value": "5.0%", "surprise_pct": -12,
"unit": "% a/a", "impact_score": 0.85, "absorption_pct": 100,
"affected_assets": _ALL,
"description": "CPI mai 2021 : 5.0% a/a, plus haut depuis 2008. Attendu 4.7%. Début de l'ère inflationniste post-COVID. Fed dit 'transitoire'. Gold réagit peu car taux réels encore très négatifs."},
{"name": "CPI Octobre 2021 — 6.2% (plus haut depuis 31 ans)",
"start_date": "2021-11-10", "level": "long", "category": "calendar", "sub_type": "CPI",
"expected_value": "5.8%", "actual_value": "6.2%", "surprise_pct": -18,
"unit": "% a/a", "impact_score": 0.88, "absorption_pct": 100,
"affected_assets": _ALL,
"description": "CPI oct 2021 : 6.2%, plus haut depuis novembre 1990. Attendu 5.8%. 'Transitoire' commence à être abandonné. Powell devra accélérer le taper. TLT -1.5%. Or +0.5% (protection inflation)."},
{"name": "CPI Décembre 2021 — 7.0% (pic 40 ans)",
"start_date": "2022-01-12", "level": "long", "category": "calendar", "sub_type": "CPI",
"expected_value": "7.0%", "actual_value": "7.0%", "surprise_pct": 0,
"unit": "% a/a", "impact_score": 0.85, "absorption_pct": 100,
"affected_assets": _ALL,
"description": "CPI déc 2021 : 7.0% a/a, plus haut depuis 1982. Comme attendu mais confirme la crise inflationniste. Enterre officiellement le mot 'transitoire'. Force la Fed vers un cycle de hausse agressif en 2022."},
{"name": "CPI Janvier 2022 — 7.5% (40 ans de record + miss hawkish)",
"start_date": "2022-02-10", "level": "long", "category": "calendar", "sub_type": "CPI",
"expected_value": "7.3%", "actual_value": "7.5%", "surprise_pct": -20,
"unit": "% a/a", "impact_score": 0.90, "absorption_pct": 100,
"affected_assets": _ALL,
"description": "CPI jan 2022 : 7.5%, au-dessus des 7.3% attendus. 40Y high. James Bullard (St. Louis Fed) appelle à 100pb de hausse d'ici juillet. TLT -1.5%. SPY -1.8%. Le marché prend conscience de l'ampleur du resserrement nécessaire."},
{"name": "CPI Mai 2022 — 8.6% (record 43 ans, miss hawkish)",
"start_date": "2022-06-10", "level": "long", "category": "calendar", "sub_type": "CPI",
"expected_value": "8.3%", "actual_value": "8.6%", "surprise_pct": -22,
"unit": "% a/a", "impact_score": 0.94, "absorption_pct": 100,
"affected_assets": _ALL,
"description": "CPI mai 2022 : 8.6%, plus haut depuis 1981. Attendu 8.3%. Force la Fed à +75pb la semaine suivante (annoncé dimanche soir). SPY -2.9%. Or, parfois hedge inflation, chute aussi car taux réels remontent. Début du bear market 2022."},
{"name": "CPI Juin 2022 — Pic 9.1% (sommet 43 ans, pire surprise hawkish 2022)",
"start_date": "2022-07-13", "level": "long", "category": "calendar", "sub_type": "CPI",
"expected_value": "8.8%", "actual_value": "9.1%", "surprise_pct": -25,
"unit": "% a/a", "impact_score": 0.97, "absorption_pct": 100,
"affected_assets": _ALL + ["EEM", "USO"],
"description": "CPI juin 2022 : 9.1% a/a, plus haut depuis novembre 1981. Pire surprise de l'année. Oblige la Fed à considérer +100pb en juillet (finalement +75pb). Dollar index au plus haut, GLD sous pression malgré l'inflation (taux réels montent)."},
{"name": "CPI Octobre 2022 — 7.7% (inflexion baissière, grosse surprise positive)",
"start_date": "2022-11-10", "level": "long", "category": "calendar", "sub_type": "CPI",
"expected_value": "7.9%", "actual_value": "7.7%", "surprise_pct": +20,
"unit": "% a/a", "impact_score": 0.93, "absorption_pct": 100,
"affected_assets": _ALL,
"description": "CPI oct 2022 : 7.7%, première inflexion claire sous les attentes. Attendu 7.9%. Rally historique : SPY +5.5%, NAS +7.4%, GLD +2%, BTC +10%, Dollar -2%. Début du pivot narratif et de la désinflation. Point d'inflexion majeur."},
{"name": "CPI Décembre 2022 — 6.5% (désinflation confirmée)",
"start_date": "2023-01-12", "level": "medium", "category": "calendar", "sub_type": "CPI",
"expected_value": "6.5%", "actual_value": "6.5%", "surprise_pct": 0,
"unit": "% a/a", "impact_score": 0.78, "absorption_pct": 100,
"affected_assets": _EQ + _RATES + ["GLD", "EURUSD=X"],
"description": "CPI déc 2022 : 6.5% a/a. Confirme la tendance désinflationniste. 6e mois consécutif de baisse depuis le pic de 9.1%. Marché confirme que le pire est derrière. Bon pour les actifs risqués et obligations."},
{"name": "CPI Mars 2024 — 3.5% (surprise hawkish, désinflation bloquée)",
"start_date": "2024-04-10", "level": "medium", "category": "calendar", "sub_type": "CPI",
"expected_value": "3.4%", "actual_value": "3.5%", "surprise_pct": -15,
"unit": "% a/a", "impact_score": 0.82, "absorption_pct": 100,
"affected_assets": _ALL,
"description": "CPI mars 2024 : 3.5%, au-dessus des 3.4% attendus. 3e mois consécutif au-dessus des attentes. Le 'last mile' est difficile. TLT -1.5%, taux 10Y → 4.5%. Report des baisses Fed de juin à septembre. SPY -1%."},
{"name": "CPI Mars 2025 — 2.4% (impact tariffs Trump commence à apparaître)",
"start_date": "2025-04-10", "level": "medium", "category": "calendar", "sub_type": "CPI",
"expected_value": "2.5%", "actual_value": "2.4%", "surprise_pct": +10,
"unit": "% a/a", "impact_score": 0.75, "absorption_pct": 100,
"affected_assets": _EQ + _RATES + ["GLD", "EURUSD=X"],
"description": "CPI mars 2025 : 2.4%, légèrement sous les 2.5% attendus. Dernier mois avant l'impact plein des tarifs Trump (annoncés 2 avril). Le marché sait que l'inflation va remonter. Fed en mode wait-and-see."},
# ══════════════════════════════════════════════════════════
# GDP — CROISSANCE US (2020-2025)
# ══════════════════════════════════════════════════════════
{"name": "GDP T1 2020 — Premier choc COVID (-4.8% annualisé)",
"start_date": "2020-04-29", "level": "long", "category": "calendar", "sub_type": "GDP",
"expected_value": "-4.0%", "actual_value": "-4.8%", "surprise_pct": -15,
"unit": "% ann.", "impact_score": 0.88, "absorption_pct": 100,
"affected_assets": _ALL,
"description": "GDP Q1 2020 (estimé) : -4.8% annualisé. 1er choc COVID sur la croissance. Attendu -4.0%. Pire depuis 2008. Mais le marché sait déjà que Q2 sera catastrophique."},
{"name": "GDP T2 2020 — Pire contraction de l'histoire US (-32.9%)",
"start_date": "2020-07-30", "level": "long", "category": "calendar", "sub_type": "GDP",
"expected_value": "-34.7%", "actual_value": "-32.9%", "surprise_pct": +12,
"unit": "% ann.", "impact_score": 0.92, "absorption_pct": 100,
"affected_assets": _ALL,
"description": "GDP Q2 2020 : -32.9% annualisé. Contraction la plus sévère de l'histoire américaine. Légèrement meilleur qu'attendu (-34.7%) → marché monte légèrement. Marché déjà 'pricé' pour le pire → rallye paradoxal."},
{"name": "GDP T3 2020 — Rebond en V historique (+33.1%)",
"start_date": "2020-10-29", "level": "long", "category": "calendar", "sub_type": "GDP",
"expected_value": "+31.0%", "actual_value": "+33.1%", "surprise_pct": +10,
"unit": "% ann.", "impact_score": 0.88, "absorption_pct": 100,
"affected_assets": _EQ + ["EURUSD=X", "GLD"],
"description": "GDP Q3 2020 : +33.1% annualisé. Rebond en V post-COVID historique. Légère beat. Economies restent sous leur potentiel pre-COVID mais la reprise est en marche. Soutient l'élan des marchés."},
{"name": "GDP T4 2021 — Croissance forte (+6.9%, beat) malgré Omicron",
"start_date": "2022-01-27", "level": "medium", "category": "calendar", "sub_type": "GDP",
"expected_value": "+5.5%", "actual_value": "+6.9%", "surprise_pct": +20,
"unit": "% ann.", "impact_score": 0.78, "absorption_pct": 100,
"affected_assets": _EQ + ["TLT", "GLD", "EURUSD=X"],
"description": "GDP Q4 2021 : +6.9%, forte beat. Malgré Omicron et les goulots d'étranglement. Ajoute au scénario 'économie forte = Fed peut hiker'. Taux 10Y remontent. TLT perd -2% sur la semaine."},
{"name": "GDP T1 2022 — Récession surprise (-1.4%, attendu +1.1%)",
"start_date": "2022-04-28", "level": "long", "category": "calendar", "sub_type": "GDP",
"expected_value": "+1.1%", "actual_value": "-1.4%", "surprise_pct": -55,
"unit": "% ann.", "impact_score": 0.88, "absorption_pct": 100,
"affected_assets": _ALL,
"description": "GDP Q1 2022 : -1.4%, première contraction depuis COVID. Attendu +1.1%. Choc : stagflation. Inflation à 8.5% et croissance négative. La Fed va-t-elle hiker malgré la récession? Oui → stagflation trade."},
{"name": "GDP T2 2022 — Récession technique confirmée (-0.9%)",
"start_date": "2022-07-28", "level": "long", "category": "calendar", "sub_type": "GDP",
"expected_value": "+0.4%", "actual_value": "-0.9%", "surprise_pct": -40,
"unit": "% ann.", "impact_score": 0.90, "absorption_pct": 100,
"affected_assets": _ALL,
"description": "GDP Q2 2022 : -0.9%. 2e trimestre négatif consécutif → définition technique de récession. Mais le NBER ne le confirme pas (marché du travail fort). Fed tient le cap hawkish malgré la récession. Jour du +75pb FOMC."},
{"name": "GDP T1 2024 — Miss (1.6% vs 2.4% attendu, stagflation fear)",
"start_date": "2024-04-25", "level": "medium", "category": "calendar", "sub_type": "GDP",
"expected_value": "+2.4%", "actual_value": "+1.6%", "surprise_pct": -35,
"unit": "% ann.", "impact_score": 0.82, "absorption_pct": 100,
"affected_assets": _ALL,
"description": "GDP Q1 2024 : +1.6%, grosse déception vs +2.4% attendu. PCE prix à +3.7% (inflation toujours haute). 'Stagflation light' : croissance ralentit mais inflation reste. SPY -1.5%. Retarde les baisses Fed."},
{"name": "GDP T1 2025 — Contraction (-0.3%) sous choc tarifaire Trump",
"start_date": "2025-04-30", "level": "long", "category": "calendar", "sub_type": "GDP",
"expected_value": "+0.4%", "actual_value": "-0.3%", "surprise_pct": -40,
"unit": "% ann.", "impact_score": 0.90, "absorption_pct": 90,
"affected_assets": _ALL,
"description": "GDP Q1 2025 : -0.3%, 1ère contraction depuis COVID. Imports explosent avant les tarifs Trump (entreprises stockent). Consommation ralentit. SPY -2%. Débat : récession ou stockage temporaire? Incertitude maximale."},
# ══════════════════════════════════════════════════════════
# ISM / PMI — ACTIVITÉ MANUFACTURIÈRE
# ══════════════════════════════════════════════════════════
{"name": "ISM Manufacturing Mars 2020 — Effondrement COVID (49.1)",
"start_date": "2020-04-01", "level": "medium", "category": "calendar", "sub_type": "ISM",
"expected_value": "44.0", "actual_value": "49.1", "surprise_pct": +13,
"unit": "index", "impact_score": 0.80, "absorption_pct": 100,
"affected_assets": _EQ + ["GLD", "USO"],
"description": "ISM Manufacturing mars 2020 : 49.1 (contraction). Meilleur qu'attendu (44.0 consensus). La déstructuration COVID arrive mais plus progressivement que craint. Les services vont bien pire."},
{"name": "ISM Manufacturing Décembre 2022 — Contraction prolongée (48.4)",
"start_date": "2023-01-04", "level": "medium", "category": "calendar", "sub_type": "ISM",
"expected_value": "48.5", "actual_value": "48.4", "surprise_pct": -2,
"unit": "index", "impact_score": 0.72, "absorption_pct": 100,
"affected_assets": _EQ + ["TLT", "EURUSD=X"],
"description": "ISM Manufacturing déc 2022 : 48.4, en contraction 2e mois consécutif. Marché commence à pricer un atterrissage plus dur. Mais les services restent solides. Tension entre signaux."},
{"name": "ISM Manufacturing Juin 2023 — Contraction accélérée (46.0)",
"start_date": "2023-07-03", "level": "medium", "category": "calendar", "sub_type": "ISM",
"expected_value": "47.2", "actual_value": "46.0", "surprise_pct": -18,
"unit": "index", "impact_score": 0.75, "absorption_pct": 100,
"affected_assets": _EQ + ["TLT", "GLD", "EURUSD=X"],
"description": "ISM Manufacturing juin 2023 : 46.0, plus bas depuis mai 2020. Contraction accélère. Attendu 47.2. Ajoute aux craintes de récession malgré le marché du travail fort. TLT + (récession trade)."},
{"name": "ISM Manufacturing Juillet 2024 — 46.8 (contraction + NFP miss → récession panic)",
"start_date": "2024-08-01", "level": "long", "category": "calendar", "sub_type": "ISM",
"expected_value": "48.8", "actual_value": "46.8", "surprise_pct": -25,
"unit": "index", "impact_score": 0.85, "absorption_pct": 100,
"affected_assets": _ALL,
"description": "ISM Manufacturing juil 2024 : 46.8 (miss vs 48.8). Combiné au NFP miss +114K le lendemain, déclenche la panique 'hard landing'. VIX spike, Nikkei -12%, SPY -6%. Catalyseur du grand drawdown d'août 2024."},
# ══════════════════════════════════════════════════════════
# BOJ — BANQUE DU JAPON (2020-2025)
# ══════════════════════════════════════════════════════════
{"name": "BOJ — Extension COVID QE + achat ETF actions (stimulus japonais)",
"start_date": "2020-04-27", "level": "medium", "category": "calendar", "sub_type": "BOJ",
"expected_value": "YCC ±20pb + CP", "actual_value": "YCC ±25pb + achats ETF x2", "surprise_pct": +10,
"unit": "signal", "impact_score": 0.78, "absorption_pct": 100,
"affected_assets": ["USDJPY=X", "EFA", "GLD"],
"description": "BOJ double ses achats d'ETF actions (de 6 à 12T¥/an) et élargit le YCC à ±25pb. Soutien sans précédent des actifs risqués japonais. Yen se déprécie modestement. Nikkei bien soutenu."},
{"name": "BOJ — Maintien YCC malgré spéculation (forte pression vendeuse JGB)",
"start_date": "2022-06-17", "level": "medium", "category": "calendar", "sub_type": "BOJ",
"expected_value": "ajustement YCC", "actual_value": "YCC maintenu (±25pb)", "surprise_pct": -15,
"unit": "signal", "impact_score": 0.80, "absorption_pct": 100,
"affected_assets": ["USDJPY=X", "EFA", "TLT", "GLD"],
"description": "BOJ maintient le YCC malgré les attaques spéculatives de hedge funds. Kuroda : 'pas de changement de politique'. USD/JPY monte à 135, plus haut depuis 2002. Yen à 24Y low. Pression inflationniste importée au Japon."},
{"name": "BOJ — Surprise : élargissement YCC de ±25pb à ±50pb",
"start_date": "2022-12-20", "level": "long", "category": "calendar", "sub_type": "BOJ",
"expected_value": "YCC ±25pb (stable)", "actual_value": "YCC ±50pb", "surprise_pct": -25,
"unit": "pb", "impact_score": 0.90, "absorption_pct": 100,
"affected_assets": ["USDJPY=X", "TLT", "GLD", "EFA", "SPY"],
"description": "BOJ surprend le monde : élargit la bande YCC de ±25pb à ±50pb. Premier mouvement vers normalisation depuis 2016. USD/JPY chute de 137 à 131 en heures. Marché obligataire mondial se vend. Début de la fin du YCC."},
{"name": "BOJ — Fin des taux négatifs (NIRP) : premier hike depuis 2007",
"start_date": "2024-03-19", "level": "long", "category": "calendar", "sub_type": "BOJ",
"expected_value": "hold ou +10pb", "actual_value": "+10pb (fin NIRP)", "surprise_pct": +5,
"unit": "pb", "impact_score": 0.88, "absorption_pct": 100,
"affected_assets": ["USDJPY=X", "EFA", "TLT", "GLD", "EURUSD=X"],
"description": "BOJ met fin aux taux négatifs (NIRP), première hausse depuis 2007. Taux 0-0.1%. Fin du YCC. Annonce plus douce qu'attendue sur le 'chemin futur'. USD/JPY chute -1.5%, puis se reprend. Début de la normalisation."},
{"name": "BOJ — Surprise +15pb → Débâcle carry trade (crash août 2024)",
"start_date": "2024-07-31", "level": "long", "category": "calendar", "sub_type": "BOJ",
"expected_value": "hold ou +5pb", "actual_value": "+15pb (taux 0.25%)", "surprise_pct": -30,
"unit": "pb", "impact_score": 0.96, "absorption_pct": 100,
"affected_assets": ["USDJPY=X", "SPY", "QQQ", "VXX", "GLD", "EFA"],
"description": "BOJ hausse +15pb de façon surprise. Yen +7% en 48h. USD/JPY 161 → 141. Débâcle massive du carry trade yen : VIX spike à 65 (3e plus haut jamais), Nikkei -12% en 2 jours, SPY -6%. Crise de liquidité mondiale brève mais intense."},
{"name": "BOJ — Hausse +25pb (Ueda confirme normalisation graduelle)",
"start_date": "2025-01-24", "level": "medium", "category": "calendar", "sub_type": "BOJ",
"expected_value": "+15pb ou +25pb", "actual_value": "+25pb (taux 0.50%)", "surprise_pct": +5,
"unit": "pb", "impact_score": 0.80, "absorption_pct": 100,
"affected_assets": ["USDJPY=X", "EFA", "TLT", "GLD"],
"description": "BOJ hausse à 0.50%, plus haut depuis 2008. Ueda signale une approche graduelle future. USD/JPY chute -1%. Confirme le cycle de normalisation mais sans panique. Yen en tendance de fond haussière vs USD."},
# ══════════════════════════════════════════════════════════
# ECB — BANQUE CENTRALE EUROPÉENNE (2022-2025)
# ══════════════════════════════════════════════════════════
{"name": "ECB — Première hausse en 11 ans : +50pb (surprise vs +25pb attendu)",
"start_date": "2022-07-21", "level": "long", "category": "calendar", "sub_type": "ECB",
"expected_value": "+25pb", "actual_value": "+50pb + TPI anti-fragmentation", "surprise_pct": +15,
"unit": "pb", "impact_score": 0.90, "absorption_pct": 100,
"affected_assets": ["EURUSD=X", "EFA", "TLT", "GLD", "GBPUSD=X"],
"description": "BCE monte +50pb (vs +25 attendu) en première hausse depuis 2011. Lance le TPI (Transmission Protection Instrument) anti-fragmentation. EUR/USD rebond +1.5%. BTP Italie spreads se compriment. Hawkish surprise."},
{"name": "ECB — Record +75pb (plus grande hausse depuis création Euro)",
"start_date": "2022-09-08", "level": "long", "category": "calendar", "sub_type": "ECB",
"expected_value": "+75pb", "actual_value": "+75pb (taux 1.25%)", "surprise_pct": 0,
"unit": "pb", "impact_score": 0.88, "absorption_pct": 100,
"affected_assets": ["EURUSD=X", "EFA", "TLT", "GLD", "GBPUSD=X"],
"description": "BCE monte +75pb, le plus fort depuis la création de l'euro en 1999. Inflation eurozone à 9.1%. Lagarde ferme la porte à une récession → marché hausse EUR. Signal que la zone euro va souffrir mais la BCE doit agir."},
{"name": "ECB — Dernière hausse du cycle : +25pb (pic 4.00%)",
"start_date": "2023-09-14", "level": "long", "category": "calendar", "sub_type": "ECB",
"expected_value": "+25pb ou hold", "actual_value": "+25pb (fin du cycle)", "surprise_pct": -5,
"unit": "pb", "impact_score": 0.82, "absorption_pct": 100,
"affected_assets": ["EURUSD=X", "EFA", "TLT", "GLD"],
"description": "BCE monte +25pb à 4.00%, signale que c'est probablement la fin. Taux maintenus à 4% pendant 9 mois. EUR/USD perd -0.5% sur la semaine. Marché commence à pricer les baisses 2024."},
{"name": "ECB — Première baisse de taux depuis 2019 : -25pb",
"start_date": "2024-06-06", "level": "long", "category": "calendar", "sub_type": "ECB",
"expected_value": "-25pb", "actual_value": "-25pb (taux 3.75%)", "surprise_pct": 0,
"unit": "pb", "impact_score": 0.82, "absorption_pct": 100,
"affected_assets": ["EURUSD=X", "EFA", "TLT", "GLD", "GBPUSD=X"],
"description": "BCE baisse -25pb pour la première fois depuis 2019. Mais projections inflation révisées à la hausse. EUR/USD recule -0.3%. Signal de 'gradualisme' — pas de séries de baisses rapides comme Fed."},
# ══════════════════════════════════════════════════════════
# BOE — BANQUE D'ANGLETERRE (2022-2024)
# ══════════════════════════════════════════════════════════
{"name": "BOE — +50pb (plus forte hausse depuis 1995) + Truss Budget Crisis",
"start_date": "2022-08-04", "level": "long", "category": "calendar", "sub_type": "BOE",
"expected_value": "+50pb", "actual_value": "+50pb + avertissement récession", "surprise_pct": -10,
"unit": "pb", "impact_score": 0.85, "absorption_pct": 100,
"affected_assets": ["GBPUSD=X", "TLT", "GLD"],
"description": "BOE monte +50pb, plus fort depuis 1995. Prévoit une récession 'prolongée'. CPI UK à 10.1%. Mini-budget Truss fin septembre va accentuer la crise (gilts crash). GBP perd -10% sur les semaines suivantes."},
{"name": "BOE — Intervention urgence (Gilts crash, fonds de pension)",
"start_date": "2022-09-28", "level": "long", "category": "calendar", "sub_type": "BOE",
"expected_value": "QT", "actual_value": "QE urgence temporaire (gilts)", "surprise_pct": +20,
"unit": "signal", "impact_score": 0.92, "absorption_pct": 100,
"affected_assets": ["GBPUSD=X", "TLT", "GLD", "VXX", "EFA"],
"description": "BOE lance un QE d'urgence pour acheter des gilts (obligations UK) après l'effondrement post mini-budget Truss. Gilts à 30Y perdent -30% en une semaine. Fonds de pension LDI en crise de liquidité. GBP à 1.035 (ATL). Crise systémique évitée de justesse."},
{"name": "BOE — Première baisse de taux depuis COVID : -25pb",
"start_date": "2024-08-01", "level": "long", "category": "calendar", "sub_type": "BOE",
"expected_value": "hold ou -25pb (50/50)", "actual_value": "-25pb (taux 5.00%)", "surprise_pct": +8,
"unit": "pb", "impact_score": 0.80, "absorption_pct": 100,
"affected_assets": ["GBPUSD=X", "EFA", "TLT", "GLD"],
"description": "BOE baisse -25pb (vote 5-4, très serré). Première baisse depuis mars 2020. Bailey : 'risques inflationnistes se dissipent'. GBP perd -0.5%. Signale le début du cycle d'assouplissement UK, avec prudence."},
# ══════════════════════════════════════════════════════════
# PCE / CORE DATA (reference Fed)
# ══════════════════════════════════════════════════════════
{"name": "PCE Janvier 2024 — Core PCE 2.8% (inflation sticky, cut repoussé)",
"start_date": "2024-02-29", "level": "medium", "category": "calendar", "sub_type": "PCE",
"expected_value": "2.4%", "actual_value": "2.8%", "surprise_pct": -25,
"unit": "% a/a", "impact_score": 0.80, "absorption_pct": 100,
"affected_assets": _EQ + ["TLT", "GLD", "EURUSD=X"],
"description": "Core PCE jan 2024 : 2.8% a/a, au-dessus de 2.4% attendu et des 2.6% de décembre. L'indicateur préféré de la Fed refuse de descendre. Repousse les anticipations de baisse mars → juin. TLT -1.2%."},
{"name": "PCE Septembre 2024 — Core PCE 2.6% (vers 2% mais résistant)",
"start_date": "2024-10-31", "level": "short", "category": "calendar", "sub_type": "PCE",
"expected_value": "2.6%", "actual_value": "2.6%", "surprise_pct": 0,
"unit": "% a/a", "impact_score": 0.68, "absorption_pct": 100,
"affected_assets": _EQ + ["TLT", "GLD"],
"description": "Core PCE sept 2024 : 2.6%, comme attendu. Toujours au-dessus de l'objectif 2%. Confirme que la désinflation 'last mile' est lente. Marché accepte le 'higher for longer' même en phase de baisses."},
]
def bootstrap_eco_events(force: bool = False) -> Dict[str, Any]:
"""
Insert curated economic calendar events into market_events DB.
Idempotent: skips events whose name already exists.
Returns: {inserted, skipped, errors, total}
"""
from services.database import get_all_market_events, save_market_event
existing = {ev.get("name", "") for ev in get_all_market_events()}
if force:
from services.database import delete_market_event, get_all_market_events as _gae
eco_names = {e["name"] for e in ECO_EVENTS}
for ev in _gae():
if ev.get("name") in eco_names:
delete_market_event(ev["id"])
existing = set()
logger.info("[eco_bootstrap] Force: existing eco events deleted")
import json
inserted = skipped = errors = 0
for ev in ECO_EVENTS:
if ev["name"] in existing:
skipped += 1
continue
try:
save_market_event({
"name": ev["name"],
"start_date": ev["start_date"],
"end_date": ev.get("end_date"),
"level": ev["level"],
"category": ev["category"],
"sub_type": ev.get("sub_type"),
"description": ev.get("description", ""),
"market_impact": ev.get("description", "")[:80],
"affected_assets": ev.get("affected_assets", []),
"impact_score": ev.get("impact_score", 0.75),
"expected_value": ev.get("expected_value"),
"actual_value": ev.get("actual_value"),
"surprise_pct": ev.get("surprise_pct"),
"unit": ev.get("unit"),
"absorption_pct": ev.get("absorption_pct", 100),
})
inserted += 1
except Exception as e:
logger.warning(f"[eco_bootstrap] Error inserting '{ev['name'][:40]}': {e}")
errors += 1
from services.database import count_market_events
total = count_market_events()
logger.info(f"[eco_bootstrap] inserted={inserted} skipped={skipped} errors={errors} total={total}")
return {"inserted": inserted, "skipped": skipped, "errors": errors, "total": total}

View File

@@ -575,8 +575,14 @@ def _get_relevant_events(
"title": ev.get("name") or ev.get("event_name") or "",
"level": ev.get("level", "medium"),
"category": ev.get("category", ""),
"sub_type": ev.get("sub_type") or None,
"description": (ev.get("description") or "")[:120],
"impact_score": float(ev.get("impact_score") or 0.5),
"expected_value": ev.get("expected_value") or None,
"actual_value": ev.get("actual_value") or None,
"surprise_pct": ev.get("surprise_pct"),
"unit": ev.get("unit") or None,
"absorption_pct": ev.get("absorption_pct"),
})
# Sort by start_date asc for timeline display, cap at 30

View File

@@ -13,7 +13,7 @@ const api = axios.create({ baseURL: '/api' })
// ── Types ─────────────────────────────────────────────────────────────────────
interface Driver {
key: string; label: string; weight: number; keywords: string[]
key: string; label: string; weight: number; keywords: string[]; type?: string
}
interface InstrumentConfig {
@@ -29,7 +29,9 @@ interface LinePoint { time: string; value: number }
interface SnapshotEvent {
date: string; end_date: string | null; title: string; level: string
category: string; description: string; impact_score: number
category: string; sub_type?: string; description: string; impact_score: number
expected_value?: string | null; actual_value?: string | null
surprise_pct?: number | null; unit?: string | null; absorption_pct?: number | null
}
interface RegimeSignals {
@@ -114,6 +116,32 @@ function pctN(a: number | undefined, b: number | undefined): number {
return ((a - b) / b) * 100
}
// ── Driver type config ────────────────────────────────────────────────────────
const DRIVER_TYPES = ['event_calendar', 'report', 'geopolitical', 'fundamental', 'sentiment', 'technical'] as const
type DriverType = typeof DRIVER_TYPES[number]
const DRIVER_TYPE_CFG: Record<string, { short: string; tw: string }> = {
event_calendar: { short: 'Cal.', tw: 'text-amber-400 bg-amber-900/30 border-amber-700/30' },
report: { short: 'Rep.', tw: 'text-blue-400 bg-blue-900/30 border-blue-700/30' },
geopolitical: { short: 'Geo.', tw: 'text-red-400 bg-red-900/30 border-red-700/30' },
fundamental: { short: 'Fund.', tw: 'text-emerald-400 bg-emerald-900/30 border-emerald-700/30' },
sentiment: { short: 'Sent.', tw: 'text-violet-400 bg-violet-900/30 border-violet-700/30' },
technical: { short: 'Tech.', tw: 'text-cyan-400 bg-cyan-900/30 border-cyan-700/30' },
}
function DriverTypeBadge({ type }: { type?: string }) {
if (!type) return null
const cfg = DRIVER_TYPE_CFG[type]
if (!cfg) return null
return (
<span className={clsx('text-xs px-1 py-0 rounded border leading-none', cfg.tw)}
style={{ fontSize: 8, paddingTop: 1, paddingBottom: 1 }}>
{cfg.short}
</span>
)
}
// ── Macro regime colour mapping ───────────────────────────────────────────────
const MACRO_COLOR_MAP: Record<string, string> = {
@@ -132,7 +160,7 @@ function macroRegimeColor(dominant: string): string {
return MACRO_COLOR_MAP[dominant] ?? 'slate'
}
// ── RegimeCard (métriques de signaux) ─────────────────────────────────────────
// ── RegimeCard ────────────────────────────────────────────────────────────────
function RegimeCard({
regime, macroRegime, signalsAt, dateLabel,
@@ -155,7 +183,6 @@ function RegimeCard({
amber: 'border-amber-700/40 bg-amber-950/30',
}
// 6 signal metrics displayed as a compact grid
const ma50Color = signals.ma50_above_ma200 ? 'text-emerald-400' : 'text-red-400'
const volColor = (signals.vol_ratio_pct ?? 0) > 130 ? 'text-orange-400' : (signals.vol_ratio_pct ?? 0) < 70 ? 'text-cyan-400' : 'text-slate-300'
@@ -166,21 +193,9 @@ function RegimeCard({
sub: signals.ma50_above_ma200 === true ? '↑ Golden cross' : signals.ma50_above_ma200 === false ? '↓ Death cross' : '',
color: ma50Color,
},
{
label: 'Slope MA50 (10j)',
value: fmt(signals.ma50_slope_pct) + '%',
color: pctColor(signals.ma50_slope_pct),
},
{
label: 'Slope MA200 (10j)',
value: fmt(signals.ma200_slope_pct) + '%',
color: pctColor(signals.ma200_slope_pct),
},
{
label: 'Momentum 20j',
value: fmt(signals.momentum_20d_pct) + '%',
color: pctColor(signals.momentum_20d_pct),
},
{ label: 'Slope MA50 (10j)', value: fmt(signals.ma50_slope_pct) + '%', color: pctColor(signals.ma50_slope_pct) },
{ label: 'Slope MA200 (10j)', value: fmt(signals.ma200_slope_pct) + '%', color: pctColor(signals.ma200_slope_pct) },
{ label: 'Momentum 20j', value: fmt(signals.momentum_20d_pct) + '%', color: pctColor(signals.momentum_20d_pct) },
{
label: 'Distance MA200',
value: fmt(signals.dist_ma200_pct) + '%',
@@ -195,13 +210,11 @@ function RegimeCard({
},
]
// Macro regime display
const macroCol = macroRegime ? macroRegimeColor(macroRegime.dominant) : 'slate'
const top3Macro = macroRegime?.ranked?.slice(0, 3) ?? []
return (
<div className={clsx('rounded-xl border p-4 space-y-3', borderMap[col])}>
{/* Header */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<BarChart2 className={clsx('w-4 h-4', `text-${col}-400`)} />
@@ -210,7 +223,6 @@ function RegimeCard({
<span className="text-xs text-slate-600">{dateLabel}</span>
</div>
{/* Macro regime section (global cycle) */}
{macroRegime && macroRegime.dominant !== 'incertain' && (
<div className="rounded-lg border border-slate-700/30 bg-dark-700/40 px-3 py-2 space-y-1.5">
<div className="flex items-center justify-between">
@@ -240,7 +252,6 @@ function RegimeCard({
</div>
)}
{/* Technical regime label + confidence */}
<div className={clsx('rounded-lg px-3 py-2', `bg-${col}-900/20`)}>
<div className="text-xs text-slate-500 mb-0.5">Régime technique</div>
<div className={clsx('text-sm font-bold leading-tight', `text-${col}-300`)}>{regime.current}</div>
@@ -253,7 +264,6 @@ function RegimeCard({
</div>
</div>
{/* 6 signal metrics — 2 column grid */}
<div className="grid grid-cols-2 gap-2">
{metrics.map(m => (
<div key={m.label} className="rounded-lg bg-dark-700/50 px-2.5 py-2">
@@ -364,13 +374,32 @@ function TrendCard({ trend, dateLabel }: { trend: TrendMetrics; dateLabel: strin
// ── EventsCard ────────────────────────────────────────────────────────────────
function EventsCard({ events }: { events: SnapshotEvent[] }) {
function EventsCard({ events, selectedDate }: { events: SnapshotEvent[]; selectedDate: string | null }) {
const navigate = useNavigate()
const LEVEL_C: Record<string, string> = {
long: 'text-violet-400 bg-violet-900/30 border-violet-700/30',
medium: 'text-blue-400 bg-blue-900/30 border-blue-700/30',
short: 'text-emerald-400 bg-emerald-900/30 border-emerald-700/30',
}
function isActiveAt(ev: SnapshotEvent): boolean {
if (!selectedDate) return false
const evStart = ev.date
const evEnd = ev.end_date
if (evStart > selectedDate) return false
if (evEnd) return evEnd >= selectedDate
// point event: active if within 30 days after
const diffDays = (new Date(selectedDate).getTime() - new Date(evStart).getTime()) / 86400000
return diffDays <= 30
}
function surpriseColor(v: number | null | undefined): string {
if (v === null || v === undefined) return 'text-slate-400'
if (v > 5) return 'text-emerald-400'
if (v < -5) return 'text-red-400'
return 'text-slate-400'
}
return (
<div className="rounded-xl border border-slate-700/40 bg-dark-800/60 p-4 space-y-3">
<div className="flex items-center gap-2">
@@ -381,25 +410,75 @@ function EventsCard({ events }: { events: SnapshotEvent[] }) {
<div className="text-xs text-slate-600 italic py-2">Aucun événement lié trouvé</div>
) : (
<div className="space-y-2 max-h-[380px] overflow-y-auto pr-1">
{events.map((ev, i) => (
<div key={i}
className="rounded-lg border border-slate-700/30 bg-dark-700/40 p-2 cursor-pointer hover:bg-dark-700/70 transition-colors"
onClick={() => navigate(`/timeline?date=${ev.date}`)}
>
<div className="flex items-start justify-between gap-2">
<span className="text-xs font-medium text-slate-300 leading-tight">{ev.title}</span>
<span className={clsx('shrink-0 text-xs px-1.5 py-0.5 rounded border', LEVEL_C[ev.level] ?? 'text-slate-400 bg-slate-800 border-slate-700/30')}>
{ev.level === 'long' ? 'LT' : ev.level === 'medium' ? 'MT' : 'CT'}
</span>
{events.map((ev, i) => {
const active = isActiveAt(ev)
return (
<div key={i}
className={clsx(
'rounded-lg border p-2 cursor-pointer transition-colors',
active
? 'border-amber-600/50 bg-amber-900/20 hover:bg-amber-900/30'
: 'border-slate-700/30 bg-dark-700/40 hover:bg-dark-700/70'
)}
onClick={() => navigate(`/timeline?date=${ev.date}`)}
>
<div className="flex items-start justify-between gap-2">
<div className="flex items-center gap-1.5 min-w-0">
{active && <span className="shrink-0 w-1.5 h-1.5 rounded-full bg-amber-400 mt-0.5" />}
<span className="text-xs font-medium text-slate-300 leading-tight truncate">{ev.title}</span>
</div>
<div className="flex items-center gap-1 shrink-0">
{ev.sub_type && (
<span className="text-xs px-1 py-0 rounded bg-slate-700/50 text-slate-400 border border-slate-600/30"
style={{ fontSize: 8, paddingTop: 1, paddingBottom: 1 }}>
{ev.sub_type}
</span>
)}
<span className={clsx('text-xs px-1.5 py-0.5 rounded border', LEVEL_C[ev.level] ?? 'text-slate-400 bg-slate-800 border-slate-700/30')}>
{ev.level === 'long' ? 'LT' : ev.level === 'medium' ? 'MT' : 'CT'}
</span>
</div>
</div>
{/* Eco calendar fields */}
{(ev.expected_value || ev.actual_value) && (
<div className="flex items-center gap-3 mt-1.5 text-xs">
{ev.expected_value && (
<span className="text-slate-500">Att. <span className="text-slate-300">{ev.expected_value}</span></span>
)}
{ev.actual_value && (
<span className="text-slate-500">Réel <span className="font-semibold text-white">{ev.actual_value}</span></span>
)}
{ev.surprise_pct !== null && ev.surprise_pct !== undefined && (
<span className={clsx('font-semibold', surpriseColor(ev.surprise_pct))}>
{ev.surprise_pct > 0 ? '+' : ''}{ev.surprise_pct.toFixed(0)}%
{ev.surprise_pct > 5 ? ' ↑' : ev.surprise_pct < -5 ? ' ↓' : ''}
</span>
)}
{ev.unit && <span className="text-slate-600">{ev.unit}</span>}
</div>
)}
{ev.absorption_pct !== null && ev.absorption_pct !== undefined && (
<div className="mt-1.5">
<div className="flex items-center justify-between text-xs text-slate-600 mb-0.5">
<span>Absorption marché</span><span>{ev.absorption_pct}%</span>
</div>
<div className="h-1 bg-slate-800 rounded-full overflow-hidden">
<div className="h-full bg-amber-600/60 rounded-full" style={{ width: `${ev.absorption_pct}%` }} />
</div>
</div>
)}
<div className="flex items-center gap-2 mt-1 text-xs text-slate-500">
<Clock className="w-3 h-3" />
{fmtDateFR(ev.date)}{ev.end_date ? `${fmtDateFR(ev.end_date)}` : ''}
{ev.impact_score > 0.7 && <AlertCircle className="w-3 h-3 text-orange-400" />}
</div>
{ev.description && <p className="text-xs text-slate-600 mt-1 line-clamp-1">{ev.description}</p>}
</div>
<div className="flex items-center gap-2 mt-1 text-xs text-slate-500">
<Clock className="w-3 h-3" />
{ev.date}{ev.end_date ? `${ev.end_date}` : ''}
{ev.impact_score > 0.7 && <AlertCircle className="w-3 h-3 text-orange-400" />}
</div>
{ev.description && <p className="text-xs text-slate-600 mt-1 line-clamp-1">{ev.description}</p>}
</div>
))}
)
})}
</div>
)}
</div>
@@ -455,9 +534,9 @@ function eventMatchesDriver(ev: SnapshotEvent, keywords: string[]): boolean {
}
function EventTimelineStrip({
events, priceData, drivers,
events, priceData, drivers, selectedDate,
}: {
events: SnapshotEvent[]; priceData: PriceCandle[]; drivers: Driver[]
events: SnapshotEvent[]; priceData: PriceCandle[]; drivers: Driver[]; selectedDate: string | null
}) {
if (priceData.length < 2) return null
@@ -468,21 +547,21 @@ function EventTimelineStrip({
function leftPct(d: string) { return Math.max(0, Math.min(99, ((dateToMs(d) - chartStart) / totalMs) * 100)) }
function widthPct(start: string, end: string | null): number {
// Default width = 3% of period if no end date
const endMs = end ? dateToMs(end) : dateToMs(start) + totalMs * 0.03
return Math.min(100, Math.max(0.4, ((endMs - dateToMs(start)) / totalMs) * 100))
}
// Top 4 drivers by weight
const topDrivers = [...drivers].sort((a, b) => b.weight - a.weight).slice(0, 4)
// Events matched by ANY driver (for fallback row)
const allKeywords = topDrivers.flatMap(d => d.keywords ?? [])
const matchedAnyIdxs = new Set(
events.map((ev, i) => eventMatchesDriver(ev, allKeywords) ? i : -1).filter(i => i >= 0)
)
const unmatchedEvs = events.filter((_ev, i) => !matchedAnyIdxs.has(i))
// Cursor position for the selected date
const cursorLeft = selectedDate ? leftPct(selectedDate) : null
return (
<div className="bg-dark-900/40 rounded-xl border border-slate-700/40 px-4 py-3">
<div className="text-xs text-slate-500 uppercase tracking-wide mb-2.5">Événements par driver</div>
@@ -492,13 +571,13 @@ function EventTimelineStrip({
const matchedEvs = events.filter(ev => eventMatchesDriver(ev, driver.keywords ?? []))
return (
<div key={driver.key} className="flex items-center gap-2 h-6">
<span className={clsx('text-xs font-semibold shrink-0 truncate w-28', cols.label)}
style={{ fontSize: 10 }}
title={driver.label}
>
{driver.label}
</span>
{/* paddingRight aligns with chart right price scale (~60px) */}
<div className="flex items-center gap-1 shrink-0 w-28">
<span className={clsx('text-xs font-semibold truncate', cols.label)}
style={{ fontSize: 10 }} title={driver.label}>
{driver.label}
</span>
<DriverTypeBadge type={driver.type} />
</div>
<div className="relative flex-1 h-full" style={{ paddingRight: 62 }}>
{matchedEvs.map((ev, i) => {
const left = leftPct(ev.date)
@@ -523,6 +602,13 @@ function EventTimelineStrip({
<div className="w-full border-t border-dashed border-slate-700/30" />
</div>
)}
{/* Vertical cursor for selected date */}
{cursorLeft !== null && (
<div
className="absolute top-0 bottom-0 w-px bg-white/50 z-20 pointer-events-none"
style={{ left: `${cursorLeft}%` }}
/>
)}
</div>
</div>
)
@@ -552,6 +638,12 @@ function EventTimelineStrip({
</div>
)
})}
{cursorLeft !== null && (
<div
className="absolute top-0 bottom-0 w-px bg-white/50 z-20 pointer-events-none"
style={{ left: `${cursorLeft}%` }}
/>
)}
</div>
</div>
)}
@@ -582,7 +674,10 @@ function DriversPanel({ instrumentId, drivers, onSave, onClose }: {
}
function addDriver() {
setLocal(prev => [...prev, { key: `driver_${Date.now()}`, label: 'Nouveau driver', weight: 0.5, keywords: [] }])
setLocal(prev => [...prev, {
key: `driver_${Date.now()}`, label: 'Nouveau driver',
weight: 0.5, keywords: [], type: 'fundamental',
}])
}
function removeDriver(i: number) {
@@ -634,6 +729,16 @@ function DriversPanel({ instrumentId, drivers, onSave, onClose }: {
value={d.label}
onChange={e => updateDriver(i, 'label', e.target.value)}
/>
{/* Type selector */}
<select
className="text-xs bg-dark-600 border border-slate-700/40 rounded px-1.5 py-1 text-slate-300"
value={d.type ?? 'fundamental'}
onChange={e => updateDriver(i, 'type', e.target.value)}
>
{DRIVER_TYPES.map(t => (
<option key={t} value={t}>{DRIVER_TYPE_CFG[t]?.short ?? t}</option>
))}
</select>
<div className="flex items-center gap-1.5">
<span className="text-xs text-slate-500">Poids</span>
<input
@@ -682,7 +787,6 @@ export default function InstrumentDashboard() {
const [selectorOpen, setSelectorOpen] = useState(false)
const [selectedDate, setSelectedDate] = useState<string | null>(null)
const [editDrivers, setEditDrivers] = useState(false)
// Local drivers (updated optimistically after save)
const [localDrivers, setLocalDrivers] = useState<Driver[] | null>(null)
const instrumentId = id.toUpperCase()
@@ -720,7 +824,6 @@ export default function InstrumentDashboard() {
if (date) setSelectedDate(date)
}, [])
// ── Lookup maps ───────────────────────────────────────────────────────────
const { priceMap, indMap, sortedDates, dateIndex } = useMemo(() => {
if (!snapshot) return { priceMap: {} as Record<string, PriceCandle>, indMap: {} as Record<string, Record<string, number>>, sortedDates: [] as string[], dateIndex: {} as Record<string, number> }
const priceMap: Record<string, PriceCandle> = {}
@@ -786,7 +889,6 @@ export default function InstrumentDashboard() {
}
}, [effectiveDate, priceMap, indMap, sortedDates, dateIndex, snapshot])
// ── UI ────────────────────────────────────────────────────────────────────
const grouped = CATEGORY_ORDER.map(cat => ({
cat, label: CATEGORY_LABELS[cat] ?? cat,
items: instruments.filter(i => i.category === cat),
@@ -797,7 +899,6 @@ export default function InstrumentDashboard() {
const dateLabel = effectiveDate ? fmtDateFR(effectiveDate) : '—'
const displayPrice = dateTrend?.current_price ?? snapshot?.current_price
// Drivers to use (local override after edit, else from snapshot)
const activeDrivers = localDrivers ?? snapshot?.instrument?.drivers ?? []
return (
@@ -860,7 +961,6 @@ export default function InstrumentDashboard() {
)}
<div className="ml-auto flex items-center gap-2">
{/* Drivers edit button */}
{snapshot && (
<button onClick={() => setEditDrivers(e => !e)}
className={clsx('flex items-center gap-1.5 text-xs px-3 py-1.5 border rounded-lg transition-colors',
@@ -873,7 +973,6 @@ export default function InstrumentDashboard() {
Drivers
</button>
)}
{/* Period selector */}
<div className="flex items-center gap-1 bg-dark-800 border border-slate-700/40 rounded-xl p-1">
{PERIODS.map(p => (
<button key={p.key} onClick={() => setPeriod(p.key)}
@@ -916,6 +1015,7 @@ export default function InstrumentDashboard() {
events={snapshot.events}
priceData={snapshot.price_data}
drivers={activeDrivers}
selectedDate={effectiveDate}
/>
{/* Date badge */}
@@ -944,10 +1044,12 @@ export default function InstrumentDashboard() {
trend={dateTrend ?? snapshot.trend}
dateLabel={dateLabel}
/>
<EventsCard events={snapshot.events} />
<EventsCard
events={snapshot.events}
selectedDate={effectiveDate}
/>
</div>
{/* Drivers edit panel */}
{editDrivers && (
<DriversPanel
instrumentId={instrumentId}
@@ -957,7 +1059,6 @@ export default function InstrumentDashboard() {
/>
)}
{/* AI Narrative */}
<NarrativeCard
narrative={narrative}
loading={loadingNarr}