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