Skip to main content

digdigdig3_station/data/
open_interest_ext.rs

1//! Extended OpenInterest DataPoint types for Indicators and Full depth.
2//!
3//! `OpenInterestPoint` (Compact, 24 B) is unchanged — see `open_interest.rs`.
4//!
5//! OpenInterest Full = same numeric fields as Indicators (no extra stable
6//! numeric fields exist beyond what Indicators covers; `symbol` is in the
7//! file path, `business_type` is a string and rare). Full is a distinct type
8//! with the same layout as Indicators.
9
10use digdigdig3::core::types::{OpenInterest, StreamEvent};
11use serde::{Deserialize, Serialize};
12
13use crate::series::DataPoint;
14
15#[inline]
16fn opt_f64(v: Option<f64>) -> f64 {
17    v.unwrap_or(f64::NAN)
18}
19
20// ─── OpenInterestIndicatorsPoint ─────────────────────────────────────────────
21
22/// 104 B OpenInterest record for Indicators depth.
23///
24/// Layout (all LE):
25///   u64 ts_ms                                          (8)
26///   f64 open_interest, open_interest_value            (2 × 8 = 16)
27///   f64 open_interest_ccy, open_interest_usd          (2 × 8 = 16)
28///   f64 single_open_interest, sum_open_interest       (2 × 8 = 16)
29///   f64 single_open_interest_value, sum_open_interest_value (2 × 8 = 16)
30///   f64 cmc_circulating_supply                        (8)
31///   f64 trade_amount, trade_volume, trade_turnover    (3 × 8 = 24)
32///
33/// Total: 8+16+16+16+16+8+24 = 104 B
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct OpenInterestIndicatorsPoint {
36    pub ts_ms: i64,
37    pub open_interest: f64,
38    pub open_interest_value: f64,
39    pub open_interest_ccy: f64,
40    pub open_interest_usd: f64,
41    pub single_open_interest: f64,
42    pub sum_open_interest: f64,
43    pub single_open_interest_value: f64,
44    pub sum_open_interest_value: f64,
45    pub cmc_circulating_supply: f64,
46    pub trade_amount: f64,
47    pub trade_volume: f64,
48    pub trade_turnover: f64,
49}
50
51const INDICATORS_SIZE: usize = 104;
52
53impl OpenInterestIndicatorsPoint {
54    /// Construct from a REST `OpenInterest` record (e.g. from `get_open_interest_history`).
55    pub fn from_open_interest(oi: &OpenInterest) -> Self {
56        Self {
57            ts_ms: oi.timestamp,
58            open_interest: oi.open_interest,
59            open_interest_value: opt_f64(oi.open_interest_value),
60            open_interest_ccy: opt_f64(oi.open_interest_ccy),
61            open_interest_usd: opt_f64(oi.open_interest_usd),
62            single_open_interest: opt_f64(oi.single_open_interest),
63            sum_open_interest: opt_f64(oi.sum_open_interest),
64            single_open_interest_value: opt_f64(oi.single_open_interest_value),
65            sum_open_interest_value: opt_f64(oi.sum_open_interest_value),
66            cmc_circulating_supply: opt_f64(oi.cmc_circulating_supply),
67            trade_amount: opt_f64(oi.trade_amount),
68            trade_volume: opt_f64(oi.trade_volume),
69            trade_turnover: opt_f64(oi.trade_turnover),
70        }
71    }
72}
73
74impl DataPoint for OpenInterestIndicatorsPoint {
75    const RECORD_SIZE: usize = INDICATORS_SIZE;
76
77    fn encode(&self, out: &mut [u8]) {
78        out[0..8].copy_from_slice(&(self.ts_ms as u64).to_le_bytes());
79        out[8..16].copy_from_slice(&self.open_interest.to_le_bytes());
80        out[16..24].copy_from_slice(&self.open_interest_value.to_le_bytes());
81        out[24..32].copy_from_slice(&self.open_interest_ccy.to_le_bytes());
82        out[32..40].copy_from_slice(&self.open_interest_usd.to_le_bytes());
83        out[40..48].copy_from_slice(&self.single_open_interest.to_le_bytes());
84        out[48..56].copy_from_slice(&self.sum_open_interest.to_le_bytes());
85        out[56..64].copy_from_slice(&self.single_open_interest_value.to_le_bytes());
86        out[64..72].copy_from_slice(&self.sum_open_interest_value.to_le_bytes());
87        out[72..80].copy_from_slice(&self.cmc_circulating_supply.to_le_bytes());
88        out[80..88].copy_from_slice(&self.trade_amount.to_le_bytes());
89        out[88..96].copy_from_slice(&self.trade_volume.to_le_bytes());
90        out[96..104].copy_from_slice(&self.trade_turnover.to_le_bytes());
91    }
92
93    fn decode(bytes: &[u8]) -> Option<Self> {
94        if bytes.len() != INDICATORS_SIZE {
95            return None;
96        }
97        Some(Self {
98            ts_ms: u64::from_le_bytes(bytes[0..8].try_into().ok()?) as i64,
99            open_interest: f64::from_le_bytes(bytes[8..16].try_into().ok()?),
100            open_interest_value: f64::from_le_bytes(bytes[16..24].try_into().ok()?),
101            open_interest_ccy: f64::from_le_bytes(bytes[24..32].try_into().ok()?),
102            open_interest_usd: f64::from_le_bytes(bytes[32..40].try_into().ok()?),
103            single_open_interest: f64::from_le_bytes(bytes[40..48].try_into().ok()?),
104            sum_open_interest: f64::from_le_bytes(bytes[48..56].try_into().ok()?),
105            single_open_interest_value: f64::from_le_bytes(bytes[56..64].try_into().ok()?),
106            sum_open_interest_value: f64::from_le_bytes(bytes[64..72].try_into().ok()?),
107            cmc_circulating_supply: f64::from_le_bytes(bytes[72..80].try_into().ok()?),
108            trade_amount: f64::from_le_bytes(bytes[80..88].try_into().ok()?),
109            trade_volume: f64::from_le_bytes(bytes[88..96].try_into().ok()?),
110            trade_turnover: f64::from_le_bytes(bytes[96..104].try_into().ok()?),
111        })
112    }
113
114    fn timestamp_ms(&self) -> i64 { self.ts_ms }
115
116    fn from_stream_event(ev: &StreamEvent) -> Option<Self> {
117        if let StreamEvent::OpenInterestUpdate { open_interest, .. } = ev {
118            Some(Self {
119                ts_ms: open_interest.timestamp,
120                open_interest: open_interest.open_interest,
121                open_interest_value: opt_f64(open_interest.open_interest_value),
122                open_interest_ccy: opt_f64(open_interest.open_interest_ccy),
123                open_interest_usd: opt_f64(open_interest.open_interest_usd),
124                single_open_interest: opt_f64(open_interest.single_open_interest),
125                sum_open_interest: opt_f64(open_interest.sum_open_interest),
126                single_open_interest_value: opt_f64(open_interest.single_open_interest_value),
127                sum_open_interest_value: opt_f64(open_interest.sum_open_interest_value),
128                cmc_circulating_supply: opt_f64(open_interest.cmc_circulating_supply),
129                trade_amount: opt_f64(open_interest.trade_amount),
130                trade_volume: opt_f64(open_interest.trade_volume),
131                trade_turnover: opt_f64(open_interest.trade_turnover),
132            })
133        } else {
134            None
135        }
136    }
137}
138
139// ─── OpenInterestFullPoint ────────────────────────────────────────────────────
140
141/// Full OpenInterest record.
142///
143/// OpenInterest has no extra numeric fields beyond Indicators (the only
144/// remaining fields are `symbol: Option<String>` which lives in the file path,
145/// and `business_type: Option<String>` which is a rare HTX-specific tag not
146/// suitable for fixed-size encoding). Full = Indicators layout, same 104 B.
147pub type OpenInterestFullPoint = OpenInterestIndicatorsPoint;