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:
@@ -11,11 +11,11 @@
|
|||||||
"description": "US large-cap equity benchmark — 500 plus grandes capitalisations américaines",
|
"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 },
|
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
|
||||||
"drivers": [
|
"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": "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", "weight": 0.85, "keywords": ["Earnings","EPS","bénéfices","résultats","Earnings Season","reporting","profits"]},
|
{"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", "weight": 0.75, "keywords": ["taux réels","TIPS","real yields","breakeven","inflation","CPI","PCE"]},
|
{"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", "weight": 0.7, "keywords": ["liquidité","liquidity","QE","QT","bilan Fed","repo","M2","conditions financières"]},
|
{"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", "weight": 0.6, "keywords": ["VIX","volatilité","vol spike","hedging","protection","skew","put","peur","fear"]}
|
{"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"],
|
"regime_labels": ["Risk-On Bull", "Risk-Off Bear", "Stagflation", "Rate Shock", "Recovery"],
|
||||||
"event_keywords": ["FOMC","CPI","NFP","GDP","Earnings Season","Fed Chair","VIX"],
|
"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",
|
"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 },
|
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
|
||||||
"drivers": [
|
"drivers": [
|
||||||
{"key": "real_rates", "label": "Real Rates", "weight": 0.9, "keywords": ["taux réels","TIPS","real yields","FOMC","Fed","CPI","breakeven"]},
|
{"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", "weight": 0.85, "keywords": ["IA","AI","Nvidia","Microsoft","capex tech","data center","GPU","intelligence artificielle","ChatGPT"]},
|
{"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", "weight": 0.8, "keywords": ["GAFAM","Apple","Meta","Alphabet","Amazon","Microsoft","earnings tech","Big Tech","résultats tech"]},
|
{"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", "weight": 0.75, "keywords": ["croissance","GDP","récession","NFP","emploi","PMI","croissance économique"]},
|
{"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", "weight": 0.5, "keywords": ["Dollar","DXY","dollar index","EUR/USD","devise"]}
|
{"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"],
|
"regime_labels": ["Tech Bull (AI-Driven)", "Tech Bear", "Rate Compression", "Earnings Crash", "Recovery"],
|
||||||
"event_keywords": ["FOMC","Big Tech Earnings","CPI","AI","Rate","NVDA","AAPL"],
|
"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",
|
"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 },
|
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
|
||||||
"drivers": [
|
"drivers": [
|
||||||
{"key": "gdp_growth", "label": "Croissance US", "weight": 0.85, "keywords": ["GDP","PIB","croissance US","NFP","emploi","récession","PMI","domestic growth"]},
|
{"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", "weight": 0.8, "keywords": ["crédit","credit","HY","high yield","spreads","LBO","conditions de crédit","credit crunch"]},
|
{"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","weight": 0.75, "keywords": ["banques régionales","SVB","FDIC","banque","banking","bank failure","crédit bancaire"]},
|
{"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", "weight": 0.6, "keywords": ["Dollar","DXY","dollar fort","USD strength"]}
|
{"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"],
|
"regime_labels": ["Small Cap Expansion", "Credit Crunch Squeeze", "Risk-Off", "Recovery", "Neutral"],
|
||||||
"event_keywords": ["FOMC","Credit","GDP","NFP","Rates","Regional Banks"],
|
"event_keywords": ["FOMC","Credit","GDP","NFP","Rates","Regional Banks"],
|
||||||
@@ -79,10 +79,10 @@
|
|||||||
"description": "MSCI Emerging Markets — grandes capitalisations des marchés émergents",
|
"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 },
|
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
|
||||||
"drivers": [
|
"drivers": [
|
||||||
{"key": "dollar_index", "label": "Dollar Index", "weight": 0.9, "keywords": ["Dollar","DXY","USD","dollar index","dollar fort","faible dollar"]},
|
{"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", "weight": 0.85, "keywords": ["China","Chine","PBOC","PBoC","PIB chinois","Xi","immobilier chinois","stimulus Chine","relance Chine"]},
|
{"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", "weight": 0.75, "keywords": ["EM","émergents","emerging","flows","flux","capitaux émergents"]},
|
{"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","weight": 0.7, "keywords": ["matières premières","commodities","minerai","pétrole","métaux","commodité"]}
|
{"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"],
|
"regime_labels": ["EM Growth Rally", "Dollar Squeeze", "China Risk-Off", "Commodity Lift", "Neutral"],
|
||||||
"event_keywords": ["China GDP","Dollar","EM","PBOC","Commodities","stimulus Chine"],
|
"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)",
|
"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 },
|
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
|
||||||
"drivers": [
|
"drivers": [
|
||||||
{"key": "ecb_policy", "label": "BCE / ECB", "weight": 0.85, "keywords": ["BCE","ECB","Lagarde","taux BCE","zone euro","politique BCE","ECB rate"]},
|
{"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", "weight": 0.8, "keywords": ["énergie","gaz européen","pétrole","LNG","crisis énergétique","energy","energy price"]},
|
{"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", "weight": 0.75, "keywords": ["EUR/USD","euro dollar","eurusd","parité euro","euro fort","euro faible"]},
|
{"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", "weight": 0.7, "keywords": ["Europe","zone euro","Allemagne","PIB européen","PMI Europe","récession Europe","croissance européenne"]}
|
{"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"],
|
"regime_labels": ["Global Risk-On", "USD Strength Drag", "Europe Recession", "Recovery", "Neutral"],
|
||||||
"event_keywords": ["ECB","EU GDP","Europe","Germany","BOJ","Japan","EUR"],
|
"event_keywords": ["ECB","EU GDP","Europe","Germany","BOJ","Japan","EUR"],
|
||||||
@@ -123,11 +123,11 @@
|
|||||||
"description": "SPDR Gold Shares — proxy physique de l'or",
|
"description": "SPDR Gold Shares — proxy physique de l'or",
|
||||||
"chart": { "ma_periods": [50, 100, 200], "bollinger_period": 50, "bollinger_std": 2, "show_volume": true },
|
"chart": { "ma_periods": [50, 100, 200], "bollinger_period": 50, "bollinger_std": 2, "show_volume": true },
|
||||||
"drivers": [
|
"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": "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", "weight": 0.85, "keywords": ["Dollar","DXY","dollar index","USD","faible dollar","dollar fort"]},
|
{"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", "weight": 0.75, "keywords": ["inflation","CPI","PCE","breakeven","attentes d'inflation","stagflation"]},
|
{"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", "weight": 0.7, "keywords": ["géopolitique","guerre","Moyen-Orient","Iran","Russie","conflit","Chine","tension","crise","war"]},
|
{"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","weight": 0.5, "keywords": ["banque centrale","central bank","réserves","gold reserves","achats d'or","PBOC gold","dédollarisation"]}
|
{"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"],
|
"regime_labels": ["Real Rates Falling", "Safe Haven Rally", "Inflation Hedge", "Dollar Weakness", "Range"],
|
||||||
"event_keywords": ["CPI","Fed","FOMC","Dollar","Geopolitical","Iran","Gold","guerre","inflation"],
|
"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",
|
"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 },
|
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
|
||||||
"drivers": [
|
"drivers": [
|
||||||
{"key": "industrial_demand", "label": "Demande Industrielle", "weight": 0.8, "keywords": ["industrie","manufacturing","PMI","production industrielle","fabrique","industry"]},
|
{"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", "weight": 0.75, "keywords": ["taux réels","TIPS","Fed","FOMC","real yields"]},
|
{"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", "weight": 0.7, "keywords": ["Dollar","DXY","USD"]},
|
{"key": "dollar", "label": "Dollar Index", "type": "technical", "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": "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", "weight": 0.6, "keywords": ["Gold","or","ratio Or/Argent","gold silver","argent","silver"]}
|
{"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"],
|
"regime_labels": ["Industrial Demand", "Safe Haven Flow", "Dollar Weakness", "Risk-Off", "Range"],
|
||||||
"event_keywords": ["Manufacturing","PMI","CPI","Dollar","Silver","EV","Solar","taux réels"],
|
"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)",
|
"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 },
|
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
|
||||||
"drivers": [
|
"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": "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", "weight": 0.8, "keywords": ["EIA","stocks pétroliers","inventaires","inventories","crude stocks","rapport EIA","crude supply"]},
|
{"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","weight": 0.75, "keywords": ["géopolitique","Moyen-Orient","Iran","Russie","conflit","guerre","war","Golfe Persique","tension"]},
|
{"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", "weight": 0.85, "keywords": ["demande mondiale","China demand","IEA","croissance mondiale","global demand","reprise économique"]},
|
{"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", "weight": 0.7, "keywords": ["Dollar","DXY","USD"]}
|
{"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"],
|
"regime_labels": ["Supply Shock", "Demand Recovery", "Inventory Draw", "Geopolitical Premium", "Neutral"],
|
||||||
"event_keywords": ["OPEC","EIA","Inventories","Brent","WTI","Oil","Middle East","Saudi"],
|
"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)",
|
"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 },
|
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
|
||||||
"drivers": [
|
"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": "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", "weight": 0.85, "keywords": ["EIA Gas Storage","stockage gaz","gas storage","inventaires gaz","rapport stockage gaz"]},
|
{"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", "weight": 0.75, "keywords": ["LNG","GNL","exportations gaz","terminal LNG","gazoduc","liquefied natural gas"]},
|
{"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", "weight": 0.7, "keywords": ["production gaz","shale gas","fracking","puits gaz","Marcellus","Appalachian","US gas output"]}
|
{"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"],
|
"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"],
|
"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",
|
"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 },
|
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
|
||||||
"drivers": [
|
"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": "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", "weight": 0.85, "keywords": ["CPI","PCE","inflation","déflation","attentes","breakeven","inflation US"]},
|
{"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", "weight": 0.75, "keywords": ["GDP","NFP","récession","croissance","emploi","PMI","chômage","payrolls"]},
|
{"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","weight": 0.7, "keywords": ["déficit","dette publique","Treasury auction","enchères","budget","fiscal","dette fédérale"]},
|
{"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é", "weight": 0.65, "keywords": ["risk-off","crise","fuite vers la qualité","safe haven","vol spike","contagion","crise bancaire"]}
|
{"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"],
|
"regime_labels": ["Safe Haven Bid", "Rate Shock Selloff", "QE Expectations", "Inflation Selloff", "Range"],
|
||||||
"event_keywords": ["FOMC","CPI","Fed","Treasury","Yield","Deficit","NFP"],
|
"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",
|
"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 },
|
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
|
||||||
"drivers": [
|
"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": "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", "weight": 0.8, "keywords": ["GDP","récession","croissance","PMI","emploi","payrolls","activité"]},
|
{"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é","weight": 0.8, "keywords": ["liquidité","crédit","QT","QE","conditions financières","FCO","financial conditions"]},
|
{"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", "weight": 0.75, "keywords": ["risk-on","risk-off","appétit pour le risque","flux crédit","sentiment marché"]}
|
{"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"],
|
"regime_labels": ["Credit Expansion", "Credit Crunch", "Spread Compression", "Recession Fear", "Neutral"],
|
||||||
"event_keywords": ["Credit","Default","GDP","FOMC","Recession","HY","spreads"],
|
"event_keywords": ["Credit","Default","GDP","FOMC","Recession","HY","spreads"],
|
||||||
@@ -259,10 +259,10 @@
|
|||||||
"description": "Taux spot Euro / Dollar américain",
|
"description": "Taux spot Euro / Dollar américain",
|
||||||
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": false },
|
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": false },
|
||||||
"drivers": [
|
"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": "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", "weight": 0.85, "keywords": ["taux 2 ans","Bund 2Y","Treasury 2Y","swap","différentiel de taux","2Y spread"]},
|
{"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", "weight": 0.75, "keywords": ["CPI","inflation","PCE","HICP","zone euro","inflation différentiel"]},
|
{"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", "weight": 0.7, "keywords": ["Dollar","DXY","dollar index","USD","dollar fort","faible dollar"]}
|
{"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"],
|
"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"],
|
"event_keywords": ["ECB","FOMC","CPI","EUR","Dollar","Fed","Eurozone","BCE"],
|
||||||
@@ -281,10 +281,10 @@
|
|||||||
"description": "Taux spot Dollar américain / Yen japonais",
|
"description": "Taux spot Dollar américain / Yen japonais",
|
||||||
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": false },
|
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": false },
|
||||||
"drivers": [
|
"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": "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","weight": 0.9, "keywords": ["différentiel US-Japon","JGB","UST","rendements US-JP","US Japan spread","10Y spread"]},
|
{"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", "weight": 0.85, "keywords": ["Fed","FOMC","taux directeur US","hawkish Fed","Fed hike","Federal Reserve"]},
|
{"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","weight": 0.7, "keywords": ["risk-off","carry trade","yen safe haven","crise","carry unwind","yen refuge"]}
|
{"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"],
|
"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"],
|
"event_keywords": ["BOJ","Fed","Japan","YCC","Carry","JPY","Yen","taux Japon"],
|
||||||
@@ -303,10 +303,10 @@
|
|||||||
"description": "Taux spot Livre Sterling / Dollar américain",
|
"description": "Taux spot Livre Sterling / Dollar américain",
|
||||||
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": false },
|
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": false },
|
||||||
"drivers": [
|
"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": "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", "weight": 0.8, "keywords": ["inflation UK","CPI UK","CPIH","RPI","inflation Angleterre"]},
|
{"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", "weight": 0.75, "keywords": ["GDP UK","PMI UK","croissance UK","récession UK","UK economy","UK GDP"]},
|
{"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","weight": 0.65, "keywords": ["politique UK","budget UK","Labour","Tories","fiscal UK","gilt","UK budget","Reeves"]}
|
{"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"],
|
"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"],
|
"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",
|
"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 },
|
"chart": { "ma_periods": [10, 20, 50], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
|
||||||
"drivers": [
|
"drivers": [
|
||||||
{"key": "market_stress", "label": "Stress de Marché", "weight": 0.9, "keywords": ["krach","crash","crise","choc","panic","volatilité","vol spike","selloff","capitulation","contagion"]},
|
{"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", "weight": 0.85, "keywords": ["risk-off","risk-on","sentiment","fear","peur","greed","appétit risque","VIX spike"]},
|
{"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)", "weight": 0.75, "keywords": ["options","puts","hedging","protection","skew","IV","couverture","implied vol","volatilité implicite"]},
|
{"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", "weight": 0.7, "keywords": ["FOMC","surprise","incertitude","géopolitique","macro","inflation choc","data surprise"]}
|
{"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"],
|
"regime_labels": ["Low Vol Complacency", "Vol Spike Crisis", "Vol Expansion", "Vol Compression", "Neutral"],
|
||||||
"event_keywords": ["VIX","Volatility","Crisis","FOMC","Market Stress","peur","vol spike"],
|
"event_keywords": ["VIX","Volatility","Crisis","FOMC","Market Stress","peur","vol spike"],
|
||||||
@@ -347,11 +347,11 @@
|
|||||||
"description": "Apple Inc. — ecosystème consommateur tech et services",
|
"description": "Apple Inc. — ecosystème consommateur tech et services",
|
||||||
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
|
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
|
||||||
"drivers": [
|
"drivers": [
|
||||||
{"key": "iphone_cycle", "label": "Cycle iPhone", "weight": 0.85, "keywords": ["iPhone","Apple","AAPL earnings","supercycle","launch","iPhone 16","hardware"]},
|
{"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", "weight": 0.8, "keywords": ["Chine","China","Huawei","ban Apple","restrictions Apple","China sales","marché chinois"]},
|
{"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","weight": 0.75, "keywords": ["Apple Intelligence","AI","IA","Siri","machine learning","IA Apple","generative AI"]},
|
{"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", "weight": 0.7, "keywords": ["services","App Store","iCloud","marge Apple","services revenue","abonnements"]},
|
{"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", "weight": 0.65, "keywords": ["Fed","taux","valorisation","taux d'intérêt","discount rate"]}
|
{"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"],
|
"regime_labels": ["Growth Bull", "Rate Squeeze", "Earnings Beat", "Tech Selloff", "Neutral"],
|
||||||
"event_keywords": ["Apple Earnings","iPhone","Services","China","Fed","AI","AAPL"],
|
"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",
|
"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 },
|
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
|
||||||
"drivers": [
|
"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": "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", "weight": 0.9, "keywords": ["NVDA earnings","résultats Nvidia","revenues","data center revenue","beat","guidance"]},
|
{"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", "weight": 0.85, "keywords": ["Amazon AWS","Microsoft Azure","Google Cloud","data center","cloud","computing","GPU demand"]},
|
{"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", "weight": 0.7, "keywords": ["export control","restrictions export","Chine","China ban","BIS","CISA","H20","chip ban"]},
|
{"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", "weight": 0.6, "keywords": ["taux","valorisation","growth stock","multiples","discount rate","Fed"]}
|
{"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"],
|
"regime_labels": ["AI Bull Run", "AI Hype Correction", "Earnings Catalyst", "Rate Compression", "Consolidation"],
|
||||||
"event_keywords": ["NVIDIA Earnings","AI","Data Center","Export","GPU","NVDA","Blackwell"],
|
"event_keywords": ["NVIDIA Earnings","AI","Data Center","Export","GPU","NVDA","Blackwell"],
|
||||||
@@ -393,10 +393,10 @@
|
|||||||
"description": "The Goldman Sachs Group — banque d'investissement globale",
|
"description": "The Goldman Sachs Group — banque d'investissement globale",
|
||||||
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
|
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
|
||||||
"drivers": [
|
"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": "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", "weight": 0.8, "keywords": ["M&A","fusion-acquisition","LBO","IPO","deal","advisory","boutique","transaction"]},
|
{"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", "weight": 0.8, "keywords": ["taux","Fed","intérêts","NIM","net interest margin","Fed rate","interest rates"]},
|
{"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", "weight": 0.75, "keywords": ["crédit","spreads","conditions financières","leverage loans","credit tightening"]}
|
{"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"],
|
"regime_labels": ["Finance Bull", "Recession Fear", "Yield Curve Steepen", "M&A Boom", "Range"],
|
||||||
"event_keywords": ["Goldman","Financials","Yield Curve","M&A","Banking","Fed","Credit"],
|
"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",
|
"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 },
|
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
|
||||||
"drivers": [
|
"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": "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", "weight": 0.8, "keywords": ["demande mondiale","China demand","IEA","croissance","global demand","reprise"]},
|
{"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", "weight": 0.75, "keywords": ["OPEC","OPEP","quotas","production","Saudi","Arabie Saoudite","réduction"]},
|
{"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","weight": 0.6, "keywords": ["capex","investissement","forage","upstream","exploration","rig count"]},
|
{"key": "capex", "label": "Capex / Investissement","type": "fundamental", "weight": 0.6, "keywords": ["capex","investissement","forage","upstream","exploration","rig count"]},
|
||||||
{"key": "dollar", "label": "Dollar Index", "weight": 0.6, "keywords": ["Dollar","DXY","USD"]}
|
{"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"],
|
"regime_labels": ["Energy Bull", "Demand Slowdown", "OPEC Benefit", "Green Transition Drag", "Range"],
|
||||||
"event_keywords": ["Oil","OPEC","Energy","XOM","EIA","Crude","Exxon","WTI"],
|
"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",
|
"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 },
|
"chart": { "ma_periods": [20, 50, 200], "bollinger_period": 20, "bollinger_std": 2, "show_volume": true },
|
||||||
"drivers": [
|
"drivers": [
|
||||||
{"key": "global_liquidity","label": "Liquidité Mondiale", "weight": 0.85, "keywords": ["liquidité mondiale","M2","Fed","QE","QT","conditions financières","liquidity","money supply"]},
|
{"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", "weight": 0.85, "keywords": ["ETF Bitcoin","spot BTC ETF","BlackRock","Fidelity","flux ETF","ETF approval","IBIT","FBTC"]},
|
{"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", "weight": 0.8, "keywords": ["risk-on","risk-off","crypto","sentiment","appétit pour le risque","halving","cycle crypto"]},
|
{"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", "weight": 0.7, "keywords": ["réglementation crypto","SEC","CFTC","crypto ban","ETF approval","régulation","MiCA"]},
|
{"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", "weight": 0.65, "keywords": ["Dollar","DXY","USD","dédollarisation"]}
|
{"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"],
|
"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"],
|
"event_keywords": ["Bitcoin","BTC","Crypto","ETF","Fed","Liquidity","Regulation","halving"],
|
||||||
|
|||||||
@@ -226,6 +226,22 @@ def bootstrap_events() -> Dict[str, Any]:
|
|||||||
return {"seeded": count, "status": "ok" if count > 0 else "already_seeded"}
|
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")
|
@router.post("/bootstrap-ma")
|
||||||
async def bootstrap_ma() -> Dict[str, Any]:
|
async def bootstrap_ma() -> Dict[str, Any]:
|
||||||
"""Detect MA ruptures on 5 key underlyings and generate historical market events via GPT."""
|
"""Detect MA ruptures on 5 key underlyings and generate historical market events via GPT."""
|
||||||
|
|||||||
@@ -753,8 +753,13 @@ def init_db():
|
|||||||
)""")
|
)""")
|
||||||
# Add columns to existing DBs (idempotent via catch)
|
# Add columns to existing DBs (idempotent via catch)
|
||||||
for _col, _def in [
|
for _col, _def in [
|
||||||
("absorption_pct", "REAL DEFAULT NULL"),
|
("absorption_pct", "REAL DEFAULT NULL"),
|
||||||
("relevant_indicators", "TEXT DEFAULT '[]'"),
|
("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:
|
try:
|
||||||
c.execute(f"ALTER TABLE market_events ADD COLUMN {_col} {_def}")
|
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:
|
try:
|
||||||
cur = conn.execute("""INSERT INTO market_events
|
cur = conn.execute("""INSERT INTO market_events
|
||||||
(name, start_date, end_date, level, category, description, market_impact,
|
(name, start_date, end_date, level, category, description, market_impact,
|
||||||
affected_assets, impact_score, parent_event_id)
|
affected_assets, impact_score, parent_event_id,
|
||||||
VALUES (?,?,?,?,?,?,?,?,?,?)""",
|
expected_value, actual_value, surprise_pct, unit, sub_type, absorption_pct)
|
||||||
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
||||||
(ev["name"], ev["start_date"], ev.get("end_date"),
|
(ev["name"], ev["start_date"], ev.get("end_date"),
|
||||||
ev["level"], ev.get("category", "macro"), ev.get("description", ""),
|
ev["level"], ev.get("category", "macro"), ev.get("description", ""),
|
||||||
ev.get("market_impact", ""), json.dumps(ev.get("affected_assets", [])),
|
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()
|
conn.commit()
|
||||||
return cur.lastrowid
|
return cur.lastrowid
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
586
backend/services/eco_calendar_bootstrap.py
Normal file
586
backend/services/eco_calendar_bootstrap.py
Normal 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}
|
||||||
@@ -575,8 +575,14 @@ def _get_relevant_events(
|
|||||||
"title": ev.get("name") or ev.get("event_name") or "",
|
"title": ev.get("name") or ev.get("event_name") or "",
|
||||||
"level": ev.get("level", "medium"),
|
"level": ev.get("level", "medium"),
|
||||||
"category": ev.get("category", ""),
|
"category": ev.get("category", ""),
|
||||||
|
"sub_type": ev.get("sub_type") or None,
|
||||||
"description": (ev.get("description") or "")[:120],
|
"description": (ev.get("description") or "")[:120],
|
||||||
"impact_score": float(ev.get("impact_score") or 0.5),
|
"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
|
# Sort by start_date asc for timeline display, cap at 30
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const api = axios.create({ baseURL: '/api' })
|
|||||||
// ── Types ─────────────────────────────────────────────────────────────────────
|
// ── Types ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
interface Driver {
|
interface Driver {
|
||||||
key: string; label: string; weight: number; keywords: string[]
|
key: string; label: string; weight: number; keywords: string[]; type?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface InstrumentConfig {
|
interface InstrumentConfig {
|
||||||
@@ -29,7 +29,9 @@ interface LinePoint { time: string; value: number }
|
|||||||
|
|
||||||
interface SnapshotEvent {
|
interface SnapshotEvent {
|
||||||
date: string; end_date: string | null; title: string; level: string
|
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 {
|
interface RegimeSignals {
|
||||||
@@ -114,6 +116,32 @@ function pctN(a: number | undefined, b: number | undefined): number {
|
|||||||
return ((a - b) / b) * 100
|
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 ───────────────────────────────────────────────
|
// ── Macro regime colour mapping ───────────────────────────────────────────────
|
||||||
|
|
||||||
const MACRO_COLOR_MAP: Record<string, string> = {
|
const MACRO_COLOR_MAP: Record<string, string> = {
|
||||||
@@ -132,7 +160,7 @@ function macroRegimeColor(dominant: string): string {
|
|||||||
return MACRO_COLOR_MAP[dominant] ?? 'slate'
|
return MACRO_COLOR_MAP[dominant] ?? 'slate'
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── RegimeCard (métriques de signaux) ─────────────────────────────────────────
|
// ── RegimeCard ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function RegimeCard({
|
function RegimeCard({
|
||||||
regime, macroRegime, signalsAt, dateLabel,
|
regime, macroRegime, signalsAt, dateLabel,
|
||||||
@@ -155,7 +183,6 @@ function RegimeCard({
|
|||||||
amber: 'border-amber-700/40 bg-amber-950/30',
|
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 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'
|
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' : '',
|
sub: signals.ma50_above_ma200 === true ? '↑ Golden cross' : signals.ma50_above_ma200 === false ? '↓ Death cross' : '',
|
||||||
color: ma50Color,
|
color: ma50Color,
|
||||||
},
|
},
|
||||||
{
|
{ label: 'Slope MA50 (10j)', value: fmt(signals.ma50_slope_pct) + '%', color: pctColor(signals.ma50_slope_pct) },
|
||||||
label: 'Slope MA50 (10j)',
|
{ label: 'Slope MA200 (10j)', value: fmt(signals.ma200_slope_pct) + '%', color: pctColor(signals.ma200_slope_pct) },
|
||||||
value: fmt(signals.ma50_slope_pct) + '%',
|
{ label: 'Momentum 20j', value: fmt(signals.momentum_20d_pct) + '%', color: pctColor(signals.momentum_20d_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',
|
label: 'Distance MA200',
|
||||||
value: fmt(signals.dist_ma200_pct) + '%',
|
value: fmt(signals.dist_ma200_pct) + '%',
|
||||||
@@ -195,13 +210,11 @@ function RegimeCard({
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
// Macro regime display
|
|
||||||
const macroCol = macroRegime ? macroRegimeColor(macroRegime.dominant) : 'slate'
|
const macroCol = macroRegime ? macroRegimeColor(macroRegime.dominant) : 'slate'
|
||||||
const top3Macro = macroRegime?.ranked?.slice(0, 3) ?? []
|
const top3Macro = macroRegime?.ranked?.slice(0, 3) ?? []
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx('rounded-xl border p-4 space-y-3', borderMap[col])}>
|
<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 justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<BarChart2 className={clsx('w-4 h-4', `text-${col}-400`)} />
|
<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>
|
<span className="text-xs text-slate-600">{dateLabel}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Macro regime section (global cycle) */}
|
|
||||||
{macroRegime && macroRegime.dominant !== 'incertain' && (
|
{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="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">
|
<div className="flex items-center justify-between">
|
||||||
@@ -240,7 +252,6 @@ function RegimeCard({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Technical regime label + confidence */}
|
|
||||||
<div className={clsx('rounded-lg px-3 py-2', `bg-${col}-900/20`)}>
|
<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="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>
|
<div className={clsx('text-sm font-bold leading-tight', `text-${col}-300`)}>{regime.current}</div>
|
||||||
@@ -253,7 +264,6 @@ function RegimeCard({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 6 signal metrics — 2 column grid */}
|
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
{metrics.map(m => (
|
{metrics.map(m => (
|
||||||
<div key={m.label} className="rounded-lg bg-dark-700/50 px-2.5 py-2">
|
<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 ────────────────────────────────────────────────────────────────
|
// ── EventsCard ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function EventsCard({ events }: { events: SnapshotEvent[] }) {
|
function EventsCard({ events, selectedDate }: { events: SnapshotEvent[]; selectedDate: string | null }) {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const LEVEL_C: Record<string, string> = {
|
const LEVEL_C: Record<string, string> = {
|
||||||
long: 'text-violet-400 bg-violet-900/30 border-violet-700/30',
|
long: 'text-violet-400 bg-violet-900/30 border-violet-700/30',
|
||||||
medium: 'text-blue-400 bg-blue-900/30 border-blue-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',
|
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 (
|
return (
|
||||||
<div className="rounded-xl border border-slate-700/40 bg-dark-800/60 p-4 space-y-3">
|
<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">
|
<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="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">
|
<div className="space-y-2 max-h-[380px] overflow-y-auto pr-1">
|
||||||
{events.map((ev, i) => (
|
{events.map((ev, i) => {
|
||||||
<div key={i}
|
const active = isActiveAt(ev)
|
||||||
className="rounded-lg border border-slate-700/30 bg-dark-700/40 p-2 cursor-pointer hover:bg-dark-700/70 transition-colors"
|
return (
|
||||||
onClick={() => navigate(`/timeline?date=${ev.date}`)}
|
<div key={i}
|
||||||
>
|
className={clsx(
|
||||||
<div className="flex items-start justify-between gap-2">
|
'rounded-lg border p-2 cursor-pointer transition-colors',
|
||||||
<span className="text-xs font-medium text-slate-300 leading-tight">{ev.title}</span>
|
active
|
||||||
<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')}>
|
? 'border-amber-600/50 bg-amber-900/20 hover:bg-amber-900/30'
|
||||||
{ev.level === 'long' ? 'LT' : ev.level === 'medium' ? 'MT' : 'CT'}
|
: 'border-slate-700/30 bg-dark-700/40 hover:bg-dark-700/70'
|
||||||
</span>
|
)}
|
||||||
|
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>
|
||||||
<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>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -455,9 +534,9 @@ function eventMatchesDriver(ev: SnapshotEvent, keywords: string[]): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function EventTimelineStrip({
|
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
|
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 leftPct(d: string) { return Math.max(0, Math.min(99, ((dateToMs(d) - chartStart) / totalMs) * 100)) }
|
||||||
function widthPct(start: string, end: string | null): number {
|
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
|
const endMs = end ? dateToMs(end) : dateToMs(start) + totalMs * 0.03
|
||||||
return Math.min(100, Math.max(0.4, ((endMs - dateToMs(start)) / totalMs) * 100))
|
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)
|
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 allKeywords = topDrivers.flatMap(d => d.keywords ?? [])
|
||||||
const matchedAnyIdxs = new Set(
|
const matchedAnyIdxs = new Set(
|
||||||
events.map((ev, i) => eventMatchesDriver(ev, allKeywords) ? i : -1).filter(i => i >= 0)
|
events.map((ev, i) => eventMatchesDriver(ev, allKeywords) ? i : -1).filter(i => i >= 0)
|
||||||
)
|
)
|
||||||
const unmatchedEvs = events.filter((_ev, i) => !matchedAnyIdxs.has(i))
|
const unmatchedEvs = events.filter((_ev, i) => !matchedAnyIdxs.has(i))
|
||||||
|
|
||||||
|
// Cursor position for the selected date
|
||||||
|
const cursorLeft = selectedDate ? leftPct(selectedDate) : null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-dark-900/40 rounded-xl border border-slate-700/40 px-4 py-3">
|
<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>
|
<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 ?? []))
|
const matchedEvs = events.filter(ev => eventMatchesDriver(ev, driver.keywords ?? []))
|
||||||
return (
|
return (
|
||||||
<div key={driver.key} className="flex items-center gap-2 h-6">
|
<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)}
|
<div className="flex items-center gap-1 shrink-0 w-28">
|
||||||
style={{ fontSize: 10 }}
|
<span className={clsx('text-xs font-semibold truncate', cols.label)}
|
||||||
title={driver.label}
|
style={{ fontSize: 10 }} title={driver.label}>
|
||||||
>
|
{driver.label}
|
||||||
{driver.label}
|
</span>
|
||||||
</span>
|
<DriverTypeBadge type={driver.type} />
|
||||||
{/* paddingRight aligns with chart right price scale (~60px) */}
|
</div>
|
||||||
<div className="relative flex-1 h-full" style={{ paddingRight: 62 }}>
|
<div className="relative flex-1 h-full" style={{ paddingRight: 62 }}>
|
||||||
{matchedEvs.map((ev, i) => {
|
{matchedEvs.map((ev, i) => {
|
||||||
const left = leftPct(ev.date)
|
const left = leftPct(ev.date)
|
||||||
@@ -523,6 +602,13 @@ function EventTimelineStrip({
|
|||||||
<div className="w-full border-t border-dashed border-slate-700/30" />
|
<div className="w-full border-t border-dashed border-slate-700/30" />
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -552,6 +638,12 @@ function EventTimelineStrip({
|
|||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -582,7 +674,10 @@ function DriversPanel({ instrumentId, drivers, onSave, onClose }: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addDriver() {
|
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) {
|
function removeDriver(i: number) {
|
||||||
@@ -634,6 +729,16 @@ function DriversPanel({ instrumentId, drivers, onSave, onClose }: {
|
|||||||
value={d.label}
|
value={d.label}
|
||||||
onChange={e => updateDriver(i, 'label', e.target.value)}
|
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">
|
<div className="flex items-center gap-1.5">
|
||||||
<span className="text-xs text-slate-500">Poids</span>
|
<span className="text-xs text-slate-500">Poids</span>
|
||||||
<input
|
<input
|
||||||
@@ -682,7 +787,6 @@ export default function InstrumentDashboard() {
|
|||||||
const [selectorOpen, setSelectorOpen] = useState(false)
|
const [selectorOpen, setSelectorOpen] = useState(false)
|
||||||
const [selectedDate, setSelectedDate] = useState<string | null>(null)
|
const [selectedDate, setSelectedDate] = useState<string | null>(null)
|
||||||
const [editDrivers, setEditDrivers] = useState(false)
|
const [editDrivers, setEditDrivers] = useState(false)
|
||||||
// Local drivers (updated optimistically after save)
|
|
||||||
const [localDrivers, setLocalDrivers] = useState<Driver[] | null>(null)
|
const [localDrivers, setLocalDrivers] = useState<Driver[] | null>(null)
|
||||||
|
|
||||||
const instrumentId = id.toUpperCase()
|
const instrumentId = id.toUpperCase()
|
||||||
@@ -720,7 +824,6 @@ export default function InstrumentDashboard() {
|
|||||||
if (date) setSelectedDate(date)
|
if (date) setSelectedDate(date)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// ── Lookup maps ───────────────────────────────────────────────────────────
|
|
||||||
const { priceMap, indMap, sortedDates, dateIndex } = useMemo(() => {
|
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> }
|
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> = {}
|
const priceMap: Record<string, PriceCandle> = {}
|
||||||
@@ -786,7 +889,6 @@ export default function InstrumentDashboard() {
|
|||||||
}
|
}
|
||||||
}, [effectiveDate, priceMap, indMap, sortedDates, dateIndex, snapshot])
|
}, [effectiveDate, priceMap, indMap, sortedDates, dateIndex, snapshot])
|
||||||
|
|
||||||
// ── UI ────────────────────────────────────────────────────────────────────
|
|
||||||
const grouped = CATEGORY_ORDER.map(cat => ({
|
const grouped = CATEGORY_ORDER.map(cat => ({
|
||||||
cat, label: CATEGORY_LABELS[cat] ?? cat,
|
cat, label: CATEGORY_LABELS[cat] ?? cat,
|
||||||
items: instruments.filter(i => i.category === cat),
|
items: instruments.filter(i => i.category === cat),
|
||||||
@@ -797,7 +899,6 @@ export default function InstrumentDashboard() {
|
|||||||
const dateLabel = effectiveDate ? fmtDateFR(effectiveDate) : '—'
|
const dateLabel = effectiveDate ? fmtDateFR(effectiveDate) : '—'
|
||||||
const displayPrice = dateTrend?.current_price ?? snapshot?.current_price
|
const displayPrice = dateTrend?.current_price ?? snapshot?.current_price
|
||||||
|
|
||||||
// Drivers to use (local override after edit, else from snapshot)
|
|
||||||
const activeDrivers = localDrivers ?? snapshot?.instrument?.drivers ?? []
|
const activeDrivers = localDrivers ?? snapshot?.instrument?.drivers ?? []
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -860,7 +961,6 @@ export default function InstrumentDashboard() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="ml-auto flex items-center gap-2">
|
<div className="ml-auto flex items-center gap-2">
|
||||||
{/* Drivers edit button */}
|
|
||||||
{snapshot && (
|
{snapshot && (
|
||||||
<button onClick={() => setEditDrivers(e => !e)}
|
<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',
|
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
|
Drivers
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{/* Period selector */}
|
|
||||||
<div className="flex items-center gap-1 bg-dark-800 border border-slate-700/40 rounded-xl p-1">
|
<div className="flex items-center gap-1 bg-dark-800 border border-slate-700/40 rounded-xl p-1">
|
||||||
{PERIODS.map(p => (
|
{PERIODS.map(p => (
|
||||||
<button key={p.key} onClick={() => setPeriod(p.key)}
|
<button key={p.key} onClick={() => setPeriod(p.key)}
|
||||||
@@ -916,6 +1015,7 @@ export default function InstrumentDashboard() {
|
|||||||
events={snapshot.events}
|
events={snapshot.events}
|
||||||
priceData={snapshot.price_data}
|
priceData={snapshot.price_data}
|
||||||
drivers={activeDrivers}
|
drivers={activeDrivers}
|
||||||
|
selectedDate={effectiveDate}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Date badge */}
|
{/* Date badge */}
|
||||||
@@ -944,10 +1044,12 @@ export default function InstrumentDashboard() {
|
|||||||
trend={dateTrend ?? snapshot.trend}
|
trend={dateTrend ?? snapshot.trend}
|
||||||
dateLabel={dateLabel}
|
dateLabel={dateLabel}
|
||||||
/>
|
/>
|
||||||
<EventsCard events={snapshot.events} />
|
<EventsCard
|
||||||
|
events={snapshot.events}
|
||||||
|
selectedDate={effectiveDate}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Drivers edit panel */}
|
|
||||||
{editDrivers && (
|
{editDrivers && (
|
||||||
<DriversPanel
|
<DriversPanel
|
||||||
instrumentId={instrumentId}
|
instrumentId={instrumentId}
|
||||||
@@ -957,7 +1059,6 @@ export default function InstrumentDashboard() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* AI Narrative */}
|
|
||||||
<NarrativeCard
|
<NarrativeCard
|
||||||
narrative={narrative}
|
narrative={narrative}
|
||||||
loading={loadingNarr}
|
loading={loadingNarr}
|
||||||
|
|||||||
Reference in New Issue
Block a user