""" Geopolitical pattern engine. Scores current events against historical templates and generates trade signals. """ from datetime import datetime, timedelta from typing import List, Dict, Any, Optional import json # ── Pattern Taxonomy Tree ───────────────────────────────────────────────────── PATTERN_TAXONOMY = { "id": "root", "label": "Pattern Library", "children": [ { "id": "geopolitical", "label": "Geopolitical", "children": [ { "id": "armed_conflict", "label": "Armed Conflict", "children": [ {"id": "armed_conflict.middle_east", "label": "Middle East"}, {"id": "armed_conflict.europe", "label": "Europe / Ukraine"}, {"id": "armed_conflict.asia_pacific", "label": "Asia-Pacific"}, ], }, { "id": "sanctions", "label": "Sanctions & Trade War", "children": [ {"id": "sanctions.us_china", "label": "US–China"}, {"id": "sanctions.russia", "label": "Russia"}, {"id": "sanctions.iran", "label": "Iran"}, ], }, { "id": "territorial", "label": "Territorial Disputes", "children": [ {"id": "territorial.taiwan", "label": "Taiwan Strait"}, {"id": "territorial.south_china_sea", "label": "South China Sea"}, ], }, { "id": "elections", "label": "Elections & Political Risk", "children": [ {"id": "elections.us", "label": "US Elections"}, {"id": "elections.europe", "label": "Europe"}, {"id": "elections.emerging", "label": "Emerging Markets"}, ], }, ], }, { "id": "monetary_policy", "label": "Monetary Policy", "children": [ {"id": "monetary_policy.fed", "label": "Federal Reserve"}, {"id": "monetary_policy.ecb", "label": "European Central Bank"}, {"id": "monetary_policy.boj", "label": "Bank of Japan"}, {"id": "monetary_policy.pivot", "label": "Policy Pivot / Surprise"}, {"id": "monetary_policy.divergence", "label": "Central Bank Divergence"}, ], }, { "id": "economic", "label": "Economic Shocks", "children": [ {"id": "economic.banking_crisis", "label": "Banking Crisis"}, {"id": "economic.recession", "label": "Recession Fears"}, {"id": "economic.inflation", "label": "Inflation Surge"}, {"id": "economic.china_slowdown", "label": "China Slowdown"}, {"id": "economic.debt_ceiling", "label": "US Debt Ceiling"}, ], }, { "id": "commodity", "label": "Commodity Shocks", "children": [ { "id": "commodity.energy", "label": "Energy", "children": [ {"id": "commodity.energy.crude", "label": "Crude Oil (OPEC / supply)"}, {"id": "commodity.energy.gas", "label": "Natural Gas"}, ], }, { "id": "commodity.agriculture", "label": "Agriculture", "children": [ {"id": "commodity.agriculture.wheat", "label": "Wheat / Grains"}, {"id": "commodity.agriculture.softs", "label": "Softs (Coffee, Sugar)"}, ], }, { "id": "commodity.metals", "label": "Metals", "children": [ {"id": "commodity.metals.gold", "label": "Gold (Safe Haven)"}, {"id": "commodity.metals.copper", "label": "Copper (China / Growth)"}, ], }, ], }, { "id": "risk_off", "label": "Risk-Off Events", "children": [ {"id": "risk_off.pandemic", "label": "Pandemic / Health Crisis"}, {"id": "risk_off.financial_contagion", "label": "Financial Contagion"}, {"id": "risk_off.natural_disaster", "label": "Natural Disaster"}, {"id": "risk_off.nuclear", "label": "Nuclear Risk"}, ], }, { "id": "market_structure", "label": "Market Structure", "children": [ {"id": "market_structure.volatility", "label": "Volatility Regime (VIX)"}, {"id": "market_structure.positioning", "label": "Positioning Extremes"}, {"id": "market_structure.sentiment", "label": "Sentiment Extremes"}, {"id": "market_structure.liquidity", "label": "Liquidity Crisis"}, ], }, ], } # ── Historical geopolitical pattern library ─────────────────────────────────── GEO_PATTERNS = [ { "id": "P001", "name": "Middle East Military Escalation → Oil Spike", "description": "Armed conflict or threat in Gulf region triggers Brent/WTI crude spike +10-20% within 2-4 weeks", "triggers": ["military", "energy", "sanctions"], "keywords": ["Iran", "Israel", "Saudi", "Gulf", "Strait of Hormuz", "OPEC"], "taxonomy_path": ["geopolitical", "armed_conflict", "armed_conflict.middle_east"], "historical_instances": [ {"date": "2019-09-14", "event": "Attack on Saudi Aramco facilities", "brent_move": +14.6, "days": 2}, {"date": "2020-01-03", "event": "Soleimani assassination", "brent_move": +4.4, "days": 1}, {"date": "2022-02-24", "event": "Russia invades Ukraine", "brent_move": +28.0, "days": 10}, ], "suggested_trades": [ {"strategy": "Bull Call Spread", "underlying": "USO", "rationale": "Oil ETF call spread, limited risk"}, {"strategy": "Long Call", "underlying": "CL=F", "rationale": "WTI crude direct exposure"}, ], "asset_class": "energy", "expected_move_pct": 12.0, "probability": 0.65, "horizon_days": 30, }, { "id": "P002", "name": "US Tariff Announcement → Agriculture Selloff", "description": "Trump/US tariff threats on China cause immediate selloff in soy, corn, wheat (retaliatory risk)", "triggers": ["trade_war", "political_speech"], "keywords": ["tariff", "China", "trade", "soybean", "agriculture", "import duty"], "taxonomy_path": ["geopolitical", "sanctions", "sanctions.us_china"], "historical_instances": [ {"date": "2018-07-06", "event": "US-China trade war tariffs", "zs_move": -10.2, "days": 30}, {"date": "2019-05-10", "event": "Trump tariff escalation tweet", "zs_move": -5.8, "days": 5}, {"date": "2025-02-01", "event": "Trump 25% tariff on Canada/Mexico", "zw_move": -3.4, "days": 3}, ], "suggested_trades": [ {"strategy": "Bear Put Spread", "underlying": "SOYB", "rationale": "Downside hedge on soy ETF"}, {"strategy": "Long Put", "underlying": "ZS=F", "rationale": "Soybean futures put"}, ], "asset_class": "agriculture", "expected_move_pct": -8.0, "probability": 0.70, "horizon_days": 21, }, { "id": "P003", "name": "Geopolitical Risk Flight → Gold Rally", "description": "Major geopolitical uncertainty drives safe-haven demand for gold +5-15%", "triggers": ["military", "health_crisis", "financial_crisis", "elections"], "keywords": ["nuclear", "war", "crisis", "uncertainty", "safe haven", "debt ceiling"], "taxonomy_path": ["commodity", "commodity.metals", "commodity.metals.gold"], "historical_instances": [ {"date": "2022-02-24", "event": "Ukraine invasion", "gc_move": +6.8, "days": 14}, {"date": "2023-10-07", "event": "Hamas attack on Israel", "gc_move": +9.2, "days": 30}, {"date": "2020-03-01", "event": "COVID-19 fear peak", "gc_move": +12.1, "days": 45}, ], "suggested_trades": [ {"strategy": "Long Call", "underlying": "GLD", "rationale": "Gold ETF call for safe-haven rally"}, {"strategy": "Bull Call Spread", "underlying": "GC=F", "rationale": "Gold futures spread, capped risk"}, ], "asset_class": "metals", "expected_move_pct": 7.5, "probability": 0.72, "horizon_days": 30, }, { "id": "P004", "name": "Fed Hawkish Pivot → Dollar Surge / EM Currency Crash", "description": "Fed signals higher-for-longer rates → USD Index rallies, EUR/USD drops", "triggers": ["political_speech"], "keywords": ["Fed", "interest rate", "hike", "hawkish", "inflation", "FOMC", "Powell"], "taxonomy_path": ["monetary_policy", "monetary_policy.fed"], "historical_instances": [ {"date": "2022-06-15", "event": "Fed 75bps hike", "dxy_move": +3.2, "days": 5}, {"date": "2023-03-22", "event": "Fed signals further hikes", "eurusd_move": -1.8, "days": 7}, ], "suggested_trades": [ {"strategy": "Bear Put Spread", "underlying": "FXE", "rationale": "EUR/USD put spread"}, {"strategy": "Long Call", "underlying": "UUP", "rationale": "Dollar index ETF call"}, ], "asset_class": "forex", "expected_move_pct": 3.0, "probability": 0.68, "horizon_days": 14, }, { "id": "P005", "name": "China Economic Slowdown → Copper/Metals Selloff", "description": "Weak Chinese PMI or stimulus disappointment drives copper lower (China = 50%+ of global demand)", "triggers": ["resource_scarcity", "trade_war"], "keywords": ["China", "PMI", "slowdown", "recession", "property", "Evergrande", "copper demand"], "taxonomy_path": ["economic", "economic.china_slowdown"], "historical_instances": [ {"date": "2015-08-24", "event": "China Black Monday", "hg_move": -8.4, "days": 5}, {"date": "2022-11-01", "event": "China PMI contraction", "hg_move": -5.2, "days": 10}, ], "suggested_trades": [ {"strategy": "Long Put", "underlying": "COPX", "rationale": "Copper miners ETF put"}, {"strategy": "Bear Put Spread", "underlying": "HG=F", "rationale": "Copper futures spread"}, ], "asset_class": "metals", "expected_move_pct": -6.5, "probability": 0.60, "horizon_days": 21, }, { "id": "P006", "name": "Ukraine/Russia War Escalation → Wheat Spike + Defense Rally", "description": "New escalation in Russia-Ukraine conflict → wheat/fertilizer spike, defense stocks rally", "triggers": ["military", "resource_scarcity"], "keywords": ["Russia", "Ukraine", "Zelensky", "Kyiv", "grain corridor", "Black Sea", "NATO"], "taxonomy_path": ["geopolitical", "armed_conflict", "armed_conflict.europe"], "historical_instances": [ {"date": "2022-02-24", "event": "Full-scale invasion", "zw_move": +50.0, "days": 45}, {"date": "2022-07-22", "event": "Grain deal collapse threat", "zw_move": +6.3, "days": 3}, {"date": "2023-07-17", "event": "Russia exits grain deal", "zw_move": +8.5, "days": 2}, ], "suggested_trades": [ {"strategy": "Long Call", "underlying": "WEAT", "rationale": "Wheat ETF call on supply shock"}, {"strategy": "Bull Call Spread", "underlying": "LMT", "rationale": "Lockheed defense stock spread"}, ], "asset_class": "agriculture", "expected_move_pct": 15.0, "probability": 0.58, "horizon_days": 45, }, { "id": "P007", "name": "Natural Gas Supply Disruption → NG Price Spike", "description": "Pipeline disruption, LNG strike, or extreme weather drives natural gas +20-40%", "triggers": ["energy", "natural_disaster", "military"], "keywords": ["pipeline", "LNG", "natural gas", "Nord Stream", "gas supply", "storage"], "taxonomy_path": ["commodity", "commodity.energy", "commodity.energy.gas"], "historical_instances": [ {"date": "2022-09-26", "event": "Nord Stream pipeline explosion", "ng_move": +18.0, "days": 5}, {"date": "2021-02-10", "event": "Texas winter storm Uri", "ng_move": +40.0, "days": 3}, ], "suggested_trades": [ {"strategy": "Long Call", "underlying": "UNG", "rationale": "Natural gas ETF call"}, {"strategy": "Bull Call Spread", "underlying": "NG=F", "rationale": "NG futures spread, capped risk"}, ], "asset_class": "energy", "expected_move_pct": 25.0, "probability": 0.55, "horizon_days": 14, }, { "id": "P008", "name": "Pandemic / Health Crisis → VIX Spike + Market Selloff", "description": "New pandemic scare or major health crisis → VIX spike, equity selloff, gold bid", "triggers": ["health_crisis"], "keywords": ["pandemic", "virus", "outbreak", "WHO", "lockdown", "COVID", "mpox", "H5N1"], "taxonomy_path": ["risk_off", "risk_off.pandemic"], "historical_instances": [ {"date": "2020-02-24", "event": "COVID-19 global spread fear", "spx_move": -34.0, "days": 30}, {"date": "2022-11-25", "event": "China COVID lockdowns", "spx_move": -3.5, "days": 3}, ], "suggested_trades": [ {"strategy": "Long Put", "underlying": "SPY", "rationale": "S&P 500 put for equity protection"}, {"strategy": "Long Call", "underlying": "^VIX", "rationale": "VIX call for volatility spike"}, {"strategy": "Long Call", "underlying": "GLD", "rationale": "Gold safe-haven call"}, ], "asset_class": "indices", "expected_move_pct": -12.0, "probability": 0.45, "horizon_days": 30, }, # ── New patterns P009-P023 ────────────────────────────────────────────────── { "id": "P009", "name": "BoJ YCC Break / Ultra-Loose Exit → JPY Surge", "description": "Bank of Japan abandons yield curve control or signals rate normalisation → JPY rally, JGB yields spike, global carry unwind", "triggers": ["political_speech"], "keywords": ["Bank of Japan", "BoJ", "YCC", "yield curve control", "yen", "JPY", "Ueda", "Kuroda", "JGB"], "taxonomy_path": ["monetary_policy", "monetary_policy.boj"], "historical_instances": [ {"date": "2022-12-20", "event": "BoJ widens YCC band to ±0.5%", "usdjpy_move": -3.7, "days": 1}, {"date": "2023-07-28", "event": "BoJ further loosens YCC to 1%", "usdjpy_move": -2.2, "days": 2}, {"date": "2024-03-19", "event": "BoJ raises rates for first time since 2007", "usdjpy_move": -0.8, "days": 1}, ], "suggested_trades": [ {"strategy": "Long Put", "underlying": "FXY", "rationale": "JPY ETF put — gains as USD/JPY falls (yen strengthens)"}, {"strategy": "Bear Put Spread", "underlying": "YCS", "rationale": "Ultra-short yen ETF put spread"}, {"strategy": "Long Call", "underlying": "FXY", "rationale": "Direct long JPY via ETF call"}, ], "asset_class": "forex", "expected_move_pct": -4.0, "probability": 0.60, "horizon_days": 7, }, { "id": "P010", "name": "Regional Banking Crisis → Financial Contagion", "description": "Bank run or failure of regional lenders triggers systemic fear, credit spreads widen, equities sell off", "triggers": ["financial_crisis"], "keywords": ["bank run", "SVB", "Silicon Valley Bank", "FDIC", "deposit", "contagion", "credit suisse", "regional bank"], "taxonomy_path": ["economic", "economic.banking_crisis"], "historical_instances": [ {"date": "2023-03-10", "event": "SVB collapse — largest US bank failure since 2008", "kre_move": -27.0, "days": 5}, {"date": "2023-03-19", "event": "Credit Suisse emergency merger with UBS", "cs_move": -60.0, "days": 2}, {"date": "2023-05-01", "event": "First Republic Bank seized by FDIC", "kre_move": -8.5, "days": 3}, ], "suggested_trades": [ {"strategy": "Long Put", "underlying": "KRE", "rationale": "Regional banks ETF put — direct contagion play"}, {"strategy": "Long Call", "underlying": "GLD", "rationale": "Gold flight-to-safety bid"}, {"strategy": "Bear Put Spread", "underlying": "XLF", "rationale": "Broad financial sector downside hedge"}, ], "asset_class": "indices", "expected_move_pct": -15.0, "probability": 0.55, "horizon_days": 14, }, { "id": "P011", "name": "Taiwan Strait Military Tension → Semis Selloff", "description": "PLA military exercises or blockade threat → global semiconductor supply fear, Taiwan semis/TSMC selloff", "triggers": ["military"], "keywords": ["Taiwan", "TSMC", "PLA", "strait", "China invasion", "semiconductor", "chip supply", "Pelosi"], "taxonomy_path": ["geopolitical", "territorial", "territorial.taiwan"], "historical_instances": [ {"date": "2022-08-02", "event": "Pelosi visits Taiwan — PLA launches live-fire drills", "tsm_move": -6.5, "days": 3}, {"date": "2023-04-08", "event": "PLA encirclement drills after Tsai-McCarthy meeting", "tsm_move": -3.2, "days": 2}, ], "suggested_trades": [ {"strategy": "Long Put", "underlying": "TSM", "rationale": "TSMC put — direct Taiwan semi exposure"}, {"strategy": "Bear Put Spread", "underlying": "SOXX", "rationale": "Semis ETF put spread on supply disruption risk"}, {"strategy": "Long Call", "underlying": "GLD", "rationale": "Safe-haven gold bid on regional military tension"}, ], "asset_class": "equities", "expected_move_pct": -8.0, "probability": 0.50, "horizon_days": 14, }, { "id": "P012", "name": "OPEC+ Surprise Production Cut → Crude Oil Spike", "description": "Unexpected OPEC+ output cut announcement drives Brent/WTI +5-15% in days", "triggers": ["energy", "political_speech"], "keywords": ["OPEC", "Saudi Arabia", "production cut", "barrel", "oil output", "supply cut"], "taxonomy_path": ["commodity", "commodity.energy", "commodity.energy.crude"], "historical_instances": [ {"date": "2023-04-02", "event": "OPEC+ surprise 1.16Mb/d cut", "brent_move": +6.3, "days": 1}, {"date": "2022-10-05", "event": "OPEC+ cuts 2Mb/d despite US pressure", "brent_move": +11.0, "days": 5}, {"date": "2020-04-12", "event": "Historic OPEC+ 9.7Mb/d cut deal", "cl_move": +20.0, "days": 2}, ], "suggested_trades": [ {"strategy": "Bull Call Spread", "underlying": "USO", "rationale": "Oil ETF bull spread, defined risk"}, {"strategy": "Long Call", "underlying": "CL=F", "rationale": "WTI crude futures call for directional upside"}, {"strategy": "Long Call", "underlying": "XLE", "rationale": "Energy sector ETF call for broader sector play"}, ], "asset_class": "energy", "expected_move_pct": 8.0, "probability": 0.65, "horizon_days": 14, }, { "id": "P013", "name": "Fed Dovish Pivot → Risk-On Rally", "description": "Fed signals rate cuts ahead of schedule → equities rally, USD weakens, risk assets bid", "triggers": ["political_speech"], "keywords": ["Fed cut", "dovish", "pivot", "rate cut", "easing", "QE", "FOMC pause", "Jackson Hole"], "taxonomy_path": ["monetary_policy", "monetary_policy.pivot"], "historical_instances": [ {"date": "2023-11-01", "event": "Fed holds rates, signals peak", "spy_move": +5.9, "days": 5}, {"date": "2023-12-13", "event": "Fed dots signal 3 cuts in 2024", "spy_move": +3.5, "days": 2}, {"date": "2019-07-31", "event": "First Fed cut since 2008", "spy_move": +2.1, "days": 1}, ], "suggested_trades": [ {"strategy": "Bull Call Spread", "underlying": "SPY", "rationale": "S&P 500 bull spread on risk-on rally"}, {"strategy": "Long Call", "underlying": "QQQ", "rationale": "Nasdaq call — rate-sensitive growth names"}, {"strategy": "Bear Put Spread", "underlying": "UUP", "rationale": "USD index downside on dovish turn"}, ], "asset_class": "indices", "expected_move_pct": 5.0, "probability": 0.70, "horizon_days": 14, }, { "id": "P014", "name": "US Debt Ceiling Standoff → T-Bill Stress", "description": "Congress deadlock on debt ceiling → short-term T-bill yields spike, credit risk perception rises, risk-off", "triggers": ["political_speech", "financial_crisis"], "keywords": ["debt ceiling", "default", "Treasury", "X-date", "fiscal cliff", "Congress", "spending"], "taxonomy_path": ["economic", "economic.debt_ceiling"], "historical_instances": [ {"date": "2023-05-01", "event": "US 1-month T-bill yield spikes to 5.9%", "vix_move": +22.0, "days": 14}, {"date": "2011-08-02", "event": "S&P downgrades US credit", "spy_move": -16.0, "days": 10}, {"date": "2013-10-01", "event": "US government shutdown + debt ceiling standoff", "vix_move": +40.0, "days": 16}, ], "suggested_trades": [ {"strategy": "Long Call", "underlying": "GLD", "rationale": "Safe-haven gold on fiscal credibility risk"}, {"strategy": "Bear Put Spread", "underlying": "SPY", "rationale": "Equity hedge during debt ceiling uncertainty"}, {"strategy": "Long Put", "underlying": "TLT", "rationale": "Long-duration Treasury put on fiscal risk premium"}, ], "asset_class": "indices", "expected_move_pct": -5.0, "probability": 0.55, "horizon_days": 21, }, { "id": "P015", "name": "Iran Nuclear Escalation → Oil + Gold Spike", "description": "Iran nuclear programme breakthrough, US/Israel strike threat, or IAEA crisis → oil and gold both bid", "triggers": ["military", "sanctions"], "keywords": ["Iran", "nuclear", "enrichment", "IAEA", "sanctions", "Strait of Hormuz", "Israel strike"], "taxonomy_path": ["geopolitical", "sanctions", "sanctions.iran"], "historical_instances": [ {"date": "2020-01-08", "event": "Iran missiles hit US bases in Iraq (after Soleimani)", "brent_move": +3.5, "days": 1}, {"date": "2024-04-01", "event": "Israel strikes Iran consulate in Damascus", "brent_move": +4.0, "days": 3}, {"date": "2024-04-13", "event": "Iran direct drone/missile attack on Israel", "brent_move": +3.0, "days": 1}, ], "suggested_trades": [ {"strategy": "Bull Call Spread", "underlying": "USO", "rationale": "Oil supply risk premium via ETF spread"}, {"strategy": "Long Call", "underlying": "GLD", "rationale": "Dual safe-haven gold bid on military escalation"}, ], "asset_class": "energy", "expected_move_pct": 6.0, "probability": 0.58, "horizon_days": 10, }, { "id": "P016", "name": "North Korea Missile / Nuclear Test → Asian Risk-Off", "description": "DPRK ICBM launch or nuclear test → KRW selloff, Nikkei/Kospi dip, gold bid", "triggers": ["military"], "keywords": ["North Korea", "DPRK", "Kim Jong-un", "missile", "ICBM", "nuclear test", "Korea"], "taxonomy_path": ["geopolitical", "armed_conflict", "armed_conflict.asia_pacific"], "historical_instances": [ {"date": "2022-11-18", "event": "DPRK fires ICBM over Japan", "krw_move": -0.8, "days": 1}, {"date": "2017-09-03", "event": "North Korea 6th nuclear test", "krw_move": -1.5, "days": 2}, {"date": "2023-03-16", "event": "DPRK fires ballistic missile", "nky_move": -0.4, "days": 1}, ], "suggested_trades": [ {"strategy": "Long Call", "underlying": "GLD", "rationale": "Safe-haven gold on regional military tension"}, {"strategy": "Bear Put Spread", "underlying": "EWY", "rationale": "South Korea ETF put on escalation risk"}, ], "asset_class": "indices", "expected_move_pct": -2.0, "probability": 0.45, "horizon_days": 7, }, { "id": "P017", "name": "VIX Backwardation (Crisis Regime) → Vol Premium Collapse", "description": "VIX term structure inverts (spot > 3m future) signalling acute stress; historically mean-reverts fast → vol sellers reload", "triggers": ["financial_crisis"], "keywords": ["VIX", "backwardation", "volatility spike", "contango flip", "fear gauge", "VX futures"], "taxonomy_path": ["market_structure", "market_structure.volatility"], "historical_instances": [ {"date": "2020-03-16", "event": "COVID crash peak backwardation VIX=82", "vix_move": +82.0, "days": 1}, {"date": "2022-01-24", "event": "Fed tightening fear, VIX=38, backwardation", "vix_move": +38.0, "days": 14}, {"date": "2018-02-05", "event": "Volmageddon — inverse VIX ETPs implode", "vix_move": +115.0, "days": 1}, ], "suggested_trades": [ {"strategy": "Bear Put Spread", "underlying": "SPY", "rationale": "Hedge equity downside during vol spike"}, {"strategy": "Bull Call Spread", "underlying": "SPY", "rationale": "Mean-reversion entry once VIX normalises"}, {"strategy": "Short Put", "underlying": "VXX", "rationale": "VIX ETF put — backwardation normalises quickly"}, ], "asset_class": "indices", "expected_move_pct": -8.0, "probability": 0.60, "horizon_days": 10, }, { "id": "P018", "name": "ECB Surprise Rate Move → EUR Volatility", "description": "ECB delivers surprise hike, cut, or emergency action outside meeting → EUR/USD sharp move", "triggers": ["political_speech"], "keywords": ["ECB", "Lagarde", "euro", "interest rate", "refi rate", "fragmentation", "BTP", "PEPP"], "taxonomy_path": ["monetary_policy", "monetary_policy.ecb"], "historical_instances": [ {"date": "2022-07-21", "event": "ECB surprise 50bps hike (vs 25 expected)", "eurusd_move": +1.4, "days": 1}, {"date": "2022-09-08", "event": "ECB hikes 75bps (largest ever)", "eurusd_move": +0.8, "days": 1}, {"date": "2024-06-06", "event": "ECB first rate cut in 5 years", "eurusd_move": -0.3, "days": 1}, ], "suggested_trades": [ {"strategy": "Long Straddle", "underlying": "FXE", "rationale": "EUR/USD straddle on surprise — benefits from direction"}, {"strategy": "Bull Call Spread", "underlying": "FXE", "rationale": "EUR call spread if dovish surprise expected"}, {"strategy": "Bear Put Spread", "underlying": "FXE", "rationale": "EUR put spread if hawkish surprise"}, ], "asset_class": "forex", "expected_move_pct": 1.5, "probability": 0.55, "horizon_days": 5, }, { "id": "P019", "name": "Extreme Fear Sentiment → Contrarian Buy Signal", "description": "CNN F&G < 20 + AAII bears > 45% → historically strong mean-reversion buy within 4-8 weeks", "triggers": ["financial_crisis", "political_speech"], "keywords": ["fear & greed", "AAII", "bearish sentiment", "extreme fear", "capitulation", "put/call ratio"], "taxonomy_path": ["market_structure", "market_structure.sentiment"], "historical_instances": [ {"date": "2022-10-13", "event": "CNN F&G=15, AAII Bears=60% — S&P bottoms", "spy_move": +19.0, "days": 60}, {"date": "2020-03-23", "event": "COVID low — CNN F&G=2 extreme fear", "spy_move": +34.0, "days": 30}, {"date": "2023-03-13", "event": "SVB panic — CNN F&G=22, bottom", "spy_move": +9.0, "days": 21}, ], "suggested_trades": [ {"strategy": "Bull Call Spread", "underlying": "SPY", "rationale": "Contrarian equity entry on extreme fear reading"}, {"strategy": "Long Call", "underlying": "QQQ", "rationale": "Growth recovery play post-capitulation"}, ], "asset_class": "indices", "expected_move_pct": 12.0, "probability": 0.68, "horizon_days": 45, }, { "id": "P020", "name": "South China Sea Incident → Shipping / Energy Risk-Off", "description": "Collision, blockade, or military incident in SCS → shipping disruption, energy supply risk, regional selloff", "triggers": ["military"], "keywords": ["South China Sea", "Philippines", "Vietnam", "shipping lane", "Spratly", "Paracel", "Taiwan"], "taxonomy_path": ["geopolitical", "territorial", "territorial.south_china_sea"], "historical_instances": [ {"date": "2023-08-05", "event": "China water cannon at Ayungin Shoal", "psei_move": -1.5, "days": 2}, {"date": "2024-02-05", "event": "Heightened China-Philippines naval standoff", "eem_move": -0.8, "days": 3}, ], "suggested_trades": [ {"strategy": "Long Call", "underlying": "GLD", "rationale": "Safe-haven bid on regional naval tension"}, {"strategy": "Bull Call Spread", "underlying": "USO", "rationale": "Oil supply disruption premium via ETF"}, {"strategy": "Bear Put Spread", "underlying": "EEM", "rationale": "EM equity downside on Asian risk-off"}, ], "asset_class": "energy", "expected_move_pct": 3.0, "probability": 0.40, "horizon_days": 10, }, { "id": "P021", "name": "European Energy Crisis → EUR Selloff + NG Spike", "description": "Russian gas cutoff, LNG supply shortfall, or extreme winter demand drives European natural gas +30%+, EUR weakens", "triggers": ["energy", "military", "resource_scarcity"], "keywords": ["Europe energy", "gas storage", "TTF", "Gazprom", "LNG", "energy crisis", "winter", "sanctions"], "taxonomy_path": ["geopolitical", "armed_conflict", "armed_conflict.europe"], "historical_instances": [ {"date": "2022-06-15", "event": "Gazprom cuts Nord Stream 1 flows 60%", "ttf_move": +60.0, "days": 10}, {"date": "2022-08-26", "event": "Nord Stream shut entirely, gas crisis peak", "eurusd_move": -3.5, "days": 7}, ], "suggested_trades": [ {"strategy": "Long Call", "underlying": "UNG", "rationale": "US nat gas proxy for European supply squeeze"}, {"strategy": "Bear Put Spread", "underlying": "FXE", "rationale": "EUR/USD put spread on energy terms-of-trade shock"}, ], "asset_class": "energy", "expected_move_pct": 20.0, "probability": 0.52, "horizon_days": 30, }, { "id": "P022", "name": "Inflation Surprise (CPI Hot Print) → Equity Drawdown", "description": "Above-consensus CPI print reignites rate hike fear → equities sell off, USD spikes, growth/duration hit hardest", "triggers": ["political_speech"], "keywords": ["CPI", "inflation", "hot print", "core inflation", "PCE", "surprise", "rate hike expectations"], "taxonomy_path": ["economic", "economic.inflation"], "historical_instances": [ {"date": "2022-09-13", "event": "Aug CPI +8.3% vs 8.1% exp — worst drop in 2y", "spy_move": -4.3, "days": 1}, {"date": "2023-02-14", "event": "Jan CPI +6.4% vs 6.2% exp", "spy_move": -1.4, "days": 1}, {"date": "2022-06-10", "event": "May CPI +8.6% — 40-year high", "spy_move": -5.8, "days": 2}, ], "suggested_trades": [ {"strategy": "Bear Put Spread", "underlying": "QQQ", "rationale": "Nasdaq put spread — growth names most rate-sensitive"}, {"strategy": "Long Call", "underlying": "UUP", "rationale": "USD call on hawkish repricing"}, {"strategy": "Bear Put Spread", "underlying": "TLT", "rationale": "Long-duration bond downside on rate expectations reset"}, ], "asset_class": "indices", "expected_move_pct": -4.0, "probability": 0.65, "horizon_days": 5, }, { "id": "P023", "name": "Flash Crash / Liquidity Crisis → Rapid Selloff + Recovery", "description": "Sudden illiquidity event (algo cascade, forced deleveraging) → intraday/multi-day panic, followed by sharp recovery", "triggers": ["financial_crisis"], "keywords": ["flash crash", "liquidity crisis", "deleveraging", "margin call", "carry unwind", "forced selling"], "taxonomy_path": ["market_structure", "market_structure.liquidity"], "historical_instances": [ {"date": "2024-08-05", "event": "JPY carry unwind — Nikkei -12% in one day", "nky_move": -12.4, "days": 1}, {"date": "2020-03-12", "event": "COVID crash peak — S&P -10% in one session", "spy_move": -10.0, "days": 1}, {"date": "2010-05-06", "event": "Flash Crash — Dow -1000pts intraday, recovers same day", "spy_move": -9.2, "days": 1}, ], "suggested_trades": [ {"strategy": "Long Put", "underlying": "SPY", "rationale": "Tail-risk put during systemic deleveraging"}, {"strategy": "Bull Call Spread", "underlying": "SPY", "rationale": "Mean-reversion entry post-capitulation"}, {"strategy": "Long Call", "underlying": "GLD", "rationale": "Gold flight-to-safety during liquidity panic"}, ], "asset_class": "indices", "expected_move_pct": -10.0, "probability": 0.50, "horizon_days": 5, }, ] GEOPOLITICAL_RISK_WEIGHTS = { "military": 0.25, "energy": 0.20, "trade_war": 0.15, "political_speech": 0.15, "natural_disaster": 0.10, "health_crisis": 0.10, "resource_scarcity": 0.05, } def compute_geo_risk_score(events: List[Dict[str, Any]]) -> Dict[str, Any]: """Compute a global geopolitical risk score 0-100 from recent events.""" if not events: return {"score": 35, "level": "medium", "breakdown": {}} category_scores: Dict[str, float] = {} for event in events[:30]: cat = event.get("category", "general") impact = event.get("impact_score", 0.1) if cat in category_scores: category_scores[cat] = max(category_scores[cat], impact) else: category_scores[cat] = impact weighted = sum( category_scores.get(cat, 0) * weight for cat, weight in GEOPOLITICAL_RISK_WEIGHTS.items() ) score = min(100, round(weighted * 100, 1)) if score < 25: level = "low" elif score < 50: level = "medium" elif score < 75: level = "high" else: level = "extreme" return { "score": score, "level": level, "breakdown": {cat: round(v * 100, 1) for cat, v in category_scores.items()}, "top_risks": sorted(category_scores.items(), key=lambda x: x[1], reverse=True)[:3], } # Map asset_class → AI directional field produced by ai_score_news_batch() _ASSET_AI_DIR: Dict[str, str] = { "energy": "ai_dir_energy", "metals": "ai_dir_metals", "indices": "ai_dir_indices", "equities": "ai_dir_indices", } def _compute_ai_alignment(events: List[Dict[str, Any]], pattern: Dict[str, Any]) -> Dict[str, Any]: """Return AI directional alignment between news signals and pattern expected direction. Uses ai_dir_* fields added by ai_score_news_batch(). Returns alignment bonus (-25..+25) and metadata for display. """ asset_class = pattern.get("asset_class", "") expected_positive = (pattern.get("expected_move_pct") or 0) > 0 ai_field = _ASSET_AI_DIR.get(asset_class) ai_news = [e for e in events if e.get("ai_scored")] empty = {"ai_alignment": 0, "ai_contra_signal": False, "ai_insights": [], "ai_scored_count": 0} if not ai_news or not ai_field: return empty bullish = sum(1 for e in ai_news if e.get(ai_field) == "bullish") bearish = sum(1 for e in ai_news if e.get(ai_field) == "bearish") resolutions = sum(1 for e in ai_news if e.get("ai_resolution")) total = len(ai_news) # Positive alignment = news confirms pattern direction if expected_positive: raw = (bullish - bearish) / total contra = bearish > bullish or (resolutions > 0 and asset_class in ("energy", "metals")) else: raw = (bearish - bullish) / total contra = bullish > bearish bonus = int(round(max(-25.0, min(25.0, raw * 25)))) insights = [ e["ai_insight"] for e in ai_news if e.get("ai_insight") and e.get(ai_field, "neutral") != "neutral" ][:3] return { "ai_alignment": bonus, "ai_contra_signal": bool(contra), "ai_insights": insights, "ai_scored_count": len(ai_news), } def match_patterns(events: List[Dict[str, Any]], patterns: Optional[List[Dict[str, Any]]] = None) -> List[Dict[str, Any]]: """Find which historical geo-patterns best match current event feed.""" if not events: return [] if patterns is None: patterns = GEO_PATTERNS current_categories = set(e.get("category", "") for e in events) current_tags = set() for e in events: current_tags.update(e.get("tags", [])) current_text = " ".join(e.get("title", "") + " " + e.get("summary", "") for e in events[:20]).lower() matches = [] for pattern in patterns: trigger_match = len(set(pattern["triggers"]) & current_categories) / len(pattern["triggers"]) keyword_match = sum(1 for kw in pattern["keywords"] if kw.lower() in current_text) / len(pattern["keywords"]) similarity = round((trigger_match * 0.5 + keyword_match * 0.5) * 100, 1) if similarity > 10: ai = _compute_ai_alignment(events, pattern) adjusted = max(0, min(100, similarity + ai["ai_alignment"])) matches.append({ "pattern_id": pattern["id"], "name": pattern["name"], "description": pattern["description"], "similarity": round(adjusted, 1), "base_similarity": similarity, "suggested_trades": pattern["suggested_trades"], "asset_class": pattern["asset_class"], "expected_move_pct": pattern["expected_move_pct"], "probability": pattern["probability"], "horizon_days": pattern["horizon_days"], "historical_instances": pattern["historical_instances"], **ai, }) return sorted(matches, key=lambda x: x["similarity"], reverse=True)[:5] def generate_trade_ideas(pattern_matches: List[Dict[str, Any]], geo_score: Dict[str, Any]) -> List[Dict[str, Any]]: """Convert pattern matches into structured trade ideas with sizing for ~1000€.""" ideas = [] for pm in pattern_matches[:5]: for i, trade in enumerate(pm["suggested_trades"]): # all suggested trades, not just first move = pm["expected_move_pct"] confidence = round(pm["probability"] * pm["similarity"] / 100 * 100) # Use trade-level asset_class if provided, else fall back to pattern-level asset_class = trade.get("asset_class") or pm["asset_class"] ideas.append({ "id": f"IDEA-{pm['pattern_id']}-{i}-{trade['strategy'][:3].upper()}", "title": f"{trade['strategy']} on {trade['underlying']}", "rationale": f"[{pm['name']}] {trade['rationale']}. Expected move: {'+' if move > 0 else ''}{move}% in {pm['horizon_days']}d", "pattern": pm["name"], "asset_class": asset_class, "underlying": trade["underlying"], "strategy": trade["strategy"], "expected_move_pct": move, "confidence": min(95, confidence), "horizon_days": pm["horizon_days"], "capital_required": 1000, "risk_level": "high" if abs(move) > 15 else "medium", "pattern_similarity": pm["similarity"], }) return ideas def compute_pattern_relevance( events: List[Dict[str, Any]], patterns: Optional[List[Dict[str, Any]]] = None, ) -> List[Dict[str, Any]]: """Return ALL patterns with news-keyword relevance score + matching news snippets. Unlike match_patterns(), no similarity threshold — every active pattern is returned. """ if patterns is None: patterns = GEO_PATTERNS current_categories = set(e.get("category", "") for e in events) current_text = " ".join( e.get("title", "") + " " + e.get("summary", "") for e in events[:30] ).lower() result = [] for pattern in patterns: triggers_list = pattern.get("triggers", []) or [] keywords_list = pattern.get("keywords", []) or [] trigger_match = ( len(set(triggers_list) & current_categories) / len(triggers_list) if triggers_list else 0 ) kw_hits = [kw for kw in keywords_list if kw.lower() in current_text] keyword_match = len(kw_hits) / len(keywords_list) if keywords_list else 0 relevance = round((trigger_match * 0.5 + keyword_match * 0.5) * 100, 1) # Find matching news with which keywords triggered matching_news = [] for e in events[:30]: text = (e.get("title", "") + " " + e.get("summary", "")).lower() hits = [kw for kw in keywords_list if kw.lower() in text] if hits: matching_news.append({ "title": e.get("title", ""), "source": e.get("source", ""), "date": str(e.get("date", ""))[:16], "impact": round(e.get("impact_score", 0), 2), "matched_keywords": hits, "url": e.get("url", ""), }) matching_news.sort(key=lambda x: x["impact"], reverse=True) ai = _compute_ai_alignment(events, pattern) adjusted_relevance = max(0, min(100, relevance + ai["ai_alignment"])) result.append({ "pattern_id": pattern.get("id", ""), "name": pattern.get("name", ""), "description": pattern.get("description", ""), "asset_class": pattern.get("asset_class", ""), "relevance": round(adjusted_relevance, 1), "base_relevance": relevance, "keyword_hits": len(kw_hits), "keyword_total": len(keywords_list), "matched_keywords": kw_hits, "matching_news": matching_news[:5], "suggested_trades": pattern.get("suggested_trades", []), "expected_move_pct": pattern.get("expected_move_pct", 0), "probability": pattern.get("probability", 0), "horizon_days": pattern.get("horizon_days", 0), **ai, }) result.sort(key=lambda x: x["relevance"], reverse=True) return result def get_all_patterns() -> List[Dict[str, Any]]: return GEO_PATTERNS