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

  1. Model Specification: We start by specifying the parameters of the Ornstein-Uhlenbeck model.

  2. Sample Generation: Using the specified parameters, we generate a large number of synthetic data samples from the OU process.

  3. Parameter Estimation: For each synthetic data sample, we apply a fitting algorithm to recover the model parameters.

  4. 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()