use ndarray::Array1;
use crate::error::DigiFiError;
use crate::lattice_models::LatticeModel;
use crate::financial_instruments::Payoff;
pub fn binomial_tree_nodes(s_0: f64, u: f64, d: f64, n_steps: usize) -> Result<Vec<Array1<f64>>, DigiFiError> {
if (u <= 0.0) || (d <= 0.0) {
return Err(DigiFiError::ParameterConstraint {
title: "Binomial Tree Nodes".to_owned(),
constraint: "The arguments `u` and `d` must be positive multiplicative factors of the binomial model.".to_owned(),
});
}
let binomial_tree: Vec<Array1<f64>> = (0..(n_steps as i32 + 1)).into_iter().fold(Vec::with_capacity(n_steps + 1), |mut bt, layer| {
let current_layer: Vec<f64> = (0..(layer + 1)).into_iter().fold(Vec::with_capacity(layer as usize + 1), |mut cl, i| {
cl.push(s_0 * u.powi(i) * d.powi(layer-i));
cl
} );
bt.push(Array1::from_vec(current_layer));
bt
} );
Ok(binomial_tree)
}
pub fn binomial_model(payoff_object: &dyn Payoff, s_0: f64, u: f64, d: f64, p_u: f64, n_steps: usize, exercise_time_steps: Option<Vec<bool>>) -> Result<f64, DigiFiError> {
let error_title: String = String::from("Binomial Model");
payoff_object.validate_payoff(5)?;
if (u < 0.0) || (d < 0.0) {
return Err(DigiFiError::ParameterConstraint {
title: error_title,
constraint: "The arguments `u` and `d` must be non-negative.".to_owned(),
});
}
if (p_u <= 0.0) || (1.0 <= p_u) {
return Err(DigiFiError::ParameterConstraint {
title: error_title,
constraint: "The argument `p_u` must be a defined over a range `[0,1]`.".to_owned(),
});
}
let exercise_time_steps: Vec<bool> = match exercise_time_steps {
Some(exercise_time_steps_vec) => {
if exercise_time_steps_vec.len() != n_steps {
return Err(DigiFiError::WrongLength { title: error_title, arg: "exercise_time_steps".to_owned(), len: n_steps, });
}
exercise_time_steps_vec
},
None => vec![true; n_steps],
};
let mut binomial_tree: Vec<Array1<f64>> = Vec::with_capacity(n_steps + 1);
let final_layer: Vec<f64> = (0..=(n_steps as i32)).into_iter()
.fold(Vec::with_capacity(n_steps + 1), |mut fl, i| {
fl.push(payoff_object.payoff(s_0 * u.powi(i) * d.powi(n_steps as i32 - i)));
fl
} );
binomial_tree.push(Array1::from_vec(final_layer));
for j in (0..n_steps).rev() {
let layer: Vec<f64> = (0..(j + 1)).into_iter()
.fold(Vec::with_capacity(j + 1), |mut layer, i| {
let value: f64 = p_u * binomial_tree[binomial_tree.len()-1][i+1] + (1.0-p_u) * binomial_tree[binomial_tree.len()-1][i];
match exercise_time_steps[i] {
true => {
let exercise: f64 = payoff_object.payoff(s_0 * u.powi(i as i32) * d.powi((j-i) as i32));
layer.push(value.max(exercise));
},
false => layer.push(value),
}
layer
} );
binomial_tree.push(Array1::from_vec(layer));
}
Ok(binomial_tree[binomial_tree.len()-1][0])
}
pub struct BrownianMotionBinomialModel {
payoff_object: Box<dyn Payoff>,
s_0: f64,
_time_to_maturity: f64,
r: f64,
_sigma: f64,
q: f64,
n_steps: usize,
dt: f64,
u: f64,
d: f64,
}
impl BrownianMotionBinomialModel {
pub fn build(payoff_object: Box<dyn Payoff>, s_0: f64, time_to_maturity: f64, r: f64, sigma: f64, q: f64, n_steps: usize) -> Result<Self, DigiFiError> {
payoff_object.validate_payoff(5)?;
let dt: f64 = time_to_maturity / (n_steps as f64);
Ok(Self {
payoff_object, s_0, _time_to_maturity: time_to_maturity, r, _sigma: sigma, q, n_steps, dt, u: (sigma * dt.sqrt()).exp(), d: (-sigma * dt.sqrt()).exp(),
})
}
}
impl LatticeModel for BrownianMotionBinomialModel {
fn european(&self) -> Result<f64, DigiFiError> {
let p_u: f64 = ((-self.q*self.dt).exp() - (-self.r*self.dt).exp()*self.d) / (self.u - self.d);
let p_d: f64 = (-self.r*self.dt).exp() - p_u;
let mut binomial_tree: Vec<Array1<f64>> = Vec::with_capacity(self.n_steps + 1);
let layer: Vec<f64> = (0..(self.n_steps as i32 + 1)).into_iter()
.fold(Vec::with_capacity(self.n_steps + 1), |mut layer, i| {
layer.push(self.payoff_object.payoff(self.s_0 * self.u.powi(i) * self.d.powi(self.n_steps as i32 - i)));
layer
} );
binomial_tree.push(Array1::from_vec(layer));
for j in (0..self.n_steps).rev() {
let layer: Vec<f64> = (0..(j + 1)).into_iter()
.fold(Vec::with_capacity(j + 1), |mut layer, i| {
layer.push(p_u*binomial_tree[binomial_tree.len()-1][i+1] + p_d*binomial_tree[binomial_tree.len()-1][i]);
layer
} );
binomial_tree.push(Array1::from_vec(layer));
}
Ok(binomial_tree[binomial_tree.len()-1][0])
}
fn american(&self) -> Result<f64, DigiFiError> {
self.bermudan(&vec![true; self.n_steps])
}
fn bermudan(&self, exercise_time_steps: &Vec<bool>) -> Result<f64, DigiFiError> {
if exercise_time_steps.len() != self.n_steps {
return Err(DigiFiError::WrongLength { title: "Brownian Motion Binomial Model".to_owned(), arg: "exercise_time_steps".to_owned(), len: self.n_steps });
}
let p_u: f64 = ((-self.q*self.dt).exp() - (-self.r*self.dt).exp()*self.d) / (self.u - self.d);
let p_d: f64 = (-self.r*self.dt).exp() - p_u;
let mut binomial_tree: Vec<Array1<f64>> = Vec::with_capacity(self.n_steps + 1);
let layer: Vec<f64> = (0..=(self.n_steps as i32)).into_iter()
.fold(Vec::with_capacity(self.n_steps + 1), |mut layer, i| {
layer.push(self.payoff_object.payoff(self.s_0 * self.u.powi(i) * self.d.powi(self.n_steps as i32 - i)));
layer
} );
binomial_tree.push(Array1::from_vec(layer));
for j in (0..self.n_steps).rev() {
let layer: Vec<f64> = (0..=j).into_iter()
.fold(Vec::with_capacity(j + 1), |mut layer, i| {
let value: f64 = p_u*binomial_tree[binomial_tree.len()-1][i+1] + p_d*binomial_tree[binomial_tree.len()-1][i];
match exercise_time_steps[j] {
true => {
let exercise: f64 = self.payoff_object.payoff(self.s_0 * (self.u.powi(i as i32)) * (self.d.powi((j-i) as i32)));
layer.push(value.max(exercise));
},
false => layer.push(value),
}
layer
} );
binomial_tree.push(Array1::from_vec(layer));
}
Ok(binomial_tree[binomial_tree.len()-1][0])
}
}
#[cfg(test)]
mod tests {
use ndarray::Array1;
use crate::utilities::TEST_ACCURACY;
#[test]
fn unit_test_binomial_tree_nodes() -> () {
use crate::lattice_models::binomial_models::binomial_tree_nodes;
let tree: Vec<Array1<f64>> = binomial_tree_nodes(10.0, 1.2, 0.9, 2).unwrap();
assert!((&tree[0] - Array1::from_vec(vec![10.0])).map(|v| v.abs() ).sum() < TEST_ACCURACY);
assert!((&tree[1] - Array1::from_vec(vec![9.0, 12.0])).map(|v| v.abs() ).sum() < TEST_ACCURACY);
assert!((&tree[2] - Array1::from_vec(vec![8.1, 10.8, 14.4])).map(|v| v.abs() ).sum() < TEST_ACCURACY);
}
#[test]
fn unit_test_binomial_model_1() -> () {
use crate::lattice_models::binomial_models::binomial_model;
use crate::financial_instruments::LongCall;
let long_call: LongCall = LongCall { k: 11.0, cost: 0.0 };
let predicted_value: f64 = binomial_model(&long_call, 10.0, 1.2, 0.9, 0.5, 2, Some(vec![false, false])).unwrap();
let analytic_solution: f64 = 0.5 * (0.5*0.0 + 0.5*3.4) + 0.5 * (0.5*0.0 + 0.5*0.0);
assert!((predicted_value - analytic_solution).abs() < TEST_ACCURACY);
}
#[test]
fn unit_test_binomial_model_2() -> () {
use crate::lattice_models::binomial_models::binomial_model;
use crate::financial_instruments::Straddle;
let straddle: Straddle = Straddle { k: 11.0, cost: 0.0 };
let predicted_value: f64 = binomial_model(&straddle, 10.0, 1.2, 0.9, 0.5, 2, Some(vec![false, false])).unwrap();
let analytic_solution: f64 = 0.5 * (0.5*3.4 + 0.5*0.2) + 0.5 * (0.5*0.2 + 0.5*2.9);
assert!((predicted_value - analytic_solution).abs() < TEST_ACCURACY);
}
#[test]
fn unit_test_brownian_motion_binomial_model_1() -> () {
use crate::lattice_models::binomial_models::BrownianMotionBinomialModel;
use crate::lattice_models::LatticeModel;
use crate::financial_instruments::LongCall;
let long_call: LongCall = LongCall { k: 11.0, cost: 0.0 };
let bmbm: BrownianMotionBinomialModel = BrownianMotionBinomialModel::build(Box::new(long_call), 10.0, 1.0, 0.02, 0.2, 0.0, 1_000).unwrap();
let predicted_value: f64 = bmbm.european().unwrap();
assert!((predicted_value - 0.49438669572304805).abs() < 100_000.0*TEST_ACCURACY);
}
#[test]
fn unit_test_brownian_motion_binomial_model_2() -> () {
use crate::lattice_models::binomial_models::BrownianMotionBinomialModel;
use crate::lattice_models::LatticeModel;
use crate::financial_instruments::LongCall;
let long_call: LongCall = LongCall { k: 11.0, cost: 0.0 };
let bmbm: BrownianMotionBinomialModel = BrownianMotionBinomialModel::build(Box::new(long_call), 10.0, 1.5, 0.02, 0.2, 0.0, 1_000).unwrap();
let predicted_value: f64 = bmbm.american().unwrap();
assert!((predicted_value - 0.71).abs() < 1_000_000.0*TEST_ACCURACY);
}
}