Skip to main content

nautilus_binance/futures/websocket/streams/
messages.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Binance Futures WebSocket message types.
17//!
18//! Futures streams use standard JSON encoding (not SBE like Spot).
19//!
20//! The handler emits venue-specific types via [`BinanceFuturesWsStreamsMessage`].
21//! Data and execution client layers convert these to Nautilus domain types.
22
23use nautilus_core::serialization::{
24    deserialize_decimal_from_str, deserialize_optional_decimal_from_str,
25};
26use nautilus_model::identifiers::{
27    ClientOrderId, InstrumentId, StrategyId, TraderId, VenueOrderId,
28};
29use nautilus_network::websocket::WebSocketClient;
30use rust_decimal::Decimal;
31use serde::{Deserialize, Serialize};
32use ustr::Ustr;
33
34use crate::{
35    common::enums::{
36        BinanceAlgoStatus, BinanceAlgoType, BinanceFuturesOrderType, BinanceKlineInterval,
37        BinanceMarginType, BinanceOrderStatus, BinancePositionSide, BinancePriceMatch,
38        BinanceSelfTradePreventionMode, BinanceSide, BinanceTimeInForce, BinanceWorkingType,
39        BinanceWsMethod,
40    },
41    futures::http::BinanceFuturesInstrument,
42};
43
44/// Output message from the Futures WebSocket streams handler.
45///
46/// Contains venue-specific types for both market data and user data stream
47/// events. The data and execution client layers convert these to Nautilus
48/// domain types using parse functions with instrument context.
49#[derive(Debug, Clone)]
50pub enum BinanceFuturesWsStreamsMessage {
51    /// Aggregate trade stream.
52    AggTrade(BinanceFuturesAggTradeMsg),
53    /// Trade stream.
54    Trade(BinanceFuturesTradeMsg),
55    /// Best bid/ask (book ticker) stream.
56    BookTicker(BinanceFuturesBookTickerMsg),
57    /// Order book depth update stream.
58    DepthUpdate(BinanceFuturesDepthUpdateMsg),
59    /// Mark price stream.
60    MarkPrice(BinanceFuturesMarkPriceMsg),
61    /// Kline/candlestick stream.
62    Kline(BinanceFuturesKlineMsg),
63    /// Force liquidation order stream.
64    ForceOrder(BinanceFuturesLiquidationMsg),
65    /// 24hr ticker stream.
66    Ticker(BinanceFuturesTickerMsg),
67    /// Account update (balance/position changes).
68    AccountUpdate(BinanceFuturesAccountUpdateMsg),
69    /// Order/trade update.
70    OrderUpdate(Box<BinanceFuturesOrderUpdateMsg>),
71    /// Trade Lite fill notification (low-latency subset of `OrderUpdate`).
72    TradeLite(Box<BinanceFuturesTradeLiteMsg>),
73    /// Algo order update (conditional orders via Algo Service).
74    AlgoUpdate(Box<BinanceFuturesAlgoUpdateMsg>),
75    /// Margin call warning.
76    MarginCall(BinanceFuturesMarginCallMsg),
77    /// Account configuration change (leverage, etc.).
78    AccountConfigUpdate(BinanceFuturesAccountConfigMsg),
79    /// Listen key expired.
80    ListenKeyExpired,
81    /// Error from the server.
82    Error(BinanceFuturesWsErrorMsg),
83    /// WebSocket reconnected; carries the correlation IDs of the unsubscribe
84    /// requests the old connection abandoned.
85    Reconnected(Vec<u64>),
86    /// Venue confirmation of an unsubscribe request, carrying the confirmed stream
87    /// names and the request's correlation ID when one was supplied.
88    Unsubscribed {
89        /// Confirmed stream names.
90        streams: Vec<String>,
91        /// Correlates the confirmation with the caller's unsubscribe lifecycle.
92        correlation: Option<u64>,
93    },
94}
95
96/// Error message from Binance Futures WebSocket.
97#[derive(Debug, Clone)]
98pub struct BinanceFuturesWsErrorMsg {
99    /// Error code from Binance.
100    pub code: i64,
101    /// Error message.
102    pub msg: String,
103}
104
105/// Handler command for data client-handler communication.
106#[derive(Debug)]
107pub enum BinanceFuturesWsStreamsCommand {
108    /// Set the WebSocket client reference.
109    SetClient(WebSocketClient),
110    /// Disconnect from the WebSocket.
111    Disconnect,
112    /// Subscribe to streams.
113    Subscribe { streams: Vec<String> },
114    /// Unsubscribe from streams.
115    Unsubscribe {
116        /// Streams to unsubscribe from.
117        streams: Vec<String>,
118        /// Correlates the venue confirmation with the caller's unsubscribe lifecycle.
119        correlation: Option<u64>,
120    },
121}
122
123/// Handler command for execution client-handler communication.
124#[derive(Debug)]
125pub enum ExecHandlerCommand {
126    /// Set the WebSocket client reference.
127    SetClient(WebSocketClient),
128    /// Disconnect from the WebSocket.
129    Disconnect,
130    /// Initialize instruments in the handler cache.
131    InitializeInstruments(Vec<BinanceFuturesInstrument>),
132    /// Update a single instrument in the handler cache.
133    UpdateInstrument(BinanceFuturesInstrument),
134    /// Subscribe to user data stream.
135    Subscribe { streams: Vec<String> },
136    /// Register an order for context tracking.
137    RegisterOrder {
138        client_order_id: ClientOrderId,
139        trader_id: TraderId,
140        strategy_id: StrategyId,
141        instrument_id: InstrumentId,
142    },
143    /// Register a cancel request for context tracking.
144    RegisterCancel {
145        client_order_id: ClientOrderId,
146        trader_id: TraderId,
147        strategy_id: StrategyId,
148        instrument_id: InstrumentId,
149        venue_order_id: Option<VenueOrderId>,
150    },
151    /// Register a modify request for context tracking.
152    RegisterModify {
153        client_order_id: ClientOrderId,
154        trader_id: TraderId,
155        strategy_id: StrategyId,
156        instrument_id: InstrumentId,
157        venue_order_id: Option<VenueOrderId>,
158    },
159}
160
161/// Aggregate trade stream message.
162#[derive(Debug, Clone, Deserialize)]
163pub struct BinanceFuturesAggTradeMsg {
164    /// Event type.
165    #[serde(rename = "e")]
166    pub event_type: String,
167    /// Event time in milliseconds.
168    #[serde(rename = "E")]
169    pub event_time: i64,
170    /// Symbol.
171    #[serde(rename = "s")]
172    pub symbol: Ustr,
173    /// Aggregate trade ID.
174    #[serde(rename = "a")]
175    pub agg_trade_id: u64,
176    /// Price.
177    #[serde(rename = "p")]
178    pub price: String,
179    /// Quantity.
180    #[serde(rename = "q")]
181    pub quantity: String,
182    /// First trade ID.
183    #[serde(rename = "f")]
184    pub first_trade_id: u64,
185    /// Last trade ID.
186    #[serde(rename = "l")]
187    pub last_trade_id: u64,
188    /// Trade time in milliseconds.
189    #[serde(rename = "T")]
190    pub trade_time: i64,
191    /// Is buyer the market maker.
192    #[serde(rename = "m")]
193    pub is_buyer_maker: bool,
194}
195
196/// Trade stream message.
197#[derive(Debug, Clone, Deserialize)]
198pub struct BinanceFuturesTradeMsg {
199    /// Event type.
200    #[serde(rename = "e")]
201    pub event_type: String,
202    /// Event time in milliseconds.
203    #[serde(rename = "E")]
204    pub event_time: i64,
205    /// Symbol.
206    #[serde(rename = "s")]
207    pub symbol: Ustr,
208    /// Trade ID.
209    #[serde(rename = "t")]
210    pub trade_id: u64,
211    /// Price.
212    #[serde(rename = "p")]
213    pub price: String,
214    /// Quantity.
215    #[serde(rename = "q")]
216    pub quantity: String,
217    /// Trade time in milliseconds.
218    #[serde(rename = "T")]
219    pub trade_time: i64,
220    /// Is buyer the market maker.
221    #[serde(rename = "m")]
222    pub is_buyer_maker: bool,
223}
224
225/// Order book depth update stream message.
226#[derive(Debug, Clone, Deserialize)]
227pub struct BinanceFuturesDepthUpdateMsg {
228    /// Event type.
229    #[serde(rename = "e")]
230    pub event_type: String,
231    /// Event time in milliseconds.
232    #[serde(rename = "E")]
233    pub event_time: i64,
234    /// Transaction time in milliseconds.
235    #[serde(rename = "T")]
236    pub transaction_time: i64,
237    /// Symbol.
238    #[serde(rename = "s")]
239    pub symbol: Ustr,
240    /// First update ID.
241    #[serde(rename = "U")]
242    pub first_update_id: u64,
243    /// Final update ID.
244    #[serde(rename = "u")]
245    pub final_update_id: u64,
246    /// Previous final update ID.
247    #[serde(rename = "pu")]
248    pub prev_final_update_id: u64,
249    /// Bids [price, quantity].
250    #[serde(rename = "b")]
251    pub bids: Vec<[String; 2]>,
252    /// Asks [price, quantity].
253    #[serde(rename = "a")]
254    pub asks: Vec<[String; 2]>,
255}
256
257/// Mark price stream message.
258#[derive(Debug, Clone, Deserialize)]
259pub struct BinanceFuturesMarkPriceMsg {
260    /// Event type.
261    #[serde(rename = "e")]
262    pub event_type: String,
263    /// Event time in milliseconds.
264    #[serde(rename = "E")]
265    pub event_time: i64,
266    /// Symbol.
267    #[serde(rename = "s")]
268    pub symbol: Ustr,
269    /// Mark price.
270    #[serde(rename = "p")]
271    pub mark_price: String,
272    /// Mark price moving average (added by venue, may be absent on older payloads).
273    #[serde(rename = "ap", default)]
274    pub mark_price_moving_avg: Option<String>,
275    /// Index price.
276    #[serde(rename = "i")]
277    pub index_price: String,
278    /// Estimated settle price.
279    #[serde(rename = "P")]
280    pub estimated_settle_price: String,
281    /// Funding rate.
282    #[serde(rename = "r")]
283    pub funding_rate: String,
284    /// Next funding time in milliseconds.
285    #[serde(rename = "T")]
286    pub next_funding_time: i64,
287}
288
289/// Book ticker stream message.
290#[derive(Debug, Clone, Deserialize)]
291pub struct BinanceFuturesBookTickerMsg {
292    /// Event type.
293    #[serde(rename = "e")]
294    pub event_type: String,
295    /// Update ID.
296    #[serde(rename = "u")]
297    pub update_id: u64,
298    /// Event time in milliseconds.
299    #[serde(rename = "E")]
300    pub event_time: i64,
301    /// Transaction time in milliseconds.
302    #[serde(rename = "T")]
303    pub transaction_time: i64,
304    /// Symbol.
305    #[serde(rename = "s")]
306    pub symbol: Ustr,
307    /// Best bid price.
308    #[serde(rename = "b")]
309    pub best_bid_price: String,
310    /// Best bid quantity.
311    #[serde(rename = "B")]
312    pub best_bid_qty: String,
313    /// Best ask price.
314    #[serde(rename = "a")]
315    pub best_ask_price: String,
316    /// Best ask quantity.
317    #[serde(rename = "A")]
318    pub best_ask_qty: String,
319}
320
321/// Kline/candlestick stream message.
322#[derive(Debug, Clone, Deserialize)]
323pub struct BinanceFuturesKlineMsg {
324    /// Event type.
325    #[serde(rename = "e")]
326    pub event_type: String,
327    /// Event time in milliseconds.
328    #[serde(rename = "E")]
329    pub event_time: i64,
330    /// Symbol.
331    #[serde(rename = "s")]
332    pub symbol: Ustr,
333    /// Kline data.
334    #[serde(rename = "k")]
335    pub kline: BinanceFuturesKlineData,
336}
337
338/// Kline data within kline message.
339#[derive(Debug, Clone, Deserialize)]
340pub struct BinanceFuturesKlineData {
341    /// Kline start time.
342    #[serde(rename = "t")]
343    pub start_time: i64,
344    /// Kline close time.
345    #[serde(rename = "T")]
346    pub close_time: i64,
347    /// Symbol.
348    #[serde(rename = "s")]
349    pub symbol: Ustr,
350    /// Kline interval.
351    #[serde(rename = "i")]
352    pub interval: BinanceKlineInterval,
353    /// First trade ID.
354    #[serde(rename = "f")]
355    pub first_trade_id: i64,
356    /// Last trade ID.
357    #[serde(rename = "L")]
358    pub last_trade_id: i64,
359    /// Open price.
360    #[serde(rename = "o")]
361    pub open: String,
362    /// Close price.
363    #[serde(rename = "c")]
364    pub close: String,
365    /// High price.
366    #[serde(rename = "h")]
367    pub high: String,
368    /// Low price.
369    #[serde(rename = "l")]
370    pub low: String,
371    /// Base asset volume.
372    #[serde(rename = "v")]
373    pub volume: String,
374    /// Number of trades.
375    #[serde(rename = "n")]
376    pub num_trades: i64,
377    /// Is this kline closed.
378    #[serde(rename = "x")]
379    pub is_closed: bool,
380    /// Quote asset volume.
381    #[serde(rename = "q")]
382    pub quote_volume: String,
383    /// Taker buy base asset volume.
384    #[serde(rename = "V")]
385    pub taker_buy_volume: String,
386    /// Taker buy quote asset volume.
387    #[serde(rename = "Q")]
388    pub taker_buy_quote_volume: String,
389}
390
391/// Liquidation order stream message.
392#[derive(Debug, Clone, Deserialize)]
393pub struct BinanceFuturesLiquidationMsg {
394    /// Event type.
395    #[serde(rename = "e")]
396    pub event_type: String,
397    /// Event time in milliseconds.
398    #[serde(rename = "E")]
399    pub event_time: i64,
400    /// Order data.
401    #[serde(rename = "o")]
402    pub order: BinanceFuturesLiquidationOrder,
403}
404
405/// Liquidation order details.
406#[derive(Debug, Clone, Deserialize)]
407pub struct BinanceFuturesLiquidationOrder {
408    /// Symbol.
409    #[serde(rename = "s")]
410    pub symbol: Ustr,
411    /// Order side.
412    #[serde(rename = "S")]
413    pub side: BinanceSide,
414    /// Order type.
415    #[serde(rename = "o")]
416    pub order_type: BinanceFuturesOrderType,
417    /// Time in force.
418    #[serde(rename = "f")]
419    pub time_in_force: BinanceTimeInForce,
420    /// Original quantity.
421    #[serde(rename = "q")]
422    pub original_qty: String,
423    /// Price.
424    #[serde(rename = "p")]
425    pub price: String,
426    /// Average price.
427    #[serde(rename = "ap")]
428    pub average_price: String,
429    /// Order status.
430    #[serde(rename = "X")]
431    pub status: BinanceOrderStatus,
432    /// Last filled quantity.
433    #[serde(rename = "l")]
434    pub last_filled_qty: String,
435    /// Accumulated filled quantity.
436    #[serde(rename = "z")]
437    pub accumulated_qty: String,
438    /// Trade time in milliseconds.
439    #[serde(rename = "T")]
440    pub trade_time: i64,
441}
442
443/// 24hr ticker stream message.
444#[derive(Debug, Clone, Deserialize)]
445pub struct BinanceFuturesTickerMsg {
446    /// Event type.
447    #[serde(rename = "e")]
448    pub event_type: String,
449    /// Event time in milliseconds.
450    #[serde(rename = "E")]
451    pub event_time: i64,
452    /// Symbol.
453    #[serde(rename = "s")]
454    pub symbol: Ustr,
455    /// Price change.
456    #[serde(rename = "p")]
457    pub price_change: String,
458    /// Price change percent.
459    #[serde(rename = "P")]
460    pub price_change_percent: String,
461    /// Weighted average price.
462    #[serde(rename = "w")]
463    pub weighted_avg_price: String,
464    /// Last price.
465    #[serde(rename = "c")]
466    pub last_price: String,
467    /// Last quantity.
468    #[serde(rename = "Q")]
469    pub last_qty: String,
470    /// Open price.
471    #[serde(rename = "o")]
472    pub open_price: String,
473    /// High price.
474    #[serde(rename = "h")]
475    pub high_price: String,
476    /// Low price.
477    #[serde(rename = "l")]
478    pub low_price: String,
479    /// Total traded base asset volume.
480    #[serde(rename = "v")]
481    pub volume: String,
482    /// Total traded quote asset volume.
483    #[serde(rename = "q")]
484    pub quote_volume: String,
485    /// Statistics open time in milliseconds.
486    #[serde(rename = "O")]
487    pub open_time: i64,
488    /// Statistics close time in milliseconds.
489    #[serde(rename = "C")]
490    pub close_time: i64,
491    /// First trade ID.
492    #[serde(rename = "F")]
493    pub first_trade_id: i64,
494    /// Last trade ID.
495    #[serde(rename = "L")]
496    pub last_trade_id: i64,
497    /// Total number of trades.
498    #[serde(rename = "n")]
499    pub num_trades: i64,
500}
501
502/// WebSocket subscription request.
503#[derive(Debug, Clone, Serialize)]
504pub struct BinanceFuturesWsSubscribeRequest {
505    /// Request method.
506    pub method: BinanceWsMethod,
507    /// Stream names to subscribe.
508    pub params: Vec<String>,
509    /// Request ID.
510    pub id: u64,
511}
512
513/// WebSocket subscription response.
514#[derive(Debug, Clone, Deserialize)]
515pub struct BinanceFuturesWsSubscribeResponse {
516    /// Response result (null on success).
517    pub result: Option<serde_json::Value>,
518    /// Request ID echoed back.
519    pub id: u64,
520}
521
522/// WebSocket error response.
523#[derive(Debug, Clone, Deserialize)]
524pub struct BinanceFuturesWsErrorResponse {
525    /// Error code.
526    pub code: i64,
527    /// Error message.
528    pub msg: String,
529    /// Request ID if available.
530    pub id: Option<u64>,
531}
532
533/// Account update event from user data stream.
534#[derive(Debug, Clone, Deserialize)]
535pub struct BinanceFuturesAccountUpdateMsg {
536    /// Event type.
537    #[serde(rename = "e")]
538    pub event_type: String,
539    /// Event time in milliseconds.
540    #[serde(rename = "E")]
541    pub event_time: i64,
542    /// Transaction time in milliseconds.
543    #[serde(rename = "T")]
544    pub transaction_time: i64,
545    /// Account update data.
546    #[serde(rename = "a")]
547    pub account: AccountUpdateData,
548}
549
550/// Account update data payload.
551#[derive(Debug, Clone, Deserialize)]
552pub struct AccountUpdateData {
553    /// Reason for account update.
554    #[serde(rename = "m")]
555    pub reason: AccountUpdateReason,
556    /// Balance updates.
557    #[serde(rename = "B", default)]
558    pub balances: Vec<BalanceUpdate>,
559    /// Position updates.
560    #[serde(rename = "P", default)]
561    pub positions: Vec<PositionUpdate>,
562    /// Symbol associated with a funding-fee update, when provided.
563    #[serde(rename = "S", default)]
564    pub symbol: Option<Ustr>,
565}
566
567/// Account update reason type.
568#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
569#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
570pub enum AccountUpdateReason {
571    Deposit,
572    Withdraw,
573    Order,
574    FundingFee,
575    WithdrawReject,
576    Adjustment,
577    InsuranceClear,
578    AdminDeposit,
579    AdminWithdraw,
580    MarginTransfer,
581    MarginTypeChange,
582    AssetTransfer,
583    OptionsPremiumFee,
584    OptionsSettleProfit,
585    AutoExchange,
586    Adl,
587    CoinSwapDeposit,
588    CoinSwapWithdraw,
589    #[serde(other)]
590    Unknown,
591}
592
593/// Balance update within account update.
594#[derive(Debug, Clone, Deserialize)]
595pub struct BalanceUpdate {
596    /// Asset name.
597    #[serde(rename = "a")]
598    pub asset: Ustr,
599    /// Wallet balance.
600    #[serde(rename = "wb", deserialize_with = "deserialize_decimal_from_str")]
601    pub wallet_balance: Decimal,
602    /// Cross wallet balance.
603    #[serde(rename = "cw", deserialize_with = "deserialize_decimal_from_str")]
604    pub cross_wallet_balance: Decimal,
605    /// Balance change (except for PnL and commission).
606    #[serde(
607        rename = "bc",
608        default,
609        deserialize_with = "deserialize_optional_decimal_from_str"
610    )]
611    pub balance_change: Option<Decimal>,
612}
613
614/// Position update within account update.
615#[derive(Debug, Clone, Deserialize)]
616pub struct PositionUpdate {
617    /// Symbol.
618    #[serde(rename = "s")]
619    pub symbol: Ustr,
620    /// Position amount.
621    #[serde(rename = "pa")]
622    pub position_amount: String,
623    /// Entry price.
624    #[serde(rename = "ep")]
625    pub entry_price: String,
626    /// Break-even price.
627    #[serde(rename = "bep", default)]
628    pub break_even_price: Option<String>,
629    /// Accumulated realized (pre-fee).
630    #[serde(rename = "cr")]
631    pub accumulated_realized: String,
632    /// Unrealized PnL.
633    #[serde(rename = "up")]
634    pub unrealized_pnl: String,
635    /// Margin type.
636    #[serde(rename = "mt")]
637    pub margin_type: BinanceMarginType,
638    /// Isolated wallet (if isolated position).
639    #[serde(rename = "iw")]
640    pub isolated_wallet: String,
641    /// Position side.
642    #[serde(rename = "ps")]
643    pub position_side: BinancePositionSide,
644}
645
646/// Order/trade update event from user data stream.
647#[derive(Debug, Clone, Deserialize)]
648pub struct BinanceFuturesOrderUpdateMsg {
649    /// Event type.
650    #[serde(rename = "e")]
651    pub event_type: String,
652    /// Event time in milliseconds.
653    #[serde(rename = "E")]
654    pub event_time: i64,
655    /// Transaction time in milliseconds.
656    #[serde(rename = "T")]
657    pub transaction_time: i64,
658    /// Order data.
659    #[serde(rename = "o")]
660    pub order: OrderUpdateData,
661}
662
663/// Order update data payload.
664#[derive(Debug, Clone, Deserialize)]
665pub struct OrderUpdateData {
666    /// Symbol.
667    #[serde(rename = "s")]
668    pub symbol: Ustr,
669    /// Client order ID.
670    #[serde(rename = "c")]
671    pub client_order_id: String,
672    /// Order side.
673    #[serde(rename = "S")]
674    pub side: BinanceSide,
675    /// Order type.
676    #[serde(rename = "o")]
677    pub order_type: BinanceFuturesOrderType,
678    /// Time in force.
679    #[serde(rename = "f")]
680    pub time_in_force: BinanceTimeInForce,
681    /// Original quantity.
682    #[serde(rename = "q")]
683    pub original_qty: String,
684    /// Original price.
685    #[serde(rename = "p")]
686    pub original_price: String,
687    /// Average price.
688    #[serde(rename = "ap")]
689    pub average_price: String,
690    /// Stop price.
691    #[serde(rename = "sp")]
692    pub stop_price: String,
693    /// Execution type.
694    #[serde(rename = "x")]
695    pub execution_type: BinanceExecutionType,
696    /// Order status.
697    #[serde(rename = "X")]
698    pub order_status: BinanceOrderStatus,
699    /// Order ID.
700    #[serde(rename = "i")]
701    pub order_id: i64,
702    /// Last executed quantity.
703    #[serde(rename = "l")]
704    pub last_filled_qty: String,
705    /// Cumulative filled quantity.
706    #[serde(rename = "z")]
707    pub cumulative_filled_qty: String,
708    /// Last executed price.
709    #[serde(rename = "L")]
710    pub last_filled_price: String,
711    /// Commission asset.
712    #[serde(rename = "N", default)]
713    pub commission_asset: Option<Ustr>,
714    /// Commission amount.
715    #[serde(rename = "n", default)]
716    pub commission: Option<String>,
717    /// Order trade time.
718    #[serde(rename = "T")]
719    pub trade_time: i64,
720    /// Trade ID.
721    #[serde(rename = "t")]
722    pub trade_id: i64,
723    /// Bids notional.
724    #[serde(rename = "b", default)]
725    pub bids_notional: Option<String>,
726    /// Asks notional.
727    #[serde(rename = "a", default)]
728    pub asks_notional: Option<String>,
729    /// Is maker.
730    #[serde(rename = "m")]
731    pub is_maker: bool,
732    /// Is reduce only.
733    #[serde(rename = "R")]
734    pub is_reduce_only: bool,
735    /// Working type.
736    #[serde(rename = "wt")]
737    pub working_type: BinanceWorkingType,
738    /// Original order type.
739    #[serde(rename = "ot")]
740    pub original_order_type: BinanceFuturesOrderType,
741    /// Position side.
742    #[serde(rename = "ps")]
743    pub position_side: BinancePositionSide,
744    /// Close all (for stop orders).
745    #[serde(rename = "cp", default)]
746    pub close_position: Option<bool>,
747    /// Activation price (for trailing stop).
748    #[serde(rename = "AP", default)]
749    pub activation_price: Option<String>,
750    /// Callback rate (for trailing stop).
751    #[serde(rename = "cr", default)]
752    pub callback_rate: Option<String>,
753    /// Price protection.
754    #[serde(rename = "pP", default)]
755    pub price_protect: Option<bool>,
756    /// Realized profit.
757    #[serde(rename = "rp")]
758    pub realized_profit: String,
759    /// Self-trade prevention mode.
760    #[serde(rename = "V", default)]
761    pub stp_mode: Option<BinanceSelfTradePreventionMode>,
762    /// Price match mode.
763    #[serde(rename = "pm", default)]
764    pub price_match: Option<BinancePriceMatch>,
765    /// Good till date for GTD orders.
766    #[serde(rename = "gtd", default)]
767    pub good_till_date: Option<i64>,
768}
769
770impl OrderUpdateData {
771    /// Returns true if this is a liquidation order.
772    #[must_use]
773    pub fn is_liquidation(&self) -> bool {
774        self.client_order_id.starts_with("autoclose-")
775    }
776
777    /// Returns true if this is an ADL (Auto-Deleveraging) order.
778    #[must_use]
779    pub fn is_adl(&self) -> bool {
780        self.client_order_id.starts_with("adl_autoclose")
781    }
782
783    /// Returns true if this is a settlement order.
784    ///
785    /// USDT-margined futures use `settlement_autoclose-` for funding/margin
786    /// settlement; coin-margined delivery futures use `delivery_autoclose-`
787    /// when an expiring contract auto-closes.
788    #[must_use]
789    pub fn is_settlement(&self) -> bool {
790        self.client_order_id.starts_with("settlement_autoclose-")
791            || self.client_order_id.starts_with("delivery_autoclose-")
792    }
793
794    /// Returns true if this is an exchange-generated order.
795    #[must_use]
796    pub fn is_exchange_generated(&self) -> bool {
797        self.is_liquidation() || self.is_adl() || self.is_settlement()
798    }
799}
800
801/// Trade Lite event from user data stream.
802///
803/// Binance pushes `TRADE_LITE` alongside `ORDER_TRADE_UPDATE` as a lower-latency
804/// subset containing only the fields needed to recognize a fill. Clients that
805/// prioritize latency can opt to act on `TRADE_LITE` and dedup the matching
806/// fill portion of the full `ORDER_TRADE_UPDATE` event.
807#[derive(Debug, Clone, Deserialize)]
808pub struct BinanceFuturesTradeLiteMsg {
809    /// Event type.
810    #[serde(rename = "e")]
811    pub event_type: String,
812    /// Event time in milliseconds.
813    #[serde(rename = "E")]
814    pub event_time: i64,
815    /// Transaction time in milliseconds.
816    #[serde(rename = "T")]
817    pub transaction_time: i64,
818    /// Symbol.
819    #[serde(rename = "s")]
820    pub symbol: Ustr,
821    /// Client order ID.
822    #[serde(rename = "c")]
823    pub client_order_id: String,
824    /// Order side.
825    #[serde(rename = "S")]
826    pub side: BinanceSide,
827    /// Original quantity.
828    #[serde(rename = "q")]
829    pub original_qty: String,
830    /// Original price.
831    #[serde(rename = "p")]
832    pub original_price: String,
833    /// Order ID.
834    #[serde(rename = "i")]
835    pub order_id: i64,
836    /// Last executed quantity.
837    #[serde(rename = "l")]
838    pub last_filled_qty: String,
839    /// Last executed price.
840    #[serde(rename = "L")]
841    pub last_filled_price: String,
842    /// Trade ID.
843    #[serde(rename = "t")]
844    pub trade_id: i64,
845    /// Is maker.
846    #[serde(rename = "m")]
847    pub is_maker: bool,
848}
849
850/// Execution type for order updates.
851#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
852#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
853pub enum BinanceExecutionType {
854    /// New order accepted.
855    New,
856    /// Order canceled.
857    Canceled,
858    /// Calculated (liquidation, ADL).
859    Calculated,
860    /// Order expired.
861    Expired,
862    /// Trade (partial or full fill).
863    Trade,
864    /// Amendment (order modified).
865    Amendment,
866    /// Unknown or undocumented execution type.
867    #[serde(other)]
868    Unknown,
869}
870
871/// Margin call event from user data stream.
872#[derive(Debug, Clone, Deserialize)]
873pub struct BinanceFuturesMarginCallMsg {
874    /// Event type.
875    #[serde(rename = "e")]
876    pub event_type: String,
877    /// Event time in milliseconds.
878    #[serde(rename = "E")]
879    pub event_time: i64,
880    /// Cross wallet balance.
881    #[serde(rename = "cw")]
882    pub cross_wallet_balance: String,
883    /// Positions at risk.
884    #[serde(rename = "p")]
885    pub positions: Vec<MarginCallPosition>,
886}
887
888/// Position at risk in margin call.
889#[derive(Debug, Clone, Deserialize)]
890pub struct MarginCallPosition {
891    /// Symbol.
892    #[serde(rename = "s")]
893    pub symbol: Ustr,
894    /// Position side.
895    #[serde(rename = "ps")]
896    pub position_side: BinancePositionSide,
897    /// Position amount.
898    #[serde(rename = "pa")]
899    pub position_amount: String,
900    /// Margin type.
901    #[serde(rename = "mt")]
902    pub margin_type: BinanceMarginType,
903    /// Isolated wallet (if any).
904    #[serde(rename = "iw")]
905    pub isolated_wallet: String,
906    /// Mark price.
907    #[serde(rename = "mp")]
908    pub mark_price: String,
909    /// Unrealized PnL.
910    #[serde(rename = "up")]
911    pub unrealized_pnl: String,
912    /// Maintenance margin required.
913    #[serde(rename = "mm")]
914    pub maintenance_margin: String,
915}
916
917/// Account configuration update event.
918#[derive(Debug, Clone, Deserialize)]
919pub struct BinanceFuturesAccountConfigMsg {
920    /// Event type.
921    #[serde(rename = "e")]
922    pub event_type: String,
923    /// Event time in milliseconds.
924    #[serde(rename = "E")]
925    pub event_time: i64,
926    /// Transaction time in milliseconds.
927    #[serde(rename = "T")]
928    pub transaction_time: i64,
929    /// Leverage configuration data.
930    #[serde(rename = "ac", default)]
931    pub leverage_config: Option<LeverageConfig>,
932    /// Asset index price data (for multi-assets mode).
933    #[serde(rename = "ai", default)]
934    pub asset_index: Option<AssetIndexConfig>,
935}
936
937/// Leverage configuration change.
938#[derive(Debug, Clone, Deserialize)]
939pub struct LeverageConfig {
940    /// Symbol.
941    #[serde(rename = "s")]
942    pub symbol: Ustr,
943    /// New leverage value.
944    #[serde(rename = "l")]
945    pub leverage: u32,
946}
947
948/// Asset index configuration (multi-assets mode).
949#[derive(Debug, Clone, Deserialize)]
950pub struct AssetIndexConfig {
951    /// Symbol.
952    #[serde(rename = "s")]
953    pub symbol: Ustr,
954}
955
956/// Algo order update event from user data stream (Binance Futures Algo Service).
957///
958/// This event is triggered for conditional orders (STOP_MARKET, STOP_LIMIT,
959/// TAKE_PROFIT, TAKE_PROFIT_MARKET, TRAILING_STOP_MARKET) managed by the
960/// Algo Service.
961///
962/// # References
963///
964/// - <https://developers.binance.com/docs/derivatives/usds-margined-futures/user-data-streams/Event-Algo-Order-Update>
965#[derive(Debug, Clone, Deserialize)]
966pub struct BinanceFuturesAlgoUpdateMsg {
967    /// Event type ("ALGO_UPDATE").
968    #[serde(rename = "e")]
969    pub event_type: String,
970    /// Event time in milliseconds.
971    #[serde(rename = "E")]
972    pub event_time: i64,
973    /// Transaction time in milliseconds.
974    #[serde(rename = "T")]
975    pub transaction_time: i64,
976    /// Algo order data.
977    #[serde(rename = "o", alias = "ao")]
978    pub algo_order: AlgoOrderUpdateData,
979}
980
981/// Algo order update data payload.
982#[derive(Debug, Clone, Deserialize)]
983pub struct AlgoOrderUpdateData {
984    /// Client algo order ID.
985    #[serde(rename = "caid")]
986    pub client_algo_id: String,
987    /// Algo order ID.
988    #[serde(rename = "aid")]
989    pub algo_id: i64,
990    /// Algo type (currently only `Conditional`).
991    #[serde(rename = "at")]
992    pub algo_type: BinanceAlgoType,
993    /// Order type (STOP_MARKET, STOP, TAKE_PROFIT, TAKE_PROFIT_MARKET, TRAILING_STOP_MARKET).
994    #[serde(rename = "o")]
995    pub order_type: BinanceFuturesOrderType,
996    /// Symbol.
997    #[serde(rename = "s")]
998    pub symbol: Ustr,
999    /// Order side.
1000    #[serde(rename = "S")]
1001    pub side: BinanceSide,
1002    /// Position side.
1003    #[serde(rename = "ps")]
1004    pub position_side: BinancePositionSide,
1005    /// Time in force.
1006    #[serde(rename = "f")]
1007    pub time_in_force: BinanceTimeInForce,
1008    /// Order quantity.
1009    #[serde(rename = "q")]
1010    pub quantity: String,
1011    /// Algo order status (NEW, TRIGGERING, TRIGGERED, FINISHED, CANCELED, EXPIRED, REJECTED).
1012    #[serde(rename = "X")]
1013    pub algo_status: BinanceAlgoStatus,
1014    /// Trigger price.
1015    #[serde(rename = "tp")]
1016    pub trigger_price: String,
1017    /// Limit price.
1018    #[serde(rename = "p")]
1019    pub price: String,
1020    /// Working type for trigger price calculation.
1021    #[serde(rename = "wt")]
1022    pub working_type: BinanceWorkingType,
1023    /// Price match mode.
1024    #[serde(rename = "pm", default)]
1025    pub price_match: Option<BinancePriceMatch>,
1026    /// Close position flag.
1027    #[serde(rename = "cp", default)]
1028    pub close_position: Option<bool>,
1029    /// Price protection flag.
1030    #[serde(rename = "pP", default)]
1031    pub price_protect: Option<bool>,
1032    /// Reduce-only flag.
1033    #[serde(rename = "R", default)]
1034    pub reduce_only: Option<bool>,
1035    /// Trigger time in milliseconds.
1036    #[serde(rename = "tt", default)]
1037    pub trigger_time: Option<i64>,
1038    /// Good till date in milliseconds.
1039    #[serde(rename = "gtd", default)]
1040    pub good_till_date: Option<i64>,
1041    /// Order ID in matching engine (populated when triggered).
1042    #[serde(rename = "ai", default)]
1043    pub actual_order_id: Option<String>,
1044    /// Average fill price in matching engine (populated when triggered).
1045    #[serde(rename = "ap", default)]
1046    pub avg_price: Option<String>,
1047    /// Executed quantity in matching engine (populated when triggered).
1048    #[serde(rename = "aq", default)]
1049    pub executed_qty: Option<String>,
1050    /// Actual order type in matching engine (populated when triggered).
1051    #[serde(rename = "act", default)]
1052    pub actual_order_type: Option<String>,
1053    /// Callback rate for trailing stop (0.1 to 10, where 1 = 1%).
1054    #[serde(rename = "cr", default)]
1055    pub callback_rate: Option<String>,
1056    /// Self-trade prevention mode.
1057    #[serde(rename = "V", default)]
1058    pub stp_mode: Option<BinanceSelfTradePreventionMode>,
1059}
1060
1061/// Listen key expired event.
1062#[derive(Debug, Clone, Deserialize)]
1063pub struct BinanceFuturesListenKeyExpiredMsg {
1064    /// Event type.
1065    #[serde(rename = "e")]
1066    pub event_type: String,
1067    /// Event time in milliseconds.
1068    #[serde(rename = "E")]
1069    pub event_time: i64,
1070}
1071
1072#[cfg(test)]
1073mod tests {
1074    use rstest::rstest;
1075    use rust_decimal_macros::dec;
1076
1077    use super::*;
1078
1079    #[rstest]
1080    #[case(
1081        include_str!("../../../../test_data/futures/user_data_json/account_update_funding_fee_usdm.json"),
1082        "BTCUSDT",
1083        "USDT",
1084        [1786060800001, 1786060800000],
1085        [dec!(1024.12345678), dec!(1010.87654321), dec!(-0.01234567)]
1086    )]
1087    #[case(
1088        include_str!("../../../../test_data/futures/user_data_json/account_update_funding_fee_coinm.json"),
1089        "BTCUSD_PERP",
1090        "BTC",
1091        [1786089600002, 1786089600000],
1092        [dec!(1.23456789), dec!(1.12345678), dec!(0.00001234)]
1093    )]
1094    fn test_account_update_funding_fee_symbol(
1095        #[case] payload: &str,
1096        #[case] symbol: &str,
1097        #[case] asset: &str,
1098        #[case] times: [i64; 2],
1099        #[case] balances: [Decimal; 3],
1100    ) {
1101        let msg: BinanceFuturesAccountUpdateMsg = serde_json::from_str(payload).unwrap();
1102
1103        assert_eq!(msg.event_type, "ACCOUNT_UPDATE");
1104        assert_eq!(msg.event_time, times[0]);
1105        assert_eq!(msg.transaction_time, times[1]);
1106        assert_eq!(msg.account.reason, AccountUpdateReason::FundingFee);
1107        assert_eq!(msg.account.symbol, Some(Ustr::from(symbol)));
1108        assert_eq!(msg.account.balances.len(), 1);
1109        assert_eq!(msg.account.positions.len(), 0);
1110        let balance = &msg.account.balances[0];
1111        assert_eq!(balance.asset, Ustr::from(asset));
1112        assert_eq!(balance.wallet_balance, balances[0]);
1113        assert_eq!(balance.cross_wallet_balance, balances[1]);
1114        assert_eq!(balance.balance_change, Some(balances[2]));
1115    }
1116
1117    #[rstest]
1118    #[case(include_str!("../../../../test_data/futures/user_data_json/account_update.json"), 1, 3)]
1119    #[case(include_str!("../../../../test_data/futures/user_data_json/account_update_bnfcr.json"), 1, 0)]
1120    fn test_account_update_without_symbol(
1121        #[case] payload: &str,
1122        #[case] balances: usize,
1123        #[case] positions: usize,
1124    ) {
1125        let msg: BinanceFuturesAccountUpdateMsg = serde_json::from_str(payload).unwrap();
1126
1127        assert_eq!(msg.account.reason, AccountUpdateReason::Order);
1128        assert_eq!(msg.account.symbol, None);
1129        assert_eq!(msg.account.balances.len(), balances);
1130        assert_eq!(msg.account.positions.len(), positions);
1131    }
1132
1133    #[rstest]
1134    #[case(include_str!("../../../../test_data/futures/user_data_json/account_update_funding_fee_usdm.json"))]
1135    #[case(include_str!("../../../../test_data/futures/user_data_json/account_update_funding_fee_coinm.json"))]
1136    fn test_account_update_funding_fee_without_symbol(#[case] payload: &str) {
1137        let mut payload: serde_json::Value = serde_json::from_str(payload).unwrap();
1138        payload["a"].as_object_mut().unwrap().remove("S");
1139
1140        let msg: BinanceFuturesAccountUpdateMsg = serde_json::from_value(payload).unwrap();
1141
1142        assert_eq!(msg.account.reason, AccountUpdateReason::FundingFee);
1143        assert_eq!(msg.account.symbol, None);
1144    }
1145
1146    #[rstest]
1147    fn test_account_update_reason_adl_deserializes() {
1148        let value: AccountUpdateReason = serde_json::from_str("\"ADL\"").unwrap();
1149        assert_eq!(value, AccountUpdateReason::Adl);
1150    }
1151
1152    #[rstest]
1153    fn test_account_update_reason_unknown_fallback() {
1154        let value: AccountUpdateReason = serde_json::from_str("\"SOMETHING_NEW\"").unwrap();
1155        assert_eq!(value, AccountUpdateReason::Unknown);
1156    }
1157}