use crate::error::PricingError;
use crate::model::types::{OptionStyle, OptionType, Side};
use crate::pricing::payoff::{Payoff, PayoffInfo};
use crate::pricing::utils::*;
use crate::{d2f, f2d};
use positive::{Positive, pos_or_panic};
use rust_decimal::{Decimal, MathematicalOps};
type BinomialTreeResult = Result<(Vec<Vec<Decimal>>, Vec<Vec<Decimal>>), PricingError>;
#[derive(Clone)]
pub struct BinomialPricingParams<'a> {
pub asset: Positive,
pub volatility: Positive,
pub int_rate: Decimal,
pub strike: Positive,
pub expiry: Positive,
pub no_steps: usize,
pub option_type: &'a OptionType,
pub option_style: &'a OptionStyle,
pub side: &'a Side,
}
pub fn price_binomial(params: BinomialPricingParams) -> Result<Decimal, PricingError> {
let mut info = PayoffInfo {
spot: params.asset,
strike: params.strike,
style: *params.option_style,
side: *params.side,
spot_prices: None,
spot_min: None,
spot_max: None,
};
if params.expiry == Decimal::ZERO {
let intrinsic_value = f2d!(params.option_type.payoff(&info));
return Ok(intrinsic_value);
}
if params.volatility == Decimal::ZERO {
return Ok(calculate_discounted_payoff(params)?);
}
let dt = (params.expiry / pos_or_panic!(params.no_steps as f64)).to_dec();
let u = calculate_up_factor(params.volatility, dt)?;
let d = calculate_down_factor(params.volatility, dt)?;
let p = calculate_probability(params.int_rate, dt, d, u)?;
let discount_factor = calculate_discount_factor(params.int_rate, dt)?;
let mut prices: Vec<Decimal> = (0..=params.no_steps)
.map(|i| calculate_option_price(params.clone(), u, d, i).unwrap())
.collect();
for step in (0..params.no_steps).rev() {
for i in 0..=step {
let option_value = option_node_value(p, prices[i + 1], prices[i], discount_factor)?;
match params.option_type {
OptionType::American => {
let spot = params.asset * u.powi(i as i64) * d.powi((step - i) as i64);
info.spot = spot;
let intrinsic_value = f2d!(params.option_type.payoff(&info));
prices[i] = option_value.max(intrinsic_value);
}
OptionType::Bermuda { exercise_dates } => {
let time_at_step = dt * Decimal::from(step as u32);
let is_exercise_date = exercise_dates.iter().any(|&t| {
let t_dec = Decimal::try_from(t).unwrap_or(Decimal::ZERO);
(time_at_step - t_dec).abs() < dt / Decimal::TWO
});
if is_exercise_date {
let spot = params.asset * u.powi(i as i64) * d.powi((step - i) as i64);
info.spot = spot;
let intrinsic_value = f2d!(params.option_type.payoff(&info));
prices[i] = option_value.max(intrinsic_value);
} else {
prices[i] = option_value;
}
}
OptionType::European => {
prices[i] = option_value;
}
_ => {
return Err(PricingError::other(
"OptionType not supported for binomial pricing",
));
}
}
}
}
Ok(prices[0])
}
pub fn generate_binomial_tree(params: &BinomialPricingParams) -> BinomialTreeResult {
let mut info = PayoffInfo {
spot: params.asset,
strike: params.strike,
style: *params.option_style,
side: *params.side,
spot_prices: None,
spot_min: None,
spot_max: None,
};
let dt = (params.expiry / f2d!(params.no_steps as f64)).to_dec();
let up_factor = calculate_up_factor(params.volatility, dt)?;
let down_factor = calculate_down_factor(params.volatility, dt)?;
let probability = calculate_probability(params.int_rate, dt, down_factor, up_factor)?;
let discount_factor = calculate_discount_factor(params.int_rate, dt)?;
let mut asset_tree = vec![vec![Decimal::ZERO; params.no_steps + 1]; params.no_steps + 1];
let mut option_tree = vec![vec![Decimal::ZERO; params.no_steps + 1]; params.no_steps + 1];
for (step, step_vec) in asset_tree.iter_mut().enumerate() {
for (node, node_val) in step_vec.iter_mut().enumerate().take(step + 1) {
*node_val =
up_factor.powi((step - node) as i64) * down_factor.powi(node as i64) * params.asset;
}
}
for (node, node_val) in asset_tree[params.no_steps]
.iter()
.enumerate()
.take(params.no_steps + 1)
{
info.spot = Positive::new_decimal(*node_val)?;
option_tree[params.no_steps][node] = f2d!(params.option_type.payoff(&info));
}
for step in (0..params.no_steps).rev() {
let (current_step_arr, next_step_arr) = option_tree.split_at_mut(step + 1);
for (node_idx, node_val) in current_step_arr[step].iter_mut().enumerate().take(step + 1) {
let node_value =
option_node_value_wrapper(probability, next_step_arr, node_idx, discount_factor)?;
match params.option_type {
OptionType::European => {
*node_val = node_value;
}
OptionType::American => {
if (step == 0) & (node_idx == 0) {
*node_val = node_value;
} else {
info.spot = Positive::new_decimal(asset_tree[step][node_idx])?;
let intrinsic_value = params.option_type.payoff(&info);
let dec_node_val = d2f!(node_value);
*node_val = f2d!(intrinsic_value.max(dec_node_val));
}
}
OptionType::Bermuda { exercise_dates } => {
let time_at_step = dt * Decimal::from(step as u32);
let is_exercise_date = exercise_dates.iter().any(|&t| {
let t_dec = Decimal::try_from(t).unwrap_or(Decimal::ZERO);
(time_at_step - t_dec).abs() < dt / Decimal::TWO
});
if is_exercise_date && !((step == 0) & (node_idx == 0)) {
info.spot = Positive::new_decimal(asset_tree[step][node_idx])?;
let intrinsic_value = params.option_type.payoff(&info);
let dec_node_val = d2f!(node_value);
*node_val = f2d!(intrinsic_value.max(dec_node_val));
} else {
*node_val = node_value;
}
}
_ => {
return Err(PricingError::other(
"OptionType not supported for binomial tree generation",
));
}
}
}
}
Ok((asset_tree, option_tree))
}
#[cfg(test)]
mod tests_price_binomial {
use super::*;
use crate::assert_decimal_eq;
use crate::model::types::OptionType;
use rust_decimal_macros::dec;
const EPSILON: Decimal = dec!(1e-6);
#[test]
fn test_european_call_option() {
let params = BinomialPricingParams {
asset: Positive::HUNDRED,
strike: Positive::HUNDRED,
int_rate: dec!(0.05),
volatility: pos_or_panic!(0.2),
expiry: Positive::ONE,
no_steps: 3,
option_type: &OptionType::European,
option_style: &OptionStyle::Call,
side: &Side::Long,
};
let price = price_binomial(params).unwrap();
assert_decimal_eq!(price, dec!(11.0438708), EPSILON);
}
#[test]
fn test_european_put_option() {
let params = BinomialPricingParams {
asset: Positive::HUNDRED,
volatility: pos_or_panic!(0.2),
int_rate: dec!(0.05),
strike: Positive::HUNDRED,
expiry: Positive::ONE,
no_steps: 1000,
option_type: &OptionType::European,
option_style: &OptionStyle::Put,
side: &Side::Long,
};
let price = price_binomial(params).unwrap();
assert_decimal_eq!(price, dec!(5.571526), EPSILON);
}
#[test]
fn test_european_put_option_extended() {
let params = BinomialPricingParams {
asset: pos_or_panic!(50.0),
volatility: pos_or_panic!(0.2),
int_rate: dec!(0.05),
strike: pos_or_panic!(52.0),
expiry: Positive::ONE,
no_steps: 1,
option_type: &OptionType::European,
option_style: &OptionStyle::Put,
side: &Side::Long,
};
let price = price_binomial(params).unwrap();
assert_decimal_eq!(price, dec!(4.446415), EPSILON);
}
#[test]
fn test_short_option() {
let params = BinomialPricingParams {
asset: Positive::HUNDRED,
volatility: pos_or_panic!(0.2),
int_rate: dec!(0.05),
strike: Positive::HUNDRED,
expiry: Positive::ONE,
no_steps: 1000,
option_type: &OptionType::European,
option_style: &OptionStyle::Call,
side: &Side::Long,
};
let long_price = price_binomial(params.clone()).unwrap();
let short_price = price_binomial(BinomialPricingParams {
side: &Side::Short,
..params
})
.unwrap();
assert_decimal_eq!(long_price, -short_price, EPSILON);
}
#[test]
fn test_zero_volatility() {
let asset = Positive::HUNDRED;
let strike = Positive::HUNDRED;
let int_rate = dec!(0.05);
let expiry = Positive::ONE;
let params = BinomialPricingParams {
asset,
volatility: Positive::ZERO,
int_rate,
strike,
expiry,
no_steps: 1000,
option_type: &OptionType::European,
option_style: &OptionStyle::Call,
side: &Side::Long,
};
let price = price_binomial(params).unwrap();
let exact_price = (asset * (int_rate * expiry).exp() - strike).max(Positive::ZERO)
* (-int_rate * expiry).exp();
assert_decimal_eq!(price, exact_price, EPSILON);
}
#[test]
fn test_deep_in_the_money() {
let params = BinomialPricingParams {
asset: pos_or_panic!(150.0),
volatility: pos_or_panic!(0.2),
int_rate: dec!(0.05),
strike: Positive::HUNDRED,
expiry: Positive::ONE,
no_steps: 1000,
option_type: &OptionType::European,
option_style: &OptionStyle::Call,
side: &Side::Long,
};
let price = price_binomial(params).unwrap();
assert!(price > dec!(50.0));
}
#[test]
fn test_deep_out_of_the_money() {
let params = BinomialPricingParams {
asset: pos_or_panic!(50.0),
volatility: pos_or_panic!(0.2),
int_rate: dec!(0.05),
strike: Positive::HUNDRED,
expiry: Positive::ONE,
no_steps: 1000,
option_type: &OptionType::European,
option_style: &OptionStyle::Call,
side: &Side::Long,
};
let price = price_binomial(params).unwrap();
assert!(price < Decimal::ONE);
}
#[test]
fn test_zero_time_to_expiry() {
let params = BinomialPricingParams {
asset: Positive::HUNDRED,
volatility: pos_or_panic!(0.2),
int_rate: dec!(0.05),
strike: Positive::HUNDRED,
expiry: Positive::ZERO,
no_steps: 1000,
option_type: &OptionType::European,
option_style: &OptionStyle::Call,
side: &Side::Long,
};
let price = price_binomial(params).unwrap();
assert_decimal_eq!(price, Decimal::ZERO, EPSILON);
}
}
#[cfg(test)]
mod tests_generate_binomial_tree {
use super::*;
use crate::assert_decimal_eq;
use crate::model::types::OptionType;
use rust_decimal_macros::dec;
const EPSILON: Decimal = dec!(1e-5);
#[test]
fn test_binomial_tree_basic() {
let params = BinomialPricingParams {
asset: Positive::HUNDRED,
strike: Positive::HUNDRED,
int_rate: dec!(0.05),
volatility: pos_or_panic!(0.2),
expiry: Positive::ONE,
no_steps: 3,
option_type: &OptionType::European,
option_style: &OptionStyle::Call,
side: &Side::Long,
};
let (asset_tree, option_tree) = generate_binomial_tree(¶ms).unwrap();
assert_eq!(asset_tree[0][0], dec!(100.0));
assert_decimal_eq!(asset_tree[1][0], dec!(112.2400899), EPSILON);
assert_decimal_eq!(asset_tree[3][1], dec!(112.2400899), EPSILON);
assert_decimal_eq!(option_tree[0][0], dec!(11.0438708), EPSILON);
assert_decimal_eq!(option_tree[1][0], dec!(17.713887), EPSILON);
assert_decimal_eq!(option_tree[1][1], dec!(3.500653), EPSILON);
assert_decimal_eq!(option_tree[2][0], dec!(27.631232), EPSILON);
assert_decimal_eq!(option_tree[2][1], dec!(6.5458625), EPSILON);
assert_decimal_eq!(option_tree[2][2], Decimal::ZERO, EPSILON);
assert_decimal_eq!(option_tree[3][0], dec!(41.398244), EPSILON);
assert_decimal_eq!(option_tree[3][1], dec!(12.240089), EPSILON);
assert_decimal_eq!(option_tree[3][2], Decimal::ZERO, EPSILON);
assert_decimal_eq!(option_tree[3][3], Decimal::ZERO, EPSILON);
}
#[test]
fn test_binomial_tree_put_option() {
let params = BinomialPricingParams {
asset: Positive::HUNDRED,
strike: Positive::HUNDRED,
int_rate: dec!(0.05),
volatility: pos_or_panic!(0.2),
expiry: Positive::ONE,
no_steps: 3,
option_type: &OptionType::European,
option_style: &OptionStyle::Put,
side: &Side::Long,
};
let (_, option_tree) = generate_binomial_tree(¶ms).unwrap();
assert_decimal_eq!(option_tree[3][0], Decimal::ZERO, EPSILON);
assert_decimal_eq!(option_tree[3][1], Decimal::ZERO, EPSILON);
assert_decimal_eq!(option_tree[3][2], dec!(10.905274), EPSILON);
assert_decimal_eq!(option_tree[3][3], dec!(29.277764), EPSILON);
}
#[test]
fn test_binomial_tree_call_option_check() {
let params = BinomialPricingParams {
asset: pos_or_panic!(30.0),
strike: pos_or_panic!(30.0),
expiry: Positive::ONE,
int_rate: dec!(0.05),
volatility: pos_or_panic!(0.17),
no_steps: 1,
option_type: &OptionType::European,
option_style: &OptionStyle::Call,
side: &Side::Long,
};
let (asset_tree, option_tree) = generate_binomial_tree(¶ms).unwrap();
assert_eq!(asset_tree.len(), 2);
assert_decimal_eq!(asset_tree[0][0], dec!(30.0), EPSILON);
assert_decimal_eq!(asset_tree[1][0], dec!(35.559145), EPSILON);
assert_decimal_eq!(asset_tree[1][1], dec!(25.309944), EPSILON);
assert_decimal_eq!(option_tree[0][0], dec!(3.213401), EPSILON);
assert_decimal_eq!(option_tree[1][0], dec!(5.559145), EPSILON);
assert_decimal_eq!(option_tree[1][1], Decimal::ZERO, EPSILON);
let params = BinomialPricingParams {
asset: pos_or_panic!(30.0),
strike: pos_or_panic!(30.0),
expiry: Positive::ONE,
int_rate: dec!(0.05),
volatility: pos_or_panic!(0.17),
no_steps: 2,
option_type: &OptionType::European,
option_style: &OptionStyle::Call,
side: &Side::Long,
};
let (asset_tree, option_tree) = generate_binomial_tree(¶ms).unwrap();
assert_eq!(asset_tree.len(), 3);
assert_decimal_eq!(asset_tree[0][0], dec!(30.0), EPSILON);
assert_decimal_eq!(asset_tree[1][0], dec!(33.831947), EPSILON);
assert_decimal_eq!(asset_tree[1][1], dec!(26.602075), EPSILON);
assert_decimal_eq!(asset_tree[2][0], dec!(38.153354), EPSILON);
assert_decimal_eq!(asset_tree[2][1], dec!(30.0), EPSILON);
assert_decimal_eq!(asset_tree[2][2], dec!(23.589013), EPSILON);
assert_decimal_eq!(option_tree[0][0], dec!(2.564481), EPSILON);
assert_decimal_eq!(option_tree[1][0], dec!(4.572649), EPSILON);
assert_decimal_eq!(option_tree[1][1], Decimal::ZERO, EPSILON);
assert_decimal_eq!(option_tree[2][0], dec!(8.153354), EPSILON);
assert_decimal_eq!(option_tree[2][1], Decimal::ZERO, EPSILON);
assert_decimal_eq!(option_tree[2][2], Decimal::ZERO, EPSILON);
}
#[test]
fn test_binomial_tree_put_option_check() {
let params = BinomialPricingParams {
asset: Positive::HUNDRED,
strike: pos_or_panic!(110.0),
expiry: pos_or_panic!(3.0), int_rate: dec!(0.05),
volatility: pos_or_panic!(0.09531018), no_steps: 3,
option_type: &OptionType::European,
option_style: &OptionStyle::Put,
side: &Side::Long,
};
let (asset_tree, option_tree) = generate_binomial_tree(¶ms).unwrap();
assert_eq!(asset_tree.len(), 4);
assert_decimal_eq!(asset_tree[0][0], dec!(100.0), EPSILON);
assert_decimal_eq!(asset_tree[1][0], dec!(110.0), EPSILON);
assert_decimal_eq!(asset_tree[1][1], dec!(90.909090), EPSILON);
assert_decimal_eq!(asset_tree[2][0], dec!(121.0), EPSILON);
assert_decimal_eq!(asset_tree[2][1], dec!(100.0), EPSILON);
assert_decimal_eq!(asset_tree[2][2], dec!(82.644628), EPSILON);
assert_decimal_eq!(asset_tree[3][0], dec!(133.1), EPSILON);
assert_decimal_eq!(asset_tree[3][1], dec!(110.0), EPSILON);
assert_decimal_eq!(asset_tree[3][2], dec!(90.909090), EPSILON);
assert_decimal_eq!(asset_tree[3][3], dec!(75.131480), EPSILON);
assert_decimal_eq!(option_tree[0][0], dec!(2.890941), EPSILON);
assert_decimal_eq!(option_tree[1][0], dec!(1.125426), EPSILON);
assert_decimal_eq!(option_tree[1][1], dec!(8.623025), EPSILON);
assert_decimal_eq!(option_tree[2][0], Decimal::ZERO, EPSILON);
assert_decimal_eq!(option_tree[2][1], dec!(4.635236), EPSILON);
assert_decimal_eq!(option_tree[2][2], dec!(21.990608), EPSILON);
assert_decimal_eq!(option_tree[3][0], Decimal::ZERO, EPSILON);
assert_decimal_eq!(option_tree[3][1], Decimal::ZERO, EPSILON);
assert_decimal_eq!(option_tree[3][2], dec!(19.090909), EPSILON);
assert_decimal_eq!(option_tree[3][3], dec!(34.868519), EPSILON);
}
#[test]
fn test_binomial_tree_european_put_option() {
let params = BinomialPricingParams {
asset: pos_or_panic!(50.0),
volatility: pos_or_panic!(0.2),
int_rate: dec!(0.05),
strike: pos_or_panic!(52.0),
expiry: Positive::TWO,
no_steps: 2,
option_type: &OptionType::European,
option_style: &OptionStyle::Put,
side: &Side::Long,
};
let (asset_tree, option_tree) = generate_binomial_tree(¶ms).unwrap();
assert_decimal_eq!(asset_tree[0][0], dec!(50.0), EPSILON);
assert_decimal_eq!(asset_tree[1][0], dec!(61.070137), EPSILON);
assert_decimal_eq!(asset_tree[1][1], dec!(40.936537), EPSILON);
assert_decimal_eq!(asset_tree[2][0], dec!(74.591234), EPSILON);
assert_decimal_eq!(asset_tree[2][1], dec!(50.0), EPSILON);
assert_decimal_eq!(asset_tree[2][2], dec!(33.516002), EPSILON);
assert_decimal_eq!(option_tree[0][0], dec!(3.8687179), EPSILON);
assert_decimal_eq!(option_tree[1][0], dec!(0.8038018), EPSILON);
assert_decimal_eq!(option_tree[1][1], dec!(8.5273923), EPSILON);
assert_decimal_eq!(option_tree[2][0], Decimal::ZERO, EPSILON);
assert_decimal_eq!(option_tree[2][1], dec!(2.0), EPSILON);
assert_decimal_eq!(option_tree[2][2], dec!(18.483997), EPSILON);
}
#[test]
fn test_binomial_tree_american_put_option() {
let params = BinomialPricingParams {
asset: pos_or_panic!(50.0),
volatility: pos_or_panic!(0.2),
int_rate: dec!(0.05),
strike: pos_or_panic!(52.0),
expiry: Positive::TWO,
no_steps: 2,
option_type: &OptionType::American,
option_style: &OptionStyle::Put,
side: &Side::Long,
};
let (asset_tree, option_tree) = generate_binomial_tree(¶ms).unwrap();
assert_decimal_eq!(asset_tree[0][0], dec!(50.0), EPSILON);
assert_decimal_eq!(asset_tree[1][0], dec!(61.070137), EPSILON);
assert_decimal_eq!(asset_tree[1][1], dec!(40.936537), EPSILON);
assert_decimal_eq!(asset_tree[2][0], dec!(74.591234), EPSILON);
assert_decimal_eq!(asset_tree[2][1], dec!(50.0), EPSILON);
assert_decimal_eq!(asset_tree[2][2], dec!(33.516002), EPSILON);
assert_decimal_eq!(option_tree[2][0], Decimal::ZERO, EPSILON);
assert_decimal_eq!(option_tree[2][1], dec!(2.0), EPSILON);
assert_decimal_eq!(option_tree[2][2], dec!(18.483997), EPSILON);
assert_decimal_eq!(option_tree[1][0], dec!(0.803801), EPSILON);
assert_decimal_eq!(option_tree[1][1], params.strike - asset_tree[1][1], EPSILON);
assert_decimal_eq!(option_tree[0][0], dec!(4.887966), EPSILON);
}
}
#[cfg(test)]
mod tests_bermuda_option {
use super::*;
use crate::assert_decimal_eq;
use crate::model::types::OptionType;
use rust_decimal_macros::dec;
const EPSILON: Decimal = dec!(1e-4);
#[test]
fn test_bermuda_price_between_european_and_american() {
let european_params = BinomialPricingParams {
asset: pos_or_panic!(50.0),
volatility: pos_or_panic!(0.2),
int_rate: dec!(0.05),
strike: pos_or_panic!(52.0),
expiry: Positive::ONE,
no_steps: 100,
option_type: &OptionType::European,
option_style: &OptionStyle::Put,
side: &Side::Long,
};
let american_params = BinomialPricingParams {
option_type: &OptionType::American,
..european_params.clone()
};
let bermuda_type = OptionType::Bermuda {
exercise_dates: vec![0.25, 0.5, 0.75],
};
let bermuda_params = BinomialPricingParams {
option_type: &bermuda_type,
..european_params.clone()
};
let european_price = price_binomial(european_params).unwrap();
let american_price = price_binomial(american_params).unwrap();
let bermuda_price = price_binomial(bermuda_params).unwrap();
assert!(
european_price <= bermuda_price,
"European {} should be <= Bermuda {}",
european_price,
bermuda_price
);
assert!(
bermuda_price <= american_price,
"Bermuda {} should be <= American {}",
bermuda_price,
american_price
);
}
#[test]
fn test_bermuda_single_exercise_date() {
let bermuda_type = OptionType::Bermuda {
exercise_dates: vec![0.5],
};
let params = BinomialPricingParams {
asset: Positive::HUNDRED,
volatility: pos_or_panic!(0.3),
int_rate: dec!(0.05),
strike: pos_or_panic!(105.0),
expiry: Positive::ONE,
no_steps: 50,
option_type: &bermuda_type,
option_style: &OptionStyle::Put,
side: &Side::Long,
};
let price = price_binomial(params).unwrap();
assert!(
price > Decimal::ZERO,
"Bermuda put price should be positive"
);
}
#[test]
fn test_bermuda_many_exercise_dates_approaches_american() {
let european_params = BinomialPricingParams {
asset: pos_or_panic!(50.0),
volatility: pos_or_panic!(0.2),
int_rate: dec!(0.05),
strike: pos_or_panic!(52.0),
expiry: Positive::ONE,
no_steps: 52,
option_type: &OptionType::European,
option_style: &OptionStyle::Put,
side: &Side::Long,
};
let american_params = BinomialPricingParams {
option_type: &OptionType::American,
..european_params.clone()
};
let exercise_dates: Vec<f64> = (1..=52).map(|i| i as f64 / 52.0).collect();
let bermuda_type = OptionType::Bermuda { exercise_dates };
let bermuda_params = BinomialPricingParams {
option_type: &bermuda_type,
..european_params.clone()
};
let american_price = price_binomial(american_params).unwrap();
let bermuda_price = price_binomial(bermuda_params).unwrap();
let diff = (american_price - bermuda_price).abs();
assert!(
diff < dec!(0.5),
"Bermuda with 52 exercise dates should be close to American: diff = {}",
diff
);
}
#[test]
fn test_bermuda_no_exercise_dates_equals_european() {
let european_params = BinomialPricingParams {
asset: Positive::HUNDRED,
volatility: pos_or_panic!(0.2),
int_rate: dec!(0.05),
strike: Positive::HUNDRED,
expiry: Positive::ONE,
no_steps: 50,
option_type: &OptionType::European,
option_style: &OptionStyle::Put,
side: &Side::Long,
};
let bermuda_type = OptionType::Bermuda {
exercise_dates: vec![],
};
let bermuda_params = BinomialPricingParams {
option_type: &bermuda_type,
..european_params.clone()
};
let european_price = price_binomial(european_params).unwrap();
let bermuda_price = price_binomial(bermuda_params).unwrap();
assert_decimal_eq!(european_price, bermuda_price, EPSILON);
}
#[test]
fn test_bermuda_call_option() {
let bermuda_type = OptionType::Bermuda {
exercise_dates: vec![0.25, 0.5, 0.75],
};
let params = BinomialPricingParams {
asset: Positive::HUNDRED,
volatility: pos_or_panic!(0.25),
int_rate: dec!(0.05),
strike: pos_or_panic!(95.0),
expiry: Positive::ONE,
no_steps: 100,
option_type: &bermuda_type,
option_style: &OptionStyle::Call,
side: &Side::Long,
};
let price = price_binomial(params).unwrap();
assert!(
price > dec!(5.0),
"ITM Bermuda call should have value > intrinsic"
);
}
}