alidenewade commited on
Commit
b024610
·
verified ·
1 Parent(s): e573a64

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +368 -0
app.py ADDED
@@ -0,0 +1,368 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ import pandas as pd
5
+ from scipy.stats import norm
6
+ import warnings
7
+ warnings.filterwarnings('ignore')
8
+
9
+ class HullWhiteModel:
10
+ """Hull-White Interest Rate Model Implementation"""
11
+
12
+ def __init__(self, scen_size=1000, time_len=30, step_size=360, a=0.1, sigma=0.1, r0=0.05):
13
+ self.scen_size = scen_size
14
+ self.time_len = time_len
15
+ self.step_size = step_size
16
+ self.a = a
17
+ self.sigma = sigma
18
+ self.r0 = r0
19
+ self.dt = time_len / step_size
20
+
21
+ # Generate time grid
22
+ self.t = np.linspace(0, time_len, step_size + 1)
23
+
24
+ # Market forward rates (constant for simplicity)
25
+ self.mkt_fwd = np.full(step_size + 1, r0)
26
+
27
+ # Market zero-coupon bond prices
28
+ self.mkt_zcb = np.exp(-self.mkt_fwd * self.t)
29
+
30
+ # Alpha function
31
+ self.alpha = self._calculate_alpha()
32
+
33
+ # Generate random numbers
34
+ np.random.seed(42) # For reproducibility
35
+ self.random_normal = np.random.standard_normal((scen_size, step_size))
36
+
37
+ def _calculate_alpha(self):
38
+ """Calculate alpha(t) = f^M(0,t) + sigma^2/(2*a^2) * (1-exp(-a*t))^2"""
39
+ return self.mkt_fwd + (self.sigma**2 / (2 * self.a**2)) * (1 - np.exp(-self.a * self.t))**2
40
+
41
+ def simulate_short_rates(self):
42
+ """Simulate short rate paths using Hull-White model"""
43
+ r_paths = np.zeros((self.scen_size, self.step_size + 1))
44
+ r_paths[:, 0] = self.r0
45
+
46
+ for i in range(1, self.step_size + 1):
47
+ # Calculate conditional mean
48
+ exp_factor = np.exp(-self.a * self.dt)
49
+ mean_r = r_paths[:, i-1] * exp_factor + self.alpha[i] - self.alpha[i-1] * exp_factor
50
+
51
+ # Calculate conditional variance
52
+ var_r = (self.sigma**2 / (2 * self.a)) * (1 - np.exp(-2 * self.a * self.dt))
53
+ std_r = np.sqrt(var_r)
54
+
55
+ # Generate next step
56
+ r_paths[:, i] = mean_r + std_r * self.random_normal[:, i-1]
57
+
58
+ return r_paths
59
+
60
+ def calculate_discount_factors(self, r_paths):
61
+ """Calculate discount factors from short rate paths"""
62
+ # Accumulate short rates (discrete approximation of integral)
63
+ accum_rates = np.zeros_like(r_paths)
64
+ for i in range(1, self.step_size + 1):
65
+ accum_rates[:, i] = accum_rates[:, i-1] + r_paths[:, i-1] * self.dt
66
+
67
+ # Calculate discount factors
68
+ discount_factors = np.exp(-accum_rates)
69
+ return discount_factors
70
+
71
+ def theoretical_mean_short_rate(self):
72
+ """Calculate theoretical mean of short rates E[r(t)|F_0]"""
73
+ return self.r0 * np.exp(-self.a * self.t) + self.alpha - self.alpha[0] * np.exp(-self.a * self.t)
74
+
75
+ def theoretical_var_short_rate(self):
76
+ """Calculate theoretical variance of short rates Var[r(t)|F_0]"""
77
+ return (self.sigma**2 / (2 * self.a)) * (1 - np.exp(-2 * self.a * self.t))
78
+
79
+ def create_short_rate_plot(scen_size, time_len, step_size, a, sigma, r0, num_paths):
80
+ """Create short rate simulation plot"""
81
+ model = HullWhiteModel(scen_size, time_len, step_size, a, sigma, r0)
82
+ r_paths = model.simulate_short_rates()
83
+
84
+ fig, ax = plt.subplots(figsize=(12, 8))
85
+
86
+ # Plot first num_paths scenarios
87
+ for i in range(min(num_paths, scen_size)):
88
+ ax.plot(model.t, r_paths[i], alpha=0.7, linewidth=1)
89
+
90
+ ax.set_xlabel('Time (years)')
91
+ ax.set_ylabel('Short Rate')
92
+ ax.set_title(f'Hull-White Short Rate Simulation ({num_paths} paths)\na={a}, σ={sigma}, scenarios={scen_size}')
93
+ ax.grid(True, alpha=0.3)
94
+
95
+ return fig
96
+
97
+ def create_convergence_plot(scen_size, time_len, step_size, a, sigma, r0):
98
+ """Create mean convergence plot"""
99
+ model = HullWhiteModel(scen_size, time_len, step_size, a, sigma, r0)
100
+ r_paths = model.simulate_short_rates()
101
+
102
+ # Calculate simulated means and theoretical expectations
103
+ simulated_mean = np.mean(r_paths, axis=0)
104
+ theoretical_mean = model.theoretical_mean_short_rate()
105
+
106
+ fig, ax = plt.subplots(figsize=(12, 8))
107
+
108
+ ax.plot(model.t, theoretical_mean, 'b-', linewidth=2, label='Theoretical E[r(t)]')
109
+ ax.plot(model.t, simulated_mean, 'r--', linewidth=2, label='Simulated Mean')
110
+
111
+ ax.set_xlabel('Time (years)')
112
+ ax.set_ylabel('Short Rate')
113
+ ax.set_title(f'Mean Convergence Analysis\na={a}, σ={sigma}, scenarios={scen_size}')
114
+ ax.legend()
115
+ ax.grid(True, alpha=0.3)
116
+
117
+ return fig
118
+
119
+ def create_variance_plot(scen_size, time_len, step_size, a, sigma, r0):
120
+ """Create variance convergence plot"""
121
+ model = HullWhiteModel(scen_size, time_len, step_size, a, sigma, r0)
122
+ r_paths = model.simulate_short_rates()
123
+
124
+ # Calculate simulated variance and theoretical variance
125
+ simulated_var = np.var(r_paths, axis=0)
126
+ theoretical_var = model.theoretical_var_short_rate()
127
+
128
+ fig, ax = plt.subplots(figsize=(12, 8))
129
+
130
+ ax.plot(model.t, theoretical_var, 'b-', linewidth=2, label='Theoretical Var[r(t)]')
131
+ ax.plot(model.t, simulated_var, 'r--', linewidth=2, label='Simulated Variance')
132
+
133
+ ax.set_xlabel('Time (years)')
134
+ ax.set_ylabel('Variance')
135
+ ax.set_title(f'Variance Convergence Analysis\na={a}, σ={sigma}, scenarios={scen_size}')
136
+ ax.legend()
137
+ ax.grid(True, alpha=0.3)
138
+
139
+ return fig
140
+
141
+ def create_discount_factor_plot(scen_size, time_len, step_size, a, sigma, r0):
142
+ """Create discount factor convergence plot"""
143
+ model = HullWhiteModel(scen_size, time_len, step_size, a, sigma, r0)
144
+ r_paths = model.simulate_short_rates()
145
+ discount_factors = model.calculate_discount_factors(r_paths)
146
+
147
+ # Calculate mean discount factors
148
+ mean_discount = np.mean(discount_factors, axis=0)
149
+
150
+ fig, ax = plt.subplots(figsize=(12, 8))
151
+
152
+ ax.plot(model.t, model.mkt_zcb, 'b-', linewidth=2, label='Market Zero-Coupon Bonds')
153
+ ax.plot(model.t, mean_discount, 'r--', linewidth=2, label='Simulated Mean Discount Factor')
154
+
155
+ ax.set_xlabel('Time (years)')
156
+ ax.set_ylabel('Discount Factor')
157
+ ax.set_title(f'Discount Factor Convergence\na={a}, σ={sigma}, σ/a={sigma/a:.2f}, scenarios={scen_size}')
158
+ ax.legend()
159
+ ax.grid(True, alpha=0.3)
160
+
161
+ return fig
162
+
163
+ def create_parameter_sensitivity_plot(base_scen_size, time_len, step_size, base_a, base_sigma, r0, vary_param):
164
+ """Create parameter sensitivity analysis"""
165
+ fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(15, 12))
166
+ fig.suptitle(f'Parameter Sensitivity Analysis - Varying {vary_param}', fontsize=16)
167
+
168
+ if vary_param == "sigma":
169
+ param_values = [0.05, 0.075, 0.1, 0.125]
170
+ base_param = base_a
171
+ param_label = "σ"
172
+ base_label = f"a={base_a}"
173
+ else: # vary a
174
+ param_values = [0.05, 0.1, 0.15, 0.2]
175
+ base_param = base_sigma
176
+ param_label = "a"
177
+ base_label = f"σ={base_sigma}"
178
+
179
+ axes = [ax1, ax2, ax3, ax4]
180
+
181
+ for i, param_val in enumerate(param_values):
182
+ if vary_param == "sigma":
183
+ model = HullWhiteModel(base_scen_size, time_len, step_size, base_a, param_val, r0)
184
+ ratio = param_val / base_a
185
+ else:
186
+ model = HullWhiteModel(base_scen_size, time_len, step_size, param_val, base_sigma, r0)
187
+ ratio = base_sigma / param_val
188
+
189
+ r_paths = model.simulate_short_rates()
190
+ discount_factors = model.calculate_discount_factors(r_paths)
191
+ mean_discount = np.mean(discount_factors, axis=0)
192
+
193
+ axes[i].plot(model.t, model.mkt_zcb, 'b-', linewidth=2, label='Market ZCB')
194
+ axes[i].plot(model.t, mean_discount, 'r--', linewidth=2, label='Simulated Mean')
195
+ axes[i].set_title(f'{param_label}={param_val}, σ/a={ratio:.2f}')
196
+ axes[i].grid(True, alpha=0.3)
197
+ axes[i].legend()
198
+
199
+ return fig
200
+
201
+ def generate_statistics_table(scen_size, time_len, step_size, a, sigma, r0):
202
+ """Generate summary statistics table"""
203
+ model = HullWhiteModel(scen_size, time_len, step_size, a, sigma, r0)
204
+ r_paths = model.simulate_short_rates()
205
+
206
+ # Calculate statistics at key time points
207
+ time_points = [0, int(step_size*0.25), int(step_size*0.5), int(step_size*0.75), step_size]
208
+ times = [model.t[i] for i in time_points]
209
+
210
+ stats_data = []
211
+ for i, t_idx in enumerate(time_points):
212
+ rates_at_t = r_paths[:, t_idx]
213
+ theoretical_mean = model.theoretical_mean_short_rate()[t_idx]
214
+ theoretical_var = model.theoretical_var_short_rate()[t_idx]
215
+
216
+ stats_data.append({
217
+ 'Time': f'{times[i]:.1f}',
218
+ 'Simulated Mean': f'{np.mean(rates_at_t):.4f}',
219
+ 'Theoretical Mean': f'{theoretical_mean:.4f}',
220
+ 'Mean Error': f'{abs(np.mean(rates_at_t) - theoretical_mean):.4f}',
221
+ 'Simulated Std': f'{np.std(rates_at_t):.4f}',
222
+ 'Theoretical Std': f'{np.sqrt(theoretical_var):.4f}',
223
+ 'Std Error': f'{abs(np.std(rates_at_t) - np.sqrt(theoretical_var)):.4f}'
224
+ })
225
+
226
+ return pd.DataFrame(stats_data)
227
+
228
+ # Create Gradio interface
229
+ with gr.Blocks(title="Hull-White Interest Rate Model Dashboard") as demo:
230
+ gr.Markdown("""
231
+ # 📊 Hull-White Interest Rate Model Dashboard
232
+
233
+ This interactive dashboard allows actuaries and financial professionals to explore the Hull-White short rate model:
234
+
235
+ **dr(t) = (θ(t) - ar(t))dt + σdW**
236
+
237
+ Adjust the parameters below to see how they affect the interest rate simulations and convergence properties.
238
+ """)
239
+
240
+ with gr.Row():
241
+ with gr.Column(scale=1):
242
+ gr.Markdown("### Model Parameters")
243
+ scen_size = gr.Slider(100, 10000, value=1000, step=100, label="Number of Scenarios")
244
+ time_len = gr.Slider(5, 50, value=30, step=5, label="Time Horizon (years)")
245
+ step_size = gr.Slider(100, 500, value=360, step=60, label="Number of Time Steps")
246
+ a = gr.Slider(0.01, 0.5, value=0.1, step=0.01, label="Mean Reversion Speed (a)")
247
+ sigma = gr.Slider(0.01, 0.3, value=0.1, step=0.01, label="Volatility (σ)")
248
+ r0 = gr.Slider(0.01, 0.15, value=0.05, step=0.01, label="Initial Rate (r₀)")
249
+
250
+ gr.Markdown("### Display Options")
251
+ num_paths = gr.Slider(1, 50, value=10, step=1, label="Number of Paths to Display")
252
+
253
+ with gr.Row():
254
+ vary_param = gr.Radio(["sigma", "a"], value="sigma", label="Parameter Sensitivity Analysis")
255
+
256
+ with gr.Column(scale=2):
257
+ with gr.Tabs():
258
+ with gr.TabItem("Short Rate Paths"):
259
+ short_rate_plot = gr.Plot(label="Short Rate Simulation")
260
+
261
+ with gr.TabItem("Mean Convergence"):
262
+ convergence_plot = gr.Plot(label="Mean Convergence Analysis")
263
+
264
+ with gr.TabItem("Variance Convergence"):
265
+ variance_plot = gr.Plot(label="Variance Convergence Analysis")
266
+
267
+ with gr.TabItem("Discount Factors"):
268
+ discount_plot = gr.Plot(label="Discount Factor Analysis")
269
+
270
+ with gr.TabItem("Parameter Sensitivity"):
271
+ sensitivity_plot = gr.Plot(label="Parameter Sensitivity Analysis")
272
+
273
+ with gr.TabItem("Statistics"):
274
+ stats_table = gr.Dataframe(label="Summary Statistics")
275
+
276
+ gr.Markdown("""
277
+ ### About the Hull-White Model
278
+
279
+ - **Mean Reversion Speed (a)**: Controls how quickly rates revert to the long-term mean
280
+ - **Volatility (σ)**: Controls the randomness in rate movements
281
+ - **σ/a Ratio**: Key parameter for convergence - ratios > 1 show poor convergence
282
+ - **Scenarios**: More scenarios improve Monte Carlo convergence but increase computation time
283
+
284
+ **Model Features:**
285
+ - Gaussian short rate process
286
+ - Analytical formulas for conditional moments
287
+ - Market-consistent calibration capability
288
+ - Monte Carlo simulation for complex derivatives
289
+ """)
290
+
291
+ # Update all plots when parameters change
292
+ inputs = [scen_size, time_len, step_size, a, sigma, r0]
293
+
294
+ # Connect inputs to outputs
295
+ for inp in inputs + [num_paths]:
296
+ inp.change(
297
+ fn=create_short_rate_plot,
298
+ inputs=inputs + [num_paths],
299
+ outputs=short_rate_plot
300
+ )
301
+
302
+ for inp in inputs:
303
+ inp.change(
304
+ fn=create_convergence_plot,
305
+ inputs=inputs,
306
+ outputs=convergence_plot
307
+ )
308
+
309
+ inp.change(
310
+ fn=create_variance_plot,
311
+ inputs=inputs,
312
+ outputs=variance_plot
313
+ )
314
+
315
+ inp.change(
316
+ fn=create_discount_factor_plot,
317
+ inputs=inputs,
318
+ outputs=discount_plot
319
+ )
320
+
321
+ inp.change(
322
+ fn=generate_statistics_table,
323
+ inputs=inputs,
324
+ outputs=stats_table
325
+ )
326
+
327
+ # Parameter sensitivity updates
328
+ for inp in inputs[:-1] + [vary_param]: # Exclude r0 from base params for sensitivity
329
+ inp.change(
330
+ fn=create_parameter_sensitivity_plot,
331
+ inputs=[scen_size, time_len, step_size, a, sigma, r0, vary_param],
332
+ outputs=sensitivity_plot
333
+ )
334
+
335
+ # Initialize plots on load
336
+ demo.load(
337
+ fn=create_short_rate_plot,
338
+ inputs=inputs + [num_paths],
339
+ outputs=short_rate_plot
340
+ )
341
+ demo.load(
342
+ fn=create_convergence_plot,
343
+ inputs=inputs,
344
+ outputs=convergence_plot
345
+ )
346
+ demo.load(
347
+ fn=create_variance_plot,
348
+ inputs=inputs,
349
+ outputs=variance_plot
350
+ )
351
+ demo.load(
352
+ fn=create_discount_factor_plot,
353
+ inputs=inputs,
354
+ outputs=discount_plot
355
+ )
356
+ demo.load(
357
+ fn=create_parameter_sensitivity_plot,
358
+ inputs=[scen_size, time_len, step_size, a, sigma, r0, vary_param],
359
+ outputs=sensitivity_plot
360
+ )
361
+ demo.load(
362
+ fn=generate_statistics_table,
363
+ inputs=inputs,
364
+ outputs=stats_table
365
+ )
366
+
367
+ if __name__ == "__main__":
368
+ demo.launch()