JayLacoma commited on
Commit
5f94e53
·
verified ·
1 Parent(s): b8a1191

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +503 -94
app.py CHANGED
@@ -4,22 +4,58 @@ import pandas as pd
4
  import numpy as np
5
  import plotly.express as px
6
  import plotly.graph_objects as go
 
7
  from datetime import datetime, timedelta
 
 
 
 
 
8
 
9
  # Import your data engine
10
  from geo_macro import UnifiedMarketDataDownloader, FRED_API_KEY
11
 
12
  # ======================
13
- # DATA LOADING & CACHING
14
  # ======================
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
- def load_or_download_data():
 
 
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  downloader = UnifiedMarketDataDownloader(fred_api_key=FRED_API_KEY)
20
  df = downloader.download_all_data(start_date='2018-01-01')
21
- df.to_csv(data_file)
22
- print(f"💾 Saved to {data_file}")
23
  return df
24
 
25
  # ======================
@@ -27,51 +63,114 @@ def load_or_download_data():
27
  # ======================
28
 
29
  def add_thematic_features(df):
 
30
  THEMES = {
31
- "AI & Datacenters": ["XLK", "SMH", "SKYY", "BOTZ", "FINX"],
32
- "Defense & Security": ["ITA", "XAR", "HACK", "URA", "Aerospace_Defense"],
33
- "Nuclear Renaissance": ["URA", "XLE", "Utilities"],
34
- "China Stress": ["KWEB", "FXI", "CNY=X"],
35
- "Commodity Inflation": ["DBA", "DBB", "Oil", "Copper", "Gold"],
36
- "Gold & Safe Havens": ["GLD", "TLT", "JPY=X", "CHF=X", "Gold"],
37
- "Early Cycle": ["IWN", "XHB", "Staffing", "Small_Cap_Value"],
38
- "Late Cycle": ["VYM", "XLU", "Consumer_Staples", "High_Dividend"],
39
- "Credit Stress": ["EMB", "HYG", "BKLN", "JNK", "Preferred_Stock"],
40
- "Liquidity Conditions": ["M2", "WALCL", "Short_Term_Treasuries"]
41
  }
42
 
43
  df = df.copy()
44
  for name, assets in THEMES.items():
45
  available = [a for a in assets if a in df.columns]
46
  if available:
47
- # Equal-weight momentum
48
- mom = df[available].pct_change().mean(axis=1)
49
- df[f"{name}_Momentum"] = mom.rolling(60).sum()
 
 
50
  # Z-score over 2 years
51
- mean = df[f"{name}_Momentum"].rolling(500, min_periods=100).mean()
52
- std = df[f"{name}_Momentum"].rolling(500, min_periods=100).std()
53
- df[f"{name}_Z"] = (df[f"{name}_Momentum"] - mean) / std
54
  else:
55
  df[f"{name}_Z"] = np.nan
 
56
  return df
57
 
58
- # ======================
59
- # PLOT FUNCTIONS (same as before, but use cached data)
60
- # ======================
61
-
62
- SHOCK_EVENTS = {
63
- "2020-03-16 (Pandemic Crash)": "2020-03-16",
64
- "2022-02-24 (Ukraine Invasion)": "2022-02-24",
65
- "2023-03-10 (SVB Collapse)": "2023-03-10",
66
- "2024-01-15 (Debt Ceiling)": "2024-01-15",
67
- "2025-04-01 (Middle East Escalation)": "2025-04-01"
68
- }
 
 
 
 
 
 
 
 
69
 
70
  def get_processed_data():
 
71
  df = load_or_download_data()
72
  return add_thematic_features(df)
73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  def plot_regime_dashboard(start_date, end_date):
 
75
  df = get_processed_data()
76
  df = df[(df.index >= pd.to_datetime(start_date)) & (df.index <= pd.to_datetime(end_date))]
77
 
@@ -86,13 +185,25 @@ def plot_regime_dashboard(start_date, end_date):
86
  z=heatmap_data.T.values,
87
  x=heatmap_data.index,
88
  y=clean_names,
89
- colorscale='RdBu',
90
- zmid=0
 
 
 
91
  ))
92
- fig.update_layout(title="🌍 Thematic Regime Heatmap", height=500)
 
 
 
 
 
 
 
 
93
  return fig
94
 
95
  def plot_thematic_pulse(start_date, end_date):
 
96
  df = get_processed_data()
97
  df = df[(df.index >= pd.to_datetime(start_date)) & (df.index <= pd.to_datetime(end_date))]
98
 
@@ -109,93 +220,391 @@ def plot_thematic_pulse(start_date, end_date):
109
  clean_names = [col.replace('_Z', '').replace('_', ' ') for col in latest.index]
110
  latest.index = clean_names
111
 
112
- colors = ['red' if x < -1.5 else 'green' if x > 1.5 else 'lightgray' for x in latest]
113
- fig = go.Figure(go.Bar(x=latest.values, y=latest.index, orientation='h', marker_color=colors))
114
- fig.update_layout(title="🔥 Thematic Pulse", height=500)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  return fig
116
 
117
- def plot_divergence(start_date, end_date):
 
118
  df = get_processed_data()
119
  df = df[(df.index >= pd.to_datetime(start_date)) & (df.index <= pd.to_datetime(end_date))]
120
 
121
- if 'KWEB' in df.columns and 'SMH' in df.columns:
122
- ratio = df['KWEB'] / df['SMH']
123
- ratio_norm = (ratio - ratio.rolling(200).mean()) / ratio.rolling(200).std()
124
- fig = go.Figure()
125
- fig.add_trace(go.Scatter(x=ratio.index, y=ratio, name='KWEB/SMH'))
126
- fig.add_trace(go.Scatter(x=ratio_norm.index, y=ratio_norm, name='Z-Score', yaxis='y2'))
127
- fig.update_layout(
128
- title="🔄 China Tech vs Global Semis",
129
- yaxis2=dict(overlaying='y', side='right')
130
- )
131
- return fig
132
- return go.Figure()
133
-
134
- def plot_shock_response(event_name, custom_date=None):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  df = get_processed_data()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
 
137
- if event_name != "Custom Date":
138
- event_date = pd.to_datetime(SHOCK_EVENTS[event_name])
139
- else:
140
- if not custom_date:
141
- return go.Figure()
142
- event_date = pd.to_datetime(custom_date)
143
 
144
- window = 30
145
- start = event_date - timedelta(days=window)
146
- end = event_date + timedelta(days=window)
147
- df_win = df[(df.index >= start) & (df.index <= end)]
 
 
 
 
 
 
 
 
 
 
 
148
 
149
- if df_win.empty:
 
 
 
 
 
150
  return go.Figure()
151
 
152
- assets = ['SP500', 'Gold', 'TLT', 'VIX', 'Oil', 'KWEB', 'SMH', 'ITA']
 
 
 
 
 
153
  fig = go.Figure()
154
- event_idx = df_win.index.get_indexer([event_date], method='nearest')[0]
155
 
156
- for asset in assets:
157
- if asset in df_win.columns:
158
- prices = df_win[asset].dropna()
159
- if len(prices) > 5:
160
- norm = (prices / prices.iloc[event_idx]) * 100
161
- fig.add_trace(go.Scatter(x=norm.index, y=norm, mode='lines', name=asset))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
 
163
- fig.add_vline(x=event_date, line_dash="dash", line_color="red")
164
- fig.update_layout(title=f"📅 Shock: {event_name}", yaxis_title="Normalized to 100")
165
  return fig
166
 
167
  # ======================
168
  # GRADIO UI
169
  # ======================
170
 
171
- with gr.Blocks(title="Macro-Thematic Intelligence") as demo:
172
- gr.Markdown("## 🌐 Top-Down Thematic Intelligence Platform")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
 
174
  with gr.Tabs():
 
 
175
  with gr.Tab("🌍 Regime Dashboard"):
176
- s1 = gr.Textbox("2022-01-01", label="Start")
177
- e1 = gr.Textbox(datetime.today().strftime('%Y-%m-%d'), label="End")
178
- p1 = gr.Plot()
179
- gr.Button("Update").click(plot_regime_dashboard, [s1, e1], p1)
 
 
180
 
 
181
  with gr.Tab("🔥 Thematic Pulse"):
182
- s2 = gr.Textbox("2022-01-01", label="Start")
183
- e2 = gr.Textbox(datetime.today().strftime('%Y-%m-%d'), label="End")
184
- p2 = gr.Plot()
185
- gr.Button("Update").click(plot_thematic_pulse, [s2, e2], p2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
 
187
- with gr.Tab("🔄 Divergence"):
188
- s3 = gr.Textbox("2022-01-01", label="Start")
189
- e3 = gr.Textbox(datetime.today().strftime('%Y-%m-%d'), label="End")
190
- p3 = gr.Plot()
191
- gr.Button("Update").click(plot_divergence, [s3, e3], p3)
 
 
 
 
 
 
 
 
 
192
 
193
- with gr.Tab("📅 Shock Explorer"):
194
- evt = gr.Dropdown(list(SHOCK_EVENTS.keys()) + ["Custom Date"], value=list(SHOCK_EVENTS.keys())[0])
195
- cdt = gr.Textbox("", visible=False)
196
- evt.change(lambda x: gr.update(visible=x=="Custom Date"), evt, cdt)
197
- p4 = gr.Plot()
198
- gr.Button("Plot").click(plot_shock_response, [evt, cdt], p4)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
 
200
  if __name__ == "__main__":
201
  demo.launch()
 
4
  import numpy as np
5
  import plotly.express as px
6
  import plotly.graph_objects as go
7
+ from plotly.subplots import make_subplots
8
  from datetime import datetime, timedelta
9
+ import warnings
10
+ from functools import lru_cache
11
+ import os
12
+
13
+ warnings.filterwarnings('ignore')
14
 
15
  # Import your data engine
16
  from geo_macro import UnifiedMarketDataDownloader, FRED_API_KEY
17
 
18
  # ======================
19
+ # CONFIGURATION
20
  # ======================
21
 
22
+ DATA_FILE = 'unified_market_data.csv'
23
+ CACHE_HOURS = 24
24
+
25
+ # Color scheme for modern dark theme
26
+ COLORS = {
27
+ 'primary': '#00D9FF',
28
+ 'secondary': '#FF6B9D',
29
+ 'accent': '#C0FF00',
30
+ 'warning': '#FFB800',
31
+ 'danger': '#FF3864',
32
+ 'success': '#00FF88',
33
+ 'bg_dark': '#0A0E27',
34
+ 'bg_card': '#151932'
35
+ }
36
 
37
+ # ======================
38
+ # DATA LOADING & CACHING
39
+ # ======================
40
 
41
+ @lru_cache(maxsize=1)
42
+ def load_or_download_data(force_refresh=False):
43
+ """Load data from cache or download fresh data"""
44
+
45
+ # Check if cache exists and is fresh
46
+ if os.path.exists(DATA_FILE) and not force_refresh:
47
+ file_time = datetime.fromtimestamp(os.path.getmtime(DATA_FILE))
48
+ if datetime.now() - file_time < timedelta(hours=CACHE_HOURS):
49
+ print(f"📦 Loading cached data from {DATA_FILE}")
50
+ df = pd.read_csv(DATA_FILE, index_col=0, parse_dates=True)
51
+ return df
52
+
53
+ # Download fresh data
54
+ print("🔄 Downloading fresh market data...")
55
  downloader = UnifiedMarketDataDownloader(fred_api_key=FRED_API_KEY)
56
  df = downloader.download_all_data(start_date='2018-01-01')
57
+ df.to_csv(DATA_FILE)
58
+ print(f"💾 Saved to {DATA_FILE}")
59
  return df
60
 
61
  # ======================
 
63
  # ======================
64
 
65
  def add_thematic_features(df):
66
+ """Add thematic momentum and z-scores"""
67
  THEMES = {
68
+ "AI & Datacenters": ["Technology", "SMH", "SKYY", "BOTZ", "Cloud_Computing"],
69
+ "Defense & Security": ["ITA", "XAR", "HACK", "Aerospace_Defense", "Defense_Stocks"],
70
+ "Nuclear Renaissance": ["URA", "Energy", "Utilities", "Energy_Security"],
71
+ "China Stress": ["KWEB", "FXI", "CNY", "China", "China_Tech"],
72
+ "Commodity Inflation": ["DBA", "DBB", "Oil", "Copper", "Gold", "Agricultural"],
73
+ "Gold & Safe Havens": ["Gold", "Gold_Safe_Haven", "TLT", "JPY", "CHF", "Gold_Miners"],
74
+ "Early Cycle": ["Small_Cap_Value", "XHB", "Homebuilders", "Regional_Banks"],
75
+ "Late Cycle": ["High_Dividend", "Utilities", "Consumer_Staples", "Value_Stocks"],
76
+ "Credit Stress": ["Emerging_Market_Debt", "HYG", "Leveraged_Loans", "JNK"],
77
+ "Liquidity Conditions": ["M2", "WALCL", "Short_Term_Treasuries", "Preferred_Stock"]
78
  }
79
 
80
  df = df.copy()
81
  for name, assets in THEMES.items():
82
  available = [a for a in assets if a in df.columns]
83
  if available:
84
+ # Equal-weight momentum (60-day)
85
+ returns = df[available].pct_change()
86
+ mom = returns.mean(axis=1).rolling(60, min_periods=30).sum()
87
+ df[f"{name}_Momentum"] = mom
88
+
89
  # Z-score over 2 years
90
+ mean = mom.rolling(500, min_periods=100).mean()
91
+ std = mom.rolling(500, min_periods=100).std()
92
+ df[f"{name}_Z"] = (mom - mean) / std
93
  else:
94
  df[f"{name}_Z"] = np.nan
95
+
96
  return df
97
 
98
+ def calculate_portfolio_metrics(df, assets, lookback=252):
99
+ """Calculate Sharpe, volatility, and drawdown for a portfolio"""
100
+ available = [a for a in assets if a in df.columns]
101
+ if not available:
102
+ return pd.DataFrame()
103
+
104
+ returns = df[available].pct_change().mean(axis=1)
105
+
106
+ metrics = pd.DataFrame(index=df.index)
107
+ metrics['Returns'] = returns
108
+ metrics['Cumulative'] = (1 + returns).cumprod()
109
+ metrics['Rolling_Vol'] = returns.rolling(lookback).std() * np.sqrt(252)
110
+ metrics['Rolling_Sharpe'] = (returns.rolling(lookback).mean() * 252) / metrics['Rolling_Vol']
111
+
112
+ # Drawdown
113
+ cum_max = metrics['Cumulative'].expanding().max()
114
+ metrics['Drawdown'] = (metrics['Cumulative'] - cum_max) / cum_max
115
+
116
+ return metrics
117
 
118
  def get_processed_data():
119
+ """Get data with all features"""
120
  df = load_or_download_data()
121
  return add_thematic_features(df)
122
 
123
+ # ======================
124
+ # ANALYSIS FUNCTIONS
125
+ # ======================
126
+
127
+ def analyze_regime_strength(df):
128
+ """Analyze current regime strength"""
129
+ z_cols = [col for col in df.columns if col.endswith('_Z')]
130
+ if not z_cols:
131
+ return pd.DataFrame()
132
+
133
+ latest = df[z_cols].iloc[-1].dropna()
134
+ prev_week = df[z_cols].iloc[-5].dropna() if len(df) > 5 else latest
135
+
136
+ analysis = pd.DataFrame({
137
+ 'Current': latest,
138
+ 'Week_Ago': prev_week,
139
+ 'Change': latest - prev_week,
140
+ 'Strength': latest.apply(lambda x: 'Strong' if abs(x) > 1.5 else 'Moderate' if abs(x) > 0.5 else 'Weak'),
141
+ 'Direction': latest.apply(lambda x: 'Bullish' if x > 0 else 'Bearish')
142
+ })
143
+
144
+ analysis.index = [col.replace('_Z', '').replace('_', ' ') for col in analysis.index]
145
+ return analysis.sort_values('Current', ascending=False)
146
+
147
+ def calculate_correlation_matrix(df, assets, window=60):
148
+ """Calculate rolling correlation matrix"""
149
+ available = [a for a in assets if a in df.columns]
150
+ if len(available) < 2:
151
+ return pd.DataFrame()
152
+
153
+ returns = df[available].pct_change()
154
+ corr = returns.rolling(window).corr()
155
+
156
+ return corr
157
+
158
+ # ======================
159
+ # PLOT FUNCTIONS
160
+ # ======================
161
+
162
+ def create_modern_theme():
163
+ """Create modern plotly theme"""
164
+ return dict(
165
+ plot_bgcolor=COLORS['bg_dark'],
166
+ paper_bgcolor=COLORS['bg_card'],
167
+ font=dict(color='white', family='Arial, sans-serif'),
168
+ xaxis=dict(gridcolor='#2a2e45', showgrid=True),
169
+ yaxis=dict(gridcolor='#2a2e45', showgrid=True),
170
+ )
171
+
172
  def plot_regime_dashboard(start_date, end_date):
173
+ """Enhanced regime heatmap with annotations"""
174
  df = get_processed_data()
175
  df = df[(df.index >= pd.to_datetime(start_date)) & (df.index <= pd.to_datetime(end_date))]
176
 
 
185
  z=heatmap_data.T.values,
186
  x=heatmap_data.index,
187
  y=clean_names,
188
+ colorscale='RdBu_r',
189
+ zmid=0,
190
+ zmin=-3,
191
+ zmax=3,
192
+ colorbar=dict(title="Z-Score", titleside='right')
193
  ))
194
+
195
+ fig.update_layout(
196
+ **create_modern_theme(),
197
+ title=dict(text="🌍 Thematic Regime Heatmap", font=dict(size=24)),
198
+ height=600,
199
+ xaxis_title="Date",
200
+ yaxis_title="Themes"
201
+ )
202
+
203
  return fig
204
 
205
  def plot_thematic_pulse(start_date, end_date):
206
+ """Current thematic strength bar chart"""
207
  df = get_processed_data()
208
  df = df[(df.index >= pd.to_datetime(start_date)) & (df.index <= pd.to_datetime(end_date))]
209
 
 
220
  clean_names = [col.replace('_Z', '').replace('_', ' ') for col in latest.index]
221
  latest.index = clean_names
222
 
223
+ # Color coding based on strength
224
+ colors = [
225
+ COLORS['danger'] if x < -1.5 else
226
+ COLORS['warning'] if x < -0.5 else
227
+ COLORS['success'] if x > 1.5 else
228
+ COLORS['primary'] if x > 0.5 else
229
+ '#444444'
230
+ for x in latest
231
+ ]
232
+
233
+ fig = go.Figure(go.Bar(
234
+ x=latest.values,
235
+ y=latest.index,
236
+ orientation='h',
237
+ marker_color=colors,
238
+ text=[f"{x:.2f}" for x in latest.values],
239
+ textposition='outside'
240
+ ))
241
+
242
+ fig.update_layout(
243
+ **create_modern_theme(),
244
+ title=dict(text="🔥 Current Thematic Pulse", font=dict(size=24)),
245
+ height=600,
246
+ xaxis_title="Z-Score",
247
+ yaxis_title="Themes"
248
+ )
249
+
250
+ return fig
251
+
252
+ def plot_multi_asset_performance(start_date, end_date, assets):
253
+ """Multi-asset normalized performance"""
254
+ df = get_processed_data()
255
+ df = df[(df.index >= pd.to_datetime(start_date)) & (df.index <= pd.to_datetime(end_date))]
256
+
257
+ available = [a for a in assets if a in df.columns]
258
+ if not available:
259
+ return go.Figure()
260
+
261
+ fig = go.Figure()
262
+
263
+ for asset in available:
264
+ prices = df[asset].dropna()
265
+ if len(prices) > 0:
266
+ normalized = (prices / prices.iloc[0]) * 100
267
+ fig.add_trace(go.Scatter(
268
+ x=normalized.index,
269
+ y=normalized,
270
+ mode='lines',
271
+ name=asset,
272
+ line=dict(width=2)
273
+ ))
274
+
275
+ fig.update_layout(
276
+ **create_modern_theme(),
277
+ title=dict(text="📈 Multi-Asset Performance (Normalized)", font=dict(size=24)),
278
+ height=600,
279
+ xaxis_title="Date",
280
+ yaxis_title="Performance (Base = 100)",
281
+ hovermode='x unified'
282
+ )
283
+
284
+ return fig
285
+
286
+ def plot_correlation_heatmap(start_date, end_date, assets):
287
+ """Correlation matrix heatmap"""
288
+ df = get_processed_data()
289
+ df = df[(df.index >= pd.to_datetime(start_date)) & (df.index <= pd.to_datetime(end_date))]
290
+
291
+ available = [a for a in assets if a in df.columns]
292
+ if len(available) < 2:
293
+ return go.Figure()
294
+
295
+ returns = df[available].pct_change().dropna()
296
+ corr = returns.corr()
297
+
298
+ fig = go.Figure(data=go.Heatmap(
299
+ z=corr.values,
300
+ x=corr.columns,
301
+ y=corr.columns,
302
+ colorscale='RdBu_r',
303
+ zmid=0,
304
+ zmin=-1,
305
+ zmax=1,
306
+ text=np.round(corr.values, 2),
307
+ texttemplate='%{text}',
308
+ textfont={"size": 10},
309
+ colorbar=dict(title="Correlation")
310
+ ))
311
+
312
+ fig.update_layout(
313
+ **create_modern_theme(),
314
+ title=dict(text="🔗 Asset Correlation Matrix", font=dict(size=24)),
315
+ height=700,
316
+ width=800
317
+ )
318
+
319
  return fig
320
 
321
+ def plot_drawdown_analysis(start_date, end_date, assets):
322
+ """Drawdown analysis for selected assets"""
323
  df = get_processed_data()
324
  df = df[(df.index >= pd.to_datetime(start_date)) & (df.index <= pd.to_datetime(end_date))]
325
 
326
+ available = [a for a in assets if a in df.columns]
327
+ if not available:
328
+ return go.Figure()
329
+
330
+ fig = make_subplots(rows=2, cols=1, shared_xaxes=True,
331
+ subplot_titles=('Cumulative Performance', 'Drawdown'),
332
+ vertical_spacing=0.1, row_heights=[0.6, 0.4])
333
+
334
+ for asset in available:
335
+ prices = df[asset].dropna()
336
+ if len(prices) > 0:
337
+ cum_ret = (prices / prices.iloc[0]) * 100
338
+ cum_max = cum_ret.expanding().max()
339
+ drawdown = ((cum_ret - cum_max) / cum_max) * 100
340
+
341
+ fig.add_trace(go.Scatter(x=cum_ret.index, y=cum_ret, mode='lines', name=asset),
342
+ row=1, col=1)
343
+ fig.add_trace(go.Scatter(x=drawdown.index, y=drawdown, mode='lines',
344
+ name=asset, showlegend=False, fill='tozeroy'),
345
+ row=2, col=1)
346
+
347
+ fig.update_layout(
348
+ **create_modern_theme(),
349
+ title=dict(text="📉 Drawdown Analysis", font=dict(size=24)),
350
+ height=800,
351
+ hovermode='x unified'
352
+ )
353
+
354
+ fig.update_xaxes(title_text="Date", row=2, col=1)
355
+ fig.update_yaxes(title_text="Performance (%)", row=1, col=1)
356
+ fig.update_yaxes(title_text="Drawdown (%)", row=2, col=1)
357
+
358
+ return fig
359
+
360
+ def plot_rolling_sharpe(start_date, end_date, assets, window=252):
361
+ """Rolling Sharpe ratio analysis"""
362
  df = get_processed_data()
363
+ df = df[(df.index >= pd.to_datetime(start_date)) & (df.index <= pd.to_datetime(end_date))]
364
+
365
+ available = [a for a in assets if a in df.columns]
366
+ if not available:
367
+ return go.Figure()
368
+
369
+ fig = go.Figure()
370
+
371
+ for asset in available:
372
+ returns = df[asset].pct_change().dropna()
373
+ if len(returns) > window:
374
+ rolling_sharpe = (returns.rolling(window).mean() * 252) / (returns.rolling(window).std() * np.sqrt(252))
375
+ fig.add_trace(go.Scatter(
376
+ x=rolling_sharpe.index,
377
+ y=rolling_sharpe,
378
+ mode='lines',
379
+ name=asset
380
+ ))
381
 
382
+ fig.add_hline(y=0, line_dash="dash", line_color="gray")
383
+ fig.add_hline(y=1, line_dash="dot", line_color=COLORS['success'], annotation_text="Sharpe=1")
 
 
 
 
384
 
385
+ fig.update_layout(
386
+ **create_modern_theme(),
387
+ title=dict(text=f"📊 Rolling Sharpe Ratio ({window//252}Y)", font=dict(size=24)),
388
+ height=600,
389
+ xaxis_title="Date",
390
+ yaxis_title="Sharpe Ratio",
391
+ hovermode='x unified'
392
+ )
393
+
394
+ return fig
395
+
396
+ def plot_sector_rotation(start_date, end_date):
397
+ """Sector rotation spider chart"""
398
+ df = get_processed_data()
399
+ df = df[(df.index >= pd.to_datetime(start_date)) & (df.index <= pd.to_datetime(end_date))]
400
 
401
+ sectors = ['Technology', 'Financials', 'Healthcare', 'Consumer_Discretionary',
402
+ 'Consumer_Staples', 'Energy', 'Materials', 'Industrials', 'Utilities',
403
+ 'Real_Estate', 'Communication_Services']
404
+
405
+ available = [s for s in sectors if s in df.columns]
406
+ if not available:
407
  return go.Figure()
408
 
409
+ # Calculate 3-month momentum
410
+ momentum = {}
411
+ for sector in available:
412
+ ret = df[sector].pct_change(60).iloc[-1] * 100
413
+ momentum[sector] = ret
414
+
415
  fig = go.Figure()
 
416
 
417
+ fig.add_trace(go.Scatterpolar(
418
+ r=list(momentum.values()),
419
+ theta=[s.replace('_', ' ') for s in momentum.keys()],
420
+ fill='toself',
421
+ name='3M Momentum',
422
+ line_color=COLORS['primary']
423
+ ))
424
+
425
+ fig.update_layout(
426
+ **create_modern_theme(),
427
+ polar=dict(
428
+ radialaxis=dict(visible=True, gridcolor='#2a2e45'),
429
+ angularaxis=dict(gridcolor='#2a2e45')
430
+ ),
431
+ title=dict(text="🎯 Sector Rotation (3M Momentum %)", font=dict(size=24)),
432
+ height=700
433
+ )
434
+
435
+ return fig
436
+
437
+ def plot_risk_dashboard(start_date, end_date):
438
+ """Risk indicators dashboard"""
439
+ df = get_processed_data()
440
+ df = df[(df.index >= pd.to_datetime(start_date)) & (df.index <= pd.to_datetime(end_date))]
441
+
442
+ risk_assets = ['VIX', 'HYG', 'T10Y2Y', 'DXY', 'Gold']
443
+ available = [a for a in risk_assets if a in df.columns]
444
+
445
+ if not available:
446
+ return go.Figure()
447
+
448
+ fig = make_subplots(
449
+ rows=len(available), cols=1,
450
+ shared_xaxes=True,
451
+ subplot_titles=[a.replace('_', ' ') for a in available],
452
+ vertical_spacing=0.05
453
+ )
454
+
455
+ for i, asset in enumerate(available, 1):
456
+ prices = df[asset].dropna()
457
+ if len(prices) > 0:
458
+ fig.add_trace(
459
+ go.Scatter(x=prices.index, y=prices, mode='lines',
460
+ name=asset, line=dict(color=COLORS['primary'])),
461
+ row=i, col=1
462
+ )
463
+
464
+ fig.update_layout(
465
+ **create_modern_theme(),
466
+ title=dict(text="⚠️ Risk Indicators Dashboard", font=dict(size=24)),
467
+ height=250 * len(available),
468
+ showlegend=False,
469
+ hovermode='x unified'
470
+ )
471
 
 
 
472
  return fig
473
 
474
  # ======================
475
  # GRADIO UI
476
  # ======================
477
 
478
+ # Custom CSS for modern dark theme
479
+ custom_css = """
480
+ .gradio-container {
481
+ background: linear-gradient(135deg, #0A0E27 0%, #1a1f3a 100%) !important;
482
+ }
483
+ .tabs {
484
+ border-radius: 10px;
485
+ }
486
+ button {
487
+ border-radius: 8px !important;
488
+ font-weight: 600 !important;
489
+ }
490
+ """
491
+
492
+ # Get all available tickers
493
+ df_sample = load_or_download_data()
494
+ all_tickers = sorted([col for col in df_sample.columns if not col.endswith('_Z') and not col.endswith('_Momentum')])
495
+
496
+ with gr.Blocks(title="Hedge Fund Intelligence Platform", css=custom_css, theme=gr.themes.Base()) as demo:
497
+
498
+ gr.Markdown("""
499
+ # 🏦 Macro-Thematic Intelligence Platform
500
+ ### Professional-Grade Market Analytics & Regime Detection
501
+ """)
502
 
503
  with gr.Tabs():
504
+
505
+ # Regime Analysis
506
  with gr.Tab("🌍 Regime Dashboard"):
507
+ with gr.Row():
508
+ s1 = gr.Textbox("2023-01-01", label="Start Date")
509
+ e1 = gr.Textbox(datetime.today().strftime('%Y-%m-%d'), label="End Date")
510
+ gr.Button("🔄 Update Dashboard", variant="primary").click(
511
+ plot_regime_dashboard, [s1, e1], gr.Plot()
512
+ )
513
 
514
+ # Thematic Pulse
515
  with gr.Tab("🔥 Thematic Pulse"):
516
+ with gr.Row():
517
+ s2 = gr.Textbox("2023-01-01", label="Start Date")
518
+ e2 = gr.Textbox(datetime.today().strftime('%Y-%m-%d'), label="End Date")
519
+ gr.Button("🔄 Analyze Themes", variant="primary").click(
520
+ plot_thematic_pulse, [s2, e2], gr.Plot()
521
+ )
522
+
523
+ # Multi-Asset Performance
524
+ with gr.Tab("📈 Performance Tracker"):
525
+ with gr.Row():
526
+ s3 = gr.Textbox("2023-01-01", label="Start Date")
527
+ e3 = gr.Textbox(datetime.today().strftime('%Y-%m-%d'), label="End Date")
528
+ assets1 = gr.Dropdown(
529
+ all_tickers,
530
+ value=['SP500', 'NASDAQ', 'Gold', 'Oil', 'TLT'],
531
+ multiselect=True,
532
+ label="Select Assets"
533
+ )
534
+ gr.Button("📊 Plot Performance", variant="primary").click(
535
+ plot_multi_asset_performance, [s3, e3, assets1], gr.Plot()
536
+ )
537
+
538
+ # Correlation Analysis
539
+ with gr.Tab("🔗 Correlation Matrix"):
540
+ with gr.Row():
541
+ s4 = gr.Textbox("2023-01-01", label="Start Date")
542
+ e4 = gr.Textbox(datetime.today().strftime('%Y-%m-%d'), label="End Date")
543
+ assets2 = gr.Dropdown(
544
+ all_tickers,
545
+ value=['SP500', 'Gold', 'Oil', 'DXY', 'TLT', 'VIX'],
546
+ multiselect=True,
547
+ label="Select Assets"
548
+ )
549
+ gr.Button("🔍 Calculate Correlations", variant="primary").click(
550
+ plot_correlation_heatmap, [s4, e4, assets2], gr.Plot()
551
+ )
552
 
553
+ # Drawdown Analysis
554
+ with gr.Tab("📉 Drawdown Analysis"):
555
+ with gr.Row():
556
+ s5 = gr.Textbox("2023-01-01", label="Start Date")
557
+ e5 = gr.Textbox(datetime.today().strftime('%Y-%m-%d'), label="End Date")
558
+ assets3 = gr.Dropdown(
559
+ all_tickers,
560
+ value=['SP500', 'NASDAQ', 'Gold', 'Oil'],
561
+ multiselect=True,
562
+ label="Select Assets"
563
+ )
564
+ gr.Button("📉 Analyze Drawdowns", variant="primary").click(
565
+ plot_drawdown_analysis, [s5, e5, assets3], gr.Plot()
566
+ )
567
 
568
+ # Rolling Sharpe
569
+ with gr.Tab("📊 Sharpe Ratio"):
570
+ with gr.Row():
571
+ s6 = gr.Textbox("2023-01-01", label="Start Date")
572
+ e6 = gr.Textbox(datetime.today().strftime('%Y-%m-%d'), label="End Date")
573
+ assets4 = gr.Dropdown(
574
+ all_tickers,
575
+ value=['SP500', 'Gold', 'TLT'],
576
+ multiselect=True,
577
+ label="Select Assets"
578
+ )
579
+ window = gr.Slider(60, 504, value=252, step=21, label="Rolling Window (days)")
580
+ gr.Button("📈 Calculate Sharpe", variant="primary").click(
581
+ plot_rolling_sharpe, [s6, e6, assets4, window], gr.Plot()
582
+ )
583
+
584
+ # Sector Rotation
585
+ with gr.Tab("🎯 Sector Rotation"):
586
+ with gr.Row():
587
+ s7 = gr.Textbox("2023-01-01", label="Start Date")
588
+ e7 = gr.Textbox(datetime.today().strftime('%Y-%m-%d'), label="End Date")
589
+ gr.Button("🔄 Analyze Sectors", variant="primary").click(
590
+ plot_sector_rotation, [s7, e7], gr.Plot()
591
+ )
592
+
593
+ # Risk Dashboard
594
+ with gr.Tab("⚠️ Risk Monitor"):
595
+ with gr.Row():
596
+ s8 = gr.Textbox("2023-01-01", label="Start Date")
597
+ e8 = gr.Textbox(datetime.today().strftime('%Y-%m-%d'), label="End Date")
598
+ gr.Button("🚨 Load Risk Indicators", variant="primary").click(
599
+ plot_risk_dashboard, [s8, e8], gr.Plot()
600
+ )
601
+
602
+ gr.Markdown("""
603
+ ---
604
+ **Data Sources:** Yahoo Finance, FRED
605
+ **Refresh Rate:** 24 hours
606
+ **Last Updated:** Real-time market data
607
+ """)
608
 
609
  if __name__ == "__main__":
610
  demo.launch()