use serde::{Deserialize, Serialize};
use super::channels::ChannelName;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
pub channel: ChannelName,
pub client_id: String,
pub timestamp: String,
pub sequence_num: u64,
pub events: Events,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Events {
Status(Vec<StatusEvent>),
Candles(Vec<CandlesEvent>),
Ticker(Vec<TickerEvent>),
Level2(Vec<Level2Event>),
User(Vec<UserEvent>),
MarketTrades(Vec<MarketTradesEvent>),
Heartbeats(Vec<HeartbeatsEvent>),
Subscriptions(Vec<SubscriptionsEvent>),
FuturesBalanceSummary(Vec<FuturesBalanceSummaryEvent>),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum EventType {
Snapshot,
Update,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StatusEvent {
pub r#type: EventType,
pub products: Vec<ProductStatus>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProductStatus {
pub product_type: String,
pub id: String,
pub base_currency: String,
pub quote_currency: String,
pub base_increment: String,
pub quote_increment: String,
pub display_name: String,
pub status: String,
#[serde(default)]
pub status_message: String,
pub min_market_funds: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CandlesEvent {
pub r#type: EventType,
pub candles: Vec<CandleUpdate>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CandleUpdate {
pub product_id: String,
pub start: String,
pub open: String,
pub high: String,
pub low: String,
pub close: String,
pub volume: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TickerEvent {
pub r#type: EventType,
pub tickers: Vec<TickerUpdate>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TickerUpdate {
pub r#type: String,
pub product_id: String,
pub price: String,
pub volume_24_h: String,
pub low_24_h: String,
pub high_24_h: String,
pub low_52_w: String,
pub high_52_w: String,
pub price_percent_chg_24_h: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Level2Event {
pub r#type: EventType,
pub product_id: String,
pub updates: Vec<Level2Update>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Level2Update {
pub side: Level2Side,
pub event_time: String,
pub price_level: String,
pub new_quantity: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum Level2Side {
Bid,
#[serde(alias = "offer")]
Ask,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserEvent {
pub r#type: EventType,
pub orders: Vec<OrderUpdate>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderUpdate {
#[serde(default)]
pub avg_price: String,
#[serde(default)]
pub cancel_reason: String,
#[serde(default)]
pub client_order_id: String,
#[serde(default)]
pub completion_percentage: String,
#[serde(default)]
pub contract_expiry_type: String,
#[serde(default)]
pub cumulative_quantity: String,
#[serde(default)]
pub filled_value: String,
#[serde(default)]
pub leaves_quantity: String,
#[serde(default)]
pub limit_price: String,
#[serde(default)]
pub number_of_fills: String,
pub order_id: String,
pub order_side: String,
pub order_type: String,
#[serde(default)]
pub outstanding_hold_amount: String,
#[serde(default)]
pub post_only: bool,
pub product_id: String,
#[serde(default)]
pub product_type: String,
#[serde(default)]
pub reject_reason: Option<String>,
#[serde(default)]
pub retail_portfolio_id: String,
#[serde(default)]
pub risk_managed_by: String,
pub status: String,
#[serde(default)]
pub stop_price: Option<String>,
#[serde(default)]
pub time_in_force: String,
#[serde(default)]
pub total_fees: String,
#[serde(default)]
pub total_value_after_fees: String,
#[serde(default)]
pub trigger_status: String,
#[serde(default)]
pub creation_time: String,
#[serde(default)]
pub end_time: String,
#[serde(default)]
pub start_time: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarketTradesEvent {
pub r#type: EventType,
pub trades: Vec<TradeUpdate>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TradeUpdate {
pub trade_id: String,
pub product_id: String,
pub price: String,
pub size: String,
pub side: String,
pub time: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HeartbeatsEvent {
pub current_time: String,
pub heartbeat_counter: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubscriptionsEvent {
pub subscriptions: SubscriptionStatus,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SubscriptionStatus {
#[serde(default)]
pub status: Vec<String>,
#[serde(default)]
pub ticker: Vec<String>,
#[serde(default)]
pub ticker_batch: Vec<String>,
#[serde(default)]
pub level2: Option<Vec<String>>,
#[serde(default)]
pub user: Option<Vec<String>>,
#[serde(default)]
pub market_trades: Option<Vec<String>>,
#[serde(default)]
pub heartbeats: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FuturesBalanceSummaryEvent {
pub r#type: EventType,
pub fcm_balance_summary: FuturesBalanceSummary,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FuturesBalanceSummary {
#[serde(default)]
pub futures_buying_power: String,
#[serde(default)]
pub total_usd_balance: String,
#[serde(default)]
pub cbi_usd_balance: String,
#[serde(default)]
pub cfm_usd_balance: String,
#[serde(default)]
pub total_open_orders_hold_amount: String,
#[serde(default)]
pub unrealized_pnl: String,
#[serde(default)]
pub daily_realized_pnl: String,
#[serde(default)]
pub initial_margin: String,
#[serde(default)]
pub available_margin: String,
#[serde(default)]
pub liquidation_threshold: String,
#[serde(default)]
pub liquidation_buffer_amount: String,
#[serde(default)]
pub liquidation_buffer_percentage: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_heartbeat_deserialize() {
let data = r#"
{
"channel":"heartbeats",
"client_id":"",
"timestamp":"2025-01-14T22:11:18.791273556Z",
"sequence_num":17,
"events":
[
{
"current_time":"2025-01-14 22:11:18.787177997 +0000 UTC m=+25541.571430466",
"heartbeat_counter":25539
}
]
}
"#;
let msg: Result<Message, _> = serde_json::from_str(data);
assert!(msg.is_ok());
}
#[test]
fn test_level2_side_deserialize() {
assert_eq!(
serde_json::from_str::<Level2Side>(r#""bid""#).unwrap(),
Level2Side::Bid
);
assert_eq!(
serde_json::from_str::<Level2Side>(r#""ask""#).unwrap(),
Level2Side::Ask
);
assert_eq!(
serde_json::from_str::<Level2Side>(r#""offer""#).unwrap(),
Level2Side::Ask
);
}
}