rustyqlib/core/
results.rs1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
11pub struct Greeks {
12 pub delta: f64,
14 pub gamma: f64,
16 pub vega: f64,
18 pub theta: f64,
20 pub rho: f64,
22 pub vanna: f64,
24 pub charm: f64,
26 pub gamma_p: f64,
28 pub zomma: f64,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
36pub struct PricingResult {
37 pub pv: f64,
39 pub greeks: Greeks,
41 pub std_err: Option<f64>,
43}
44
45impl PricingResult {
46 pub fn from_pv(pv: f64) -> Self {
50 PricingResult { pv, greeks: Greeks::default(), std_err: None }
51 }
52}
53
54#[cfg(test)]
55mod tests {
56 use crate::core::trade::PutOrCall;
57 use crate::core::traits::Instrument;
58 use crate::equity::builder::EquityOptionBuilder;
59 use crate::equity::utils::Engine;
60
61 fn vanilla(engine: Engine) -> crate::equity::vanilla_option::EquityOption {
62 EquityOptionBuilder::new()
63 .spot(100.0)
64 .strike(100.0)
65 .flat_vol(0.30)
66 .flat_rate(0.05)
67 .years_to_maturity(1.0)
68 .vanilla(PutOrCall::Call)
69 .engine(engine)
70 .build().expect("option must build")
71 }
72
73 #[test]
74 fn price_matches_individual_accessors() {
75 let option = vanilla(Engine::BlackScholes);
76 let result = option.price().unwrap();
77 assert_eq!(result.pv, option.npv());
78 assert_eq!(result.greeks.delta, option.delta());
79 assert_eq!(result.greeks.gamma, option.gamma());
80 assert_eq!(result.greeks.vega, option.vega());
81 assert_eq!(result.greeks.theta, option.theta());
82 assert_eq!(result.greeks.rho, option.rho());
83 assert_eq!(result.greeks.vanna, option.vanna());
84 assert_eq!(result.greeks.charm, option.charm());
85 assert_eq!(result.greeks.zomma, option.zomma());
86 assert_eq!(result.std_err, None, "deterministic engine has no std_err");
87 }
88
89 #[test]
90 fn monte_carlo_price_reports_std_err_and_reproducible_pv() {
91 let option = vanilla(Engine::MonteCarlo);
92 let result = option.price().unwrap();
93 let se = result.std_err.expect("MC engine must report a standard error");
94 assert!(se > 0.0 && se.is_finite());
95 assert_eq!(result.pv, option.npv());
97 }
98
99 #[test]
100 fn unsupported_combination_errors_through_price() {
101 use crate::core::errors::RustyQLibError;
102 let mut option = vanilla(Engine::MonteCarlo);
105 option.payoff = Box::new(crate::equity::vanilla_option::LookbackPayoff {
106 put_or_call: PutOrCall::Call,
107 exercise_style: crate::core::utils::ContractStyle::European,
108 lookback_type: crate::equity::vanilla_option::LookbackType::FloatingStrike,
109 });
110 option.engine = crate::equity::utils::PricingEngine::Binomial(Default::default());
111 match option.price() {
112 Err(RustyQLibError::UnsupportedEngine(msg)) => {
113 assert!(msg.contains("Binomial"), "should explain the refusal: {msg}")
114 }
115 other => panic!("expected UnsupportedEngine, got {other:?}"),
116 }
117 }
118}