Skip to main content

digdigdig3_station/data/
trade.rs

1use digdigdig3::core::types::{StreamEvent, TradeSide};
2use serde::{Deserialize, Serialize};
3
4use crate::series::DataPoint;
5
6/// On-disk record (48 bytes LE):
7///   u64 ts_ms (8)
8///   f64 price (8)
9///   f64 quantity (8)
10///   u8  side (1)  0=Buy 1=Sell
11///   u64 trade_id_hash (8) — fnv1a-64 of original id string
12///   23 bytes reserved (zero-padded) — flags / sequence (future use)
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct TradePoint {
15    pub ts_ms: i64,
16    pub price: f64,
17    pub quantity: f64,
18    pub side: u8,
19    pub trade_id_hash: u64,
20}
21
22const SIZE: usize = 48;
23
24impl TradePoint {
25    pub fn from_public(t: &digdigdig3::core::types::PublicTrade) -> Self {
26        Self {
27            ts_ms: t.timestamp,
28            price: t.price,
29            quantity: t.quantity,
30            side: match t.side {
31                TradeSide::Buy => 0,
32                TradeSide::Sell => 1,
33            },
34            trade_id_hash: fnv1a_64(t.id.as_bytes()),
35        }
36    }
37
38    pub fn side_label(&self) -> &'static str {
39        match self.side {
40            0 => "Buy",
41            1 => "Sell",
42            _ => "?",
43        }
44    }
45}
46
47impl DataPoint for TradePoint {
48    const RECORD_SIZE: usize = SIZE;
49
50    fn encode(&self, out: &mut [u8]) {
51        out[0..8].copy_from_slice(&(self.ts_ms as u64).to_le_bytes());
52        out[8..16].copy_from_slice(&self.price.to_le_bytes());
53        out[16..24].copy_from_slice(&self.quantity.to_le_bytes());
54        out[24] = self.side;
55        out[25..33].copy_from_slice(&self.trade_id_hash.to_le_bytes());
56        // bytes 33..48 reserved (already zero in caller-allocated buffer)
57    }
58
59    fn decode(bytes: &[u8]) -> Option<Self> {
60        if bytes.len() != SIZE {
61            return None;
62        }
63        let ts_ms = u64::from_le_bytes(bytes[0..8].try_into().ok()?) as i64;
64        let price = f64::from_le_bytes(bytes[8..16].try_into().ok()?);
65        let quantity = f64::from_le_bytes(bytes[16..24].try_into().ok()?);
66        let side = bytes[24];
67        let trade_id_hash = u64::from_le_bytes(bytes[25..33].try_into().ok()?);
68        Some(Self { ts_ms, price, quantity, side, trade_id_hash })
69    }
70
71    fn timestamp_ms(&self) -> i64 {
72        self.ts_ms
73    }
74
75    fn from_stream_event(ev: &StreamEvent) -> Option<Self> {
76        if let StreamEvent::Trade { trade, .. } = ev {
77            Some(Self::from_public(trade))
78        } else {
79            None
80        }
81    }
82}
83
84fn fnv1a_64(bytes: &[u8]) -> u64 {
85    let mut h: u64 = 0xcbf29ce484222325;
86    for b in bytes {
87        h ^= *b as u64;
88        h = h.wrapping_mul(0x100000001b3);
89    }
90    h
91}