1use serde::{Deserialize, Serialize};
4
5use super::channels::ChannelName;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct Message {
10 pub channel: ChannelName,
12 pub client_id: String,
14 pub timestamp: String,
16 pub sequence_num: u64,
18 pub events: Events,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24#[serde(untagged)]
25pub enum Events {
26 Status(Vec<StatusEvent>),
28 Candles(Vec<CandlesEvent>),
30 Ticker(Vec<TickerEvent>),
32 Level2(Vec<Level2Event>),
34 User(Vec<UserEvent>),
36 MarketTrades(Vec<MarketTradesEvent>),
38 Heartbeats(Vec<HeartbeatsEvent>),
40 Subscriptions(Vec<SubscriptionsEvent>),
42 FuturesBalanceSummary(Vec<FuturesBalanceSummaryEvent>),
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
48#[serde(rename_all = "snake_case")]
49pub enum EventType {
50 Snapshot,
52 Update,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct StatusEvent {
59 pub r#type: EventType,
61 pub products: Vec<ProductStatus>,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct ProductStatus {
68 pub product_type: String,
70 pub id: String,
72 pub base_currency: String,
74 pub quote_currency: String,
76 pub base_increment: String,
78 pub quote_increment: String,
80 pub display_name: String,
82 pub status: String,
84 #[serde(default)]
86 pub status_message: String,
87 pub min_market_funds: String,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct CandlesEvent {
94 pub r#type: EventType,
96 pub candles: Vec<CandleUpdate>,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct CandleUpdate {
103 pub product_id: String,
105 pub start: String,
107 pub open: String,
109 pub high: String,
111 pub low: String,
113 pub close: String,
115 pub volume: String,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct TickerEvent {
122 pub r#type: EventType,
124 pub tickers: Vec<TickerUpdate>,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct TickerUpdate {
131 pub r#type: String,
133 pub product_id: String,
135 pub price: String,
137 pub volume_24_h: String,
139 pub low_24_h: String,
141 pub high_24_h: String,
143 pub low_52_w: String,
145 pub high_52_w: String,
147 pub price_percent_chg_24_h: String,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct Level2Event {
154 pub r#type: EventType,
156 pub product_id: String,
158 pub updates: Vec<Level2Update>,
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct Level2Update {
165 pub side: Level2Side,
167 pub event_time: String,
169 pub price_level: String,
171 pub new_quantity: String,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
177#[serde(rename_all = "lowercase")]
178pub enum Level2Side {
179 Bid,
181 #[serde(alias = "offer")]
183 Ask,
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct UserEvent {
189 pub r#type: EventType,
191 pub orders: Vec<OrderUpdate>,
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct OrderUpdate {
198 #[serde(default)]
200 pub avg_price: String,
201 #[serde(default)]
203 pub cancel_reason: String,
204 #[serde(default)]
206 pub client_order_id: String,
207 #[serde(default)]
209 pub completion_percentage: String,
210 #[serde(default)]
212 pub contract_expiry_type: String,
213 #[serde(default)]
215 pub cumulative_quantity: String,
216 #[serde(default)]
218 pub filled_value: String,
219 #[serde(default)]
221 pub leaves_quantity: String,
222 #[serde(default)]
224 pub limit_price: String,
225 #[serde(default)]
227 pub number_of_fills: String,
228 pub order_id: String,
230 pub order_side: String,
232 pub order_type: String,
234 #[serde(default)]
236 pub outstanding_hold_amount: String,
237 #[serde(default)]
239 pub post_only: bool,
240 pub product_id: String,
242 #[serde(default)]
244 pub product_type: String,
245 #[serde(default)]
247 pub reject_reason: Option<String>,
248 #[serde(default)]
250 pub retail_portfolio_id: String,
251 #[serde(default)]
253 pub risk_managed_by: String,
254 pub status: String,
256 #[serde(default)]
258 pub stop_price: Option<String>,
259 #[serde(default)]
261 pub time_in_force: String,
262 #[serde(default)]
264 pub total_fees: String,
265 #[serde(default)]
267 pub total_value_after_fees: String,
268 #[serde(default)]
270 pub trigger_status: String,
271 #[serde(default)]
273 pub creation_time: String,
274 #[serde(default)]
276 pub end_time: String,
277 #[serde(default)]
279 pub start_time: String,
280}
281
282#[derive(Debug, Clone, Serialize, Deserialize)]
284pub struct MarketTradesEvent {
285 pub r#type: EventType,
287 pub trades: Vec<TradeUpdate>,
289}
290
291#[derive(Debug, Clone, Serialize, Deserialize)]
293pub struct TradeUpdate {
294 pub trade_id: String,
296 pub product_id: String,
298 pub price: String,
300 pub size: String,
302 pub side: String,
304 pub time: String,
306}
307
308#[derive(Debug, Clone, Serialize, Deserialize)]
310pub struct HeartbeatsEvent {
311 pub current_time: String,
313 pub heartbeat_counter: u64,
315}
316
317#[derive(Debug, Clone, Serialize, Deserialize)]
319pub struct SubscriptionsEvent {
320 pub subscriptions: SubscriptionStatus,
322}
323
324#[derive(Debug, Clone, Serialize, Deserialize, Default)]
326pub struct SubscriptionStatus {
327 #[serde(default)]
329 pub status: Vec<String>,
330 #[serde(default)]
332 pub ticker: Vec<String>,
333 #[serde(default)]
335 pub ticker_batch: Vec<String>,
336 #[serde(default)]
338 pub level2: Option<Vec<String>>,
339 #[serde(default)]
341 pub user: Option<Vec<String>>,
342 #[serde(default)]
344 pub market_trades: Option<Vec<String>>,
345 #[serde(default)]
347 pub heartbeats: Option<Vec<String>>,
348}
349
350#[derive(Debug, Clone, Serialize, Deserialize)]
352pub struct FuturesBalanceSummaryEvent {
353 pub r#type: EventType,
355 pub fcm_balance_summary: FuturesBalanceSummary,
357}
358
359#[derive(Debug, Clone, Serialize, Deserialize)]
361pub struct FuturesBalanceSummary {
362 #[serde(default)]
364 pub futures_buying_power: String,
365 #[serde(default)]
367 pub total_usd_balance: String,
368 #[serde(default)]
370 pub cbi_usd_balance: String,
371 #[serde(default)]
373 pub cfm_usd_balance: String,
374 #[serde(default)]
376 pub total_open_orders_hold_amount: String,
377 #[serde(default)]
379 pub unrealized_pnl: String,
380 #[serde(default)]
382 pub daily_realized_pnl: String,
383 #[serde(default)]
385 pub initial_margin: String,
386 #[serde(default)]
388 pub available_margin: String,
389 #[serde(default)]
391 pub liquidation_threshold: String,
392 #[serde(default)]
394 pub liquidation_buffer_amount: String,
395 #[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 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 assert_eq!(
439 serde_json::from_str::<Level2Side>(r#""offer""#).unwrap(),
440 Level2Side::Ask
441 );
442 }
443}