1use std::collections::HashMap;
19
20use chrono::{DateTime, Utc};
21use nautilus_model::{
22 data::{Data, funding::FundingRateUpdate},
23 events::{AccountState, OrderUpdated},
24 reports::{FillReport, OrderStatusReport, PositionStatusReport},
25};
26use serde::{Deserialize, Deserializer, Serialize, de};
27use serde_json::Value;
28use strum::Display;
29use ustr::Ustr;
30use uuid::Uuid;
31
32use super::enums::{
33 BitmexAction, BitmexSide, BitmexTickDirection, BitmexWsAuthAction, BitmexWsOperation,
34};
35use crate::common::enums::{
36 BitmexContingencyType, BitmexExecInstruction, BitmexExecType, BitmexLiquidityIndicator,
37 BitmexOrderStatus, BitmexOrderType, BitmexPegPriceType, BitmexTimeInForce,
38};
39
40fn deserialize_exec_instructions<'de, D>(
42 deserializer: D,
43) -> Result<Option<Vec<BitmexExecInstruction>>, D::Error>
44where
45 D: serde::Deserializer<'de>,
46{
47 let s: Option<String> = Option::deserialize(deserializer)?;
48 match s {
49 None => Ok(None),
50 Some(ref s) if s.is_empty() => Ok(None),
51 Some(s) => {
52 let instructions: Result<Vec<BitmexExecInstruction>, _> = s
53 .split(',')
54 .map(|inst| {
55 let trimmed = inst.trim();
56 match trimmed {
57 "ParticipateDoNotInitiate" => {
58 Ok(BitmexExecInstruction::ParticipateDoNotInitiate)
59 }
60 "AllOrNone" => Ok(BitmexExecInstruction::AllOrNone),
61 "MarkPrice" => Ok(BitmexExecInstruction::MarkPrice),
62 "IndexPrice" => Ok(BitmexExecInstruction::IndexPrice),
63 "LastPrice" => Ok(BitmexExecInstruction::LastPrice),
64 "Close" => Ok(BitmexExecInstruction::Close),
65 "ReduceOnly" => Ok(BitmexExecInstruction::ReduceOnly),
66 "Fixed" => Ok(BitmexExecInstruction::Fixed),
67 "" => Ok(BitmexExecInstruction::Unknown),
68 _ => Err(format!("Unknown exec instruction: {trimmed}")),
69 }
70 })
71 .collect();
72 instructions.map(Some).map_err(de::Error::custom)
73 }
74 }
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct BitmexAuthentication {
83 pub op: BitmexWsAuthAction,
84 pub args: (String, i64, String),
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct BitmexSubscription {
90 pub op: BitmexWsOperation,
91 pub args: Vec<Ustr>,
92}
93
94#[derive(Clone, Debug)]
96pub enum NautilusWsMessage {
97 Data(Vec<Data>),
98 OrderStatusReports(Vec<OrderStatusReport>),
99 OrderUpdated(OrderUpdated),
100 FillReports(Vec<FillReport>),
101 PositionStatusReport(PositionStatusReport),
102 FundingRateUpdates(Vec<FundingRateUpdate>),
103 AccountState(AccountState),
104 Reconnected,
105}
106
107#[derive(Debug, Display, Deserialize)]
109#[serde(untagged)]
110pub enum BitmexWsMessage {
111 Table(BitmexTableMessage),
113 Welcome {
115 info: String,
117 version: String,
119 timestamp: DateTime<Utc>,
121 docs: String,
123 #[serde(rename = "heartbeatEnabled")]
125 heartbeat_enabled: bool,
126 limit: BitmexRateLimit,
128 #[serde(rename = "appName")]
130 app_name: Option<String>,
131 },
132 Subscription {
134 success: bool,
136 subscribe: Option<String>,
138 request: Option<BitmexHttpRequest>,
140 error: Option<String>,
142 },
143 Error {
145 status: u16,
146 error: String,
147 meta: HashMap<String, String>,
148 request: BitmexHttpRequest,
149 },
150 #[serde(skip)]
152 Reconnected,
153}
154
155#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
156pub struct BitmexHttpRequest {
157 pub op: String,
158 pub args: Vec<Value>,
159}
160
161#[derive(Debug, Deserialize)]
163pub struct BitmexRateLimit {
164 pub remaining: Option<i32>,
166}
167
168#[derive(Debug, Display, Deserialize)]
170#[serde(rename_all = "camelCase")]
171#[serde(tag = "table")]
172pub enum BitmexTableMessage {
173 OrderBookL2 {
174 action: BitmexAction,
175 data: Vec<BitmexOrderBookMsg>,
176 },
177 OrderBookL2_25 {
178 action: BitmexAction,
179 data: Vec<BitmexOrderBookMsg>,
180 },
181 OrderBook10 {
182 action: BitmexAction,
183 data: Vec<BitmexOrderBook10Msg>,
184 },
185 Quote {
186 action: BitmexAction,
187 data: Vec<BitmexQuoteMsg>,
188 },
189 Trade {
190 action: BitmexAction,
191 data: Vec<BitmexTradeMsg>,
192 },
193 TradeBin1m {
194 action: BitmexAction,
195 data: Vec<BitmexTradeBinMsg>,
196 },
197 TradeBin5m {
198 action: BitmexAction,
199 data: Vec<BitmexTradeBinMsg>,
200 },
201 TradeBin1h {
202 action: BitmexAction,
203 data: Vec<BitmexTradeBinMsg>,
204 },
205 TradeBin1d {
206 action: BitmexAction,
207 data: Vec<BitmexTradeBinMsg>,
208 },
209 Instrument {
210 action: BitmexAction,
211 data: Vec<BitmexInstrumentMsg>,
212 },
213 Order {
214 action: BitmexAction,
215 #[serde(deserialize_with = "deserialize_order_data")]
216 data: Vec<OrderData>,
217 },
218 Execution {
219 action: BitmexAction,
220 data: Vec<BitmexExecutionMsg>,
221 },
222 Position {
223 action: BitmexAction,
224 data: Vec<BitmexPositionMsg>,
225 },
226 Wallet {
227 action: BitmexAction,
228 data: Vec<BitmexWalletMsg>,
229 },
230 Margin {
231 action: BitmexAction,
232 data: Vec<BitmexMarginMsg>,
233 },
234 Funding {
235 action: BitmexAction,
236 data: Vec<BitmexFundingMsg>,
237 },
238 Insurance {
239 action: BitmexAction,
240 data: Vec<BitmexInsuranceMsg>,
241 },
242 Liquidation {
243 action: BitmexAction,
244 data: Vec<BitmexLiquidationMsg>,
245 },
246}
247
248#[derive(Clone, Debug, Deserialize)]
250#[serde(rename_all = "camelCase")]
251pub struct BitmexOrderBookMsg {
252 pub symbol: Ustr,
254 pub id: u64,
256 pub side: BitmexSide,
258 pub size: Option<u64>,
260 pub price: f64,
262 pub timestamp: DateTime<Utc>,
264 pub transact_time: DateTime<Utc>,
266}
267
268#[derive(Clone, Debug, Deserialize)]
270#[serde(rename_all = "camelCase")]
271pub struct BitmexOrderBook10Msg {
272 pub symbol: Ustr,
274 pub bids: Vec<[f64; 2]>,
276 pub asks: Vec<[f64; 2]>,
278 pub timestamp: DateTime<Utc>,
280}
281
282#[derive(Clone, Debug, Deserialize)]
284#[serde(rename_all = "camelCase")]
285pub struct BitmexQuoteMsg {
286 pub symbol: Ustr,
288 pub bid_price: Option<f64>,
290 pub bid_size: Option<u64>,
292 pub ask_price: Option<f64>,
294 pub ask_size: Option<u64>,
296 pub timestamp: DateTime<Utc>,
298}
299
300#[derive(Clone, Debug, Deserialize)]
302#[serde(rename_all = "camelCase")]
303pub struct BitmexTradeMsg {
304 pub timestamp: DateTime<Utc>,
306 pub symbol: Ustr,
308 pub side: BitmexSide,
310 pub size: u64,
312 pub price: f64,
314 pub tick_direction: BitmexTickDirection,
316 #[serde(rename = "trdMatchID")]
318 pub trd_match_id: Option<Uuid>,
319 pub gross_value: Option<i64>,
321 pub home_notional: Option<f64>,
323 pub foreign_notional: Option<f64>,
325 #[serde(rename = "trdType")]
327 pub trade_type: Ustr, }
329
330#[derive(Clone, Debug, Deserialize)]
331#[serde(rename_all = "camelCase")]
332pub struct BitmexTradeBinMsg {
333 pub timestamp: DateTime<Utc>,
335 pub symbol: Ustr,
337 pub open: f64,
339 pub high: f64,
341 pub low: f64,
343 pub close: f64,
345 pub trades: i64,
347 pub volume: i64,
349 pub vwap: f64,
351 pub last_size: i64,
353 pub turnover: i64,
355 pub home_notional: f64,
357 pub foreign_notional: f64,
359}
360
361#[derive(Clone, Debug, Deserialize)]
363#[serde(rename_all = "camelCase")]
364pub struct BitmexInstrumentMsg {
365 pub symbol: Ustr,
367 pub last_price: Option<f64>,
369 pub last_tick_direction: Option<BitmexTickDirection>,
371 pub mark_price: Option<f64>,
373 pub index_price: Option<f64>,
375 pub indicative_settle_price: Option<f64>,
377 pub open_interest: Option<i64>,
379 pub open_value: Option<i64>,
381 pub fair_basis: Option<f64>,
383 pub fair_basis_rate: Option<f64>,
385 pub fair_price: Option<f64>,
387 pub mark_method: Option<Ustr>,
389 pub indicative_tax_rate: Option<f64>,
391 pub timestamp: DateTime<Utc>,
393}
394
395#[derive(Clone, Debug, Deserialize)]
398#[serde(rename_all = "camelCase")]
399pub struct BitmexOrderUpdateMsg {
400 #[serde(rename = "orderID")]
401 pub order_id: Uuid,
402 #[serde(rename = "clOrdID")]
403 pub cl_ord_id: Option<Ustr>,
404 pub account: i64,
405 pub symbol: Ustr,
406 pub side: Option<BitmexSide>,
407 pub price: Option<f64>,
408 pub currency: Option<Ustr>,
409 pub text: Option<Ustr>,
410 pub transact_time: Option<DateTime<Utc>>,
411 pub timestamp: Option<DateTime<Utc>>,
412 pub leaves_qty: Option<i64>,
413 pub cum_qty: Option<i64>,
414 pub ord_status: Option<BitmexOrderStatus>,
415}
416
417#[derive(Clone, Debug, Deserialize)]
420#[serde(rename_all = "camelCase")]
421pub struct BitmexOrderMsg {
422 #[serde(rename = "orderID")]
423 pub order_id: Uuid,
424 #[serde(rename = "clOrdID")]
425 pub cl_ord_id: Option<Ustr>,
426 #[serde(rename = "clOrdLinkID")]
427 pub cl_ord_link_id: Option<Ustr>,
428 pub account: i64,
429 pub symbol: Ustr,
430 pub side: BitmexSide,
431 pub order_qty: i64,
432 pub price: Option<f64>,
433 pub display_qty: Option<i64>,
434 pub stop_px: Option<f64>,
435 pub peg_offset_value: Option<f64>,
436 pub peg_price_type: Option<BitmexPegPriceType>,
437 pub currency: Ustr,
438 pub settl_currency: Ustr,
439 pub ord_type: Option<BitmexOrderType>,
440 pub time_in_force: Option<BitmexTimeInForce>,
441 #[serde(default, deserialize_with = "deserialize_exec_instructions")]
442 pub exec_inst: Option<Vec<BitmexExecInstruction>>,
443 pub contingency_type: Option<BitmexContingencyType>,
444 pub ord_status: BitmexOrderStatus,
445 pub triggered: Option<Ustr>,
446 pub working_indicator: bool,
447 pub ord_rej_reason: Option<Ustr>,
448 pub leaves_qty: i64,
449 pub cum_qty: i64,
450 pub avg_px: Option<f64>,
451 pub text: Option<Ustr>,
452 pub transact_time: DateTime<Utc>,
453 pub timestamp: DateTime<Utc>,
454}
455
456#[derive(Clone, Debug)]
458pub enum OrderData {
459 Full(BitmexOrderMsg),
460 Update(BitmexOrderUpdateMsg),
461}
462
463fn deserialize_order_data<'de, D>(deserializer: D) -> Result<Vec<OrderData>, D::Error>
466where
467 D: Deserializer<'de>,
468{
469 let raw_values: Vec<serde_json::Value> = Vec::deserialize(deserializer)?;
470 let mut result = Vec::new();
471
472 for value in raw_values {
473 if let Ok(full_msg) = serde_json::from_value::<BitmexOrderMsg>(value.clone()) {
475 result.push(OrderData::Full(full_msg));
476 } else if let Ok(update_msg) = serde_json::from_value::<BitmexOrderUpdateMsg>(value) {
477 result.push(OrderData::Update(update_msg));
478 } else {
479 return Err(de::Error::custom(
480 "Failed to deserialize order data as either full or update message",
481 ));
482 }
483 }
484
485 Ok(result)
486}
487
488#[derive(Clone, Debug, Deserialize)]
490#[serde(rename_all = "camelCase")]
491pub struct BitmexExecutionMsg {
492 #[serde(rename = "execID")]
493 pub exec_id: Option<Uuid>,
494 #[serde(rename = "orderID")]
495 pub order_id: Option<Uuid>,
496 #[serde(rename = "clOrdID")]
497 pub cl_ord_id: Option<Ustr>,
498 #[serde(rename = "clOrdLinkID")]
499 pub cl_ord_link_id: Option<Ustr>,
500 pub account: Option<i64>,
501 pub symbol: Option<Ustr>,
502 pub side: Option<BitmexSide>,
503 pub last_qty: Option<i64>,
504 pub last_px: Option<f64>,
505 pub underlying_last_px: Option<f64>,
506 pub last_mkt: Option<Ustr>,
507 pub last_liquidity_ind: Option<BitmexLiquidityIndicator>,
508 pub order_qty: Option<i64>,
509 pub price: Option<f64>,
510 pub display_qty: Option<i64>,
511 pub stop_px: Option<f64>,
512 pub peg_offset_value: Option<f64>,
513 pub peg_price_type: Option<BitmexPegPriceType>,
514 pub currency: Option<Ustr>,
515 pub settl_currency: Option<Ustr>,
516 pub exec_type: Option<BitmexExecType>,
517 pub ord_type: Option<BitmexOrderType>,
518 pub time_in_force: Option<BitmexTimeInForce>,
519 #[serde(default, deserialize_with = "deserialize_exec_instructions")]
520 pub exec_inst: Option<Vec<BitmexExecInstruction>>,
521 pub contingency_type: Option<BitmexContingencyType>,
522 pub ex_destination: Option<Ustr>,
523 pub ord_status: Option<BitmexOrderStatus>,
524 pub triggered: Option<Ustr>,
525 pub working_indicator: Option<bool>,
526 pub ord_rej_reason: Option<Ustr>,
527 pub leaves_qty: Option<i64>,
528 pub cum_qty: Option<i64>,
529 pub avg_px: Option<f64>,
530 pub commission: Option<f64>,
531 pub trade_publish_indicator: Option<Ustr>,
532 pub multi_leg_reporting_type: Option<Ustr>,
533 pub text: Option<Ustr>,
534 #[serde(rename = "trdMatchID")]
535 pub trd_match_id: Option<Uuid>,
536 pub exec_cost: Option<i64>,
537 pub exec_comm: Option<i64>,
538 pub home_notional: Option<f64>,
539 pub foreign_notional: Option<f64>,
540 pub transact_time: Option<DateTime<Utc>>,
541 pub timestamp: Option<DateTime<Utc>>,
542}
543
544#[derive(Clone, Debug, Deserialize)]
546#[serde(rename_all = "camelCase")]
547pub struct BitmexPositionMsg {
548 pub account: i64,
549 pub symbol: Ustr,
550 pub currency: Option<Ustr>,
551 pub underlying: Option<Ustr>,
552 pub quote_currency: Option<Ustr>,
553 pub commission: Option<f64>,
554 pub init_margin_req: Option<f64>,
555 pub maint_margin_req: Option<f64>,
556 pub risk_limit: Option<i64>,
557 pub leverage: Option<f64>,
558 pub cross_margin: Option<bool>,
559 pub deleverage_percentile: Option<f64>,
560 pub rebalanced_pnl: Option<i64>,
561 pub prev_realised_pnl: Option<i64>,
562 pub prev_unrealised_pnl: Option<i64>,
563 pub prev_close_price: Option<f64>,
564 pub opening_timestamp: Option<DateTime<Utc>>,
565 pub opening_qty: Option<i64>,
566 pub opening_cost: Option<i64>,
567 pub opening_comm: Option<i64>,
568 pub open_order_buy_qty: Option<i64>,
569 pub open_order_buy_cost: Option<i64>,
570 pub open_order_buy_premium: Option<i64>,
571 pub open_order_sell_qty: Option<i64>,
572 pub open_order_sell_cost: Option<i64>,
573 pub open_order_sell_premium: Option<i64>,
574 pub exec_buy_qty: Option<i64>,
575 pub exec_buy_cost: Option<i64>,
576 pub exec_sell_qty: Option<i64>,
577 pub exec_sell_cost: Option<i64>,
578 pub exec_qty: Option<i64>,
579 pub exec_cost: Option<i64>,
580 pub exec_comm: Option<i64>,
581 pub current_timestamp: Option<DateTime<Utc>>,
582 pub current_qty: Option<i64>,
583 pub current_cost: Option<i64>,
584 pub current_comm: Option<i64>,
585 pub realised_cost: Option<i64>,
586 pub unrealised_cost: Option<i64>,
587 pub gross_open_cost: Option<i64>,
588 pub gross_open_premium: Option<i64>,
589 pub gross_exec_cost: Option<i64>,
590 pub is_open: Option<bool>,
591 pub mark_price: Option<f64>,
592 pub mark_value: Option<i64>,
593 pub risk_value: Option<i64>,
594 pub home_notional: Option<f64>,
595 pub foreign_notional: Option<f64>,
596 pub pos_state: Option<Ustr>,
597 pub pos_cost: Option<i64>,
598 pub pos_cost2: Option<i64>,
599 pub pos_cross: Option<i64>,
600 pub pos_init: Option<i64>,
601 pub pos_comm: Option<i64>,
602 pub pos_loss: Option<i64>,
603 pub pos_margin: Option<i64>,
604 pub pos_maint: Option<i64>,
605 pub pos_allowance: Option<i64>,
606 pub taxable_margin: Option<i64>,
607 pub init_margin: Option<i64>,
608 pub maint_margin: Option<i64>,
609 pub session_margin: Option<i64>,
610 pub target_excess_margin: Option<i64>,
611 pub var_margin: Option<i64>,
612 pub realised_gross_pnl: Option<i64>,
613 pub realised_tax: Option<i64>,
614 pub realised_pnl: Option<i64>,
615 pub unrealised_gross_pnl: Option<i64>,
616 pub long_bankrupt: Option<i64>,
617 pub short_bankrupt: Option<i64>,
618 pub tax_base: Option<i64>,
619 pub indicative_tax_rate: Option<f64>,
620 pub indicative_tax: Option<i64>,
621 pub unrealised_tax: Option<i64>,
622 pub unrealised_pnl: Option<i64>,
623 pub unrealised_pnl_pcnt: Option<f64>,
624 pub unrealised_roe_pcnt: Option<f64>,
625 pub avg_cost_price: Option<f64>,
626 pub avg_entry_price: Option<f64>,
627 pub break_even_price: Option<f64>,
628 pub margin_call_price: Option<f64>,
629 pub liquidation_price: Option<f64>,
630 pub bankrupt_price: Option<f64>,
631 pub timestamp: Option<DateTime<Utc>>,
632 pub last_price: Option<f64>,
633 pub last_value: Option<i64>,
634}
635
636#[derive(Clone, Debug, Deserialize)]
637#[serde(rename_all = "camelCase")]
638pub struct BitmexWalletMsg {
639 pub account: i64,
640 pub currency: Ustr,
641 pub prev_deposited: Option<i64>,
642 pub prev_withdrawn: Option<i64>,
643 pub prev_transfer_in: Option<i64>,
644 pub prev_transfer_out: Option<i64>,
645 pub prev_amount: Option<i64>,
646 pub prev_timestamp: Option<DateTime<Utc>>,
647 pub delta_deposited: Option<i64>,
648 pub delta_withdrawn: Option<i64>,
649 pub delta_transfer_in: Option<i64>,
650 pub delta_transfer_out: Option<i64>,
651 pub delta_amount: Option<i64>,
652 pub deposited: Option<i64>,
653 pub withdrawn: Option<i64>,
654 pub transfer_in: Option<i64>,
655 pub transfer_out: Option<i64>,
656 pub amount: Option<i64>,
657 pub pending_credit: Option<i64>,
658 pub pending_debit: Option<i64>,
659 pub confirmed_debit: Option<i64>,
660 pub timestamp: Option<DateTime<Utc>>,
661 pub addr: Option<Ustr>,
662 pub script: Option<Ustr>,
663 pub withdrawal_lock: Option<Vec<Ustr>>,
664}
665
666#[derive(Clone, Debug, Deserialize)]
668#[serde(rename_all = "camelCase")]
669pub struct BitmexMarginMsg {
670 pub account: i64,
672 pub currency: Ustr,
674 pub risk_limit: Option<i64>,
676 pub amount: Option<i64>,
678 pub prev_realised_pnl: Option<i64>,
680 pub gross_comm: Option<i64>,
682 pub gross_open_cost: Option<i64>,
684 pub gross_open_premium: Option<i64>,
686 pub gross_exec_cost: Option<i64>,
688 pub gross_mark_value: Option<i64>,
690 pub risk_value: Option<i64>,
692 pub init_margin: Option<i64>,
694 pub maint_margin: Option<i64>,
696 pub target_excess_margin: Option<i64>,
698 pub realised_pnl: Option<i64>,
700 pub unrealised_pnl: Option<i64>,
702 pub wallet_balance: Option<i64>,
704 pub margin_balance: Option<i64>,
706 pub margin_leverage: Option<f64>,
708 pub margin_used_pcnt: Option<f64>,
710 pub excess_margin: Option<i64>,
712 pub available_margin: Option<i64>,
714 pub withdrawable_margin: Option<i64>,
716 pub maker_fee_discount: Option<f64>,
718 pub taker_fee_discount: Option<f64>,
720 pub timestamp: DateTime<Utc>,
722 pub foreign_margin_balance: Option<i64>,
724 pub foreign_requirement: Option<i64>,
726}
727
728#[derive(Clone, Debug, Deserialize)]
730#[serde(rename_all = "camelCase")]
731pub struct BitmexFundingMsg {
732 pub timestamp: DateTime<Utc>,
734 pub symbol: Ustr,
736 pub funding_rate: f64,
738 pub funding_rate_daily: f64,
740}
741
742#[derive(Clone, Debug, Deserialize)]
744#[serde(rename_all = "camelCase")]
745pub struct BitmexInsuranceMsg {
746 pub currency: Ustr,
748 pub timestamp: DateTime<Utc>,
750 pub wallet_balance: i64,
752}
753
754#[derive(Clone, Debug, Deserialize)]
756#[serde(rename_all = "camelCase")]
757pub struct BitmexLiquidationMsg {
758 pub order_id: Ustr,
760 pub symbol: Ustr,
762 pub side: BitmexSide,
764 pub price: f64,
766 pub leaves_qty: i64,
768}