-
Basics Part 1 2
-
Lecture1.1
-
Lecture1.2
-
-
Basics Part 2 1
-
Lecture2.1
-
-
Credit Migration 2
-
Lecture3.1
-
Lecture3.2
-
-
Vasicek Model 2
-
Lecture4.1
-
Lecture4.2
-
-
Payments 2
-
Lecture5.1
-
Lecture5.2
-
-
Complete System 2
-
Lecture6.1
-
Lecture6.2
-
Building the Simulation
In this final lesson, we tie all the different pieces together to make a full simulation. First we bring back the initial portfolio we created in the payments part of the course, as well as the transition matrix for credit migration.
import pandas as pd
import numpy as np
#Create the portfolio
portfolio = pd.DataFrame(["AAA"]*10+["AA"]*10+["A"]*10, columns = ['Rating'])
portfolio = pd.DataFrame(["BBB"]*10+["BB"]*10+["B"]*10, columns = ['Rating'])
portfolio['Coupon Rate'] = [.08] * 10 + [.09] * 10 + [.1] * 10
portfolio['Payment Frequency'] = [6,12] * 15
portfolio['Face Value'] = 1000
#Set initial portfolio
initial_state = {
'portfolio': portfolio,
'default': 0,
'r': .06,
'cash': 0,
'payment': 0
}
#Credit transition matrix (yearly) from: https://www.msci.com/documents/10199/93396227-d449-4229-9143-24a94dab122f
transition_matrix = pd.DataFrame([[.9081, .0833,.0068,.0006,.0012,0,0,0],
[.0070,.9065,.0779,.0064,.0006,.0014,.0002,0],
[.0009,.0227,.9105,.0552,.0074,.0026,.0001,.0006],
[.0002, .0033, .0595, .8693, .0530,.0117,.0012,.0018],
[.0003, .0014,.0067,.0773,.8053,.0884, .0100, .0106],
[0, .0011, .0024, .0043, .0648, .8346, .0407, .0521],
[.0022, 0, .0022, .0130, .0238, .1124, .6486, .1978],
[0,0,0,0,0,0,0,1]],
index=["AAA", "AA", "A", "BBB", "BB", "B", "CCC", "Default"],
columns=["AAA", "AA", "A", "BBB", "BB", "B", "CCC", "Default"])
#Convert to monthly transition matrix
u, V = np.linalg.eig(transition_matrix)
P = V
D = np.diag(u)
transition_matrix_monthly = pd.DataFrame(P.dot(D ** (1/12)).dot(np.linalg.inv(P)),
index=["AAA", "AA", "A", "BBB", "BB", "B", "CCC", "Default"],
columns=["AAA", "AA", "A", "BBB", "BB", "B", "CCC", "Default"])
#Get rid of the rounding error negative numbers
transition_matrix_monthly = transition_matrix_monthly.clip(0)
#Renormalize
transition_matrix_monthly = transition_matrix_monthly.multiply(1/transition_matrix_monthly.sum(axis=1), axis=0)
To start with, let’s bring back the credit migration portion as the first two substeps.
import pandas as pd
import matplotlib.pyplot as plt
from scipy.stats import norm
import numpy as np
import pandas as pd
from cadCAD.configuration.utils import config_sim
from cadCAD.configuration import Experiment
from cadCAD.engine import ExecutionMode, ExecutionContext, Executor
from cadCAD import configs
#Step 1: Create the credit transition
#Function to transition the matrix randomly, updated to use dataframe
def credit_migration(_params, substep, sH, s):
portfolio = s['portfolio']
portfolio['Rating'] = portfolio['Rating'].map(lambda x: np.random.RandomState().choice(
transition_matrix_monthly.index,
p=transition_matrix_monthly.loc[x].values))
return ({"portfolio": portfolio})
#Update the ratings
def update_portfolio(_params, substep, sH, s, _input):
return ('portfolio', _input['portfolio'])
#Function to value the portfolio, for now just finding default %
def value_portfolio(_params, substep, sH, s):
return {"default": (s['portfolio']['Rating'] == "Default").sum() / len(s['portfolio']['Rating'])}
#Update the value
def update_value(_params, substep, sH, s, _input):
return ('default', _input['default'])
#Create partial updates
PSUBs = [
{
"policies": {
"migration_policy": credit_migration,
},
"variables": {
"portfolio": update_portfolio,
}
},
{
"policies": {
"valuation_policy": value_portfolio,
},
"variables": {
"default": update_value,
}
}
]
sim_config_dict = {
'T': range(360),
'N': 5}
c = config_sim(sim_config_dict)
exp = Experiment()
exp.append_configs(
initial_state = initial_state,
partial_state_update_blocks = PSUBs,
sim_configs = c
)
#Execute!
exec_mode = ExecutionMode()
local_mode_ctx = ExecutionContext(context=exec_mode.single_mode)
simulation = Executor(exec_context=local_mode_ctx, configs=configs)
raw_result, field, sessions = simulation.execute()
result = pd.DataFrame(raw_result)
Print the results to see that our default tracking is working but the rate r is unchanged and cash is not being added.
print(result)
Clear the simulations so that we can run again.
import pathos
pathos.helpers.shutdown()
configs.clear()
The next step we want to add in is the cash payments. Notice now we have four substeps.
#Step 2: Add in payments
#Function to compute bond payments
def bond_payments(_params, substep, sH, s):
portfolio = s['portfolio']
#Compute coupon payments
payment = (((s['timestep']) % portfolio['Payment Frequency']) == 0).astype(int) * portfolio['Coupon Rate'] * portfolio['Face Value'] * portfolio['Payment Frequency'] / 12
#If the timestep is equal to 360, add the face value
payment = payment + portfolio['Face Value'] * ((s['timestep']) % 360 == 0)
#Set payments for defaulted bonds equal to 0
payment = payment * (portfolio['Rating'] != 'Default').astype(int)
payment = payment.sum()
return ({"payment": payment})
#Update the payment
def update_payments(_params, substep, sH, s, _input):
return ('payment', _input['payment'])
#Compute cash
def cash_change(_params, substep, sH, s):
cash = s['cash'] * (1+s['r']) ** (1/12)
cash += s['payment']
return ({"cash": cash})
#Update cash
def update_cash(_params, substep, sH, s, _input):
return ('cash', _input['cash'])
PSUBs = [
{
'policies': {
'credit_policy': credit_migration
},
'variables': {
'portfolio': update_portfolio
}
},
{
'policies': {
'default_policy': value_portfolio
},
'variables': {
'default': update_value
}
},
{
'policies': {
'payment_policy': bond_payments
},
'variables': {
'payment': update_payments
}
},
{
'policies': {
'cash_policy': cash_change
},
'variables': {
'cash': update_cash
}
}
]
sim_config_dict = {
'T': range(360),
'N': 5}
c = config_sim(sim_config_dict)
exp = Experiment()
exp.append_configs(
initial_state = initial_state,
partial_state_update_blocks = PSUBs,
sim_configs = c
)
#Execute!
exec_mode = ExecutionMode()
local_mode_ctx = ExecutionContext(context=exec_mode.single_mode)
simulation = Executor(exec_context=local_mode_ctx, configs=configs)
raw_result, field, sessions = simulation.execute()
result = pd.DataFrame(raw_result)
If we plot cash positions (only taking the last substep of 4), it is obvious how different default patterns can change the profile of the cash over time.
#Now we see that there is a difference between payments/cash position given whether bonds are defaulting
result[result['substep'] == 4].pivot('timestep','run', 'cash').plot(kind='line')
plt.show()
Reset the simulation.
pathos.helpers.shutdown()
configs.clear()
Add in our final step, the change of the r. This will change the way cashflows are re-invested.
#Step 3: Add in the rate change component
from scipy.stats import norm
#Basic vasicek rate change model
def rate_change(r, a=.5, b=.06, sigma=.02, t=1/12):
delta = a* (b-r) * t + sigma * t ** .5 * norm.ppf(np.random.RandomState().rand())
return delta + r
#Policy for updating
def vasicek_policy(_params, substep, sH, s):
r = rate_change(s['r'], _params['a'], _params['b'], _params['sigma'], _params['t'])
return {'r': r}
#Simple state update
def update_rate(_params, substep, sH, s, _input):
return ('r', _input['r'])
PSUBs = [
{
'policies': {
'credit_policy': credit_migration
},
'variables': {
'portfolio': update_portfolio
}
},
{
'policies': {
'default_policy': value_portfolio
},
'variables': {
'default': update_value
}
},
{
'policies': {
'payment_policy': bond_payments
},
'variables': {
'payment': update_payments
}
},
{
'policies': {
'cash_policy': cash_change
},
'variables': {
'cash': update_cash
}
},
{
'policies': {
'rate_policy': vasicek_policy
},
'variables': {
'r': update_rate
}
}
]
sim_config_dict = {
'T': range(360),
'N': 6,
"M": {'a': [.5], 'b':[.04], 'sigma':[.02], 't':[1/12]}
}
c = config_sim(sim_config_dict)
exp = Experiment()
exp.append_configs(
initial_state = initial_state,
partial_state_update_blocks = PSUBs,
sim_configs = c
)
#Execute!
exec_mode = ExecutionMode()
local_mode_ctx = ExecutionContext(context=exec_mode.single_mode)
simulation = Executor(exec_context=local_mode_ctx, configs=configs)
raw_result, field, sessions = simulation.execute()
result = pd.DataFrame(raw_result)
The differences are even larger now!
#Rates changing adds more complexity, now the cash is re-invested at different rates
result[result['substep'] == 5].pivot('timestep','run', 'cash').plot(kind='line')
plt.show()