1use rust_decimal::Decimal;
19use serde::{Deserialize, Serialize};
20use ustr::Ustr;
21
22use crate::common::{
23 enums::{
24 BybitAccountType, BybitCancelType, BybitContractType, BybitExecType, BybitInnovationFlag,
25 BybitInstrumentStatus, BybitMarginTrading, BybitOptionType, BybitOrderSide,
26 BybitOrderStatus, BybitOrderType, BybitPositionIdx, BybitPositionSide, BybitProductType,
27 BybitStopOrderType, BybitTimeInForce, BybitTpSlMode, BybitTriggerDirection,
28 BybitTriggerType,
29 },
30 models::{
31 BybitCursorListResponse, BybitListResponse, BybitResponse, LeverageFilter,
32 LinearLotSizeFilter, LinearPriceFilter, OptionLotSizeFilter, SpotLotSizeFilter,
33 SpotPriceFilter,
34 },
35 parse::{
36 deserialize_decimal_or_zero, deserialize_optional_decimal_or_zero, deserialize_string_to_u8,
37 },
38};
39
40#[derive(Clone, Debug, Serialize, Deserialize)]
45#[serde(rename_all = "camelCase")]
46pub struct BybitServerTime {
47 pub time_second: String,
49 pub time_nano: String,
51}
52
53pub type BybitServerTimeResponse = BybitResponse<BybitServerTime>;
58
59#[derive(Clone, Debug, Serialize, Deserialize)]
64#[serde(rename_all = "camelCase")]
65pub struct BybitTickerSpot {
66 pub symbol: Ustr,
67 pub bid1_price: String,
68 pub bid1_size: String,
69 pub ask1_price: String,
70 pub ask1_size: String,
71 pub last_price: String,
72 pub prev_price24h: String,
73 pub price24h_pcnt: String,
74 pub high_price24h: String,
75 pub low_price24h: String,
76 pub turnover24h: String,
77 pub volume24h: String,
78 pub usd_index_price: String,
79}
80
81#[derive(Clone, Debug, Serialize, Deserialize)]
86#[serde(rename_all = "camelCase")]
87pub struct BybitTickerLinear {
88 pub symbol: Ustr,
89 pub last_price: String,
90 pub index_price: String,
91 pub mark_price: String,
92 pub prev_price24h: String,
93 pub price24h_pcnt: String,
94 pub high_price24h: String,
95 pub low_price24h: String,
96 pub prev_price1h: String,
97 pub open_interest: String,
98 pub open_interest_value: String,
99 pub turnover24h: String,
100 pub volume24h: String,
101 pub funding_rate: String,
102 pub next_funding_time: String,
103 pub predicted_delivery_price: String,
104 pub basis_rate: String,
105 pub delivery_fee_rate: String,
106 pub delivery_time: String,
107 pub ask1_size: String,
108 pub bid1_price: String,
109 pub ask1_price: String,
110 pub bid1_size: String,
111 pub basis: String,
112}
113
114#[derive(Clone, Debug, Serialize, Deserialize)]
119#[serde(rename_all = "camelCase")]
120pub struct BybitTickerOption {
121 pub symbol: Ustr,
122 pub bid1_price: String,
123 pub bid1_size: String,
124 pub bid1_iv: String,
125 pub ask1_price: String,
126 pub ask1_size: String,
127 pub ask1_iv: String,
128 pub last_price: String,
129 pub high_price24h: String,
130 pub low_price24h: String,
131 pub mark_price: String,
132 pub index_price: String,
133 pub mark_iv: String,
134 pub underlying_price: String,
135 pub open_interest: String,
136 pub turnover24h: String,
137 pub volume24h: String,
138 pub total_volume: String,
139 pub total_turnover: String,
140 pub delta: String,
141 pub gamma: String,
142 pub vega: String,
143 pub theta: String,
144 pub predicted_delivery_price: String,
145 pub change24h: String,
146}
147
148pub type BybitTickersSpotResponse = BybitListResponse<BybitTickerSpot>;
153pub type BybitTickersLinearResponse = BybitListResponse<BybitTickerLinear>;
158pub type BybitTickersOptionResponse = BybitListResponse<BybitTickerOption>;
163
164#[derive(Clone, Debug, Serialize, Deserialize)]
169#[serde(rename_all = "camelCase")]
170#[cfg_attr(
171 feature = "python",
172 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bybit")
173)]
174pub struct BybitTickerData {
175 pub symbol: Ustr,
176 pub bid1_price: String,
177 pub bid1_size: String,
178 pub ask1_price: String,
179 pub ask1_size: String,
180 pub last_price: String,
181 pub high_price24h: String,
182 pub low_price24h: String,
183 pub turnover24h: String,
184 pub volume24h: String,
185}
186
187#[cfg(feature = "python")]
188#[pyo3::pymethods]
189impl BybitTickerData {
190 #[getter]
191 #[must_use]
192 pub fn symbol(&self) -> &str {
193 self.symbol.as_str()
194 }
195
196 #[getter]
197 #[must_use]
198 pub fn bid1_price(&self) -> &str {
199 &self.bid1_price
200 }
201
202 #[getter]
203 #[must_use]
204 pub fn bid1_size(&self) -> &str {
205 &self.bid1_size
206 }
207
208 #[getter]
209 #[must_use]
210 pub fn ask1_price(&self) -> &str {
211 &self.ask1_price
212 }
213
214 #[getter]
215 #[must_use]
216 pub fn ask1_size(&self) -> &str {
217 &self.ask1_size
218 }
219
220 #[getter]
221 #[must_use]
222 pub fn last_price(&self) -> &str {
223 &self.last_price
224 }
225
226 #[getter]
227 #[must_use]
228 pub fn high_price24h(&self) -> &str {
229 &self.high_price24h
230 }
231
232 #[getter]
233 #[must_use]
234 pub fn low_price24h(&self) -> &str {
235 &self.low_price24h
236 }
237
238 #[getter]
239 #[must_use]
240 pub fn turnover24h(&self) -> &str {
241 &self.turnover24h
242 }
243
244 #[getter]
245 #[must_use]
246 pub fn volume24h(&self) -> &str {
247 &self.volume24h
248 }
249}
250
251#[derive(Clone, Debug, Serialize)]
259pub struct BybitKline {
260 pub start: String,
261 pub open: String,
262 pub high: String,
263 pub low: String,
264 pub close: String,
265 pub volume: String,
266 pub turnover: String,
267}
268
269impl<'de> Deserialize<'de> for BybitKline {
270 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
271 where
272 D: serde::Deserializer<'de>,
273 {
274 let arr: [String; 7] = Deserialize::deserialize(deserializer)?;
275 Ok(Self {
276 start: arr[0].clone(),
277 open: arr[1].clone(),
278 high: arr[2].clone(),
279 low: arr[3].clone(),
280 close: arr[4].clone(),
281 volume: arr[5].clone(),
282 turnover: arr[6].clone(),
283 })
284 }
285}
286
287#[derive(Clone, Debug, Serialize, Deserialize)]
292#[serde(rename_all = "camelCase")]
293pub struct BybitKlineResult {
294 pub category: BybitProductType,
295 pub symbol: Ustr,
296 pub list: Vec<BybitKline>,
297}
298
299pub type BybitKlinesResponse = BybitResponse<BybitKlineResult>;
304
305#[derive(Clone, Debug, Serialize, Deserialize)]
310#[serde(rename_all = "camelCase")]
311pub struct BybitTrade {
312 pub exec_id: String,
313 pub symbol: Ustr,
314 pub price: String,
315 pub size: String,
316 pub side: BybitOrderSide,
317 pub time: String,
318 pub is_block_trade: bool,
319 #[serde(default)]
320 pub m_p: Option<String>,
321 #[serde(default)]
322 pub i_p: Option<String>,
323 #[serde(default)]
324 pub mlv: Option<String>,
325 #[serde(default)]
326 pub iv: Option<String>,
327}
328
329#[derive(Clone, Debug, Serialize, Deserialize)]
334#[serde(rename_all = "camelCase")]
335pub struct BybitTradeResult {
336 pub category: BybitProductType,
337 pub list: Vec<BybitTrade>,
338}
339
340pub type BybitTradesResponse = BybitResponse<BybitTradeResult>;
345
346#[derive(Clone, Debug, Serialize, Deserialize)]
351#[serde(rename_all = "camelCase")]
352pub struct BybitInstrumentSpot {
353 pub symbol: Ustr,
354 pub base_coin: Ustr,
355 pub quote_coin: Ustr,
356 pub innovation: BybitInnovationFlag,
357 pub status: BybitInstrumentStatus,
358 pub margin_trading: BybitMarginTrading,
359 pub lot_size_filter: SpotLotSizeFilter,
360 pub price_filter: SpotPriceFilter,
361}
362
363#[derive(Clone, Debug, Serialize, Deserialize)]
368#[serde(rename_all = "camelCase")]
369pub struct BybitInstrumentLinear {
370 pub symbol: Ustr,
371 pub contract_type: BybitContractType,
372 pub status: BybitInstrumentStatus,
373 pub base_coin: Ustr,
374 pub quote_coin: Ustr,
375 pub launch_time: String,
376 pub delivery_time: String,
377 pub delivery_fee_rate: String,
378 pub price_scale: String,
379 pub leverage_filter: LeverageFilter,
380 pub price_filter: LinearPriceFilter,
381 pub lot_size_filter: LinearLotSizeFilter,
382 pub unified_margin_trade: bool,
383 pub funding_interval: i64,
384 pub settle_coin: Ustr,
385}
386
387#[derive(Clone, Debug, Serialize, Deserialize)]
392#[serde(rename_all = "camelCase")]
393pub struct BybitInstrumentInverse {
394 pub symbol: Ustr,
395 pub contract_type: BybitContractType,
396 pub status: BybitInstrumentStatus,
397 pub base_coin: Ustr,
398 pub quote_coin: Ustr,
399 pub launch_time: String,
400 pub delivery_time: String,
401 pub delivery_fee_rate: String,
402 pub price_scale: String,
403 pub leverage_filter: LeverageFilter,
404 pub price_filter: LinearPriceFilter,
405 pub lot_size_filter: LinearLotSizeFilter,
406 pub unified_margin_trade: bool,
407 pub funding_interval: i64,
408 pub settle_coin: Ustr,
409}
410
411#[derive(Clone, Debug, Serialize, Deserialize)]
416#[serde(rename_all = "camelCase")]
417pub struct BybitInstrumentOption {
418 pub symbol: Ustr,
419 pub status: BybitInstrumentStatus,
420 pub base_coin: Ustr,
421 pub quote_coin: Ustr,
422 pub settle_coin: Ustr,
423 pub options_type: BybitOptionType,
424 pub launch_time: String,
425 pub delivery_time: String,
426 pub delivery_fee_rate: String,
427 pub price_filter: LinearPriceFilter,
428 pub lot_size_filter: OptionLotSizeFilter,
429}
430
431pub type BybitInstrumentSpotResponse = BybitCursorListResponse<BybitInstrumentSpot>;
436pub type BybitInstrumentLinearResponse = BybitCursorListResponse<BybitInstrumentLinear>;
441pub type BybitInstrumentInverseResponse = BybitCursorListResponse<BybitInstrumentInverse>;
446pub type BybitInstrumentOptionResponse = BybitCursorListResponse<BybitInstrumentOption>;
451
452#[derive(Clone, Debug, Serialize, Deserialize)]
457#[serde(rename_all = "camelCase")]
458#[cfg_attr(
459 feature = "python",
460 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.adapters")
461)]
462pub struct BybitFeeRate {
463 pub symbol: Ustr,
464 pub taker_fee_rate: String,
465 pub maker_fee_rate: String,
466 #[serde(default)]
467 pub base_coin: Option<Ustr>,
468}
469
470#[cfg(feature = "python")]
471#[pyo3::pymethods]
472impl BybitFeeRate {
473 #[getter]
474 #[must_use]
475 pub fn symbol(&self) -> &str {
476 self.symbol.as_str()
477 }
478
479 #[getter]
480 #[must_use]
481 pub fn taker_fee_rate(&self) -> &str {
482 &self.taker_fee_rate
483 }
484
485 #[getter]
486 #[must_use]
487 pub fn maker_fee_rate(&self) -> &str {
488 &self.maker_fee_rate
489 }
490
491 #[getter]
492 #[must_use]
493 pub fn base_coin(&self) -> Option<&str> {
494 self.base_coin.as_ref().map(|u| u.as_str())
495 }
496}
497
498pub type BybitFeeRateResponse = BybitListResponse<BybitFeeRate>;
503
504#[derive(Clone, Debug, Serialize, Deserialize)]
509#[serde(rename_all = "camelCase")]
510pub struct BybitCoinBalance {
511 pub available_to_borrow: String,
512 pub bonus: String,
513 pub accrued_interest: String,
514 pub available_to_withdraw: String,
515 #[serde(default, rename = "totalOrderIM")]
516 pub total_order_im: Option<String>,
517 pub equity: String,
518 pub usd_value: String,
519 pub borrow_amount: String,
520 #[serde(default, rename = "totalPositionMM")]
521 pub total_position_mm: Option<String>,
522 #[serde(default, rename = "totalPositionIM")]
523 pub total_position_im: Option<String>,
524 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
525 pub wallet_balance: Decimal,
526 pub unrealised_pnl: String,
527 pub cum_realised_pnl: String,
528 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
529 pub locked: Decimal,
530 pub collateral_switch: bool,
531 pub margin_collateral: bool,
532 pub coin: Ustr,
533 #[serde(default)]
534 pub spot_hedging_qty: Option<String>,
535 #[serde(default, deserialize_with = "deserialize_optional_decimal_or_zero")]
536 pub spot_borrow: Decimal,
537}
538
539#[derive(Clone, Debug, Serialize, Deserialize)]
544#[serde(rename_all = "camelCase")]
545pub struct BybitWalletBalance {
546 pub total_equity: String,
547 #[serde(rename = "accountIMRate")]
548 pub account_im_rate: String,
549 pub total_margin_balance: String,
550 pub total_initial_margin: String,
551 pub account_type: BybitAccountType,
552 pub total_available_balance: String,
553 #[serde(rename = "accountMMRate")]
554 pub account_mm_rate: String,
555 #[serde(rename = "totalPerpUPL")]
556 pub total_perp_upl: String,
557 pub total_wallet_balance: String,
558 #[serde(rename = "accountLTV")]
559 pub account_ltv: String,
560 pub total_maintenance_margin: String,
561 pub coin: Vec<BybitCoinBalance>,
562}
563
564pub type BybitWalletBalanceResponse = BybitListResponse<BybitWalletBalance>;
569
570#[derive(Clone, Debug, Serialize, Deserialize)]
575#[serde(rename_all = "camelCase")]
576pub struct BybitOrder {
577 pub order_id: Ustr,
578 pub order_link_id: Ustr,
579 pub block_trade_id: Option<Ustr>,
580 pub symbol: Ustr,
581 pub price: String,
582 pub qty: String,
583 pub side: BybitOrderSide,
584 pub is_leverage: String,
585 pub position_idx: i32,
586 pub order_status: BybitOrderStatus,
587 pub cancel_type: BybitCancelType,
588 pub reject_reason: Ustr,
589 pub avg_price: Option<String>,
590 pub leaves_qty: String,
591 pub leaves_value: String,
592 pub cum_exec_qty: String,
593 pub cum_exec_value: String,
594 pub cum_exec_fee: String,
595 pub time_in_force: BybitTimeInForce,
596 pub order_type: BybitOrderType,
597 pub stop_order_type: BybitStopOrderType,
598 pub order_iv: Option<String>,
599 pub trigger_price: String,
600 pub take_profit: String,
601 pub stop_loss: String,
602 pub tp_trigger_by: BybitTriggerType,
603 pub sl_trigger_by: BybitTriggerType,
604 pub trigger_direction: BybitTriggerDirection,
605 pub trigger_by: BybitTriggerType,
606 pub last_price_on_created: String,
607 pub reduce_only: bool,
608 pub close_on_trigger: bool,
609 pub smp_type: Ustr,
610 pub smp_group: i32,
611 pub smp_order_id: Ustr,
612 pub tpsl_mode: Option<BybitTpSlMode>,
613 pub tp_limit_price: String,
614 pub sl_limit_price: String,
615 pub place_type: Ustr,
616 pub created_time: String,
617 pub updated_time: String,
618}
619
620pub type BybitOpenOrdersResponse = BybitCursorListResponse<BybitOrder>;
625pub type BybitOrderHistoryResponse = BybitCursorListResponse<BybitOrder>;
630
631#[derive(Clone, Debug, Serialize, Deserialize)]
636#[serde(rename_all = "camelCase")]
637pub struct BybitPlaceOrderResult {
638 pub order_id: Option<Ustr>,
639 pub order_link_id: Option<Ustr>,
640}
641
642pub type BybitPlaceOrderResponse = BybitResponse<BybitPlaceOrderResult>;
647
648#[derive(Clone, Debug, Serialize, Deserialize)]
653#[serde(rename_all = "camelCase")]
654pub struct BybitCancelOrderResult {
655 pub order_id: Option<Ustr>,
656 pub order_link_id: Option<Ustr>,
657}
658
659pub type BybitCancelOrderResponse = BybitResponse<BybitCancelOrderResult>;
664
665#[derive(Clone, Debug, Serialize, Deserialize)]
670#[serde(rename_all = "camelCase")]
671pub struct BybitExecution {
672 pub symbol: Ustr,
673 pub order_id: Ustr,
674 pub order_link_id: Ustr,
675 pub side: BybitOrderSide,
676 pub order_price: String,
677 pub order_qty: String,
678 pub leaves_qty: String,
679 pub create_type: Option<String>,
680 pub order_type: BybitOrderType,
681 pub stop_order_type: Option<BybitStopOrderType>,
682 pub exec_fee: String,
683 pub exec_id: String,
684 pub exec_price: String,
685 pub exec_qty: String,
686 pub exec_type: BybitExecType,
687 pub exec_value: String,
688 pub exec_time: String,
689 pub fee_currency: Ustr,
690 pub is_maker: bool,
691 pub fee_rate: String,
692 pub trade_iv: String,
693 pub mark_iv: String,
694 pub mark_price: String,
695 pub index_price: String,
696 pub underlying_price: String,
697 pub block_trade_id: String,
698 pub closed_size: String,
699 pub seq: i64,
700}
701
702pub type BybitTradeHistoryResponse = BybitCursorListResponse<BybitExecution>;
707
708#[derive(Clone, Debug, Serialize, Deserialize)]
713#[serde(rename_all = "camelCase")]
714pub struct BybitPosition {
715 pub position_idx: BybitPositionIdx,
716 pub risk_id: i32,
717 pub risk_limit_value: String,
718 pub symbol: Ustr,
719 pub side: BybitPositionSide,
720 pub size: String,
721 pub avg_price: String,
722 pub position_value: String,
723 pub trade_mode: i32,
724 pub position_status: String,
725 pub auto_add_margin: i32,
726 pub adl_rank_indicator: i32,
727 pub leverage: String,
728 pub position_balance: String,
729 pub mark_price: String,
730 pub liq_price: String,
731 pub bust_price: String,
732 #[serde(rename = "positionMM")]
733 pub position_mm: String,
734 #[serde(rename = "positionIM")]
735 pub position_im: String,
736 pub tpsl_mode: String,
737 pub take_profit: String,
738 pub stop_loss: String,
739 pub trailing_stop: String,
740 pub unrealised_pnl: String,
741 pub cur_realised_pnl: String,
742 pub cum_realised_pnl: String,
743 pub seq: i64,
744 pub is_reduce_only: bool,
745 pub mmr_sys_updated_time: String,
746 pub leverage_sys_updated_time: String,
747 pub created_time: String,
748 pub updated_time: String,
749}
750
751pub type BybitPositionListResponse = BybitCursorListResponse<BybitPosition>;
756
757#[derive(Clone, Debug, Serialize, Deserialize)]
762#[serde(rename_all = "camelCase")]
763pub struct BybitSetMarginModeReason {
764 pub reason_code: String,
765 pub reason_msg: String,
766}
767
768#[derive(Clone, Debug, Serialize, Deserialize)]
773#[serde(rename_all = "camelCase")]
774pub struct BybitSetMarginModeResult {
775 #[serde(default)]
776 pub reasons: Vec<BybitSetMarginModeReason>,
777}
778
779pub type BybitSetMarginModeResponse = BybitResponse<BybitSetMarginModeResult>;
784
785#[derive(Clone, Debug, Serialize, Deserialize)]
787pub struct BybitSetLeverageResult {}
788
789pub type BybitSetLeverageResponse = BybitResponse<BybitSetLeverageResult>;
794
795#[derive(Clone, Debug, Serialize, Deserialize)]
797pub struct BybitSwitchModeResult {}
798
799pub type BybitSwitchModeResponse = BybitResponse<BybitSwitchModeResult>;
804
805#[derive(Clone, Debug, Serialize, Deserialize)]
807pub struct BybitSetTradingStopResult {}
808
809pub type BybitSetTradingStopResponse = BybitResponse<BybitSetTradingStopResult>;
814
815#[derive(Clone, Debug, Serialize, Deserialize)]
817#[serde(rename_all = "camelCase")]
818pub struct BybitBorrowResult {
819 pub coin: String,
820 pub amount: String,
821}
822
823pub type BybitBorrowResponse = BybitResponse<BybitBorrowResult>;
829
830#[derive(Clone, Debug, Serialize, Deserialize)]
832#[serde(rename_all = "camelCase")]
833pub struct BybitNoConvertRepayResult {
834 pub result_status: String,
835}
836
837pub type BybitNoConvertRepayResponse = BybitResponse<BybitNoConvertRepayResult>;
843
844#[derive(Clone, Debug, Serialize, Deserialize)]
846#[cfg_attr(
847 feature = "python",
848 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.adapters")
849)]
850#[serde(rename_all = "PascalCase")]
851pub struct BybitApiKeyPermissions {
852 #[serde(default)]
853 pub contract_trade: Vec<String>,
854 #[serde(default)]
855 pub spot: Vec<String>,
856 #[serde(default)]
857 pub wallet: Vec<String>,
858 #[serde(default)]
859 pub options: Vec<String>,
860 #[serde(default)]
861 pub derivatives: Vec<String>,
862 #[serde(default)]
863 pub exchange: Vec<String>,
864 #[serde(default)]
865 pub copy_trading: Vec<String>,
866 #[serde(default)]
867 pub block_trade: Vec<String>,
868 #[serde(default)]
869 pub nft: Vec<String>,
870 #[serde(default)]
871 pub affiliate: Vec<String>,
872}
873
874#[derive(Clone, Debug, Serialize, Deserialize)]
876#[cfg_attr(
877 feature = "python",
878 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.adapters")
879)]
880#[serde(rename_all = "camelCase")]
881pub struct BybitAccountDetails {
882 pub id: String,
883 pub note: String,
884 pub api_key: String,
885 pub read_only: u8,
886 pub secret: String,
887 #[serde(rename = "type")]
888 pub key_type: u8,
889 pub permissions: BybitApiKeyPermissions,
890 pub ips: Vec<String>,
891 #[serde(default)]
892 pub user_id: Option<u64>,
893 #[serde(default)]
894 pub inviter_id: Option<u64>,
895 pub vip_level: String,
896 #[serde(deserialize_with = "deserialize_string_to_u8", default)]
897 pub mkt_maker_level: u8,
898 #[serde(default)]
899 pub affiliate_id: Option<u64>,
900 pub rsa_public_key: String,
901 pub is_master: bool,
902 pub parent_uid: String,
903 pub uta: u8,
904 pub kyc_level: String,
905 pub kyc_region: String,
906 #[serde(default)]
907 pub deadline_day: i64,
908 #[serde(default)]
909 pub expired_at: Option<String>,
910 pub created_at: String,
911}
912
913#[cfg(feature = "python")]
914#[pyo3::pymethods]
915impl BybitAccountDetails {
916 #[getter]
917 #[must_use]
918 pub fn id(&self) -> &str {
919 &self.id
920 }
921
922 #[getter]
923 #[must_use]
924 pub fn note(&self) -> &str {
925 &self.note
926 }
927
928 #[getter]
929 #[must_use]
930 pub fn api_key(&self) -> &str {
931 &self.api_key
932 }
933
934 #[getter]
935 #[must_use]
936 pub fn read_only(&self) -> u8 {
937 self.read_only
938 }
939
940 #[getter]
941 #[must_use]
942 pub fn key_type(&self) -> u8 {
943 self.key_type
944 }
945
946 #[getter]
947 #[must_use]
948 pub fn user_id(&self) -> Option<u64> {
949 self.user_id
950 }
951
952 #[getter]
953 #[must_use]
954 pub fn inviter_id(&self) -> Option<u64> {
955 self.inviter_id
956 }
957
958 #[getter]
959 #[must_use]
960 pub fn vip_level(&self) -> &str {
961 &self.vip_level
962 }
963
964 #[getter]
965 #[must_use]
966 pub fn mkt_maker_level(&self) -> u8 {
967 self.mkt_maker_level
968 }
969
970 #[getter]
971 #[must_use]
972 pub fn affiliate_id(&self) -> Option<u64> {
973 self.affiliate_id
974 }
975
976 #[getter]
977 #[must_use]
978 pub fn rsa_public_key(&self) -> &str {
979 &self.rsa_public_key
980 }
981
982 #[getter]
983 #[must_use]
984 pub fn is_master(&self) -> bool {
985 self.is_master
986 }
987
988 #[getter]
989 #[must_use]
990 pub fn parent_uid(&self) -> &str {
991 &self.parent_uid
992 }
993
994 #[getter]
995 #[must_use]
996 pub fn uta(&self) -> u8 {
997 self.uta
998 }
999
1000 #[getter]
1001 #[must_use]
1002 pub fn kyc_level(&self) -> &str {
1003 &self.kyc_level
1004 }
1005
1006 #[getter]
1007 #[must_use]
1008 pub fn kyc_region(&self) -> &str {
1009 &self.kyc_region
1010 }
1011
1012 #[getter]
1013 #[must_use]
1014 pub fn deadline_day(&self) -> i64 {
1015 self.deadline_day
1016 }
1017
1018 #[getter]
1019 #[must_use]
1020 pub fn expired_at(&self) -> Option<&str> {
1021 self.expired_at.as_deref()
1022 }
1023
1024 #[getter]
1025 #[must_use]
1026 pub fn created_at(&self) -> &str {
1027 &self.created_at
1028 }
1029}
1030
1031pub type BybitAccountDetailsResponse = BybitResponse<BybitAccountDetails>;
1037
1038#[cfg(test)]
1043mod tests {
1044 use nautilus_core::UnixNanos;
1045 use nautilus_model::identifiers::AccountId;
1046 use rstest::rstest;
1047 use rust_decimal::Decimal;
1048 use rust_decimal_macros::dec;
1049
1050 use super::*;
1051 use crate::common::testing::load_test_json;
1052
1053 #[rstest]
1054 fn deserialize_spot_instrument_uses_enums() {
1055 let json = load_test_json("http_get_instruments_spot.json");
1056 let response: BybitInstrumentSpotResponse = serde_json::from_str(&json).unwrap();
1057 let instrument = &response.result.list[0];
1058
1059 assert_eq!(instrument.status, BybitInstrumentStatus::Trading);
1060 assert_eq!(instrument.innovation, BybitInnovationFlag::Standard);
1061 assert_eq!(instrument.margin_trading, BybitMarginTrading::UtaOnly);
1062 }
1063
1064 #[rstest]
1065 fn deserialize_linear_instrument_status() {
1066 let json = load_test_json("http_get_instruments_linear.json");
1067 let response: BybitInstrumentLinearResponse = serde_json::from_str(&json).unwrap();
1068 let instrument = &response.result.list[0];
1069
1070 assert_eq!(instrument.status, BybitInstrumentStatus::Trading);
1071 assert_eq!(instrument.contract_type, BybitContractType::LinearPerpetual);
1072 }
1073
1074 #[rstest]
1075 fn deserialize_order_response_maps_enums() {
1076 let json = load_test_json("http_get_orders_history.json");
1077 let response: BybitOrderHistoryResponse = serde_json::from_str(&json).unwrap();
1078 let order = &response.result.list[0];
1079
1080 assert_eq!(order.cancel_type, BybitCancelType::CancelByUser);
1081 assert_eq!(order.tp_trigger_by, BybitTriggerType::MarkPrice);
1082 assert_eq!(order.sl_trigger_by, BybitTriggerType::LastPrice);
1083 assert_eq!(order.tpsl_mode, Some(BybitTpSlMode::Full));
1084 assert_eq!(order.order_type, BybitOrderType::Limit);
1085 }
1086
1087 #[rstest]
1088 fn deserialize_wallet_balance_without_optional_fields() {
1089 let json = r#"{
1090 "retCode": 0,
1091 "retMsg": "OK",
1092 "result": {
1093 "list": [{
1094 "totalEquity": "1000.00",
1095 "accountIMRate": "0",
1096 "totalMarginBalance": "1000.00",
1097 "totalInitialMargin": "0",
1098 "accountType": "UNIFIED",
1099 "totalAvailableBalance": "1000.00",
1100 "accountMMRate": "0",
1101 "totalPerpUPL": "0",
1102 "totalWalletBalance": "1000.00",
1103 "accountLTV": "0",
1104 "totalMaintenanceMargin": "0",
1105 "coin": [{
1106 "availableToBorrow": "0",
1107 "bonus": "0",
1108 "accruedInterest": "0",
1109 "availableToWithdraw": "1000.00",
1110 "equity": "1000.00",
1111 "usdValue": "1000.00",
1112 "borrowAmount": "0",
1113 "totalPositionIM": "0",
1114 "walletBalance": "1000.00",
1115 "unrealisedPnl": "0",
1116 "cumRealisedPnl": "0",
1117 "locked": "0",
1118 "collateralSwitch": true,
1119 "marginCollateral": true,
1120 "coin": "USDT"
1121 }]
1122 }]
1123 }
1124 }"#;
1125
1126 let response: BybitWalletBalanceResponse = serde_json::from_str(json)
1127 .expect("Failed to parse wallet balance without optional fields");
1128
1129 assert_eq!(response.ret_code, 0);
1130 assert_eq!(response.result.list[0].coin[0].total_order_im, None);
1131 assert_eq!(response.result.list[0].coin[0].total_position_mm, None);
1132 }
1133
1134 #[rstest]
1135 fn deserialize_wallet_balance_from_docs() {
1136 let json = include_str!("../../test_data/http_get_wallet_balance.json");
1137
1138 let response: BybitWalletBalanceResponse = serde_json::from_str(json)
1139 .expect("Failed to parse wallet balance from Bybit docs example");
1140
1141 assert_eq!(response.ret_code, 0);
1142 assert_eq!(response.ret_msg, "OK");
1143
1144 let wallet = &response.result.list[0];
1145 assert_eq!(wallet.total_equity, "3.31216591");
1146 assert_eq!(wallet.account_im_rate, "0");
1147 assert_eq!(wallet.account_mm_rate, "0");
1148 assert_eq!(wallet.total_perp_upl, "0");
1149 assert_eq!(wallet.account_ltv, "0");
1150
1151 let btc = &wallet.coin[0];
1153 assert_eq!(btc.coin.as_str(), "BTC");
1154 assert_eq!(btc.available_to_borrow, "3");
1155 assert_eq!(btc.total_order_im, Some("0".to_string()));
1156 assert_eq!(btc.total_position_mm, Some("0".to_string()));
1157 assert_eq!(btc.total_position_im, Some("0".to_string()));
1158
1159 let usdt = &wallet.coin[1];
1161 assert_eq!(usdt.coin.as_str(), "USDT");
1162 assert_eq!(usdt.wallet_balance, dec!(1000.50));
1163 assert_eq!(usdt.total_order_im, None);
1164 assert_eq!(usdt.total_position_mm, None);
1165 assert_eq!(usdt.total_position_im, None);
1166 assert_eq!(btc.spot_borrow, Decimal::ZERO);
1167 assert_eq!(usdt.spot_borrow, Decimal::ZERO);
1168 }
1169
1170 #[rstest]
1171 fn test_parse_wallet_balance_with_spot_borrow() {
1172 let json = include_str!("../../test_data/http_get_wallet_balance_with_spot_borrow.json");
1173 let response: BybitWalletBalanceResponse =
1174 serde_json::from_str(json).expect("Failed to parse wallet balance with spotBorrow");
1175
1176 let wallet = &response.result.list[0];
1177 let usdt = &wallet.coin[0];
1178
1179 assert_eq!(usdt.coin.as_str(), "USDT");
1180 assert_eq!(usdt.wallet_balance, dec!(1200.00));
1181 assert_eq!(usdt.spot_borrow, dec!(200.00));
1182 assert_eq!(usdt.borrow_amount, "200.00");
1183
1184 let account_id = crate::common::parse::parse_account_state(
1186 wallet,
1187 AccountId::new("BYBIT-001"),
1188 UnixNanos::default(),
1189 )
1190 .expect("Failed to parse account state");
1191
1192 let balance = &account_id.balances[0];
1193 assert_eq!(balance.total.as_f64(), 1000.0);
1194 }
1195
1196 #[rstest]
1197 fn test_parse_wallet_balance_spot_short() {
1198 let json = include_str!("../../test_data/http_get_wallet_balance_spot_short.json");
1199 let response: BybitWalletBalanceResponse = serde_json::from_str(json)
1200 .expect("Failed to parse wallet balance with SHORT SPOT position");
1201
1202 let wallet = &response.result.list[0];
1203 let eth = &wallet.coin[0];
1204
1205 assert_eq!(eth.coin.as_str(), "ETH");
1206 assert_eq!(eth.wallet_balance, dec!(0));
1207 assert_eq!(eth.spot_borrow, dec!(0.06142));
1208 assert_eq!(eth.borrow_amount, "0.06142");
1209
1210 let account_state = crate::common::parse::parse_account_state(
1211 wallet,
1212 AccountId::new("BYBIT-001"),
1213 UnixNanos::default(),
1214 )
1215 .expect("Failed to parse account state");
1216
1217 let eth_balance = account_state
1218 .balances
1219 .iter()
1220 .find(|b| b.currency.code.as_str() == "ETH")
1221 .expect("ETH balance not found");
1222
1223 assert_eq!(eth_balance.total.as_f64(), -0.06142);
1225 }
1226
1227 #[rstest]
1228 fn deserialize_borrow_response() {
1229 let json = r#"{
1230 "retCode": 0,
1231 "retMsg": "success",
1232 "result": {
1233 "coin": "BTC",
1234 "amount": "0.01"
1235 },
1236 "retExtInfo": {},
1237 "time": 1756197991955
1238 }"#;
1239
1240 let response: BybitBorrowResponse = serde_json::from_str(json).unwrap();
1241
1242 assert_eq!(response.ret_code, 0);
1243 assert_eq!(response.ret_msg, "success");
1244 assert_eq!(response.result.coin, "BTC");
1245 assert_eq!(response.result.amount, "0.01");
1246 }
1247
1248 #[rstest]
1249 fn deserialize_no_convert_repay_response() {
1250 let json = r#"{
1251 "retCode": 0,
1252 "retMsg": "OK",
1253 "result": {
1254 "resultStatus": "SU"
1255 },
1256 "retExtInfo": {},
1257 "time": 1234567890
1258 }"#;
1259
1260 let response: BybitNoConvertRepayResponse = serde_json::from_str(json).unwrap();
1261
1262 assert_eq!(response.ret_code, 0);
1263 assert_eq!(response.ret_msg, "OK");
1264 assert_eq!(response.result.result_status, "SU");
1265 }
1266}