alidenewade's picture
Update app.py
780a070 verified
import gradio as gr
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.stats import norm, lognorm
import seaborn as sns
# Set matplotlib style for professional plots
plt.style.use('default')
sns.set_palette("husl")
class TVOGAnalysis:
def __init__(self):
self.reset_parameters()
def reset_parameters(self):
"""Reset to default parameters"""
self.scenarios = 10000
self.risk_free_rate = 0.02
self.volatility = 0.03
self.maturity = 10
self.sum_assured = 500000
self.policy_count = 100
def generate_random_numbers(self, scenarios, time_steps):
"""Generate standard normal random numbers"""
np.random.seed(42) # For reproducibility
return np.random.standard_normal((scenarios, time_steps))
def simulate_account_values(self, initial_av, scenarios, time_steps):
"""Simulate account value paths using geometric Brownian motion"""
dt = 1/12 # Monthly time steps
rand_nums = self.generate_random_numbers(scenarios, time_steps)
# Initialize account value matrix
av_paths = np.zeros((scenarios, time_steps + 1))
av_paths[:, 0] = initial_av
# Simulate paths
for t in range(time_steps):
drift = (self.risk_free_rate - 0.5 * self.volatility**2) * dt
diffusion = self.volatility * np.sqrt(dt) * rand_nums[:, t]
av_paths[:, t+1] = av_paths[:, t] * np.exp(drift + diffusion)
return av_paths
def calculate_gmab_payouts(self, av_paths):
"""Calculate GMAB payouts at maturity"""
final_av = av_paths[:, -1]
guarantee = self.sum_assured * self.policy_count
payouts = np.maximum(guarantee - final_av, 0)
# Present value of payouts
discount_factor = np.exp(-self.risk_free_rate * self.maturity)
pv_payouts = payouts * discount_factor
return pv_payouts, payouts
def black_scholes_put(self, S0, K, T, r, sigma):
"""Black-Scholes-Merton formula for European put option"""
d1 = (np.log(S0/K) + (r + 0.5*sigma**2)*T) / (sigma*np.sqrt(T))
d2 = d1 - sigma*np.sqrt(T)
put_price = K*np.exp(-r*T)*norm.cdf(-d2) - S0*norm.cdf(-d1)
return put_price
def create_dashboard():
tvog = TVOGAnalysis()
def update_analysis(scenarios, risk_free_rate, volatility, maturity,
sum_assured, policy_count, min_premium, max_premium, num_points):
# Update parameters
tvog.scenarios = int(scenarios)
tvog.risk_free_rate = risk_free_rate
tvog.volatility = volatility
tvog.maturity = maturity
tvog.sum_assured = sum_assured
tvog.policy_count = policy_count
# Create model points with varying initial account values
premiums = np.linspace(min_premium, max_premium, int(num_points))
initial_avs = premiums * policy_count
monte_carlo_results = []
black_scholes_results = []
time_steps = int(maturity * 12) # Monthly steps
for initial_av in initial_avs:
# Monte Carlo simulation
av_paths = tvog.simulate_account_values(initial_av, tvog.scenarios, time_steps)
pv_payouts, _ = tvog.calculate_gmab_payouts(av_paths)
mc_tvog = np.mean(pv_payouts)
monte_carlo_results.append(mc_tvog)
# Black-Scholes-Merton
guarantee = sum_assured * policy_count
bs_tvog = tvog.black_scholes_put(initial_av, guarantee, maturity,
risk_free_rate, volatility)
black_scholes_results.append(bs_tvog)
# Create results DataFrame
results_df = pd.DataFrame({
'Model_Point': range(1, len(premiums) + 1),
'Premium_per_Policy': premiums,
'Initial_Account_Value': initial_avs,
'Monte_Carlo_TVOG': monte_carlo_results,
'Black_Scholes_TVOG': black_scholes_results,
'Ratio_MC_BS': np.array(monte_carlo_results) / np.array(black_scholes_results),
'Difference': np.array(monte_carlo_results) - np.array(black_scholes_results)
})
# Create plots
fig1 = create_tvog_comparison_plot(results_df)
fig2 = create_sample_paths_plot(tvog, initial_avs[len(initial_avs)//2], time_steps)
fig3 = create_distribution_plots(tvog, initial_avs[len(initial_avs)//2], time_steps)
fig4 = create_convergence_plot(tvog, initial_avs[len(initial_avs)//2], time_steps)
return results_df, fig1, fig2, fig3, fig4
def create_tvog_comparison_plot(results_df):
"""Create TVOG comparison plot"""
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
# TVOG Comparison
ax1.scatter(results_df['Initial_Account_Value'], results_df['Monte_Carlo_TVOG'],
s=50, alpha=0.7, label='Monte Carlo', color='blue')
ax1.scatter(results_df['Initial_Account_Value'], results_df['Black_Scholes_TVOG'],
s=50, alpha=0.7, label='Black-Scholes-Merton', color='red')
ax1.set_xlabel('Initial Account Value')
ax1.set_ylabel('TVOG Value')
ax1.set_title('TVOG: Monte Carlo vs Black-Scholes-Merton')
ax1.legend()
ax1.grid(True, alpha=0.3)
# Ratio Plot
ax2.plot(results_df['Initial_Account_Value'], results_df['Ratio_MC_BS'],
'o-', color='green', markersize=6)
ax2.axhline(y=1, color='red', linestyle='--', alpha=0.7)
ax2.set_xlabel('Initial Account Value')
ax2.set_ylabel('Monte Carlo / Black-Scholes Ratio')
ax2.set_title('Convergence Ratio (MC/BS)')
ax2.grid(True, alpha=0.3)
plt.tight_layout()
return fig
def create_sample_paths_plot(tvog, initial_av, time_steps):
"""Create sample simulation paths plot"""
sample_scenarios = min(100, tvog.scenarios)
av_paths = tvog.simulate_account_values(initial_av, sample_scenarios, time_steps)
fig, ax = plt.subplots(1, 1, figsize=(12, 6))
time_axis = np.arange(time_steps + 1) / 12 # Convert to years
for i in range(sample_scenarios):
ax.plot(time_axis, av_paths[i, :], alpha=0.3, linewidth=0.8)
# Add mean path
mean_path = np.mean(av_paths, axis=0)
ax.plot(time_axis, mean_path, color='red', linewidth=3, label='Mean Path')
# Add guarantee line
guarantee = tvog.sum_assured * tvog.policy_count
ax.axhline(y=guarantee, color='black', linestyle='--', linewidth=2,
label=f'Guarantee Level ({guarantee:,.0f})')
ax.set_xlabel('Time (Years)')
ax.set_ylabel('Account Value')
ax.set_title(f'Sample Account Value Simulation Paths (n={sample_scenarios})')
ax.legend()
ax.grid(True, alpha=0.3)
return fig
def create_distribution_plots(tvog, initial_av, time_steps):
"""Create distribution analysis plots"""
av_paths = tvog.simulate_account_values(initial_av, tvog.scenarios, time_steps)
final_av = av_paths[:, -1]
# Present value of final account values
pv_final_av = final_av * np.exp(-tvog.risk_free_rate * tvog.maturity)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
# Histogram of final account values
ax1.hist(pv_final_av, bins=50, density=True, alpha=0.7, color='skyblue')
# Theoretical lognormal distribution
S0 = initial_av
sigma = tvog.volatility
T = tvog.maturity
x_range = np.linspace(pv_final_av.min(), pv_final_av.max(), 1000)
theoretical_pdf = lognorm.pdf(x_range, sigma * np.sqrt(T), scale=S0)
ax1.plot(x_range, theoretical_pdf, 'r-', linewidth=2, label='Theoretical Lognormal')
ax1.axvline(x=S0, color='green', linestyle='--', label=f'Initial Value: {S0:,.0f}')
ax1.axvline(x=np.mean(pv_final_av), color='orange', linestyle='--',
label=f'Simulated Mean: {np.mean(pv_final_av):,.0f}')
ax1.set_xlabel('Present Value of Final Account Value')
ax1.set_ylabel('Density')
ax1.set_title('Distribution of Final Account Values')
ax1.legend()
ax1.grid(True, alpha=0.3)
# GMAB Payouts
pv_payouts, _ = tvog.calculate_gmab_payouts(av_paths)
non_zero_payouts = pv_payouts[pv_payouts > 0]
ax2.hist(pv_payouts, bins=50, alpha=0.7, color='lightcoral')
ax2.set_xlabel('GMAB Payout (Present Value)')
ax2.set_ylabel('Frequency')
ax2.set_title(f'GMAB Payout Distribution\n({len(non_zero_payouts)} non-zero payouts)')
ax2.grid(True, alpha=0.3)
# Add statistics text
stats_text = f'Mean Payout: {np.mean(pv_payouts):,.0f}\n'
stats_text += f'Max Payout: {np.max(pv_payouts):,.0f}\n'
stats_text += f'Payout Probability: {len(non_zero_payouts)/len(pv_payouts):.1%}'
ax2.text(0.95, 0.95, stats_text, transform=ax2.transAxes,
verticalalignment='top', horizontalalignment='right',
bbox=dict(boxstyle='round', facecolor='white', alpha=0.8))
plt.tight_layout()
return fig
def create_convergence_plot(tvog, initial_av, time_steps):
"""Create convergence analysis plot"""
# Test different numbers of scenarios
scenario_counts = [100, 500, 1000, 2000, 5000, 10000]
if tvog.scenarios not in scenario_counts:
scenario_counts.append(tvog.scenarios)
scenario_counts.sort()
mc_results = []
guarantee = tvog.sum_assured * tvog.policy_count
bs_result = tvog.black_scholes_put(initial_av, guarantee, tvog.maturity,
tvog.risk_free_rate, tvog.volatility)
np.random.seed(42) # For reproducible convergence
for n_scenarios in scenario_counts:
av_paths = tvog.simulate_account_values(initial_av, n_scenarios, time_steps)
pv_payouts, _ = tvog.calculate_gmab_payouts(av_paths)
mc_tvog = np.mean(pv_payouts)
mc_results.append(mc_tvog)
fig, ax = plt.subplots(1, 1, figsize=(10, 6))
ax.plot(scenario_counts, mc_results, 'bo-', markersize=8, linewidth=2,
label='Monte Carlo Results')
ax.axhline(y=bs_result, color='red', linestyle='--', linewidth=2,
label=f'Black-Scholes Result: {bs_result:.0f}')
ax.set_xlabel('Number of Scenarios')
ax.set_ylabel('TVOG Value')
ax.set_title('Monte Carlo Convergence Analysis')
ax.legend()
ax.grid(True, alpha=0.3)
ax.set_xscale('log')
# Add convergence statistics
final_error = abs(mc_results[-1] - bs_result) / bs_result * 100
ax.text(0.02, 0.98, f'Final Error: {final_error:.2f}%',
transform=ax.transAxes, verticalalignment='top',
bbox=dict(boxstyle='round', facecolor='white', alpha=0.8))
return fig
# Create Gradio interface
with gr.Blocks(title="TVOG Analysis Dashboard") as app:
gr.Markdown("""
# Time Value of Options and Guarantees (TVOG) Analysis Dashboard
This dashboard compares Monte Carlo simulation results with Black-Scholes-Merton analytical solutions
for Variable Annuity products with Guaranteed Minimum Accumulation Benefits (GMAB).
**Target Users:** Actuaries, Finance Professionals, Economists, and Academics
""")
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### Model Parameters")
scenarios = gr.Slider(
minimum=1000, maximum=50000, step=1000, value=10000,
label="Number of Monte Carlo Scenarios"
)
risk_free_rate = gr.Slider(
minimum=0.001, maximum=0.1, step=0.001, value=0.02,
label="Risk-Free Rate (continuous)"
)
volatility = gr.Slider(
minimum=0.01, maximum=0.5, step=0.01, value=0.03,
label="Volatility (σ)"
)
maturity = gr.Slider(
minimum=1, maximum=30, step=1, value=10,
label="Maturity (years)"
)
with gr.Row():
sum_assured = gr.Number(
value=500000, label="Sum Assured per Policy"
)
policy_count = gr.Number(
value=100, label="Number of Policies"
)
gr.Markdown("### Model Point Range")
with gr.Row():
min_premium = gr.Number(
value=300000, label="Min Premium per Policy"
)
max_premium = gr.Number(
value=500000, label="Max Premium per Policy"
)
num_points = gr.Slider(
minimum=3, maximum=20, step=1, value=9,
label="Number of Model Points"
)
calculate_btn = gr.Button("Run Analysis", variant="primary")
with gr.Column(scale=2):
gr.Markdown("### Results Summary")
results_table = gr.Dataframe(
headers=["Model Point", "Premium per Policy", "Initial Account Value", "Monte Carlo TVOG",
"Black-Scholes TVOG", "MC/BS Ratio", "Difference"],
label="TVOG Comparison Results"
)
with gr.Tabs():
with gr.Tab("TVOG Comparison"):
tvog_plot = gr.Plot(label="Monte Carlo vs Black-Scholes Analysis")
with gr.Tab("Simulation Paths"):
paths_plot = gr.Plot(label="Sample Account Value Trajectories")
with gr.Tab("Distribution Analysis"):
dist_plot = gr.Plot(label="Final Values & Payout Distributions")
with gr.Tab("Convergence Analysis"):
conv_plot = gr.Plot(label="Monte Carlo Convergence to Analytical Solution")
# Event handlers
calculate_btn.click(
fn=update_analysis,
inputs=[scenarios, risk_free_rate, volatility, maturity,
sum_assured, policy_count, min_premium, max_premium, num_points],
outputs=[results_table, tvog_plot, paths_plot, dist_plot, conv_plot]
)
# Initial calculation
app.load(
fn=update_analysis,
inputs=[scenarios, risk_free_rate, volatility, maturity,
sum_assured, policy_count, min_premium, max_premium, num_points],
outputs=[results_table, tvog_plot, paths_plot, dist_plot, conv_plot]
)
return app
if __name__ == "__main__":
app = create_dashboard()
app.launch()