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
|
(***************************************************************************)
(* Copyright (C) 2000-2013 LexiFi SAS. All rights reserved. *)
(* *)
(* This program is free software: you can redistribute it and/or modify *)
(* it under the terms of the GNU General Public License as published *)
(* by the Free Software Foundation, either version 3 of the License, *)
(* or (at your option) any later version. *)
(* *)
(* This program is distributed in the hope that it will be useful, *)
(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)
(* GNU General Public License for more details. *)
(* *)
(* You should have received a copy of the GNU General Public License *)
(* along with this program. If not, see <http://www.gnu.org/licenses/>. *)
(***************************************************************************)
(* A 2-factor interest-rate model. Reference book: Brigo and Mercurio "Interest Rate Models" 2005 *)
open Date
let pi = 2. *. asin 1.
type extended_swaption =
{
maturity: date;
strike: float;
swap_schedule: (date * date) list;
}
type swap_frequency =
| Freq_3M
| Freq_6M
| Freq_1Y
type swaption =
{
swaption_maturity_in_year: int;
swap_frequency: swap_frequency;
swap_term_in_year: int;
}
let extended_swaption_of_swaption ~today ~zc {swaption_maturity_in_year; swap_frequency; swap_term_in_year} =
let maturity = add_years today swaption_maturity_in_year in
let frq =
match swap_frequency with
| Freq_3M -> 3
| Freq_6M -> 6
| Freq_1Y -> 12
in
let nschedule = 12 * swap_term_in_year / frq in
let swap_schedule = Array.init nschedule (fun i -> add_months maturity (i * frq), add_months maturity ((i + 1) * frq)) in
let swap_schedule = Array.to_list swap_schedule in
let lvl, t0, tn =
List.fold_left
(fun (acc_lvl, acc_t0, acc_tn) (tf, tp) ->
acc_lvl +. zc tp *. (act_365 tp tf), min acc_t0 tf, max acc_tn tp
) (0., max_date, min_date) swap_schedule
in
let strike = (zc t0 -. zc tn) /. lvl in (* atm strike *)
{maturity; swap_schedule; strike}
type parameters =
{
g_a : float;
g_b : float;
g_sigma : float;
g_nu : float;(* nu is eta in Brigo and Mercurio *)
g_rho : float;
}
(* Places with ** comments should take account of sigma=0 case *)
let b_fun z tau = (* ** *)
(1. -. exp (-. z *. tau)) /. z
exception Sdomain
(* \var(\int_t^T [x(u)+y(u)]du) *)
let bigv {g_a; g_b; g_rho; g_nu; g_sigma} tau = (* 4.10 p 135 *)
let t sigma x =
let expxtau = exp (-. x *. tau) in (* ** *)
let exp2xtau = expxtau *. expxtau in (* ** *)
sigma *. sigma /. (x *. x) *.
(tau +. 2. /. x *. expxtau -. 1. /. (2. *. x) *. exp2xtau -. 3. /. (2. *. x))
in
let t1 = t g_sigma g_a in
let t2 = t g_nu g_b in
let ba = b_fun g_a tau in
let bb = b_fun g_b tau in
let t3 = (* ** *)
2. *. g_rho *. g_nu *. g_sigma /. (g_a *. g_b) *.
(tau -. ba -. bb +. b_fun (g_a +. g_b) tau)
in
t1 +. t2 +. t3, ba, bb
(* x drift term in tmat-forward measure *)
let bigmx {g_a; g_b; g_rho; g_nu; g_sigma} today tmat s t =
let ts = act_365 t s in
let tmatt = act_365 tmat t in
let tmat0 = act_365 tmat today in
let tmats = act_365 tmat s in
let t0 = act_365 t today in
let s0 = act_365 s today in
(g_sigma *. g_sigma /. (g_a *. g_a) +. g_sigma *. g_rho *. g_nu /. (g_a *. g_b)) *. (1. -. exp (-. g_a *. ts)) -.
(g_sigma *. g_sigma /. (2. *. g_a *. g_a) *. (exp (-. g_a *. tmatt) -. exp (-. g_a *. (tmats +. ts)))) -.
g_rho *. g_sigma *. g_nu /. (g_b *. (g_a +. g_b)) *. (exp (-. g_b *. tmatt) -. exp (-. g_b *. tmat0 -. g_a *. t0 +. (g_a +. g_b) *. s0))
(* y drift term in tmat-forward measure *)
let bigmy {g_a; g_b; g_rho; g_nu; g_sigma} today tmat s t =
let ts = act_365 t s in
let tmatt = act_365 tmat t in
let tmat0 = act_365 tmat today in
let tmats = act_365 tmat s in
let t0 = act_365 t today in
let s0 = act_365 s today in
(g_nu *. g_nu /. (g_b *. g_b) +. g_sigma *. g_rho *. g_nu /. (g_a *. g_b)) *. (1. -. exp (-. g_b *. ts)) -.
(g_nu *. g_nu /. (2. *. g_b *. g_b) *. (exp (-. g_b *. tmatt) -. exp (-. g_b *. (tmats +. ts)))) -.
g_rho *. g_sigma *. g_nu /. (g_a *. (g_a +. g_b)) *. (exp (-. g_a *. tmatt) -. exp (-. g_a *. tmat0 -. g_b *. t0 +. (g_a +. g_b) *. s0))
let x_quadrature, w_quadrature = Math.Gaussian_quadrature.gauss_hermite_coefficients
let nquadrature = Array.length x_quadrature
let pricer_of_swaption ~today ~zc swaption =
let swaption = extended_swaption_of_swaption ~today ~zc swaption in
let maturity = swaption.maturity in
let strike = swaption.strike in
let tmat0 = act_365 maturity today in
let schedulei = Array.of_list swaption.swap_schedule in
let lastindex = Array.length schedulei - 1 in
let taui = Array.map (fun (start_date, end_date) -> act_365 end_date start_date) schedulei in
let ci =
Array.mapi
(fun i tau -> if i = lastindex then 1. +. tau *. strike else tau *. strike)
taui
in
let n_schedi = Array.length schedulei in
let bai = Array.make n_schedi 0. in
let bbi = Array.make n_schedi 0. in
let aici = Array.make n_schedi 0. in
let log_aici = Array.make n_schedi 0. in
let scales = Array.make n_schedi 0. in
let t1_cst = Array.make n_schedi 0. in
let scale = Array.make n_schedi 0. in
fun params ->
let {g_a; g_b; g_rho; g_nu; g_sigma} = params in
let v0_mat, _, _ = bigv params (act_365 maturity today) in
let zc_mat = zc maturity in
let a_fun end_date = (* defined top p138 *)
let v0_end, _, _ = bigv params (act_365 end_date today) in
let vt_end, ba, bb = bigv params (act_365 end_date maturity) in
zc end_date /. zc_mat *. exp (0.5 *. (vt_end -. v0_end +. v0_mat)), ba, bb
in
let sigmax = g_sigma *. sqrt (b_fun (2. *. g_a) tmat0) in
let sigmay = g_nu *. sqrt (b_fun (2. *. g_b) tmat0) in
let rhoxy = g_rho *. g_sigma *. g_nu /. (sigmax *. sigmay) *. (b_fun (g_a +. g_b) tmat0) in
let rhoxyc = 1. -. rhoxy *. rhoxy in
let rhoxycs = sqrt rhoxyc in
let t2 = rhoxy /. (sigmax *. rhoxycs) in
let sigmay_rhoxycs = sigmay *. rhoxycs in
let t4 = rhoxy *. sigmay /. sigmax in
let mux = -. bigmx params today maturity today maturity in
let muy = -. bigmy params today maturity today maturity in
for i = 0 to n_schedi - 1 do
let a, ba, bb = a_fun (snd schedulei.(i)) in
let x = ci.(i) *. a in
let log_ac = log x in
aici.(i) <- x;
log_aici.(i) <- log_ac;
bai.(i) <- ba;
bbi.(i) <- bb;
let t3 = muy -. 0.5 *. rhoxyc *. sigmay *. sigmay *. bb in
let cst = bb *. (mux *. t4 -. t3) in
t1_cst.(i) <- x *. exp cst;
scale.(i) <- -. (ba +. bb *. t4);
done;
let k = (-3.71901648545568) in (* ugaussian_Pinv 1e-4 *)
let exact_yhat x =
(* y_lo x <= yhat x <= y_up x*)
let lo = ref neg_infinity in
let up = ref 0. in
for i = 0 to n_schedi - 1 do
let baix = bai.(i) *. x in
lo := max !lo ((log_aici.(i) -. baix) /. bbi.(i));
up := !up +. aici.(i) *. exp (-. baix)
done;
let lo = !lo and s_up = !up in
if n_schedi = 1 then lo
else
let open Math.Rootfinder in
let up =
let log_s = log s_up in
let tmp = log_s /. bbi.(n_schedi - 1) in
if tmp <= 0. then tmp
else let tmp = log_s /. bbi.(0) in if 0. <= tmp then tmp
(* This case happens when all ai * ci are too close to 0. or x to high => to_solve x y is close to -1 for all y,
thus the root is reached for y negative with high absolute value (tosolve x is a decreasing function of y) *)
else neg_infinity
in
let yl = lo -. 1e-5 in
let yu = up +. 1e-5 in
(* y01 x = y0, y1 / phi(h_i(x, y0)) <= epsilon, 1 - phi(h_i(x, y1)) <= epsilon *)
let y0 = sigmay *. (rhoxy *. (x -. mux) /. sigmax +. k *. rhoxycs) -. rhoxyc /. g_b +. muy in
let y1 = sigmay *. (rhoxy *. (x -. mux) /. sigmax -. k *. rhoxycs) +. muy in
if y1 <= yl then y1 +. 1. (* yhat is greater than y1 => 1 - phi(h_i(x, yhat)) < epsilon *)
else if yu <= y0 then y0 -. 1. (* yhat is lower than y0 => phi(h_i(x, yhat)) < epsilon *)
else try
for i = 0 to n_schedi - 1 do
scales.(i) <- aici.(i) *. exp (-. bai.(i) *. x);
done;
let to_solve yhat = (* eqn at bottom p148 *)
let sum = ref (-1.) in
for i = 0 to n_schedi - 1 do
sum := !sum +. scales.(i) *. exp (-. bbi.(i) *. yhat);
done;
assert(!sum = !sum);
!sum
in
brent (max yl y0) (min yu y1) to_solve 1e-4
with
| Rootfinder NotbracketedBelow -> y0 -. 1.
| Rootfinder NotbracketedAbove -> y1 +. 1.
in
let yhat =
let eps = 0.5 *. sigmax in
let f = exact_yhat mux in
let df = 0.5 *. (exact_yhat (mux +. eps) -. exact_yhat (mux -. eps)) /. eps in
fun x -> f +. df *. (x -. mux)
in
let integrand x =
let t1 = exp (-. 0.5 *. ((x -. mux) /. sigmax) ** 2.) in
let yhat = yhat x in
let h1 =
let t1 = (yhat -. muy) /. sigmay_rhoxycs in
t1 -. t2 *. (x -. mux)
in
let t2 = Math.ugaussian_P (-.h1) in
let acc = ref 0. in
for i = 0 to n_schedi - 1 do
let h2 = h1 +. bbi.(i) *. sigmay_rhoxycs in
acc := !acc +. t1_cst.(i) *. exp (scale.(i) *. x) *. Math.ugaussian_P (-.h2)
done;
t1 *. (t2 -. !acc)
in
let integral =
let sqrt2sigmax = sqrt 2. *. sigmax in
let sum = ref 0. in
for i = 0 to nquadrature - 1 do
sum := !sum +. w_quadrature.(i) *. integrand (sqrt2sigmax *. x_quadrature.(i) +. mux)
done;
!sum /. (sqrt pi)
in
zc_mat *. integral
let black_price ~today ~zc swaption vol =
let swaption = extended_swaption_of_swaption ~today ~zc swaption in
let {swap_schedule; strike; maturity; _} = swaption in
let sqrtt = act_365 maturity today in
let lvl, t0, tn =
List.fold_left
(fun (acc_lvl, acc_t0, acc_tn) (tf, tp) ->
acc_lvl +. zc tp *. (act_365 tp tf), min acc_t0 tf, max acc_tn tp
) (0., max_date, min_date) swap_schedule
in
let s0 = (zc t0 -. zc tn) /. lvl in
let d1 = log (s0 /. strike) /. (vol *. sqrtt) +. 0.5 *. vol *. sqrtt in
let d2 = d1 -. vol *. sqrtt in
lvl *. (s0 *. Math.ugaussian_P d1 -. strike *. Math.ugaussian_P d2)
let calibrate ?feedback ~max_global ~today ~swaption_quotes ~variables ~zc () =
assert(Array.length variables = 5);
let quotes = Array.map (fun (sw, q) -> black_price ~today ~zc sw q) swaption_quotes in
let parameters_of_vector x =
let g_a = x.(0) in
let g_b = x.(1) in
let g_sigma = x.(2) in
let g_nu = x.(3) in
let g_rho = x.(4) in
{g_a; g_b; g_sigma; g_nu; g_rho}
in
let pricers = Array.map (fun (sw, _) -> pricer_of_swaption ~today ~zc sw) swaption_quotes in
let pricer params h =
try
for i = 0 to Array.length pricers - 1 do
h.(i) <- pricers.(i) params
done
with Sdomain -> raise Optimization.Infeasible
in
Optimization.least_squares ?feedback ~max_global ~parameters_of_vector ~pricer ~variables quotes
|