use crate::config::SolverConfig;
use crate::greeks::compute_greeks;
use crate::iv_solver::solve_iv;
use crate::pricing::{call_price, d1_d2, intrinsic_value, price, put_price, vega};
use crate::types::{InstrumentGreeks, SolverResult};
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub struct BlackInputs {
pub f: f64,
pub k: f64,
pub t: f64,
pub sigma: f64,
pub r: f64,
}
impl BlackInputs {
#[must_use]
pub const fn new(f: f64, k: f64, t: f64, sigma: f64, r: f64) -> Self {
Self { f, k, t, sigma, r }
}
#[must_use]
pub fn d1_d2(&self) -> (f64, f64) {
d1_d2(self.f, self.k, self.t, self.sigma)
}
#[must_use]
pub fn call_price(&self) -> f64 {
call_price(self.f, self.k, self.t, self.sigma, self.r)
}
#[must_use]
pub fn put_price(&self) -> f64 {
put_price(self.f, self.k, self.t, self.sigma, self.r)
}
#[must_use]
pub fn price(&self, is_call: bool) -> f64 {
price(self.f, self.k, self.t, self.sigma, self.r, is_call)
}
#[must_use]
pub fn vega(&self) -> f64 {
vega(self.f, self.k, self.t, self.sigma, self.r)
}
#[must_use]
pub fn greeks(&self, is_call: bool) -> InstrumentGreeks {
compute_greeks(self.f, self.k, self.t, self.sigma, self.r, is_call)
}
#[must_use]
pub fn intrinsic_value(&self, is_call: bool) -> f64 {
intrinsic_value(self.f, self.k, is_call)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub struct IvQuery {
pub market_price: f64,
pub f: f64,
pub k: f64,
pub t: f64,
pub r: f64,
pub is_call: bool,
}
impl IvQuery {
#[must_use]
pub const fn new(market_price: f64, f: f64, k: f64, t: f64, r: f64, is_call: bool) -> Self {
Self {
market_price,
f,
k,
t,
r,
is_call,
}
}
#[must_use]
pub fn solve(&self, config: &SolverConfig) -> SolverResult {
solve_iv(
self.market_price,
self.f,
self.k,
self.t,
self.r,
self.is_call,
config,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn black_inputs_match_free_functions() {
let (f, k, t, s, r) = (100.0, 105.0, 0.5, 0.25, 0.03);
let bi = BlackInputs::new(f, k, t, s, r);
assert_eq!(bi.call_price(), call_price(f, k, t, s, r));
assert_eq!(bi.put_price(), put_price(f, k, t, s, r));
assert_eq!(bi.price(true), price(f, k, t, s, r, true));
assert_eq!(bi.price(false), price(f, k, t, s, r, false));
assert_eq!(bi.vega(), vega(f, k, t, s, r));
assert_eq!(bi.d1_d2(), d1_d2(f, k, t, s));
assert_eq!(bi.intrinsic_value(true), intrinsic_value(f, k, true));
let g = bi.greeks(true);
let g2 = compute_greeks(f, k, t, s, r, true);
assert_eq!(g.delta, g2.delta);
assert_eq!(g.gamma, g2.gamma);
assert_eq!(g.vega, g2.vega);
assert_eq!(g.theta, g2.theta);
assert_eq!(g.rho, g2.rho);
}
#[test]
fn iv_query_round_trip() {
let cfg = SolverConfig::default();
let market = call_price(100.0, 100.0, 1.0, 0.20, 0.0);
let result = IvQuery::new(market, 100.0, 100.0, 1.0, 0.0, true).solve(&cfg);
assert!(result.converged);
assert!((result.iv - 0.20).abs() < 1e-6);
}
#[test]
fn const_construction() {
const BI: BlackInputs = BlackInputs::new(100.0, 100.0, 1.0, 0.20, 0.0);
const Q: IvQuery = IvQuery::new(7.96, 100.0, 100.0, 1.0, 0.0, true);
let (bi, q) = (BI, Q);
assert_eq!(bi.k, 100.0);
assert!(q.is_call);
}
}