GLAkavya commited on
Commit
754657d
Β·
verified Β·
1 Parent(s): c8dcc9d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +104 -168
app.py CHANGED
@@ -1,4 +1,7 @@
1
- #ui/ux
 
 
 
2
 
3
  import gradio as gr
4
  import pandas as pd
@@ -6,12 +9,12 @@ import numpy as np
6
  import plotly.graph_objects as go
7
  import plotly.express as px
8
  import yfinance as yf
9
- from datetime import datetime, timedelta
10
  import pickle
11
  import warnings
12
  warnings.filterwarnings('ignore')
13
 
14
- # Custom unpickler to handle numpy version issues
15
  class SafeUnpickler(pickle.Unpickler):
16
  def find_class(self, module, name):
17
  if module == "numpy._core.multiarray" or module == "numpy.core.multiarray":
@@ -20,6 +23,7 @@ class SafeUnpickler(pickle.Unpickler):
20
  return getattr(np, name)
21
  return super().find_class(module, name)
22
 
 
23
  def load_model():
24
  try:
25
  with open('portfolio_risk_model.pkl', 'rb') as f:
@@ -28,28 +32,21 @@ def load_model():
28
  return model
29
  except Exception as e:
30
  print(f"❌ Model loading error: {str(e)}")
31
- # Create model from scratch using yfinance data
32
  return create_model_from_scratch()
33
 
 
34
  def create_model_from_scratch():
35
- """Create model directly from yfinance data"""
36
  print("πŸ”„ Creating model from scratch...")
37
-
38
  stocks = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA']
39
-
40
  try:
41
- # Fetch fresh data
42
  data = yf.download(stocks, period='5y')['Adj Close']
43
  returns = data.pct_change().dropna()
44
-
45
- # Calculate model parameters
46
  mean_returns = returns.mean().tolist()
47
  cov_matrix = returns.cov().values.tolist()
48
  volatilities = (returns.std() * np.sqrt(252)).tolist()
49
-
50
- # Calculate portfolio metrics
51
- portfolio_returns = returns.dot([0.2, 0.2, 0.2, 0.2, 0.2])
52
-
53
  model = {
54
  'mean_returns': mean_returns,
55
  'covariance_matrix': cov_matrix,
@@ -61,15 +58,6 @@ def create_model_from_scratch():
61
  'annual_volatility': float(portfolio_returns.std() * np.sqrt(252)),
62
  'sharpe_ratio': float((portfolio_returns.mean() * 252) / (portfolio_returns.std() * np.sqrt(252)))
63
  },
64
- 'loss_probabilities': {
65
- 'probability_10_percent_loss': 0.15, # Placeholder
66
- 'probability_20_percent_loss': 0.08, # Placeholder
67
- 'probability_30_percent_loss': 0.03, # Placeholder
68
- 'expected_portfolio_value': 110.0, # Placeholder
69
- 'median_portfolio_value': 108.5, # Placeholder
70
- 'worst_case_5th_percentile': 85.0, # Placeholder
71
- 'best_case_95th_percentile': 135.0 # Placeholder
72
- },
73
  'tickers': stocks,
74
  'training_date': datetime.now().strftime("%Y-%m-%d"),
75
  'data_period': '5 years'
@@ -80,241 +68,187 @@ def create_model_from_scratch():
80
  print(f"❌ Error creating model: {str(e)}")
81
  return None
82
 
 
 
 
83
  class GradioRiskApp:
84
  def __init__(self):
85
  self.model = load_model()
86
  self.available_stocks = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA']
87
  print(f"🎯 Available stocks: {self.available_stocks}")
88
-
89
- def run_monte_carlo(self, selected_stocks, days=252, simulations=5000):
90
- """Run Monte Carlo simulation"""
 
91
  try:
92
- if self.model is None:
93
- print("❌ No model available")
94
- return None
95
-
96
- if set(selected_stocks) != set(self.available_stocks):
97
- print("❌ Stocks don't match trained stocks")
98
  return None
99
-
100
- print("🎯 Running Monte Carlo simulation...")
101
-
102
- # Extract parameters from model
103
  mean_returns = np.array(self.model['mean_returns'])
104
  cov_matrix = np.array(self.model['covariance_matrix'])
105
-
106
- # Ensure covariance matrix is positive definite
 
 
 
107
  cov_matrix = self.make_positive_definite(cov_matrix)
108
-
109
- # Generate simulation
110
  np.random.seed(42)
111
  L = np.linalg.cholesky(cov_matrix)
112
-
 
 
 
113
  simulation_results = []
114
- weights = np.array([0.2, 0.2, 0.2, 0.2, 0.2])
115
-
116
  for i in range(simulations):
117
- # Generate correlated returns
118
- random_numbers = np.random.normal(0, 1, size=(days, 5))
119
  correlated_returns = random_numbers @ L.T + mean_returns
120
-
121
- # Calculate portfolio path
122
  portfolio_value = 100.0
123
  portfolio_path = [portfolio_value]
124
-
125
  for day in range(days):
126
  daily_return = np.dot(correlated_returns[day], weights)
127
- portfolio_value = portfolio_value * (1 + daily_return)
128
  portfolio_path.append(portfolio_value)
129
-
130
  simulation_results.append(portfolio_path)
131
-
132
- print(f"βœ… Simulation completed with {len(simulation_results)} paths")
133
  return np.array(simulation_results)
134
-
135
  except Exception as e:
136
  print(f"❌ Simulation error: {str(e)}")
137
  return None
138
-
 
139
  def make_positive_definite(self, matrix):
140
- """Ensure covariance matrix is positive definite"""
141
  min_eig = np.min(np.real(np.linalg.eigvals(matrix)))
142
  if min_eig < 0:
143
  matrix -= 10 * min_eig * np.eye(*matrix.shape)
144
  return matrix
145
-
 
146
  def calculate_metrics(self, simulation_results):
147
- """Calculate risk metrics"""
148
  if simulation_results is None:
149
  return None
150
-
151
  final_values = simulation_results[:, -1]
152
-
153
  metrics = {
154
  'expected_value': float(np.mean(final_values)),
155
  'prob_10_loss': float(np.mean(final_values < 90)),
156
- 'prob_20_loss': float(np.mean(final_values < 80)),
157
- 'prob_30_loss': float(np.mean(final_values < 70)),
158
  'var_95': float(np.percentile(final_values, 5)),
159
  'best_case': float(np.percentile(final_values, 95)),
160
  'worst_case': float(np.percentile(final_values, 5))
161
  }
162
-
163
  return metrics
164
-
 
165
  def create_simulation_plot(self, simulation_results):
166
- """Create simulation plot"""
167
  if simulation_results is None:
168
  return None
169
-
170
  fig = go.Figure()
171
-
172
- # Sample paths
173
  for i in range(min(50, len(simulation_results))):
174
- fig.add_trace(go.Scatter(
175
- y=simulation_results[i],
176
- mode='lines',
177
- line=dict(width=1, color='lightblue'),
178
- opacity=0.1,
179
- showlegend=False
180
- ))
181
-
182
- # Mean path
183
  mean_path = np.mean(simulation_results, axis=0)
184
- fig.add_trace(go.Scatter(
185
- y=mean_path,
186
- mode='lines',
187
- line=dict(width=3, color='red'),
188
- name='Average Path'
189
- ))
190
-
191
- fig.update_layout(
192
- title="πŸ“ˆ Monte Carlo Simulation Paths",
193
- xaxis_title="Trading Days",
194
- yaxis_title="Portfolio Value ($)",
195
- height=400
196
- )
197
-
198
  return fig
199
-
200
  def create_distribution_plot(self, simulation_results, metrics):
201
- """Create distribution plot"""
202
  if simulation_results is None:
203
  return None
204
-
205
  final_values = simulation_results[:, -1]
206
-
207
- fig = px.histogram(
208
- x=final_values,
209
- nbins=50,
210
- title="πŸ“Š Portfolio Value Distribution",
211
- color_discrete_sequence=['#667eea']
212
- )
213
-
214
  if metrics:
215
- fig.add_vline(x=metrics['expected_value'], line_dash="dash",
216
- line_color="red", annotation_text=f"Mean: ${metrics['expected_value']:.2f}")
217
- fig.add_vline(x=metrics['var_95'], line_dash="dash",
218
- line_color="orange", annotation_text=f"5% VaR: ${metrics['var_95']:.2f}")
219
-
220
  fig.update_layout(height=400, showlegend=False)
221
  return fig
222
-
223
  def create_risk_gauge(self, metrics):
224
- """Create risk gauge"""
225
  if metrics is None:
226
  return None
227
-
228
  risk_prob = metrics['prob_10_loss'] * 100
229
-
230
  fig = go.Figure(go.Indicator(
231
- mode="gauge+number",
232
- value=risk_prob,
233
  domain={'x': [0, 1], 'y': [0, 1]},
234
  title={'text': "Probability of >10% Loss"},
235
- gauge={
236
- 'axis': {'range': [0, 50]},
237
- 'bar': {'color': "darkblue"},
238
- 'steps': [
239
- {'range': [0, 15], 'color': "lightgreen"},
240
- {'range': [15, 30], 'color': "yellow"},
241
- {'range': [30, 50], 'color': "red"}
242
- ]
243
- }
244
- ))
245
  fig.update_layout(height=300)
246
  return fig
247
-
 
248
  def analyze_portfolio(self, selected_stocks, simulation_days, num_simulations):
249
- """Main analysis function"""
250
  print(f"πŸ” Analyzing: {selected_stocks}")
251
-
252
  if not selected_stocks:
253
- return self.create_error_html("Please select at least one stock"), None, None, None
254
-
255
- if set(selected_stocks) != set(self.available_stocks):
256
- return self.create_error_html(f"Please select exactly these 5 stocks: {', '.join(self.available_stocks)}"), None, None, None
257
-
258
- try:
259
- simulations = self.run_monte_carlo(selected_stocks, simulation_days, int(num_simulations))
260
-
261
- if simulations is None:
262
- return self.create_error_html("Simulation failed"), None, None, None
263
-
264
- metrics = self.calculate_metrics(simulations)
265
-
266
- if metrics is None:
267
- return self.create_error_html("Metrics calculation failed"), None, None, None
268
-
269
- summary = self.create_summary_html(metrics, selected_stocks, simulation_days, num_simulations)
270
- sim_plot = self.create_simulation_plot(simulations)
271
- dist_plot = self.create_distribution_plot(simulations, metrics)
272
- gauge = self.create_risk_gauge(metrics)
273
-
274
- return summary, sim_plot, dist_plot, gauge
275
-
276
- except Exception as e:
277
- return self.create_error_html(f"Analysis error: {str(e)}"), None, None, None
278
-
279
  def create_error_html(self, message):
280
- """Create error message HTML"""
281
  return f"""
282
  <div style='text-align: center; padding: 40px; background: #f8d7da; border-radius: 10px;'>
283
  <h3 style='color: #721c24;'>❌ Error</h3>
284
  <p>{message}</p>
285
  </div>
286
  """
287
-
288
  def create_summary_html(self, metrics, stocks, days, sims):
289
- """Create summary HTML"""
290
  risk_level = "LOW" if metrics['prob_10_loss'] < 0.1 else "MEDIUM" if metrics['prob_10_loss'] < 0.2 else "HIGH"
291
  risk_color = "#28a745" if risk_level == "LOW" else "#ffc107" if risk_level == "MEDIUM" else "#dc3545"
292
-
293
  return f"""
294
- <div style="padding: 25px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px; color: white;">
 
295
  <h2 style="text-align: center;">πŸ“Š Risk Analysis Report</h2>
296
-
297
  <div style="background: rgba(255,255,255,0.1); padding: 20px; border-radius: 10px; margin: 15px 0;">
298
  <h3>Portfolio: {', '.join(stocks)}</h3>
299
- <p>Weights: 20% each | Period: {days} days | Simulations: {sims}</p>
300
  </div>
301
-
302
  <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
303
  <div style="background: rgba(255,255,255,0.2); padding: 15px; border-radius: 8px;">
304
  <h4>Expected Value</h4>
305
  <p style="font-size: 24px; font-weight: bold;">${metrics['expected_value']:.2f}</p>
306
  </div>
307
-
308
  <div style="background: rgba(255,255,255,0.2); padding: 15px; border-radius: 8px;">
309
  <h4>Risk Level</h4>
310
  <p style="font-size: 24px; font-weight: bold; color: {risk_color};">{risk_level}</p>
311
  </div>
312
-
313
  <div style="background: rgba(255,255,255,0.2); padding: 15px; border-radius: 8px;">
314
  <h4>10% Loss Probability</h4>
315
  <p style="font-size: 24px; font-weight: bold;">{metrics['prob_10_loss']*100:.1f}%</p>
316
  </div>
317
-
318
  <div style="background: rgba(255,255,255,0.2); padding: 15px; border-radius: 8px;">
319
  <h4>Worst Case (5%)</h4>
320
  <p style="font-size: 24px; font-weight: bold;">${metrics['var_95']:.2f}</p>
@@ -323,31 +257,33 @@ class GradioRiskApp:
323
  </div>
324
  """
325
 
326
- # Create and launch app
 
 
327
  app = GradioRiskApp()
328
 
329
  with gr.Blocks(theme=gr.themes.Soft(), title="QuantRisk Pro") as demo:
330
  gr.Markdown("# πŸ“Š QuantRisk Pro - Portfolio Risk Analyzer")
331
-
332
  with gr.Row():
333
  with gr.Column(scale=1):
334
  gr.Markdown("### πŸ”§ Configuration")
335
  stocks = gr.CheckboxGroup(
336
  choices=app.available_stocks,
337
  value=app.available_stocks,
338
- label="Select Stocks (All 5 Required)"
339
  )
340
  days = gr.Slider(30, 500, 252, label="Time Horizon (Days)")
341
  sims = gr.Dropdown([1000, 2500, 5000, 10000], value=5000, label="Simulations")
342
  btn = gr.Button("πŸš€ Analyze", variant="primary")
343
-
344
  with gr.Column(scale=2):
345
  summary = gr.HTML()
346
  with gr.Row():
347
  gauge = gr.Plot()
348
  sim_plot = gr.Plot()
349
  dist_plot = gr.Plot()
350
-
351
  btn.click(
352
  fn=app.analyze_portfolio,
353
  inputs=[stocks, days, sims],
@@ -355,4 +291,4 @@ with gr.Blocks(theme=gr.themes.Soft(), title="QuantRisk Pro") as demo:
355
  )
356
 
357
  if __name__ == "__main__":
358
- demo.launch()
 
1
+ # =====================================================
2
+ # πŸ“Š QuantRisk Pro - Portfolio Risk Analyzer (Gradio)
3
+ # Works for any 1–5 stock combination
4
+ # =====================================================
5
 
6
  import gradio as gr
7
  import pandas as pd
 
9
  import plotly.graph_objects as go
10
  import plotly.express as px
11
  import yfinance as yf
12
+ from datetime import datetime
13
  import pickle
14
  import warnings
15
  warnings.filterwarnings('ignore')
16
 
17
+ # --- Safe unpickler (for numpy version mismatch)
18
  class SafeUnpickler(pickle.Unpickler):
19
  def find_class(self, module, name):
20
  if module == "numpy._core.multiarray" or module == "numpy.core.multiarray":
 
23
  return getattr(np, name)
24
  return super().find_class(module, name)
25
 
26
+ # --- Model loader
27
  def load_model():
28
  try:
29
  with open('portfolio_risk_model.pkl', 'rb') as f:
 
32
  return model
33
  except Exception as e:
34
  print(f"❌ Model loading error: {str(e)}")
 
35
  return create_model_from_scratch()
36
 
37
+ # --- Create model from scratch if missing
38
  def create_model_from_scratch():
 
39
  print("πŸ”„ Creating model from scratch...")
 
40
  stocks = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA']
 
41
  try:
 
42
  data = yf.download(stocks, period='5y')['Adj Close']
43
  returns = data.pct_change().dropna()
44
+
 
45
  mean_returns = returns.mean().tolist()
46
  cov_matrix = returns.cov().values.tolist()
47
  volatilities = (returns.std() * np.sqrt(252)).tolist()
48
+ portfolio_returns = returns.dot(np.ones(len(stocks)) / len(stocks))
49
+
 
 
50
  model = {
51
  'mean_returns': mean_returns,
52
  'covariance_matrix': cov_matrix,
 
58
  'annual_volatility': float(portfolio_returns.std() * np.sqrt(252)),
59
  'sharpe_ratio': float((portfolio_returns.mean() * 252) / (portfolio_returns.std() * np.sqrt(252)))
60
  },
 
 
 
 
 
 
 
 
 
61
  'tickers': stocks,
62
  'training_date': datetime.now().strftime("%Y-%m-%d"),
63
  'data_period': '5 years'
 
68
  print(f"❌ Error creating model: {str(e)}")
69
  return None
70
 
71
+ # =====================================================
72
+ # 🎯 Gradio App Class
73
+ # =====================================================
74
  class GradioRiskApp:
75
  def __init__(self):
76
  self.model = load_model()
77
  self.available_stocks = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA']
78
  print(f"🎯 Available stocks: {self.available_stocks}")
79
+
80
+ # --- Simulation Runner
81
+ def run_monte_carlo(self, selected_stocks, days=252, simulations=5000, weights=None):
82
+ """Run Monte Carlo simulation for 1–5 stocks"""
83
  try:
84
+ if self.model is None or len(selected_stocks) == 0:
85
+ print("❌ No model or stocks selected")
 
 
 
 
86
  return None
87
+
88
+ print(f"🎯 Running Monte Carlo for {len(selected_stocks)} stocks: {selected_stocks}")
89
+
 
90
  mean_returns = np.array(self.model['mean_returns'])
91
  cov_matrix = np.array(self.model['covariance_matrix'])
92
+
93
+ stock_indices = [self.available_stocks.index(s) for s in selected_stocks]
94
+ mean_returns = mean_returns[stock_indices]
95
+ cov_matrix = cov_matrix[np.ix_(stock_indices, stock_indices)]
96
+
97
  cov_matrix = self.make_positive_definite(cov_matrix)
 
 
98
  np.random.seed(42)
99
  L = np.linalg.cholesky(cov_matrix)
100
+
101
+ if weights is None:
102
+ weights = np.ones(len(selected_stocks)) / len(selected_stocks)
103
+
104
  simulation_results = []
 
 
105
  for i in range(simulations):
106
+ random_numbers = np.random.normal(0, 1, size=(days, len(selected_stocks)))
 
107
  correlated_returns = random_numbers @ L.T + mean_returns
108
+
 
109
  portfolio_value = 100.0
110
  portfolio_path = [portfolio_value]
111
+
112
  for day in range(days):
113
  daily_return = np.dot(correlated_returns[day], weights)
114
+ portfolio_value *= (1 + daily_return)
115
  portfolio_path.append(portfolio_value)
116
+
117
  simulation_results.append(portfolio_path)
118
+
119
+ print(f"βœ… Simulation completed: {len(simulation_results)} paths")
120
  return np.array(simulation_results)
121
+
122
  except Exception as e:
123
  print(f"❌ Simulation error: {str(e)}")
124
  return None
125
+
126
+ # --- Ensure covariance matrix stability
127
  def make_positive_definite(self, matrix):
 
128
  min_eig = np.min(np.real(np.linalg.eigvals(matrix)))
129
  if min_eig < 0:
130
  matrix -= 10 * min_eig * np.eye(*matrix.shape)
131
  return matrix
132
+
133
+ # --- Calculate metrics
134
  def calculate_metrics(self, simulation_results):
 
135
  if simulation_results is None:
136
  return None
 
137
  final_values = simulation_results[:, -1]
 
138
  metrics = {
139
  'expected_value': float(np.mean(final_values)),
140
  'prob_10_loss': float(np.mean(final_values < 90)),
 
 
141
  'var_95': float(np.percentile(final_values, 5)),
142
  'best_case': float(np.percentile(final_values, 95)),
143
  'worst_case': float(np.percentile(final_values, 5))
144
  }
 
145
  return metrics
146
+
147
+ # --- Plots
148
  def create_simulation_plot(self, simulation_results):
 
149
  if simulation_results is None:
150
  return None
 
151
  fig = go.Figure()
 
 
152
  for i in range(min(50, len(simulation_results))):
153
+ fig.add_trace(go.Scatter(y=simulation_results[i],
154
+ mode='lines', line=dict(width=1, color='lightblue'),
155
+ opacity=0.1, showlegend=False))
 
 
 
 
 
 
156
  mean_path = np.mean(simulation_results, axis=0)
157
+ fig.add_trace(go.Scatter(y=mean_path, mode='lines',
158
+ line=dict(width=3, color='red'), name='Average Path'))
159
+ fig.update_layout(title="πŸ“ˆ Monte Carlo Simulation Paths",
160
+ xaxis_title="Trading Days",
161
+ yaxis_title="Portfolio Value ($)", height=400)
 
 
 
 
 
 
 
 
 
162
  return fig
163
+
164
  def create_distribution_plot(self, simulation_results, metrics):
 
165
  if simulation_results is None:
166
  return None
 
167
  final_values = simulation_results[:, -1]
168
+ fig = px.histogram(x=final_values, nbins=50,
169
+ title="πŸ“Š Portfolio Value Distribution",
170
+ color_discrete_sequence=['#667eea'])
 
 
 
 
 
171
  if metrics:
172
+ fig.add_vline(x=metrics['expected_value'], line_dash="dash",
173
+ line_color="red", annotation_text=f"Mean: ${metrics['expected_value']:.2f}")
174
+ fig.add_vline(x=metrics['var_95'], line_dash="dash",
175
+ line_color="orange", annotation_text=f"5% VaR: ${metrics['var_95']:.2f}")
 
176
  fig.update_layout(height=400, showlegend=False)
177
  return fig
178
+
179
  def create_risk_gauge(self, metrics):
 
180
  if metrics is None:
181
  return None
 
182
  risk_prob = metrics['prob_10_loss'] * 100
 
183
  fig = go.Figure(go.Indicator(
184
+ mode="gauge+number", value=risk_prob,
 
185
  domain={'x': [0, 1], 'y': [0, 1]},
186
  title={'text': "Probability of >10% Loss"},
187
+ gauge={'axis': {'range': [0, 50]},
188
+ 'bar': {'color': "darkblue"},
189
+ 'steps': [{'range': [0, 15], 'color': "lightgreen"},
190
+ {'range': [15, 30], 'color': "yellow"},
191
+ {'range': [30, 50], 'color': "red"}]}))
 
 
 
 
 
192
  fig.update_layout(height=300)
193
  return fig
194
+
195
+ # --- Main Analysis Function (Fixed)
196
  def analyze_portfolio(self, selected_stocks, simulation_days, num_simulations):
 
197
  print(f"πŸ” Analyzing: {selected_stocks}")
198
+
199
  if not selected_stocks:
200
+ return self.create_error_html("Please select at least one stock."), None, None, None
201
+
202
+ weights = np.ones(len(selected_stocks)) / len(selected_stocks)
203
+ simulations = self.run_monte_carlo(selected_stocks, simulation_days, int(num_simulations), weights=weights)
204
+ if simulations is None:
205
+ return self.create_error_html("Simulation failed"), None, None, None
206
+
207
+ metrics = self.calculate_metrics(simulations)
208
+ if metrics is None:
209
+ return self.create_error_html("Metrics calculation failed"), None, None, None
210
+
211
+ summary = self.create_summary_html(metrics, selected_stocks, simulation_days, num_simulations)
212
+ sim_plot = self.create_simulation_plot(simulations)
213
+ dist_plot = self.create_distribution_plot(simulations, metrics)
214
+ gauge = self.create_risk_gauge(metrics)
215
+
216
+ return summary, sim_plot, dist_plot, gauge
217
+
218
+ # --- HTML renderers
 
 
 
 
 
 
 
219
  def create_error_html(self, message):
 
220
  return f"""
221
  <div style='text-align: center; padding: 40px; background: #f8d7da; border-radius: 10px;'>
222
  <h3 style='color: #721c24;'>❌ Error</h3>
223
  <p>{message}</p>
224
  </div>
225
  """
226
+
227
  def create_summary_html(self, metrics, stocks, days, sims):
 
228
  risk_level = "LOW" if metrics['prob_10_loss'] < 0.1 else "MEDIUM" if metrics['prob_10_loss'] < 0.2 else "HIGH"
229
  risk_color = "#28a745" if risk_level == "LOW" else "#ffc107" if risk_level == "MEDIUM" else "#dc3545"
230
+
231
  return f"""
232
+ <div style="padding: 25px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
233
+ border-radius: 15px; color: white;">
234
  <h2 style="text-align: center;">πŸ“Š Risk Analysis Report</h2>
 
235
  <div style="background: rgba(255,255,255,0.1); padding: 20px; border-radius: 10px; margin: 15px 0;">
236
  <h3>Portfolio: {', '.join(stocks)}</h3>
237
+ <p>Equal weights | Period: {days} days | Simulations: {sims}</p>
238
  </div>
 
239
  <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
240
  <div style="background: rgba(255,255,255,0.2); padding: 15px; border-radius: 8px;">
241
  <h4>Expected Value</h4>
242
  <p style="font-size: 24px; font-weight: bold;">${metrics['expected_value']:.2f}</p>
243
  </div>
 
244
  <div style="background: rgba(255,255,255,0.2); padding: 15px; border-radius: 8px;">
245
  <h4>Risk Level</h4>
246
  <p style="font-size: 24px; font-weight: bold; color: {risk_color};">{risk_level}</p>
247
  </div>
 
248
  <div style="background: rgba(255,255,255,0.2); padding: 15px; border-radius: 8px;">
249
  <h4>10% Loss Probability</h4>
250
  <p style="font-size: 24px; font-weight: bold;">{metrics['prob_10_loss']*100:.1f}%</p>
251
  </div>
 
252
  <div style="background: rgba(255,255,255,0.2); padding: 15px; border-radius: 8px;">
253
  <h4>Worst Case (5%)</h4>
254
  <p style="font-size: 24px; font-weight: bold;">${metrics['var_95']:.2f}</p>
 
257
  </div>
258
  """
259
 
260
+ # =====================================================
261
+ # πŸš€ Gradio UI
262
+ # =====================================================
263
  app = GradioRiskApp()
264
 
265
  with gr.Blocks(theme=gr.themes.Soft(), title="QuantRisk Pro") as demo:
266
  gr.Markdown("# πŸ“Š QuantRisk Pro - Portfolio Risk Analyzer")
267
+
268
  with gr.Row():
269
  with gr.Column(scale=1):
270
  gr.Markdown("### πŸ”§ Configuration")
271
  stocks = gr.CheckboxGroup(
272
  choices=app.available_stocks,
273
  value=app.available_stocks,
274
+ label="Select Stocks (1–5 Allowed)"
275
  )
276
  days = gr.Slider(30, 500, 252, label="Time Horizon (Days)")
277
  sims = gr.Dropdown([1000, 2500, 5000, 10000], value=5000, label="Simulations")
278
  btn = gr.Button("πŸš€ Analyze", variant="primary")
279
+
280
  with gr.Column(scale=2):
281
  summary = gr.HTML()
282
  with gr.Row():
283
  gauge = gr.Plot()
284
  sim_plot = gr.Plot()
285
  dist_plot = gr.Plot()
286
+
287
  btn.click(
288
  fn=app.analyze_portfolio,
289
  inputs=[stocks, days, sims],
 
291
  )
292
 
293
  if __name__ == "__main__":
294
+ demo.launch()