Skip to main content

coinbase_advanced/ws/
messages.rs

1//! WebSocket message types.
2
3use serde::{Deserialize, Serialize};
4
5use super::channels::ChannelName;
6
7/// A message received from the WebSocket.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct Message {
10    /// The channel the message is from.
11    pub channel: ChannelName,
12    /// The client ID for the message.
13    pub client_id: String,
14    /// The timestamp for the message.
15    pub timestamp: String,
16    /// The sequence number for the message.
17    pub sequence_num: u64,
18    /// The events in the message.
19    pub events: Events,
20}
21
22/// Events that can be received in a message.
23#[derive(Debug, Clone, Serialize, Deserialize)]
24#[serde(untagged)]
25pub enum Events {
26    /// Status events.
27    Status(Vec<StatusEvent>),
28    /// Candle events.
29    Candles(Vec<CandlesEvent>),
30    /// Ticker events.
31    Ticker(Vec<TickerEvent>),
32    /// Level 2 order book events.
33    Level2(Vec<Level2Event>),
34    /// User events.
35    User(Vec<UserEvent>),
36    /// Market trade events.
37    MarketTrades(Vec<MarketTradesEvent>),
38    /// Heartbeat events.
39    Heartbeats(Vec<HeartbeatsEvent>),
40    /// Subscription confirmation events.
41    Subscriptions(Vec<SubscriptionsEvent>),
42    /// Futures balance summary events.
43    FuturesBalanceSummary(Vec<FuturesBalanceSummaryEvent>),
44}
45
46/// Event type (snapshot or update).
47#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
48#[serde(rename_all = "snake_case")]
49pub enum EventType {
50    /// Initial snapshot of data.
51    Snapshot,
52    /// Incremental update.
53    Update,
54}
55
56/// Status event containing product status updates.
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct StatusEvent {
59    /// Event type.
60    pub r#type: EventType,
61    /// Product updates.
62    pub products: Vec<ProductStatus>,
63}
64
65/// Product status information.
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct ProductStatus {
68    /// Product type.
69    pub product_type: String,
70    /// Product ID.
71    pub id: String,
72    /// Base currency symbol.
73    pub base_currency: String,
74    /// Quote currency symbol.
75    pub quote_currency: String,
76    /// Minimum base increment.
77    pub base_increment: String,
78    /// Minimum quote increment.
79    pub quote_increment: String,
80    /// Display name.
81    pub display_name: String,
82    /// Product status.
83    pub status: String,
84    /// Additional status message.
85    #[serde(default)]
86    pub status_message: String,
87    /// Minimum market funds.
88    pub min_market_funds: String,
89}
90
91/// Candles event containing candle updates.
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct CandlesEvent {
94    /// Event type.
95    pub r#type: EventType,
96    /// Candle updates.
97    pub candles: Vec<CandleUpdate>,
98}
99
100/// A candle (OHLCV) update.
101#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct CandleUpdate {
103    /// Product ID.
104    pub product_id: String,
105    /// Start time.
106    pub start: String,
107    /// Open price.
108    pub open: String,
109    /// High price.
110    pub high: String,
111    /// Low price.
112    pub low: String,
113    /// Close price.
114    pub close: String,
115    /// Volume.
116    pub volume: String,
117}
118
119/// Ticker event containing ticker updates.
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct TickerEvent {
122    /// Event type.
123    pub r#type: EventType,
124    /// Ticker updates.
125    pub tickers: Vec<TickerUpdate>,
126}
127
128/// A ticker update.
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct TickerUpdate {
131    /// Ticker type.
132    pub r#type: String,
133    /// Product ID.
134    pub product_id: String,
135    /// Current price.
136    pub price: String,
137    /// 24-hour volume.
138    pub volume_24_h: String,
139    /// 24-hour low.
140    pub low_24_h: String,
141    /// 24-hour high.
142    pub high_24_h: String,
143    /// 52-week low.
144    pub low_52_w: String,
145    /// 52-week high.
146    pub high_52_w: String,
147    /// 24-hour price percentage change.
148    pub price_percent_chg_24_h: String,
149}
150
151/// Level 2 order book event.
152#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct Level2Event {
154    /// Event type.
155    pub r#type: EventType,
156    /// Product ID.
157    pub product_id: String,
158    /// Order book updates.
159    pub updates: Vec<Level2Update>,
160}
161
162/// A Level 2 order book update.
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct Level2Update {
165    /// Side (bid or ask).
166    pub side: Level2Side,
167    /// Event time.
168    pub event_time: String,
169    /// Price level.
170    pub price_level: String,
171    /// New quantity at this level.
172    pub new_quantity: String,
173}
174
175/// Side of a Level 2 order book entry.
176#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
177#[serde(rename_all = "lowercase")]
178pub enum Level2Side {
179    /// Bid (buy) side.
180    Bid,
181    /// Ask (sell) side.
182    #[serde(alias = "offer")]
183    Ask,
184}
185
186/// User event containing order updates.
187#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct UserEvent {
189    /// Event type.
190    pub r#type: EventType,
191    /// Order updates.
192    pub orders: Vec<OrderUpdate>,
193}
194
195/// An order update for the user channel.
196#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct OrderUpdate {
198    /// Average fill price.
199    #[serde(default)]
200    pub avg_price: String,
201    /// Cancel reason if cancelled.
202    #[serde(default)]
203    pub cancel_reason: String,
204    /// Client-provided order ID.
205    #[serde(default)]
206    pub client_order_id: String,
207    /// Completion percentage.
208    #[serde(default)]
209    pub completion_percentage: String,
210    /// Contract expiry type.
211    #[serde(default)]
212    pub contract_expiry_type: String,
213    /// Cumulative filled quantity.
214    #[serde(default)]
215    pub cumulative_quantity: String,
216    /// Total filled value.
217    #[serde(default)]
218    pub filled_value: String,
219    /// Remaining quantity.
220    #[serde(default)]
221    pub leaves_quantity: String,
222    /// Limit price.
223    #[serde(default)]
224    pub limit_price: String,
225    /// Number of fills.
226    #[serde(default)]
227    pub number_of_fills: String,
228    /// Order ID.
229    pub order_id: String,
230    /// Order side (BUY or SELL).
231    pub order_side: String,
232    /// Order type.
233    pub order_type: String,
234    /// Outstanding hold amount.
235    #[serde(default)]
236    pub outstanding_hold_amount: String,
237    /// Post-only flag.
238    #[serde(default)]
239    pub post_only: bool,
240    /// Product ID.
241    pub product_id: String,
242    /// Product type.
243    #[serde(default)]
244    pub product_type: String,
245    /// Reject reason if rejected.
246    #[serde(default)]
247    pub reject_reason: Option<String>,
248    /// Retail portfolio ID.
249    #[serde(default)]
250    pub retail_portfolio_id: String,
251    /// Risk managed by.
252    #[serde(default)]
253    pub risk_managed_by: String,
254    /// Order status.
255    pub status: String,
256    /// Stop price.
257    #[serde(default)]
258    pub stop_price: Option<String>,
259    /// Time in force.
260    #[serde(default)]
261    pub time_in_force: String,
262    /// Total fees.
263    #[serde(default)]
264    pub total_fees: String,
265    /// Total value after fees.
266    #[serde(default)]
267    pub total_value_after_fees: String,
268    /// Trigger status.
269    #[serde(default)]
270    pub trigger_status: String,
271    /// Creation time.
272    #[serde(default)]
273    pub creation_time: String,
274    /// End time.
275    #[serde(default)]
276    pub end_time: String,
277    /// Start time.
278    #[serde(default)]
279    pub start_time: String,
280}
281
282/// Market trades event.
283#[derive(Debug, Clone, Serialize, Deserialize)]
284pub struct MarketTradesEvent {
285    /// Event type.
286    pub r#type: EventType,
287    /// Trade updates.
288    pub trades: Vec<TradeUpdate>,
289}
290
291/// A trade update.
292#[derive(Debug, Clone, Serialize, Deserialize)]
293pub struct TradeUpdate {
294    /// Trade ID.
295    pub trade_id: String,
296    /// Product ID.
297    pub product_id: String,
298    /// Trade price.
299    pub price: String,
300    /// Trade size.
301    pub size: String,
302    /// Trade side (BUY or SELL).
303    pub side: String,
304    /// Trade time.
305    pub time: String,
306}
307
308/// Heartbeat event.
309#[derive(Debug, Clone, Serialize, Deserialize)]
310pub struct HeartbeatsEvent {
311    /// Current server time.
312    pub current_time: String,
313    /// Heartbeat counter.
314    pub heartbeat_counter: u64,
315}
316
317/// Subscriptions confirmation event.
318#[derive(Debug, Clone, Serialize, Deserialize)]
319pub struct SubscriptionsEvent {
320    /// Current subscriptions.
321    pub subscriptions: SubscriptionStatus,
322}
323
324/// Current subscription status.
325#[derive(Debug, Clone, Serialize, Deserialize, Default)]
326pub struct SubscriptionStatus {
327    /// Status channel subscriptions.
328    #[serde(default)]
329    pub status: Vec<String>,
330    /// Ticker channel subscriptions.
331    #[serde(default)]
332    pub ticker: Vec<String>,
333    /// Ticker batch channel subscriptions.
334    #[serde(default)]
335    pub ticker_batch: Vec<String>,
336    /// Level 2 channel subscriptions.
337    #[serde(default)]
338    pub level2: Option<Vec<String>>,
339    /// User channel subscriptions.
340    #[serde(default)]
341    pub user: Option<Vec<String>>,
342    /// Market trades channel subscriptions.
343    #[serde(default)]
344    pub market_trades: Option<Vec<String>>,
345    /// Heartbeats channel subscriptions.
346    #[serde(default)]
347    pub heartbeats: Option<Vec<String>>,
348}
349
350/// Futures balance summary event.
351#[derive(Debug, Clone, Serialize, Deserialize)]
352pub struct FuturesBalanceSummaryEvent {
353    /// Event type.
354    pub r#type: EventType,
355    /// Balance summary data.
356    pub fcm_balance_summary: FuturesBalanceSummary,
357}
358
359/// Futures balance summary data.
360#[derive(Debug, Clone, Serialize, Deserialize)]
361pub struct FuturesBalanceSummary {
362    /// Futures buying power.
363    #[serde(default)]
364    pub futures_buying_power: String,
365    /// Total USD balance.
366    #[serde(default)]
367    pub total_usd_balance: String,
368    /// CBI USD balance.
369    #[serde(default)]
370    pub cbi_usd_balance: String,
371    /// CFM USD balance.
372    #[serde(default)]
373    pub cfm_usd_balance: String,
374    /// Total open orders hold amount.
375    #[serde(default)]
376    pub total_open_orders_hold_amount: String,
377    /// Unrealized PnL.
378    #[serde(default)]
379    pub unrealized_pnl: String,
380    /// Daily realized PnL.
381    #[serde(default)]
382    pub daily_realized_pnl: String,
383    /// Initial margin.
384    #[serde(default)]
385    pub initial_margin: String,
386    /// Available margin.
387    #[serde(default)]
388    pub available_margin: String,
389    /// Liquidation threshold.
390    #[serde(default)]
391    pub liquidation_threshold: String,
392    /// Liquidation buffer amount.
393    #[serde(default)]
394    pub liquidation_buffer_amount: String,
395    /// Liquidation buffer percentage.
396    #[serde(default)]
397    pub liquidation_buffer_percentage: String,
398}
399
400#[cfg(test)]
401mod tests {
402    use super::*;
403
404    #[test]
405    fn test_heartbeat_deserialize() {
406        let data = r#"
407            {
408                "channel":"heartbeats",
409                "client_id":"",
410                "timestamp":"2025-01-14T22:11:18.791273556Z",
411                "sequence_num":17,
412                "events":
413                [
414                    {
415                        "current_time":"2025-01-14 22:11:18.787177997 +0000 UTC m=+25541.571430466",
416                        "heartbeat_counter":25539
417                    }
418                ]
419            }
420        "#;
421
422        let msg: Result<Message, _> = serde_json::from_str(data);
423        assert!(msg.is_ok());
424    }
425
426    #[test]
427    fn test_level2_side_deserialize() {
428        // Test normal cases
429        assert_eq!(
430            serde_json::from_str::<Level2Side>(r#""bid""#).unwrap(),
431            Level2Side::Bid
432        );
433        assert_eq!(
434            serde_json::from_str::<Level2Side>(r#""ask""#).unwrap(),
435            Level2Side::Ask
436        );
437        // Test "offer" alias
438        assert_eq!(
439            serde_json::from_str::<Level2Side>(r#""offer""#).unwrap(),
440            Level2Side::Ask
441        );
442    }
443}