Coding towards CFA (23) – Parallel Monte Carlo Simulations with DolphinDB
Linxiao Ma
Financial Data Architect | Hands-on Engineer | PhD | CFA Candidate | Distributed Database Expert | DolphinDB UK Rep. | Tech Blogger | Insane Coder
*More articles can be found from my blog site - https://dataninjago.com
The Monte Carlo simulation method for pricing fixed-income instruments is introduced in CFA Level 2, Fixed Income, Module 2, Section 7. In this blog post, I will walk through the coding of the steps outlined in the CFA curriculum. The Monte Carlo approach can be computationally intensive, especially due to the need for simulating a large number of interest rate paths. As a result, the key to its practical application in real-world scenarios lies in computational efficiency. This is where DolphinDB excels. In this post, I will implement the Monte Carlo simulation using DolphinDB, leveraging its native parallel computing capabilities to enhance performance.
Two Theoretical Supports
The Monte Carlo approach for pricing fixed-income instruments relies on two key theoretical supports: pathwise valuation and interest rate structure modelling.
Pathwise valuation is a key concept behind the Monte Carlo simulation approach, particularly when valuing the path-dependent instruments. Pathwise valuation allows us to simulate multiple paths of interest rates over time, compute the corresponding cash flows, and then average these values to estimate the price of the instrument.
Interest rate structure modelling refers to the mathematical modelling of interest rates evolution over time, which can be used to simulate different interest rate paths required by the Monte Carlo approach. The interest rate structure modelling has been discussed in the previous blog post. In this blog post, we will use the Cox-Ingersoll-Ross (CIR) model as example.
Implement in DolphinDB
First, we create a function, simulate_CIR, to simulate the interest rate paths in CIR process. Further details of the CIR process can be found in the previous blog post.
领英推荐
We then create the simulate_per_path function that simulates one interest rate path and calculate the present value of the cash flows along this path.
We start by calling the simulate_CIR function to generate the interest rate path, which is stored in a list of interest rates at each time step. Next, we identify the time points of cash flow events (such as coupon or principal payments) along the interest rate path. We then discount the cash flows at these time points to their present values, resulting in the bond price based on this simulated interest rate path.
Once we have the function to calculate the bond price for a single simulated interest rate path, we can then call the function repeatedly to simulate enough paths to achieve acceptable accuracy, and calculate the bond price by averaging the prices from all paths.
Here, we use DolphinDB optimised ploop function for run the simulate_per_path function in parallel.
Full Code – DolphinDB
// Simulate the CIR process
def simulate_CIR(r0, rL, T, steps, sigma, kappa){
dt = T / steps
rates = []
rates.append!(r0)
dWs = randNormal(0, sqrt(dt), steps)
for (i in 1..steps){
dR = kappa * (rL - rates[i-1]) * dt + sigma * sqrt(rates[i-1]) * dWs[i]
rates.append!(rates[i-1] + dR)
}
return rates
}
// price bond by simulating an interest path
def simulate_per_path(r0, rL, T, payment_freq, coupon_rate, face_value,
steps, sigma, kappa, num_simulation){
// generate an interest rate path following CIR model
rate_path = simulate_CIR(r0, rL, T, steps, sigma, kappa)
// locate the cashflow time points on the rate path
cashflow_steps = steps / (T * payment_freq)
cashflow_points = []
for (i in 1..int(T * payment_freq)) {
cashflow_points.append!(cashflow_steps * i)
}
// calculate the present value of the coupon payments
coupon_value = coupon_rate * face_value
dt = T /steps
coupons = []
for (p in cashflow_points){
coupons.append!(coupon_value * exp(-rate_path[p]*(p*dt)))
}
// calculate the present value of the principal payments
principal = face_value * exp(-rate_path[steps-1]*T)
// calculate the bond price
bond_price = sum(coupons) + principal
return bond_price
}
// run the simulate_per_path funciton in parallel and return the results
// from all simulated paths
def run_simulations(r0, rL, T, payment_freq, coupon_rate, face_value,
steps, sigma, kappa, num_simulations){
simulate = partial(simulate_per_path, r0, rL, T, payment_freq,
coupon_rate, face_value, steps, sigma, kappa)
prices = ploop(simulate, 1..num_simulations)
bond_price = sum(prices) / count(prices)
return bond_price
}
r0 = 0.05
rL = 0.05
kappa = 0.05
sigma = 0.05
T = 5.0
steps = 1000
num_simulations = 100
payment_freq = 2
face_value = 100
coupon_rate = 0.05
bond_prices = run_simulations(r0, rL, T, payment_freq, coupon_rate,
face_value, steps, sigma, kappa, num_simulations)
print(bond_price)