use super::{ExerciseFlag, TypeFlag};
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone, Copy)]
pub struct BinomialOption {
initial_price: f64,
strike_price: f64,
time_to_expiry: f64,
risk_free_rate: f64,
dividend_yield: f64,
volatility: f64,
}
impl BinomialOption {
#[must_use]
pub fn price_CoxRossRubinstein(
&self,
output_flag: &str,
ame_eur_flag: ExerciseFlag,
call_put_flag: TypeFlag,
n: usize,
) -> f64 {
let S = self.initial_price;
let K = self.strike_price;
let T = self.time_to_expiry;
let r = self.risk_free_rate;
let q = self.dividend_yield;
let v = self.volatility;
let mut option_value: Vec<f64> = Vec::with_capacity(n + 1);
let mut return_value: Vec<f64> = vec![0.0; 5];
let b: f64 = r - q;
let (u, d, p, dt, Df): (f64, f64, f64, f64, f64);
let z = match call_put_flag {
TypeFlag::Call => 1,
TypeFlag::Put => -1,
};
dt = T / n as f64;
u = (v * dt.sqrt()).exp();
d = 1.0 / u;
p = ((b * dt).exp() - d) / (u - d);
Df = (-r * dt).exp();
for i in 0..option_value.capacity() {
option_value.push(
(f64::from(z) * (S * u.powi(i as i32) * d.powi((n - i) as i32) - K)).max(0.0),
);
}
for j in (0..n).rev() {
for i in 0..=j {
match ame_eur_flag {
ExerciseFlag::American { .. } => {
option_value[i] = (f64::from(z)
* (S * u.powi(i as i32) * d.powi(j as i32 - i as i32) - K))
.max(Df * (p * (option_value[i + 1]) + (1.0 - p) * option_value[i]));
}
ExerciseFlag::European { .. } => {
option_value[i] =
Df * (p * (option_value[i + 1]) + (1.0 - p) * option_value[i]);
}
ExerciseFlag::Bermudan { .. } => {
panic!("Bermudan option pricing not implemented yet.");
}
}
}
if j == 2 {
return_value[2] = (option_value[2] - option_value[1]) / (S * u * u - S)
- (option_value[1] - option_value[0])
/ (S - S * d * d)
/ (0.5 * (S * u * u - S * d * d));
return_value[3] = option_value[1];
}
if j == 1 {
return_value[1] = (option_value[1] - option_value[0]) / (S * u - S * d);
}
}
return_value[3] = (option_value[3] - option_value[0]) / (2.0 * dt) / 365.0;
return_value[0] = option_value[0];
match output_flag {
"p" => return_value[0],
"d" => return_value[1],
"g" => return_value[2],
"t" => return_value[3],
_ => panic!("Check OutputFlag. Should be one of: 'p', 'd', 'g', 't'."),
}
}
}
#[cfg(test)]
mod tests_binomial {
use crate::{
assert_approx_equal,
instruments::{BinomialOption, ExerciseFlag, TypeFlag},
};
#[test]
fn TEST_CRRBinomial() {
let BinOpt = BinomialOption {
initial_price: 100.0,
strike_price: 95.0,
time_to_expiry: 0.5,
risk_free_rate: 0.08,
dividend_yield: 0.0,
volatility: 0.3,
};
let c = BinOpt.price_CoxRossRubinstein("p", ExerciseFlag::American, TypeFlag::Call, 100);
let p = BinOpt.price_CoxRossRubinstein("p", ExerciseFlag::American, TypeFlag::Put, 100);
let c_intrinsic = (100_f64 - 95_f64).max(0.0);
let p_intrinsic = (95_f64 - 100_f64).max(0.0);
let parity = c - p - 100.0 + 95.0 * (-0.08_f64 * 0.5).exp();
println!("CRR Call price = {}", c);
println!("CRR Put price = {}", p);
println!("CRR Parity = {}", parity);
assert!(c >= c_intrinsic);
assert!(p >= p_intrinsic);
assert_approx_equal!(parity, 0.0, 0.5);
}
}