use stochastic_rs_distributions::special::norm_cdf;
use crate::OptionType;
#[derive(Debug, Clone)]
pub struct FloatingLookbackPricer {
pub s: f64,
pub s_min: Option<f64>,
pub s_max: Option<f64>,
pub r: f64,
pub q: f64,
pub sigma: f64,
pub t: f64,
pub option_type: OptionType,
}
impl FloatingLookbackPricer {
pub fn price(&self) -> f64 {
let s = self.s;
let r = self.r;
let q = self.q;
let b = r - q;
let sigma = self.sigma;
let t = self.t;
let sqrt_t = t.sqrt();
let sigma2 = sigma * sigma;
match self.option_type {
OptionType::Call => {
let s_min = self.s_min.unwrap_or(s);
let a1 = ((s / s_min).ln() + (b + 0.5 * sigma2) * t) / (sigma * sqrt_t);
let term1 = s * (-q * t).exp() * norm_cdf(a1);
let term2 = s_min * (-r * t).exp() * norm_cdf(a1 - sigma * sqrt_t);
let premium = if b.abs() < 1e-10 {
s * (-r * t).exp() * sigma * sqrt_t * norm_cdf(a1).max(0.0) * sigma
} else {
let coeff = s * (-r * t).exp() * sigma2 / (2.0 * b);
let bracket = (s / s_min).powf(-2.0 * b / sigma2)
* norm_cdf(-a1 + 2.0 * b * sqrt_t / sigma)
- (b * t).exp() * norm_cdf(-a1);
coeff * bracket
};
term1 - term2 + premium
}
OptionType::Put => {
let s_max = self.s_max.unwrap_or(s);
let b1 = ((s / s_max).ln() + (b + 0.5 * sigma2) * t) / (sigma * sqrt_t);
let term1 = s_max * (-r * t).exp() * norm_cdf(-b1 + sigma * sqrt_t);
let term2 = s * (-q * t).exp() * norm_cdf(-b1);
let premium = if b.abs() < 1e-10 {
s * (-r * t).exp() * sigma * sqrt_t * norm_cdf(-b1).max(0.0) * sigma
} else {
let coeff = s * (-r * t).exp() * sigma2 / (2.0 * b);
let bracket = (b * t).exp() * norm_cdf(b1)
- (s / s_max).powf(-2.0 * b / sigma2) * norm_cdf(b1 - 2.0 * b * sqrt_t / sigma);
coeff * bracket
};
term1 - term2 + premium
}
}
}
}
#[derive(Debug, Clone)]
pub struct FixedLookbackPricer {
pub s: f64,
pub k: f64,
pub s_min: Option<f64>,
pub s_max: Option<f64>,
pub r: f64,
pub q: f64,
pub sigma: f64,
pub t: f64,
pub option_type: OptionType,
}
impl FixedLookbackPricer {
pub fn price(&self) -> f64 {
match self.option_type {
OptionType::Call => {
let s_max = self.s_max.unwrap_or(self.s);
let m = s_max.max(self.k);
let fp = FloatingLookbackPricer {
s: self.s,
s_min: None,
s_max: Some(m),
r: self.r,
q: self.q,
sigma: self.sigma,
t: self.t,
option_type: OptionType::Put,
};
fp.price() + self.s * (-self.q * self.t).exp() - self.k * (-self.r * self.t).exp()
}
OptionType::Put => {
let s_min = self.s_min.unwrap_or(self.s);
let m = s_min.min(self.k);
let fc = FloatingLookbackPricer {
s: self.s,
s_min: Some(m),
s_max: None,
r: self.r,
q: self.q,
sigma: self.sigma,
t: self.t,
option_type: OptionType::Call,
};
fc.price() + self.k * (-self.r * self.t).exp() - self.s * (-self.q * self.t).exp()
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn floating_call_intrinsic_bound() {
let p = FloatingLookbackPricer {
s: 100.0,
s_min: Some(90.0),
s_max: None,
r: 0.05,
q: 0.0,
sigma: 0.2,
t: 1.0,
option_type: OptionType::Call,
};
let price = p.price();
let intrinsic = (100.0 - 90.0) * (-0.05_f64).exp();
assert!(price > 0.0);
assert!(price >= intrinsic, "price={price} < intrinsic={intrinsic}");
}
#[test]
fn floating_call_goldman_sosin_gatto() {
let p = FloatingLookbackPricer {
s: 100.0,
s_min: Some(100.0),
s_max: None,
r: 0.10,
q: 0.0,
sigma: 0.10,
t: 0.5,
option_type: OptionType::Call,
};
let price = p.price();
assert!(
(price - 8.2687).abs() < 0.01,
"floating call={price}, expected≈8.2687"
);
}
#[test]
fn floating_put_positive() {
let p = FloatingLookbackPricer {
s: 100.0,
s_min: None,
s_max: Some(110.0),
r: 0.05,
q: 0.0,
sigma: 0.2,
t: 1.0,
option_type: OptionType::Put,
};
let price = p.price();
assert!(price > 0.0, "floating lookback put={price}");
let intrinsic = (110.0 - 100.0) * (-0.05_f64).exp();
assert!(price >= intrinsic, "price={price} < intrinsic={intrinsic}");
}
#[test]
fn floating_call_more_volatile_is_more_expensive() {
let base = FloatingLookbackPricer {
s: 100.0,
s_min: Some(100.0),
s_max: None,
r: 0.05,
q: 0.0,
sigma: 0.2,
t: 1.0,
option_type: OptionType::Call,
};
let high_vol = FloatingLookbackPricer { sigma: 0.4, ..base };
assert!(high_vol.price() > base.price());
}
#[test]
fn fixed_call_geq_vanilla() {
let p = FixedLookbackPricer {
s: 100.0,
k: 100.0,
s_min: None,
s_max: Some(100.0),
r: 0.05,
q: 0.0,
sigma: 0.2,
t: 1.0,
option_type: OptionType::Call,
};
let price = p.price();
assert!(
price > 10.0,
"fixed lookback call={price}, should be > vanilla≈10.45"
);
}
#[test]
fn fixed_put_geq_vanilla() {
let p = FixedLookbackPricer {
s: 100.0,
k: 100.0,
s_min: Some(100.0),
s_max: None,
r: 0.05,
q: 0.0,
sigma: 0.2,
t: 1.0,
option_type: OptionType::Put,
};
let price = p.price();
assert!(
price > 5.0,
"fixed lookback put={price}, should be > vanilla≈5.57"
);
}
}