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}