JayLacoma commited on
Commit
b183b82
·
verified ·
1 Parent(s): ec50c09

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +471 -84
app.py CHANGED
@@ -5,17 +5,41 @@ import gradio as gr
5
  import pandas as pd
6
  import plotly.graph_objects as go
7
  import plotly.express as px
 
8
  from geo_macro import UnifiedMarketDataDownloader, FRED_API_KEY
9
  from feature_engineering import IntegratedTheoryFeatures
10
 
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
- # Cache to avoid re-downloading data
 
 
 
 
 
 
 
 
 
14
  _cached_df = None
15
  _cached_dates = (None, None)
16
 
17
 
18
  def get_data(start_date: str, end_date: str):
 
19
  global _cached_df, _cached_dates
20
  if _cached_df is not None and _cached_dates == (start_date, end_date):
21
  return _cached_df.copy()
@@ -29,147 +53,510 @@ def get_data(start_date: str, end_date: str):
29
  return df
30
 
31
 
 
 
32
  def create_composite_bar(latest):
33
- """Bar chart of the 4 core normalized scores"""
34
  scores = {
35
- "Dalio Composite": latest['dalio_composite_norm'],
36
- "Stevenson Inequality": latest['stevenson_inequality_norm'],
37
- "Thiel Monopoly": latest['thiel_monopoly_norm'],
38
- "Gundlach Reckoning": latest['gundlach_reckoning_norm'],
39
  }
40
- colors = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728"]
 
 
41
  fig = go.Figure(go.Bar(
42
  x=list(scores.keys()),
43
  y=list(scores.values()),
44
- marker_color=colors,
45
- text=[f"{v:.3f}" for v in scores.values()],
46
- textposition='auto',
 
 
 
 
 
47
  ))
 
48
  fig.update_layout(
49
- title="Core Theory Scores (Normalized)",
50
- yaxis_range=[-1, 1],
51
- height=350
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  )
 
53
  return fig
54
 
55
 
56
- def create_probabilities_bar(latest):
57
- """Horizontal bar chart of scenario probabilities"""
58
- probs = {
59
- "Credit Collapse": latest['prob_credit_collapse'],
60
- "Stagflation": latest['prob_stagflation'],
61
- "Tech Boom": latest['prob_tech_boom'],
62
  }
63
- fig = go.Figure(go.Bar(
64
- x=list(probs.values()),
65
- y=list(probs.keys()),
66
- orientation='h',
67
- marker_color=["#d62728", "#ff7f0e", "#2ca02c"],
68
- text=[f"{v:.1%}" for v in probs.values()],
69
- textposition='auto',
70
- ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  fig.update_layout(
72
- title="Scenario Probabilities",
73
- xaxis_tickformat='.0%',
74
- xaxis_range=[0, 1],
75
- height=250
76
  )
 
77
  return fig
78
 
79
 
80
  def create_regime_timeline(features):
81
- """Timeline of regime over last 252 days"""
82
  tail = features[['regime']].tail(252).copy()
83
  tail['date'] = tail.index
84
- color_map = {
85
- 'CRISIS': '#d62728',
86
- 'INEQUALITY_TRAP': '#ff7f0e',
87
- 'GEOPOLITICAL_SHOCK': '#9467bd',
88
- 'TECH_MONOPOLY': '#2ca02c',
89
- 'TRANSITION': '#7f7f7f'
 
 
90
  }
91
- tail['color'] = tail['regime'].map(color_map)
92
- fig = go.Figure(go.Scatter(
93
- x=tail['date'],
94
- y=tail['regime'],
95
- mode='markers',
96
- marker=dict(color=tail['color'], size=8),
97
- showlegend=False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  ))
 
99
  fig.update_layout(
100
- title="Regime Timeline (Last 12 Months)",
101
- height=200,
102
- yaxis_title="Regime",
103
- xaxis_title="Date"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  )
 
105
  return fig
106
 
107
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  def run_pipeline(days_back: int = 1825):
 
109
  try:
110
  today = pd.Timestamp.today()
111
  start_date = (today - pd.Timedelta(days=days_back)).strftime('%Y-%m-%d')
112
  end_date = today.strftime('%Y-%m-%d')
113
 
 
114
  df = get_data(start_date, end_date)
115
  if len(df) < 300:
116
- return {"Error": "Insufficient data. Try increasing lookback window."}, None, None, None
 
 
 
 
 
 
 
 
117
 
 
118
  engine = IntegratedTheoryFeatures(df)
119
  features = engine.build_all_features()
120
  latest = features.dropna(subset=['regime']).iloc[-1]
121
 
122
- # Format JSON
123
- def fmt(x):
124
- if pd.isna(x): return "N/A"
125
- if isinstance(x, float) and -1 <= x <= 1: return f"{x:.3f}"
126
- elif isinstance(x, float) and 0 <= x <= 1: return f"{x:.1%}"
127
- else: return str(x)
128
 
 
129
  json_output = {
130
- "Regime": str(latest["regime"]),
131
- "Dalio Composite": fmt(latest['dalio_composite_norm']),
132
- "Stevenson Inequality": fmt(latest['stevenson_inequality_norm']),
133
- "Thiel Monopoly": fmt(latest['thiel_monopoly_norm']),
134
- "Gundlach Reckoning": fmt(latest['gundlach_reckoning_norm']),
135
- "Prob Credit Collapse": fmt(latest['prob_credit_collapse']),
136
- "Prob Stagflation": fmt(latest['prob_stagflation']),
137
- "Prob Tech Boom": fmt(latest['prob_tech_boom']),
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  }
139
 
140
- # Create plots
141
- composite_fig = create_composite_bar(latest)
142
- prob_fig = create_probabilities_bar(latest)
143
- timeline_fig = create_regime_timeline(features)
144
-
145
- return json_output, composite_fig, prob_fig, timeline_fig
146
 
147
  except Exception as e:
148
- error = {"Error": str(e)}
149
- return error, None, None, None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
 
 
 
 
 
151
 
152
- # Gradio UI
153
- with gr.Blocks(title="🌍 Integrated Market Theory Dashboard") as demo:
154
- gr.Markdown("# 🌍 Integrated Market Theory Dashboard")
155
- gr.Markdown("Real-time macro regime detection using Dalio, Stevenson, Thiel & Gundlach frameworks")
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
  with gr.Row():
158
- days = gr.Slider(365, 2500, value=1825, step=90, label="Lookback Window (days)")
159
- run_btn = gr.Button("🔄 Update Analysis", variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
 
161
  with gr.Row():
162
- json_output = gr.JSON(label="Current State")
163
- composite_plot = gr.Plot(label="Core Theory Scores")
 
 
 
 
 
164
 
165
  with gr.Row():
 
166
  prob_plot = gr.Plot(label="Scenario Probabilities")
 
 
 
 
 
167
  timeline_plot = gr.Plot(label="Regime Timeline")
168
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  run_btn.click(
170
  run_pipeline,
171
- inputs=days,
172
- outputs=[json_output, composite_plot, prob_plot, timeline_plot]
173
  )
 
 
 
 
 
 
 
 
174
 
175
- demo.launch()
 
 
 
 
 
 
 
5
  import pandas as pd
6
  import plotly.graph_objects as go
7
  import plotly.express as px
8
+ from datetime import datetime, timedelta
9
  from geo_macro import UnifiedMarketDataDownloader, FRED_API_KEY
10
  from feature_engineering import IntegratedTheoryFeatures
11
 
12
 
13
+ # ==================== COLOR PALETTE ====================
14
+ COLORS = {
15
+ 'primary': '#2E5EAA', # Deep blue
16
+ 'secondary': '#4A90E2', # Light blue
17
+ 'success': '#52B788', # Green
18
+ 'warning': '#F4A261', # Orange
19
+ 'danger': '#E63946', # Red
20
+ 'purple': '#9D4EDD', # Purple
21
+ 'teal': '#06AED5', # Teal
22
+ 'gray': '#6C757D', # Gray
23
+ 'light_bg': '#F8F9FA', # Light background
24
+ 'border': '#DEE2E6', # Border
25
+ }
26
 
27
+ REGIME_COLORS = {
28
+ 'CRISIS': COLORS['danger'],
29
+ 'INEQUALITY_TRAP': COLORS['warning'],
30
+ 'GEOPOLITICAL_SHOCK': COLORS['purple'],
31
+ 'TECH_MONOPOLY': COLORS['success'],
32
+ 'TRANSITION': COLORS['gray']
33
+ }
34
+
35
+
36
+ # ==================== DATA CACHING ====================
37
  _cached_df = None
38
  _cached_dates = (None, None)
39
 
40
 
41
  def get_data(start_date: str, end_date: str):
42
+ """Fetch market data with caching"""
43
  global _cached_df, _cached_dates
44
  if _cached_df is not None and _cached_dates == (start_date, end_date):
45
  return _cached_df.copy()
 
53
  return df
54
 
55
 
56
+ # ==================== VISUALIZATION FUNCTIONS ====================
57
+
58
  def create_composite_bar(latest):
59
+ """Enhanced bar chart of the 4 core normalized scores"""
60
  scores = {
61
+ "Dalio\nComposite": latest['dalio_composite_norm'],
62
+ "Stevenson\nInequality": latest['stevenson_inequality_norm'],
63
+ "Thiel\nMonopoly": latest['thiel_monopoly_norm'],
64
+ "Gundlach\nReckoning": latest['gundlach_reckoning_norm'],
65
  }
66
+
67
+ colors = [COLORS['primary'], COLORS['warning'], COLORS['success'], COLORS['danger']]
68
+
69
  fig = go.Figure(go.Bar(
70
  x=list(scores.keys()),
71
  y=list(scores.values()),
72
+ marker=dict(
73
+ color=colors,
74
+ line=dict(color='white', width=2)
75
+ ),
76
+ text=[f"{v:.2f}" for v in scores.values()],
77
+ textposition='outside',
78
+ textfont=dict(size=14, color='#2C3E50'),
79
+ hovertemplate='<b>%{x}</b><br>Score: %{y:.3f}<extra></extra>'
80
  ))
81
+
82
  fig.update_layout(
83
+ title=dict(
84
+ text="<b>Core Theory Scores</b>",
85
+ font=dict(size=18, color='#2C3E50'),
86
+ x=0.5,
87
+ xanchor='center'
88
+ ),
89
+ yaxis=dict(
90
+ range=[-1, 1],
91
+ title="Normalized Score",
92
+ gridcolor=COLORS['border'],
93
+ zeroline=True,
94
+ zerolinecolor=COLORS['gray'],
95
+ zerolinewidth=2
96
+ ),
97
+ xaxis=dict(
98
+ title="",
99
+ tickfont=dict(size=11)
100
+ ),
101
+ height=400,
102
+ plot_bgcolor='white',
103
+ paper_bgcolor='white',
104
+ margin=dict(t=60, b=40, l=60, r=40),
105
+ font=dict(family="Arial, sans-serif")
106
  )
107
+
108
  return fig
109
 
110
 
111
+ def create_probabilities_gauge(latest):
112
+ """Three gauge charts for scenario probabilities"""
113
+ scenarios = {
114
+ "Credit Collapse": (latest['prob_credit_collapse'], COLORS['danger']),
115
+ "Stagflation": (latest['prob_stagflation'], COLORS['warning']),
116
+ "Tech Boom": (latest['prob_tech_boom'], COLORS['success']),
117
  }
118
+
119
+ fig = go.Figure()
120
+
121
+ positions = [(0, 0.5), (0.365, 0.5), (0.73, 0.5)]
122
+
123
+ for i, (name, (value, color)) in enumerate(scenarios.items()):
124
+ x_pos, y_pos = positions[i]
125
+
126
+ fig.add_trace(go.Indicator(
127
+ mode="gauge+number",
128
+ value=value * 100,
129
+ title={'text': f"<b>{name}</b>", 'font': {'size': 14}},
130
+ number={'suffix': "%", 'font': {'size': 20}},
131
+ gauge={
132
+ 'axis': {'range': [0, 100], 'tickwidth': 1},
133
+ 'bar': {'color': color, 'thickness': 0.75},
134
+ 'bgcolor': "white",
135
+ 'borderwidth': 2,
136
+ 'bordercolor': COLORS['border'],
137
+ 'steps': [
138
+ {'range': [0, 30], 'color': '#E8F5E9'},
139
+ {'range': [30, 70], 'color': '#FFF3E0'},
140
+ {'range': [70, 100], 'color': '#FFEBEE'}
141
+ ],
142
+ 'threshold': {
143
+ 'line': {'color': "#2C3E50", 'width': 3},
144
+ 'thickness': 0.75,
145
+ 'value': value * 100
146
+ }
147
+ },
148
+ domain={'x': [x_pos, x_pos + 0.27], 'y': [y_pos, y_pos + 0.5]}
149
+ ))
150
+
151
  fig.update_layout(
152
+ height=300,
153
+ paper_bgcolor='white',
154
+ font={'family': "Arial, sans-serif", 'color': '#2C3E50'},
155
+ margin=dict(t=40, b=20, l=20, r=20)
156
  )
157
+
158
  return fig
159
 
160
 
161
  def create_regime_timeline(features):
162
+ """Enhanced timeline with area fill and annotations"""
163
  tail = features[['regime']].tail(252).copy()
164
  tail['date'] = tail.index
165
+
166
+ # Map regime to numeric value for plotting
167
+ regime_order = {
168
+ 'CRISIS': 4,
169
+ 'GEOPOLITICAL_SHOCK': 3,
170
+ 'INEQUALITY_TRAP': 2,
171
+ 'TECH_MONOPOLY': 1,
172
+ 'TRANSITION': 0
173
  }
174
+ tail['regime_num'] = tail['regime'].map(regime_order)
175
+ tail['color'] = tail['regime'].map(REGIME_COLORS)
176
+
177
+ fig = go.Figure()
178
+
179
+ # Add colored area segments
180
+ for regime in regime_order.keys():
181
+ mask = tail['regime'] == regime
182
+ if mask.any():
183
+ fig.add_trace(go.Scatter(
184
+ x=tail[mask]['date'],
185
+ y=tail[mask]['regime_num'],
186
+ mode='markers',
187
+ name=regime,
188
+ marker=dict(
189
+ color=REGIME_COLORS[regime],
190
+ size=10,
191
+ line=dict(color='white', width=1)
192
+ ),
193
+ hovertemplate=f'<b>{regime}</b><br>Date: %{{x|%Y-%m-%d}}<extra></extra>'
194
+ ))
195
+
196
+ fig.update_layout(
197
+ title=dict(
198
+ text="<b>Regime Timeline (Last 12 Months)</b>",
199
+ font=dict(size=18, color='#2C3E50'),
200
+ x=0.5,
201
+ xanchor='center'
202
+ ),
203
+ height=350,
204
+ yaxis=dict(
205
+ title="Market Regime",
206
+ ticktext=['Transition', 'Tech Monopoly', 'Inequality Trap', 'Geo Shock', 'Crisis'],
207
+ tickvals=[0, 1, 2, 3, 4],
208
+ gridcolor=COLORS['border']
209
+ ),
210
+ xaxis=dict(
211
+ title="Date",
212
+ gridcolor=COLORS['border']
213
+ ),
214
+ plot_bgcolor='white',
215
+ paper_bgcolor='white',
216
+ margin=dict(t=60, b=40, l=80, r=40),
217
+ legend=dict(
218
+ orientation="h",
219
+ yanchor="bottom",
220
+ y=-0.3,
221
+ xanchor="center",
222
+ x=0.5
223
+ ),
224
+ font=dict(family="Arial, sans-serif")
225
+ )
226
+
227
+ return fig
228
+
229
+
230
+ def create_forces_radar(latest):
231
+ """Radar chart showing Dalio's five forces"""
232
+ forces = {
233
+ 'Debt Cycle': latest['dalio_debt_cycle'],
234
+ 'Internal Conflict': latest['dalio_internal_conflict'],
235
+ 'External Conflict': latest['dalio_external_conflict'],
236
+ 'Tech Force': latest['dalio_tech_force'],
237
+ 'Nature Force': latest['dalio_nature_force']
238
+ }
239
+
240
+ # Normalize to 0-1 for better visualization
241
+ categories = list(forces.keys())
242
+ values = [(v + 3) / 6 for v in forces.values()] # Scale from [-3,3] to [0,1]
243
+
244
+ fig = go.Figure()
245
+
246
+ fig.add_trace(go.Scatterpolar(
247
+ r=values + [values[0]], # Close the loop
248
+ theta=categories + [categories[0]],
249
+ fill='toself',
250
+ fillcolor=f'rgba(46, 94, 170, 0.3)',
251
+ line=dict(color=COLORS['primary'], width=2),
252
+ name='Current State',
253
+ hovertemplate='<b>%{theta}</b><br>Intensity: %{r:.2f}<extra></extra>'
254
  ))
255
+
256
  fig.update_layout(
257
+ polar=dict(
258
+ radialaxis=dict(
259
+ visible=True,
260
+ range=[0, 1],
261
+ gridcolor=COLORS['border'],
262
+ tickformat='.1f'
263
+ ),
264
+ angularaxis=dict(
265
+ gridcolor=COLORS['border']
266
+ ),
267
+ bgcolor='white'
268
+ ),
269
+ title=dict(
270
+ text="<b>Dalio's Five Forces</b>",
271
+ font=dict(size=18, color='#2C3E50'),
272
+ x=0.5,
273
+ xanchor='center'
274
+ ),
275
+ height=350,
276
+ paper_bgcolor='white',
277
+ margin=dict(t=80, b=40, l=40, r=40),
278
+ font=dict(family="Arial, sans-serif"),
279
+ showlegend=False
280
  )
281
+
282
  return fig
283
 
284
 
285
+ def create_summary_card(latest):
286
+ """HTML summary card with key metrics"""
287
+ regime = str(latest['regime'])
288
+ regime_color = REGIME_COLORS.get(regime, COLORS['gray'])
289
+
290
+ html = f"""
291
+ <div style="
292
+ background: linear-gradient(135deg, {regime_color}15 0%, {regime_color}05 100%);
293
+ border-left: 5px solid {regime_color};
294
+ padding: 25px;
295
+ border-radius: 10px;
296
+ box-shadow: 0 2px 8px rgba(0,0,0,0.08);
297
+ font-family: Arial, sans-serif;
298
+ ">
299
+ <h2 style="margin: 0 0 20px 0; color: #2C3E50; font-size: 24px;">
300
+ 📊 Current Market Regime
301
+ </h2>
302
+ <div style="
303
+ background: white;
304
+ padding: 15px;
305
+ border-radius: 8px;
306
+ margin-bottom: 15px;
307
+ text-align: center;
308
+ ">
309
+ <div style="font-size: 14px; color: #6C757D; margin-bottom: 5px;">Status</div>
310
+ <div style="
311
+ font-size: 28px;
312
+ font-weight: bold;
313
+ color: {regime_color};
314
+ text-transform: uppercase;
315
+ letter-spacing: 1px;
316
+ ">{regime.replace('_', ' ')}</div>
317
+ </div>
318
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
319
+ <div style="background: white; padding: 15px; border-radius: 8px;">
320
+ <div style="font-size: 12px; color: #6C757D; margin-bottom: 5px;">Credit Collapse Risk</div>
321
+ <div style="font-size: 22px; font-weight: bold; color: {COLORS['danger']};">
322
+ {latest['prob_credit_collapse']:.1%}
323
+ </div>
324
+ </div>
325
+ <div style="background: white; padding: 15px; border-radius: 8px;">
326
+ <div style="font-size: 12px; color: #6C757D; margin-bottom: 5px;">Tech Boom Probability</div>
327
+ <div style="font-size: 22px; font-weight: bold; color: {COLORS['success']};">
328
+ {latest['prob_tech_boom']:.1%}
329
+ </div>
330
+ </div>
331
+ <div style="background: white; padding: 15px; border-radius: 8px;">
332
+ <div style="font-size: 12px; color: #6C757D; margin-bottom: 5px;">Stagflation Risk</div>
333
+ <div style="font-size: 22px; font-weight: bold; color: {COLORS['warning']};">
334
+ {latest['prob_stagflation']:.1%}
335
+ </div>
336
+ </div>
337
+ <div style="background: white; padding: 15px; border-radius: 8px;">
338
+ <div style="font-size: 12px; color: #6C757D; margin-bottom: 5px;">Geopolitical Stress</div>
339
+ <div style="font-size: 22px; font-weight: bold; color: {COLORS['purple']};">
340
+ {latest['geopolitical_risk_norm']:.2f}
341
+ </div>
342
+ </div>
343
+ </div>
344
+ <div style="
345
+ margin-top: 15px;
346
+ padding: 12px;
347
+ background: white;
348
+ border-radius: 8px;
349
+ font-size: 12px;
350
+ color: #6C757D;
351
+ text-align: center;
352
+ ">
353
+ Last Updated: {latest.name.strftime('%Y-%m-%d %H:%M') if hasattr(latest.name, 'strftime') else 'N/A'}
354
+ </div>
355
+ </div>
356
+ """
357
+ return html
358
+
359
+
360
+ # ==================== MAIN PIPELINE ====================
361
+
362
  def run_pipeline(days_back: int = 1825):
363
+ """Execute the full analysis pipeline"""
364
  try:
365
  today = pd.Timestamp.today()
366
  start_date = (today - pd.Timedelta(days=days_back)).strftime('%Y-%m-%d')
367
  end_date = today.strftime('%Y-%m-%d')
368
 
369
+ # Fetch data
370
  df = get_data(start_date, end_date)
371
  if len(df) < 300:
372
+ error_html = """
373
+ <div style="padding: 30px; background: #FFEBEE; border-radius: 10px; border-left: 5px solid #E63946;">
374
+ <h3 style="color: #E63946; margin: 0 0 10px 0;">⚠️ Insufficient Data</h3>
375
+ <p style="margin: 0; color: #2C3E50;">
376
+ Not enough data points for analysis. Try increasing the lookback window to at least 1000 days.
377
+ </p>
378
+ </div>
379
+ """
380
+ return error_html, None, None, None, None, None
381
 
382
+ # Build features
383
  engine = IntegratedTheoryFeatures(df)
384
  features = engine.build_all_features()
385
  latest = features.dropna(subset=['regime']).iloc[-1]
386
 
387
+ # Create visualizations
388
+ summary_html = create_summary_card(latest)
389
+ composite_fig = create_composite_bar(latest)
390
+ prob_fig = create_probabilities_gauge(latest)
391
+ timeline_fig = create_regime_timeline(features)
392
+ radar_fig = create_forces_radar(latest)
393
 
394
+ # Create detailed JSON
395
  json_output = {
396
+ "🎯 Current Regime": str(latest["regime"]),
397
+ "📊 Core Theories": {
398
+ "Dalio Composite": f"{latest['dalio_composite_norm']:.3f}",
399
+ "Stevenson Inequality": f"{latest['stevenson_inequality_norm']:.3f}",
400
+ "Thiel Monopoly": f"{latest['thiel_monopoly_norm']:.3f}",
401
+ "Gundlach Reckoning": f"{latest['gundlach_reckoning_norm']:.3f}",
402
+ },
403
+ "🎲 Scenario Probabilities": {
404
+ "Credit Collapse": f"{latest['prob_credit_collapse']:.1%}",
405
+ "Stagflation": f"{latest['prob_stagflation']:.1%}",
406
+ "Tech Boom": f"{latest['prob_tech_boom']:.1%}",
407
+ },
408
+ "🌍 Geopolitical": {
409
+ "Overall Risk": f"{latest['geopolitical_risk_norm']:.3f}",
410
+ },
411
+ "⚠️ Regime Flags": {
412
+ "Debt Unsustainable": bool(latest['debt_unsustainable']),
413
+ "Inequality Trap": bool(latest['inequality_trap']),
414
+ "Tech Monopoly": bool(latest['tech_monopoly']),
415
+ "Geopolitical Shock": bool(latest['geopolitical_shock']),
416
+ }
417
  }
418
 
419
+ return summary_html, json_output, composite_fig, prob_fig, timeline_fig, radar_fig
 
 
 
 
 
420
 
421
  except Exception as e:
422
+ error_html = f"""
423
+ <div style="padding: 30px; background: #FFEBEE; border-radius: 10px; border-left: 5px solid #E63946;">
424
+ <h3 style="color: #E63946; margin: 0 0 10px 0;">❌ Error</h3>
425
+ <p style="margin: 0; color: #2C3E50; font-family: monospace;">
426
+ {str(e)}
427
+ </p>
428
+ </div>
429
+ """
430
+ return error_html, {"Error": str(e)}, None, None, None, None
431
+
432
+
433
+ # ==================== GRADIO UI ====================
434
+
435
+ custom_css = """
436
+ .gradio-container {
437
+ font-family: 'Arial', sans-serif !important;
438
+ max-width: 1400px !important;
439
+ margin: auto !important;
440
+ }
441
+
442
+ .header-text {
443
+ text-align: center;
444
+ padding: 20px;
445
+ background: linear-gradient(135deg, #2E5EAA 0%, #4A90E2 100%);
446
+ color: white;
447
+ border-radius: 10px;
448
+ margin-bottom: 20px;
449
+ }
450
+
451
+ .header-text h1 {
452
+ margin: 0;
453
+ font-size: 32px;
454
+ font-weight: bold;
455
+ }
456
+
457
+ .header-text p {
458
+ margin: 10px 0 0 0;
459
+ font-size: 16px;
460
+ opacity: 0.9;
461
+ }
462
 
463
+ .btn-primary {
464
+ background: linear-gradient(135deg, #2E5EAA 0%, #4A90E2 100%) !important;
465
+ border: none !important;
466
+ font-weight: bold !important;
467
+ }
468
 
469
+ .panel {
470
+ background: white;
471
+ border-radius: 10px;
472
+ padding: 15px;
473
+ box-shadow: 0 2px 8px rgba(0,0,0,0.08);
474
+ }
475
+ """
476
+
477
+ with gr.Blocks(css=custom_css, title="🌍 Integrated Market Theory Dashboard", theme=gr.themes.Soft()) as demo:
478
+
479
+ gr.HTML("""
480
+ <div class="header-text">
481
+ <h1>🌍 Integrated Market Theory Dashboard</h1>
482
+ <p>Real-time macro regime detection using Dalio, Stevenson, Thiel & Gundlach frameworks</p>
483
+ </div>
484
+ """)
485
 
486
  with gr.Row():
487
+ with gr.Column(scale=3):
488
+ days = gr.Slider(
489
+ 365, 2500,
490
+ value=1825,
491
+ step=90,
492
+ label="📅 Lookback Window (days)",
493
+ info="Minimum 1000 days recommended for stable results"
494
+ )
495
+ with gr.Column(scale=1):
496
+ run_btn = gr.Button(
497
+ "🔄 Update Analysis",
498
+ variant="primary",
499
+ size="lg"
500
+ )
501
+
502
+ gr.Markdown("---")
503
 
504
  with gr.Row():
505
+ with gr.Column(scale=1):
506
+ summary_html = gr.HTML(label="Summary")
507
+ with gr.Column(scale=1):
508
+ json_output = gr.JSON(label="📋 Detailed Metrics", show_label=True)
509
+
510
+ gr.Markdown("---")
511
+ gr.Markdown("## 📊 Theory Scores & Probabilities")
512
 
513
  with gr.Row():
514
+ composite_plot = gr.Plot(label="Core Theory Scores")
515
  prob_plot = gr.Plot(label="Scenario Probabilities")
516
+
517
+ gr.Markdown("---")
518
+ gr.Markdown("## 📈 Historical Analysis")
519
+
520
+ with gr.Row():
521
  timeline_plot = gr.Plot(label="Regime Timeline")
522
+ radar_plot = gr.Plot(label="Dalio's Five Forces")
523
+
524
+ gr.Markdown("---")
525
+ gr.Markdown("""
526
+ <div style="text-align: center; padding: 20px; color: #6C757D; font-size: 14px;">
527
+ <p><b>Theoretical Framework:</b></p>
528
+ <p>
529
+ <b>Ray Dalio</b> - Five Forces (Debt, Internal/External Conflict, Technology, Nature) |
530
+ <b>Betsey Stevenson</b> - Economic Inequality Dynamics |
531
+ <b>Peter Thiel</b> - Zero to One Monopoly Theory |
532
+ <b>Jeffrey Gundlach</b> - Debt Reckoning Framework
533
+ </p>
534
+ <p style="margin-top: 10px;">
535
+ Data Sources: Yahoo Finance, FRED Economic Data |
536
+ All scores normalized to [-1, 1] range
537
+ </p>
538
+ </div>
539
+ """)
540
+
541
+ # Event handler
542
  run_btn.click(
543
  run_pipeline,
544
+ inputs=[days],
545
+ outputs=[summary_html, json_output, composite_plot, prob_plot, timeline_plot, radar_plot]
546
  )
547
+
548
+ # Auto-run on load
549
+ demo.load(
550
+ run_pipeline,
551
+ inputs=[days],
552
+ outputs=[summary_html, json_output, composite_plot, prob_plot, timeline_plot, radar_plot]
553
+ )
554
+
555
 
556
+ if __name__ == "__main__":
557
+ demo.launch(
558
+ share=False,
559
+ server_name="0.0.0.0",
560
+ server_port=7860,
561
+ show_error=True
562
+ )