rusty_feeder/exchange/coinbase/data/
orderbook.rs

1use rust_decimal::Decimal;
2use serde::{Deserialize, Serialize};
3use smallvec::SmallVec;
4use smartstring::alias::String;
5
6type PriceLevel = [String; 2]; // [price, size]
7
8/// Coinbase WebSocket Level2 snapshot message
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Level2Snapshot {
11    /// Message type (always "snapshot")
12    #[serde(rename = "type")]
13    pub message_type: String,
14    /// Trading pair identifier
15    pub product_id: String,
16    /// Bid price levels as [price, size] pairs
17    pub bids: SmallVec<[PriceLevel; 32]>,
18    /// Ask price levels as [price, size] pairs
19    pub asks: SmallVec<[PriceLevel; 32]>,
20}
21
22/// Coinbase WebSocket Level2 update message
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct Level2Update {
25    /// Message type (always "l2update")
26    #[serde(rename = "type")]
27    pub message_type: String,
28    /// Trading pair identifier
29    pub product_id: String,
30    /// Update timestamp in ISO 8601 format
31    pub time: String,
32    /// Price level changes [side, price, size]
33    pub changes: SmallVec<[Level2Change; 32]>,
34}
35
36/// Level2 change entry - [side, price, size]
37pub type Level2Change = [String; 3];
38
39/// Parsed Level2 update for efficient processing
40#[derive(Debug, Clone)]
41pub struct ParsedLevel2Update {
42    /// Trading pair identifier
43    pub product_id: String,
44    /// Update timestamp
45    pub time: String,
46    /// Bid updates as (price, size) tuples
47    pub bids: SmallVec<[(Decimal, Decimal); 32]>,
48    /// Ask updates as (price, size) tuples
49    pub asks: SmallVec<[(Decimal, Decimal); 32]>,
50}
51
52impl ParsedLevel2Update {
53    /// Convert a Level2Update to a ParsedLevel2Update with typed decimals
54    #[must_use]
55    pub fn from_update(update: Level2Update) -> Self {
56        let mut bids = SmallVec::with_capacity(20);
57        let mut asks = SmallVec::with_capacity(20);
58
59        for change in update.changes {
60            if change.len() != 3 {
61                continue;
62            }
63
64            let side = &change[0];
65            let price = match change[1].parse::<Decimal>() {
66                Ok(p) => p,
67                Err(_) => continue,
68            };
69            let size = match change[2].parse::<Decimal>() {
70                Ok(s) => s,
71                Err(_) => continue,
72            };
73
74            match side.as_str() {
75                "buy" => bids.push((price, size)),
76                "sell" => asks.push((price, size)),
77                _ => continue,
78            }
79        }
80
81        Self {
82            product_id: update.product_id,
83            time: update.time,
84            bids,
85            asks,
86        }
87    }
88}
89
90/// Parsed Level2 snapshot for efficient processing
91#[derive(Debug, Clone)]
92pub struct ParsedLevel2Snapshot {
93    /// Trading pair identifier
94    pub product_id: String,
95    /// Bid levels as (price, size) tuples
96    pub bids: SmallVec<[(Decimal, Decimal); 32]>,
97    /// Ask levels as (price, size) tuples
98    pub asks: SmallVec<[(Decimal, Decimal); 32]>,
99}
100
101impl ParsedLevel2Snapshot {
102    /// Convert a Level2Snapshot to a ParsedLevel2Snapshot with typed decimals
103    #[must_use]
104    pub fn from_snapshot(snapshot: Level2Snapshot) -> Self {
105        let bids = snapshot
106            .bids
107            .iter()
108            .filter_map(|level| {
109                if level.len() != 2 {
110                    return None;
111                }
112                let price = level[0].parse::<Decimal>().ok()?;
113                let size = level[1].parse::<Decimal>().ok()?;
114                Some((price, size))
115            })
116            .collect();
117
118        let asks = snapshot
119            .asks
120            .iter()
121            .filter_map(|level| {
122                if level.len() != 2 {
123                    return None;
124                }
125                let price = level[0].parse::<Decimal>().ok()?;
126                let size = level[1].parse::<Decimal>().ok()?;
127                Some((price, size))
128            })
129            .collect();
130
131        Self {
132            product_id: snapshot.product_id,
133            bids,
134            asks,
135        }
136    }
137}