nautilus_binance/spot/websocket/trading/
user_data.rs1use nautilus_core::serialization::deserialize_decimal_from_str;
22use rust_decimal::Decimal;
23use serde::Deserialize;
24use ustr::Ustr;
25
26use crate::common::enums::{BinanceOrderStatus, BinanceSide, BinanceTimeInForce};
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
30#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
31pub enum BinanceSpotExecutionType {
32 New,
34 Canceled,
36 Replaced,
38 Rejected,
40 Trade,
42 Expired,
44 TradePrevention,
46}
47
48#[derive(Debug, Clone, Deserialize)]
56pub struct BinanceSpotExecutionReport {
57 #[serde(rename = "e")]
59 pub event_type: String,
60 #[serde(rename = "E")]
62 pub event_time: i64,
63 #[serde(rename = "s")]
65 pub symbol: Ustr,
66 #[serde(rename = "c")]
68 pub client_order_id: String,
69 #[serde(rename = "S")]
71 pub side: BinanceSide,
72 #[serde(rename = "o")]
74 pub order_type: String,
75 #[serde(rename = "f")]
77 pub time_in_force: BinanceTimeInForce,
78 #[serde(rename = "q")]
80 pub original_qty: String,
81 #[serde(rename = "p")]
83 pub price: String,
84 #[serde(rename = "P")]
86 pub stop_price: String,
87 #[serde(rename = "x")]
89 pub execution_type: BinanceSpotExecutionType,
90 #[serde(rename = "X")]
92 pub order_status: BinanceOrderStatus,
93 #[serde(rename = "r")]
95 pub reject_reason: String,
96 #[serde(rename = "i")]
98 pub order_id: i64,
99 #[serde(rename = "l")]
101 pub last_filled_qty: String,
102 #[serde(rename = "z")]
104 pub cumulative_filled_qty: String,
105 #[serde(rename = "L")]
107 pub last_filled_price: String,
108 #[serde(rename = "n")]
110 pub commission: String,
111 #[serde(rename = "N", default)]
113 pub commission_asset: Option<Ustr>,
114 #[serde(rename = "T")]
116 pub transaction_time: i64,
117 #[serde(rename = "t")]
119 pub trade_id: i64,
120 #[serde(rename = "w")]
122 pub is_working: bool,
123 #[serde(rename = "m")]
125 pub is_maker: bool,
126 #[serde(rename = "O")]
128 pub order_creation_time: i64,
129 #[serde(rename = "Z")]
131 pub cumulative_quote_qty: String,
132 #[serde(rename = "C", default)]
134 pub original_client_order_id: Option<String>,
135}
136
137#[derive(Debug, Clone, Deserialize)]
145pub struct BinanceSpotAccountPositionMsg {
146 #[serde(rename = "e")]
148 pub event_type: String,
149 #[serde(rename = "E")]
151 pub event_time: i64,
152 #[serde(rename = "u")]
154 pub last_update_time: i64,
155 #[serde(rename = "B")]
157 pub balances: Vec<BinanceSpotBalanceEntry>,
158}
159
160#[derive(Debug, Clone, Deserialize)]
162pub struct BinanceSpotBalanceEntry {
163 #[serde(rename = "a")]
165 pub asset: Ustr,
166 #[serde(rename = "f", deserialize_with = "deserialize_decimal_from_str")]
168 pub free: Decimal,
169 #[serde(rename = "l", deserialize_with = "deserialize_decimal_from_str")]
171 pub locked: Decimal,
172}
173
174#[derive(Debug, Clone, Deserialize)]
183pub struct BinanceSpotBalanceUpdateMsg {
184 #[serde(rename = "e")]
186 pub event_type: String,
187 #[serde(rename = "E")]
189 pub event_time: i64,
190 #[serde(rename = "a")]
192 pub asset: Ustr,
193 #[serde(rename = "d")]
195 pub delta: String,
196 #[serde(rename = "T")]
198 pub clear_time: i64,
199}
200
201#[cfg(test)]
202mod tests {
203 use rstest::rstest;
204
205 use super::*;
206 use crate::common::testing::{load_event_fixture, load_fixture_string};
207
208 #[rstest]
209 fn test_deserialize_execution_report_new() {
210 let json = load_event_fixture("spot/user_data_json/execution_report_wrapped.json");
211 let msg: BinanceSpotExecutionReport = serde_json::from_value(json).unwrap();
212
213 assert_eq!(msg.event_type, "executionReport");
214 assert_eq!(msg.symbol.as_str(), "ETHBTC");
215 assert_eq!(msg.execution_type, BinanceSpotExecutionType::New);
216 assert_eq!(msg.order_status, BinanceOrderStatus::New);
217 assert_eq!(msg.order_id, 4293153);
218 assert_eq!(msg.side, BinanceSide::Buy);
219 }
220
221 #[rstest]
222 fn test_deserialize_execution_report_trade() {
223 let json = load_fixture_string("spot/user_data_json/execution_report_trade.json");
224 let msg: BinanceSpotExecutionReport = serde_json::from_str(&json).unwrap();
225
226 assert_eq!(msg.execution_type, BinanceSpotExecutionType::Trade);
227 assert_eq!(msg.order_status, BinanceOrderStatus::Filled);
228 assert_eq!(msg.trade_id, 98765432);
229 assert_eq!(msg.last_filled_qty, "1.00000000");
230 assert_eq!(msg.last_filled_price, "2500.00000000");
231 assert!(msg.is_maker);
232 }
233
234 #[rstest]
235 fn test_deserialize_execution_report_canceled() {
236 let json = load_fixture_string("spot/user_data_json/execution_report_canceled.json");
237 let msg: BinanceSpotExecutionReport = serde_json::from_str(&json).unwrap();
238
239 assert_eq!(msg.execution_type, BinanceSpotExecutionType::Canceled);
240 assert_eq!(msg.order_status, BinanceOrderStatus::Canceled);
241 }
242
243 #[rstest]
244 fn test_deserialize_account_position() {
245 let json = load_event_fixture("spot/user_data_json/account_position_wrapped.json");
246 let msg: BinanceSpotAccountPositionMsg = serde_json::from_value(json).unwrap();
247
248 assert_eq!(msg.event_type, "outboundAccountPosition");
249 assert!(!msg.balances.is_empty());
250 assert_eq!(msg.balances[0].asset.as_str(), "ETH");
251 }
252
253 #[rstest]
254 fn test_deserialize_balance_update() {
255 let json = load_event_fixture("spot/user_data_json/balance_update_wrapped.json");
256 let msg: BinanceSpotBalanceUpdateMsg = serde_json::from_value(json).unwrap();
257
258 assert_eq!(msg.event_type, "balanceUpdate");
259 assert_eq!(msg.asset.as_str(), "BTC");
260 assert_eq!(msg.delta, "100.00000000");
261 }
262}