nautilus_binance/futures/websocket/
parse.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//! Parsing utilities for Binance Futures WebSocket JSON messages.
17
18use nautilus_core::nanos::UnixNanos;
19use nautilus_model::{
20    data::{BookOrder, OrderBookDelta, OrderBookDeltas, QuoteTick, TradeTick},
21    enums::{AggressorSide, BookAction, OrderSide, RecordFlag},
22    identifiers::TradeId,
23    instruments::{Instrument, InstrumentAny},
24    types::{Price, Quantity},
25};
26use ustr::Ustr;
27
28use super::messages::{
29    BinanceFuturesAggTradeMsg, BinanceFuturesBookTickerMsg, BinanceFuturesDepthUpdateMsg,
30    BinanceFuturesTradeMsg,
31};
32use crate::{common::enums::BinanceWsEventType, websocket::error::BinanceWsResult};
33
34/// Parses an aggregate trade message into a `TradeTick`.
35///
36/// # Errors
37///
38/// Returns an error if parsing fails.
39pub fn parse_agg_trade(
40    msg: &BinanceFuturesAggTradeMsg,
41    instrument: &InstrumentAny,
42) -> BinanceWsResult<TradeTick> {
43    let instrument_id = instrument.id();
44    let price_precision = instrument.price_precision();
45    let size_precision = instrument.size_precision();
46
47    let price = msg
48        .price
49        .parse::<f64>()
50        .map_err(|e| crate::websocket::error::BinanceWsError::ParseError(e.to_string()))?;
51    let size = msg
52        .quantity
53        .parse::<f64>()
54        .map_err(|e| crate::websocket::error::BinanceWsError::ParseError(e.to_string()))?;
55
56    let aggressor_side = if msg.is_buyer_maker {
57        AggressorSide::Seller
58    } else {
59        AggressorSide::Buyer
60    };
61
62    let ts_event = UnixNanos::from(msg.trade_time as u64 * 1_000_000); // ms to ns
63    let trade_id = TradeId::new(msg.agg_trade_id.to_string());
64
65    Ok(TradeTick::new(
66        instrument_id,
67        Price::new(price, price_precision),
68        Quantity::new(size, size_precision),
69        aggressor_side,
70        trade_id,
71        ts_event,
72        ts_event,
73    ))
74}
75
76/// Parses a trade message into a `TradeTick`.
77///
78/// # Errors
79///
80/// Returns an error if parsing fails.
81pub fn parse_trade(
82    msg: &BinanceFuturesTradeMsg,
83    instrument: &InstrumentAny,
84) -> BinanceWsResult<TradeTick> {
85    let instrument_id = instrument.id();
86    let price_precision = instrument.price_precision();
87    let size_precision = instrument.size_precision();
88
89    let price = msg
90        .price
91        .parse::<f64>()
92        .map_err(|e| crate::websocket::error::BinanceWsError::ParseError(e.to_string()))?;
93    let size = msg
94        .quantity
95        .parse::<f64>()
96        .map_err(|e| crate::websocket::error::BinanceWsError::ParseError(e.to_string()))?;
97
98    let aggressor_side = if msg.is_buyer_maker {
99        AggressorSide::Seller
100    } else {
101        AggressorSide::Buyer
102    };
103
104    let ts_event = UnixNanos::from(msg.trade_time as u64 * 1_000_000); // ms to ns
105    let trade_id = TradeId::new(msg.trade_id.to_string());
106
107    Ok(TradeTick::new(
108        instrument_id,
109        Price::new(price, price_precision),
110        Quantity::new(size, size_precision),
111        aggressor_side,
112        trade_id,
113        ts_event,
114        ts_event,
115    ))
116}
117
118/// Parses a book ticker message into a `QuoteTick`.
119///
120/// # Errors
121///
122/// Returns an error if parsing fails.
123pub fn parse_book_ticker(
124    msg: &BinanceFuturesBookTickerMsg,
125    instrument: &InstrumentAny,
126) -> BinanceWsResult<QuoteTick> {
127    let instrument_id = instrument.id();
128    let price_precision = instrument.price_precision();
129    let size_precision = instrument.size_precision();
130
131    let bid_price = msg
132        .best_bid_price
133        .parse::<f64>()
134        .map_err(|e| crate::websocket::error::BinanceWsError::ParseError(e.to_string()))?;
135    let bid_size = msg
136        .best_bid_qty
137        .parse::<f64>()
138        .map_err(|e| crate::websocket::error::BinanceWsError::ParseError(e.to_string()))?;
139    let ask_price = msg
140        .best_ask_price
141        .parse::<f64>()
142        .map_err(|e| crate::websocket::error::BinanceWsError::ParseError(e.to_string()))?;
143    let ask_size = msg
144        .best_ask_qty
145        .parse::<f64>()
146        .map_err(|e| crate::websocket::error::BinanceWsError::ParseError(e.to_string()))?;
147
148    let ts_event = UnixNanos::from(msg.transaction_time as u64 * 1_000_000); // ms to ns
149
150    Ok(QuoteTick::new(
151        instrument_id,
152        Price::new(bid_price, price_precision),
153        Price::new(ask_price, price_precision),
154        Quantity::new(bid_size, size_precision),
155        Quantity::new(ask_size, size_precision),
156        ts_event,
157        ts_event,
158    ))
159}
160
161/// Parses a depth update message into `OrderBookDeltas`.
162///
163/// # Errors
164///
165/// Returns an error if parsing fails.
166pub fn parse_depth_update(
167    msg: &BinanceFuturesDepthUpdateMsg,
168    instrument: &InstrumentAny,
169) -> BinanceWsResult<OrderBookDeltas> {
170    let instrument_id = instrument.id();
171    let price_precision = instrument.price_precision();
172    let size_precision = instrument.size_precision();
173
174    let ts_event = UnixNanos::from(msg.transaction_time as u64 * 1_000_000); // ms to ns
175
176    let mut deltas = Vec::with_capacity(msg.bids.len() + msg.asks.len());
177
178    // Process bids
179    for (i, bid) in msg.bids.iter().enumerate() {
180        let price = bid[0]
181            .parse::<f64>()
182            .map_err(|e| crate::websocket::error::BinanceWsError::ParseError(e.to_string()))?;
183        let size = bid[1]
184            .parse::<f64>()
185            .map_err(|e| crate::websocket::error::BinanceWsError::ParseError(e.to_string()))?;
186
187        let action = if size == 0.0 {
188            BookAction::Delete
189        } else {
190            BookAction::Update
191        };
192
193        let is_last = i == msg.bids.len() - 1 && msg.asks.is_empty();
194        let flags = if is_last { RecordFlag::F_LAST as u8 } else { 0 };
195
196        let order = BookOrder::new(
197            OrderSide::Buy,
198            Price::new(price, price_precision),
199            Quantity::new(size, size_precision),
200            0,
201        );
202
203        deltas.push(OrderBookDelta::new(
204            instrument_id,
205            action,
206            order,
207            flags,
208            msg.final_update_id,
209            ts_event,
210            ts_event,
211        ));
212    }
213
214    // Process asks
215    for (i, ask) in msg.asks.iter().enumerate() {
216        let price = ask[0]
217            .parse::<f64>()
218            .map_err(|e| crate::websocket::error::BinanceWsError::ParseError(e.to_string()))?;
219        let size = ask[1]
220            .parse::<f64>()
221            .map_err(|e| crate::websocket::error::BinanceWsError::ParseError(e.to_string()))?;
222
223        let action = if size == 0.0 {
224            BookAction::Delete
225        } else {
226            BookAction::Update
227        };
228
229        let is_last = i == msg.asks.len() - 1;
230        let flags = if is_last { RecordFlag::F_LAST as u8 } else { 0 };
231
232        let order = BookOrder::new(
233            OrderSide::Sell,
234            Price::new(price, price_precision),
235            Quantity::new(size, size_precision),
236            0,
237        );
238
239        deltas.push(OrderBookDelta::new(
240            instrument_id,
241            action,
242            order,
243            flags,
244            msg.final_update_id,
245            ts_event,
246            ts_event,
247        ));
248    }
249
250    Ok(OrderBookDeltas::new(instrument_id, deltas))
251}
252
253/// Extracts the symbol from a raw JSON message.
254pub fn extract_symbol(json: &serde_json::Value) -> Option<Ustr> {
255    json.get("s").and_then(|v| v.as_str()).map(Ustr::from)
256}
257
258/// Extracts the event type from a raw JSON message.
259pub fn extract_event_type(json: &serde_json::Value) -> Option<BinanceWsEventType> {
260    json.get("e")
261        .and_then(|v| serde_json::from_value(v.clone()).ok())
262}