-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathCoxIngersollRossModel.jl
300 lines (260 loc) · 7.42 KB
/
CoxIngersollRossModel.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
"""
A Cox-Ingersoll-Ross model with constant parameters.
"""
struct CoxIngersollRossModel <: ComponentModel
alias::String
params::ParameterTermstructure # (z0, chi, theta, sigma)
state_alias::AbstractVector
factor_alias::AbstractVector
end
"""
Create a CoxIngersollRossModel.
"""
function cox_ingersoll_ross_model(
alias::String,
params::ParameterTermstructure,
)
#
@assert size(params.values) == (4,1)
@assert params()[1] > 0.0 # z0
@assert params()[2] > 0.0 # chi
@assert params()[3] > 0.0 # theta
@assert params()[4] > 0.0 # sigma
state_alias = [ alias * "_x" ]
factor_alias = [ alias * "_x" ]
return CoxIngersollRossModel(alias, params, state_alias, factor_alias)
end
"""
Create a CoxIngersollRossModel from parameter vector.
"""
function cox_ingersoll_ross_model(
alias::String,
param_vector::AbstractVector,
)
#
return cox_ingersoll_ross_model(alias, flat_parameter(param_vector))
end
"""
Create a CoxIngersollRossModel from scalar parameters.
"""
function cox_ingersoll_ross_model(
alias::String,
z0::ModelValue,
chi::ModelValue,
theta::ModelValue,
sigma::ModelValue,
)
#
return cox_ingersoll_ross_model(alias, [z0, chi, theta, sigma])
end
"Return z0 parameter."
cir_z0(m::CoxIngersollRossModel) = m.params()[1]
"Return chi parameter."
cir_chi(m::CoxIngersollRossModel) = m.params()[2]
"Return theta parameter."
cir_theta(m::CoxIngersollRossModel) = m.params()[3]
"Return sigma parameter."
cir_sigma(m::CoxIngersollRossModel) = m.params()[4]
"""
simulation_parameters(
m::CevAssetModel,
ch::Union{CorrelationHolder, Nothing},
s::ModelTime,
t::ModelTime,
)
Pre-calculate parameters that are used in state-dependent Theta and Sigma calculation.
"""
function simulation_parameters(
m::CoxIngersollRossModel,
ch::Union{CorrelationHolder, Nothing},
s::ModelTime,
t::ModelTime,
)
exp_m_chi_dt = exp(-cir_chi(m)*(t-s))
one_m_exp = 1.0 - exp_m_chi_dt
sig2_over_chi = cir_sigma(m)^2 / cir_chi(m)
fac1 = sig2_over_chi * exp_m_chi_dt * one_m_exp
fac2 = cir_theta(m) * sig2_over_chi / 2.0 * one_m_exp^2
return (
exp_m_chi_dt = exp_m_chi_dt,
one_m_exp = one_m_exp,
fac1 = fac1,
fac2 = fac2,
)
end
"Return first and second moments E[z_t | z_s] and Var[z_t | z_s]."
function cir_moments(
m::CoxIngersollRossModel,
zs::Union{ModelValue,AbstractVector}, # allow vectorise via paths
p::NamedTuple,
)
#
E_zt = zs .* p.exp_m_chi_dt .+ cir_theta(m) * p.one_m_exp
V_zt = zs .* p.fac1 .+ p.fac2
return (E_zt, V_zt)
end
"Return first and second moments E[z_t | z_s] and Var[z_t | z_s]."
function cir_moments(
m::CoxIngersollRossModel,
zs::Union{ModelValue,AbstractVector}, # allow vectorise via paths
s::ModelTime,
t::ModelTime,
)
#
return cir_moments(m, zs, simulation_parameters(m, nothing, s, t))
end
"Calculate lognormal approximation in CIR model."
function cir_lognormal_approximation(
m::CoxIngersollRossModel,
zs::Union{ModelValue,AbstractVector}, # vectorise via paths
p::NamedTuple,
)
#
(μ, ν²) = cir_moments(m, zs, p)
b2 = log.(1.0 .+ ν² ./ μ.^2)
a = log.(μ) .- b2 / 2
return (a, b2)
end
"Calculate lognormal approximation in CIR model."
function cir_lognormal_approximation(
m::CoxIngersollRossModel,
zs::Union{ModelValue,AbstractVector}, # allow vectorise via paths
s::ModelTime,
t::ModelTime,
)
#
(μ, ν²) = cir_moments(m, zs, s, t)
b2 = log.(1.0 .+ ν² ./ μ.^2)
a = log.(μ) .- b2 / 2
return (a, b2)
end
"Calculate deterministic drift and diffusion component."
function func_Theta_Sigma(
m::CoxIngersollRossModel,
zs::Union{ModelValue,AbstractVector}, # allow vectorise via paths
s::ModelTime,
t::ModelTime,
)
#
(a, b2) = cir_lognormal_approximation(m,zs,s,t)
Θ_x = a .- log(cir_z0(m))
Σ_x = sqrt.(b2 ./ (t-s))
return (Θ_x, Σ_x)
end
# Model interface
"""
Return whether Theta requires a state vector input X.
"""
state_dependent_Theta(m::CoxIngersollRossModel) = true
"""
Return a list of state alias strings required for (H * X) calculation.
"""
state_alias_H(m::CoxIngersollRossModel) = state_alias(m)
"""
Return whether H requires a state vector input X.
"""
state_dependent_H(m::CoxIngersollRossModel) = false
"""
Return a list of factor alias strings required for (Sigma(u)^T Gamma Sigma(u)) calculation.
"""
factor_alias_Sigma(m::CoxIngersollRossModel) = factor_alias(m)
"""
Return whether Sigma requires a state vector input X.
"""
state_dependent_Sigma(m::CoxIngersollRossModel) = true
"""
Theta(
m::CoxIngersollRossModel,
s::ModelTime,
t::ModelTime,
X::Union{ModelState, Nothing} = nothing,
)
Return the deterministic drift component for simulation over the time period [s, t].
"""
function Theta(
m::CoxIngersollRossModel,
s::ModelTime,
t::ModelTime,
X::Union{ModelState, Nothing} = nothing,
)
@assert isnothing(X) == !state_dependent_Theta(m)
@assert size(X.X)[2] == 1 # require a single state
@assert isa(X.params, NamedTuple)
x_s = X(state_alias(m)[1]) # this should be a vector with size (1,)
z_s = cir_z0(m) * exp.(x_s)
(a, b2) = cir_lognormal_approximation(m,z_s,X.params)
Θ_x = a .- log(cir_z0(m))
#
return Θ_x
end
"""
H_T(
m::CoxIngersollRossModel,
s::ModelTime,
t::ModelTime,
X::Union{ModelState, Nothing} = nothing,
)
Return the transposed of the convection matrix H for simulation over the time period
[s, t].
There is no benefit in allowing H_T to be state-dependent. If H_T would need to be
state-dependent then it should be incorporated into Theta.
"""
function H_T(
m::CoxIngersollRossModel,
s::ModelTime,
t::ModelTime,
X::Union{ModelState, Nothing} = nothing,
)
@assert isnothing(X) == !state_dependent_H(m)
return zeros((1,1))
end
"""
Sigma_T(
m::CoxIngersollRossModel,
s::ModelTime,
t::ModelTime,
X::Union{ModelState, Nothing} = nothing,
)
Return a matrix-valued function representing the volatility matrix function.
"""
function Sigma_T(
m::CoxIngersollRossModel,
s::ModelTime,
t::ModelTime,
X::Union{ModelState, Nothing} = nothing,
)
@assert isnothing(X) == !state_dependent_Sigma(m)
@assert size(X.X)[2] == 1 # require a single state
@assert isa(X.params, NamedTuple)
x_s = X(state_alias(m)[1])
z_s = cir_z0(m) * exp.(x_s)
(a, b2) = cir_lognormal_approximation(m,z_s,X.params)
Σ_x = sqrt.(b2 ./ (t-s))
f(u) = reshape(Σ_x, (1,1))
return f
end
"""
diagonal_volatility(
m::CoxIngersollRossModel,
s::ModelTime,
t::ModelTime,
X::ModelState,
)
Calculate the path-dependent volatilities for CoxIngersollRossModel.
`X` is supposed to hold a state matrix of size `(n, p)`. Here, `n` is
`length(state_alias(m))` and `p` is the number of paths.
The method returns a matrix of size `(n, p)`.
"""
function diagonal_volatility(
m::CoxIngersollRossModel,
s::ModelTime,
t::ModelTime,
X::ModelState,
)
@assert isa(X.params, NamedTuple)
x_s = X(state_alias(m)[1]) # this is a vector of the x-variable
z_s = cir_z0(m) * exp.(x_s)
(a, b2) = cir_lognormal_approximation(m,z_s,X.params)
Σ_x = sqrt.(b2 ./ (t-s))
return reshape(Σ_x, (1,:))
end