use crate::util::error::{require_finite, FinanceError, FinanceResult};
use std::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum OptionType {
Call,
Put,
}
impl OptionType {
pub fn is_call(self) -> bool {
matches!(self, OptionType::Call)
}
pub fn is_put(self) -> bool {
matches!(self, OptionType::Put)
}
}
impl fmt::Display for OptionType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OptionType::Call => write!(f, "Call"),
OptionType::Put => write!(f, "Put"),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct BsmParams {
pub spot: f64,
pub strike: f64,
pub time_years: f64,
pub rate: f64,
pub dividend_yield: f64,
pub vol: f64,
}
impl BsmParams {
pub const fn atm_one_year(spot: f64, rate: f64, vol: f64) -> Self {
Self {
spot,
strike: spot,
time_years: 1.0,
rate,
dividend_yield: 0.0,
vol,
}
}
pub fn with_days_365_25(
spot: f64,
strike: f64,
days: f64,
rate: f64,
dividend_yield: f64,
vol: f64,
) -> Self {
Self {
spot,
strike,
time_years: days / 365.25,
rate,
dividend_yield,
vol,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ValidatedBsm {
params: BsmParams,
}
impl ValidatedBsm {
pub fn new(params: BsmParams) -> FinanceResult<Self> {
validate_bsm_params(params)?;
Ok(Self { params })
}
pub fn params(self) -> BsmParams {
self.params
}
pub fn price(self, option_type: OptionType) -> FinanceResult<f64> {
crate::derivatives::black_scholes::bsm_price(self.params, option_type)
}
pub fn greeks(
self,
option_type: OptionType,
) -> FinanceResult<crate::derivatives::black_scholes::BsmGreeks> {
crate::derivatives::black_scholes::bsm_greeks(self.params, option_type)
}
pub fn cross_greeks(
self,
option_type: OptionType,
) -> FinanceResult<crate::derivatives::black_scholes::BsmCrossGreeks> {
crate::derivatives::black_scholes::bsm_cross_greeks(self.params, option_type)
}
}
pub(crate) fn validate_bsm_params(p: BsmParams) -> FinanceResult<()> {
require_finite("spot", p.spot)?;
require_finite("strike", p.strike)?;
require_finite("time_years", p.time_years)?;
require_finite("rate", p.rate)?;
require_finite("dividend_yield", p.dividend_yield)?;
require_finite("vol", p.vol)?;
if p.spot <= 0.0 {
return Err(FinanceError::InvalidCashflow {
message: "spot must be strictly positive",
});
}
if p.strike <= 0.0 {
return Err(FinanceError::InvalidCashflow {
message: "strike must be strictly positive",
});
}
if p.time_years < 0.0 {
return Err(FinanceError::Unsolvable {
message: "time_years must be non-negative",
});
}
if p.vol < 0.0 {
return Err(FinanceError::Unsolvable {
message: "vol must be non-negative",
});
}
Ok(())
}
pub fn intrinsic(spot: f64, strike: f64, option_type: OptionType) -> FinanceResult<f64> {
require_finite("spot", spot)?;
require_finite("strike", strike)?;
if spot <= 0.0 || strike <= 0.0 {
return Err(FinanceError::InvalidCashflow {
message: "spot and strike must be strictly positive",
});
}
Ok(match option_type {
OptionType::Call => (spot - strike).max(0.0),
OptionType::Put => (strike - spot).max(0.0),
})
}
pub fn time_value(
premium: f64,
spot: f64,
strike: f64,
option_type: OptionType,
) -> FinanceResult<f64> {
require_finite("premium", premium)?;
let i = intrinsic(spot, strike, option_type)?;
Ok((premium - i).max(0.0))
}
pub fn forward_moneyness(p: BsmParams) -> FinanceResult<f64> {
validate_bsm_params(p)?;
let f = p.spot * ((p.rate - p.dividend_yield) * p.time_years).exp();
Ok(f / p.strike)
}
pub fn spot_moneyness(spot: f64, strike: f64) -> FinanceResult<f64> {
require_finite("spot", spot)?;
require_finite("strike", strike)?;
if spot <= 0.0 || strike <= 0.0 {
return Err(FinanceError::InvalidCashflow {
message: "spot and strike must be strictly positive",
});
}
Ok(spot / strike)
}