Parametric Bootstrapping with the Ornstein Uhlenbeck Model
Introduction
In this notebook, we illustrate the method of parametric bootstrapping using the Ornstein-Uhlenbeck (OU) model. The Ornstein-Uhlenbeck process is a stochastic process often used to model mean-reverting behavior in various fields such as finance, physics, and biology.
Overview
Parametric bootstrapping is a powerful technique used to assess the uncertainty and potential bias in parameter estimates derived from a given generative model. The method involves generating synthetic data samples from the model, estimating the parameters from these samples, and analyzing the distribution of the estimated parameters.
How It Works
Model Specification: We start by specifying the parameters of the Ornstein-Uhlenbeck model.
Sample Generation: Using the specified parameters, we generate a large number of synthetic data samples from the OU process.
Parameter Estimation: For each synthetic data sample, we apply a fitting algorithm to recover the model parameters.
Distribution Analysis: We analyze the distribution of the recovered parameter estimates to understand the uncertainty and bias in our estimation process.
This notebook demonstrates each of these steps in detail, providing a clear example of how parametric bootstrapping can be used to evaluate the reliability of parameter estimates in a stochastic process model. By the end of this notebook, you will have a thorough understanding of the parametric bootstrapping method and how it can be applied to the Ornstein-Uhlenbeck model.
In [19]:
import plotly
import plotly.express as px
import plotly.figure_factory as ff
import plotly.graph_objects as go
plotly.offline.init_notebook_mode()
import pandas as pd
from quantfinlib.sim import OrnsteinUhlenbeck
Model Specification and Sample Generation
Below are the true parameters of the Ornstein Uhlenbeck process and simluation settings. We are going to generate a 1.000 random paths of length 50.
In [20]:
# True parmeters
params = {
'mean': 4,
'mrr': 10,
'vol': 1
}
num_paths = 1000
num_steps = 50
dt = 1 / num_steps
Next we are going to generate random path. We’ll plot the first 10.
In [21]:
# Generate a random path
model = OrnsteinUhlenbeck(**params)
paths = model.path_sample(x0=1, dt=dt, num_steps=num_steps, num_paths=num_paths)
fig = px.line(paths[:, :10], width=600, height=400)
fig.show()
Parameter Estimation
For each generated path we estimate the model parameters.
In [22]:
# Dict where we collect param estimations
est = {'mean': [], 'mrr': [], 'vol': []}
for i in range(num_paths):
# Fit a new model to the path
fitted_model = OrnsteinUhlenbeck()
fitted_model.fit(paths[:, i], dt=dt)
# save the estimated model parameters
est['mean'].append(fitted_model.mean)
est['mrr'].append(fitted_model.mrr)
est['vol'].append(fitted_model.vol[0,0])
# Convert collected fitted values in a pandas DataFrame
est = pd.DataFrame(est)
est.index.name = 'path_id'
est
Out[22]:
| mean | mrr | vol | |
|---|---|---|---|
| path_id | |||
| 0 | 4.079258 | 14.796889 | 1.308052 |
| 1 | 4.046144 | 8.317787 | 0.992475 |
| 2 | 3.979842 | 10.625892 | 1.164236 |
| 3 | 4.099115 | 9.542345 | 1.237511 |
| 4 | 4.099805 | 10.246823 | 0.930701 |
| ... | ... | ... | ... |
| 995 | 3.946206 | 11.307757 | 1.095761 |
| 996 | 3.872201 | 8.205354 | 0.911075 |
| 997 | 3.935176 | 9.088530 | 0.978699 |
| 998 | 3.889375 | 13.752017 | 1.039912 |
| 999 | 3.929120 | 9.477551 | 0.990253 |
1000 rows × 3 columns
Distribution Analysis
Next we plot 2D scatter-plots of all pair combinations of the Ornstein Uhlenbeck parameters estimates. The red star indicates the ‘true’ parameters.
In [23]:
# Make all the pair-plots
for i in range(3):
x = est.iloc[:, i]
x_name = est.columns[i]
for j in range(i+1, 3):
y = est.iloc[:, j]
y_name = est.columns[j]
# Density plot
fig = ff.create_2d_density(x, y, point_size=3, width=400, height=400)
# True value red start indicator
fig.add_trace(go.Scatter(
x=[params[x_name]],
y=[params[y_name]],
mode='markers',
marker=dict(symbol='star', size=10, color='red')
))
# Update title and axis labels
fig.update_layout(
title=f'Bootstrap: {x_name} vs {y_name}',
xaxis_title=x_name,
yaxis_title=y_name,
margin=dict(l=0, r=20, t=40, b=20) # Adjust margins to reduce whitespace
)
fig.show()