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