Skip to main content

futu_cache/
option_chain_statistic.rs

1//! Option chain statistic cache for 3255/3257 break-even probability.
2//!
3//! C++ writes CMD20675 responses into `INNData_Qot_ChainStatistic`.
4//! The local option break-even engine reads:
5//! - `strike_date_iv` by `(underlying_stock_id, strike_date_sec)`
6//! - `underlying_iv` / `underlying_hv` by `underlying_stock_id`
7
8use std::collections::HashMap;
9use std::sync::Arc;
10
11use parking_lot::RwLock;
12
13#[derive(Debug, Clone, Default, PartialEq, Eq)]
14pub struct OptionChainStatisticSnapshot {
15    pub strike_date_iv_1e5: HashMap<i64, i64>,
16    pub underlying_iv_1e5: Option<i64>,
17    pub underlying_hv_1e5: Option<i64>,
18}
19
20#[derive(Debug, Default)]
21pub struct OptionChainStatisticCache {
22    by_underlying: RwLock<HashMap<u64, OptionChainStatisticSnapshot>>,
23}
24
25impl OptionChainStatisticCache {
26    #[must_use]
27    pub fn new() -> Arc<Self> {
28        Arc::new(Self::default())
29    }
30
31    pub fn replace_underlying(
32        &self,
33        underlying_stock_id: u64,
34        snapshot: OptionChainStatisticSnapshot,
35    ) {
36        if underlying_stock_id == 0 {
37            return;
38        }
39        self.by_underlying
40            .write()
41            .insert(underlying_stock_id, snapshot);
42    }
43
44    #[must_use]
45    pub fn snapshot(&self, underlying_stock_id: u64) -> Option<OptionChainStatisticSnapshot> {
46        if underlying_stock_id == 0 {
47            return None;
48        }
49        self.by_underlying.read().get(&underlying_stock_id).cloned()
50    }
51
52    #[must_use]
53    pub fn strike_date_iv_decimal(
54        &self,
55        underlying_stock_id: u64,
56        strike_date_sec: i64,
57    ) -> Option<f64> {
58        let raw = self
59            .snapshot(underlying_stock_id)?
60            .strike_date_iv_1e5
61            .get(&strike_date_sec)
62            .copied()?;
63        (raw != 0).then(|| iv_1e5_to_decimal(raw))
64    }
65
66    #[must_use]
67    pub fn underlying_iv_decimal(&self, underlying_stock_id: u64) -> Option<f64> {
68        self.snapshot(underlying_stock_id)?
69            .underlying_iv_1e5
70            .map(iv_1e5_to_decimal)
71    }
72
73    #[must_use]
74    pub fn underlying_hv_decimal(&self, underlying_stock_id: u64) -> Option<f64> {
75        self.snapshot(underlying_stock_id)?
76            .underlying_hv_1e5
77            .map(iv_1e5_to_decimal)
78    }
79}
80
81#[must_use]
82pub fn iv_1e5_to_decimal(raw: i64) -> f64 {
83    raw as f64 / 100_000.0
84}
85
86#[cfg(test)]
87mod tests;