GLAkavya commited on
Commit
0a90e72
Β·
verified Β·
1 Parent(s): adea40e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +198 -245
app.py CHANGED
@@ -9,113 +9,171 @@ import pickle
9
  import warnings
10
  warnings.filterwarnings('ignore')
11
 
12
- # Load trained model - USING YOUR SPECIFIC STOCKS
 
 
 
 
 
 
 
 
13
  def load_model():
14
  try:
15
  with open('portfolio_risk_model.pkl', 'rb') as f:
16
- model = pickle.load(f)
17
  print("βœ… Model loaded successfully!")
18
- print(f"πŸ“Š Model trained on: {model['tickers']}")
19
  return model
20
  except Exception as e:
21
  print(f"❌ Model loading error: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  return None
23
 
24
  class GradioRiskApp:
25
  def __init__(self):
26
  self.model = load_model()
27
-
28
- # USE EXACTLY THE SAME 5 STOCKS YOU TRAINED WITH
29
  self.available_stocks = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA']
30
-
31
- print(f"🎯 Using trained stocks: {self.available_stocks}")
32
 
33
  def run_monte_carlo(self, selected_stocks, days=252, simulations=5000):
34
- """Run Monte Carlo simulation using the trained model"""
35
  try:
36
  if self.model is None:
37
- return "❌ Model not loaded properly", None, None, None
 
38
 
39
- # Check if selected stocks match trained stocks
40
  if set(selected_stocks) != set(self.available_stocks):
41
- return f"❌ Please use exactly these 5 stocks: {self.available_stocks}", None, None, None
 
42
 
43
- n_stocks = len(selected_stocks)
44
- print(f"🎯 Running MC simulation for {selected_stocks}...")
45
 
46
- # Use the trained model parameters
47
  mean_returns = np.array(self.model['mean_returns'])
48
  cov_matrix = np.array(self.model['covariance_matrix'])
49
 
 
 
 
50
  # Generate simulation
51
  np.random.seed(42)
52
- try:
53
- L = np.linalg.cholesky(cov_matrix)
54
- except:
55
- # Use eigenvalue decomposition if Cholesky fails
56
- eigvals, eigvecs = np.linalg.eigh(cov_matrix)
57
- L = eigvecs @ np.diag(np.sqrt(np.maximum(eigvals, 0)))
58
 
59
  simulation_results = []
60
- equal_weights = np.array([0.2, 0.2, 0.2, 0.2, 0.2]) # Equal weights for 5 stocks
61
 
62
  for i in range(simulations):
63
- random_numbers = np.random.normal(0, 1, size=(days, n_stocks))
64
- correlated_randoms = random_numbers @ L.T
65
- daily_returns = correlated_randoms + mean_returns
66
 
67
- portfolio_value = 100
 
68
  portfolio_path = [portfolio_value]
69
 
70
  for day in range(days):
71
- daily_portfolio_return = np.dot(daily_returns[day], equal_weights)
72
- portfolio_value *= (1 + daily_portfolio_return)
73
  portfolio_path.append(portfolio_value)
74
 
75
  simulation_results.append(portfolio_path)
76
 
77
- print(f"βœ… Simulation completed: {len(simulation_results)} paths")
78
- return simulation_results
79
 
80
  except Exception as e:
81
  print(f"❌ Simulation error: {str(e)}")
82
  return None
83
 
 
 
 
 
 
 
 
84
  def calculate_metrics(self, simulation_results):
85
- """Calculate risk metrics from simulation"""
86
  if simulation_results is None:
87
  return None
88
-
89
- simulation_array = np.array(simulation_results)
90
- final_values = simulation_array[:, -1]
91
 
92
  metrics = {
93
- 'expected_value': np.mean(final_values),
94
- 'prob_10_loss': np.mean(final_values < 90),
95
- 'prob_20_loss': np.mean(final_values < 80),
96
- 'prob_30_loss': np.mean(final_values < 70),
97
- 'var_95': np.percentile(final_values, 5),
98
- 'best_case': np.percentile(final_values, 95),
99
- 'worst_case': np.percentile(final_values, 5),
100
- 'median_value': np.median(final_values)
101
  }
102
 
103
- return metrics, simulation_array
104
 
105
  def create_simulation_plot(self, simulation_results):
106
- """Create Monte Carlo simulation plot"""
107
  if simulation_results is None:
108
  return None
109
-
110
  fig = go.Figure()
111
 
112
- # Show only 30 paths for clarity
113
- for i in range(min(30, len(simulation_results))):
114
  fig.add_trace(go.Scatter(
115
  y=simulation_results[i],
116
  mode='lines',
117
  line=dict(width=1, color='lightblue'),
118
- opacity=0.3,
119
  showlegend=False
120
  ))
121
 
@@ -132,272 +190,167 @@ class GradioRiskApp:
132
  title="πŸ“ˆ Monte Carlo Simulation Paths",
133
  xaxis_title="Trading Days",
134
  yaxis_title="Portfolio Value ($)",
135
- height=400,
136
- showlegend=True
137
  )
138
 
139
  return fig
140
 
141
  def create_distribution_plot(self, simulation_results, metrics):
142
- """Create final value distribution plot"""
143
  if simulation_results is None:
144
  return None
145
-
146
- final_values = np.array(simulation_results)[:, -1]
147
 
148
  fig = px.histogram(
149
  x=final_values,
150
  nbins=50,
151
- title="πŸ“Š Distribution of Final Portfolio Values",
152
- color_discrete_sequence=['#FF6B6B']
153
  )
154
 
155
  if metrics:
156
- fig.add_vline(x=metrics['expected_value'], line_dash="dash", line_color="red",
157
- annotation_text=f"Mean: ${metrics['expected_value']:.2f}")
158
- fig.add_vline(x=metrics['var_95'], line_dash="dash", line_color="orange",
159
- annotation_text=f"Worst 5%: ${metrics['var_95']:.2f}")
160
 
161
  fig.update_layout(height=400, showlegend=False)
162
  return fig
163
 
164
  def create_risk_gauge(self, metrics):
165
- """Create risk probability gauge"""
166
  if metrics is None:
167
  return None
168
-
169
  risk_prob = metrics['prob_10_loss'] * 100
170
 
171
  fig = go.Figure(go.Indicator(
172
- mode = "gauge+number",
173
- value = risk_prob,
174
- domain = {'x': [0, 1], 'y': [0, 1]},
175
- title = {'text': "πŸ”΄ Probability of >10% Loss"},
176
- gauge = {
177
- 'axis': {'range': [None, 50]},
178
- 'bar': {'color': "darkred"},
179
  'steps': [
180
  {'range': [0, 15], 'color': "lightgreen"},
181
  {'range': [15, 30], 'color': "yellow"},
182
- {'range': [30, 50], 'color': "red"}],
183
- 'threshold': {
184
- 'line': {'color': "black", 'width': 4},
185
- 'thickness': 0.75,
186
- 'value': 25}}
187
  ))
188
  fig.update_layout(height=300)
189
  return fig
190
 
191
  def analyze_portfolio(self, selected_stocks, simulation_days, num_simulations):
192
  """Main analysis function"""
193
- print(f"πŸ” Analyzing portfolio: {selected_stocks}")
194
 
195
  if not selected_stocks:
196
- error_html = """
197
- <div style='text-align: center; padding: 50px; background: #fff3cd; border-radius: 10px;'>
198
- <h3 style='color: #856404;'>⚠️ Please Select Stocks</h3>
199
- <p>Choose at least one stock from the list to run the analysis.</p>
200
- </div>
201
- """
202
- return error_html, None, None, None
203
 
204
- # Check if user selected exactly the 5 trained stocks
205
  if set(selected_stocks) != set(self.available_stocks):
206
- error_html = f"""
207
- <div style='text-align: center; padding: 50px; background: #fff3cd; border-radius: 10px;'>
208
- <h3 style='color: #856404;'>⚠️ Use Trained Stocks Only</h3>
209
- <p>This model was trained specifically on these 5 stocks:</p>
210
- <p style='font-weight: bold; font-size: 18px;'>{', '.join(self.available_stocks)}</p>
211
- <p>Please select <strong>all 5 stocks</strong> for accurate risk analysis.</p>
212
- </div>
213
- """
214
- return error_html, None, None, None
215
 
216
  try:
217
- # Convert simulations to integer
218
- num_simulations = int(num_simulations)
219
 
220
- # Run simulation
221
- simulation_results = self.run_monte_carlo(selected_stocks, simulation_days, num_simulations)
222
 
223
- if simulation_results is None:
224
- error_html = """
225
- <div style='text-align: center; padding: 50px; background: #f8d7da; border-radius: 10px;'>
226
- <h3 style='color: #721c24;'>❌ Simulation Failed</h3>
227
- <p>Unable to run simulation. Please try again.</p>
228
- </div>
229
- """
230
- return error_html, None, None, None
231
-
232
- # Calculate metrics
233
- metrics, simulation_array = self.calculate_metrics(simulation_results)
234
 
235
  if metrics is None:
236
- error_html = """
237
- <div style='text-align: center; padding: 50px; background: #f8d7da; border-radius: 10px;'>
238
- <h3 style='color: #721c24;'>❌ Metrics Calculation Failed</h3>
239
- <p>Unable to calculate risk metrics.</p>
240
- </div>
241
- """
242
- return error_html, None, None, None
243
 
244
- # Create plots
245
- sim_plot = self.create_simulation_plot(simulation_results)
246
- dist_plot = self.create_distribution_plot(simulation_results, metrics)
247
- risk_gauge = self.create_risk_gauge(metrics)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
 
249
- # Create success summary
250
- risk_level = "LOW" if metrics['prob_10_loss'] < 0.15 else "MEDIUM" if metrics['prob_10_loss'] < 0.3 else "HIGH"
251
- risk_color = "#28a745" if risk_level == "LOW" else "#ffc107" if risk_level == "MEDIUM" else "#dc3545"
 
252
 
253
- summary_html = f"""
254
- <div style="font-family: Arial, sans-serif; padding: 25px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px; color: white;">
255
- <h2 style="text-align: center; margin-bottom: 30px;">πŸ“Š Portfolio Risk Analysis Report</h2>
 
 
256
 
257
- <div style="background: rgba(255,255,255,0.1); padding: 20px; border-radius: 10px; margin-bottom: 20px;">
258
- <h3 style="margin-top: 0;">🎯 Trained Portfolio Analysis</h3>
259
- <p style="font-size: 18px; font-weight: bold;">{', '.join(selected_stocks)}</p>
260
- <p>Equal weights (20% each) β€’ {simulation_days} days β€’ {num_simulations} simulations</p>
261
  </div>
262
 
263
- <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
264
- <div style="background: rgba(255,255,255,0.2); padding: 15px; border-radius: 8px;">
265
- <h4 style="margin: 0;">Expected Value</h4>
266
- <p style="font-size: 24px; font-weight: bold; margin: 5px 0;">${metrics['expected_value']:.2f}</p>
267
- </div>
268
-
269
- <div style="background: rgba(255,255,255,0.2); padding: 15px; border-radius: 8px;">
270
- <h4 style="margin: 0;">Risk Level</h4>
271
- <p style="font-size: 24px; font-weight: bold; margin: 5px 0; color: {risk_color};">{risk_level}</p>
272
- </div>
273
-
274
- <div style="background: rgba(255,255,255,0.2); padding: 15px; border-radius: 8px;">
275
- <h4 style="margin: 0;">10% Loss Probability</h4>
276
- <p style="font-size: 24px; font-weight: bold; margin: 5px 0;">{metrics['prob_10_loss']*100:.1f}%</p>
277
- </div>
278
-
279
- <div style="background: rgba(255,255,255,0.2); padding: 15px; border-radius: 8px;">
280
- <h4 style="margin: 0;">Worst Case (5%)</h4>
281
- <p style="font-size: 24px; font-weight: bold; margin: 5px 0;">${metrics['var_95']:.2f}</p>
282
- </div>
283
  </div>
284
 
285
- <div style="margin-top: 20px; background: rgba(255,255,255,0.1); padding: 15px; border-radius: 8px;">
286
- <h4>πŸ“ˆ Additional Metrics</h4>
287
- <p>20% Loss Probability: <strong>{metrics['prob_20_loss']*100:.1f}%</strong> |
288
- 30% Loss Probability: <strong>{metrics['prob_30_loss']*100:.1f}%</strong> |
289
- Best Case (95%): <strong>${metrics['best_case']:.2f}</strong></p>
290
  </div>
291
  </div>
292
- """
293
-
294
- print("βœ… Analysis completed successfully!")
295
- return summary_html, sim_plot, dist_plot, risk_gauge
296
-
297
- except Exception as e:
298
- error_html = f"""
299
- <div style='text-align: center; padding: 50px; background: #f8d7da; border-radius: 10px;'>
300
- <h3 style='color: #721c24;'>❌ Analysis Error</h3>
301
- <p>Error: {str(e)}</p>
302
- <p>Please try again.</p>
303
- </div>
304
- """
305
- print(f"❌ Analysis error: {str(e)}")
306
- return error_html, None, None, None
307
 
308
- # Initialize app
309
  app = GradioRiskApp()
310
 
311
- # Create Gradio interface
312
  with gr.Blocks(theme=gr.themes.Soft(), title="QuantRisk Pro") as demo:
313
- gr.Markdown(
314
- """
315
- # πŸ“Š QuantRisk Pro - Portfolio Risk Analyzer
316
- **AI-Powered Monte Carlo Simulations for Portfolio Risk Assessment**
317
-
318
- *Model trained on: AAPL, MSFT, GOOGL, AMZN, TSLA (5 years historical data)*
319
- """
320
- )
321
 
322
  with gr.Row():
323
  with gr.Column(scale=1):
324
- gr.Markdown("### πŸ”§ Portfolio Configuration")
325
-
326
- gr.Markdown("""
327
- **🎯 Trained Stock Portfolio**
328
- *This model was specifically trained on these 5 stocks*
329
- """)
330
-
331
- stock_checkboxes = gr.CheckboxGroup(
332
  choices=app.available_stocks,
333
- value=app.available_stocks, # Auto-select all 5 stocks
334
- label="**Select Stocks (Choose ALL 5)**",
335
- info="Model works only with these specific stocks"
336
  )
337
-
338
- simulation_days = gr.Slider(
339
- minimum=30,
340
- maximum=500,
341
- value=252,
342
- step=30,
343
- label="**Time Horizon (Days)**",
344
- info="Simulation period in trading days (252 = 1 year)"
345
- )
346
-
347
- num_simulations = gr.Dropdown(
348
- choices=[1000, 2500, 5000, 10000],
349
- value=5000,
350
- label="**Number of Simulations**",
351
- info="More simulations = more accuracy but slower"
352
- )
353
-
354
- analyze_btn = gr.Button("πŸš€ Run Risk Analysis", variant="primary", size="lg")
355
-
356
- gr.Markdown("---")
357
- gr.Markdown("""
358
- **πŸ’‘ Important Notes:**
359
- - βœ… Model trained on 5 years of AAPL, MSFT, GOOGL, AMZN, TSLA data
360
- - βœ… Uses actual historical volatility and correlations
361
- - βœ… All 5 stocks must be selected for accurate results
362
- - βœ… Equal weights (20% each) applied automatically
363
- """)
364
 
365
  with gr.Column(scale=2):
366
- gr.Markdown("### πŸ“ˆ Analysis Results")
367
-
368
- summary_html = gr.HTML(
369
- value=f"""
370
- <div style='text-align: center; padding: 50px; background: #f8f9fa; border-radius: 10px;'>
371
- <h3 style='color: #6c757d;'>πŸš€ Ready to Analyze!</h3>
372
- <p>Model trained on: <strong>{', '.join(app.available_stocks)}</strong></p>
373
- <p>All 5 stocks are pre-selected. Click 'Run Risk Analysis' to see your risk report.</p>
374
- </div>
375
- """
376
- )
377
-
378
  with gr.Row():
379
- risk_gauge = gr.Plot(label="**Risk Probability Gauge**")
380
- simulation_plot = gr.Plot(label="**Monte Carlo Simulation**")
381
-
382
- distribution_plot = gr.Plot(label="**Portfolio Value Distribution**")
383
-
384
- # Footer
385
- gr.Markdown("---")
386
- gr.Markdown(
387
- """
388
- <div style="text-align: center; color: #666;">
389
- <p>Built with ❀️ using Gradio | QuantRisk Pro v2.0 | Trained on 5 years of historical data</p>
390
- </div>
391
- """
392
- )
393
 
394
- # Set up event handling
395
- analyze_btn.click(
396
  fn=app.analyze_portfolio,
397
- inputs=[stock_checkboxes, simulation_days, num_simulations],
398
- outputs=[summary_html, simulation_plot, distribution_plot, risk_gauge]
399
  )
400
 
401
- # Launch for Hugging Face
402
  if __name__ == "__main__":
403
  demo.launch()
 
9
  import warnings
10
  warnings.filterwarnings('ignore')
11
 
12
+ # Custom unpickler to handle numpy version issues
13
+ class SafeUnpickler(pickle.Unpickler):
14
+ def find_class(self, module, name):
15
+ if module == "numpy._core.multiarray" or module == "numpy.core.multiarray":
16
+ return np.core.multiarray.scalar
17
+ if module == "numpy":
18
+ return getattr(np, name)
19
+ return super().find_class(module, name)
20
+
21
  def load_model():
22
  try:
23
  with open('portfolio_risk_model.pkl', 'rb') as f:
24
+ model = SafeUnpickler(f).load()
25
  print("βœ… Model loaded successfully!")
 
26
  return model
27
  except Exception as e:
28
  print(f"❌ Model loading error: {str(e)}")
29
+ # Create model from scratch using yfinance data
30
+ return create_model_from_scratch()
31
+
32
+ def create_model_from_scratch():
33
+ """Create model directly from yfinance data"""
34
+ print("πŸ”„ Creating model from scratch...")
35
+
36
+ stocks = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA']
37
+
38
+ try:
39
+ # Fetch fresh data
40
+ data = yf.download(stocks, period='5y')['Adj Close']
41
+ returns = data.pct_change().dropna()
42
+
43
+ # Calculate model parameters
44
+ mean_returns = returns.mean().tolist()
45
+ cov_matrix = returns.cov().values.tolist()
46
+ volatilities = (returns.std() * np.sqrt(252)).tolist()
47
+
48
+ # Calculate portfolio metrics
49
+ portfolio_returns = returns.dot([0.2, 0.2, 0.2, 0.2, 0.2])
50
+
51
+ model = {
52
+ 'mean_returns': mean_returns,
53
+ 'covariance_matrix': cov_matrix,
54
+ 'volatilities': volatilities,
55
+ 'risk_metrics': {
56
+ 'historical_var_95': float(np.percentile(portfolio_returns, 5)),
57
+ 'cvar_95': float(portfolio_returns[portfolio_returns <= np.percentile(portfolio_returns, 5)].mean()),
58
+ 'annual_mean_return': float(portfolio_returns.mean() * 252),
59
+ 'annual_volatility': float(portfolio_returns.std() * np.sqrt(252)),
60
+ 'sharpe_ratio': float((portfolio_returns.mean() * 252) / (portfolio_returns.std() * np.sqrt(252)))
61
+ },
62
+ 'loss_probabilities': {
63
+ 'probability_10_percent_loss': 0.15, # Placeholder
64
+ 'probability_20_percent_loss': 0.08, # Placeholder
65
+ 'probability_30_percent_loss': 0.03, # Placeholder
66
+ 'expected_portfolio_value': 110.0, # Placeholder
67
+ 'median_portfolio_value': 108.5, # Placeholder
68
+ 'worst_case_5th_percentile': 85.0, # Placeholder
69
+ 'best_case_95th_percentile': 135.0 # Placeholder
70
+ },
71
+ 'tickers': stocks,
72
+ 'training_date': datetime.now().strftime("%Y-%m-%d"),
73
+ 'data_period': '5 years'
74
+ }
75
+ print("βœ… Model created from scratch!")
76
+ return model
77
+ except Exception as e:
78
+ print(f"❌ Error creating model: {str(e)}")
79
  return None
80
 
81
  class GradioRiskApp:
82
  def __init__(self):
83
  self.model = load_model()
 
 
84
  self.available_stocks = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA']
85
+ print(f"🎯 Available stocks: {self.available_stocks}")
 
86
 
87
  def run_monte_carlo(self, selected_stocks, days=252, simulations=5000):
88
+ """Run Monte Carlo simulation"""
89
  try:
90
  if self.model is None:
91
+ print("❌ No model available")
92
+ return None
93
 
 
94
  if set(selected_stocks) != set(self.available_stocks):
95
+ print("❌ Stocks don't match trained stocks")
96
+ return None
97
 
98
+ print("🎯 Running Monte Carlo simulation...")
 
99
 
100
+ # Extract parameters from model
101
  mean_returns = np.array(self.model['mean_returns'])
102
  cov_matrix = np.array(self.model['covariance_matrix'])
103
 
104
+ # Ensure covariance matrix is positive definite
105
+ cov_matrix = self.make_positive_definite(cov_matrix)
106
+
107
  # Generate simulation
108
  np.random.seed(42)
109
+ L = np.linalg.cholesky(cov_matrix)
 
 
 
 
 
110
 
111
  simulation_results = []
112
+ weights = np.array([0.2, 0.2, 0.2, 0.2, 0.2])
113
 
114
  for i in range(simulations):
115
+ # Generate correlated returns
116
+ random_numbers = np.random.normal(0, 1, size=(days, 5))
117
+ correlated_returns = random_numbers @ L.T + mean_returns
118
 
119
+ # Calculate portfolio path
120
+ portfolio_value = 100.0
121
  portfolio_path = [portfolio_value]
122
 
123
  for day in range(days):
124
+ daily_return = np.dot(correlated_returns[day], weights)
125
+ portfolio_value = portfolio_value * (1 + daily_return)
126
  portfolio_path.append(portfolio_value)
127
 
128
  simulation_results.append(portfolio_path)
129
 
130
+ print(f"βœ… Simulation completed with {len(simulation_results)} paths")
131
+ return np.array(simulation_results)
132
 
133
  except Exception as e:
134
  print(f"❌ Simulation error: {str(e)}")
135
  return None
136
 
137
+ def make_positive_definite(self, matrix):
138
+ """Ensure covariance matrix is positive definite"""
139
+ min_eig = np.min(np.real(np.linalg.eigvals(matrix)))
140
+ if min_eig < 0:
141
+ matrix -= 10 * min_eig * np.eye(*matrix.shape)
142
+ return matrix
143
+
144
  def calculate_metrics(self, simulation_results):
145
+ """Calculate risk metrics"""
146
  if simulation_results is None:
147
  return None
148
+
149
+ final_values = simulation_results[:, -1]
 
150
 
151
  metrics = {
152
+ 'expected_value': float(np.mean(final_values)),
153
+ 'prob_10_loss': float(np.mean(final_values < 90)),
154
+ 'prob_20_loss': float(np.mean(final_values < 80)),
155
+ 'prob_30_loss': float(np.mean(final_values < 70)),
156
+ 'var_95': float(np.percentile(final_values, 5)),
157
+ 'best_case': float(np.percentile(final_values, 95)),
158
+ 'worst_case': float(np.percentile(final_values, 5))
 
159
  }
160
 
161
+ return metrics
162
 
163
  def create_simulation_plot(self, simulation_results):
164
+ """Create simulation plot"""
165
  if simulation_results is None:
166
  return None
167
+
168
  fig = go.Figure()
169
 
170
+ # Sample paths
171
+ for i in range(min(50, len(simulation_results))):
172
  fig.add_trace(go.Scatter(
173
  y=simulation_results[i],
174
  mode='lines',
175
  line=dict(width=1, color='lightblue'),
176
+ opacity=0.1,
177
  showlegend=False
178
  ))
179
 
 
190
  title="πŸ“ˆ Monte Carlo Simulation Paths",
191
  xaxis_title="Trading Days",
192
  yaxis_title="Portfolio Value ($)",
193
+ height=400
 
194
  )
195
 
196
  return fig
197
 
198
  def create_distribution_plot(self, simulation_results, metrics):
199
+ """Create distribution plot"""
200
  if simulation_results is None:
201
  return None
202
+
203
+ final_values = simulation_results[:, -1]
204
 
205
  fig = px.histogram(
206
  x=final_values,
207
  nbins=50,
208
+ title="πŸ“Š Portfolio Value Distribution",
209
+ color_discrete_sequence=['#667eea']
210
  )
211
 
212
  if metrics:
213
+ fig.add_vline(x=metrics['expected_value'], line_dash="dash",
214
+ line_color="red", annotation_text=f"Mean: ${metrics['expected_value']:.2f}")
215
+ fig.add_vline(x=metrics['var_95'], line_dash="dash",
216
+ line_color="orange", annotation_text=f"5% VaR: ${metrics['var_95']:.2f}")
217
 
218
  fig.update_layout(height=400, showlegend=False)
219
  return fig
220
 
221
  def create_risk_gauge(self, metrics):
222
+ """Create risk gauge"""
223
  if metrics is None:
224
  return None
225
+
226
  risk_prob = metrics['prob_10_loss'] * 100
227
 
228
  fig = go.Figure(go.Indicator(
229
+ mode="gauge+number",
230
+ value=risk_prob,
231
+ domain={'x': [0, 1], 'y': [0, 1]},
232
+ title={'text': "Probability of >10% Loss"},
233
+ gauge={
234
+ 'axis': {'range': [0, 50]},
235
+ 'bar': {'color': "darkblue"},
236
  'steps': [
237
  {'range': [0, 15], 'color': "lightgreen"},
238
  {'range': [15, 30], 'color': "yellow"},
239
+ {'range': [30, 50], 'color': "red"}
240
+ ]
241
+ }
 
 
242
  ))
243
  fig.update_layout(height=300)
244
  return fig
245
 
246
  def analyze_portfolio(self, selected_stocks, simulation_days, num_simulations):
247
  """Main analysis function"""
248
+ print(f"πŸ” Analyzing: {selected_stocks}")
249
 
250
  if not selected_stocks:
251
+ return self.create_error_html("Please select at least one stock"), None, None, None
 
 
 
 
 
 
252
 
 
253
  if set(selected_stocks) != set(self.available_stocks):
254
+ return self.create_error_html(f"Please select exactly these 5 stocks: {', '.join(self.available_stocks)}"), None, None, None
 
 
 
 
 
 
 
 
255
 
256
  try:
257
+ simulations = self.run_monte_carlo(selected_stocks, simulation_days, int(num_simulations))
 
258
 
259
+ if simulations is None:
260
+ return self.create_error_html("Simulation failed"), None, None, None
261
 
262
+ metrics = self.calculate_metrics(simulations)
 
 
 
 
 
 
 
 
 
 
263
 
264
  if metrics is None:
265
+ return self.create_error_html("Metrics calculation failed"), None, None, None
266
+
267
+ summary = self.create_summary_html(metrics, selected_stocks, simulation_days, num_simulations)
268
+ sim_plot = self.create_simulation_plot(simulations)
269
+ dist_plot = self.create_distribution_plot(simulations, metrics)
270
+ gauge = self.create_risk_gauge(metrics)
 
271
 
272
+ return summary, sim_plot, dist_plot, gauge
273
+
274
+ except Exception as e:
275
+ return self.create_error_html(f"Analysis error: {str(e)}"), None, None, None
276
+
277
+ def create_error_html(self, message):
278
+ """Create error message HTML"""
279
+ return f"""
280
+ <div style='text-align: center; padding: 40px; background: #f8d7da; border-radius: 10px;'>
281
+ <h3 style='color: #721c24;'>❌ Error</h3>
282
+ <p>{message}</p>
283
+ </div>
284
+ """
285
+
286
+ def create_summary_html(self, metrics, stocks, days, sims):
287
+ """Create summary HTML"""
288
+ risk_level = "LOW" if metrics['prob_10_loss'] < 0.1 else "MEDIUM" if metrics['prob_10_loss'] < 0.2 else "HIGH"
289
+ risk_color = "#28a745" if risk_level == "LOW" else "#ffc107" if risk_level == "MEDIUM" else "#dc3545"
290
+
291
+ return f"""
292
+ <div style="padding: 25px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px; color: white;">
293
+ <h2 style="text-align: center;">πŸ“Š Risk Analysis Report</h2>
294
 
295
+ <div style="background: rgba(255,255,255,0.1); padding: 20px; border-radius: 10px; margin: 15px 0;">
296
+ <h3>Portfolio: {', '.join(stocks)}</h3>
297
+ <p>Weights: 20% each | Period: {days} days | Simulations: {sims}</p>
298
+ </div>
299
 
300
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
301
+ <div style="background: rgba(255,255,255,0.2); padding: 15px; border-radius: 8px;">
302
+ <h4>Expected Value</h4>
303
+ <p style="font-size: 24px; font-weight: bold;">${metrics['expected_value']:.2f}</p>
304
+ </div>
305
 
306
+ <div style="background: rgba(255,255,255,0.2); padding: 15px; border-radius: 8px;">
307
+ <h4>Risk Level</h4>
308
+ <p style="font-size: 24px; font-weight: bold; color: {risk_color};">{risk_level}</p>
 
309
  </div>
310
 
311
+ <div style="background: rgba(255,255,255,0.2); padding: 15px; border-radius: 8px;">
312
+ <h4>10% Loss Probability</h4>
313
+ <p style="font-size: 24px; font-weight: bold;">{metrics['prob_10_loss']*100:.1f}%</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
  </div>
315
 
316
+ <div style="background: rgba(255,255,255,0.2); padding: 15px; border-radius: 8px;">
317
+ <h4>Worst Case (5%)</h4>
318
+ <p style="font-size: 24px; font-weight: bold;">${metrics['var_95']:.2f}</p>
 
 
319
  </div>
320
  </div>
321
+ </div>
322
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
323
 
324
+ # Create and launch app
325
  app = GradioRiskApp()
326
 
 
327
  with gr.Blocks(theme=gr.themes.Soft(), title="QuantRisk Pro") as demo:
328
+ gr.Markdown("# πŸ“Š QuantRisk Pro - Portfolio Risk Analyzer")
 
 
 
 
 
 
 
329
 
330
  with gr.Row():
331
  with gr.Column(scale=1):
332
+ gr.Markdown("### πŸ”§ Configuration")
333
+ stocks = gr.CheckboxGroup(
 
 
 
 
 
 
334
  choices=app.available_stocks,
335
+ value=app.available_stocks,
336
+ label="Select Stocks (All 5 Required)"
 
337
  )
338
+ days = gr.Slider(30, 500, 252, label="Time Horizon (Days)")
339
+ sims = gr.Dropdown([1000, 2500, 5000, 10000], value=5000, label="Simulations")
340
+ btn = gr.Button("πŸš€ Analyze", variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
341
 
342
  with gr.Column(scale=2):
343
+ summary = gr.HTML()
 
 
 
 
 
 
 
 
 
 
 
344
  with gr.Row():
345
+ gauge = gr.Plot()
346
+ sim_plot = gr.Plot()
347
+ dist_plot = gr.Plot()
 
 
 
 
 
 
 
 
 
 
 
348
 
349
+ btn.click(
 
350
  fn=app.analyze_portfolio,
351
+ inputs=[stocks, days, sims],
352
+ outputs=[summary, sim_plot, dist_plot, gauge]
353
  )
354
 
 
355
  if __name__ == "__main__":
356
  demo.launch()