use crate::{SpecialFn, lets_be_rational};
use bon::Builder;
#[derive(Builder)]
#[builder(const, derive(Clone, Debug),
finish_fn(name = build_unchecked,
doc{
/// Build without performing any validation.
///
/// This constructor constructs the `ImpliedBlackVolatility` directly from
/// the builder's fields and does **not** check for NaNs, infinities, or
/// sign constraints. Use only when you are certain the inputs are valid
/// or when you want to avoid the cost of runtime validation.
})
)]
pub struct ImpliedBlackVolatility {
forward: f64,
strike: f64,
expiry: f64,
is_call: bool,
option_price: f64,
}
impl<S: implied_black_volatility_builder::IsComplete> ImpliedBlackVolatilityBuilder<S> {
pub const fn build(self) -> Option<ImpliedBlackVolatility> {
let implied_black_volatility = self.build_unchecked();
if !(implied_black_volatility.forward > 0.0)
|| implied_black_volatility.forward.is_infinite()
{
return None;
}
if !(implied_black_volatility.strike > 0.0) || implied_black_volatility.strike.is_infinite()
{
return None;
}
if !(implied_black_volatility.expiry >= 0.0) {
return None;
}
if !(implied_black_volatility.option_price >= 0.0)
|| implied_black_volatility.option_price.is_infinite()
{
return None;
}
Some(implied_black_volatility)
}
}
impl ImpliedBlackVolatility {
#[must_use]
#[inline(always)]
pub fn calculate<SpFn: SpecialFn>(&self) -> Option<f64> {
if self.is_call {
lets_be_rational::implied_black_volatility_input_unchecked::<SpFn, true>(
self.option_price,
self.forward,
self.strike,
self.expiry,
)
} else {
lets_be_rational::implied_black_volatility_input_unchecked::<SpFn, false>(
self.option_price,
self.forward,
self.strike,
self.expiry,
)
}
}
}
#[cfg(test)]
mod tests {
use crate::DefaultSpecialFn;
use crate::builder::implied_black_volatility::ImpliedBlackVolatility;
#[test]
const fn normal_const() {
const PRICE: f64 = 10.0;
const F: f64 = 100.0;
const K: f64 = 100.0;
const T: f64 = 1.0;
const Q: bool = true;
const IV_BUILDER: Option<ImpliedBlackVolatility> = ImpliedBlackVolatility::builder()
.option_price(PRICE)
.forward(F)
.strike(K)
.expiry(T)
.is_call(Q)
.build();
assert!(IV_BUILDER.is_some());
}
#[test]
fn strike_anomaly() {
for k in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 0.0] {
let price = 100.0;
let f = 100.0;
let t = 1.0;
const Q: bool = true;
assert!(
ImpliedBlackVolatility::builder()
.option_price(price)
.forward(f)
.strike(k)
.expiry(t)
.is_call(Q)
.build()
.is_none()
);
}
}
#[test]
fn strike_boundary() {
let price = 100.0;
let f = 100.0;
let k = f64::MIN_POSITIVE;
let t = 1.0;
const Q: bool = true;
assert!(
ImpliedBlackVolatility::builder()
.option_price(price)
.forward(f)
.strike(k)
.expiry(t)
.is_call(Q)
.build()
.is_some()
);
}
#[test]
fn forward_anomaly() {
for f in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 0.0] {
let price = 100.0;
let k = 100.0;
let t = 1.0;
const Q: bool = true;
assert!(
ImpliedBlackVolatility::builder()
.option_price(price)
.forward(f)
.strike(k)
.expiry(t)
.is_call(Q)
.build()
.is_none()
);
}
}
#[test]
fn price_anomaly() {
for price in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
let f = 100.0;
let t = 1.0;
let k = 100.0;
const Q: bool = true;
assert!(
ImpliedBlackVolatility::builder()
.option_price(price)
.forward(f)
.strike(k)
.expiry(t)
.is_call(Q)
.build()
.is_none()
);
}
}
#[test]
fn price_below_intrinsic() {
let price = 10.0;
let f = 120.0;
let t = 1.0;
let k = 100.0;
const Q: bool = true;
let iv_builder = ImpliedBlackVolatility::builder()
.option_price(price)
.forward(f)
.strike(k)
.expiry(t)
.is_call(Q)
.build();
assert!(iv_builder.is_some());
let iv = iv_builder.unwrap();
assert!(iv.calculate::<DefaultSpecialFn>().is_none());
}
#[test]
fn time_anomaly() {
for t in [f64::NAN, f64::NEG_INFINITY] {
let price = 10.0;
let f = 100.0;
let k = 100.0;
const Q: bool = true;
assert!(
ImpliedBlackVolatility::builder()
.option_price(price)
.forward(f)
.strike(k)
.expiry(t)
.is_call(Q)
.build()
.is_none()
);
}
}
#[test]
fn time_zero() {
let price = 10.0;
let f = 120.0;
let k = 100.0;
let t = 0.0;
let q = true;
let vol = ImpliedBlackVolatility::builder()
.option_price(price)
.forward(f)
.strike(k)
.expiry(t)
.is_call(q)
.build()
.unwrap()
.calculate::<DefaultSpecialFn>();
assert!(vol.is_none());
let price = 20.0;
let f = 120.0;
let k = 100.0;
let t = 0.0;
let vol = ImpliedBlackVolatility::builder()
.option_price(price)
.forward(f)
.strike(k)
.expiry(t)
.is_call(q)
.build()
.unwrap()
.calculate::<DefaultSpecialFn>();
assert_eq!(vol.unwrap(), 0.0);
}
#[test]
fn time_inf() {
let price = 10.0;
let f = 100.0;
let k = 100.0;
let t = f64::INFINITY;
const Q: bool = true;
let vol = ImpliedBlackVolatility::builder()
.option_price(price)
.forward(f)
.strike(k)
.expiry(t)
.is_call(Q)
.build()
.unwrap()
.calculate::<DefaultSpecialFn>()
.unwrap();
assert_eq!(vol, 0.0);
}
}