alidenewade commited on
Commit
19cb70e
·
verified ·
1 Parent(s): c97a5df

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +373 -0
app.py ADDED
@@ -0,0 +1,373 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import numpy as np
3
+ import pandas as pd
4
+ import matplotlib.pyplot as plt
5
+ from scipy.stats import norm, lognorm
6
+ import seaborn as sns
7
+
8
+ # Set matplotlib style for professional plots
9
+ plt.style.use('default')
10
+ sns.set_palette("husl")
11
+
12
+ class TVOGAnalysis:
13
+ def __init__(self):
14
+ self.reset_parameters()
15
+
16
+ def reset_parameters(self):
17
+ """Reset to default parameters"""
18
+ self.scenarios = 10000
19
+ self.risk_free_rate = 0.02
20
+ self.volatility = 0.03
21
+ self.maturity = 10
22
+ self.sum_assured = 500000
23
+ self.policy_count = 100
24
+
25
+ def generate_random_numbers(self, scenarios, time_steps):
26
+ """Generate standard normal random numbers"""
27
+ np.random.seed(42) # For reproducibility
28
+ return np.random.standard_normal((scenarios, time_steps))
29
+
30
+ def simulate_account_values(self, initial_av, scenarios, time_steps):
31
+ """Simulate account value paths using geometric Brownian motion"""
32
+ dt = 1/12 # Monthly time steps
33
+ rand_nums = self.generate_random_numbers(scenarios, time_steps)
34
+
35
+ # Initialize account value matrix
36
+ av_paths = np.zeros((scenarios, time_steps + 1))
37
+ av_paths[:, 0] = initial_av
38
+
39
+ # Simulate paths
40
+ for t in range(time_steps):
41
+ drift = (self.risk_free_rate - 0.5 * self.volatility**2) * dt
42
+ diffusion = self.volatility * np.sqrt(dt) * rand_nums[:, t]
43
+ av_paths[:, t+1] = av_paths[:, t] * np.exp(drift + diffusion)
44
+
45
+ return av_paths
46
+
47
+ def calculate_gmab_payouts(self, av_paths):
48
+ """Calculate GMAB payouts at maturity"""
49
+ final_av = av_paths[:, -1]
50
+ guarantee = self.sum_assured * self.policy_count
51
+ payouts = np.maximum(guarantee - final_av, 0)
52
+
53
+ # Present value of payouts
54
+ discount_factor = np.exp(-self.risk_free_rate * self.maturity)
55
+ pv_payouts = payouts * discount_factor
56
+
57
+ return pv_payouts, payouts
58
+
59
+ def black_scholes_put(self, S0, K, T, r, sigma):
60
+ """Black-Scholes-Merton formula for European put option"""
61
+ d1 = (np.log(S0/K) + (r + 0.5*sigma**2)*T) / (sigma*np.sqrt(T))
62
+ d2 = d1 - sigma*np.sqrt(T)
63
+
64
+ put_price = K*np.exp(-r*T)*norm.cdf(-d2) - S0*norm.cdf(-d1)
65
+ return put_price
66
+
67
+ def create_dashboard():
68
+ tvog = TVOGAnalysis()
69
+
70
+ def update_analysis(scenarios, risk_free_rate, volatility, maturity,
71
+ sum_assured, policy_count, min_premium, max_premium, num_points):
72
+
73
+ # Update parameters
74
+ tvog.scenarios = int(scenarios)
75
+ tvog.risk_free_rate = risk_free_rate
76
+ tvog.volatility = volatility
77
+ tvog.maturity = maturity
78
+ tvog.sum_assured = sum_assured
79
+ tvog.policy_count = policy_count
80
+
81
+ # Create model points with varying initial account values
82
+ premiums = np.linspace(min_premium, max_premium, int(num_points))
83
+ initial_avs = premiums * policy_count
84
+
85
+ monte_carlo_results = []
86
+ black_scholes_results = []
87
+
88
+ time_steps = int(maturity * 12) # Monthly steps
89
+
90
+ for initial_av in initial_avs:
91
+ # Monte Carlo simulation
92
+ av_paths = tvog.simulate_account_values(initial_av, tvog.scenarios, time_steps)
93
+ pv_payouts, _ = tvog.calculate_gmab_payouts(av_paths)
94
+ mc_tvog = np.mean(pv_payouts)
95
+ monte_carlo_results.append(mc_tvog)
96
+
97
+ # Black-Scholes-Merton
98
+ guarantee = sum_assured * policy_count
99
+ bs_tvog = tvog.black_scholes_put(initial_av, guarantee, maturity,
100
+ risk_free_rate, volatility)
101
+ black_scholes_results.append(bs_tvog)
102
+
103
+ # Create results DataFrame
104
+ results_df = pd.DataFrame({
105
+ 'Premium_per_Policy': premiums,
106
+ 'Initial_Account_Value': initial_avs,
107
+ 'Monte_Carlo_TVOG': monte_carlo_results,
108
+ 'Black_Scholes_TVOG': black_scholes_results,
109
+ 'Ratio_MC_BS': np.array(monte_carlo_results) / np.array(black_scholes_results),
110
+ 'Difference': np.array(monte_carlo_results) - np.array(black_scholes_results)
111
+ })
112
+
113
+ # Create plots
114
+ fig1 = create_tvog_comparison_plot(results_df)
115
+ fig2 = create_sample_paths_plot(tvog, initial_avs[len(initial_avs)//2], time_steps)
116
+ fig3 = create_distribution_plots(tvog, initial_avs[len(initial_avs)//2], time_steps)
117
+ fig4 = create_convergence_plot(tvog, initial_avs[len(initial_avs)//2], time_steps)
118
+
119
+ return results_df, fig1, fig2, fig3, fig4
120
+
121
+ def create_tvog_comparison_plot(results_df):
122
+ """Create TVOG comparison plot"""
123
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
124
+
125
+ # TVOG Comparison
126
+ ax1.scatter(results_df['Initial_Account_Value'], results_df['Monte_Carlo_TVOG'],
127
+ s=50, alpha=0.7, label='Monte Carlo', color='blue')
128
+ ax1.scatter(results_df['Initial_Account_Value'], results_df['Black_Scholes_TVOG'],
129
+ s=50, alpha=0.7, label='Black-Scholes-Merton', color='red')
130
+ ax1.set_xlabel('Initial Account Value')
131
+ ax1.set_ylabel('TVOG Value')
132
+ ax1.set_title('TVOG: Monte Carlo vs Black-Scholes-Merton')
133
+ ax1.legend()
134
+ ax1.grid(True, alpha=0.3)
135
+
136
+ # Ratio Plot
137
+ ax2.plot(results_df['Initial_Account_Value'], results_df['Ratio_MC_BS'],
138
+ 'o-', color='green', markersize=6)
139
+ ax2.axhline(y=1, color='red', linestyle='--', alpha=0.7)
140
+ ax2.set_xlabel('Initial Account Value')
141
+ ax2.set_ylabel('Monte Carlo / Black-Scholes Ratio')
142
+ ax2.set_title('Convergence Ratio (MC/BS)')
143
+ ax2.grid(True, alpha=0.3)
144
+
145
+ plt.tight_layout()
146
+ return fig
147
+
148
+ def create_sample_paths_plot(tvog, initial_av, time_steps):
149
+ """Create sample simulation paths plot"""
150
+ sample_scenarios = min(100, tvog.scenarios)
151
+ av_paths = tvog.simulate_account_values(initial_av, sample_scenarios, time_steps)
152
+
153
+ fig, ax = plt.subplots(1, 1, figsize=(12, 6))
154
+
155
+ time_axis = np.arange(time_steps + 1) / 12 # Convert to years
156
+
157
+ for i in range(sample_scenarios):
158
+ ax.plot(time_axis, av_paths[i, :], alpha=0.3, linewidth=0.8)
159
+
160
+ # Add mean path
161
+ mean_path = np.mean(av_paths, axis=0)
162
+ ax.plot(time_axis, mean_path, color='red', linewidth=3, label='Mean Path')
163
+
164
+ # Add guarantee line
165
+ guarantee = tvog.sum_assured * tvog.policy_count
166
+ ax.axhline(y=guarantee, color='black', linestyle='--', linewidth=2,
167
+ label=f'Guarantee Level ({guarantee:,.0f})')
168
+
169
+ ax.set_xlabel('Time (Years)')
170
+ ax.set_ylabel('Account Value')
171
+ ax.set_title(f'Sample Account Value Simulation Paths (n={sample_scenarios})')
172
+ ax.legend()
173
+ ax.grid(True, alpha=0.3)
174
+
175
+ return fig
176
+
177
+ def create_distribution_plots(tvog, initial_av, time_steps):
178
+ """Create distribution analysis plots"""
179
+ av_paths = tvog.simulate_account_values(initial_av, tvog.scenarios, time_steps)
180
+ final_av = av_paths[:, -1]
181
+
182
+ # Present value of final account values
183
+ pv_final_av = final_av * np.exp(-tvog.risk_free_rate * tvog.maturity)
184
+
185
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
186
+
187
+ # Histogram of final account values
188
+ ax1.hist(pv_final_av, bins=50, density=True, alpha=0.7, color='skyblue')
189
+
190
+ # Theoretical lognormal distribution
191
+ S0 = initial_av
192
+ sigma = tvog.volatility
193
+ T = tvog.maturity
194
+
195
+ x_range = np.linspace(pv_final_av.min(), pv_final_av.max(), 1000)
196
+ theoretical_pdf = lognorm.pdf(x_range, sigma * np.sqrt(T), scale=S0)
197
+ ax1.plot(x_range, theoretical_pdf, 'r-', linewidth=2, label='Theoretical Lognormal')
198
+ ax1.axvline(x=S0, color='green', linestyle='--', label=f'Initial Value: {S0:,.0f}')
199
+ ax1.axvline(x=np.mean(pv_final_av), color='orange', linestyle='--',
200
+ label=f'Simulated Mean: {np.mean(pv_final_av):,.0f}')
201
+
202
+ ax1.set_xlabel('Present Value of Final Account Value')
203
+ ax1.set_ylabel('Density')
204
+ ax1.set_title('Distribution of Final Account Values')
205
+ ax1.legend()
206
+ ax1.grid(True, alpha=0.3)
207
+
208
+ # GMAB Payouts
209
+ pv_payouts, _ = tvog.calculate_gmab_payouts(av_paths)
210
+ non_zero_payouts = pv_payouts[pv_payouts > 0]
211
+
212
+ ax2.hist(pv_payouts, bins=50, alpha=0.7, color='lightcoral')
213
+ ax2.set_xlabel('GMAB Payout (Present Value)')
214
+ ax2.set_ylabel('Frequency')
215
+ ax2.set_title(f'GMAB Payout Distribution\n({len(non_zero_payouts)} non-zero payouts)')
216
+ ax2.grid(True, alpha=0.3)
217
+
218
+ # Add statistics text
219
+ stats_text = f'Mean Payout: {np.mean(pv_payouts):,.0f}\n'
220
+ stats_text += f'Max Payout: {np.max(pv_payouts):,.0f}\n'
221
+ stats_text += f'Payout Probability: {len(non_zero_payouts)/len(pv_payouts):.1%}'
222
+ ax2.text(0.95, 0.95, stats_text, transform=ax2.transAxes,
223
+ verticalalignment='top', horizontalalignment='right',
224
+ bbox=dict(boxstyle='round', facecolor='white', alpha=0.8))
225
+
226
+ plt.tight_layout()
227
+ return fig
228
+
229
+ def create_convergence_plot(tvog, initial_av, time_steps):
230
+ """Create convergence analysis plot"""
231
+ # Test different numbers of scenarios
232
+ scenario_counts = [100, 500, 1000, 2000, 5000, 10000]
233
+ if tvog.scenarios not in scenario_counts:
234
+ scenario_counts.append(tvog.scenarios)
235
+ scenario_counts.sort()
236
+
237
+ mc_results = []
238
+
239
+ guarantee = tvog.sum_assured * tvog.policy_count
240
+ bs_result = tvog.black_scholes_put(initial_av, guarantee, tvog.maturity,
241
+ tvog.risk_free_rate, tvog.volatility)
242
+
243
+ np.random.seed(42) # For reproducible convergence
244
+
245
+ for n_scenarios in scenario_counts:
246
+ av_paths = tvog.simulate_account_values(initial_av, n_scenarios, time_steps)
247
+ pv_payouts, _ = tvog.calculate_gmab_payouts(av_paths)
248
+ mc_tvog = np.mean(pv_payouts)
249
+ mc_results.append(mc_tvog)
250
+
251
+ fig, ax = plt.subplots(1, 1, figsize=(10, 6))
252
+
253
+ ax.plot(scenario_counts, mc_results, 'bo-', markersize=8, linewidth=2,
254
+ label='Monte Carlo Results')
255
+ ax.axhline(y=bs_result, color='red', linestyle='--', linewidth=2,
256
+ label=f'Black-Scholes Result: {bs_result:.0f}')
257
+
258
+ ax.set_xlabel('Number of Scenarios')
259
+ ax.set_ylabel('TVOG Value')
260
+ ax.set_title('Monte Carlo Convergence Analysis')
261
+ ax.legend()
262
+ ax.grid(True, alpha=0.3)
263
+ ax.set_xscale('log')
264
+
265
+ # Add convergence statistics
266
+ final_error = abs(mc_results[-1] - bs_result) / bs_result * 100
267
+ ax.text(0.02, 0.98, f'Final Error: {final_error:.2f}%',
268
+ transform=ax.transAxes, verticalalignment='top',
269
+ bbox=dict(boxstyle='round', facecolor='white', alpha=0.8))
270
+
271
+ return fig
272
+
273
+ # Create Gradio interface
274
+ with gr.Blocks(title="TVOG Analysis Dashboard") as app:
275
+ gr.Markdown("""
276
+ # Time Value of Options and Guarantees (TVOG) Analysis Dashboard
277
+
278
+ This dashboard compares Monte Carlo simulation results with Black-Scholes-Merton analytical solutions
279
+ for Variable Annuity products with Guaranteed Minimum Accumulation Benefits (GMAB).
280
+
281
+ **Target Users:** Actuaries, Finance Professionals, Economists, and Academics
282
+ """)
283
+
284
+ with gr.Row():
285
+ with gr.Column(scale=1):
286
+ gr.Markdown("### Model Parameters")
287
+
288
+ scenarios = gr.Slider(
289
+ minimum=1000, maximum=50000, step=1000, value=10000,
290
+ label="Number of Monte Carlo Scenarios"
291
+ )
292
+
293
+ risk_free_rate = gr.Slider(
294
+ minimum=0.001, maximum=0.1, step=0.001, value=0.02,
295
+ label="Risk-Free Rate (continuous)"
296
+ )
297
+
298
+ volatility = gr.Slider(
299
+ minimum=0.01, maximum=0.5, step=0.01, value=0.03,
300
+ label="Volatility (σ)"
301
+ )
302
+
303
+ maturity = gr.Slider(
304
+ minimum=1, maximum=30, step=1, value=10,
305
+ label="Maturity (years)"
306
+ )
307
+
308
+ with gr.Row():
309
+ sum_assured = gr.Number(
310
+ value=500000, label="Sum Assured per Policy"
311
+ )
312
+ policy_count = gr.Number(
313
+ value=100, label="Number of Policies"
314
+ )
315
+
316
+ gr.Markdown("### Model Point Range")
317
+
318
+ with gr.Row():
319
+ min_premium = gr.Number(
320
+ value=300000, label="Min Premium per Policy"
321
+ )
322
+ max_premium = gr.Number(
323
+ value=500000, label="Max Premium per Policy"
324
+ )
325
+
326
+ num_points = gr.Slider(
327
+ minimum=3, maximum=20, step=1, value=9,
328
+ label="Number of Model Points"
329
+ )
330
+
331
+ calculate_btn = gr.Button("Run Analysis", variant="primary")
332
+
333
+ with gr.Column(scale=2):
334
+ gr.Markdown("### Results Summary")
335
+ results_table = gr.Dataframe(
336
+ headers=["Premium per Policy", "Initial Account Value", "Monte Carlo TVOG",
337
+ "Black-Scholes TVOG", "MC/BS Ratio", "Difference"],
338
+ label="TVOG Comparison Results"
339
+ )
340
+
341
+ with gr.Row():
342
+ tvog_plot = gr.Plot(label="TVOG Comparison Analysis")
343
+
344
+ with gr.Row():
345
+ paths_plot = gr.Plot(label="Sample Simulation Paths")
346
+
347
+ with gr.Row():
348
+ dist_plot = gr.Plot(label="Distribution Analysis")
349
+
350
+ with gr.Row():
351
+ conv_plot = gr.Plot(label="Monte Carlo Convergence")
352
+
353
+ # Event handlers
354
+ calculate_btn.click(
355
+ fn=update_analysis,
356
+ inputs=[scenarios, risk_free_rate, volatility, maturity,
357
+ sum_assured, policy_count, min_premium, max_premium, num_points],
358
+ outputs=[results_table, tvog_plot, paths_plot, dist_plot, conv_plot]
359
+ )
360
+
361
+ # Initial calculation
362
+ app.load(
363
+ fn=update_analysis,
364
+ inputs=[scenarios, risk_free_rate, volatility, maturity,
365
+ sum_assured, policy_count, min_premium, max_premium, num_points],
366
+ outputs=[results_table, tvog_plot, paths_plot, dist_plot, conv_plot]
367
+ )
368
+
369
+ return app
370
+
371
+ if __name__ == "__main__":
372
+ app = create_dashboard()
373
+ app.launch()