1use ahash::AHashMap;
19use nautilus_core::{UnixNanos, datetime::NANOSECONDS_IN_MILLISECOND};
20use nautilus_model::{
21 data::{BookOrder, Data, OrderBookDelta, OrderBookDeltas, QuoteTick, TradeTick},
22 enums::{AggressorSide, BookAction, OrderSide, RecordFlag},
23 identifiers::TradeId,
24 instruments::{Instrument, InstrumentAny},
25 types::{Price, Quantity},
26};
27use ustr::Ustr;
28
29use super::{
30 enums::{DeribitBookAction, DeribitBookMsgType},
31 messages::{DeribitBookMsg, DeribitQuoteMsg, DeribitTickerMsg, DeribitTradeMsg},
32};
33
34pub fn parse_trade_msg(
40 msg: &DeribitTradeMsg,
41 instrument: &InstrumentAny,
42 ts_init: UnixNanos,
43) -> anyhow::Result<TradeTick> {
44 let instrument_id = instrument.id();
45 let price_precision = instrument.price_precision();
46 let size_precision = instrument.size_precision();
47
48 let price = Price::new(msg.price, price_precision);
49 let size = Quantity::new(msg.amount.abs(), size_precision);
50
51 let aggressor_side = match msg.direction.as_str() {
52 "buy" => AggressorSide::Buyer,
53 "sell" => AggressorSide::Seller,
54 _ => AggressorSide::NoAggressor,
55 };
56
57 let trade_id = TradeId::new(&msg.trade_id);
58 let ts_event = UnixNanos::new(msg.timestamp * NANOSECONDS_IN_MILLISECOND);
59
60 TradeTick::new_checked(
61 instrument_id,
62 price,
63 size,
64 aggressor_side,
65 trade_id,
66 ts_event,
67 ts_init,
68 )
69}
70
71pub fn parse_trades_data(
73 trades: Vec<DeribitTradeMsg>,
74 instruments_cache: &AHashMap<Ustr, InstrumentAny>,
75 ts_init: UnixNanos,
76) -> Vec<Data> {
77 trades
78 .iter()
79 .filter_map(|msg| {
80 instruments_cache
81 .get(&msg.instrument_name)
82 .and_then(|inst| parse_trade_msg(msg, inst, ts_init).ok())
83 .map(Data::Trade)
84 })
85 .collect()
86}
87
88#[allow(dead_code)] fn convert_book_action(action: &DeribitBookAction) -> BookAction {
91 match action {
92 DeribitBookAction::New => BookAction::Add,
93 DeribitBookAction::Change => BookAction::Update,
94 DeribitBookAction::Delete => BookAction::Delete,
95 }
96}
97
98pub fn parse_book_snapshot(
104 msg: &DeribitBookMsg,
105 instrument: &InstrumentAny,
106 ts_init: UnixNanos,
107) -> anyhow::Result<OrderBookDeltas> {
108 let instrument_id = instrument.id();
109 let price_precision = instrument.price_precision();
110 let size_precision = instrument.size_precision();
111 let ts_event = UnixNanos::new(msg.timestamp * NANOSECONDS_IN_MILLISECOND);
112
113 let mut deltas = Vec::new();
114
115 deltas.push(OrderBookDelta::clear(
117 instrument_id,
118 msg.change_id,
119 ts_event,
120 ts_init,
121 ));
122
123 for (i, bid) in msg.bids.iter().enumerate() {
125 if bid.len() >= 3 {
126 let price_val = bid[1].as_f64().unwrap_or(0.0);
128 let amount_val = bid[2].as_f64().unwrap_or(0.0);
129
130 if amount_val > 0.0 {
131 let price = Price::new(price_val, price_precision);
132 let size = Quantity::new(amount_val, size_precision);
133
134 deltas.push(OrderBookDelta::new(
135 instrument_id,
136 BookAction::Add,
137 BookOrder::new(OrderSide::Buy, price, size, i as u64),
138 0, msg.change_id,
140 ts_event,
141 ts_init,
142 ));
143 }
144 }
145 }
146
147 let num_bids = msg.bids.len();
149 for (i, ask) in msg.asks.iter().enumerate() {
150 if ask.len() >= 3 {
151 let price_val = ask[1].as_f64().unwrap_or(0.0);
153 let amount_val = ask[2].as_f64().unwrap_or(0.0);
154
155 if amount_val > 0.0 {
156 let price = Price::new(price_val, price_precision);
157 let size = Quantity::new(amount_val, size_precision);
158
159 deltas.push(OrderBookDelta::new(
160 instrument_id,
161 BookAction::Add,
162 BookOrder::new(OrderSide::Sell, price, size, (num_bids + i) as u64),
163 0, msg.change_id,
165 ts_event,
166 ts_init,
167 ));
168 }
169 }
170 }
171
172 if let Some(last) = deltas.last_mut() {
174 *last = OrderBookDelta::new(
175 last.instrument_id,
176 last.action,
177 last.order,
178 RecordFlag::F_LAST as u8,
179 last.sequence,
180 last.ts_event,
181 last.ts_init,
182 );
183 }
184
185 Ok(OrderBookDeltas::new(instrument_id, deltas))
186}
187
188pub fn parse_book_delta(
194 msg: &DeribitBookMsg,
195 instrument: &InstrumentAny,
196 ts_init: UnixNanos,
197) -> anyhow::Result<OrderBookDeltas> {
198 let instrument_id = instrument.id();
199 let price_precision = instrument.price_precision();
200 let size_precision = instrument.size_precision();
201 let ts_event = UnixNanos::new(msg.timestamp * NANOSECONDS_IN_MILLISECOND);
202
203 let mut deltas = Vec::new();
204
205 for (i, bid) in msg.bids.iter().enumerate() {
207 if bid.len() >= 3 {
208 let action_str = bid[0].as_str().unwrap_or("new");
209 let price_val = bid[1].as_f64().unwrap_or(0.0);
210 let amount_val = bid[2].as_f64().unwrap_or(0.0);
211
212 let action = match action_str {
213 "new" => BookAction::Add,
214 "change" => BookAction::Update,
215 "delete" => BookAction::Delete,
216 _ => continue,
217 };
218
219 let price = Price::new(price_val, price_precision);
220 let size = Quantity::new(amount_val.abs(), size_precision);
221
222 deltas.push(OrderBookDelta::new(
223 instrument_id,
224 action,
225 BookOrder::new(OrderSide::Buy, price, size, i as u64),
226 0, msg.change_id,
228 ts_event,
229 ts_init,
230 ));
231 }
232 }
233
234 let num_bids = msg.bids.len();
236 for (i, ask) in msg.asks.iter().enumerate() {
237 if ask.len() >= 3 {
238 let action_str = ask[0].as_str().unwrap_or("new");
239 let price_val = ask[1].as_f64().unwrap_or(0.0);
240 let amount_val = ask[2].as_f64().unwrap_or(0.0);
241
242 let action = match action_str {
243 "new" => BookAction::Add,
244 "change" => BookAction::Update,
245 "delete" => BookAction::Delete,
246 _ => continue,
247 };
248
249 let price = Price::new(price_val, price_precision);
250 let size = Quantity::new(amount_val.abs(), size_precision);
251
252 deltas.push(OrderBookDelta::new(
253 instrument_id,
254 action,
255 BookOrder::new(OrderSide::Sell, price, size, (num_bids + i) as u64),
256 0, msg.change_id,
258 ts_event,
259 ts_init,
260 ));
261 }
262 }
263
264 if let Some(last) = deltas.last_mut() {
266 *last = OrderBookDelta::new(
267 last.instrument_id,
268 last.action,
269 last.order,
270 RecordFlag::F_LAST as u8,
271 last.sequence,
272 last.ts_event,
273 last.ts_init,
274 );
275 }
276
277 Ok(OrderBookDeltas::new(instrument_id, deltas))
278}
279
280pub fn parse_book_msg(
286 msg: &DeribitBookMsg,
287 instrument: &InstrumentAny,
288 ts_init: UnixNanos,
289) -> anyhow::Result<OrderBookDeltas> {
290 match msg.msg_type {
291 DeribitBookMsgType::Snapshot => parse_book_snapshot(msg, instrument, ts_init),
292 DeribitBookMsgType::Change => parse_book_delta(msg, instrument, ts_init),
293 }
294}
295
296pub fn parse_ticker_to_quote(
302 msg: &DeribitTickerMsg,
303 instrument: &InstrumentAny,
304 ts_init: UnixNanos,
305) -> anyhow::Result<QuoteTick> {
306 let instrument_id = instrument.id();
307 let price_precision = instrument.price_precision();
308 let size_precision = instrument.size_precision();
309
310 let bid_price = Price::new(msg.best_bid_price.unwrap_or(0.0), price_precision);
311 let ask_price = Price::new(msg.best_ask_price.unwrap_or(0.0), price_precision);
312 let bid_size = Quantity::new(msg.best_bid_amount.unwrap_or(0.0), size_precision);
313 let ask_size = Quantity::new(msg.best_ask_amount.unwrap_or(0.0), size_precision);
314 let ts_event = UnixNanos::new(msg.timestamp * NANOSECONDS_IN_MILLISECOND);
315
316 QuoteTick::new_checked(
317 instrument_id,
318 bid_price,
319 ask_price,
320 bid_size,
321 ask_size,
322 ts_event,
323 ts_init,
324 )
325}
326
327pub fn parse_quote_msg(
333 msg: &DeribitQuoteMsg,
334 instrument: &InstrumentAny,
335 ts_init: UnixNanos,
336) -> anyhow::Result<QuoteTick> {
337 let instrument_id = instrument.id();
338 let price_precision = instrument.price_precision();
339 let size_precision = instrument.size_precision();
340
341 let bid_price = Price::new(msg.best_bid_price, price_precision);
342 let ask_price = Price::new(msg.best_ask_price, price_precision);
343 let bid_size = Quantity::new(msg.best_bid_amount, size_precision);
344 let ask_size = Quantity::new(msg.best_ask_amount, size_precision);
345 let ts_event = UnixNanos::new(msg.timestamp * NANOSECONDS_IN_MILLISECOND);
346
347 QuoteTick::new_checked(
348 instrument_id,
349 bid_price,
350 ask_price,
351 bid_size,
352 ask_size,
353 ts_event,
354 ts_init,
355 )
356}
357
358#[cfg(test)]
359mod tests {
360 use rstest::rstest;
361
362 use super::*;
363 use crate::{
364 common::{parse::parse_deribit_instrument_any, testing::load_test_json},
365 http::models::{DeribitInstrument, DeribitJsonRpcResponse},
366 };
367
368 fn test_perpetual_instrument() -> InstrumentAny {
370 let json = load_test_json("http_get_instruments.json");
371 let response: DeribitJsonRpcResponse<Vec<DeribitInstrument>> =
372 serde_json::from_str(&json).unwrap();
373 let instrument = &response.result.unwrap()[0];
374 parse_deribit_instrument_any(instrument, UnixNanos::default(), UnixNanos::default())
375 .unwrap()
376 .unwrap()
377 }
378
379 #[rstest]
380 fn test_parse_trade_msg_sell() {
381 let instrument = test_perpetual_instrument();
382 let json = load_test_json("ws_trades.json");
383 let response: serde_json::Value = serde_json::from_str(&json).unwrap();
384 let trades: Vec<DeribitTradeMsg> =
385 serde_json::from_value(response["params"]["data"].clone()).unwrap();
386 let msg = &trades[0];
387
388 let tick = parse_trade_msg(msg, &instrument, UnixNanos::default()).unwrap();
389
390 assert_eq!(tick.instrument_id, instrument.id());
391 assert_eq!(tick.price, instrument.make_price(92294.5));
392 assert_eq!(tick.size, instrument.make_qty(10.0, None));
393 assert_eq!(tick.aggressor_side, AggressorSide::Seller);
394 assert_eq!(tick.trade_id.to_string(), "403691824");
395 assert_eq!(tick.ts_event, UnixNanos::new(1_765_531_356_452_000_000));
396 }
397
398 #[rstest]
399 fn test_parse_trade_msg_buy() {
400 let instrument = test_perpetual_instrument();
401 let json = load_test_json("ws_trades.json");
402 let response: serde_json::Value = serde_json::from_str(&json).unwrap();
403 let trades: Vec<DeribitTradeMsg> =
404 serde_json::from_value(response["params"]["data"].clone()).unwrap();
405 let msg = &trades[1];
406
407 let tick = parse_trade_msg(msg, &instrument, UnixNanos::default()).unwrap();
408
409 assert_eq!(tick.instrument_id, instrument.id());
410 assert_eq!(tick.price, instrument.make_price(92288.5));
411 assert_eq!(tick.size, instrument.make_qty(750.0, None));
412 assert_eq!(tick.aggressor_side, AggressorSide::Seller);
413 assert_eq!(tick.trade_id.to_string(), "403691825");
414 }
415
416 #[rstest]
417 fn test_parse_book_snapshot() {
418 let instrument = test_perpetual_instrument();
419 let json = load_test_json("ws_book_snapshot.json");
420 let response: serde_json::Value = serde_json::from_str(&json).unwrap();
421 let msg: DeribitBookMsg =
422 serde_json::from_value(response["params"]["data"].clone()).unwrap();
423
424 let deltas = parse_book_snapshot(&msg, &instrument, UnixNanos::default()).unwrap();
425
426 assert_eq!(deltas.instrument_id, instrument.id());
427 assert_eq!(deltas.deltas.len(), 11);
429
430 assert_eq!(deltas.deltas[0].action, BookAction::Clear);
432
433 let first_bid = &deltas.deltas[1];
435 assert_eq!(first_bid.action, BookAction::Add);
436 assert_eq!(first_bid.order.side, OrderSide::Buy);
437 assert_eq!(first_bid.order.price, instrument.make_price(42500.0));
438 assert_eq!(first_bid.order.size, instrument.make_qty(1000.0, None));
439
440 let first_ask = &deltas.deltas[6];
442 assert_eq!(first_ask.action, BookAction::Add);
443 assert_eq!(first_ask.order.side, OrderSide::Sell);
444 assert_eq!(first_ask.order.price, instrument.make_price(42501.0));
445 assert_eq!(first_ask.order.size, instrument.make_qty(800.0, None));
446
447 let last = deltas.deltas.last().unwrap();
449 assert_eq!(
450 last.flags & RecordFlag::F_LAST as u8,
451 RecordFlag::F_LAST as u8
452 );
453 }
454
455 #[rstest]
456 fn test_parse_book_delta() {
457 let instrument = test_perpetual_instrument();
458 let json = load_test_json("ws_book_delta.json");
459 let response: serde_json::Value = serde_json::from_str(&json).unwrap();
460 let msg: DeribitBookMsg =
461 serde_json::from_value(response["params"]["data"].clone()).unwrap();
462
463 let deltas = parse_book_delta(&msg, &instrument, UnixNanos::default()).unwrap();
464
465 assert_eq!(deltas.instrument_id, instrument.id());
466 assert_eq!(deltas.deltas.len(), 4);
468
469 let bid_change = &deltas.deltas[0];
471 assert_eq!(bid_change.action, BookAction::Update);
472 assert_eq!(bid_change.order.side, OrderSide::Buy);
473 assert_eq!(bid_change.order.price, instrument.make_price(42500.0));
474 assert_eq!(bid_change.order.size, instrument.make_qty(950.0, None));
475
476 let bid_new = &deltas.deltas[1];
478 assert_eq!(bid_new.action, BookAction::Add);
479 assert_eq!(bid_new.order.side, OrderSide::Buy);
480 assert_eq!(bid_new.order.price, instrument.make_price(42498.5));
481 assert_eq!(bid_new.order.size, instrument.make_qty(300.0, None));
482
483 let ask_delete = &deltas.deltas[2];
485 assert_eq!(ask_delete.action, BookAction::Delete);
486 assert_eq!(ask_delete.order.side, OrderSide::Sell);
487 assert_eq!(ask_delete.order.price, instrument.make_price(42501.0));
488 assert_eq!(ask_delete.order.size, instrument.make_qty(0.0, None));
489
490 let ask_change = &deltas.deltas[3];
492 assert_eq!(ask_change.action, BookAction::Update);
493 assert_eq!(ask_change.order.side, OrderSide::Sell);
494 assert_eq!(ask_change.order.price, instrument.make_price(42501.5));
495 assert_eq!(ask_change.order.size, instrument.make_qty(700.0, None));
496
497 let last = deltas.deltas.last().unwrap();
499 assert_eq!(
500 last.flags & RecordFlag::F_LAST as u8,
501 RecordFlag::F_LAST as u8
502 );
503 }
504
505 #[rstest]
506 fn test_parse_ticker_to_quote() {
507 let instrument = test_perpetual_instrument();
508 let json = load_test_json("ws_ticker.json");
509 let response: serde_json::Value = serde_json::from_str(&json).unwrap();
510 let msg: DeribitTickerMsg =
511 serde_json::from_value(response["params"]["data"].clone()).unwrap();
512
513 assert_eq!(msg.instrument_name.as_str(), "BTC-PERPETUAL");
515 assert_eq!(msg.timestamp, 1_765_541_474_086);
516 assert_eq!(msg.best_bid_price, Some(92283.5));
517 assert_eq!(msg.best_ask_price, Some(92284.0));
518 assert_eq!(msg.best_bid_amount, Some(117660.0));
519 assert_eq!(msg.best_ask_amount, Some(186520.0));
520 assert_eq!(msg.mark_price, 92281.78);
521 assert_eq!(msg.index_price, 92263.55);
522 assert_eq!(msg.open_interest, 1132329370.0);
523
524 let quote = parse_ticker_to_quote(&msg, &instrument, UnixNanos::default()).unwrap();
525
526 assert_eq!(quote.instrument_id, instrument.id());
527 assert_eq!(quote.bid_price, instrument.make_price(92283.5));
528 assert_eq!(quote.ask_price, instrument.make_price(92284.0));
529 assert_eq!(quote.bid_size, instrument.make_qty(117660.0, None));
530 assert_eq!(quote.ask_size, instrument.make_qty(186520.0, None));
531 assert_eq!(quote.ts_event, UnixNanos::new(1_765_541_474_086_000_000));
532 }
533
534 #[rstest]
535 fn test_parse_quote_msg() {
536 let instrument = test_perpetual_instrument();
537 let json = load_test_json("ws_quote.json");
538 let response: serde_json::Value = serde_json::from_str(&json).unwrap();
539 let msg: DeribitQuoteMsg =
540 serde_json::from_value(response["params"]["data"].clone()).unwrap();
541
542 assert_eq!(msg.instrument_name.as_str(), "BTC-PERPETUAL");
544 assert_eq!(msg.timestamp, 1_765_541_767_174);
545 assert_eq!(msg.best_bid_price, 92288.0);
546 assert_eq!(msg.best_ask_price, 92288.5);
547 assert_eq!(msg.best_bid_amount, 133440.0);
548 assert_eq!(msg.best_ask_amount, 99470.0);
549
550 let quote = parse_quote_msg(&msg, &instrument, UnixNanos::default()).unwrap();
551
552 assert_eq!(quote.instrument_id, instrument.id());
553 assert_eq!(quote.bid_price, instrument.make_price(92288.0));
554 assert_eq!(quote.ask_price, instrument.make_price(92288.5));
555 assert_eq!(quote.bid_size, instrument.make_qty(133440.0, None));
556 assert_eq!(quote.ask_size, instrument.make_qty(99470.0, None));
557 assert_eq!(quote.ts_event, UnixNanos::new(1_765_541_767_174_000_000));
558 }
559
560 #[rstest]
561 fn test_parse_book_msg_snapshot() {
562 let instrument = test_perpetual_instrument();
563 let json = load_test_json("ws_book_snapshot.json");
564 let response: serde_json::Value = serde_json::from_str(&json).unwrap();
565 let msg: DeribitBookMsg =
566 serde_json::from_value(response["params"]["data"].clone()).unwrap();
567
568 assert_eq!(
570 msg.bids[0].len(),
571 3,
572 "Snapshot bids should have 3 elements: [action, price, amount]"
573 );
574 assert_eq!(
575 msg.bids[0][0].as_str(),
576 Some("new"),
577 "First element should be 'new' action for snapshot"
578 );
579 assert_eq!(
580 msg.asks[0].len(),
581 3,
582 "Snapshot asks should have 3 elements: [action, price, amount]"
583 );
584 assert_eq!(
585 msg.asks[0][0].as_str(),
586 Some("new"),
587 "First element should be 'new' action for snapshot"
588 );
589
590 let deltas = parse_book_msg(&msg, &instrument, UnixNanos::default()).unwrap();
591
592 assert_eq!(deltas.instrument_id, instrument.id());
593 assert_eq!(deltas.deltas.len(), 11);
595
596 assert_eq!(deltas.deltas[0].action, BookAction::Clear);
598
599 let first_bid = &deltas.deltas[1];
601 assert_eq!(first_bid.action, BookAction::Add);
602 assert_eq!(first_bid.order.side, OrderSide::Buy);
603 assert_eq!(first_bid.order.price, instrument.make_price(42500.0));
604 assert_eq!(first_bid.order.size, instrument.make_qty(1000.0, None));
605
606 let first_ask = &deltas.deltas[6];
608 assert_eq!(first_ask.action, BookAction::Add);
609 assert_eq!(first_ask.order.side, OrderSide::Sell);
610 assert_eq!(first_ask.order.price, instrument.make_price(42501.0));
611 assert_eq!(first_ask.order.size, instrument.make_qty(800.0, None));
612 }
613
614 #[rstest]
615 fn test_parse_book_msg_delta() {
616 let instrument = test_perpetual_instrument();
617 let json = load_test_json("ws_book_delta.json");
618 let response: serde_json::Value = serde_json::from_str(&json).unwrap();
619 let msg: DeribitBookMsg =
620 serde_json::from_value(response["params"]["data"].clone()).unwrap();
621
622 assert_eq!(
624 msg.bids[0].len(),
625 3,
626 "Delta bids should have 3 elements: [action, price, amount]"
627 );
628 assert_eq!(
629 msg.bids[0][0].as_str(),
630 Some("change"),
631 "First bid should be 'change' action"
632 );
633 assert_eq!(
634 msg.bids[1][0].as_str(),
635 Some("new"),
636 "Second bid should be 'new' action"
637 );
638 assert_eq!(
639 msg.asks[0].len(),
640 3,
641 "Delta asks should have 3 elements: [action, price, amount]"
642 );
643 assert_eq!(
644 msg.asks[0][0].as_str(),
645 Some("delete"),
646 "First ask should be 'delete' action"
647 );
648
649 let deltas = parse_book_msg(&msg, &instrument, UnixNanos::default()).unwrap();
650
651 assert_eq!(deltas.instrument_id, instrument.id());
652 assert_eq!(deltas.deltas.len(), 4);
654
655 assert_ne!(deltas.deltas[0].action, BookAction::Clear);
657
658 let bid_change = &deltas.deltas[0];
660 assert_eq!(bid_change.action, BookAction::Update);
661 assert_eq!(bid_change.order.side, OrderSide::Buy);
662 assert_eq!(bid_change.order.price, instrument.make_price(42500.0));
663 assert_eq!(bid_change.order.size, instrument.make_qty(950.0, None));
664
665 let bid_new = &deltas.deltas[1];
667 assert_eq!(bid_new.action, BookAction::Add);
668 assert_eq!(bid_new.order.side, OrderSide::Buy);
669 assert_eq!(bid_new.order.price, instrument.make_price(42498.5));
670 assert_eq!(bid_new.order.size, instrument.make_qty(300.0, None));
671
672 let ask_delete = &deltas.deltas[2];
674 assert_eq!(ask_delete.action, BookAction::Delete);
675 assert_eq!(ask_delete.order.side, OrderSide::Sell);
676 assert_eq!(ask_delete.order.price, instrument.make_price(42501.0));
677
678 let ask_change = &deltas.deltas[3];
680 assert_eq!(ask_change.action, BookAction::Update);
681 assert_eq!(ask_change.order.side, OrderSide::Sell);
682 assert_eq!(ask_change.order.price, instrument.make_price(42501.5));
683 assert_eq!(ask_change.order.size, instrument.make_qty(700.0, None));
684 }
685}