Skip to main content

rustrade_data/subscription/
greeks.rs

1//! Option Greeks subscription type.
2//!
3//! This module defines the [`OptionGreeks`](crate::subscription::greeks::OptionGreeks) data type for option analytics
4//! (delta, gamma, theta, vega, rho, implied volatility).
5//!
6//! Unlike other subscription types in this module, Greeks are typically
7//! computed by the exchange/broker from live market data rather than being
8//! raw market data themselves.
9
10use serde::{Deserialize, Serialize};
11
12/// Option Greeks values.
13///
14/// All fields are optional because the data source may not return all Greeks
15/// depending on the contract type, market state, or subscription level.
16///
17/// # Fields
18///
19/// - `delta`: Rate of change of option price with respect to underlying price
20/// - `gamma`: Rate of change of delta with respect to underlying price
21/// - `theta`: Rate of change of option price with respect to time (per day)
22/// - `vega`: Rate of change of option price with respect to volatility
23/// - `rho`: Rate of change of option price with respect to the risk-free interest rate. `None`
24///   wherever the source does not publish it, which today includes IBKR and Massive
25/// - `implied_volatility`: Market-implied volatility
26/// - `theoretical_price`: Model price computed by the exchange/broker
27/// - `underlying_price`: Current underlying price used in computation
28///
29/// # Note on Precision
30///
31/// Values are stored as `f64` rather than `Decimal` because Greeks are
32/// analytics (not monetary values) and the precision loss is acceptable.
33#[non_exhaustive]
34#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
35pub struct OptionGreeks {
36    pub delta: Option<f64>,
37    pub gamma: Option<f64>,
38    pub theta: Option<f64>,
39    pub vega: Option<f64>,
40    pub rho: Option<f64>,
41    pub implied_volatility: Option<f64>,
42    pub theoretical_price: Option<f64>,
43    pub underlying_price: Option<f64>,
44}
45
46impl OptionGreeks {
47    /// Returns true if at least one Greek (delta, gamma, theta, vega, rho, or
48    /// implied volatility) is present.
49    ///
50    /// Does NOT consider `theoretical_price` or `underlying_price` — a tick
51    /// containing only those fields is treated as having no Greek data.
52    pub fn has_any_greek(&self) -> bool {
53        self.delta.is_some()
54            || self.gamma.is_some()
55            || self.theta.is_some()
56            || self.vega.is_some()
57            || self.rho.is_some()
58            || self.implied_volatility.is_some()
59    }
60}
61
62#[cfg(test)]
63#[allow(clippy::unwrap_used)] // Test code: panics on bad input are acceptable
64mod tests {
65    use super::*;
66
67    #[test]
68    fn option_greeks_default_has_no_values() {
69        let greeks = OptionGreeks::default();
70        assert!(!greeks.has_any_greek());
71    }
72
73    #[test]
74    fn option_greeks_partial_has_any_greek() {
75        let greeks = OptionGreeks {
76            delta: Some(0.55),
77            ..Default::default()
78        };
79        assert!(greeks.has_any_greek());
80    }
81
82    #[test]
83    fn option_greeks_full_has_any_greek() {
84        let greeks = OptionGreeks {
85            delta: Some(0.55),
86            gamma: Some(0.02),
87            theta: Some(-0.05),
88            vega: Some(0.15),
89            rho: Some(0.01),
90            implied_volatility: Some(0.25),
91            theoretical_price: Some(5.50),
92            underlying_price: Some(150.0),
93        };
94        assert!(greeks.has_any_greek());
95    }
96
97    #[test]
98    fn option_greeks_rho_only_has_any_greek() {
99        let greeks = OptionGreeks {
100            rho: Some(0.01),
101            ..Default::default()
102        };
103        assert!(greeks.has_any_greek());
104    }
105
106    #[test]
107    fn option_greeks_serialised_before_rho_existed_still_deserialise() {
108        // Persisted greeks written before the field was added carry no `rho` key at all; they must
109        // still decode, as an explicit unknown rather than a zero.
110        let greeks: OptionGreeks = serde_json::from_str(
111            r#"{"delta":0.5,"gamma":null,"theta":null,"vega":null,
112                "implied_volatility":null,"theoretical_price":null,"underlying_price":null}"#,
113        )
114        .unwrap();
115        assert_eq!(greeks.rho, None);
116        assert_eq!(greeks.delta, Some(0.5));
117    }
118
119    #[test]
120    fn option_greeks_price_only_has_no_greek() {
121        let greeks = OptionGreeks {
122            theoretical_price: Some(5.50),
123            underlying_price: Some(150.0),
124            ..Default::default()
125        };
126        assert!(!greeks.has_any_greek());
127    }
128}