use serde::{Deserialize, Serialize};
use crate::types::{ExecutionType, KlineInterval, OrderSide, OrderStatus, OrderType, TimeInForce};
use super::market::string_or_float;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "e")]
pub enum WebSocketEvent {
#[serde(rename = "aggTrade")]
AggTrade(AggTradeEvent),
#[serde(rename = "trade")]
Trade(TradeEvent),
#[serde(rename = "kline")]
Kline(KlineEvent),
#[serde(rename = "24hrMiniTicker")]
MiniTicker(MiniTickerEvent),
#[serde(rename = "24hrTicker")]
Ticker(TickerEvent),
#[serde(rename = "bookTicker")]
BookTicker(BookTickerEvent),
#[serde(rename = "depthUpdate")]
Depth(DepthEvent),
#[serde(rename = "outboundAccountPosition")]
AccountPosition(AccountPositionEvent),
#[serde(rename = "balanceUpdate")]
BalanceUpdate(BalanceUpdateEvent),
#[serde(rename = "executionReport")]
ExecutionReport(ExecutionReportEvent),
#[serde(rename = "listStatus")]
ListStatus(ListStatusEvent),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AggTradeEvent {
#[serde(rename = "E")]
pub event_time: u64,
#[serde(rename = "s")]
pub symbol: String,
#[serde(rename = "a")]
pub agg_trade_id: u64,
#[serde(rename = "p", with = "string_or_float")]
pub price: f64,
#[serde(rename = "q", with = "string_or_float")]
pub quantity: f64,
#[serde(rename = "f")]
pub first_trade_id: u64,
#[serde(rename = "l")]
pub last_trade_id: u64,
#[serde(rename = "T")]
pub trade_time: u64,
#[serde(rename = "m")]
pub is_buyer_maker: bool,
#[serde(rename = "M")]
pub is_best_match: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TradeEvent {
#[serde(rename = "E")]
pub event_time: u64,
#[serde(rename = "s")]
pub symbol: String,
#[serde(rename = "t")]
pub trade_id: u64,
#[serde(rename = "p", with = "string_or_float")]
pub price: f64,
#[serde(rename = "q", with = "string_or_float")]
pub quantity: f64,
#[serde(rename = "b")]
pub buyer_order_id: u64,
#[serde(rename = "a")]
pub seller_order_id: u64,
#[serde(rename = "T")]
pub trade_time: u64,
#[serde(rename = "m")]
pub is_buyer_maker: bool,
#[serde(rename = "M")]
pub is_best_match: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KlineEvent {
#[serde(rename = "E")]
pub event_time: u64,
#[serde(rename = "s")]
pub symbol: String,
#[serde(rename = "k")]
pub kline: KlineData,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KlineData {
#[serde(rename = "t")]
pub start_time: i64,
#[serde(rename = "T")]
pub close_time: i64,
#[serde(rename = "s")]
pub symbol: String,
#[serde(rename = "i")]
pub interval: KlineInterval,
#[serde(rename = "f")]
pub first_trade_id: i64,
#[serde(rename = "L")]
pub last_trade_id: i64,
#[serde(rename = "o", with = "string_or_float")]
pub open: f64,
#[serde(rename = "c", with = "string_or_float")]
pub close: f64,
#[serde(rename = "h", with = "string_or_float")]
pub high: f64,
#[serde(rename = "l", with = "string_or_float")]
pub low: f64,
#[serde(rename = "v", with = "string_or_float")]
pub volume: f64,
#[serde(rename = "n")]
pub number_of_trades: i64,
#[serde(rename = "x")]
pub is_closed: bool,
#[serde(rename = "q", with = "string_or_float")]
pub quote_asset_volume: f64,
#[serde(rename = "V", with = "string_or_float")]
pub taker_buy_base_volume: f64,
#[serde(rename = "Q", with = "string_or_float")]
pub taker_buy_quote_volume: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MiniTickerEvent {
#[serde(rename = "E")]
pub event_time: u64,
#[serde(rename = "s")]
pub symbol: String,
#[serde(rename = "c", with = "string_or_float")]
pub close: f64,
#[serde(rename = "o", with = "string_or_float")]
pub open: f64,
#[serde(rename = "h", with = "string_or_float")]
pub high: f64,
#[serde(rename = "l", with = "string_or_float")]
pub low: f64,
#[serde(rename = "v", with = "string_or_float")]
pub volume: f64,
#[serde(rename = "q", with = "string_or_float")]
pub quote_volume: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TickerEvent {
#[serde(rename = "E")]
pub event_time: u64,
#[serde(rename = "s")]
pub symbol: String,
#[serde(rename = "p", with = "string_or_float")]
pub price_change: f64,
#[serde(rename = "P", with = "string_or_float")]
pub price_change_percent: f64,
#[serde(rename = "w", with = "string_or_float")]
pub weighted_avg_price: f64,
#[serde(rename = "x", with = "string_or_float")]
pub prev_close_price: f64,
#[serde(rename = "c", with = "string_or_float")]
pub close_price: f64,
#[serde(rename = "Q", with = "string_or_float")]
pub close_quantity: f64,
#[serde(rename = "b", with = "string_or_float")]
pub bid_price: f64,
#[serde(rename = "B", with = "string_or_float")]
pub bid_quantity: f64,
#[serde(rename = "a", with = "string_or_float")]
pub ask_price: f64,
#[serde(rename = "A", with = "string_or_float")]
pub ask_quantity: f64,
#[serde(rename = "o", with = "string_or_float")]
pub open_price: f64,
#[serde(rename = "h", with = "string_or_float")]
pub high_price: f64,
#[serde(rename = "l", with = "string_or_float")]
pub low_price: f64,
#[serde(rename = "v", with = "string_or_float")]
pub volume: f64,
#[serde(rename = "q", with = "string_or_float")]
pub quote_volume: f64,
#[serde(rename = "O")]
pub open_time: u64,
#[serde(rename = "C")]
pub close_time: u64,
#[serde(rename = "F")]
pub first_trade_id: i64,
#[serde(rename = "L")]
pub last_trade_id: i64,
#[serde(rename = "n")]
pub number_of_trades: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BookTickerEvent {
#[serde(rename = "u")]
pub update_id: u64,
#[serde(rename = "s")]
pub symbol: String,
#[serde(rename = "b", with = "string_or_float")]
pub bid_price: f64,
#[serde(rename = "B", with = "string_or_float")]
pub bid_quantity: f64,
#[serde(rename = "a", with = "string_or_float")]
pub ask_price: f64,
#[serde(rename = "A", with = "string_or_float")]
pub ask_quantity: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DepthEvent {
#[serde(rename = "E")]
pub event_time: u64,
#[serde(rename = "s")]
pub symbol: String,
#[serde(rename = "U")]
pub first_update_id: u64,
#[serde(rename = "u")]
pub final_update_id: u64,
#[serde(rename = "b")]
pub bids: Vec<DepthLevel>,
#[serde(rename = "a")]
pub asks: Vec<DepthLevel>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DepthLevel {
#[serde(with = "string_or_float")]
pub price: f64,
#[serde(with = "string_or_float")]
pub quantity: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountPositionEvent {
#[serde(rename = "E")]
pub event_time: u64,
#[serde(rename = "u")]
pub last_update_time: u64,
#[serde(rename = "B")]
pub balances: Vec<AccountBalance>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountBalance {
#[serde(rename = "a")]
pub asset: String,
#[serde(rename = "f", with = "string_or_float")]
pub free: f64,
#[serde(rename = "l", with = "string_or_float")]
pub locked: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BalanceUpdateEvent {
#[serde(rename = "E")]
pub event_time: u64,
#[serde(rename = "a")]
pub asset: String,
#[serde(rename = "d", with = "string_or_float")]
pub balance_delta: f64,
#[serde(rename = "T")]
pub clear_time: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExecutionReportEvent {
#[serde(rename = "E")]
pub event_time: u64,
#[serde(rename = "s")]
pub symbol: String,
#[serde(rename = "c")]
pub client_order_id: String,
#[serde(rename = "S")]
pub side: OrderSide,
#[serde(rename = "o")]
pub order_type: OrderType,
#[serde(rename = "f")]
pub time_in_force: TimeInForce,
#[serde(rename = "q", with = "string_or_float")]
pub quantity: f64,
#[serde(rename = "p", with = "string_or_float")]
pub price: f64,
#[serde(rename = "P", with = "string_or_float")]
pub stop_price: f64,
#[serde(rename = "F", with = "string_or_float")]
pub iceberg_quantity: f64,
#[serde(rename = "g")]
pub order_list_id: i64,
#[serde(rename = "C")]
pub orig_client_order_id: String,
#[serde(rename = "x")]
pub execution_type: ExecutionType,
#[serde(rename = "X")]
pub order_status: OrderStatus,
#[serde(rename = "r")]
pub reject_reason: String,
#[serde(rename = "i")]
pub order_id: u64,
#[serde(rename = "l", with = "string_or_float")]
pub last_executed_quantity: f64,
#[serde(rename = "z", with = "string_or_float")]
pub cumulative_filled_quantity: f64,
#[serde(rename = "L", with = "string_or_float")]
pub last_executed_price: f64,
#[serde(rename = "n", with = "string_or_float")]
pub commission: f64,
#[serde(rename = "N")]
pub commission_asset: Option<String>,
#[serde(rename = "T")]
pub transaction_time: u64,
#[serde(rename = "t")]
pub trade_id: i64,
#[serde(rename = "I")]
pub ignore_a: u64,
#[serde(rename = "w")]
pub is_on_book: bool,
#[serde(rename = "m")]
pub is_maker: bool,
#[serde(rename = "M")]
pub ignore_b: bool,
#[serde(rename = "O")]
pub order_creation_time: u64,
#[serde(rename = "Z", with = "string_or_float")]
pub cumulative_quote_quantity: f64,
#[serde(rename = "Y", with = "string_or_float")]
pub last_quote_quantity: f64,
#[serde(rename = "Q", with = "string_or_float")]
pub quote_order_quantity: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListStatusEvent {
#[serde(rename = "E")]
pub event_time: u64,
#[serde(rename = "s")]
pub symbol: String,
#[serde(rename = "g")]
pub order_list_id: u64,
#[serde(rename = "c")]
pub contingency_type: String,
#[serde(rename = "l")]
pub list_status_type: String,
#[serde(rename = "L")]
pub list_order_status: String,
#[serde(rename = "r")]
pub list_reject_reason: String,
#[serde(rename = "C")]
pub list_client_order_id: String,
#[serde(rename = "T")]
pub transaction_time: u64,
#[serde(rename = "O")]
pub orders: Vec<ListStatusOrder>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListStatusOrder {
#[serde(rename = "s")]
pub symbol: String,
#[serde(rename = "i")]
pub order_id: u64,
#[serde(rename = "c")]
pub client_order_id: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_agg_trade_event_deserialize() {
let json = r#"{
"e": "aggTrade",
"E": 1234567890123,
"s": "BTCUSDT",
"a": 12345,
"p": "50000.00",
"q": "1.5",
"f": 100,
"l": 105,
"T": 1234567890123,
"m": true,
"M": true
}"#;
let event: WebSocketEvent = serde_json::from_str(json).unwrap();
match event {
WebSocketEvent::AggTrade(e) => {
assert_eq!(e.symbol, "BTCUSDT");
assert_eq!(e.agg_trade_id, 12345);
assert_eq!(e.price, 50000.0);
assert_eq!(e.quantity, 1.5);
}
_ => panic!("Expected AggTrade event"),
}
}
#[test]
fn test_book_ticker_event_deserialize() {
let json = r#"{
"e": "bookTicker",
"u": 400900217,
"s": "BTCUSDT",
"b": "50000.00",
"B": "1.5",
"a": "50001.00",
"A": "2.0"
}"#;
let event: WebSocketEvent = serde_json::from_str(json).unwrap();
match event {
WebSocketEvent::BookTicker(e) => {
assert_eq!(e.symbol, "BTCUSDT");
assert_eq!(e.bid_price, 50000.0);
assert_eq!(e.ask_price, 50001.0);
}
_ => panic!("Expected BookTicker event"),
}
}
#[test]
fn test_mini_ticker_event_deserialize() {
let json = r#"{
"e": "24hrMiniTicker",
"E": 1234567890123,
"s": "BTCUSDT",
"c": "50000.00",
"o": "49000.00",
"h": "51000.00",
"l": "48000.00",
"v": "1000.00",
"q": "50000000.00"
}"#;
let event: WebSocketEvent = serde_json::from_str(json).unwrap();
match event {
WebSocketEvent::MiniTicker(e) => {
assert_eq!(e.symbol, "BTCUSDT");
assert_eq!(e.close, 50000.0);
}
_ => panic!("Expected MiniTicker event"),
}
}
#[test]
fn test_depth_level_deserialize() {
let json = r#"["50000.00", "1.5"]"#;
let level: DepthLevel = serde_json::from_str(json).unwrap();
assert_eq!(level.price, 50000.0);
assert_eq!(level.quantity, 1.5);
}
#[test]
fn test_account_balance_deserialize() {
let json = r#"{"a": "BTC", "f": "1.5", "l": "0.5"}"#;
let balance: AccountBalance = serde_json::from_str(json).unwrap();
assert_eq!(balance.asset, "BTC");
assert_eq!(balance.free, 1.5);
assert_eq!(balance.locked, 0.5);
}
}