GLAkavya commited on
Commit
cdcad39
Β·
verified Β·
1 Parent(s): 196bd59

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +255 -51
app.py CHANGED
@@ -1,17 +1,29 @@
 
 
 
 
 
1
  import gradio as gr
 
2
  import numpy as np
 
 
 
 
3
  import pickle
4
  import warnings
5
- warnings.filterwarnings("ignore")
6
 
 
7
  class SafeUnpickler(pickle.Unpickler):
8
  def find_class(self, module, name):
9
- if module in ["numpy._core.multiarray", "numpy.core.multiarray"]:
10
  return np.core.multiarray.scalar
11
  if module == "numpy":
12
  return getattr(np, name)
13
  return super().find_class(module, name)
14
 
 
15
  def load_model():
16
  try:
17
  with open('portfolio_risk_model.pkl', 'rb') as f:
@@ -19,72 +31,264 @@ def load_model():
19
  print("βœ… Model loaded successfully!")
20
  return model
21
  except Exception as e:
22
- print("❌ Model loading error:", e)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  return None
24
 
25
- model = load_model()
26
- available_stocks = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
- def analyze(stocks, days, sims):
29
- if model is None:
30
- return "<b>❌ Model Missing</b>", None, None, None
 
 
 
 
 
 
 
 
 
 
 
31
 
32
- if len(stocks) == 0:
33
- return "<b>❌ Please select at least 1 stock</b>", None, None, None
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
- idx = [available_stocks.index(s) for s in stocks]
36
- means = np.array(model["mean_returns"])[idx]
37
- cov = np.array(model["covariance_matrix"])
38
- cov = cov[np.ix_(idx, idx)]
39
 
40
- eigvals = np.linalg.eigvals(cov)
41
- if np.min(eigvals) < 0:
42
- cov -= 1.3 * np.min(eigvals) * np.eye(*cov.shape)
43
 
44
- L = np.linalg.cholesky(cov)
45
- weights = np.ones(len(stocks)) / len(stocks)
 
 
46
 
47
- paths = []
48
- sims = int(sims)
49
- days = int(days)
50
 
51
- for _ in range(sims):
52
- r = np.random.normal(0, 1, (days, len(stocks)))
53
- corr = r @ L.T + means
54
- val = 100
55
- p = [val]
56
- for d in range(days):
57
- val *= (1 + np.dot(corr[d], weights))
58
- p.append(val)
59
- paths.append(p)
60
 
61
- paths = np.array(paths)
62
- final = paths[:, -1]
63
 
64
- summary = f"""
65
- βœ… <b>Expected Value:</b> ${np.mean(final):.2f}<br>
66
- πŸ“‰ <b>10% Loss Probability:</b> {(np.mean(final < 90)*100):.2f}%<br>
67
- 🚨 <b>Worst Case (5%):</b> ${np.percentile(final, 5):.2f}<br>
68
- 🟒 <b>Best Case (95%):</b> ${np.percentile(final, 95):.2f}
69
- """
 
 
70
 
71
- return summary, paths.tolist(), final.tolist(), float(np.mean(final))
 
 
72
 
73
- with gr.Blocks(css=None, theme=None) as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
- gr.HTML(open("templates/index.html", "r").read())
 
 
 
76
 
77
- backend_stocks = gr.CheckboxGroup(choices=available_stocks, value=available_stocks)
78
- backend_days = gr.Slider(30, 500, 252)
79
- backend_sims = gr.Dropdown([1000, 2500, 5000, 10000], value=5000)
80
 
81
- backend_btn = gr.Button("RUN_BACKEND", visible=False)
 
 
 
 
 
 
 
 
 
 
82
 
83
- backend_result = gr.HTML()
84
- backend_monte = gr.Plot()
85
- backend_dist = gr.Plot()
 
 
 
86
 
87
- backend_btn.click(analyze, [backend_stocks, backend_days, backend_sims], [backend_result, backend_monte, backend_dist, None])
 
 
 
 
88
 
89
  if __name__ == "__main__":
90
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
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
8
  import numpy as np
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":
21
  return np.core.multiarray.scalar
22
  if module == "numpy":
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:
 
31
  print("βœ… Model loaded successfully!")
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,
53
+ 'volatilities': volatilities,
54
+ 'risk_metrics': {
55
+ 'historical_var_95': float(np.percentile(portfolio_returns, 5)),
56
+ 'cvar_95': float(portfolio_returns[portfolio_returns <= np.percentile(portfolio_returns, 5)].mean()),
57
+ 'annual_mean_return': float(portfolio_returns.mean() * 252),
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'
64
+ }
65
+ print("βœ… Model created from scratch!")
66
+ return model
67
+ except Exception as e:
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>
255
+ </div>
256
+ </div>
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],
290
+ outputs=[summary, sim_plot, dist_plot, gauge]
291
+ )
292
 
293
  if __name__ == "__main__":
294
+ demo.launch()