use crate::error::PricingError;
use crate::greeks::big_n;
use crate::model::decimal::{d_add, d_mul, d_sub};
use crate::model::types::OptionStyle;
use positive::Positive;
use rust_decimal::{Decimal, MathematicalOps};
use rust_decimal_macros::dec;
const MAX_ITERATIONS: usize = 100;
const TOLERANCE: f64 = 1e-6;
pub fn barone_adesi_whaley(
spot: Positive,
strike: Positive,
time_to_expiry: Positive,
risk_free_rate: Decimal,
dividend_yield: Positive,
volatility: Positive,
option_style: &OptionStyle,
) -> Result<Decimal, PricingError> {
let s = spot.to_dec();
let k = strike.to_dec();
let t = time_to_expiry.to_dec();
let r = risk_free_rate;
let q = dividend_yield.to_dec();
let sigma = volatility.to_dec();
if t <= Decimal::ZERO {
return Ok(match option_style {
OptionStyle::Call => {
d_sub(s, k, "pricing::american::intrinsic::call")?.max(Decimal::ZERO)
}
OptionStyle::Put => {
d_sub(k, s, "pricing::american::intrinsic::put")?.max(Decimal::ZERO)
}
});
}
if sigma <= Decimal::ZERO {
let neg_rt = d_mul(-r, t, "pricing::american::zero_vol::rt")?;
let neg_qt = d_mul(-q, t, "pricing::american::zero_vol::qt")?;
let discount_r = neg_rt.exp();
let discount_q = neg_qt.exp();
let s_disc = d_mul(s, discount_q, "pricing::american::zero_vol::s_disc")?;
let k_disc = d_mul(k, discount_r, "pricing::american::zero_vol::k_disc")?;
return Ok(match option_style {
OptionStyle::Call => {
d_sub(s_disc, k_disc, "pricing::american::zero_vol::call")?.max(Decimal::ZERO)
}
OptionStyle::Put => {
d_sub(k_disc, s_disc, "pricing::american::zero_vol::put")?.max(Decimal::ZERO)
}
});
}
let european_price = black_scholes_european(s, k, t, r, q, sigma, option_style)?;
if matches!(option_style, OptionStyle::Call) && q <= Decimal::ZERO {
return Ok(european_price);
}
let sigma_sq = sigma * sigma;
let m = dec!(2) * r / sigma_sq;
let n = dec!(2) * (r - q) / sigma_sq;
let k_factor = dec!(1) - (-r * t).exp();
match option_style {
OptionStyle::Call => {
let discriminant = (n - dec!(1)).powi(2) + dec!(4) * m / k_factor;
let sqrt_disc = discriminant.sqrt().ok_or_else(|| {
PricingError::method_error(
"baw",
"cannot calculate square root of negative discriminant",
)
})?;
let q2 = (-(n - dec!(1)) + sqrt_disc) / dec!(2);
let s_star = find_critical_price_call(s, k, t, r, q, sigma, q2)?;
if s >= s_star {
d_sub(s, k, "pricing::american::call::immediate_exercise")
.map_err(PricingError::from)
} else {
let d1_val = d1(s_star, k, t, r, q, sigma)?;
let n_d1 = big_n(d1_val)?;
let a2 = (s_star / q2) * (dec!(1) - (-q * t).exp() * n_d1);
let early_exercise_premium = a2 * (s / s_star).powd(q2);
d_add(
european_price,
early_exercise_premium,
"pricing::american::call::price",
)
.map_err(PricingError::from)
}
}
OptionStyle::Put => {
let discriminant = (n - dec!(1)).powi(2) + dec!(4) * m / k_factor;
let sqrt_disc = discriminant.sqrt().ok_or_else(|| {
PricingError::method_error(
"baw",
"cannot calculate square root of negative discriminant",
)
})?;
let q1 = (-(n - dec!(1)) - sqrt_disc) / dec!(2);
let s_star_star = find_critical_price_put(s, k, t, r, q, sigma, q1)?;
if s <= s_star_star {
d_sub(k, s, "pricing::american::put::immediate_exercise")
.map_err(PricingError::from)
} else {
let d1_val = d1(s_star_star, k, t, r, q, sigma)?;
let n_minus_d1 = big_n(-d1_val)?;
let a1 = -(s_star_star / q1) * (dec!(1) - (-q * t).exp() * n_minus_d1);
let early_exercise_premium = a1 * (s / s_star_star).powd(q1);
d_add(
european_price,
early_exercise_premium,
"pricing::american::put::price",
)
.map_err(PricingError::from)
}
}
}
}
fn black_scholes_european(
s: Decimal,
k: Decimal,
t: Decimal,
r: Decimal,
q: Decimal,
sigma: Decimal,
option_style: &OptionStyle,
) -> Result<Decimal, PricingError> {
let d1_val = d1(s, k, t, r, q, sigma)?;
let sqrt_t = t.sqrt().ok_or_else(|| {
PricingError::method_error("black_scholes", "cannot calculate square root of time")
})?;
let d2_val = d1_val - sigma * sqrt_t;
let discount = (-r * t).exp();
let forward_factor = (-q * t).exp();
let n_d1 = big_n(d1_val)?;
let n_d2 = big_n(d2_val)?;
let n_minus_d1 = big_n(-d1_val)?;
let n_minus_d2 = big_n(-d2_val)?;
match option_style {
OptionStyle::Call => Ok(s * forward_factor * n_d1 - k * discount * n_d2),
OptionStyle::Put => Ok(k * discount * n_minus_d2 - s * forward_factor * n_minus_d1),
}
}
fn d1(
s: Decimal,
k: Decimal,
t: Decimal,
r: Decimal,
q: Decimal,
sigma: Decimal,
) -> Result<Decimal, PricingError> {
if t <= Decimal::ZERO || sigma <= Decimal::ZERO {
return Err(PricingError::method_error(
"d1",
"time and volatility must be positive",
));
}
let sqrt_t = t
.sqrt()
.ok_or_else(|| PricingError::method_error("d1", "cannot calculate square root of time"))?;
let ln_s_k = (s / k).ln();
Ok((ln_s_k + (r - q + sigma * sigma / dec!(2)) * t) / (sigma * sqrt_t))
}
fn find_critical_price_call(
_spot: Decimal,
strike: Decimal,
t: Decimal,
r: Decimal,
q: Decimal,
sigma: Decimal,
q2: Decimal,
) -> Result<Decimal, PricingError> {
let mut s_star = strike * dec!(1.1);
for _ in 0..MAX_ITERATIONS {
let d1_val = d1(s_star, strike, t, r, q, sigma)?;
let n_d1 = big_n(d1_val)?;
let exp_qt = (-q * t).exp();
let c_euro = black_scholes_european(s_star, strike, t, r, q, sigma, &OptionStyle::Call)?;
let lhs = s_star - strike;
let rhs = c_euro + (s_star / q2) * (dec!(1) - exp_qt * n_d1);
let f = lhs - rhs;
let delta_s = s_star * dec!(0.0001);
let d1_plus = d1(s_star + delta_s, strike, t, r, q, sigma)?;
let n_d1_plus = big_n(d1_plus)?;
let c_euro_plus =
black_scholes_european(s_star + delta_s, strike, t, r, q, sigma, &OptionStyle::Call)?;
let rhs_plus = c_euro_plus + ((s_star + delta_s) / q2) * (dec!(1) - exp_qt * n_d1_plus);
let f_plus = (s_star + delta_s) - strike - rhs_plus;
let f_prime: Decimal = (f_plus - f) / delta_s;
if f_prime.abs() < dec!(1e-10) {
break;
}
let s_star_new = s_star - f / f_prime;
if (s_star_new - s_star).abs() < Decimal::from_f64_retain(TOLERANCE).unwrap_or(dec!(1e-6)) {
return Ok(s_star_new.max(strike)); }
s_star = s_star_new.max(strike * dec!(0.5)); }
Ok(s_star.max(strike))
}
fn find_critical_price_put(
_spot: Decimal,
strike: Decimal,
t: Decimal,
r: Decimal,
q: Decimal,
sigma: Decimal,
q1: Decimal,
) -> Result<Decimal, PricingError> {
let mut s_star = strike * dec!(0.9);
for _ in 0..MAX_ITERATIONS {
let d1_val = d1(s_star, strike, t, r, q, sigma)?;
let n_minus_d1 = big_n(-d1_val)?;
let exp_qt = (-q * t).exp();
let p_euro = black_scholes_european(s_star, strike, t, r, q, sigma, &OptionStyle::Put)?;
let lhs = strike - s_star;
let rhs = p_euro - (s_star / q1) * (dec!(1) - exp_qt * n_minus_d1);
let f = lhs - rhs;
let delta_s = s_star * dec!(0.0001);
let delta_s = delta_s.max(dec!(0.01)); let d1_plus = d1(s_star + delta_s, strike, t, r, q, sigma)?;
let n_minus_d1_plus = big_n(-d1_plus)?;
let p_euro_plus =
black_scholes_european(s_star + delta_s, strike, t, r, q, sigma, &OptionStyle::Put)?;
let rhs_plus =
p_euro_plus - ((s_star + delta_s) / q1) * (dec!(1) - exp_qt * n_minus_d1_plus);
let f_plus = strike - (s_star + delta_s) - rhs_plus;
let f_prime: Decimal = (f_plus - f) / delta_s;
if f_prime.abs() < dec!(1e-10) {
break;
}
let s_star_new = s_star - f / f_prime;
if (s_star_new - s_star).abs() < Decimal::from_f64_retain(TOLERANCE).unwrap_or(dec!(1e-6)) {
return Ok(s_star_new.max(dec!(0.01)).min(strike)); }
s_star = s_star_new.max(dec!(0.01)).min(strike * dec!(1.5)); }
Ok(s_star.max(dec!(0.01)).min(strike))
}
#[cfg(test)]
mod tests_american_pricing {
use super::*;
use approx::assert_relative_eq;
use num_traits::ToPrimitive;
use positive::pos_or_panic;
#[test]
fn test_baw_call_at_expiry() {
let price = barone_adesi_whaley(
pos_or_panic!(110.0),
Positive::HUNDRED,
Positive::ZERO,
dec!(0.05),
Positive::ZERO,
pos_or_panic!(0.2),
&OptionStyle::Call,
)
.unwrap();
assert_relative_eq!(price.to_f64().unwrap(), 10.0, epsilon = 0.01);
}
#[test]
fn test_baw_put_at_expiry() {
let price = barone_adesi_whaley(
pos_or_panic!(90.0),
Positive::HUNDRED,
Positive::ZERO,
dec!(0.05),
Positive::ZERO,
pos_or_panic!(0.2),
&OptionStyle::Put,
)
.unwrap();
assert_relative_eq!(price.to_f64().unwrap(), 10.0, epsilon = 0.01);
}
#[test]
fn test_baw_call_no_dividend() {
let price = barone_adesi_whaley(
Positive::HUNDRED,
Positive::HUNDRED,
Positive::ONE,
dec!(0.05),
Positive::ZERO,
pos_or_panic!(0.2),
&OptionStyle::Call,
)
.unwrap();
assert!(price.to_f64().unwrap() > 9.0);
assert!(price.to_f64().unwrap() < 12.0);
}
#[test]
fn test_baw_put_has_early_exercise_premium() {
let american_put = barone_adesi_whaley(
Positive::HUNDRED,
Positive::HUNDRED,
Positive::ONE,
dec!(0.05),
Positive::ZERO,
pos_or_panic!(0.2),
&OptionStyle::Put,
)
.unwrap();
let european_put = black_scholes_european(
dec!(100),
dec!(100),
dec!(1),
dec!(0.05),
dec!(0),
dec!(0.2),
&OptionStyle::Put,
)
.unwrap();
assert!(american_put >= european_put);
}
#[test]
fn test_baw_deep_itm_put() {
let price = barone_adesi_whaley(
pos_or_panic!(50.0),
Positive::HUNDRED,
Positive::ONE,
dec!(0.05),
Positive::ZERO,
pos_or_panic!(0.2),
&OptionStyle::Put,
)
.unwrap();
assert!(price.to_f64().unwrap() >= 49.0);
}
#[test]
fn test_baw_call_with_dividend() {
let american_call = barone_adesi_whaley(
Positive::HUNDRED,
Positive::HUNDRED,
Positive::ONE,
dec!(0.05),
pos_or_panic!(0.03), pos_or_panic!(0.2),
&OptionStyle::Call,
)
.unwrap();
assert!(american_call.to_f64().unwrap() > 0.0);
}
#[test]
fn test_baw_zero_volatility() {
let price = barone_adesi_whaley(
pos_or_panic!(110.0),
Positive::HUNDRED,
Positive::ONE,
dec!(0.05),
Positive::ZERO,
Positive::ZERO,
&OptionStyle::Call,
)
.unwrap();
assert!(price.to_f64().unwrap() > 0.0);
}
#[test]
fn test_baw_otm_call() {
let price = barone_adesi_whaley(
pos_or_panic!(90.0),
Positive::HUNDRED,
Positive::ONE,
dec!(0.05),
Positive::ZERO,
pos_or_panic!(0.2),
&OptionStyle::Call,
)
.unwrap();
assert!(price.to_f64().unwrap() > 0.0);
assert!(price.to_f64().unwrap() < 10.0);
}
#[test]
fn test_baw_otm_put() {
let price = barone_adesi_whaley(
pos_or_panic!(110.0),
Positive::HUNDRED,
Positive::ONE,
dec!(0.05),
Positive::ZERO,
pos_or_panic!(0.2),
&OptionStyle::Put,
)
.unwrap();
assert!(price.to_f64().unwrap() > 0.0);
}
}