use ndarray::Array1;
use ndarray::Array2;
use super::TwoFactorShortRateModel;
use super::common::OrnsteinUhlenbeckFactor;
use super::common::build_one_factor_trinomial_tree;
use super::common::correlated_joint_probabilities;
use crate::lattice::tree::TrinomialTree;
use crate::traits::FloatExt;
#[derive(Debug, Clone)]
pub struct G2ppTreeModel<T: FloatExt> {
pub initial_x: T,
pub initial_y: T,
pub phi: T,
pub mean_reversion_x: T,
pub mean_reversion_y: T,
pub sigma_x: T,
pub sigma_y: T,
pub rho: T,
}
impl<T: FloatExt> G2ppTreeModel<T> {
#[allow(clippy::too_many_arguments)]
pub fn new(
initial_x: T,
initial_y: T,
phi: T,
mean_reversion_x: T,
mean_reversion_y: T,
sigma_x: T,
sigma_y: T,
rho: T,
) -> Self {
Self {
initial_x,
initial_y,
phi,
mean_reversion_x,
mean_reversion_y,
sigma_x,
sigma_y,
rho,
}
}
}
impl<T: FloatExt> TwoFactorShortRateModel<T> for G2ppTreeModel<T> {
fn initial_x(&self) -> T {
self.initial_x
}
fn initial_y(&self) -> T {
self.initial_y
}
fn drift_x(&self, _time: T, x: T) -> T {
-self.mean_reversion_x * x
}
fn drift_y(&self, _time: T, y: T) -> T {
-self.mean_reversion_y * y
}
fn diffusion_x(&self, _time: T, _x: T) -> T {
self.sigma_x
}
fn diffusion_y(&self, _time: T, _y: T) -> T {
self.sigma_y
}
fn correlation(&self) -> T {
self.rho
}
fn short_rate(&self, _time: T, x: T, y: T) -> T {
x + y + self.phi
}
}
#[derive(Debug, Clone)]
pub struct G2ppTree<T: FloatExt> {
pub model: G2ppTreeModel<T>,
pub x_tree: TrinomialTree<T>,
pub y_tree: TrinomialTree<T>,
pub horizon: T,
pub dt: T,
}
impl<T: FloatExt> G2ppTree<T> {
pub fn new(model: G2ppTreeModel<T>, horizon: T, steps: usize) -> Self {
let x_model = OrnsteinUhlenbeckFactor {
initial_state: model.initial_x,
mean_reversion: model.mean_reversion_x,
sigma: model.sigma_x,
};
let y_model = OrnsteinUhlenbeckFactor {
initial_state: model.initial_y,
mean_reversion: model.mean_reversion_y,
sigma: model.sigma_y,
};
let x_tree = build_one_factor_trinomial_tree(&x_model, horizon, steps);
let y_tree = build_one_factor_trinomial_tree(&y_model, horizon, steps);
Self {
model,
dt: horizon / T::from_usize_(steps),
x_tree,
y_tree,
horizon,
}
}
pub fn zero_coupon_bond_price(&self) -> T {
let mut values = Array2::from_elem(
(
self.x_tree.states.last().map_or(0, Array1::len),
self.y_tree.states.last().map_or(0, Array1::len),
),
T::one(),
);
for level in (0..self.x_tree.branches.len()).rev() {
let x_width = self.x_tree.states[level].len();
let y_width = self.y_tree.states[level].len();
let mut step_values = Array2::zeros((x_width, y_width));
let time = T::from_usize_(level) * self.dt;
for ix in 0..x_width {
let x_branch = self.x_tree.branches[level][ix];
let x_children = [
x_branch.center_index - 1,
x_branch.center_index,
x_branch.center_index + 1,
];
for iy in 0..y_width {
let y_branch = self.y_tree.branches[level][iy];
let y_children = [
y_branch.center_index - 1,
y_branch.center_index,
y_branch.center_index + 1,
];
let joint = correlated_joint_probabilities(x_branch, y_branch, self.model.correlation());
let mut expected = T::zero();
for ax in 0..3 {
for ay in 0..3 {
expected += joint[ax][ay] * values[[x_children[ax], y_children[ay]]];
}
}
let rate = self.model.short_rate(
time,
self.x_tree.states[level][ix],
self.y_tree.states[level][iy],
);
step_values[[ix, iy]] = (-rate * self.dt).exp() * expected;
}
}
values = step_values;
}
values[[0, 0]]
}
}