JayLacoma commited on
Commit
5717149
·
verified ·
1 Parent(s): 87076e2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +323 -389
app.py CHANGED
@@ -1,413 +1,347 @@
1
  import gradio as gr
 
 
2
  import pandas as pd
3
  import numpy as np
4
- import plotly.graph_objects as go
5
- from plotly.subplots import make_subplots
6
- from datetime import datetime
7
  import warnings
8
- import os
9
-
10
  warnings.filterwarnings('ignore')
11
 
12
- from geo_macro import UnifiedMarketDataDownloader
 
13
 
14
- # Configuration
15
- FRED_API_KEY = os.getenv("FRED_API_KEY", "23f3511b0ca43918ccd503ef64cb844e")
 
16
 
17
- # Professional color palette
18
- COLORS = {
19
- 'primary': '#1a1a1a',
20
- 'secondary': '#4a4a4a',
21
- 'accent': '#0066cc',
22
- 'grid': '#e5e5e5',
23
- 'bg': '#ffffff',
24
- 'green': '#00a86b',
25
- 'red': '#d32f2f',
26
  }
27
 
28
- # Cache data globally
29
- _cached_data = None
30
-
31
- def load_data():
32
- """Load and cache market data"""
33
- global _cached_data
34
- if _cached_data is None:
35
- downloader = UnifiedMarketDataDownloader(fred_api_key=FRED_API_KEY)
36
- _cached_data = downloader.download_all_data(start_date='2020-01-01')
37
- return _cached_data
38
-
39
- def calculate_momentum_zscore(series, window=60):
40
- """Calculate momentum z-score"""
41
- returns = series.pct_change(window)
42
- mean = returns.rolling(252).mean()
43
- std = returns.rolling(252).std()
44
- return (returns - mean) / std
45
-
46
- def create_layout(title, height=450):
47
- """Standard plot layout"""
48
- return go.Layout(
49
- title=dict(text=title, font=dict(size=16, color=COLORS['primary']), x=0.5, xanchor='center'),
50
- plot_bgcolor=COLORS['bg'],
51
- paper_bgcolor=COLORS['bg'],
52
- font=dict(color=COLORS['secondary'], size=11),
53
- xaxis=dict(gridcolor=COLORS['grid'], showline=True, linecolor=COLORS['grid']),
54
- yaxis=dict(gridcolor=COLORS['grid'], showline=True, linecolor=COLORS['grid']),
55
- hovermode='x unified',
56
- height=height,
57
- margin=dict(l=60, r=40, t=60, b=40),
58
- )
59
 
60
- # === PLOT FUNCTIONS ===
 
 
61
 
62
- def plot_geopolitical_risk(start_date, end_date):
63
- """Geopolitical risk composite index"""
64
- df = load_data().loc[start_date:end_date]
65
-
66
- # Composite: Defense, Oil, Gold, VIX
67
- components = ['Defense', 'Oil', 'Gold', 'VIX']
68
- available = [c for c in components if c in df.columns]
69
-
70
- if len(available) < 2:
71
- return go.Figure().update_layout(create_layout("Insufficient Data"))
72
-
73
- # Normalize each component
74
- normalized = df[available].apply(lambda x: (x / x.iloc[0]) - 1)
75
- risk_index = normalized.mean(axis=1)
76
-
77
- # Calculate z-score
78
- zscore = (risk_index - risk_index.rolling(252).mean()) / risk_index.rolling(252).std()
79
-
80
- fig = go.Figure()
81
-
82
- # Add risk index
83
- fig.add_trace(go.Scatter(
84
- x=zscore.index,
85
- y=zscore,
86
- fill='tozeroy',
87
- line=dict(color=COLORS['accent'], width=2),
88
- name='Risk Index'
89
- ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
- # Add threshold lines
92
- fig.add_hline(y=0, line_dash="dash", line_color=COLORS['secondary'], line_width=1)
93
- fig.add_hline(y=2, line_dash="dot", line_color=COLORS['red'], line_width=1)
94
- fig.add_hline(y=-2, line_dash="dot", line_color=COLORS['green'], line_width=1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
- fig.update_layout(create_layout("Geopolitical Risk Index (Z-Score)"))
97
- fig.update_yaxes(title="Standard Deviations from Mean")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
 
99
- return fig
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
- def plot_macro_indicators(start_date, end_date):
102
- """Key macro indicators dashboard"""
103
- df = load_data().loc[start_date:end_date]
104
-
105
- indicators = {
106
- 'CPI': ('Inflation (YoY %)', lambda x: x.pct_change(252) * 100),
107
- 'Yield_Curve': ('Yield Curve (10Y-2Y)', lambda x: x),
108
- 'VIX': ('Market Volatility', lambda x: x),
109
- 'USD_Index': ('Dollar Strength', lambda x: x),
110
- }
111
-
112
- available = {k: v for k, v in indicators.items() if k in df.columns}
113
-
114
- if not available:
115
- return go.Figure().update_layout(create_layout("No Data Available"))
116
-
117
- n_plots = len(available)
118
- fig = make_subplots(
119
- rows=n_plots, cols=1,
120
- shared_xaxes=True,
121
- vertical_spacing=0.05,
122
- subplot_titles=[v[0] for v in available.values()]
123
- )
124
-
125
- for i, (col, (title, transform)) in enumerate(available.items(), 1):
126
- series = transform(df[col]).dropna()
127
-
128
- # Color based on last value
129
- color = COLORS['red'] if series.iloc[-1] > series.mean() else COLORS['green']
130
- if col == 'Yield_Curve':
131
- color = COLORS['red'] if series.iloc[-1] < 0 else COLORS['green']
132
-
133
- fig.add_trace(
134
- go.Scatter(
135
- x=series.index,
136
- y=series,
137
- line=dict(color=color, width=2),
138
- showlegend=False
139
- ),
140
- row=i, col=1
141
  )
142
 
143
- # Add mean line
144
- mean_val = series.mean()
145
- fig.add_hline(
146
- y=mean_val,
147
- line_dash="dash",
148
- line_color=COLORS['secondary'],
149
- line_width=1,
150
- row=i, col=1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  )
152
 
153
- fig.update_layout(
154
- create_layout("Macroeconomic Indicators", height=150 * n_plots),
155
- showlegend=False
156
- )
157
-
158
- return fig
159
-
160
- def plot_safe_haven_flows(start_date, end_date):
161
- """Safe haven vs risk asset flows"""
162
- df = load_data().loc[start_date:end_date]
163
-
164
- safe_havens = ['Gold', 'US_10Y', 'CHF']
165
- risk_assets = ['US_Equity', 'Emerging_Markets', 'High_Yield']
166
-
167
- safe_available = [c for c in safe_havens if c in df.columns]
168
- risk_available = [c for c in risk_assets if c in df.columns]
169
-
170
- if not safe_available or not risk_available:
171
- return go.Figure().update_layout(create_layout("Insufficient Data"))
172
-
173
- # Normalize to 100
174
- safe_norm = df[safe_available].apply(lambda x: x / x.iloc[0] * 100).mean(axis=1)
175
- risk_norm = df[risk_available].apply(lambda x: x / x.iloc[0] * 100).mean(axis=1)
176
-
177
- fig = go.Figure()
178
-
179
- fig.add_trace(go.Scatter(
180
- x=safe_norm.index,
181
- y=safe_norm,
182
- name='Safe Havens',
183
- line=dict(color=COLORS['green'], width=2)
184
- ))
185
-
186
- fig.add_trace(go.Scatter(
187
- x=risk_norm.index,
188
- y=risk_norm,
189
- name='Risk Assets',
190
- line=dict(color=COLORS['red'], width=2)
191
- ))
192
-
193
- fig.update_layout(create_layout("Safe Haven vs Risk Asset Performance"))
194
- fig.update_yaxes(title="Indexed to 100")
195
-
196
- return fig
197
-
198
- def plot_relative_strength(start_date, end_date):
199
- """Regional equity performance"""
200
- df = load_data().loc[start_date:end_date]
201
-
202
- regions = ['US_Equity', 'Europe', 'China', 'Emerging_Markets']
203
- available = [r for r in regions if r in df.columns]
204
-
205
- if len(available) < 2:
206
- return go.Figure().update_layout(create_layout("Insufficient Data"))
207
-
208
- # Calculate 3-month momentum
209
- momentum = df[available].pct_change(60).iloc[-1] * 100
210
- momentum = momentum.sort_values(ascending=False)
211
-
212
- colors = [COLORS['green'] if x > 0 else COLORS['red'] for x in momentum.values]
213
-
214
- fig = go.Figure(go.Bar(
215
- x=momentum.values,
216
- y=[name.replace('_', ' ') for name in momentum.index],
217
- orientation='h',
218
- marker_color=colors,
219
- text=[f"{x:.1f}%" for x in momentum.values],
220
- textposition='auto',
221
- ))
222
-
223
- fig.add_vline(x=0, line_color=COLORS['secondary'], line_width=1)
224
-
225
- fig.update_layout(create_layout("Regional Equity Strength (3M Return)"))
226
- fig.update_xaxes(title="Return (%)")
227
-
228
- return fig
229
-
230
- def plot_correlation_matrix(start_date, end_date, assets):
231
- """Correlation heatmap for selected assets"""
232
- df = load_data().loc[start_date:end_date]
233
-
234
- available = [a for a in assets if a in df.columns]
235
- if len(available) < 2:
236
- return go.Figure().update_layout(create_layout("Select at least 2 assets"))
237
-
238
- corr = df[available].pct_change().corr()
239
-
240
- fig = go.Figure(go.Heatmap(
241
- z=corr.values,
242
- x=[c.replace('_', ' ') for c in corr.columns],
243
- y=[c.replace('_', ' ') for c in corr.columns],
244
- colorscale='RdBu_r',
245
- zmid=0,
246
- zmin=-1,
247
- zmax=1,
248
- text=np.round(corr.values, 2),
249
- texttemplate='%{text}',
250
- textfont=dict(size=10),
251
- colorbar=dict(title="Correlation")
252
- ))
253
-
254
- fig.update_layout(create_layout("Asset Correlation Matrix", height=500))
255
-
256
- return fig
257
-
258
- def plot_drawdown(start_date, end_date, assets):
259
- """Drawdown analysis"""
260
- df = load_data().loc[start_date:end_date]
261
-
262
- available = [a for a in assets if a in df.columns]
263
- if not available:
264
- return go.Figure().update_layout(create_layout("Select assets to analyze"))
265
-
266
- fig = go.Figure()
267
-
268
- for asset in available:
269
- prices = df[asset].dropna()
270
- if len(prices) > 0:
271
- rolling_max = prices.expanding().max()
272
- drawdown = ((prices - rolling_max) / rolling_max) * 100
273
-
274
- fig.add_trace(go.Scatter(
275
- x=drawdown.index,
276
- y=drawdown,
277
- name=asset.replace('_', ' '),
278
- line=dict(width=2)
279
- ))
280
-
281
- fig.update_layout(create_layout("Drawdown Analysis"))
282
- fig.update_yaxes(title="Drawdown (%)")
283
-
284
- return fig
285
-
286
- # === GRADIO APP ===
287
 
288
- custom_css = """
289
- .gradio-container {
290
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
291
- max-width: 1400px;
292
- margin: auto;
293
- }
294
- h1 {
295
- font-size: 2.2em !important;
296
- font-weight: 600 !important;
297
- color: #1a1a1a !important;
298
- text-align: center;
299
- margin-bottom: 0.3em !important;
300
- }
301
- h3 {
302
- font-size: 1.1em !important;
303
- font-weight: 400 !important;
304
- color: #4a4a4a !important;
305
- text-align: center;
306
- margin-top: 0 !important;
307
- }
308
- .tabs button {
309
- font-size: 0.95em !important;
310
- font-weight: 500 !important;
311
- }
312
- .tabs button.selected {
313
- border-bottom: 2px solid #0066cc !important;
314
- }
315
- button.primary {
316
- background: #0066cc !important;
317
- font-weight: 500 !important;
318
- }
319
- """
320
-
321
- AVAILABLE_ASSETS = [
322
- 'US_Equity', 'Europe', 'China', 'Emerging_Markets',
323
- 'Gold', 'Oil', 'Copper', 'US_10Y', 'High_Yield',
324
- 'VIX', 'USD_Index', 'Defense', 'Technology'
325
- ]
326
-
327
- with gr.Blocks(title="Geopolitical & Macro Intelligence", css=custom_css, theme=gr.themes.Soft()) as demo:
328
-
329
- gr.Markdown("# Geopolitical & Macro Intelligence")
330
- gr.Markdown("### Professional analysis of global markets and economic indicators")
331
-
332
- with gr.Tabs():
333
-
334
- # TAB 1: Geopolitical Overview
335
- with gr.Tab("🌍 Geopolitical Risk"):
336
- with gr.Row():
337
- start_1 = gr.Textbox("2023-01-01", label="Start Date", scale=1)
338
- end_1 = gr.Textbox(datetime.today().strftime('%Y-%m-%d'), label="End Date", scale=1)
339
- btn_1 = gr.Button("Update", variant="primary", scale=1)
340
-
341
- with gr.Row():
342
- plot_geo_risk = gr.Plot()
343
- plot_safe_haven = gr.Plot()
344
-
345
- btn_1.click(
346
- fn=plot_geopolitical_risk,
347
- inputs=[start_1, end_1],
348
- outputs=plot_geo_risk
349
- ).then(
350
- fn=plot_safe_haven_flows,
351
- inputs=[start_1, end_1],
352
- outputs=plot_safe_haven
353
- )
354
-
355
- # TAB 2: Macro Dashboard
356
- with gr.Tab("📊 Macro Indicators"):
357
- with gr.Row():
358
- start_2 = gr.Textbox("2023-01-01", label="Start Date", scale=1)
359
- end_2 = gr.Textbox(datetime.today().strftime('%Y-%m-%d'), label="End Date", scale=1)
360
- btn_2 = gr.Button("Update", variant="primary", scale=1)
361
-
362
- with gr.Row():
363
- plot_macro = gr.Plot(scale=2)
364
- plot_regional = gr.Plot(scale=1)
365
-
366
- btn_2.click(
367
- fn=plot_macro_indicators,
368
- inputs=[start_2, end_2],
369
- outputs=plot_macro
370
- ).then(
371
- fn=plot_relative_strength,
372
- inputs=[start_2, end_2],
373
- outputs=plot_regional
374
- )
375
-
376
- # TAB 3: Custom Analysis
377
- with gr.Tab("🔍 Custom Analysis"):
378
- with gr.Row():
379
- start_3 = gr.Textbox("2023-01-01", label="Start Date", scale=1)
380
- end_3 = gr.Textbox(datetime.today().strftime('%Y-%m-%d'), label="End Date", scale=1)
381
-
382
- assets_select = gr.Dropdown(
383
- AVAILABLE_ASSETS,
384
- value=['US_Equity', 'Gold', 'Oil', 'VIX'],
385
- multiselect=True,
386
- label="Select Assets"
387
- )
388
-
389
- btn_3 = gr.Button("Analyze", variant="primary")
390
-
391
- with gr.Row():
392
- plot_corr = gr.Plot()
393
- plot_dd = gr.Plot()
394
-
395
- btn_3.click(
396
- fn=plot_correlation_matrix,
397
- inputs=[start_3, end_3, assets_select],
398
- outputs=plot_corr
399
- ).then(
400
- fn=plot_drawdown,
401
- inputs=[start_3, end_3, assets_select],
402
- outputs=plot_dd
403
- )
404
-
405
- # Load initial data
406
- demo.load(
407
- fn=plot_geopolitical_risk,
408
- inputs=[start_1, end_1],
409
- outputs=plot_geo_risk
410
- )
411
 
412
  if __name__ == "__main__":
 
 
413
  demo.launch()
 
1
  import gradio as gr
2
+ import matplotlib.pyplot as plt
3
+ import seaborn as sns
4
  import pandas as pd
5
  import numpy as np
6
+ from datetime import datetime, timedelta
 
 
7
  import warnings
 
 
8
  warnings.filterwarnings('ignore')
9
 
10
+ # Import from your geo_macro.py
11
+ from geo_macro import UnifiedMarketDataDownloader, FRED_API_KEY
12
 
13
+ # ======================
14
+ # CUSTOM STYLING & COLORS
15
+ # ======================
16
 
17
+ # Custom color palette matching your theme
18
+ CUSTOM_COLORS = {
19
+ 'primary': '#00ff9b',
20
+ 'secondary': '#050505',
21
+ 'accent': '#0066ff',
22
+ 'background': '#0a0a0a',
23
+ 'text': '#ffffff',
24
+ 'grid': '#1a1a1a'
 
25
  }
26
 
27
+ # Apply custom styling
28
+ plt.style.use('dark_background')
29
+ sns.set_palette([CUSTOM_COLORS['primary'], CUSTOM_COLORS['accent'], '#ff6b6b', '#ffd93d'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
+ # ======================
32
+ # ANALYSIS FUNCTIONS
33
+ # ======================
34
 
35
+ class GradioMarketAnalyzer:
36
+ def __init__(self):
37
+ self.data = None
38
+ self.downloader = UnifiedMarketDataDownloader(fred_api_key=FRED_API_KEY)
39
+
40
+ def download_data(self, start_date, end_date):
41
+ """Download market data with progress tracking"""
42
+ try:
43
+ self.data = self.downloader.download_all_data(start_date=start_date, end_date=end_date)
44
+ return f" Data downloaded successfully! Shape: {self.data.shape}"
45
+ except Exception as e:
46
+ return f"❌ Error downloading data: {str(e)}"
47
+
48
+ def plot_market_overview(self):
49
+ """Create market overview plot with custom styling"""
50
+ if self.data is None:
51
+ return None
52
+
53
+ fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(16, 12))
54
+ fig.patch.set_facecolor(CUSTOM_COLORS['background'])
55
+
56
+ # Plot 1: Major US Indices
57
+ equity_cols = [col for col in ['SP500', 'NASDAQ', 'DJI'] if col in self.data.columns]
58
+ if equity_cols:
59
+ for col in equity_cols:
60
+ ax1.plot(self.data.index, self.data[col] / self.data[col].iloc[0],
61
+ label=col, linewidth=2)
62
+ ax1.set_title('US Equity Indices (Normalized)', color=CUSTOM_COLORS['text'], fontsize=14, fontweight='bold')
63
+ ax1.legend()
64
+ ax1.grid(True, alpha=0.3, color=CUSTOM_COLORS['grid'])
65
+
66
+ # Plot 2: Key Rates
67
+ rate_cols = [col for col in ['DGS10', 'DGS2', 'DGS3MO'] if col in self.data.columns]
68
+ if rate_cols:
69
+ for col in rate_cols:
70
+ ax2.plot(self.data.index, self.data[col], label=col, linewidth=2)
71
+ ax2.set_title('Treasury Yields', color=CUSTOM_COLORS['text'], fontsize=14, fontweight='bold')
72
+ ax2.legend()
73
+ ax2.grid(True, alpha=0.3, color=CUSTOM_COLORS['grid'])
74
+
75
+ # Plot 3: Commodities
76
+ commodity_cols = [col for col in ['Gold', 'Oil', 'Copper'] if col in self.data.columns]
77
+ if commodity_cols:
78
+ for col in commodity_cols:
79
+ ax3.plot(self.data.index, self.data[col] / self.data[col].iloc[0],
80
+ label=col, linewidth=2)
81
+ ax3.set_title('Commodities (Normalized)', color=CUSTOM_COLORS['text'], fontsize=14, fontweight='bold')
82
+ ax3.legend()
83
+ ax3.grid(True, alpha=0.3, color=CUSTOM_COLORS['grid'])
84
+
85
+ # Plot 4: Risk Indicators
86
+ risk_cols = [col for col in ['VIX', 'HYG'] if col in self.data.columns]
87
+ if risk_cols:
88
+ for col in risk_cols:
89
+ ax4.plot(self.data.index, self.data[col], label=col, linewidth=2)
90
+ ax4.set_title('Risk Indicators', color=CUSTOM_COLORS['text'], fontsize=14, fontweight='bold')
91
+ ax4.legend()
92
+ ax4.grid(True, alpha=0.3, color=CUSTOM_COLORS['grid'])
93
+
94
+ plt.tight_layout()
95
+ return fig
96
 
97
+ def plot_economic_indicators(self):
98
+ """Plot key economic indicators"""
99
+ if self.data is None:
100
+ return None
101
+
102
+ fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(16, 12))
103
+ fig.patch.set_facecolor(CUSTOM_COLORS['background'])
104
+
105
+ # Plot 1: Inflation
106
+ inflation_cols = [col for col in ['CPIAUCSL', 'CPILFESL'] if col in self.data.columns]
107
+ if inflation_cols:
108
+ for col in inflation_cols:
109
+ ax1.plot(self.data.index, self.data[col], label=col, linewidth=2)
110
+ ax1.set_title('Inflation Indicators', color=CUSTOM_COLORS['text'], fontsize=14, fontweight='bold')
111
+ ax1.legend()
112
+ ax1.grid(True, alpha=0.3, color=CUSTOM_COLORS['grid'])
113
+
114
+ # Plot 2: Labor Market
115
+ labor_cols = [col for col in ['UNRATE', 'PAYEMS'] if col in self.data.columns]
116
+ if labor_cols:
117
+ for col in labor_cols:
118
+ if col == 'PAYEMS': # Scale payrolls for better visualization
119
+ scaled_data = self.data[col] / 1000
120
+ ax2.plot(self.data.index, scaled_data, label=f"{col} (thousands)", linewidth=2)
121
+ else:
122
+ ax2.plot(self.data.index, self.data[col], label=col, linewidth=2)
123
+ ax2.set_title('Labor Market', color=CUSTOM_COLORS['text'], fontsize=14, fontweight='bold')
124
+ ax2.legend()
125
+ ax2.grid(True, alpha=0.3, color=CUSTOM_COLORS['grid'])
126
+
127
+ # Plot 3: Yield Curve Spread
128
+ if 'T10Y2Y' in self.data.columns:
129
+ ax3.plot(self.data.index, self.data['T10Y2Y'],
130
+ color=CUSTOM_COLORS['primary'], linewidth=2)
131
+ ax3.axhline(y=0, color='white', linestyle='--', alpha=0.5)
132
+ ax3.set_title('10Y-2Y Yield Spread (Recession Indicator)',
133
+ color=CUSTOM_COLORS['text'], fontsize=14, fontweight='bold')
134
+ ax3.grid(True, alpha=0.3, color=CUSTOM_COLORS['grid'])
135
+
136
+ # Plot 4: Consumer Sentiment
137
+ sentiment_cols = [col for col in ['UMCSENT'] if col in self.data.columns]
138
+ if sentiment_cols:
139
+ for col in sentiment_cols:
140
+ ax4.plot(self.data.index, self.data[col],
141
+ color=CUSTOM_COLORS['accent'], linewidth=2)
142
+ ax4.set_title('Consumer Sentiment', color=CUSTOM_COLORS['text'], fontsize=14, fontweight='bold')
143
+ ax4.grid(True, alpha=0.3, color=CUSTOM_COLORS['grid'])
144
+
145
+ plt.tight_layout()
146
+ return fig
147
 
148
+ def plot_geopolitical_indicators(self):
149
+ """Plot geopolitical risk indicators"""
150
+ if self.data is None:
151
+ return None
152
+
153
+ fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(16, 12))
154
+ fig.patch.set_facecolor(CUSTOM_COLORS['background'])
155
+
156
+ # Plot 1: Defense vs Market
157
+ if all(col in self.data.columns for col in ['Defense_Stocks', 'SP500']):
158
+ defense_relative = self.data['Defense_Stocks'] / self.data['SP500']
159
+ ax1.plot(self.data.index, defense_relative,
160
+ color=CUSTOM_COLORS['primary'], linewidth=2)
161
+ ax1.set_title('Defense Stocks Relative to S&P 500',
162
+ color=CUSTOM_COLORS['text'], fontsize=14, fontweight='bold')
163
+ ax1.grid(True, alpha=0.3, color=CUSTOM_COLORS['grid'])
164
+
165
+ # Plot 2: Safe Havens
166
+ safe_haven_cols = [col for col in ['Gold', 'TLT', 'Bitcoin'] if col in self.data.columns]
167
+ if safe_haven_cols:
168
+ for col in safe_haven_cols:
169
+ ax2.plot(self.data.index, self.data[col] / self.data[col].iloc[0],
170
+ label=col, linewidth=2)
171
+ ax2.set_title('Safe Haven Assets (Normalized)',
172
+ color=CUSTOM_COLORS['text'], fontsize=14, fontweight='bold')
173
+ ax2.legend()
174
+ ax2.grid(True, alpha=0.3, color=CUSTOM_COLORS['grid'])
175
+
176
+ # Plot 3: Global Markets
177
+ global_cols = [col for col in ['China', 'Europe', 'Japan', 'Emerging_Markets'] if col in self.data.columns]
178
+ if global_cols:
179
+ for col in global_cols:
180
+ ax3.plot(self.data.index, self.data[col] / self.data[col].iloc[0],
181
+ label=col, linewidth=2)
182
+ ax3.set_title('Global Markets (Normalized)',
183
+ color=CUSTOM_COLORS['text'], fontsize=14, fontweight='bold')
184
+ ax3.legend()
185
+ ax3.grid(True, alpha=0.3, color=CUSTOM_COLORS['grid'])
186
+
187
+ # Plot 4: VIX - Fear Index
188
+ if 'VIX' in self.data.columns:
189
+ ax4.plot(self.data.index, self.data['VIX'],
190
+ color='#ff6b6b', linewidth=2)
191
+ ax4.axhline(y=20, color='white', linestyle='--', alpha=0.5, label='High Volatility')
192
+ ax4.set_title('VIX - Volatility Index',
193
+ color=CUSTOM_COLORS['text'], fontsize=14, fontweight='bold')
194
+ ax4.legend()
195
+ ax4.grid(True, alpha=0.3, color=CUSTOM_COLORS['grid'])
196
+
197
+ plt.tight_layout()
198
+ return fig
199
 
200
+ def get_market_summary(self):
201
+ """Generate market summary statistics"""
202
+ if self.data is None:
203
+ return "No data available. Please download data first."
204
+
205
+ summary_text = "## 📊 Market Summary\n\n"
206
+
207
+ # Equity Performance
208
+ if 'SP500' in self.data.columns:
209
+ spx_current = self.data['SP500'].iloc[-1]
210
+ spx_1m = (self.data['SP500'].iloc[-1] / self.data['SP500'].iloc[-22] - 1) * 100
211
+ summary_text += f"**S&P 500**: {spx_current:,.0f} ({spx_1m:+.2f}% 1M)\n"
212
+
213
+ # Key Rates
214
+ if 'DGS10' in self.data.columns:
215
+ ten_yr = self.data['DGS10'].iloc[-1]
216
+ summary_text += f"**10Y Yield**: {ten_yr:.2f}%\n"
217
+
218
+ # VIX
219
+ if 'VIX' in self.data.columns:
220
+ vix = self.data['VIX'].iloc[-1]
221
+ summary_text += f"**VIX**: {vix:.1f}\n"
222
+
223
+ # Gold
224
+ if 'Gold' in self.data.columns:
225
+ gold = self.data['Gold'].iloc[-1]
226
+ summary_text += f"**Gold**: ${gold:,.0f}\n"
227
+
228
+ # Yield Curve
229
+ if 'T10Y2Y' in self.data.columns:
230
+ spread = self.data['T10Y2Y'].iloc[-1]
231
+ curve_status = "INVERTED 🔴" if spread < 0 else "NORMAL 🟢"
232
+ summary_text += f"**Yield Curve**: {spread:.2f}% ({curve_status})\n"
233
+
234
+ # Unemployment
235
+ if 'UNRATE' in self.data.columns:
236
+ unemployment = self.data['UNRATE'].iloc[-1]
237
+ summary_text += f"**Unemployment**: {unemployment:.1f}%\n"
238
+
239
+ return summary_text
240
 
241
+ # ======================
242
+ # GRADIO INTERFACE
243
+ # ======================
244
+
245
+ def create_interface():
246
+ analyzer = GradioMarketAnalyzer()
247
+
248
+ with gr.Blocks(theme='JohnSmith9982/small_and_pretty', css=".gradio-container {background: linear-gradient(135deg, #050505, #00ff9b);}") as demo:
249
+ gr.HTML("""
250
+ <div style='text-align: center; padding: 20px; background: linear-gradient(135deg, #050505, #00ff9b);
251
+ border-radius: 15px; margin: 20px 0;'>
252
+ <h1 style='color: white; margin: 0;'>🌍 Geo-Macro Analysis Dashboard</h1>
253
+ <p style='color: white; margin: 10px 0 0 0;'>Comprehensive Geopolitical & Macroeconomic Analysis</p>
254
+ </div>
255
+ """)
256
+
257
+ with gr.Row():
258
+ with gr.Column(scale=1):
259
+ start_date = gr.Textbox(value="2020-01-01", label="Start Date")
260
+ end_date = gr.Textbox(value=datetime.now().strftime('%Y-%m-%d'), label="End Date")
261
+ download_btn = gr.Button("📥 Download Market Data", variant="primary")
262
+ download_status = gr.Textbox(label="Download Status", interactive=False)
263
+
264
+ with gr.Column(scale=2):
265
+ summary = gr.Markdown(label="Market Summary")
266
+
267
+ with gr.Tab("Market Overview"):
268
+ market_plot = gr.Plot(label="Market Overview")
269
+
270
+ with gr.Tab("Economic Indicators"):
271
+ economic_plot = gr.Plot(label="Economic Indicators")
272
+
273
+ with gr.Tab("Geopolitical Analysis"):
274
+ geo_plot = gr.Plot(label="Geopolitical Indicators")
275
+
276
+ # Event handlers
277
+ download_btn.click(
278
+ fn=analyzer.download_data,
279
+ inputs=[start_date, end_date],
280
+ outputs=[download_status]
281
  )
282
 
283
+ download_btn.click(
284
+ fn=analyzer.get_market_summary,
285
+ inputs=[],
286
+ outputs=[summary]
287
+ )
288
+
289
+ download_btn.click(
290
+ fn=analyzer.plot_market_overview,
291
+ inputs=[],
292
+ outputs=[market_plot]
293
+ )
294
+
295
+ download_btn.click(
296
+ fn=analyzer.plot_economic_indicators,
297
+ inputs=[],
298
+ outputs=[economic_plot]
299
+ )
300
+
301
+ download_btn.click(
302
+ fn=analyzer.plot_geopolitical_indicators,
303
+ inputs=[],
304
+ outputs=[geo_plot]
305
+ )
306
+
307
+ # Initial load
308
+ demo.load(
309
+ fn=analyzer.download_data,
310
+ inputs=[start_date, end_date],
311
+ outputs=[download_status]
312
+ )
313
+
314
+ demo.load(
315
+ fn=analyzer.get_market_summary,
316
+ inputs=[],
317
+ outputs=[summary]
318
+ )
319
+
320
+ demo.load(
321
+ fn=analyzer.plot_market_overview,
322
+ inputs=[],
323
+ outputs=[market_plot]
324
+ )
325
+
326
+ demo.load(
327
+ fn=analyzer.plot_economic_indicators,
328
+ inputs=[],
329
+ outputs=[economic_plot]
330
+ )
331
+
332
+ demo.load(
333
+ fn=analyzer.plot_geopolitical_indicators,
334
+ inputs=[],
335
+ outputs=[geo_plot]
336
  )
337
 
338
+ return demo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
339
 
340
+ # ======================
341
+ # MAIN EXECUTION
342
+ # ======================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
343
 
344
  if __name__ == "__main__":
345
+ # Create and launch the interface
346
+ demo = create_interface()
347
  demo.launch()