diff --git a/backend/config/instruments.json b/backend/config/instruments.json index 234ffd5..f58e58b 100644 --- a/backend/config/instruments.json +++ b/backend/config/instruments.json @@ -4,450 +4,2366 @@ "id": "SPY", "name": "S&P 500 ETF", "yf_ticker": "SPY", + "saxo_quote_symbol": null, "ib_ticker": "SPY", "exchange": "CBOE", "category": "equity_index", "currency": "USD", "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": [ - {"key": "fed_policy", "label": "Fed Policy", "type": "event_calendar", "weight": 0.9, "keywords": ["FOMC","Fed","Federal Reserve","Powell","taux directeur","pivot","dot plot","QT","QE","taux Fed"]}, - {"key": "earnings_growth", "label": "Earnings Growth", "type": "report", "weight": 0.85, "keywords": ["Earnings","EPS","bénéfices","résultats","Earnings Season","reporting","profits"]}, - {"key": "real_rates", "label": "Real Rates", "type": "fundamental", "weight": 0.75, "keywords": ["taux réels","TIPS","real yields","breakeven","inflation","CPI","PCE"]}, - {"key": "liquidity", "label": "Global Liquidity", "type": "fundamental", "weight": 0.7, "keywords": ["liquidité","liquidity","QE","QT","bilan Fed","repo","M2","conditions financières"]}, - {"key": "vix", "label": "VIX / Fear", "type": "sentiment", "weight": 0.6, "keywords": ["VIX","volatilité","vol spike","hedging","protection","skew","put","peur","fear"]} + { + "key": "fed_policy", + "label": "Fed Policy", + "type": "event_calendar", + "weight": 0.9, + "keywords": [ + "FOMC", + "Fed", + "Federal Reserve", + "Powell", + "taux directeur", + "pivot", + "dot plot", + "QT", + "QE", + "taux Fed" + ] + }, + { + "key": "earnings_growth", + "label": "Earnings Growth", + "type": "report", + "weight": 0.85, + "keywords": [ + "Earnings", + "EPS", + "bénéfices", + "résultats", + "Earnings Season", + "reporting", + "profits" + ] + }, + { + "key": "real_rates", + "label": "Real Rates", + "type": "fundamental", + "weight": 0.75, + "keywords": [ + "taux réels", + "TIPS", + "real yields", + "breakeven", + "inflation", + "CPI", + "PCE" + ] + }, + { + "key": "liquidity", + "label": "Global Liquidity", + "type": "fundamental", + "weight": 0.7, + "keywords": [ + "liquidité", + "liquidity", + "QE", + "QT", + "bilan Fed", + "repo", + "M2", + "conditions financières" + ] + }, + { + "key": "vix", + "label": "VIX / Fear", + "type": "sentiment", + "weight": 0.6, + "keywords": [ + "VIX", + "volatilité", + "vol spike", + "hedging", + "protection", + "skew", + "put", + "peur", + "fear" + ] + } + ], + "regime_labels": [ + "Risk-On Bull", + "Risk-Off Bear", + "Stagflation", + "Rate Shock", + "Recovery" + ], + "event_keywords": [ + "FOMC", + "CPI", + "NFP", + "GDP", + "Earnings Season", + "Fed Chair", + "VIX" + ], + "related_assets": [ + "SPX", + "SPY", + "QQQ", + "ES" + ], + "correlation_instruments": [ + "QQQ", + "TLT", + "HYG", + "VXX" ], - "regime_labels": ["Risk-On Bull", "Risk-Off Bear", "Stagflation", "Rate Shock", "Recovery"], - "event_keywords": ["FOMC","CPI","NFP","GDP","Earnings Season","Fed Chair","VIX"], - "related_assets": ["SPX","SPY","QQQ","ES"], - "correlation_instruments": ["QQQ","TLT","HYG","VXX"], "ai_context": "SPY tracks the S&P 500. Bullish: dovish Fed, strong earnings, liquidity expansion. Bearish: rate hikes, recession, credit events. Watch: VIX, HYG spreads, FOMC." }, { "id": "QQQ", "name": "Nasdaq 100 ETF", "yf_ticker": "QQQ", + "saxo_quote_symbol": null, "ib_ticker": "QQQ", "exchange": "NASDAQ", "category": "equity_index", "currency": "USD", "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": [ - {"key": "real_rates", "label": "Real Rates", "type": "fundamental", "weight": 0.9, "keywords": ["taux réels","TIPS","real yields","FOMC","Fed","CPI","breakeven"]}, - {"key": "ai_capex", "label": "AI Capex", "type": "fundamental", "weight": 0.85, "keywords": ["IA","AI","Nvidia","Microsoft","capex tech","data center","GPU","intelligence artificielle","ChatGPT"]}, - {"key": "megacaps", "label": "Mégacaps GAFAM", "type": "report", "weight": 0.8, "keywords": ["GAFAM","Apple","Meta","Alphabet","Amazon","Microsoft","earnings tech","Big Tech","résultats tech"]}, - {"key": "growth", "label": "Croissance US", "type": "event_calendar", "weight": 0.75, "keywords": ["croissance","GDP","récession","NFP","emploi","PMI","croissance économique"]}, - {"key": "dollar", "label": "Dollar Index", "type": "technical", "weight": 0.5, "keywords": ["Dollar","DXY","dollar index","EUR/USD","devise"]} + { + "key": "real_rates", + "label": "Real Rates", + "type": "fundamental", + "weight": 0.9, + "keywords": [ + "taux réels", + "TIPS", + "real yields", + "FOMC", + "Fed", + "CPI", + "breakeven" + ] + }, + { + "key": "ai_capex", + "label": "AI Capex", + "type": "fundamental", + "weight": 0.85, + "keywords": [ + "IA", + "AI", + "Nvidia", + "Microsoft", + "capex tech", + "data center", + "GPU", + "intelligence artificielle", + "ChatGPT" + ] + }, + { + "key": "megacaps", + "label": "Mégacaps GAFAM", + "type": "report", + "weight": 0.8, + "keywords": [ + "GAFAM", + "Apple", + "Meta", + "Alphabet", + "Amazon", + "Microsoft", + "earnings tech", + "Big Tech", + "résultats tech" + ] + }, + { + "key": "growth", + "label": "Croissance US", + "type": "event_calendar", + "weight": 0.75, + "keywords": [ + "croissance", + "GDP", + "récession", + "NFP", + "emploi", + "PMI", + "croissance économique" + ] + }, + { + "key": "dollar", + "label": "Dollar Index", + "type": "technical", + "weight": 0.5, + "keywords": [ + "Dollar", + "DXY", + "dollar index", + "EUR/USD", + "devise" + ] + } + ], + "regime_labels": [ + "Tech Bull (AI-Driven)", + "Tech Bear", + "Rate Compression", + "Earnings Crash", + "Recovery" + ], + "event_keywords": [ + "FOMC", + "Big Tech Earnings", + "CPI", + "AI", + "Rate", + "NVDA", + "AAPL" + ], + "related_assets": [ + "QQQ", + "NDX", + "NQ" + ], + "correlation_instruments": [ + "SPY", + "TLT", + "NVDA", + "AAPL" ], - "regime_labels": ["Tech Bull (AI-Driven)", "Tech Bear", "Rate Compression", "Earnings Crash", "Recovery"], - "event_keywords": ["FOMC","Big Tech Earnings","CPI","AI","Rate","NVDA","AAPL"], - "related_assets": ["QQQ","NDX","NQ"], - "correlation_instruments": ["SPY","TLT","NVDA","AAPL"], "ai_context": "QQQ tracks Nasdaq 100. Bullish: dovish Fed, AI capex boom, Big Tech earnings. Bearish: rate hikes, earnings misses, AI regulation. Watch: real yields, mega-cap earnings." }, { "id": "IWM", "name": "Russell 2000 ETF", "yf_ticker": "IWM", + "saxo_quote_symbol": null, "ib_ticker": "IWM", "exchange": "NYSE", "category": "equity_index", "currency": "USD", "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": [ - {"key": "gdp_growth", "label": "Croissance US", "type": "event_calendar", "weight": 0.85, "keywords": ["GDP","PIB","croissance US","NFP","emploi","récession","PMI","domestic growth"]}, - {"key": "credit", "label": "Crédit & Spreads", "type": "fundamental", "weight": 0.8, "keywords": ["crédit","credit","HY","high yield","spreads","LBO","conditions de crédit","credit crunch"]}, - {"key": "regional_banks", "label": "Banques Régionales","type": "fundamental", "weight": 0.75, "keywords": ["banques régionales","SVB","FDIC","banque","banking","bank failure","crédit bancaire"]}, - {"key": "dollar", "label": "Dollar Index", "type": "technical", "weight": 0.6, "keywords": ["Dollar","DXY","dollar fort","USD strength"]} + { + "key": "gdp_growth", + "label": "Croissance US", + "type": "event_calendar", + "weight": 0.85, + "keywords": [ + "GDP", + "PIB", + "croissance US", + "NFP", + "emploi", + "récession", + "PMI", + "domestic growth" + ] + }, + { + "key": "credit", + "label": "Crédit & Spreads", + "type": "fundamental", + "weight": 0.8, + "keywords": [ + "crédit", + "credit", + "HY", + "high yield", + "spreads", + "LBO", + "conditions de crédit", + "credit crunch" + ] + }, + { + "key": "regional_banks", + "label": "Banques Régionales", + "type": "fundamental", + "weight": 0.75, + "keywords": [ + "banques régionales", + "SVB", + "FDIC", + "banque", + "banking", + "bank failure", + "crédit bancaire" + ] + }, + { + "key": "dollar", + "label": "Dollar Index", + "type": "technical", + "weight": 0.6, + "keywords": [ + "Dollar", + "DXY", + "dollar fort", + "USD strength" + ] + } + ], + "regime_labels": [ + "Small Cap Expansion", + "Credit Crunch Squeeze", + "Risk-Off", + "Recovery", + "Neutral" + ], + "event_keywords": [ + "FOMC", + "Credit", + "GDP", + "NFP", + "Rates", + "Regional Banks" + ], + "related_assets": [ + "IWM", + "RTY", + "RUT" + ], + "correlation_instruments": [ + "SPY", + "HYG", + "TLT" ], - "regime_labels": ["Small Cap Expansion", "Credit Crunch Squeeze", "Risk-Off", "Recovery", "Neutral"], - "event_keywords": ["FOMC","Credit","GDP","NFP","Rates","Regional Banks"], - "related_assets": ["IWM","RTY","RUT"], - "correlation_instruments": ["SPY","HYG","TLT"], "ai_context": "IWM tracks Russell 2000. Heavily credit-sensitive. Bullish: credit loosening, domestic growth. Bearish: credit tightening, recession, dollar strength. Watch: HYG spreads, regional bank health." }, { "id": "EEM", "name": "Emerging Markets ETF", "yf_ticker": "EEM", + "saxo_quote_symbol": null, "ib_ticker": "EEM", "exchange": "NYSE", "category": "equity_intl", "currency": "USD", "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": [ - {"key": "dollar_index", "label": "Dollar Index", "type": "technical", "weight": 0.9, "keywords": ["Dollar","DXY","USD","dollar index","dollar fort","faible dollar"]}, - {"key": "china_growth", "label": "Chine / PBoC", "type": "event_calendar", "weight": 0.85, "keywords": ["China","Chine","PBOC","PBoC","PIB chinois","Xi","immobilier chinois","stimulus Chine","relance Chine"]}, - {"key": "em_flows", "label": "Flux EM", "type": "sentiment", "weight": 0.75, "keywords": ["EM","émergents","emerging","flows","flux","capitaux émergents"]}, - {"key": "commodities", "label": "Matières Premières","type": "fundamental", "weight": 0.7, "keywords": ["matières premières","commodities","minerai","pétrole","métaux","commodité"]} + { + "key": "dollar_index", + "label": "Dollar Index", + "type": "technical", + "weight": 0.9, + "keywords": [ + "Dollar", + "DXY", + "USD", + "dollar index", + "dollar fort", + "faible dollar" + ] + }, + { + "key": "china_growth", + "label": "Chine / PBoC", + "type": "event_calendar", + "weight": 0.85, + "keywords": [ + "China", + "Chine", + "PBOC", + "PBoC", + "PIB chinois", + "Xi", + "immobilier chinois", + "stimulus Chine", + "relance Chine" + ] + }, + { + "key": "em_flows", + "label": "Flux EM", + "type": "sentiment", + "weight": 0.75, + "keywords": [ + "EM", + "émergents", + "emerging", + "flows", + "flux", + "capitaux émergents" + ] + }, + { + "key": "commodities", + "label": "Matières Premières", + "type": "fundamental", + "weight": 0.7, + "keywords": [ + "matières premières", + "commodities", + "minerai", + "pétrole", + "métaux", + "commodité" + ] + } + ], + "regime_labels": [ + "EM Growth Rally", + "Dollar Squeeze", + "China Risk-Off", + "Commodity Lift", + "Neutral" + ], + "event_keywords": [ + "China GDP", + "Dollar", + "EM", + "PBOC", + "Commodities", + "stimulus Chine" + ], + "related_assets": [ + "EEM", + "EM", + "CNY", + "China" + ], + "correlation_instruments": [ + "GLD", + "USO", + "EURUSD=X" ], - "regime_labels": ["EM Growth Rally", "Dollar Squeeze", "China Risk-Off", "Commodity Lift", "Neutral"], - "event_keywords": ["China GDP","Dollar","EM","PBOC","Commodities","stimulus Chine"], - "related_assets": ["EEM","EM","CNY","China"], - "correlation_instruments": ["GLD","USO","EURUSD=X"], "ai_context": "EEM tracks MSCI Emerging Markets. Bullish: PBOC stimulus, commodities rally, weak USD. Bearish: Fed tightening, China slowdown, trade tensions. Watch: USDCNY, China PMI, EM bond flows." }, { "id": "EFA", "name": "MSCI EAFE ETF", "yf_ticker": "EFA", + "saxo_quote_symbol": null, "ib_ticker": "EFA", "exchange": "NYSE", "category": "equity_intl", "currency": "USD", "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": [ - {"key": "ecb_policy", "label": "BCE / ECB", "type": "event_calendar", "weight": 0.85, "keywords": ["BCE","ECB","Lagarde","taux BCE","zone euro","politique BCE","ECB rate"]}, - {"key": "energy", "label": "Énergie Europe", "type": "fundamental", "weight": 0.8, "keywords": ["énergie","gaz européen","pétrole","LNG","crisis énergétique","energy","energy price"]}, - {"key": "eurusd", "label": "EUR/USD", "type": "technical", "weight": 0.75, "keywords": ["EUR/USD","euro dollar","eurusd","parité euro","euro fort","euro faible"]}, - {"key": "european_growth", "label": "Croissance Européenne", "type": "event_calendar", "weight": 0.7, "keywords": ["Europe","zone euro","Allemagne","PIB européen","PMI Europe","récession Europe","croissance européenne"]} + { + "key": "ecb_policy", + "label": "BCE / ECB", + "type": "event_calendar", + "weight": 0.85, + "keywords": [ + "BCE", + "ECB", + "Lagarde", + "taux BCE", + "zone euro", + "politique BCE", + "ECB rate" + ] + }, + { + "key": "energy", + "label": "Énergie Europe", + "type": "fundamental", + "weight": 0.8, + "keywords": [ + "énergie", + "gaz européen", + "pétrole", + "LNG", + "crisis énergétique", + "energy", + "energy price" + ] + }, + { + "key": "eurusd", + "label": "EUR/USD", + "type": "technical", + "weight": 0.75, + "keywords": [ + "EUR/USD", + "euro dollar", + "eurusd", + "parité euro", + "euro fort", + "euro faible" + ] + }, + { + "key": "european_growth", + "label": "Croissance Européenne", + "type": "event_calendar", + "weight": 0.7, + "keywords": [ + "Europe", + "zone euro", + "Allemagne", + "PIB européen", + "PMI Europe", + "récession Europe", + "croissance européenne" + ] + } + ], + "regime_labels": [ + "Global Risk-On", + "USD Strength Drag", + "Europe Recession", + "Recovery", + "Neutral" + ], + "event_keywords": [ + "ECB", + "EU GDP", + "Europe", + "Germany", + "BOJ", + "Japan", + "EUR" + ], + "related_assets": [ + "EFA", + "EAFE", + "EUR", + "EZU", + "SX5E" + ], + "correlation_instruments": [ + "EURUSD=X", + "EEM", + "TLT" ], - "regime_labels": ["Global Risk-On", "USD Strength Drag", "Europe Recession", "Recovery", "Neutral"], - "event_keywords": ["ECB","EU GDP","Europe","Germany","BOJ","Japan","EUR"], - "related_assets": ["EFA","EAFE","EUR","EZU","SX5E"], - "correlation_instruments": ["EURUSD=X","EEM","TLT"], "ai_context": "EFA tracks EAFE (Europe, Japan, Australia). Bullish: EUR/JPY strength, ECB/BOJ dovishness, global reflation. Bearish: USD strength, European recession, geopolitical disruption." }, { "id": "GLD", "name": "Gold ETF", "yf_ticker": "GLD", + "saxo_quote_symbol": null, "ib_ticker": "GLD", "exchange": "NYSE", "category": "metal", "currency": "USD", "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": [ - {"key": "real_rates_us", "label": "Taux Réels US", "type": "fundamental", "weight": 0.95, "keywords": ["taux réels","TIPS","real yields","Fed","FOMC","CPI","breakeven","inflation réelle"]}, - {"key": "dollar_index", "label": "Dollar Index", "type": "technical", "weight": 0.85, "keywords": ["Dollar","DXY","dollar index","USD","faible dollar","dollar fort"]}, - {"key": "inflation_expectations", "label": "Anticipations Inflation", "type": "fundamental", "weight": 0.75, "keywords": ["inflation","CPI","PCE","breakeven","attentes d'inflation","stagflation"]}, - {"key": "geopolitical_risk", "label": "Risque Géopolitique", "type": "geopolitical", "weight": 0.7, "keywords": ["géopolitique","guerre","Moyen-Orient","Iran","Russie","conflit","Chine","tension","crise","war"]}, - {"key": "cb_buying", "label": "Achats Banques Centrales","type": "fundamental", "weight": 0.5, "keywords": ["banque centrale","central bank","réserves","gold reserves","achats d'or","PBOC gold","dédollarisation"]} + { + "key": "real_rates_us", + "label": "Taux Réels US", + "type": "fundamental", + "weight": 0.95, + "keywords": [ + "taux réels", + "TIPS", + "real yields", + "Fed", + "FOMC", + "CPI", + "breakeven", + "inflation réelle" + ] + }, + { + "key": "dollar_index", + "label": "Dollar Index", + "type": "technical", + "weight": 0.85, + "keywords": [ + "Dollar", + "DXY", + "dollar index", + "USD", + "faible dollar", + "dollar fort" + ] + }, + { + "key": "inflation_expectations", + "label": "Anticipations Inflation", + "type": "fundamental", + "weight": 0.75, + "keywords": [ + "inflation", + "CPI", + "PCE", + "breakeven", + "attentes d'inflation", + "stagflation" + ] + }, + { + "key": "geopolitical_risk", + "label": "Risque Géopolitique", + "type": "geopolitical", + "weight": 0.7, + "keywords": [ + "géopolitique", + "guerre", + "Moyen-Orient", + "Iran", + "Russie", + "conflit", + "Chine", + "tension", + "crise", + "war" + ] + }, + { + "key": "cb_buying", + "label": "Achats Banques Centrales", + "type": "fundamental", + "weight": 0.5, + "keywords": [ + "banque centrale", + "central bank", + "réserves", + "gold reserves", + "achats d'or", + "PBOC gold", + "dédollarisation" + ] + } + ], + "regime_labels": [ + "Real Rates Falling", + "Safe Haven Rally", + "Inflation Hedge", + "Dollar Weakness", + "Range" + ], + "event_keywords": [ + "CPI", + "Fed", + "FOMC", + "Dollar", + "Geopolitical", + "Iran", + "Gold", + "guerre", + "inflation" + ], + "related_assets": [ + "GLD", + "GC", + "Gold", + "XAU" + ], + "correlation_instruments": [ + "TLT", + "SLV", + "EURUSD=X", + "USDJPY=X" ], - "regime_labels": ["Real Rates Falling", "Safe Haven Rally", "Inflation Hedge", "Dollar Weakness", "Range"], - "event_keywords": ["CPI","Fed","FOMC","Dollar","Geopolitical","Iran","Gold","guerre","inflation"], - "related_assets": ["GLD","GC","Gold","XAU"], - "correlation_instruments": ["TLT","SLV","EURUSD=X","USDJPY=X"], "ai_context": "GLD tracks physical gold. Primary driver: real rates (inverse). Bullish: falling real yields, dollar weakness, CB buying, geopolitical crises. Bearish: rising real yields, strong dollar." }, { "id": "SLV", "name": "Silver ETF", "yf_ticker": "SLV", + "saxo_quote_symbol": null, "ib_ticker": "SLV", "exchange": "NYSE", "category": "metal", "currency": "USD", "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": [ - {"key": "industrial_demand", "label": "Demande Industrielle", "type": "fundamental", "weight": 0.8, "keywords": ["industrie","manufacturing","PMI","production industrielle","fabrique","industry"]}, - {"key": "real_rates", "label": "Taux Réels", "type": "fundamental", "weight": 0.75, "keywords": ["taux réels","TIPS","Fed","FOMC","real yields"]}, - {"key": "dollar", "label": "Dollar Index", "type": "technical", "weight": 0.7, "keywords": ["Dollar","DXY","USD"]}, - {"key": "solar_ev_demand", "label": "Solaire / Véhicule EV","type": "fundamental", "weight": 0.65, "keywords": ["solaire","solar","EV","véhicule électrique","énergie verte","green energy","photovoltaïque","battery"]}, - {"key": "gold_ratio", "label": "Ratio Or/Argent", "type": "technical", "weight": 0.6, "keywords": ["Gold","or","ratio Or/Argent","gold silver","argent","silver"]} + { + "key": "industrial_demand", + "label": "Demande Industrielle", + "type": "fundamental", + "weight": 0.8, + "keywords": [ + "industrie", + "manufacturing", + "PMI", + "production industrielle", + "fabrique", + "industry" + ] + }, + { + "key": "real_rates", + "label": "Taux Réels", + "type": "fundamental", + "weight": 0.75, + "keywords": [ + "taux réels", + "TIPS", + "Fed", + "FOMC", + "real yields" + ] + }, + { + "key": "dollar", + "label": "Dollar Index", + "type": "technical", + "weight": 0.7, + "keywords": [ + "Dollar", + "DXY", + "USD" + ] + }, + { + "key": "solar_ev_demand", + "label": "Solaire / Véhicule EV", + "type": "fundamental", + "weight": 0.65, + "keywords": [ + "solaire", + "solar", + "EV", + "véhicule électrique", + "énergie verte", + "green energy", + "photovoltaïque", + "battery" + ] + }, + { + "key": "gold_ratio", + "label": "Ratio Or/Argent", + "type": "technical", + "weight": 0.6, + "keywords": [ + "Gold", + "or", + "ratio Or/Argent", + "gold silver", + "argent", + "silver" + ] + } + ], + "regime_labels": [ + "Industrial Demand", + "Safe Haven Flow", + "Dollar Weakness", + "Risk-Off", + "Range" + ], + "event_keywords": [ + "Manufacturing", + "PMI", + "CPI", + "Dollar", + "Silver", + "EV", + "Solar", + "taux réels" + ], + "related_assets": [ + "SLV", + "SI", + "Silver" + ], + "correlation_instruments": [ + "GLD", + "USO", + "EEM" ], - "regime_labels": ["Industrial Demand", "Safe Haven Flow", "Dollar Weakness", "Risk-Off", "Range"], - "event_keywords": ["Manufacturing","PMI","CPI","Dollar","Silver","EV","Solar","taux réels"], - "related_assets": ["SLV","SI","Silver"], - "correlation_instruments": ["GLD","USO","EEM"], "ai_context": "SLV tracks silver. Dual nature: monetary (like gold) + industrial (solar, EVs). Bullish: green energy demand, falling real rates, weak dollar. Bearish: manufacturing slowdown, strong dollar." }, { "id": "USO", "name": "WTI Oil ETF", "yf_ticker": "USO", + "saxo_quote_symbol": null, "ib_ticker": "USO", "exchange": "NYSE", "category": "energy", "currency": "USD", "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": [ - {"key": "opec_policy", "label": "OPEP+ / Production", "type": "event_calendar", "weight": 0.9, "keywords": ["OPEC","OPEP","production pétrolière","quotas","Arabie Saoudite","Saudi","réduction","coupes","OPEC meeting"]}, - {"key": "inventories", "label": "Stocks EIA", "type": "report", "weight": 0.8, "keywords": ["EIA","stocks pétroliers","inventaires","inventories","crude stocks","rapport EIA","crude supply"]}, - {"key": "geopolitical_risk","label": "Risque Géopolitique","type": "geopolitical", "weight": 0.75, "keywords": ["géopolitique","Moyen-Orient","Iran","Russie","conflit","guerre","war","Golfe Persique","tension"]}, - {"key": "global_demand", "label": "Demande Mondiale", "type": "fundamental", "weight": 0.85, "keywords": ["demande mondiale","China demand","IEA","croissance mondiale","global demand","reprise économique"]}, - {"key": "dollar", "label": "Dollar Index", "type": "technical", "weight": 0.7, "keywords": ["Dollar","DXY","USD"]} + { + "key": "opec_policy", + "label": "OPEP+ / Production", + "type": "event_calendar", + "weight": 0.9, + "keywords": [ + "OPEC", + "OPEP", + "production pétrolière", + "quotas", + "Arabie Saoudite", + "Saudi", + "réduction", + "coupes", + "OPEC meeting" + ] + }, + { + "key": "inventories", + "label": "Stocks EIA", + "type": "report", + "weight": 0.8, + "keywords": [ + "EIA", + "stocks pétroliers", + "inventaires", + "inventories", + "crude stocks", + "rapport EIA", + "crude supply" + ] + }, + { + "key": "geopolitical_risk", + "label": "Risque Géopolitique", + "type": "geopolitical", + "weight": 0.75, + "keywords": [ + "géopolitique", + "Moyen-Orient", + "Iran", + "Russie", + "conflit", + "guerre", + "war", + "Golfe Persique", + "tension" + ] + }, + { + "key": "global_demand", + "label": "Demande Mondiale", + "type": "fundamental", + "weight": 0.85, + "keywords": [ + "demande mondiale", + "China demand", + "IEA", + "croissance mondiale", + "global demand", + "reprise économique" + ] + }, + { + "key": "dollar", + "label": "Dollar Index", + "type": "technical", + "weight": 0.7, + "keywords": [ + "Dollar", + "DXY", + "USD" + ] + } + ], + "regime_labels": [ + "Supply Shock", + "Demand Recovery", + "Inventory Draw", + "Geopolitical Premium", + "Neutral" + ], + "event_keywords": [ + "OPEC", + "EIA", + "Inventories", + "Brent", + "WTI", + "Oil", + "Middle East", + "Saudi" + ], + "related_assets": [ + "USO", + "CL", + "WTI", + "Oil", + "Brent" + ], + "correlation_instruments": [ + "XOM", + "EEM", + "GLD", + "UNG" ], - "regime_labels": ["Supply Shock", "Demand Recovery", "Inventory Draw", "Geopolitical Premium", "Neutral"], - "event_keywords": ["OPEC","EIA","Inventories","Brent","WTI","Oil","Middle East","Saudi"], - "related_assets": ["USO","CL","WTI","Oil","Brent"], - "correlation_instruments": ["XOM","EEM","GLD","UNG"], "ai_context": "USO tracks WTI crude. Bullish: OPEC+ cuts, geopolitical disruption, demand recovery, weak dollar. Bearish: OPEC output increases, demand recession, inventory builds." }, { "id": "UNG", "name": "Natural Gas ETF", "yf_ticker": "UNG", + "saxo_quote_symbol": null, "ib_ticker": "UNG", "exchange": "NYSE", "category": "energy", "currency": "USD", "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": [ - {"key": "weather_seasonality", "label": "Météo / Saisonnalité","type": "fundamental", "weight": 0.9, "keywords": ["météo","weather","hiver","été","froid","chaleur","El Niño","La Niña","saison","vague de froid","heat wave"]}, - {"key": "storage", "label": "Stockage EIA Gaz", "type": "report", "weight": 0.85, "keywords": ["EIA Gas Storage","stockage gaz","gas storage","inventaires gaz","rapport stockage gaz"]}, - {"key": "lng_exports", "label": "Exports LNG", "type": "fundamental", "weight": 0.75, "keywords": ["LNG","GNL","exportations gaz","terminal LNG","gazoduc","liquefied natural gas"]}, - {"key": "production", "label": "Production US", "type": "fundamental", "weight": 0.7, "keywords": ["production gaz","shale gas","fracking","puits gaz","Marcellus","Appalachian","US gas output"]} + { + "key": "weather_seasonality", + "label": "Météo / Saisonnalité", + "type": "fundamental", + "weight": 0.9, + "keywords": [ + "météo", + "weather", + "hiver", + "été", + "froid", + "chaleur", + "El Niño", + "La Niña", + "saison", + "vague de froid", + "heat wave" + ] + }, + { + "key": "storage", + "label": "Stockage EIA Gaz", + "type": "report", + "weight": 0.85, + "keywords": [ + "EIA Gas Storage", + "stockage gaz", + "gas storage", + "inventaires gaz", + "rapport stockage gaz" + ] + }, + { + "key": "lng_exports", + "label": "Exports LNG", + "type": "fundamental", + "weight": 0.75, + "keywords": [ + "LNG", + "GNL", + "exportations gaz", + "terminal LNG", + "gazoduc", + "liquefied natural gas" + ] + }, + { + "key": "production", + "label": "Production US", + "type": "fundamental", + "weight": 0.7, + "keywords": [ + "production gaz", + "shale gas", + "fracking", + "puits gaz", + "Marcellus", + "Appalachian", + "US gas output" + ] + } + ], + "regime_labels": [ + "Winter Storage Fill", + "Supply Glut", + "Demand Surge", + "Export LNG Lift", + "Neutral" + ], + "event_keywords": [ + "EIA Gas Storage", + "LNG", + "Gas", + "Natural Gas", + "Weather", + "stockage gaz" + ], + "related_assets": [ + "UNG", + "NG", + "Natgas" + ], + "correlation_instruments": [ + "USO", + "XOM" ], - "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"], - "related_assets": ["UNG","NG","Natgas"], - "correlation_instruments": ["USO","XOM"], "ai_context": "UNG tracks natural gas (Henry Hub). Highly seasonal and weather-driven. Bullish: cold winter, low storage, LNG export growth. Bearish: warm weather, record storage builds." }, { "id": "TLT", "name": "US 20Y Treasury ETF", "yf_ticker": "TLT", + "saxo_quote_symbol": null, "ib_ticker": "TLT", "exchange": "NASDAQ", "category": "bond", "currency": "USD", "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": [ - {"key": "fed_path", "label": "Trajectoire Fed", "type": "event_calendar", "weight": 0.9, "keywords": ["FOMC","Fed","taux directeur","Fed hike","Fed cut","pivot","dot plot","Federal Reserve"]}, - {"key": "inflation_expectations", "label": "Inflation / CPI", "type": "fundamental", "weight": 0.85, "keywords": ["CPI","PCE","inflation","déflation","attentes","breakeven","inflation US"]}, - {"key": "growth", "label": "Croissance / NFP", "type": "event_calendar", "weight": 0.75, "keywords": ["GDP","NFP","récession","croissance","emploi","PMI","chômage","payrolls"]}, - {"key": "deficit_supply", "label": "Déficit / Offre Treasury","type": "fundamental", "weight": 0.7, "keywords": ["déficit","dette publique","Treasury auction","enchères","budget","fiscal","dette fédérale"]}, - {"key": "risk_aversion", "label": "Fuite Vers Qualité", "type": "sentiment", "weight": 0.65, "keywords": ["risk-off","crise","fuite vers la qualité","safe haven","vol spike","contagion","crise bancaire"]} + { + "key": "fed_path", + "label": "Trajectoire Fed", + "type": "event_calendar", + "weight": 0.9, + "keywords": [ + "FOMC", + "Fed", + "taux directeur", + "Fed hike", + "Fed cut", + "pivot", + "dot plot", + "Federal Reserve" + ] + }, + { + "key": "inflation_expectations", + "label": "Inflation / CPI", + "type": "fundamental", + "weight": 0.85, + "keywords": [ + "CPI", + "PCE", + "inflation", + "déflation", + "attentes", + "breakeven", + "inflation US" + ] + }, + { + "key": "growth", + "label": "Croissance / NFP", + "type": "event_calendar", + "weight": 0.75, + "keywords": [ + "GDP", + "NFP", + "récession", + "croissance", + "emploi", + "PMI", + "chômage", + "payrolls" + ] + }, + { + "key": "deficit_supply", + "label": "Déficit / Offre Treasury", + "type": "fundamental", + "weight": 0.7, + "keywords": [ + "déficit", + "dette publique", + "Treasury auction", + "enchères", + "budget", + "fiscal", + "dette fédérale" + ] + }, + { + "key": "risk_aversion", + "label": "Fuite Vers Qualité", + "type": "sentiment", + "weight": 0.65, + "keywords": [ + "risk-off", + "crise", + "fuite vers la qualité", + "safe haven", + "vol spike", + "contagion", + "crise bancaire" + ] + } + ], + "regime_labels": [ + "Safe Haven Bid", + "Rate Shock Selloff", + "QE Expectations", + "Inflation Selloff", + "Range" + ], + "event_keywords": [ + "FOMC", + "CPI", + "Fed", + "Treasury", + "Yield", + "Deficit", + "NFP" + ], + "related_assets": [ + "TLT", + "TNX", + "UST", + "Bonds" + ], + "correlation_instruments": [ + "SPY", + "GLD", + "HYG", + "USDJPY=X" ], - "regime_labels": ["Safe Haven Bid", "Rate Shock Selloff", "QE Expectations", "Inflation Selloff", "Range"], - "event_keywords": ["FOMC","CPI","Fed","Treasury","Yield","Deficit","NFP"], - "related_assets": ["TLT","TNX","UST","Bonds"], - "correlation_instruments": ["SPY","GLD","HYG","USDJPY=X"], "ai_context": "TLT tracks 20Y US Treasuries. Bullish: recession fears, Fed cuts, falling inflation, risk-off. Bearish: rising inflation, Fed hikes, fiscal deficit expansion, strong growth." }, { "id": "HYG", "name": "High Yield Bond ETF", "yf_ticker": "HYG", + "saxo_quote_symbol": null, "ib_ticker": "HYG", "exchange": "NYSE", "category": "credit", "currency": "USD", "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": [ - {"key": "default_rate", "label": "Taux de Défaut", "type": "fundamental", "weight": 0.85, "keywords": ["défauts","default","faillite","bankruptcy","spreads HY","taux de défaut","HY spreads"]}, - {"key": "growth", "label": "Croissance Économique", "type": "event_calendar", "weight": 0.8, "keywords": ["GDP","récession","croissance","PMI","emploi","payrolls","activité"]}, - {"key": "liquidity", "label": "Conditions de Liquidité","type": "fundamental", "weight": 0.8, "keywords": ["liquidité","crédit","QT","QE","conditions financières","FCO","financial conditions"]}, - {"key": "risk_appetite", "label": "Appétit pour le Risque", "type": "sentiment", "weight": 0.75, "keywords": ["risk-on","risk-off","appétit pour le risque","flux crédit","sentiment marché"]} + { + "key": "default_rate", + "label": "Taux de Défaut", + "type": "fundamental", + "weight": 0.85, + "keywords": [ + "défauts", + "default", + "faillite", + "bankruptcy", + "spreads HY", + "taux de défaut", + "HY spreads" + ] + }, + { + "key": "growth", + "label": "Croissance Économique", + "type": "event_calendar", + "weight": 0.8, + "keywords": [ + "GDP", + "récession", + "croissance", + "PMI", + "emploi", + "payrolls", + "activité" + ] + }, + { + "key": "liquidity", + "label": "Conditions de Liquidité", + "type": "fundamental", + "weight": 0.8, + "keywords": [ + "liquidité", + "crédit", + "QT", + "QE", + "conditions financières", + "FCO", + "financial conditions" + ] + }, + { + "key": "risk_appetite", + "label": "Appétit pour le Risque", + "type": "sentiment", + "weight": 0.75, + "keywords": [ + "risk-on", + "risk-off", + "appétit pour le risque", + "flux crédit", + "sentiment marché" + ] + } + ], + "regime_labels": [ + "Credit Expansion", + "Credit Crunch", + "Spread Compression", + "Recession Fear", + "Neutral" + ], + "event_keywords": [ + "Credit", + "Default", + "GDP", + "FOMC", + "Recession", + "HY", + "spreads" + ], + "related_assets": [ + "HYG", + "LQD", + "Credit", + "HY" + ], + "correlation_instruments": [ + "SPY", + "IWM", + "TLT" ], - "regime_labels": ["Credit Expansion", "Credit Crunch", "Spread Compression", "Recession Fear", "Neutral"], - "event_keywords": ["Credit","Default","GDP","FOMC","Recession","HY","spreads"], - "related_assets": ["HYG","LQD","Credit","HY"], - "correlation_instruments": ["SPY","IWM","TLT"], "ai_context": "HYG tracks US high-yield corporate bonds. Key credit stress barometer. Bullish: strong growth, low defaults, liquidity expansion. Bearish: recession, default cycle, credit crunch." }, { "id": "EURUSD=X", "name": "EUR/USD", "yf_ticker": "EURUSD=X", + "saxo_quote_symbol": null, "ib_ticker": "EUR.USD", "exchange": "FX", "category": "fx", "currency": "USD", "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": [ - {"key": "fed_ecb_differential", "label": "Différentiel Fed/BCE", "type": "event_calendar", "weight": 0.95, "keywords": ["BCE","ECB","Fed","FOMC","différentiel","taux directeurs","politique monétaire","rate differential"]}, - {"key": "rate_differential_2y", "label": "Taux 2 ans US-EUR", "type": "technical", "weight": 0.85, "keywords": ["taux 2 ans","Bund 2Y","Treasury 2Y","swap","différentiel de taux","2Y spread"]}, - {"key": "inflation_differential", "label": "Inflation US vs EUR", "type": "fundamental", "weight": 0.75, "keywords": ["CPI","inflation","PCE","HICP","zone euro","inflation différentiel"]}, - {"key": "dxy", "label": "Dollar Index DXY", "type": "technical", "weight": 0.7, "keywords": ["Dollar","DXY","dollar index","USD","dollar fort","faible dollar"]} + { + "key": "fed_ecb_differential", + "label": "Différentiel Fed/BCE", + "type": "event_calendar", + "weight": 0.95, + "keywords": [ + "BCE", + "ECB", + "Fed", + "FOMC", + "différentiel", + "taux directeurs", + "politique monétaire", + "rate differential" + ] + }, + { + "key": "rate_differential_2y", + "label": "Taux 2 ans US-EUR", + "type": "technical", + "weight": 0.85, + "keywords": [ + "taux 2 ans", + "Bund 2Y", + "Treasury 2Y", + "swap", + "différentiel de taux", + "2Y spread" + ] + }, + { + "key": "inflation_differential", + "label": "Inflation US vs EUR", + "type": "fundamental", + "weight": 0.75, + "keywords": [ + "CPI", + "inflation", + "PCE", + "HICP", + "zone euro", + "inflation différentiel" + ] + }, + { + "key": "dxy", + "label": "Dollar Index DXY", + "type": "technical", + "weight": 0.7, + "keywords": [ + "Dollar", + "DXY", + "dollar index", + "USD", + "dollar fort", + "faible dollar" + ] + } + ], + "regime_labels": [ + "EUR Strength (ECB Hawkish)", + "USD Strength (Fed Hawkish)", + "Risk-On EUR Rally", + "Risk-Off USD Safe", + "Range" + ], + "event_keywords": [ + "ECB", + "FOMC", + "CPI", + "EUR", + "Dollar", + "Fed", + "Eurozone", + "BCE" + ], + "related_assets": [ + "EUR", + "USD", + "EURUSD", + "6E" + ], + "correlation_instruments": [ + "GLD", + "EFA", + "TLT" ], - "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"], - "related_assets": ["EUR","USD","EURUSD","6E"], - "correlation_instruments": ["GLD","EFA","TLT"], "ai_context": "EUR/USD driven by Fed-ECB rate differential. EUR bullish: ECB hawkishness, European growth surprise, risk-on. EUR bearish: Fed hawkishness, European recession, risk-off USD demand." }, { "id": "USDJPY=X", "name": "USD/JPY", "yf_ticker": "USDJPY=X", + "saxo_quote_symbol": null, "ib_ticker": "USD.JPY", "exchange": "FX", "category": "fx", "currency": "JPY", "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": [ - {"key": "boj_policy", "label": "BOJ Policy", "type": "event_calendar", "weight": 0.95, "keywords": ["BOJ","Banque du Japon","Ueda","YCC","yield curve control","politique monétaire japonaise","BOJ rate","JGB"]}, - {"key": "rate_diff_us_jp","label": "Différentiel US-JP","type": "technical", "weight": 0.9, "keywords": ["différentiel US-Japon","JGB","UST","rendements US-JP","US Japan spread","10Y spread"]}, - {"key": "fed_policy", "label": "Fed Policy", "type": "event_calendar", "weight": 0.85, "keywords": ["Fed","FOMC","taux directeur US","hawkish Fed","Fed hike","Federal Reserve"]}, - {"key": "risk_appetite", "label": "Risk Appetite / Carry","type": "sentiment", "weight": 0.7, "keywords": ["risk-off","carry trade","yen safe haven","crise","carry unwind","yen refuge"]} + { + "key": "boj_policy", + "label": "BOJ Policy", + "type": "event_calendar", + "weight": 0.95, + "keywords": [ + "BOJ", + "Banque du Japon", + "Ueda", + "YCC", + "yield curve control", + "politique monétaire japonaise", + "BOJ rate", + "JGB" + ] + }, + { + "key": "rate_diff_us_jp", + "label": "Différentiel US-JP", + "type": "technical", + "weight": 0.9, + "keywords": [ + "différentiel US-Japon", + "JGB", + "UST", + "rendements US-JP", + "US Japan spread", + "10Y spread" + ] + }, + { + "key": "fed_policy", + "label": "Fed Policy", + "type": "event_calendar", + "weight": 0.85, + "keywords": [ + "Fed", + "FOMC", + "taux directeur US", + "hawkish Fed", + "Fed hike", + "Federal Reserve" + ] + }, + { + "key": "risk_appetite", + "label": "Risk Appetite / Carry", + "type": "sentiment", + "weight": 0.7, + "keywords": [ + "risk-off", + "carry trade", + "yen safe haven", + "crise", + "carry unwind", + "yen refuge" + ] + } + ], + "regime_labels": [ + "Carry Trade On", + "BOJ Tightening Surprise", + "Risk-Off JPY Safe Haven", + "Dollar Bull", + "YCC Adjustment" + ], + "event_keywords": [ + "BOJ", + "Fed", + "Japan", + "YCC", + "Carry", + "JPY", + "Yen", + "taux Japon" + ], + "related_assets": [ + "JPY", + "USDJPY", + "6J", + "Yen" + ], + "correlation_instruments": [ + "TLT", + "GLD", + "VXX" ], - "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"], - "related_assets": ["JPY","USDJPY","6J","Yen"], - "correlation_instruments": ["TLT","GLD","VXX"], "ai_context": "USD/JPY driven by US-Japan rate differential and BOJ yield curve control. USD bullish: Fed hikes, BOJ ultra-dovish. JPY bullish: BOJ tightening surprise, risk-off. Watch: BOJ meetings, 10Y UST-JGB spread." }, { "id": "GBPUSD=X", "name": "GBP/USD", "yf_ticker": "GBPUSD=X", + "saxo_quote_symbol": null, "ib_ticker": "GBP.USD", "exchange": "FX", "category": "fx", "currency": "USD", "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": [ - {"key": "boe_policy", "label": "BOE Policy", "type": "event_calendar", "weight": 0.9, "keywords": ["BOE","Bank of England","Bailey","taux UK","Royaume-Uni","Angleterre","UK rate","BOE meeting"]}, - {"key": "uk_inflation", "label": "Inflation UK", "type": "event_calendar", "weight": 0.8, "keywords": ["inflation UK","CPI UK","CPIH","RPI","inflation Angleterre"]}, - {"key": "uk_growth", "label": "Croissance UK", "type": "event_calendar", "weight": 0.75, "keywords": ["GDP UK","PMI UK","croissance UK","récession UK","UK economy","UK GDP"]}, - {"key": "political_risk","label": "Risque Politique UK","type": "geopolitical", "weight": 0.65, "keywords": ["politique UK","budget UK","Labour","Tories","fiscal UK","gilt","UK budget","Reeves"]} + { + "key": "boe_policy", + "label": "BOE Policy", + "type": "event_calendar", + "weight": 0.9, + "keywords": [ + "BOE", + "Bank of England", + "Bailey", + "taux UK", + "Royaume-Uni", + "Angleterre", + "UK rate", + "BOE meeting" + ] + }, + { + "key": "uk_inflation", + "label": "Inflation UK", + "type": "event_calendar", + "weight": 0.8, + "keywords": [ + "inflation UK", + "CPI UK", + "CPIH", + "RPI", + "inflation Angleterre" + ] + }, + { + "key": "uk_growth", + "label": "Croissance UK", + "type": "event_calendar", + "weight": 0.75, + "keywords": [ + "GDP UK", + "PMI UK", + "croissance UK", + "récession UK", + "UK economy", + "UK GDP" + ] + }, + { + "key": "political_risk", + "label": "Risque Politique UK", + "type": "geopolitical", + "weight": 0.65, + "keywords": [ + "politique UK", + "budget UK", + "Labour", + "Tories", + "fiscal UK", + "gilt", + "UK budget", + "Reeves" + ] + } + ], + "regime_labels": [ + "GBP Strength (BOE Hawkish)", + "USD Dominance", + "Political Risk GBP", + "UK Recession Pressure", + "Range" + ], + "event_keywords": [ + "BOE", + "Fed", + "UK", + "Britain", + "GBP", + "Pound", + "UK CPI" + ], + "related_assets": [ + "GBP", + "GBPUSD", + "6B", + "Pound" + ], + "correlation_instruments": [ + "EURUSD=X", + "TLT" ], - "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"], - "related_assets": ["GBP","GBPUSD","6B","Pound"], - "correlation_instruments": ["EURUSD=X","TLT"], "ai_context": "GBP/USD reflects BOE vs Fed differential plus UK political risk premium. GBP bullish: BOE hawkishness, strong UK data, political stability. GBP bearish: UK recession, political uncertainty." }, { "id": "VXX", "name": "VIX Tracker", "yf_ticker": "VXX", + "saxo_quote_symbol": null, "ib_ticker": "VXX", "exchange": "CBOE", "category": "volatility", "currency": "USD", "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": [ - {"key": "market_stress", "label": "Stress de Marché", "type": "geopolitical", "weight": 0.9, "keywords": ["krach","crash","crise","choc","panic","volatilité","vol spike","selloff","capitulation","contagion"]}, - {"key": "risk_appetite", "label": "Peur / Risk-Off", "type": "sentiment", "weight": 0.85, "keywords": ["risk-off","risk-on","sentiment","fear","peur","greed","appétit risque","VIX spike"]}, - {"key": "options_demand", "label": "Demande Options (Puts)", "type": "technical", "weight": 0.75, "keywords": ["options","puts","hedging","protection","skew","IV","couverture","implied vol","volatilité implicite"]}, - {"key": "macro_uncertainty", "label": "Incertitude Macro", "type": "event_calendar", "weight": 0.7, "keywords": ["FOMC","surprise","incertitude","géopolitique","macro","inflation choc","data surprise"]} + { + "key": "market_stress", + "label": "Stress de Marché", + "type": "geopolitical", + "weight": 0.9, + "keywords": [ + "krach", + "crash", + "crise", + "choc", + "panic", + "volatilité", + "vol spike", + "selloff", + "capitulation", + "contagion" + ] + }, + { + "key": "risk_appetite", + "label": "Peur / Risk-Off", + "type": "sentiment", + "weight": 0.85, + "keywords": [ + "risk-off", + "risk-on", + "sentiment", + "fear", + "peur", + "greed", + "appétit risque", + "VIX spike" + ] + }, + { + "key": "options_demand", + "label": "Demande Options (Puts)", + "type": "technical", + "weight": 0.75, + "keywords": [ + "options", + "puts", + "hedging", + "protection", + "skew", + "IV", + "couverture", + "implied vol", + "volatilité implicite" + ] + }, + { + "key": "macro_uncertainty", + "label": "Incertitude Macro", + "type": "event_calendar", + "weight": 0.7, + "keywords": [ + "FOMC", + "surprise", + "incertitude", + "géopolitique", + "macro", + "inflation choc", + "data surprise" + ] + } + ], + "regime_labels": [ + "Low Vol Complacency", + "Vol Spike Crisis", + "Vol Expansion", + "Vol Compression", + "Neutral" + ], + "event_keywords": [ + "VIX", + "Volatility", + "Crisis", + "FOMC", + "Market Stress", + "peur", + "vol spike" + ], + "related_assets": [ + "VXX", + "VIX", + "UVXY", + "SVXY" + ], + "correlation_instruments": [ + "SPY", + "TLT", + "GLD" ], - "regime_labels": ["Low Vol Complacency", "Vol Spike Crisis", "Vol Expansion", "Vol Compression", "Neutral"], - "event_keywords": ["VIX","Volatility","Crisis","FOMC","Market Stress","peur","vol spike"], - "related_assets": ["VXX","VIX","UVXY","SVXY"], - "correlation_instruments": ["SPY","TLT","GLD"], "ai_context": "VXX tracks short-term VIX futures. Spikes on: market crashes, geopolitical shocks, FOMC surprises. Mean-reverts due to futures roll decay. Watch: VIX term structure, SPX put skew." }, { "id": "AAPL", "name": "Apple Inc", "yf_ticker": "AAPL", + "saxo_quote_symbol": null, "ib_ticker": "AAPL", "exchange": "NASDAQ", "category": "stock", "currency": "USD", "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": [ - {"key": "iphone_cycle", "label": "Cycle iPhone", "type": "report", "weight": 0.85, "keywords": ["iPhone","Apple","AAPL earnings","supercycle","launch","iPhone 16","hardware"]}, - {"key": "china_sales", "label": "Ventes Chine", "type": "geopolitical", "weight": 0.8, "keywords": ["Chine","China","Huawei","ban Apple","restrictions Apple","China sales","marché chinois"]}, - {"key": "ai_integration","label": "Apple Intelligence / IA","type": "fundamental", "weight": 0.75, "keywords": ["Apple Intelligence","AI","IA","Siri","machine learning","IA Apple","generative AI"]}, - {"key": "margins", "label": "Services & Marges", "type": "report", "weight": 0.7, "keywords": ["services","App Store","iCloud","marge Apple","services revenue","abonnements"]}, - {"key": "fed_policy", "label": "Valorisation / Taux", "type": "event_calendar", "weight": 0.65, "keywords": ["Fed","taux","valorisation","taux d'intérêt","discount rate"]} + { + "key": "iphone_cycle", + "label": "Cycle iPhone", + "type": "report", + "weight": 0.85, + "keywords": [ + "iPhone", + "Apple", + "AAPL earnings", + "supercycle", + "launch", + "iPhone 16", + "hardware" + ] + }, + { + "key": "china_sales", + "label": "Ventes Chine", + "type": "geopolitical", + "weight": 0.8, + "keywords": [ + "Chine", + "China", + "Huawei", + "ban Apple", + "restrictions Apple", + "China sales", + "marché chinois" + ] + }, + { + "key": "ai_integration", + "label": "Apple Intelligence / IA", + "type": "fundamental", + "weight": 0.75, + "keywords": [ + "Apple Intelligence", + "AI", + "IA", + "Siri", + "machine learning", + "IA Apple", + "generative AI" + ] + }, + { + "key": "margins", + "label": "Services & Marges", + "type": "report", + "weight": 0.7, + "keywords": [ + "services", + "App Store", + "iCloud", + "marge Apple", + "services revenue", + "abonnements" + ] + }, + { + "key": "fed_policy", + "label": "Valorisation / Taux", + "type": "event_calendar", + "weight": 0.65, + "keywords": [ + "Fed", + "taux", + "valorisation", + "taux d'intérêt", + "discount rate" + ] + } + ], + "regime_labels": [ + "Growth Bull", + "Rate Squeeze", + "Earnings Beat", + "Tech Selloff", + "Neutral" + ], + "event_keywords": [ + "Apple Earnings", + "iPhone", + "Services", + "China", + "Fed", + "AI", + "AAPL" + ], + "related_assets": [ + "AAPL", + "Apple" + ], + "correlation_instruments": [ + "QQQ", + "NVDA", + "SPY" ], - "regime_labels": ["Growth Bull", "Rate Squeeze", "Earnings Beat", "Tech Selloff", "Neutral"], - "event_keywords": ["Apple Earnings","iPhone","Services","China","Fed","AI","AAPL"], - "related_assets": ["AAPL","Apple"], - "correlation_instruments": ["QQQ","NVDA","SPY"], "ai_context": "AAPL is world's largest market cap. Key drivers: iPhone cycle, Services revenue growth, China exposure, AI integration. Watch: quarterly earnings, China sales, Apple Intelligence traction." }, { "id": "NVDA", "name": "NVIDIA Corp", "yf_ticker": "NVDA", + "saxo_quote_symbol": null, "ib_ticker": "NVDA", "exchange": "NASDAQ", "category": "stock", "currency": "USD", "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": [ - {"key": "ai_capex_cycle", "label": "Capex IA / Hyperscalers","type": "fundamental", "weight": 0.95, "keywords": ["IA","AI","data center","capex","Nvidia","GPU","H100","Blackwell","NVLink","hyperscalers","infrastructure IA"]}, - {"key": "earnings_growth", "label": "Croissance Bénéfices", "type": "report", "weight": 0.9, "keywords": ["NVDA earnings","résultats Nvidia","revenues","data center revenue","beat","guidance"]}, - {"key": "data_center_demand","label": "Demande Data Center", "type": "fundamental", "weight": 0.85, "keywords": ["Amazon AWS","Microsoft Azure","Google Cloud","data center","cloud","computing","GPU demand"]}, - {"key": "export_restrictions","label": "Restrictions Export", "type": "geopolitical", "weight": 0.7, "keywords": ["export control","restrictions export","Chine","China ban","BIS","CISA","H20","chip ban"]}, - {"key": "rates", "label": "Taux / Valorisation", "type": "event_calendar", "weight": 0.6, "keywords": ["taux","valorisation","growth stock","multiples","discount rate","Fed"]} + { + "key": "ai_capex_cycle", + "label": "Capex IA / Hyperscalers", + "type": "fundamental", + "weight": 0.95, + "keywords": [ + "IA", + "AI", + "data center", + "capex", + "Nvidia", + "GPU", + "H100", + "Blackwell", + "NVLink", + "hyperscalers", + "infrastructure IA" + ] + }, + { + "key": "earnings_growth", + "label": "Croissance Bénéfices", + "type": "report", + "weight": 0.9, + "keywords": [ + "NVDA earnings", + "résultats Nvidia", + "revenues", + "data center revenue", + "beat", + "guidance" + ] + }, + { + "key": "data_center_demand", + "label": "Demande Data Center", + "type": "fundamental", + "weight": 0.85, + "keywords": [ + "Amazon AWS", + "Microsoft Azure", + "Google Cloud", + "data center", + "cloud", + "computing", + "GPU demand" + ] + }, + { + "key": "export_restrictions", + "label": "Restrictions Export", + "type": "geopolitical", + "weight": 0.7, + "keywords": [ + "export control", + "restrictions export", + "Chine", + "China ban", + "BIS", + "CISA", + "H20", + "chip ban" + ] + }, + { + "key": "rates", + "label": "Taux / Valorisation", + "type": "event_calendar", + "weight": 0.6, + "keywords": [ + "taux", + "valorisation", + "growth stock", + "multiples", + "discount rate", + "Fed" + ] + } + ], + "regime_labels": [ + "AI Bull Run", + "AI Hype Correction", + "Earnings Catalyst", + "Rate Compression", + "Consolidation" + ], + "event_keywords": [ + "NVIDIA Earnings", + "AI", + "Data Center", + "Export", + "GPU", + "NVDA", + "Blackwell" + ], + "related_assets": [ + "NVDA", + "NVIDIA", + "AI" + ], + "correlation_instruments": [ + "QQQ", + "AAPL", + "SPY" ], - "regime_labels": ["AI Bull Run", "AI Hype Correction", "Earnings Catalyst", "Rate Compression", "Consolidation"], - "event_keywords": ["NVIDIA Earnings","AI","Data Center","Export","GPU","NVDA","Blackwell"], - "related_assets": ["NVDA","NVIDIA","AI"], - "correlation_instruments": ["QQQ","AAPL","SPY"], "ai_context": "NVDA dominates AI infrastructure (H100/Blackwell GPUs). Bullish: AI capex spending, earnings beats, new GPU launches. Bearish: US-China export restrictions, AI spending slowdown." }, { "id": "GS", "name": "Goldman Sachs", "yf_ticker": "GS", + "saxo_quote_symbol": null, "ib_ticker": "GS", "exchange": "NYSE", "category": "stock", "currency": "USD", "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": [ - {"key": "yield_curve", "label": "Courbe des Taux", "type": "technical", "weight": 0.85, "keywords": ["courbe des taux","2Y-10Y","steepening","inversion","yield curve","10Y-2Y","pente"]}, - {"key": "ma_activity", "label": "M&A / Activité Deals", "type": "fundamental", "weight": 0.8, "keywords": ["M&A","fusion-acquisition","LBO","IPO","deal","advisory","boutique","transaction"]}, - {"key": "rates", "label": "Taux d'Intérêt", "type": "event_calendar", "weight": 0.8, "keywords": ["taux","Fed","intérêts","NIM","net interest margin","Fed rate","interest rates"]}, - {"key": "credit_conditions","label": "Conditions de Crédit", "type": "fundamental", "weight": 0.75, "keywords": ["crédit","spreads","conditions financières","leverage loans","credit tightening"]} + { + "key": "yield_curve", + "label": "Courbe des Taux", + "type": "technical", + "weight": 0.85, + "keywords": [ + "courbe des taux", + "2Y-10Y", + "steepening", + "inversion", + "yield curve", + "10Y-2Y", + "pente" + ] + }, + { + "key": "ma_activity", + "label": "M&A / Activité Deals", + "type": "fundamental", + "weight": 0.8, + "keywords": [ + "M&A", + "fusion-acquisition", + "LBO", + "IPO", + "deal", + "advisory", + "boutique", + "transaction" + ] + }, + { + "key": "rates", + "label": "Taux d'Intérêt", + "type": "event_calendar", + "weight": 0.8, + "keywords": [ + "taux", + "Fed", + "intérêts", + "NIM", + "net interest margin", + "Fed rate", + "interest rates" + ] + }, + { + "key": "credit_conditions", + "label": "Conditions de Crédit", + "type": "fundamental", + "weight": 0.75, + "keywords": [ + "crédit", + "spreads", + "conditions financières", + "leverage loans", + "credit tightening" + ] + } + ], + "regime_labels": [ + "Finance Bull", + "Recession Fear", + "Yield Curve Steepen", + "M&A Boom", + "Range" + ], + "event_keywords": [ + "Goldman", + "Financials", + "Yield Curve", + "M&A", + "Banking", + "Fed", + "Credit" + ], + "related_assets": [ + "GS", + "Goldman", + "XLF", + "Financials" + ], + "correlation_instruments": [ + "SPY", + "TLT", + "HYG" ], - "regime_labels": ["Finance Bull", "Recession Fear", "Yield Curve Steepen", "M&A Boom", "Range"], - "event_keywords": ["Goldman","Financials","Yield Curve","M&A","Banking","Fed","Credit"], - "related_assets": ["GS","Goldman","XLF","Financials"], - "correlation_instruments": ["SPY","TLT","HYG"], "ai_context": "Goldman benefits from: steep yield curve, M&A advisory boom, robust trading. Bullish: rate curve steepening, IPO/M&A surge. Bearish: yield curve inversion, recession, credit crisis." }, { "id": "XOM", "name": "ExxonMobil", "yf_ticker": "XOM", + "saxo_quote_symbol": null, "ib_ticker": "XOM", "exchange": "NYSE", "category": "stock", "currency": "USD", "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": [ - {"key": "oil_price", "label": "Prix du Pétrole", "type": "fundamental", "weight": 0.9, "keywords": ["WTI","Brent","pétrole","crude","oil price","prix pétrole","cours pétrole"]}, - {"key": "global_demand", "label": "Demande Mondiale", "type": "fundamental", "weight": 0.8, "keywords": ["demande mondiale","China demand","IEA","croissance","global demand","reprise"]}, - {"key": "opec_policy", "label": "OPEP+ Production", "type": "event_calendar", "weight": 0.75, "keywords": ["OPEC","OPEP","quotas","production","Saudi","Arabie Saoudite","réduction"]}, - {"key": "capex", "label": "Capex / Investissement","type": "fundamental", "weight": 0.6, "keywords": ["capex","investissement","forage","upstream","exploration","rig count"]}, - {"key": "dollar", "label": "Dollar Index", "type": "technical", "weight": 0.6, "keywords": ["Dollar","DXY","USD"]} + { + "key": "oil_price", + "label": "Prix du Pétrole", + "type": "fundamental", + "weight": 0.9, + "keywords": [ + "WTI", + "Brent", + "pétrole", + "crude", + "oil price", + "prix pétrole", + "cours pétrole" + ] + }, + { + "key": "global_demand", + "label": "Demande Mondiale", + "type": "fundamental", + "weight": 0.8, + "keywords": [ + "demande mondiale", + "China demand", + "IEA", + "croissance", + "global demand", + "reprise" + ] + }, + { + "key": "opec_policy", + "label": "OPEP+ Production", + "type": "event_calendar", + "weight": 0.75, + "keywords": [ + "OPEC", + "OPEP", + "quotas", + "production", + "Saudi", + "Arabie Saoudite", + "réduction" + ] + }, + { + "key": "capex", + "label": "Capex / Investissement", + "type": "fundamental", + "weight": 0.6, + "keywords": [ + "capex", + "investissement", + "forage", + "upstream", + "exploration", + "rig count" + ] + }, + { + "key": "dollar", + "label": "Dollar Index", + "type": "technical", + "weight": 0.6, + "keywords": [ + "Dollar", + "DXY", + "USD" + ] + } + ], + "regime_labels": [ + "Energy Bull", + "Demand Slowdown", + "OPEC Benefit", + "Green Transition Drag", + "Range" + ], + "event_keywords": [ + "Oil", + "OPEC", + "Energy", + "XOM", + "EIA", + "Crude", + "Exxon", + "WTI" + ], + "related_assets": [ + "XOM", + "Exxon", + "CL", + "Oil", + "Energy" + ], + "correlation_instruments": [ + "USO", + "UNG", + "EEM" ], - "regime_labels": ["Energy Bull", "Demand Slowdown", "OPEC Benefit", "Green Transition Drag", "Range"], - "event_keywords": ["Oil","OPEC","Energy","XOM","EIA","Crude","Exxon","WTI"], - "related_assets": ["XOM","Exxon","CL","Oil","Energy"], - "correlation_instruments": ["USO","UNG","EEM"], "ai_context": "XOM revenues highly correlated to oil/gas prices. Bullish: OPEC cuts, geopolitical supply risk, refining margins. Bearish: oil price collapse, global recession, energy transition." }, { "id": "BTC-USD", "name": "Bitcoin", "yf_ticker": "BTC-USD", + "saxo_quote_symbol": null, "ib_ticker": "BTC", "exchange": "Crypto", "category": "crypto", "currency": "USD", "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": [ - {"key": "global_liquidity","label": "Liquidité Mondiale", "type": "fundamental", "weight": 0.85, "keywords": ["liquidité mondiale","M2","Fed","QE","QT","conditions financières","liquidity","money supply"]}, - {"key": "etf_flows", "label": "Flux ETF Bitcoin", "type": "report", "weight": 0.85, "keywords": ["ETF Bitcoin","spot BTC ETF","BlackRock","Fidelity","flux ETF","ETF approval","IBIT","FBTC"]}, - {"key": "risk_appetite", "label": "Sentiment Risk-On", "type": "sentiment", "weight": 0.8, "keywords": ["risk-on","risk-off","crypto","sentiment","appétit pour le risque","halving","cycle crypto"]}, - {"key": "regulatory", "label": "Réglementation Crypto", "type": "geopolitical", "weight": 0.7, "keywords": ["réglementation crypto","SEC","CFTC","crypto ban","ETF approval","régulation","MiCA"]}, - {"key": "dollar", "label": "Dollar Index", "type": "technical", "weight": 0.65, "keywords": ["Dollar","DXY","USD","dédollarisation"]} + { + "key": "global_liquidity", + "label": "Liquidité Mondiale", + "type": "fundamental", + "weight": 0.85, + "keywords": [ + "liquidité mondiale", + "M2", + "Fed", + "QE", + "QT", + "conditions financières", + "liquidity", + "money supply" + ] + }, + { + "key": "etf_flows", + "label": "Flux ETF Bitcoin", + "type": "report", + "weight": 0.85, + "keywords": [ + "ETF Bitcoin", + "spot BTC ETF", + "BlackRock", + "Fidelity", + "flux ETF", + "ETF approval", + "IBIT", + "FBTC" + ] + }, + { + "key": "risk_appetite", + "label": "Sentiment Risk-On", + "type": "sentiment", + "weight": 0.8, + "keywords": [ + "risk-on", + "risk-off", + "crypto", + "sentiment", + "appétit pour le risque", + "halving", + "cycle crypto" + ] + }, + { + "key": "regulatory", + "label": "Réglementation Crypto", + "type": "geopolitical", + "weight": 0.7, + "keywords": [ + "réglementation crypto", + "SEC", + "CFTC", + "crypto ban", + "ETF approval", + "régulation", + "MiCA" + ] + }, + { + "key": "dollar", + "label": "Dollar Index", + "type": "technical", + "weight": 0.65, + "keywords": [ + "Dollar", + "DXY", + "USD", + "dédollarisation" + ] + } + ], + "regime_labels": [ + "Crypto Bull (ETF Inflows)", + "Risk-Off Crypto Selloff", + "Liquidity Expansion", + "Regulatory Fear", + "Consolidation" + ], + "event_keywords": [ + "Bitcoin", + "BTC", + "Crypto", + "ETF", + "Fed", + "Liquidity", + "Regulation", + "halving" + ], + "related_assets": [ + "BTC", + "Bitcoin", + "Crypto" + ], + "correlation_instruments": [ + "SPY", + "QQQ", + "GLD" ], - "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"], - "related_assets": ["BTC","Bitcoin","Crypto"], - "correlation_instruments": ["SPY","QQQ","GLD"], "ai_context": "Bitcoin: high-beta risk asset + emerging macro hedge. Bullish: Fed liquidity expansion, spot ETF inflows, halving cycle. Bearish: regulatory crackdown, risk-off, Fed tightening." } ] diff --git a/backend/routers/instruments.py b/backend/routers/instruments.py index c2f657d..20a79ba 100644 --- a/backend/routers/instruments.py +++ b/backend/routers/instruments.py @@ -15,11 +15,16 @@ from services.instrument_service import ( get_snapshot, get_narrative, update_instrument_drivers, + update_instrument_saxo_link, ) class DriverUpdate(BaseModel): drivers: List[Dict[str, Any]] + +class SaxoLinkBody(BaseModel): + saxo_symbol: Optional[str] = None + router = APIRouter(prefix="/api/instruments", tags=["instruments"]) @@ -90,6 +95,25 @@ def update_drivers(instrument_id: str, body: DriverUpdate) -> Dict[str, Any]: return {"ok": True, "instrument_id": instrument_id.upper(), "drivers_count": len(body.drivers)} +@router.put("/{instrument_id}/saxo-link") +def set_saxo_link(instrument_id: str, body: SaxoLinkBody) -> Dict[str, Any]: + """Link this instrument to the Saxo symbol used to price it (chart, indicators, regime, + wavelets) instead of yfinance — or pass null to unlink. Mirrors + routers/instruments_watchlist.py's /{ticker}/saxo-quote-link, but persisted in + instruments.json (this catalog's own store) rather than the instruments_watchlist table.""" + config = get_instrument(instrument_id) + if not config: + raise HTTPException(status_code=404, detail=f"Instrument '{instrument_id}' not found") + + saxo_symbol = (body.saxo_symbol or "").strip().upper() or None + try: + update_instrument_saxo_link(instrument_id, saxo_symbol) + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + return {"instrument_id": instrument_id.upper(), "saxo_quote_symbol": saxo_symbol} + + # ── Instrument mult (pips → price conversion) ───────────────────────────────── _INST_MULT: Dict[str, int] = {"EURUSD": 10000, "GBPUSD": 10000, "USDJPY": 100, "AUDUSD": 10000} diff --git a/backend/routers/wavelet.py b/backend/routers/wavelet.py index b60a250..61e0563 100644 --- a/backend/routers/wavelet.py +++ b/backend/routers/wavelet.py @@ -23,7 +23,26 @@ _PERIOD_TO_DAYS = { } -def _fetch_history(symbol: str, period: str, interval: str = "1d", start: Optional[str] = None, end: Optional[str] = None): +def _fetch_history(symbol: str, period: str, interval: str = "1d", start: Optional[str] = None, end: Optional[str] = None, saxo_symbol: Optional[str] = None): + """Saxo-first when saxo_symbol is given (resolved via the watchlist/Instrument Analysis + Saxo link), yfinance otherwise or as a silent fallback on any Saxo failure. Wavelets only + ever consume the close series, so unlike instrument_service's snapshot fetch this doesn't + need full OHLCV.""" + if saxo_symbol and start is None and end is None: + # Saxo's Chart API is day-count-from-now only (no explicit date range), so a custom + # start_date/end_date request can't be served from Saxo — falls through to yfinance. + try: + from services.database import get_saxo_catalog_by_symbol + from services.saxo_client import get_price_history + entry = get_saxo_catalog_by_symbol(saxo_symbol) + asset_type = entry["asset_type"] if entry else "FxSpot" + days = _PERIOD_TO_DAYS.get(period, 365) + bars = get_price_history(saxo_symbol, asset_type, days=days) + return [b["close"] for b in bars], [b["date"] for b in bars] + except Exception as e: + import logging + logging.getLogger(__name__).warning(f"[wavelet] Saxo fetch failed for '{saxo_symbol}', falling back to yfinance: {e}") + from services.data_fetcher import get_historical hist = get_historical(symbol, period=period, interval=interval, start=start, end=end) values = [h["close"] for h in hist] @@ -34,7 +53,7 @@ def _fetch_history(symbol: str, period: str, interval: str = "1d", start: Option def _fetch_padded_history( symbol: str, lookback: int, period: str = "1y", start_date: Optional[str] = None, end_date: Optional[str] = None, - future_padding_days: int = 0, + future_padding_days: int = 0, saxo_symbol: Optional[str] = None, ): """Fetch enough history to cover the requested causal-output range plus a `lookback`-sized warmup window before it (mirrors main.py's fetch_start padding). @@ -60,7 +79,7 @@ def _fetch_padded_history( if start_date: fetch_start = (date.fromisoformat(start_date) - timedelta(days=pad_days)).isoformat() fetch_end = (date.fromisoformat(end_date) + timedelta(days=future_padding_days)).isoformat() if end_date and future_padding_days else end_date - values, dates = _fetch_history(symbol, period, start=fetch_start, end=fetch_end) + values, dates = _fetch_history(symbol, period, start=fetch_start, end=fetch_end, saxo_symbol=saxo_symbol) return values, dates, start_date out_days = _PERIOD_TO_DAYS.get(period, 365) @@ -69,7 +88,7 @@ def _fetch_padded_history( (p for p, d in sorted(_PERIOD_TO_DAYS.items(), key=lambda kv: kv[1]) if d >= total_days), "10y", ) - values, dates = _fetch_history(symbol, fetch_period) + values, dates = _fetch_history(symbol, fetch_period, saxo_symbol=saxo_symbol) cutoff = (datetime.utcnow() - timedelta(days=out_days)).date().isoformat() return values, dates, cutoff @@ -84,10 +103,11 @@ def wavelet_analyze( method: str = Query("cwt", description="cwt (default) or ssq"), start_date: Optional[str] = Query(None, description="ISO date (YYYY-MM-DD) — overrides `period` with an explicit custom range"), end_date: Optional[str] = Query(None, description="ISO date (YYYY-MM-DD), only used alongside start_date; omit for 'through today'"), + saxo_symbol: Optional[str] = Query(None, description="Saxo quote symbol — Saxo-first, falls back to `symbol` (yfinance) on any failure"), ): from services.wavelet_engine import windowed_band_decompose, band_decompose_ssq - values, dates = _fetch_history(symbol, period, start=start_date, end=end_date) + values, dates = _fetch_history(symbol, period, start=start_date, end=end_date, saxo_symbol=saxo_symbol) if len(values) < 32: raise HTTPException(400, "Historique insuffisant pour une analyse ondelette (32 points minimum).") @@ -112,13 +132,14 @@ def wavelet_rolling( method: str = Query("cwt", description="cwt (default) or ssq"), start_date: Optional[str] = Query(None, description="ISO date (YYYY-MM-DD) — overrides `period`; the causal output starts here"), end_date: Optional[str] = Query(None, description="ISO date (YYYY-MM-DD), only used alongside start_date; omit for 'through today'"), + saxo_symbol: Optional[str] = Query(None, description="Saxo quote symbol — Saxo-first, falls back to `symbol` (yfinance) on any failure"), ): """Walk-forward version of /analyze: band values are computed day by day from a trailing `lookback`-point window only, so a trade simulation built on this never sees data past its own decision date.""" from services.wavelet_engine import rolling_causal_bands, rolling_causal_bands_ssq - values, dates, cutoff = _fetch_padded_history(symbol, lookback, period, start_date, end_date) + values, dates, cutoff = _fetch_padded_history(symbol, lookback, period, start_date, end_date, saxo_symbol=saxo_symbol) if len(values) < lookback + 32: raise HTTPException(400, "Historique insuffisant pour une analyse ondelette (32 points minimum).") @@ -158,6 +179,7 @@ def wavelet_reliability_endpoint( max_future_padding: int = Query(60, ge=10, le=180, description="extra real days fetched beyond the requested range, to cover the slowest band's own (data-driven) confirm horizon"), start_date: Optional[str] = Query(None, description="ISO date (YYYY-MM-DD) — overrides `period`; the causal output starts here"), end_date: Optional[str] = Query(None, description="ISO date (YYYY-MM-DD), only used alongside start_date; omit for 'through today'"), + saxo_symbol: Optional[str] = Query(None, description="Saxo quote symbol — Saxo-first, falls back to `symbol` (yfinance) on any failure"), ): """For every reversal a live (causal, walk-forward) decomposition would have flagged, checks whether redoing the decomposition later still shows the same reversal — a @@ -169,7 +191,7 @@ def wavelet_reliability_endpoint( # The horizon is now computed per band inside wavelet_reliability (from each band's own # measured cycle length), so we don't know it in advance here — pad generously enough to # cover even a slow band's cycle instead. - values, dates, cutoff = _fetch_padded_history(symbol, lookback, period, start_date, end_date, future_padding_days=max_future_padding) + values, dates, cutoff = _fetch_padded_history(symbol, lookback, period, start_date, end_date, future_padding_days=max_future_padding, saxo_symbol=saxo_symbol) if len(values) < lookback + max_future_padding + 32: raise HTTPException(400, "Historique insuffisant pour un test de fiabilité (32 points minimum au-delà de la fenêtre + marge).") diff --git a/backend/services/instrument_service.py b/backend/services/instrument_service.py index 7e9a198..d2a8926 100644 --- a/backend/services/instrument_service.py +++ b/backend/services/instrument_service.py @@ -75,6 +75,32 @@ def update_instrument_drivers(instrument_id: str, drivers: List[Dict]) -> None: logger.info(f"[instrument_service] Updated drivers for {uid} ({len(drivers)} drivers)") +def update_instrument_saxo_link(instrument_id: str, saxo_symbol: Optional[str]) -> None: + """Persist the Saxo quote-symbol link to instruments.json and refresh in-memory config. + saxo_symbol=None clears the link (falls back to yfinance).""" + global _configs + if _configs is None: + _load_configs() + + uid = instrument_id.upper() + if uid not in _configs: + raise ValueError(f"Instrument {uid} not found") + + with open(CONFIG_PATH, "r", encoding="utf-8") as f: + raw = json.load(f) + + for inst in raw["instruments"]: + if inst["id"] == uid: + inst["saxo_quote_symbol"] = saxo_symbol + break + + with open(CONFIG_PATH, "w", encoding="utf-8") as f: + json.dump(raw, f, ensure_ascii=False, indent=2) + + _configs[uid]["saxo_quote_symbol"] = saxo_symbol + logger.info(f"[instrument_service] Updated Saxo link for {uid}: {saxo_symbol}") + + # ── DataFrame helpers ────────────────────────────────────────────────────────── def _ohlcv_to_df(records: List[Dict]) -> pd.DataFrame: @@ -653,6 +679,46 @@ def _get_relevant_events( # ── Main snapshot ────────────────────────────────────────────────────────────── +# Approximate calendar-day span of each yfinance-style period string — used to size the +# Saxo `days` fetch window (Saxo's Chart API is day-count based, not period-string based). +# Same values as routers/wavelet.py's _PERIOD_TO_DAYS; duplicated locally since it's a +# tiny, stable lookup table not worth sharing across modules. +_PERIOD_TO_DAYS = { + "5d": 5, "1mo": 30, "3mo": 90, "6mo": 182, + "1y": 365, "2y": 730, "5y": 1825, "10y": 3650, "max": 3650, +} + + +def _fetch_ohlcv(config: Dict[str, Any], instrument_id: str, period: str, interval: str) -> Tuple[List[Dict], str]: + """Fetch OHLCV records for the snapshot — Saxo-first when the instrument has a + saxo_quote_symbol linked, yfinance otherwise (or as a silent fallback on any Saxo + failure). Returns (records, source).""" + saxo_symbol = config.get("saxo_quote_symbol") + if saxo_symbol: + try: + from services.database import get_saxo_catalog_by_symbol + from services.saxo_client import get_price_history + entry = get_saxo_catalog_by_symbol(saxo_symbol) + asset_type = entry["asset_type"] if entry else "FxSpot" + days = _PERIOD_TO_DAYS.get(period, 365) + bars = get_price_history(saxo_symbol, asset_type, days=days) + records = [{"date": b["date"], "open": b.get("open"), "high": b.get("high"), + "low": b.get("low"), "close": b.get("close"), "volume": b.get("volume")} + for b in bars] + return records, "saxo" + except Exception as e: + logger.warning(f"[instrument_service] Saxo fetch failed for {instrument_id} ({saxo_symbol}), falling back to yfinance: {e}") + + yf_ticker = config.get("yf_ticker", instrument_id) + try: + from services.data_fetcher import get_historical + records = get_historical(yf_ticker, period=period, interval=interval) + except Exception as e: + logger.error(f"[instrument_service] Data fetch failed for {instrument_id}: {e}") + records = [] + return records, "yfinance" + + async def get_snapshot( instrument_id: str, period: str = "1y", @@ -666,16 +732,7 @@ async def get_snapshot( if not config: return {"error": f"Unknown instrument: {instrument_id}"} - yf_ticker = config.get("yf_ticker", instrument_id) - - # Fetch OHLCV - try: - from services.data_fetcher import get_historical - records = get_historical(yf_ticker, period=period, interval=interval) - except Exception as e: - logger.error(f"[instrument_service] Data fetch failed for {instrument_id}: {e}") - records = [] - + records, source = _fetch_ohlcv(config, instrument_id, period, interval) df = _ohlcv_to_df(records) # Compute everything @@ -752,6 +809,7 @@ async def get_snapshot( "change_pct": change_pct, "change_abs": change_abs, "period": period, + "source": source, } diff --git a/backend/services/saxo_client.py b/backend/services/saxo_client.py index 60eae19..76e26f5 100644 --- a/backend/services/saxo_client.py +++ b/backend/services/saxo_client.py @@ -249,7 +249,9 @@ def get_price_history(symbol: str, asset_type: str = "FxSpot", days: int = 90) - Callers should treat any failure here (entitlement gap, still-wrong field names for some other asset type, etc.) as routine and fall back to another source, not surface it as a hard error. - Returns oldest-first [{"date": "YYYY-MM-DD", "close": float}, ...]. + Returns oldest-first [{"date": "YYYY-MM-DD", "close": float, "open": float|None, + "high": float|None, "low": float|None, "volume": float|None}, ...] — open/high/low/volume + are None when the raw bar doesn't carry them (e.g. FX Spot has no traded volume). """ instrument = resolve_instrument(symbol, asset_types=asset_type) data = _get("/chart/v3/charts", { @@ -271,7 +273,14 @@ def get_price_history(symbol: str, asset_type: str = "FxSpot", days: int = 90) - close = (bar["CloseBid"] + bar["CloseAsk"]) / 2 if close is None or not time_str: continue - out.append({"date": str(time_str)[:10], "close": float(close)}) + out.append({ + "date": str(time_str)[:10], + "close": float(close), + "open": float(bar["Open"]) if bar.get("Open") is not None else None, + "high": float(bar["High"]) if bar.get("High") is not None else None, + "low": float(bar["Low"]) if bar.get("Low") is not None else None, + "volume": float(bar["Volume"]) if bar.get("Volume") is not None else None, + }) if not out: raise ValueError(f"Chart data for '{symbol}' had no usable Close/CloseMid/CloseBid+CloseAsk/Time fields — raw bar keys: {list(bars[0].keys()) if bars else []}") return out diff --git a/frontend/src/components/SaxoLinkPicker.tsx b/frontend/src/components/SaxoLinkPicker.tsx new file mode 100644 index 0000000..3cb59d1 --- /dev/null +++ b/frontend/src/components/SaxoLinkPicker.tsx @@ -0,0 +1,106 @@ +import { useState } from 'react' +import { Link2, Unlink } from 'lucide-react' +import clsx from 'clsx' +import { useSaxoCatalog } from '../hooks/useApi' + +// Saxo has no shared naming convention with yfinance — its catalog search matches on +// Saxo's own instrument description text ("Brent Crude"), not the yfinance ticker code +// ("BZ=F"). This maps the common yfinance futures-root/index tickers to the plain-English +// name worth searching for, so opening a picker pre-fills something useful instead of an +// empty box the user has to guess into. Not exhaustive — anything not listed here (most +// FX pairs, where the Saxo symbol IS the yfinance root, e.g. "EURUSD=X" -> "EURUSD") falls +// back to a best-effort strip of yfinance's own suffix/prefix decoration. +const YFINANCE_SEARCH_HINTS: Record = { + 'BZ=F': 'Brent', 'CL=F': 'WTI Crude', 'NG=F': 'Natural Gas', + 'GC=F': 'Gold', 'SI=F': 'Silver', 'HG=F': 'Copper', 'PL=F': 'Platinum', + '^GSPC': 'S&P 500', '^NDX': 'Nasdaq 100', '^DJI': 'Dow Jones', '^RUT': 'Russell 2000', + '^VIX': 'VIX', 'IEF': 'Treasury', +} + +export function guessSaxoSearchHint(ticker: string): string { + if (YFINANCE_SEARCH_HINTS[ticker]) return YFINANCE_SEARCH_HINTS[ticker] + return ticker.replace(/=F$|=X$|^\^/g, '') +} + +export const SAXO_LINK_KIND_META = { + option: { + label: 'Option', placeholder: 'Search Saxo option symbol…', + activeCls: 'text-emerald-400 hover:text-emerald-300 bg-emerald-900/20 border-emerald-700/30', + assetTypes: 'FuturesOption,FxVanillaOption,StockOption,StockIndexOption', + }, + quote: { + label: 'Quote', placeholder: 'Search Saxo spot/futures symbol…', + activeCls: 'text-sky-400 hover:text-sky-300 bg-sky-900/20 border-sky-700/30', + assetTypes: 'ContractFutures,CfdOnFutures,FxSpot,StockIndex', + }, +} as const + +export type SaxoLinkKind = keyof typeof SAXO_LINK_KIND_META + +// Generic Saxo symbol linker — ticker/saxoSymbol are display data, onSave/isPending are +// injected by the caller so this can drive any backing store (instruments_watchlist's +// saxo_option_symbol/saxo_quote_symbol columns via Config.tsx, or instruments.json's +// saxo_quote_symbol field via InstrumentDashboard.tsx) without knowing which one it is. +export default function SaxoLinkPicker({ ticker, kind, saxoSymbol, onSave, isPending }: { + ticker: string + kind: SaxoLinkKind + saxoSymbol: string | null + onSave: (symbol: string | null) => void + isPending: boolean +}) { + const [editing, setEditing] = useState(false) + const [value, setValue] = useState(saxoSymbol ?? '') + const [showDropdown, setShowDropdown] = useState(false) + const meta = SAXO_LINK_KIND_META[kind] + const { data: catalogMatches } = useSaxoCatalog(meta.assetTypes, value.length >= 2 ? value : undefined) + + const save = (sym?: string) => { + const resolved = (sym ?? value).trim().toUpperCase() || null + onSave(resolved) + setEditing(false) + } + + if (!editing) { + return saxoSymbol ? ( + + ) : ( + + ) + } + + return ( +
+ { setValue(e.target.value); setShowDropdown(true) }} + onFocus={() => setShowDropdown(true)} + onKeyDown={e => { if (e.key === 'Enter') save(); if (e.key === 'Escape') setEditing(false) }} + onBlur={() => save()} + placeholder={meta.placeholder} + className="bg-dark-800 border border-slate-700/40 rounded px-1.5 py-0.5 text-[9px] text-white w-28 focus:outline-none focus:border-blue-500/50" + /> + {isPending && } + {showDropdown && (catalogMatches ?? []).length > 0 && ( +
+ {(catalogMatches ?? []).map((c: any) => ( + + ))} +
+ )} +
+ ) +} diff --git a/frontend/src/hooks/useApi.ts b/frontend/src/hooks/useApi.ts index c95ad4b..303a4ae 100644 --- a/frontend/src/hooks/useApi.ts +++ b/frontend/src/hooks/useApi.ts @@ -200,6 +200,17 @@ export const useSetWatchlistSaxoQuoteLink = () => { }) } +// Links an Instrument Analysis instrument (backend/config/instruments.json — a separate +// catalog from instruments_watchlist above) to a Saxo quote symbol, so its chart/indicators/ +// wavelets are Saxo-sourced instead of yfinance. InstrumentDashboard.tsx doesn't use React +// Query for its own data (plain axios + useState), so this mutation carries no +// invalidateQueries — the caller re-fetches the instrument list/snapshot itself on success. +export const useSetInstrumentSaxoLink = () => + useMutation({ + mutationFn: ({ instrumentId, saxoSymbol }: { instrumentId: string; saxoSymbol: string | null }) => + api.put(`/instruments/${encodeURIComponent(instrumentId)}/saxo-link`, { saxo_symbol: saxoSymbol }).then(r => r.data), + }) + // Free-form display name — no longer tied to yfinance's long_name lookup, since an // instrument can now be entirely Saxo-sourced. export const useRenameWatchlistInstrument = () => { diff --git a/frontend/src/pages/Config.tsx b/frontend/src/pages/Config.tsx index 9f43db9..f230145 100644 --- a/frontend/src/pages/Config.tsx +++ b/frontend/src/pages/Config.tsx @@ -4,6 +4,7 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { useSources, useUpdateSources, useUpdateApiKeys, useConfig, useAiStatus, useAnalysisConfig, useSaveAnalysisConfig, useCycleStatus, useUpdateCycleConfig, useTriggerCycle, useCycleStepCatalog, useRiskProfiles, useUpsertProfile, useDeleteProfile, useExitDefaults, useSaveExitDefaults, useOptionsGate, useSaveOptionsGate, useTechIndicatorsConfig, useSaveTechIndicatorsConfig, useInstrumentsWatchlist, useAddWatchlistInstrument, useRemoveWatchlistInstrument, useSetWatchlistSaxoOptionLink, useSetWatchlistSaxoQuoteLink, useRenameWatchlistInstrument, useSaxoStatus, useDisconnectSaxo, useSaxoWatchlist, useUpdateSaxoWatchlist, useSnapshotSaxoNow, useValidateSaxoWatchlist, useSaxoCatalog, useSaxoCatalogSummary, useRefreshSaxoCatalog, useTestSaxoQuote, useSaxoSettings, useUpdateSaxoSettings, useSnapshotAllSaxoNow, useExpandSaxoWatchlist, type CycleStepDef } from '../hooks/useApi' import { Settings, Key, Globe, CheckCircle, XCircle, AlertCircle, Save, Eye, EyeOff, Brain, SlidersHorizontal, RefreshCw, Zap, Plus, Trash2, Pencil, X, Lock, Gauge, DollarSign, TrendingUp, ShieldAlert, DatabaseBackup, Radar, Link2, Unlink, Camera, ShieldCheck, ExternalLink } from 'lucide-react' import clsx from 'clsx' +import SaxoLinkPicker from '../components/SaxoLinkPicker' const API = '' @@ -338,101 +339,6 @@ function RiskProfilesCard() { // scheduler's watchlist (Config -> Saxo below) so it gets snapshotted. // 'quote' — which Saxo spot/futures symbol prices this instrument in the Cockpit, // replacing yfinance's unadjusted continuous-futures tickers. -// Asset-type filters matching backend services.saxo_client.OPTION_ASSET_TYPES / -// UNDERLYING_ASSET_TYPES — keeps each picker's search scoped to the catalog rows that -// are actually relevant, instead of both searching the same undifferentiated list. -// Saxo has no shared naming convention with yfinance — its catalog search matches on -// Saxo's own instrument description text ("Brent Crude"), not the yfinance ticker code -// ("BZ=F"). This maps the common yfinance futures-root/index tickers to the plain-English -// name worth searching for, so opening a picker pre-fills something useful instead of an -// empty box the user has to guess into. Not exhaustive — anything not listed here (most -// FX pairs, where the Saxo symbol IS the yfinance root, e.g. "EURUSD=X" -> "EURUSD") falls -// back to a best-effort strip of yfinance's own suffix/prefix decoration. -const YFINANCE_SEARCH_HINTS: Record = { - 'BZ=F': 'Brent', 'CL=F': 'WTI Crude', 'NG=F': 'Natural Gas', - 'GC=F': 'Gold', 'SI=F': 'Silver', 'HG=F': 'Copper', 'PL=F': 'Platinum', - '^GSPC': 'S&P 500', '^NDX': 'Nasdaq 100', '^DJI': 'Dow Jones', '^RUT': 'Russell 2000', - '^VIX': 'VIX', 'IEF': 'Treasury', -} - -function guessSaxoSearchHint(ticker: string): string { - if (YFINANCE_SEARCH_HINTS[ticker]) return YFINANCE_SEARCH_HINTS[ticker] - return ticker.replace(/=F$|=X$|^\^/g, '') -} - -const SAXO_LINK_KIND_META = { - option: { - label: 'Option', placeholder: 'Search Saxo option symbol…', - activeCls: 'text-emerald-400 hover:text-emerald-300 bg-emerald-900/20 border-emerald-700/30', - assetTypes: 'FuturesOption,FxVanillaOption,StockOption,StockIndexOption', - }, - quote: { - label: 'Quote', placeholder: 'Search Saxo spot/futures symbol…', - activeCls: 'text-sky-400 hover:text-sky-300 bg-sky-900/20 border-sky-700/30', - assetTypes: 'ContractFutures,CfdOnFutures,FxSpot,StockIndex', - }, -} as const - -function SaxoLinkPicker({ ticker, kind, saxoSymbol }: { ticker: string; kind: keyof typeof SAXO_LINK_KIND_META; saxoSymbol: string | null }) { - const [editing, setEditing] = useState(false) - const [value, setValue] = useState(saxoSymbol ?? '') - const [showDropdown, setShowDropdown] = useState(false) - const optionLink = useSetWatchlistSaxoOptionLink() - const quoteLink = useSetWatchlistSaxoQuoteLink() - const { mutate: setLink, isPending } = kind === 'option' ? optionLink : quoteLink - const meta = SAXO_LINK_KIND_META[kind] - const { data: catalogMatches } = useSaxoCatalog(meta.assetTypes, value.length >= 2 ? value : undefined) - - const save = (sym?: string) => { - const resolved = (sym ?? value).trim().toUpperCase() || null - setLink({ ticker, saxoSymbol: resolved }, { onSuccess: () => setEditing(false) }) - } - - if (!editing) { - return saxoSymbol ? ( - - ) : ( - - ) - } - - return ( -
- { setValue(e.target.value); setShowDropdown(true) }} - onFocus={() => setShowDropdown(true)} - onKeyDown={e => { if (e.key === 'Enter') save(); if (e.key === 'Escape') setEditing(false) }} - onBlur={() => save()} - placeholder={meta.placeholder} - className="bg-dark-800 border border-slate-700/40 rounded px-1.5 py-0.5 text-[9px] text-white w-28 focus:outline-none focus:border-blue-500/50" - /> - {isPending && } - {showDropdown && (catalogMatches ?? []).length > 0 && ( -
- {(catalogMatches ?? []).map((c: any) => ( - - ))} -
- )} -
- ) -} - // Inline-editable display name — the ticker (primary key) no longer has to be a // yfinance-recognized code now that an instrument can be entirely Saxo-sourced, so the // name shouldn't be stuck at whatever yfinance's long_name lookup produced (often just @@ -474,6 +380,8 @@ function WatchlistCard() { const { data: items, isLoading } = useInstrumentsWatchlist() const { mutateAsync: addTicker, isPending: adding } = useAddWatchlistInstrument() const { mutate: removeTicker } = useRemoveWatchlistInstrument() + const optionLink = useSetWatchlistSaxoOptionLink() + const quoteLink = useSetWatchlistSaxoQuoteLink() const [input, setInput] = useState('') const [error, setError] = useState('') const [note, setNote] = useState('') @@ -543,8 +451,12 @@ function WatchlistCard() { {item.asset_class}
- - + optionLink.mutate({ ticker: item.ticker, saxoSymbol: sym })} /> + quoteLink.mutate({ ticker: item.ticker, saxoSymbol: sym })} /> diff --git a/frontend/src/pages/InstrumentDashboard.tsx b/frontend/src/pages/InstrumentDashboard.tsx index 22675af..ff6307b 100644 --- a/frontend/src/pages/InstrumentDashboard.tsx +++ b/frontend/src/pages/InstrumentDashboard.tsx @@ -2,12 +2,14 @@ import { useState, useEffect, useCallback, useMemo, useRef } from 'react' import { useParams, useNavigate, useSearchParams } from 'react-router-dom' import { Sparkles, RefreshCw, ChevronDown, TrendingUp, TrendingDown, - Minus, BarChart2, Clock, Calendar, AlertCircle, + Minus, BarChart2, Clock, Calendar, AlertCircle, Link2, } from 'lucide-react' import axios from 'axios' import clsx from 'clsx' import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts' import InstrumentChart, { TheoPoint } from '../components/InstrumentChart' +import SaxoLinkPicker from '../components/SaxoLinkPicker' +import { useSetInstrumentSaxoLink } from '../hooks/useApi' import { fmtPrice } from '../lib/format' const api = axios.create({ baseURL: '/api' }) @@ -122,6 +124,7 @@ interface InstrumentConfig { regime_labels: string[] chart: { ma_periods: number[]; show_volume: boolean } correlation_instruments: string[] + saxo_quote_symbol?: string | null } interface PriceCandle { time: string; open: number; high: number; low: number; close: number; volume: number } @@ -182,6 +185,7 @@ interface Snapshot { trend: TrendMetrics events: SnapshotEvent[] current_price: number; change_pct: number; change_abs: number; period: string + source?: 'saxo' | 'yfinance' } // ── Helpers ─────────────────────────────────────────────────────────────────── @@ -1419,6 +1423,7 @@ export default function InstrumentDashboard({ instrumentIdProp, isVisible }: { i const [period, setPeriod] = useState('1y') const [chartStyle, setChartStyle] = useState<'candles' | 'line'>('candles') const [instruments, setInstruments] = useState([]) + const setSaxoLink = useSetInstrumentSaxoLink() const [snapshot, setSnapshot] = useState(null) const [narrative, setNarrative] = useState('') const [loading, setLoading] = useState(false) @@ -1641,6 +1646,7 @@ export default function InstrumentDashboard({ instrumentIdProp, isVisible }: { i try { const path = waveletCausal ? '/wavelet/rolling' : '/wavelet/analyze' const params: any = { symbol: selected.yf_ticker, period, levels: waveletLevels, wavelet: waveletFamily, method: waveletMethod } + if (selected.saxo_quote_symbol) params.saxo_symbol = selected.saxo_quote_symbol if (waveletCausal) params.lookback = waveletLookback const { data } = await api.get(path, { params }) setWaveletData(data) @@ -1661,6 +1667,7 @@ export default function InstrumentDashboard({ instrumentIdProp, isVisible }: { i params: { symbol: selected.yf_ticker, period, levels: waveletLevels, wavelet: waveletFamily, method: waveletMethod, lookback: waveletLookback, + ...(selected.saxo_quote_symbol ? { saxo_symbol: selected.saxo_quote_symbol } : {}), }, }) setWaveletReliability(data) @@ -1672,6 +1679,19 @@ export default function InstrumentDashboard({ instrumentIdProp, isVisible }: { i } } + // Re-fetch the instrument catalog (source of `selected.saxo_quote_symbol`) and the + // current snapshot after linking/unlinking a Saxo symbol — this component manages its + // own data via useState/axios rather than React Query, so there's no cache to invalidate. + const handleSaxoLinkSave = (saxoSymbol: string | null) => { + if (!instrumentId) return + setSaxoLink.mutate({ instrumentId, saxoSymbol }, { + onSuccess: () => { + api.get('/instruments').then(r => setInstruments(r.data)).catch(() => {}) + fetchSnapshot() + }, + }) + } + const waveletChartData = useMemo(() => { if (!waveletData) return [] const { dates, original, bands, mean } = waveletData @@ -1907,6 +1927,11 @@ export default function InstrumentDashboard({ instrumentIdProp, isVisible }: { i )} + {selected && ( + + )} + {displayPrice !== undefined && (
@@ -1917,6 +1942,11 @@ export default function InstrumentDashboard({ instrumentIdProp, isVisible }: { i {(snapshot.change_pct ?? 0) >= 0 ? '+' : ''}{fmtPrice(snapshot.change_abs)} ({(snapshot.change_pct ?? 0) >= 0 ? '+' : ''}{snapshot.change_pct?.toFixed(2)}%) )} + {snapshot?.source === 'saxo' && ( + + ● Saxo + + )}
)}