Skip to main content

digdigdig3_station/data/
position_update.rs

1use digdigdig3::core::types::StreamEvent;
2use serde::{Deserialize, Serialize};
3
4use crate::series::DataPoint;
5
6/// On-disk record (64 bytes LE) for a futures position change event:
7///   u64  ts_ms             (8)
8///   u8   side              (1)  — 0=Long 1=Short 2=Net/Both
9///   u8   _pad              (7)
10///   f64  qty               (8)  — position size (signed in net mode; absolute here)
11///   f64  entry_price       (8)
12///   f64  mark_price        (8)  — 0.0 if unknown
13///   f64  unrealized_pnl    (8)
14///   f64  realized_pnl      (8)  — 0.0 if not provided
15///   f64  liquidation_price (8)  — 0.0 if unknown
16/// Total: 64 bytes
17#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18pub struct PositionUpdatePoint {
19    pub ts_ms: i64,
20    /// 0=Long, 1=Short, 2=Net/Both
21    pub side: u8,
22    pub qty: f64,
23    pub entry_price: f64,
24    pub mark_price: f64,
25    pub unrealized_pnl: f64,
26    pub realized_pnl: f64,
27    pub liquidation_price: f64,
28}
29
30const SIZE: usize = 64;
31
32impl PositionUpdatePoint {
33    pub fn side_label(&self) -> &'static str {
34        match self.side {
35            0 => "Long",
36            1 => "Short",
37            _ => "Net",
38        }
39    }
40}
41
42impl DataPoint for PositionUpdatePoint {
43    const RECORD_SIZE: usize = SIZE;
44
45    fn encode(&self, out: &mut [u8]) {
46        out[0..8].copy_from_slice(&(self.ts_ms as u64).to_le_bytes());
47        out[8] = self.side;
48        // out[9..16] = padding
49        out[16..24].copy_from_slice(&self.qty.to_le_bytes());
50        out[24..32].copy_from_slice(&self.entry_price.to_le_bytes());
51        out[32..40].copy_from_slice(&self.mark_price.to_le_bytes());
52        out[40..48].copy_from_slice(&self.unrealized_pnl.to_le_bytes());
53        out[48..56].copy_from_slice(&self.realized_pnl.to_le_bytes());
54        out[56..64].copy_from_slice(&self.liquidation_price.to_le_bytes());
55    }
56
57    fn decode(bytes: &[u8]) -> Option<Self> {
58        if bytes.len() != SIZE {
59            return None;
60        }
61        let ts_ms = u64::from_le_bytes(bytes[0..8].try_into().ok()?) as i64;
62        let side = bytes[8];
63        let qty = f64::from_le_bytes(bytes[16..24].try_into().ok()?);
64        let entry_price = f64::from_le_bytes(bytes[24..32].try_into().ok()?);
65        let mark_price = f64::from_le_bytes(bytes[32..40].try_into().ok()?);
66        let unrealized_pnl = f64::from_le_bytes(bytes[40..48].try_into().ok()?);
67        let realized_pnl = f64::from_le_bytes(bytes[48..56].try_into().ok()?);
68        let liquidation_price = f64::from_le_bytes(bytes[56..64].try_into().ok()?);
69        Some(Self {
70            ts_ms,
71            side,
72            qty,
73            entry_price,
74            mark_price,
75            unrealized_pnl,
76            realized_pnl,
77            liquidation_price,
78        })
79    }
80
81    fn timestamp_ms(&self) -> i64 {
82        self.ts_ms
83    }
84
85    fn from_stream_event(ev: &StreamEvent) -> Option<Self> {
86        if let StreamEvent::PositionUpdate { event, .. } = ev {
87            use digdigdig3::core::types::PositionSide;
88            let side = match event.side {
89                PositionSide::Long => 0,
90                PositionSide::Short => 1,
91                PositionSide::Both => 2,
92            };
93            Some(Self {
94                ts_ms: event.timestamp,
95                side,
96                qty: event.quantity,
97                entry_price: event.entry_price,
98                mark_price: event.mark_price.unwrap_or(0.0),
99                unrealized_pnl: event.unrealized_pnl,
100                realized_pnl: event.realized_pnl.unwrap_or(0.0),
101                liquidation_price: event.liquidation_price.unwrap_or(0.0),
102            })
103        } else {
104            None
105        }
106    }
107}