use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
pub struct Greeks {
pub delta: f64,
pub gamma: f64,
pub vega: f64,
pub theta: f64,
pub rho: f64,
pub vanna: f64,
pub charm: f64,
pub gamma_p: f64,
pub zomma: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct PricingResult {
pub pv: f64,
pub greeks: Greeks,
pub std_err: Option<f64>,
}
impl PricingResult {
pub fn from_pv(pv: f64) -> Self {
PricingResult { pv, greeks: Greeks::default(), std_err: None }
}
}
#[cfg(test)]
mod tests {
use crate::core::trade::PutOrCall;
use crate::core::traits::Instrument;
use crate::equity::builder::EquityOptionBuilder;
use crate::equity::utils::Engine;
fn vanilla(engine: Engine) -> crate::equity::vanilla_option::EquityOption {
EquityOptionBuilder::new()
.spot(100.0)
.strike(100.0)
.flat_vol(0.30)
.flat_rate(0.05)
.years_to_maturity(1.0)
.vanilla(PutOrCall::Call)
.engine(engine)
.build().expect("option must build")
}
#[test]
fn price_matches_individual_accessors() {
let option = vanilla(Engine::BlackScholes);
let result = option.price().unwrap();
assert_eq!(result.pv, option.npv());
assert_eq!(result.greeks.delta, option.delta());
assert_eq!(result.greeks.gamma, option.gamma());
assert_eq!(result.greeks.vega, option.vega());
assert_eq!(result.greeks.theta, option.theta());
assert_eq!(result.greeks.rho, option.rho());
assert_eq!(result.greeks.vanna, option.vanna());
assert_eq!(result.greeks.charm, option.charm());
assert_eq!(result.greeks.zomma, option.zomma());
assert_eq!(result.std_err, None, "deterministic engine has no std_err");
}
#[test]
fn monte_carlo_price_reports_std_err_and_reproducible_pv() {
let option = vanilla(Engine::MonteCarlo);
let result = option.price().unwrap();
let se = result.std_err.expect("MC engine must report a standard error");
assert!(se > 0.0 && se.is_finite());
assert_eq!(result.pv, option.npv());
}
#[test]
fn unsupported_combination_errors_through_price() {
use crate::core::errors::RustyQLibError;
let mut option = vanilla(Engine::MonteCarlo);
option.payoff = Box::new(crate::equity::vanilla_option::LookbackPayoff {
put_or_call: PutOrCall::Call,
exercise_style: crate::core::utils::ContractStyle::European,
lookback_type: crate::equity::vanilla_option::LookbackType::FloatingStrike,
});
option.engine = crate::equity::utils::PricingEngine::Binomial(Default::default());
match option.price() {
Err(RustyQLibError::UnsupportedEngine(msg)) => {
assert!(msg.contains("Binomial"), "should explain the refusal: {msg}")
}
other => panic!("expected UnsupportedEngine, got {other:?}"),
}
}
}