1use nautilus_model::{
18 data::{Data, FundingRateUpdate, IndexPriceUpdate, MarkPriceUpdate, OrderBookDeltas},
19 events::{AccountState, OrderCancelRejected, OrderModifyRejected, OrderRejected},
20 reports::{FillReport, OrderStatusReport, PositionStatusReport},
21};
22use rust_decimal::Decimal;
23use serde::{Deserialize, Serialize};
24use serde_json::Value;
25use ustr::Ustr;
26
27use crate::{
28 common::{
29 enums::{
30 BybitCancelType, BybitCreateType, BybitExecType, BybitOrderSide, BybitOrderStatus,
31 BybitOrderType, BybitProductType, BybitStopOrderType, BybitTimeInForce, BybitTpSlMode,
32 BybitTriggerDirection, BybitTriggerType, BybitWsOrderRequestOp,
33 },
34 parse::{
35 deserialize_decimal_or_zero, deserialize_optional_decimal_or_zero,
36 deserialize_optional_decimal_str,
37 },
38 },
39 websocket::enums::BybitWsOperation,
40};
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct BybitSubscription {
45 pub op: BybitWsOperation,
46 pub args: Vec<String>,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct BybitAuthRequest {
52 pub op: BybitWsOperation,
53 pub args: Vec<serde_json::Value>,
54}
55
56#[derive(Debug, Clone)]
58pub enum BybitWsMessage {
59 Response(BybitWsResponse),
61 Auth(BybitWsAuthResponse),
63 Subscription(BybitWsSubscriptionMsg),
65 OrderResponse(BybitWsOrderResponse),
67 Orderbook(BybitWsOrderbookDepthMsg),
69 Trade(BybitWsTradeMsg),
71 Kline(BybitWsKlineMsg),
73 TickerLinear(BybitWsTickerLinearMsg),
75 TickerOption(BybitWsTickerOptionMsg),
77 AccountOrder(BybitWsAccountOrderMsg),
79 AccountExecution(BybitWsAccountExecutionMsg),
81 AccountWallet(BybitWsAccountWalletMsg),
83 AccountPosition(BybitWsAccountPositionMsg),
85 Error(BybitWebSocketError),
87 Raw(Value),
89 Reconnected,
91 Pong,
93}
94
95#[derive(Debug, Clone)]
100pub enum NautilusWsMessage {
101 Data(Vec<Data>),
103 Deltas(OrderBookDeltas),
105 MarkPrices(Vec<MarkPriceUpdate>),
107 IndexPrices(Vec<IndexPriceUpdate>),
109 FundingRates(Vec<FundingRateUpdate>),
111 OrderStatusReports(Vec<OrderStatusReport>),
113 FillReports(Vec<FillReport>),
115 PositionStatusReport(PositionStatusReport),
117 AccountState(AccountState),
119 OrderRejected(OrderRejected),
121 OrderCancelRejected(OrderCancelRejected),
123 OrderModifyRejected(OrderModifyRejected),
125 Error(BybitWebSocketError),
127 Reconnected,
129 Authenticated,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
135#[serde(rename_all = "camelCase")]
136#[cfg_attr(feature = "python", pyo3::pyclass(from_py_object))]
137pub struct BybitWebSocketError {
138 pub code: i64,
140 pub message: String,
142 #[serde(default)]
144 pub conn_id: Option<String>,
145 #[serde(default)]
147 pub topic: Option<String>,
148 #[serde(default)]
150 pub req_id: Option<String>,
151}
152
153impl BybitWebSocketError {
154 #[must_use]
156 pub fn new(code: i64, message: impl Into<String>) -> Self {
157 Self {
158 code,
159 message: message.into(),
160 conn_id: None,
161 topic: None,
162 req_id: None,
163 }
164 }
165
166 #[must_use]
168 pub fn from_response(response: &BybitWsResponse) -> Self {
169 let message = response.ret_msg.clone().unwrap_or_else(|| {
171 let mut parts = vec![];
172
173 if let Some(op) = &response.op {
174 parts.push(format!("op={op}"));
175 }
176 if let Some(topic) = &response.topic {
177 parts.push(format!("topic={topic}"));
178 }
179 if let Some(success) = response.success {
180 parts.push(format!("success={success}"));
181 }
182
183 if parts.is_empty() {
184 "Bybit websocket error (no error message provided)".to_string()
185 } else {
186 format!("Bybit websocket error: {}", parts.join(", "))
187 }
188 });
189
190 Self {
191 code: response.ret_code.unwrap_or_default(),
192 message,
193 conn_id: response.conn_id.clone(),
194 topic: response.topic.map(|t| t.to_string()),
195 req_id: response.req_id.clone(),
196 }
197 }
198
199 #[must_use]
201 pub fn from_message(message: impl Into<String>) -> Self {
202 Self::new(-1, message)
203 }
204}
205
206#[derive(Debug, Clone, Serialize)]
208#[serde(rename_all = "camelCase")]
209pub struct BybitWsRequest<T> {
210 #[serde(skip_serializing_if = "Option::is_none")]
212 pub req_id: Option<String>,
213 pub op: BybitWsOrderRequestOp,
215 pub header: BybitWsHeader,
217 pub args: Vec<T>,
219}
220
221#[derive(Debug, Clone, Serialize)]
223#[serde(rename_all = "SCREAMING-KEBAB-CASE")]
224pub struct BybitWsHeader {
225 pub x_bapi_timestamp: String,
227 #[serde(rename = "Referer", skip_serializing_if = "Option::is_none")]
229 pub referer: Option<String>,
230}
231
232impl BybitWsHeader {
233 #[must_use]
235 pub fn now() -> Self {
236 Self::with_referer(None)
237 }
238
239 #[must_use]
241 pub fn with_referer(referer: Option<String>) -> Self {
242 use nautilus_core::time::get_atomic_clock_realtime;
243 Self {
244 x_bapi_timestamp: get_atomic_clock_realtime().get_time_ms().to_string(),
245 referer,
246 }
247 }
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize)]
252#[serde(rename_all = "camelCase")]
253pub struct BybitWsPlaceOrderParams {
254 pub category: BybitProductType,
255 pub symbol: Ustr,
256 pub side: BybitOrderSide,
257 pub order_type: BybitOrderType,
258 pub qty: String,
259 #[serde(skip_serializing_if = "Option::is_none")]
260 pub is_leverage: Option<i32>,
261 #[serde(skip_serializing_if = "Option::is_none")]
262 pub market_unit: Option<String>,
263 #[serde(skip_serializing_if = "Option::is_none")]
264 pub price: Option<String>,
265 #[serde(skip_serializing_if = "Option::is_none")]
266 pub time_in_force: Option<BybitTimeInForce>,
267 #[serde(skip_serializing_if = "Option::is_none")]
268 pub order_link_id: Option<String>,
269 #[serde(skip_serializing_if = "Option::is_none")]
270 pub reduce_only: Option<bool>,
271 #[serde(skip_serializing_if = "Option::is_none")]
272 pub close_on_trigger: Option<bool>,
273 #[serde(skip_serializing_if = "Option::is_none")]
274 pub trigger_price: Option<String>,
275 #[serde(skip_serializing_if = "Option::is_none")]
276 pub trigger_by: Option<BybitTriggerType>,
277 #[serde(skip_serializing_if = "Option::is_none")]
278 pub trigger_direction: Option<i32>,
279 #[serde(skip_serializing_if = "Option::is_none")]
280 pub tpsl_mode: Option<String>,
281 #[serde(skip_serializing_if = "Option::is_none")]
282 pub take_profit: Option<String>,
283 #[serde(skip_serializing_if = "Option::is_none")]
284 pub stop_loss: Option<String>,
285 #[serde(skip_serializing_if = "Option::is_none")]
286 pub tp_trigger_by: Option<BybitTriggerType>,
287 #[serde(skip_serializing_if = "Option::is_none")]
288 pub sl_trigger_by: Option<BybitTriggerType>,
289 #[serde(skip_serializing_if = "Option::is_none")]
290 pub sl_trigger_price: Option<String>,
291 #[serde(skip_serializing_if = "Option::is_none")]
292 pub tp_trigger_price: Option<String>,
293 #[serde(skip_serializing_if = "Option::is_none")]
294 pub sl_order_type: Option<BybitOrderType>,
295 #[serde(skip_serializing_if = "Option::is_none")]
296 pub tp_order_type: Option<BybitOrderType>,
297 #[serde(skip_serializing_if = "Option::is_none")]
298 pub sl_limit_price: Option<String>,
299 #[serde(skip_serializing_if = "Option::is_none")]
300 pub tp_limit_price: Option<String>,
301}
302
303#[derive(Debug, Clone, Serialize, Deserialize)]
305#[serde(rename_all = "camelCase")]
306pub struct BybitWsAmendOrderParams {
307 pub category: BybitProductType,
308 pub symbol: Ustr,
309 #[serde(skip_serializing_if = "Option::is_none")]
310 pub order_id: Option<String>,
311 #[serde(skip_serializing_if = "Option::is_none")]
312 pub order_link_id: Option<String>,
313 #[serde(skip_serializing_if = "Option::is_none")]
314 pub qty: Option<String>,
315 #[serde(skip_serializing_if = "Option::is_none")]
316 pub price: Option<String>,
317 #[serde(skip_serializing_if = "Option::is_none")]
318 pub trigger_price: Option<String>,
319 #[serde(skip_serializing_if = "Option::is_none")]
320 pub take_profit: Option<String>,
321 #[serde(skip_serializing_if = "Option::is_none")]
322 pub stop_loss: Option<String>,
323 #[serde(skip_serializing_if = "Option::is_none")]
324 pub tp_trigger_by: Option<BybitTriggerType>,
325 #[serde(skip_serializing_if = "Option::is_none")]
326 pub sl_trigger_by: Option<BybitTriggerType>,
327}
328
329#[derive(Debug, Clone, Serialize, Deserialize)]
331#[serde(rename_all = "camelCase")]
332pub struct BybitWsCancelOrderParams {
333 pub category: BybitProductType,
334 pub symbol: Ustr,
335 #[serde(skip_serializing_if = "Option::is_none")]
336 pub order_id: Option<String>,
337 #[serde(skip_serializing_if = "Option::is_none")]
338 pub order_link_id: Option<String>,
339}
340
341#[derive(Debug, Clone, Serialize, Deserialize)]
343#[serde(rename_all = "camelCase")]
344pub struct BybitWsBatchCancelItem {
345 pub symbol: Ustr,
346 #[serde(skip_serializing_if = "Option::is_none")]
347 pub order_id: Option<String>,
348 #[serde(skip_serializing_if = "Option::is_none")]
349 pub order_link_id: Option<String>,
350}
351
352#[derive(Debug, Clone, Serialize, Deserialize)]
354pub struct BybitWsBatchCancelOrderArgs {
355 pub category: BybitProductType,
356 pub request: Vec<BybitWsBatchCancelItem>,
357}
358
359#[derive(Debug, Clone, Serialize, Deserialize)]
361#[serde(rename_all = "camelCase")]
362pub struct BybitWsBatchPlaceItem {
363 pub symbol: Ustr,
364 pub side: BybitOrderSide,
365 pub order_type: BybitOrderType,
366 pub qty: String,
367 #[serde(skip_serializing_if = "Option::is_none")]
368 pub is_leverage: Option<i32>,
369 #[serde(skip_serializing_if = "Option::is_none")]
370 pub market_unit: Option<String>,
371 #[serde(skip_serializing_if = "Option::is_none")]
372 pub price: Option<String>,
373 #[serde(skip_serializing_if = "Option::is_none")]
374 pub time_in_force: Option<BybitTimeInForce>,
375 #[serde(skip_serializing_if = "Option::is_none")]
376 pub order_link_id: Option<String>,
377 #[serde(skip_serializing_if = "Option::is_none")]
378 pub reduce_only: Option<bool>,
379 #[serde(skip_serializing_if = "Option::is_none")]
380 pub close_on_trigger: Option<bool>,
381 #[serde(skip_serializing_if = "Option::is_none")]
382 pub trigger_price: Option<String>,
383 #[serde(skip_serializing_if = "Option::is_none")]
384 pub trigger_by: Option<BybitTriggerType>,
385 #[serde(skip_serializing_if = "Option::is_none")]
386 pub trigger_direction: Option<i32>,
387 #[serde(skip_serializing_if = "Option::is_none")]
388 pub tpsl_mode: Option<String>,
389 #[serde(skip_serializing_if = "Option::is_none")]
390 pub take_profit: Option<String>,
391 #[serde(skip_serializing_if = "Option::is_none")]
392 pub stop_loss: Option<String>,
393 #[serde(skip_serializing_if = "Option::is_none")]
394 pub tp_trigger_by: Option<BybitTriggerType>,
395 #[serde(skip_serializing_if = "Option::is_none")]
396 pub sl_trigger_by: Option<BybitTriggerType>,
397 #[serde(skip_serializing_if = "Option::is_none")]
398 pub sl_trigger_price: Option<String>,
399 #[serde(skip_serializing_if = "Option::is_none")]
400 pub tp_trigger_price: Option<String>,
401 #[serde(skip_serializing_if = "Option::is_none")]
402 pub sl_order_type: Option<BybitOrderType>,
403 #[serde(skip_serializing_if = "Option::is_none")]
404 pub tp_order_type: Option<BybitOrderType>,
405 #[serde(skip_serializing_if = "Option::is_none")]
406 pub sl_limit_price: Option<String>,
407 #[serde(skip_serializing_if = "Option::is_none")]
408 pub tp_limit_price: Option<String>,
409}
410
411#[derive(Debug, Clone, Serialize, Deserialize)]
413pub struct BybitWsBatchPlaceOrderArgs {
414 pub category: BybitProductType,
415 pub request: Vec<BybitWsBatchPlaceItem>,
416}
417
418#[derive(Clone, Debug, Serialize, Deserialize)]
420pub struct BybitWsSubscriptionMsg {
421 pub success: bool,
422 pub op: BybitWsOperation,
423 #[serde(default)]
424 pub conn_id: Option<String>,
425 #[serde(default)]
426 pub req_id: Option<String>,
427 #[serde(default)]
428 pub ret_msg: Option<String>,
429}
430
431#[derive(Clone, Debug, Serialize, Deserialize)]
433pub struct BybitWsResponse {
434 #[serde(default)]
435 pub op: Option<BybitWsOperation>,
436 #[serde(default)]
437 pub topic: Option<Ustr>,
438 #[serde(default)]
439 pub success: Option<bool>,
440 #[serde(default)]
441 pub conn_id: Option<String>,
442 #[serde(default)]
443 pub req_id: Option<String>,
444 #[serde(default)]
445 pub ret_code: Option<i64>,
446 #[serde(default)]
447 pub ret_msg: Option<String>,
448}
449
450#[derive(Clone, Debug, Serialize, Deserialize)]
452#[serde(rename_all = "camelCase")]
453pub struct BybitWsOrderResponse {
454 pub op: Ustr,
456 #[serde(default)]
458 pub conn_id: Option<String>,
459 pub ret_code: i64,
461 pub ret_msg: String,
463 #[serde(default)]
465 pub data: Value,
466 #[serde(default)]
468 pub req_id: Option<String>,
469 #[serde(default)]
471 pub header: Option<Value>,
472 #[serde(default)]
474 pub ret_ext_info: Option<Value>,
475}
476
477impl BybitWsOrderResponse {
478 #[must_use]
483 pub fn extract_batch_errors(&self) -> Vec<BybitBatchOrderError> {
484 self.ret_ext_info
485 .as_ref()
486 .and_then(|ext| ext.get("list"))
487 .and_then(|list| list.as_array())
488 .map(|arr| {
489 arr.iter()
490 .filter_map(|item| {
491 let code = item.get("code")?.as_i64()?;
492 let msg = item.get("msg")?.as_str()?.to_string();
493 Some(BybitBatchOrderError { code, msg })
494 })
495 .collect()
496 })
497 .unwrap_or_default()
498 }
499}
500
501#[derive(Clone, Debug)]
503pub struct BybitBatchOrderError {
504 pub code: i64,
506 pub msg: String,
508}
509
510#[derive(Clone, Debug, Serialize, Deserialize)]
512#[serde(rename_all = "camelCase")]
513pub struct BybitWsAuthResponse {
514 pub op: BybitWsOperation,
515 #[serde(default)]
516 pub conn_id: Option<String>,
517 #[serde(default)]
518 pub ret_code: Option<i64>,
519 #[serde(default)]
520 pub ret_msg: Option<String>,
521 #[serde(default)]
522 pub success: Option<bool>,
523}
524
525#[derive(Clone, Debug, Serialize, Deserialize)]
527#[serde(rename_all = "camelCase")]
528pub struct BybitWsKline {
529 pub start: i64,
530 pub end: i64,
531 pub interval: Ustr,
532 pub open: String,
533 pub close: String,
534 pub high: String,
535 pub low: String,
536 pub volume: String,
537 pub turnover: String,
538 pub confirm: bool,
539 pub timestamp: i64,
540}
541
542#[derive(Clone, Debug, Serialize, Deserialize)]
544#[serde(rename_all = "camelCase")]
545pub struct BybitWsKlineMsg {
546 pub topic: Ustr,
547 pub ts: i64,
548 #[serde(rename = "type")]
549 pub msg_type: Ustr,
550 pub data: Vec<BybitWsKline>,
551}
552
553#[derive(Clone, Debug, Serialize, Deserialize)]
555pub struct BybitWsOrderbookDepth {
556 pub s: Ustr,
558 pub b: Vec<Vec<String>>,
560 pub a: Vec<Vec<String>>,
562 pub u: i64,
564 pub seq: i64,
566}
567
568#[derive(Clone, Debug, Serialize, Deserialize)]
570#[serde(rename_all = "camelCase")]
571pub struct BybitWsOrderbookDepthMsg {
572 pub topic: Ustr,
573 #[serde(rename = "type")]
574 pub msg_type: Ustr,
575 pub ts: i64,
576 pub data: BybitWsOrderbookDepth,
577 #[serde(default)]
578 pub cts: Option<i64>,
579}
580
581#[derive(Clone, Debug, Serialize, Deserialize)]
583#[serde(rename_all = "camelCase")]
584pub struct BybitWsTickerLinear {
585 pub symbol: Ustr,
586 #[serde(default)]
587 pub tick_direction: Option<String>,
588 #[serde(default)]
589 pub price24h_pcnt: Option<String>,
590 #[serde(default)]
591 pub last_price: Option<String>,
592 #[serde(default)]
593 pub prev_price24h: Option<String>,
594 #[serde(default)]
595 pub high_price24h: Option<String>,
596 #[serde(default)]
597 pub low_price24h: Option<String>,
598 #[serde(default)]
599 pub prev_price1h: Option<String>,
600 #[serde(default)]
601 pub mark_price: Option<String>,
602 #[serde(default)]
603 pub index_price: Option<String>,
604 #[serde(default)]
605 pub open_interest: Option<String>,
606 #[serde(default)]
607 pub open_interest_value: Option<String>,
608 #[serde(default)]
609 pub turnover24h: Option<String>,
610 #[serde(default)]
611 pub volume24h: Option<String>,
612 #[serde(default)]
613 pub next_funding_time: Option<String>,
614 #[serde(default)]
615 pub funding_rate: Option<String>,
616 #[serde(default)]
617 pub bid1_price: Option<String>,
618 #[serde(default)]
619 pub bid1_size: Option<String>,
620 #[serde(default)]
621 pub ask1_price: Option<String>,
622 #[serde(default)]
623 pub ask1_size: Option<String>,
624}
625
626#[derive(Clone, Debug, Serialize, Deserialize)]
628#[serde(rename_all = "camelCase")]
629pub struct BybitWsTickerLinearMsg {
630 pub topic: Ustr,
631 #[serde(rename = "type")]
632 pub msg_type: Ustr,
633 pub ts: i64,
634 #[serde(default)]
635 pub cs: Option<i64>,
636 pub data: BybitWsTickerLinear,
637}
638
639#[derive(Clone, Debug, Serialize, Deserialize)]
641#[serde(rename_all = "camelCase")]
642pub struct BybitWsTickerOption {
643 pub symbol: Ustr,
644 pub bid_price: String,
645 pub bid_size: String,
646 pub bid_iv: String,
647 pub ask_price: String,
648 pub ask_size: String,
649 pub ask_iv: String,
650 pub last_price: String,
651 pub high_price24h: String,
652 pub low_price24h: String,
653 pub mark_price: String,
654 pub index_price: String,
655 pub mark_price_iv: String,
656 pub underlying_price: String,
657 pub open_interest: String,
658 pub turnover24h: String,
659 pub volume24h: String,
660 pub total_volume: String,
661 pub total_turnover: String,
662 pub delta: String,
663 pub gamma: String,
664 pub vega: String,
665 pub theta: String,
666 pub predicted_delivery_price: String,
667 pub change24h: String,
668}
669
670#[derive(Clone, Debug, Serialize, Deserialize)]
672#[serde(rename_all = "camelCase")]
673pub struct BybitWsTickerOptionMsg {
674 #[serde(default)]
675 pub id: Option<String>,
676 pub topic: Ustr,
677 #[serde(rename = "type")]
678 pub msg_type: Ustr,
679 pub ts: i64,
680 pub data: BybitWsTickerOption,
681}
682
683#[derive(Clone, Debug, Serialize, Deserialize)]
685pub struct BybitWsTrade {
686 #[serde(rename = "T")]
687 pub t: i64,
688 #[serde(rename = "s")]
689 pub s: Ustr,
690 #[serde(rename = "S")]
691 pub taker_side: BybitOrderSide,
692 #[serde(rename = "v")]
693 pub v: String,
694 #[serde(rename = "p")]
695 pub p: String,
696 #[serde(rename = "i")]
697 pub i: String,
698 #[serde(rename = "BT")]
699 pub bt: bool,
700 #[serde(rename = "L")]
701 #[serde(default)]
702 pub l: Option<String>,
703 #[serde(rename = "id")]
704 #[serde(default)]
705 pub id: Option<Ustr>,
706 #[serde(rename = "mP")]
707 #[serde(default)]
708 pub m_p: Option<String>,
709 #[serde(rename = "iP")]
710 #[serde(default)]
711 pub i_p: Option<String>,
712 #[serde(rename = "mIv")]
713 #[serde(default)]
714 pub m_iv: Option<String>,
715 #[serde(rename = "iv")]
716 #[serde(default)]
717 pub iv: Option<String>,
718}
719
720#[derive(Clone, Debug, Serialize, Deserialize)]
722#[serde(rename_all = "camelCase")]
723pub struct BybitWsTradeMsg {
724 pub topic: Ustr,
725 #[serde(rename = "type")]
726 pub msg_type: Ustr,
727 pub ts: i64,
728 pub data: Vec<BybitWsTrade>,
729}
730
731#[derive(Clone, Debug, Serialize, Deserialize)]
733#[serde(rename_all = "camelCase")]
734pub struct BybitWsAccountOrder {
735 pub category: BybitProductType,
736 pub symbol: Ustr,
737 pub order_id: Ustr,
738 pub side: BybitOrderSide,
739 pub order_type: BybitOrderType,
740 pub cancel_type: BybitCancelType,
741 pub price: String,
742 pub qty: String,
743 pub order_iv: String,
744 pub time_in_force: BybitTimeInForce,
745 pub order_status: BybitOrderStatus,
746 pub order_link_id: Ustr,
747 pub last_price_on_created: Ustr,
748 pub reduce_only: bool,
749 pub leaves_qty: String,
750 pub leaves_value: String,
751 pub cum_exec_qty: String,
752 pub cum_exec_value: String,
753 pub avg_price: String,
754 pub block_trade_id: Ustr,
755 pub position_idx: i32,
756 pub cum_exec_fee: String,
757 pub created_time: String,
758 pub updated_time: String,
759 pub reject_reason: Ustr,
760 pub trigger_price: String,
761 pub take_profit: String,
762 pub stop_loss: String,
763 pub tp_trigger_by: BybitTriggerType,
764 pub sl_trigger_by: BybitTriggerType,
765 pub tp_limit_price: String,
766 pub sl_limit_price: String,
767 pub close_on_trigger: bool,
768 pub place_type: Ustr,
769 pub smp_type: Ustr,
770 pub smp_group: i32,
771 pub smp_order_id: Ustr,
772 pub fee_currency: Ustr,
773 pub trigger_by: BybitTriggerType,
774 pub stop_order_type: BybitStopOrderType,
775 pub trigger_direction: BybitTriggerDirection,
776 #[serde(default)]
777 pub tpsl_mode: Option<BybitTpSlMode>,
778 #[serde(default)]
779 pub create_type: Option<BybitCreateType>,
780}
781
782#[derive(Clone, Debug, Serialize, Deserialize)]
784#[serde(rename_all = "camelCase")]
785pub struct BybitWsAccountOrderMsg {
786 pub topic: Ustr,
787 pub id: String,
788 pub creation_time: i64,
789 pub data: Vec<BybitWsAccountOrder>,
790}
791
792#[derive(Clone, Debug, Serialize, Deserialize)]
794#[serde(rename_all = "camelCase")]
795pub struct BybitWsAccountExecution {
796 pub category: BybitProductType,
797 pub symbol: Ustr,
798 pub exec_fee: String,
799 pub exec_id: String,
800 pub exec_price: String,
801 pub exec_qty: String,
802 pub exec_type: BybitExecType,
803 pub exec_value: String,
804 pub is_maker: bool,
805 pub fee_rate: String,
806 pub trade_iv: String,
807 pub mark_iv: String,
808 pub block_trade_id: Ustr,
809 pub mark_price: String,
810 pub index_price: String,
811 pub underlying_price: String,
812 pub leaves_qty: String,
813 pub order_id: Ustr,
814 pub order_link_id: Ustr,
815 pub order_price: String,
816 pub order_qty: String,
817 pub order_type: BybitOrderType,
818 pub side: BybitOrderSide,
819 pub exec_time: String,
820 pub is_leverage: String,
821 pub closed_size: String,
822 pub seq: i64,
823 pub stop_order_type: BybitStopOrderType,
824}
825
826#[derive(Clone, Debug, Serialize, Deserialize)]
828#[serde(rename_all = "camelCase")]
829pub struct BybitWsAccountExecutionMsg {
830 pub topic: Ustr,
831 pub id: String,
832 pub creation_time: i64,
833 pub data: Vec<BybitWsAccountExecution>,
834}
835
836#[derive(Clone, Debug, Serialize, Deserialize)]
838#[serde(rename_all = "camelCase")]
839pub struct BybitWsAccountWalletCoin {
840 pub coin: Ustr,
841 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
842 pub wallet_balance: Decimal,
843 pub available_to_withdraw: String,
844 pub available_to_borrow: String,
845 pub accrued_interest: String,
846 #[serde(
847 default,
848 rename = "totalOrderIM",
849 deserialize_with = "deserialize_optional_decimal_or_zero"
850 )]
851 pub total_order_im: Decimal,
852 #[serde(
853 default,
854 rename = "totalPositionIM",
855 deserialize_with = "deserialize_optional_decimal_or_zero"
856 )]
857 pub total_position_im: Decimal,
858 #[serde(default, rename = "totalPositionMM")]
859 pub total_position_mm: Option<String>,
860 pub equity: String,
861 #[serde(default, deserialize_with = "deserialize_optional_decimal_or_zero")]
862 pub spot_borrow: Decimal,
863}
864
865#[derive(Clone, Debug, Serialize, Deserialize)]
867#[serde(rename_all = "camelCase")]
868pub struct BybitWsAccountWallet {
869 pub total_wallet_balance: String,
870 pub total_equity: String,
871 pub total_available_balance: String,
872 pub total_margin_balance: String,
873 pub total_initial_margin: String,
874 pub total_maintenance_margin: String,
875 #[serde(rename = "accountIMRate")]
876 pub account_im_rate: String,
877 #[serde(rename = "accountMMRate")]
878 pub account_mm_rate: String,
879 #[serde(rename = "accountLTV")]
880 pub account_ltv: String,
881 pub coin: Vec<BybitWsAccountWalletCoin>,
882}
883
884#[derive(Clone, Debug, Serialize, Deserialize)]
886#[serde(rename_all = "camelCase")]
887pub struct BybitWsAccountWalletMsg {
888 pub topic: Ustr,
889 pub id: String,
890 pub creation_time: i64,
891 pub data: Vec<BybitWsAccountWallet>,
892}
893
894#[derive(Clone, Debug, Serialize, Deserialize)]
896#[serde(rename_all = "camelCase")]
897pub struct BybitWsAccountPosition {
898 pub category: BybitProductType,
899 pub symbol: Ustr,
900 pub side: Ustr,
901 pub size: String,
902 pub position_idx: i32,
903 pub trade_mode: i32,
904 pub position_value: String,
905 pub risk_id: i64,
906 pub risk_limit_value: String,
907 #[serde(deserialize_with = "deserialize_optional_decimal_str")]
908 pub entry_price: Option<Decimal>,
909 pub mark_price: String,
910 pub leverage: String,
911 pub position_balance: String,
912 pub auto_add_margin: i32,
913 #[serde(rename = "positionIM")]
914 pub position_im: String,
915 #[serde(rename = "positionIMByMp")]
916 pub position_im_by_mp: String,
917 #[serde(rename = "positionMM")]
918 pub position_mm: String,
919 #[serde(rename = "positionMMByMp")]
920 pub position_mm_by_mp: String,
921 pub liq_price: String,
922 pub bust_price: String,
923 pub tpsl_mode: Ustr,
924 pub take_profit: String,
925 pub stop_loss: String,
926 pub trailing_stop: String,
927 pub unrealised_pnl: String,
928 pub session_avg_price: String,
929 pub cur_realised_pnl: String,
930 pub cum_realised_pnl: String,
931 pub position_status: Ustr,
932 pub adl_rank_indicator: i32,
933 pub created_time: String,
934 pub updated_time: String,
935 pub seq: i64,
936 pub is_reduce_only: bool,
937 pub mmr_sys_updated_time: String,
938 pub leverage_sys_updated_time: String,
939}
940
941#[derive(Clone, Debug, Serialize, Deserialize)]
943#[serde(rename_all = "camelCase")]
944pub struct BybitWsAccountPositionMsg {
945 pub topic: Ustr,
946 pub id: String,
947 pub creation_time: i64,
948 pub data: Vec<BybitWsAccountPosition>,
949}
950
951#[cfg(test)]
952mod tests {
953 use rstest::rstest;
954
955 use super::*;
956 use crate::common::testing::load_test_json;
957
958 #[rstest]
959 fn deserialize_account_order_frame_uses_enums() {
960 let json = load_test_json("ws_account_order.json");
961 let frame: BybitWsAccountOrderMsg = serde_json::from_str(&json).unwrap();
962 let order = &frame.data[0];
963
964 assert_eq!(order.cancel_type, BybitCancelType::CancelByUser);
965 assert_eq!(order.tp_trigger_by, BybitTriggerType::MarkPrice);
966 assert_eq!(order.sl_trigger_by, BybitTriggerType::LastPrice);
967 assert_eq!(order.tpsl_mode, Some(BybitTpSlMode::Full));
968 assert_eq!(order.create_type, Some(BybitCreateType::CreateByUser));
969 assert_eq!(order.side, BybitOrderSide::Buy);
970 }
971}