use serde::Deserialize;
#[derive(Debug)]
pub enum UserDataEvent {
OrderUpdate(Box<OrderUpdateEvent>),
AccountUpdate(Box<AccountUpdateEvent>),
AccountConfigUpdate(AccountConfigUpdateEvent),
Unknown(serde_json::Value),
}
#[derive(Debug, Deserialize)]
pub struct OrderUpdateEvent {
#[serde(rename = "E")] pub event_time: u64,
#[serde(rename = "T")] pub transaction_time: u64,
#[serde(rename = "o")] pub order: OrderUpdateData,
}
#[derive(Debug, Deserialize)]
pub struct OrderUpdateData {
#[serde(rename = "s")] pub symbol: String,
#[serde(rename = "c")] pub client_order_id: String,
#[serde(rename = "S")] pub side: String,
#[serde(rename = "o")] pub order_type: String,
#[serde(rename = "f")] pub time_in_force: String,
#[serde(rename = "q")] pub quantity: String,
#[serde(rename = "p")] pub price: String,
#[serde(rename = "ap")] pub avg_price: String,
#[serde(rename = "sp")] pub stop_price: String,
#[serde(rename = "x")] pub execution_type: String,
#[serde(rename = "X")] pub order_status: String,
#[serde(rename = "i")] pub order_id: i64,
#[serde(rename = "l")] pub last_filled_qty: String,
#[serde(rename = "z")] pub cumulative_filled_qty: String,
#[serde(rename = "L")] pub last_filled_price: String,
#[serde(rename = "t")] pub trade_id: i64,
#[serde(rename = "T")] pub order_trade_time: u64,
#[serde(rename = "ps")] pub position_side: String,
#[serde(rename = "rp")] pub realized_profit: String,
}
#[derive(Debug, Deserialize)]
pub struct AccountUpdateEvent {
#[serde(rename = "E")] pub event_time: u64,
#[serde(rename = "T")] pub transaction_time: u64,
#[serde(rename = "a")] pub account: AccountUpdateData,
}
#[derive(Debug, Deserialize)]
pub struct AccountUpdateData {
#[serde(rename = "m")] pub event_reason: String,
#[serde(rename = "B")] pub balances: Vec<BalanceDelta>,
#[serde(rename = "P")] pub positions: Vec<PositionDelta>,
}
#[derive(Debug, Deserialize)]
pub struct BalanceDelta {
#[serde(rename = "a")] pub asset: String,
#[serde(rename = "wb")] pub wallet_balance: String,
#[serde(rename = "cw")] pub cross_wallet_balance: String,
#[serde(rename = "bc")] pub balance_change: String,
}
#[derive(Debug, Deserialize)]
pub struct PositionDelta {
#[serde(rename = "s")] pub symbol: String,
#[serde(rename = "pa")] pub position_amount: String,
#[serde(rename = "ep")] pub entry_price: String,
#[serde(rename = "cr")] pub accumulated_realized: String,
#[serde(rename = "up")] pub unrealized_pnl: String,
#[serde(rename = "mt")] pub margin_type: String,
#[serde(rename = "iw")] pub isolated_wallet: String,
#[serde(rename = "ps")] pub position_side: String,
}
#[derive(Debug, Deserialize)]
pub struct AccountConfigUpdateEvent {
#[serde(rename = "E")] pub event_time: u64,
#[serde(rename = "T")] pub transaction_time: u64,
#[serde(rename = "ac")] pub leverage_change: Option<LeverageChangeData>,
#[serde(rename = "ai")] pub multi_assets_margin: Option<MultiAssetsModeData>,
}
#[derive(Debug, Deserialize)]
pub struct LeverageChangeData {
#[serde(rename = "s")] pub symbol: String,
#[serde(rename = "l")] pub leverage: u32,
}
#[derive(Debug, Deserialize)]
pub struct MultiAssetsModeData {
#[serde(rename = "j")] pub is_enabled: bool,
}
impl<'de> serde::Deserialize<'de> for UserDataEvent {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let v = serde_json::Value::deserialize(deserializer)?;
let event_type = v.get("e").and_then(|e| e.as_str()).unwrap_or("");
match event_type {
"ORDER_TRADE_UPDATE" => serde_json::from_value::<OrderUpdateEvent>(v)
.map(|e| UserDataEvent::OrderUpdate(Box::new(e)))
.map_err(serde::de::Error::custom),
"ACCOUNT_UPDATE" => serde_json::from_value::<AccountUpdateEvent>(v)
.map(|e| UserDataEvent::AccountUpdate(Box::new(e)))
.map_err(serde::de::Error::custom),
"ACCOUNT_CONFIG_UPDATE" => serde_json::from_value::<AccountConfigUpdateEvent>(v)
.map(UserDataEvent::AccountConfigUpdate)
.map_err(serde::de::Error::custom),
_ => {
tracing::warn!(event_type = event_type, "Unknown user data event type");
Ok(UserDataEvent::Unknown(v))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn order_update_event_deserializes() {
let json = r#"{
"e": "ORDER_TRADE_UPDATE",
"E": 1625184000000,
"T": 1625184000000,
"o": {
"s": "BTCUSDT",
"c": "client-id-1",
"S": "BUY",
"o": "LIMIT",
"f": "GTC",
"q": "0.001",
"p": "30000",
"ap": "0",
"sp": "0",
"x": "NEW",
"X": "NEW",
"i": 123456789,
"l": "0",
"z": "0",
"L": "0",
"t": -1,
"T": 1625184000000,
"ps": "BOTH",
"rp": "0"
}
}"#;
let event: UserDataEvent = serde_json::from_str(json).unwrap();
assert!(matches!(event, UserDataEvent::OrderUpdate(ref e) if e.order.symbol == "BTCUSDT"));
}
#[test]
fn account_update_event_deserializes() {
let json = r#"{
"e": "ACCOUNT_UPDATE",
"E": 1625184000000,
"T": 1625184000000,
"a": {
"m": "DEPOSIT",
"B": [{"a": "USDT", "wb": "1000.00", "cw": "1000.00", "bc": "100.00"}],
"P": []
}
}"#;
let event: UserDataEvent = serde_json::from_str(json).unwrap();
assert!(matches!(event, UserDataEvent::AccountUpdate(_)));
if let UserDataEvent::AccountUpdate(e) = event {
assert_eq!(e.account.balances[0].asset, "USDT");
assert_eq!(e.account.balances[0].wallet_balance, "1000.00");
assert!(e.account.positions.is_empty());
}
}
#[test]
fn unknown_event_type_maps_to_unknown() {
let json = r#"{"e":"NEW_FUTURE_EVENT","x":"data"}"#;
let event: UserDataEvent = serde_json::from_str(json).unwrap();
assert!(matches!(event, UserDataEvent::Unknown(_)));
}
#[test]
fn account_config_update_leverage_deserializes() {
let json = r#"{
"e": "ACCOUNT_CONFIG_UPDATE",
"E": 1625184000000,
"T": 1625184000000,
"ac": { "s": "BTCUSDT", "l": 25 }
}"#;
let event: UserDataEvent = serde_json::from_str(json).unwrap();
assert!(matches!(event, UserDataEvent::AccountConfigUpdate(_)));
if let UserDataEvent::AccountConfigUpdate(e) = event {
assert!(e.leverage_change.is_some());
assert!(e.multi_assets_margin.is_none());
}
}
#[test]
fn account_config_update_multi_assets_deserializes() {
let json = r#"{
"e": "ACCOUNT_CONFIG_UPDATE",
"E": 1625184000000,
"T": 1625184000000,
"ai": { "j": true }
}"#;
let event: UserDataEvent = serde_json::from_str(json).unwrap();
assert!(matches!(event, UserDataEvent::AccountConfigUpdate(_)));
if let UserDataEvent::AccountConfigUpdate(e) = event {
assert!(e.multi_assets_margin.is_some());
assert!(e.leverage_change.is_none());
}
}
}