Vasicek One Factor Model
This post provides a simple implementation of Vasicek’s model for depicting the movement of interest rates in a simplified environment. Further theoretical foundation will be updated in this post later on.
A Direct and Plain Implementation
import numpy as np
def vasicek_model(r0, K, theta, sigma, T=1., N=10, seed=28):
np.random.seed(seed)
dt = T/float(N)
rates = [r0]
for i in range(N):
dr = K*(theta-rates[-1])*dt + sigma*np.random.normal()
rates.append(rates[-1] + dr)
return range(N+1), rates
if __name__ == "__main__":
x, y = vasicek_model(0.01875, 0.20, 0.01, 0.012, 10., 200)
import matplotlib.pyplot as plt
plt.plot(x,y)
plt.show()A Detailed Implmentation
import numpy as np
import pandas as pd
from typing import Any
import matplotlib.pyplot as plt
def Brownian_process(T = 1, dt = 0.001, rho = None):
N = int(T / dt)
if not rho:
W = np.zeros(N) # preallocate the output array holding the sample paths with the inital point
for i in range(1, N):
W[i] = W[i-1] + np.random.normal(scale = np.sqrt(dt)) # Increment a random normal at each step
return W
if rho is not None: # if rho is defined, that means that the output will be a 2-dimensional Brownian motion
W_1 = np.zeros(N)
W_2 = np.zeros(N)
for i in range(1, N): # Simulate two Brownian motions and construct a joint bivariate Brownian motion with parameter rho
Z_1 = np.random.normal(scale = np.sqrt(dt))
Z_2 = np.random.normal(scale = np.sqrt(dt))
Z_3 = rho * Z_1 + np.sqrt(1- rho**2) * Z_2
W_1[i] = W_1[i-1] + Z_1 # First Brownian motion
W_2[i] = W_2[i-1] + Z_3 # Second Brownian motion
return [W_1, W_2]
# Example
# The economist wants to generate discrete sample paths of two Brownian motions with a correlation coefficient of 0.6.
# The Brownian motions needs to start at 0 at time 0 and ends at time T = 3 with an increment of 0.5.
x = range(0,6)
y = Brownian_process(3, 0.5, 0.6)
plt.plot(x, y[0], label = 'Path 1')
plt.plot(x, y[1], label = 'Path 2')
plt.xlabel('Time step')
plt.title('Two Brownian Motion Paths with Rho equal to 0.6')
plt.legend()
plt.show()def Vasicek_One_Factor(r0: float = 0.1, a: float = 1.0, b: float = 0.1, sigma: float = 0.2, T: int = 52, dt = 0.1) -> pd.DataFrame:
"""
:param r0: float, starting interest rate of the Vasicek process
:param a: float, speed of reversion which is a parameter that characterizes the velocity at which such trajectories will regroup around b in time
:param b: float, long term mean level. All future trajectories of r will evolve around mean level b in the long run
:param sigma: float, instantaneous volatility measures instant by instant the amplitude of randomness entering the system
:param T: integer, end modelling time. From 0 to T the time series runs.
:param dt: float, increment of time that the process runs on. Ex. dt = 0.1 then the time series is 0, 0.1, 0.2,...
:return: N x 2 pd.DataFrame where index is modeling time and values are a realisation of the underlying's interest rates
"""
N = int(T / dt) # number of subintervals of length 1/dt between 0 and max modeling time T
time, delta_t = np.linspace(0, T, num=N, retstep=True)
brownian_process = Brownian_process(T, dt)
r = np.ones(N) * r0
for t in range(1, N):
r[t] = r[t - 1] + a * (b - r[t - 1]) * dt + sigma * (brownian_process[t] - brownian_process[t - 1])
dict = {'Time': time, 'Interest Rate': r}
interest_rate_simulation = pd.DataFrame.from_dict(data=dict)
interest_rate_simulation.set_index('Time', inplace=True)
return interest_rate_simulation
Vasicek_One_Factor(0.1, 0.8, 0.08, 0.2, 10, 0.5)## Interest Rate
## Time
## 0.000000 0.100000
## 0.526316 0.206994
## 1.052632 0.167578
## 1.578947 0.158354
## 2.105263 0.091284
## 2.631579 0.078209
## 3.157895 -0.026067
## 3.684211 -0.078579
## 4.210526 -0.038257
## 4.736842 0.169093
## 5.263158 -0.001400
## 5.789474 0.195144
## 6.315789 0.102006
## 6.842105 0.053728
## 7.368421 0.177737
## 7.894737 0.262885
## 8.421053 0.126673
## 8.947368 -0.045028
## 9.473684 -0.349509
## 10.000000 -0.222734