1use rust_decimal::Decimal;
19use serde::{Deserialize, Serialize};
20use ustr::Ustr;
21
22use crate::common::{
23 enums::{
24 BybitAccountType, BybitApiKeyType, BybitCancelType, BybitContractType, BybitCreateType,
25 BybitExecType, BybitInnovationFlag, BybitInstrumentStatus, BybitMarginMode,
26 BybitMarginTrading, BybitOptionType, BybitOrderSide, BybitOrderStatus, BybitOrderType,
27 BybitPositionIdx, BybitPositionSide, BybitPositionStatus, BybitProductType,
28 BybitRepayStatus, BybitSmpType, BybitStopOrderType, BybitSymbolType, BybitTimeInForce,
29 BybitTpSlMode, BybitTriggerDirection, BybitTriggerType, BybitUnifiedMarginStatus,
30 },
31 models::{
32 BybitCursorList, BybitCursorListResponse, BybitListResponse, BybitResponse, LeverageFilter,
33 LinearLotSizeFilter, LinearPriceFilter, OptionLotSizeFilter, SpotLotSizeFilter,
34 SpotPriceFilter,
35 },
36 parse::{
37 bool_or_int, deserialize_decimal_or_zero, deserialize_i32_or_string,
38 deserialize_optional_decimal_or_zero, deserialize_string_to_u8, masked_secret, on_off_bool,
39 },
40};
41
42#[derive(Clone, Debug, Default, Serialize, Deserialize)]
44#[cfg_attr(
45 feature = "python",
46 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", from_py_object)
47)]
48#[cfg_attr(
49 feature = "python",
50 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.bybit")
51)]
52pub struct BybitOrderCursorList {
53 pub list: Vec<BybitOrder>,
55 pub next_page_cursor: Option<String>,
57 #[serde(default)]
59 pub category: Option<BybitProductType>,
60}
61
62impl From<BybitCursorList<BybitOrder>> for BybitOrderCursorList {
63 fn from(cursor_list: BybitCursorList<BybitOrder>) -> Self {
64 Self {
65 list: cursor_list.list,
66 next_page_cursor: cursor_list.next_page_cursor,
67 category: cursor_list.category,
68 }
69 }
70}
71
72#[cfg(feature = "python")]
73#[pyo3::pymethods]
74impl BybitOrderCursorList {
75 #[getter]
76 #[must_use]
77 pub fn list(&self) -> Vec<BybitOrder> {
78 self.list.clone()
79 }
80
81 #[getter]
82 #[must_use]
83 pub fn next_page_cursor(&self) -> Option<&str> {
84 self.next_page_cursor.as_deref()
85 }
86
87 #[getter]
88 #[must_use]
89 pub fn category(&self) -> Option<BybitProductType> {
90 self.category
91 }
92}
93
94#[derive(Clone, Debug, Serialize, Deserialize)]
99#[cfg_attr(
100 feature = "python",
101 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", from_py_object)
102)]
103#[cfg_attr(
104 feature = "python",
105 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.bybit")
106)]
107#[serde(rename_all = "camelCase")]
108pub struct BybitServerTime {
109 pub time_second: String,
111 pub time_nano: String,
113}
114
115#[cfg(feature = "python")]
116#[pyo3::pymethods]
117impl BybitServerTime {
118 #[getter]
119 #[must_use]
120 pub fn time_second(&self) -> &str {
121 &self.time_second
122 }
123
124 #[getter]
125 #[must_use]
126 pub fn time_nano(&self) -> &str {
127 &self.time_nano
128 }
129}
130
131pub type BybitServerTimeResponse = BybitResponse<BybitServerTime>;
136
137#[derive(Clone, Debug, Serialize, Deserialize)]
142#[serde(rename_all = "camelCase")]
143pub struct BybitTickerSpot {
144 pub symbol: Ustr,
145 pub bid1_price: String,
146 pub bid1_size: String,
147 pub ask1_price: String,
148 pub ask1_size: String,
149 pub last_price: String,
150 pub prev_price24h: String,
151 pub price24h_pcnt: String,
152 pub high_price24h: String,
153 pub low_price24h: String,
154 pub turnover24h: String,
155 pub volume24h: String,
156 #[serde(default)]
157 pub usd_index_price: String,
158}
159
160#[derive(Clone, Debug, Serialize, Deserialize)]
165#[serde(rename_all = "camelCase")]
166pub struct BybitTickerLinear {
167 pub symbol: Ustr,
168 pub last_price: String,
169 pub index_price: String,
170 pub mark_price: String,
171 pub prev_price24h: String,
172 pub price24h_pcnt: String,
173 pub high_price24h: String,
174 pub low_price24h: String,
175 pub prev_price1h: String,
176 pub open_interest: String,
177 pub open_interest_value: String,
178 pub turnover24h: String,
179 pub volume24h: String,
180 pub funding_rate: String,
181 pub next_funding_time: String,
182 pub predicted_delivery_price: String,
183 pub basis_rate: String,
184 pub delivery_fee_rate: String,
185 pub delivery_time: String,
186 pub ask1_size: String,
187 pub bid1_price: String,
188 pub ask1_price: String,
189 pub bid1_size: String,
190 pub basis: String,
191}
192
193#[derive(Clone, Debug, Serialize, Deserialize)]
198#[serde(rename_all = "camelCase")]
199pub struct BybitTickerOption {
200 pub symbol: Ustr,
201 pub bid1_price: String,
202 pub bid1_size: String,
203 pub bid1_iv: String,
204 pub ask1_price: String,
205 pub ask1_size: String,
206 pub ask1_iv: String,
207 pub last_price: String,
208 pub high_price24h: String,
209 pub low_price24h: String,
210 pub mark_price: String,
211 pub index_price: String,
212 pub mark_iv: String,
213 pub underlying_price: String,
214 pub open_interest: String,
215 pub turnover24h: String,
216 pub volume24h: String,
217 pub total_volume: String,
218 pub total_turnover: String,
219 pub delta: String,
220 pub gamma: String,
221 pub vega: String,
222 pub theta: String,
223 pub predicted_delivery_price: String,
224 pub change24h: String,
225}
226
227pub type BybitTickersSpotResponse = BybitListResponse<BybitTickerSpot>;
232pub type BybitTickersLinearResponse = BybitListResponse<BybitTickerLinear>;
237pub type BybitTickersOptionResponse = BybitListResponse<BybitTickerOption>;
242
243#[derive(Clone, Debug, Serialize, Deserialize)]
248#[serde(rename_all = "camelCase")]
249#[cfg_attr(
250 feature = "python",
251 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", from_py_object)
252)]
253#[cfg_attr(
254 feature = "python",
255 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.bybit")
256)]
257pub struct BybitTickerData {
258 pub symbol: Ustr,
259 pub bid1_price: String,
260 pub bid1_size: String,
261 pub ask1_price: String,
262 pub ask1_size: String,
263 pub last_price: String,
264 pub high_price24h: String,
265 pub low_price24h: String,
266 pub turnover24h: String,
267 pub volume24h: String,
268 #[serde(default)]
269 pub open_interest: Option<String>,
270 #[serde(default)]
271 pub funding_rate: Option<String>,
272 #[serde(default)]
273 pub next_funding_time: Option<String>,
274 #[serde(default)]
275 pub mark_price: Option<String>,
276 #[serde(default)]
277 pub index_price: Option<String>,
278}
279
280#[cfg(feature = "python")]
281#[pyo3::pymethods]
282impl BybitTickerData {
283 #[getter]
284 #[must_use]
285 pub fn symbol(&self) -> &str {
286 self.symbol.as_str()
287 }
288
289 #[getter]
290 #[must_use]
291 pub fn bid1_price(&self) -> &str {
292 &self.bid1_price
293 }
294
295 #[getter]
296 #[must_use]
297 pub fn bid1_size(&self) -> &str {
298 &self.bid1_size
299 }
300
301 #[getter]
302 #[must_use]
303 pub fn ask1_price(&self) -> &str {
304 &self.ask1_price
305 }
306
307 #[getter]
308 #[must_use]
309 pub fn ask1_size(&self) -> &str {
310 &self.ask1_size
311 }
312
313 #[getter]
314 #[must_use]
315 pub fn last_price(&self) -> &str {
316 &self.last_price
317 }
318
319 #[getter]
320 #[must_use]
321 pub fn high_price24h(&self) -> &str {
322 &self.high_price24h
323 }
324
325 #[getter]
326 #[must_use]
327 pub fn low_price24h(&self) -> &str {
328 &self.low_price24h
329 }
330
331 #[getter]
332 #[must_use]
333 pub fn turnover24h(&self) -> &str {
334 &self.turnover24h
335 }
336
337 #[getter]
338 #[must_use]
339 pub fn volume24h(&self) -> &str {
340 &self.volume24h
341 }
342
343 #[getter]
344 #[must_use]
345 pub fn open_interest(&self) -> Option<&str> {
346 self.open_interest.as_deref()
347 }
348
349 #[getter]
350 #[must_use]
351 pub fn funding_rate(&self) -> Option<&str> {
352 self.funding_rate.as_deref()
353 }
354
355 #[getter]
356 #[must_use]
357 pub fn next_funding_time(&self) -> Option<&str> {
358 self.next_funding_time.as_deref()
359 }
360
361 #[getter]
362 #[must_use]
363 pub fn mark_price(&self) -> Option<&str> {
364 self.mark_price.as_deref()
365 }
366
367 #[getter]
368 #[must_use]
369 pub fn index_price(&self) -> Option<&str> {
370 self.index_price.as_deref()
371 }
372}
373
374impl From<BybitTickerSpot> for BybitTickerData {
375 fn from(ticker: BybitTickerSpot) -> Self {
376 Self {
377 symbol: ticker.symbol,
378 bid1_price: ticker.bid1_price,
379 bid1_size: ticker.bid1_size,
380 ask1_price: ticker.ask1_price,
381 ask1_size: ticker.ask1_size,
382 last_price: ticker.last_price,
383 high_price24h: ticker.high_price24h,
384 low_price24h: ticker.low_price24h,
385 turnover24h: ticker.turnover24h,
386 volume24h: ticker.volume24h,
387 open_interest: None,
388 funding_rate: None,
389 next_funding_time: None,
390 mark_price: None,
391 index_price: None,
392 }
393 }
394}
395
396impl From<BybitTickerLinear> for BybitTickerData {
397 fn from(ticker: BybitTickerLinear) -> Self {
398 Self {
399 symbol: ticker.symbol,
400 bid1_price: ticker.bid1_price,
401 bid1_size: ticker.bid1_size,
402 ask1_price: ticker.ask1_price,
403 ask1_size: ticker.ask1_size,
404 last_price: ticker.last_price,
405 high_price24h: ticker.high_price24h,
406 low_price24h: ticker.low_price24h,
407 turnover24h: ticker.turnover24h,
408 volume24h: ticker.volume24h,
409 open_interest: Some(ticker.open_interest),
410 funding_rate: Some(ticker.funding_rate),
411 next_funding_time: Some(ticker.next_funding_time),
412 mark_price: Some(ticker.mark_price),
413 index_price: Some(ticker.index_price),
414 }
415 }
416}
417
418impl From<BybitTickerOption> for BybitTickerData {
419 fn from(ticker: BybitTickerOption) -> Self {
420 Self {
421 symbol: ticker.symbol,
422 bid1_price: ticker.bid1_price,
423 bid1_size: ticker.bid1_size,
424 ask1_price: ticker.ask1_price,
425 ask1_size: ticker.ask1_size,
426 last_price: ticker.last_price,
427 high_price24h: ticker.high_price24h,
428 low_price24h: ticker.low_price24h,
429 turnover24h: ticker.turnover24h,
430 volume24h: ticker.volume24h,
431 open_interest: Some(ticker.open_interest),
432 funding_rate: None,
433 next_funding_time: None,
434 mark_price: Some(ticker.mark_price),
435 index_price: Some(ticker.index_price),
436 }
437 }
438}
439
440#[derive(Clone, Debug, Serialize)]
448pub struct BybitKline {
449 pub start: String,
450 pub open: String,
451 pub high: String,
452 pub low: String,
453 pub close: String,
454 pub volume: String,
455 pub turnover: String,
456}
457
458impl<'de> Deserialize<'de> for BybitKline {
459 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
460 where
461 D: serde::Deserializer<'de>,
462 {
463 let [start, open, high, low, close, volume, turnover]: [String; 7] =
464 Deserialize::deserialize(deserializer)?;
465 Ok(Self {
466 start,
467 open,
468 high,
469 low,
470 close,
471 volume,
472 turnover,
473 })
474 }
475}
476
477#[derive(Clone, Debug, Serialize, Deserialize)]
482#[serde(rename_all = "camelCase")]
483pub struct BybitKlineResult {
484 pub category: BybitProductType,
485 pub symbol: Ustr,
486 pub list: Vec<BybitKline>,
487}
488
489pub type BybitKlinesResponse = BybitResponse<BybitKlineResult>;
494
495#[derive(Clone, Debug, Serialize, Deserialize)]
500#[serde(rename_all = "camelCase")]
501pub struct BybitTrade {
502 pub exec_id: String,
503 pub symbol: Ustr,
504 pub price: String,
505 pub size: String,
506 pub side: BybitOrderSide,
507 pub time: String,
508 pub is_block_trade: bool,
509 #[serde(default)]
510 pub m_p: Option<String>,
511 #[serde(default)]
512 pub i_p: Option<String>,
513 #[serde(default)]
514 pub mlv: Option<String>,
515 #[serde(default)]
516 pub iv: Option<String>,
517}
518
519#[derive(Clone, Debug, Serialize, Deserialize)]
524#[serde(rename_all = "camelCase")]
525pub struct BybitTradeResult {
526 pub category: BybitProductType,
527 pub list: Vec<BybitTrade>,
528}
529
530pub type BybitTradesResponse = BybitResponse<BybitTradeResult>;
535
536#[derive(Clone, Debug, Serialize, Deserialize)]
541#[serde(rename_all = "camelCase")]
542pub struct BybitFunding {
543 pub symbol: Ustr,
544 pub funding_rate: String,
545 pub funding_rate_timestamp: String,
546}
547
548#[derive(Clone, Debug, Serialize, Deserialize)]
553#[serde(rename_all = "camelCase")]
554pub struct BybitFundingResult {
555 pub category: BybitProductType,
556 pub list: Vec<BybitFunding>,
557}
558
559pub type BybitFundingResponse = BybitResponse<BybitFundingResult>;
564
565#[derive(Clone, Debug, Serialize, Deserialize)]
570#[serde(rename_all = "camelCase")]
571pub struct BybitOrderbookResult {
572 pub s: Ustr,
574 pub b: Vec<[String; 2]>,
576 pub a: Vec<[String; 2]>,
578 pub ts: i64,
579 pub u: i64,
581 pub seq: i64,
583 pub cts: i64,
584}
585
586pub type BybitOrderbookResponse = BybitResponse<BybitOrderbookResult>;
591
592#[derive(Clone, Debug, Serialize, Deserialize)]
597#[serde(rename_all = "camelCase")]
598pub struct BybitInstrumentSpot {
599 pub symbol: Ustr,
600 pub base_coin: Ustr,
601 pub quote_coin: Ustr,
602 pub innovation: BybitInnovationFlag,
603 pub status: BybitInstrumentStatus,
604 pub margin_trading: BybitMarginTrading,
605 pub lot_size_filter: SpotLotSizeFilter,
606 pub price_filter: SpotPriceFilter,
607 #[serde(default)]
608 pub symbol_id: Option<i64>,
609 #[serde(default)]
610 pub symbol_type: Option<BybitSymbolType>,
611 #[serde(default)]
612 pub xstock_multiplier: Option<String>,
613}
614
615#[derive(Clone, Debug, Serialize, Deserialize)]
620#[serde(rename_all = "camelCase")]
621pub struct BybitInstrumentLinear {
622 pub symbol: Ustr,
623 pub contract_type: BybitContractType,
624 pub status: BybitInstrumentStatus,
625 pub base_coin: Ustr,
626 pub quote_coin: Ustr,
627 pub launch_time: String,
628 pub delivery_time: String,
629 pub delivery_fee_rate: String,
630 pub price_scale: String,
631 pub leverage_filter: LeverageFilter,
632 pub price_filter: LinearPriceFilter,
633 pub lot_size_filter: LinearLotSizeFilter,
634 pub unified_margin_trade: bool,
635 pub funding_interval: i64,
636 pub settle_coin: Ustr,
637 #[serde(default)]
638 pub symbol_id: Option<i64>,
639 #[serde(default)]
640 pub symbol_type: Option<BybitSymbolType>,
641}
642
643#[derive(Clone, Debug, Serialize, Deserialize)]
648#[serde(rename_all = "camelCase")]
649pub struct BybitInstrumentInverse {
650 pub symbol: Ustr,
651 pub contract_type: BybitContractType,
652 pub status: BybitInstrumentStatus,
653 pub base_coin: Ustr,
654 pub quote_coin: Ustr,
655 pub launch_time: String,
656 pub delivery_time: String,
657 pub delivery_fee_rate: String,
658 pub price_scale: String,
659 pub leverage_filter: LeverageFilter,
660 pub price_filter: LinearPriceFilter,
661 pub lot_size_filter: LinearLotSizeFilter,
662 pub unified_margin_trade: bool,
663 pub funding_interval: i64,
664 pub settle_coin: Ustr,
665 #[serde(default)]
666 pub symbol_id: Option<i64>,
667 #[serde(default)]
668 pub symbol_type: Option<BybitSymbolType>,
669}
670
671#[derive(Clone, Debug, Serialize, Deserialize)]
676#[serde(rename_all = "camelCase")]
677pub struct BybitInstrumentOption {
678 pub symbol: Ustr,
679 pub status: BybitInstrumentStatus,
680 pub base_coin: Ustr,
681 pub quote_coin: Ustr,
682 pub settle_coin: Ustr,
683 pub options_type: BybitOptionType,
684 pub launch_time: String,
685 pub delivery_time: String,
686 pub delivery_fee_rate: String,
687 pub price_filter: LinearPriceFilter,
688 pub lot_size_filter: OptionLotSizeFilter,
689 #[serde(default)]
690 pub symbol_id: Option<i64>,
691}
692
693pub type BybitInstrumentSpotResponse = BybitCursorListResponse<BybitInstrumentSpot>;
698pub type BybitInstrumentLinearResponse = BybitCursorListResponse<BybitInstrumentLinear>;
703pub type BybitInstrumentInverseResponse = BybitCursorListResponse<BybitInstrumentInverse>;
708pub type BybitInstrumentOptionResponse = BybitCursorListResponse<BybitInstrumentOption>;
713
714#[derive(Clone, Debug, Serialize, Deserialize)]
719#[serde(rename_all = "camelCase")]
720#[cfg_attr(
721 feature = "python",
722 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", from_py_object)
723)]
724#[cfg_attr(
725 feature = "python",
726 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.bybit")
727)]
728pub struct BybitFeeRate {
729 pub symbol: Ustr,
730 pub taker_fee_rate: String,
731 pub maker_fee_rate: String,
732 #[serde(default)]
733 pub base_coin: Option<Ustr>,
734}
735
736#[cfg(feature = "python")]
737#[pyo3::pymethods]
738impl BybitFeeRate {
739 #[getter]
740 #[must_use]
741 pub fn symbol(&self) -> &str {
742 self.symbol.as_str()
743 }
744
745 #[getter]
746 #[must_use]
747 pub fn taker_fee_rate(&self) -> &str {
748 &self.taker_fee_rate
749 }
750
751 #[getter]
752 #[must_use]
753 pub fn maker_fee_rate(&self) -> &str {
754 &self.maker_fee_rate
755 }
756
757 #[getter]
758 #[must_use]
759 pub fn base_coin(&self) -> Option<&str> {
760 self.base_coin.as_ref().map(|u| u.as_str())
761 }
762}
763
764pub type BybitFeeRateResponse = BybitListResponse<BybitFeeRate>;
769
770#[derive(Clone, Debug, Serialize, Deserialize)]
775#[serde(rename_all = "camelCase")]
776pub struct BybitCoinBalance {
777 pub available_to_borrow: String,
778 pub bonus: String,
779 pub accrued_interest: String,
780 pub available_to_withdraw: String,
781 #[serde(default, rename = "totalOrderIM")]
782 pub total_order_im: Option<String>,
783 pub equity: String,
784 pub usd_value: String,
785 pub borrow_amount: String,
786 #[serde(default, rename = "totalPositionMM")]
787 pub total_position_mm: Option<String>,
788 #[serde(default, rename = "totalPositionIM")]
789 pub total_position_im: Option<String>,
790 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
791 pub wallet_balance: Decimal,
792 pub unrealised_pnl: String,
793 pub cum_realised_pnl: String,
794 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
795 pub locked: Decimal,
796 pub collateral_switch: bool,
797 pub margin_collateral: bool,
798 pub coin: Ustr,
799 #[serde(default)]
800 pub spot_hedging_qty: Option<String>,
801 #[serde(default, deserialize_with = "deserialize_optional_decimal_or_zero")]
802 pub spot_borrow: Decimal,
803}
804
805#[derive(Clone, Debug, Serialize, Deserialize)]
810#[serde(rename_all = "camelCase")]
811pub struct BybitWalletBalance {
812 pub total_equity: String,
813 #[serde(rename = "accountIMRate")]
814 pub account_im_rate: String,
815 pub total_margin_balance: String,
816 pub total_initial_margin: String,
817 pub account_type: BybitAccountType,
818 pub total_available_balance: String,
819 #[serde(rename = "accountMMRate")]
820 pub account_mm_rate: String,
821 #[serde(rename = "totalPerpUPL")]
822 pub total_perp_upl: String,
823 pub total_wallet_balance: String,
824 #[serde(rename = "accountLTV")]
825 pub account_ltv: String,
826 pub total_maintenance_margin: String,
827 pub coin: Vec<BybitCoinBalance>,
828}
829
830pub type BybitWalletBalanceResponse = BybitListResponse<BybitWalletBalance>;
835
836#[derive(Clone, Debug, Serialize, Deserialize)]
841#[serde(rename_all = "camelCase")]
842pub struct BybitAccountInfo {
843 pub unified_margin_status: BybitUnifiedMarginStatus,
844 pub margin_mode: BybitMarginMode,
845 pub is_master_trader: bool,
846 #[serde(with = "on_off_bool")]
847 pub spot_hedging_status: bool,
848 pub updated_time: String,
849 #[serde(default, with = "on_off_bool")]
852 pub dcp_status: bool,
853 #[serde(default, deserialize_with = "deserialize_i32_or_string")]
854 pub time_window: i32,
855 #[serde(default, deserialize_with = "deserialize_i32_or_string")]
856 pub smp_group: i32,
857}
858
859pub type BybitAccountInfoResponse = BybitResponse<BybitAccountInfo>;
864
865#[derive(Clone, Debug, Serialize, Deserialize)]
870#[cfg_attr(
871 feature = "python",
872 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", from_py_object)
873)]
874#[cfg_attr(
875 feature = "python",
876 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.bybit")
877)]
878#[serde(rename_all = "camelCase")]
879pub struct BybitOrder {
880 pub order_id: Ustr,
881 pub order_link_id: Ustr,
882 pub block_trade_id: Option<Ustr>,
883 pub symbol: Ustr,
884 pub price: String,
885 pub qty: String,
886 pub side: BybitOrderSide,
887 pub is_leverage: String,
888 #[serde(deserialize_with = "deserialize_i32_or_string")]
889 pub position_idx: i32,
890 pub order_status: BybitOrderStatus,
891 pub cancel_type: BybitCancelType,
892 pub reject_reason: Ustr,
893 pub avg_price: Option<String>,
894 pub leaves_qty: String,
895 pub leaves_value: String,
896 pub cum_exec_qty: String,
897 pub cum_exec_value: String,
898 pub cum_exec_fee: String,
899 pub time_in_force: BybitTimeInForce,
900 pub order_type: BybitOrderType,
901 pub stop_order_type: BybitStopOrderType,
902 pub order_iv: Option<String>,
903 pub trigger_price: String,
904 pub take_profit: String,
905 pub stop_loss: String,
906 pub tp_trigger_by: BybitTriggerType,
907 pub sl_trigger_by: BybitTriggerType,
908 pub trigger_direction: BybitTriggerDirection,
909 pub trigger_by: BybitTriggerType,
910 pub last_price_on_created: String,
911 pub reduce_only: bool,
912 pub close_on_trigger: bool,
913 pub smp_type: BybitSmpType,
914 #[serde(deserialize_with = "deserialize_i32_or_string")]
915 pub smp_group: i32,
916 pub smp_order_id: Ustr,
917 pub tpsl_mode: Option<BybitTpSlMode>,
918 pub tp_limit_price: String,
919 pub sl_limit_price: String,
920 pub place_type: Ustr,
921 pub created_time: String,
922 pub updated_time: String,
923}
924
925#[cfg(feature = "python")]
926#[pyo3::pymethods]
927impl BybitOrder {
928 #[getter]
929 #[must_use]
930 pub fn order_id(&self) -> &str {
931 self.order_id.as_str()
932 }
933
934 #[getter]
935 #[must_use]
936 pub fn order_link_id(&self) -> &str {
937 self.order_link_id.as_str()
938 }
939
940 #[getter]
941 #[must_use]
942 pub fn block_trade_id(&self) -> Option<&str> {
943 self.block_trade_id.as_ref().map(|s| s.as_str())
944 }
945
946 #[getter]
947 #[must_use]
948 pub fn symbol(&self) -> &str {
949 self.symbol.as_str()
950 }
951
952 #[getter]
953 #[must_use]
954 pub fn price(&self) -> &str {
955 &self.price
956 }
957
958 #[getter]
959 #[must_use]
960 pub fn qty(&self) -> &str {
961 &self.qty
962 }
963
964 #[getter]
965 #[must_use]
966 pub fn side(&self) -> BybitOrderSide {
967 self.side
968 }
969
970 #[getter]
971 #[must_use]
972 pub fn is_leverage(&self) -> &str {
973 &self.is_leverage
974 }
975
976 #[getter]
977 #[must_use]
978 pub fn position_idx(&self) -> i32 {
979 self.position_idx
980 }
981
982 #[getter]
983 #[must_use]
984 pub fn order_status(&self) -> BybitOrderStatus {
985 self.order_status
986 }
987
988 #[getter]
989 #[must_use]
990 pub fn cancel_type(&self) -> BybitCancelType {
991 self.cancel_type
992 }
993
994 #[getter]
995 #[must_use]
996 pub fn reject_reason(&self) -> &str {
997 self.reject_reason.as_str()
998 }
999
1000 #[getter]
1001 #[must_use]
1002 pub fn avg_price(&self) -> Option<&str> {
1003 self.avg_price.as_deref()
1004 }
1005
1006 #[getter]
1007 #[must_use]
1008 pub fn leaves_qty(&self) -> &str {
1009 &self.leaves_qty
1010 }
1011
1012 #[getter]
1013 #[must_use]
1014 pub fn leaves_value(&self) -> &str {
1015 &self.leaves_value
1016 }
1017
1018 #[getter]
1019 #[must_use]
1020 pub fn cum_exec_qty(&self) -> &str {
1021 &self.cum_exec_qty
1022 }
1023
1024 #[getter]
1025 #[must_use]
1026 pub fn cum_exec_value(&self) -> &str {
1027 &self.cum_exec_value
1028 }
1029
1030 #[getter]
1031 #[must_use]
1032 pub fn cum_exec_fee(&self) -> &str {
1033 &self.cum_exec_fee
1034 }
1035
1036 #[getter]
1037 #[must_use]
1038 pub fn time_in_force(&self) -> BybitTimeInForce {
1039 self.time_in_force
1040 }
1041
1042 #[getter]
1043 #[must_use]
1044 pub fn order_type(&self) -> BybitOrderType {
1045 self.order_type
1046 }
1047
1048 #[getter]
1049 #[must_use]
1050 pub fn stop_order_type(&self) -> BybitStopOrderType {
1051 self.stop_order_type
1052 }
1053
1054 #[getter]
1055 #[must_use]
1056 pub fn order_iv(&self) -> Option<&str> {
1057 self.order_iv.as_deref()
1058 }
1059
1060 #[getter]
1061 #[must_use]
1062 pub fn trigger_price(&self) -> &str {
1063 &self.trigger_price
1064 }
1065
1066 #[getter]
1067 #[must_use]
1068 pub fn take_profit(&self) -> &str {
1069 &self.take_profit
1070 }
1071
1072 #[getter]
1073 #[must_use]
1074 pub fn stop_loss(&self) -> &str {
1075 &self.stop_loss
1076 }
1077
1078 #[getter]
1079 #[must_use]
1080 pub fn tp_trigger_by(&self) -> BybitTriggerType {
1081 self.tp_trigger_by
1082 }
1083
1084 #[getter]
1085 #[must_use]
1086 pub fn sl_trigger_by(&self) -> BybitTriggerType {
1087 self.sl_trigger_by
1088 }
1089
1090 #[getter]
1091 #[must_use]
1092 pub fn trigger_direction(&self) -> BybitTriggerDirection {
1093 self.trigger_direction
1094 }
1095
1096 #[getter]
1097 #[must_use]
1098 pub fn trigger_by(&self) -> BybitTriggerType {
1099 self.trigger_by
1100 }
1101
1102 #[getter]
1103 #[must_use]
1104 pub fn last_price_on_created(&self) -> &str {
1105 &self.last_price_on_created
1106 }
1107
1108 #[getter]
1109 #[must_use]
1110 pub fn reduce_only(&self) -> bool {
1111 self.reduce_only
1112 }
1113
1114 #[getter]
1115 #[must_use]
1116 pub fn close_on_trigger(&self) -> bool {
1117 self.close_on_trigger
1118 }
1119
1120 #[getter]
1121 #[must_use]
1122 #[expect(
1123 clippy::missing_panics_doc,
1124 reason = "serialization of a simple enum cannot fail"
1125 )]
1126 pub fn smp_type(&self) -> String {
1127 serde_json::to_string(&self.smp_type)
1128 .expect("Failed to serialize BybitSmpType")
1129 .trim_matches('"')
1130 .to_string()
1131 }
1132
1133 #[getter]
1134 #[must_use]
1135 pub fn smp_group(&self) -> i32 {
1136 self.smp_group
1137 }
1138
1139 #[getter]
1140 #[must_use]
1141 pub fn smp_order_id(&self) -> &str {
1142 self.smp_order_id.as_str()
1143 }
1144
1145 #[getter]
1146 #[must_use]
1147 pub fn tpsl_mode(&self) -> Option<BybitTpSlMode> {
1148 self.tpsl_mode
1149 }
1150
1151 #[getter]
1152 #[must_use]
1153 pub fn tp_limit_price(&self) -> &str {
1154 &self.tp_limit_price
1155 }
1156
1157 #[getter]
1158 #[must_use]
1159 pub fn sl_limit_price(&self) -> &str {
1160 &self.sl_limit_price
1161 }
1162
1163 #[getter]
1164 #[must_use]
1165 pub fn place_type(&self) -> &str {
1166 self.place_type.as_str()
1167 }
1168
1169 #[getter]
1170 #[must_use]
1171 pub fn created_time(&self) -> &str {
1172 &self.created_time
1173 }
1174
1175 #[getter]
1176 #[must_use]
1177 pub fn updated_time(&self) -> &str {
1178 &self.updated_time
1179 }
1180}
1181
1182pub type BybitOpenOrdersResponse = BybitCursorListResponse<BybitOrder>;
1187pub type BybitOrderHistoryResponse = BybitCursorListResponse<BybitOrder>;
1192
1193#[derive(Clone, Debug, Serialize, Deserialize)]
1198#[serde(rename_all = "camelCase")]
1199pub struct BybitPlaceOrderResult {
1200 pub order_id: Option<Ustr>,
1201 pub order_link_id: Option<Ustr>,
1202}
1203
1204pub type BybitPlaceOrderResponse = BybitResponse<BybitPlaceOrderResult>;
1209
1210#[derive(Clone, Debug, Serialize, Deserialize)]
1215#[serde(rename_all = "camelCase")]
1216pub struct BybitCancelOrderResult {
1217 pub order_id: Option<Ustr>,
1218 pub order_link_id: Option<Ustr>,
1219}
1220
1221pub type BybitCancelOrderResponse = BybitResponse<BybitCancelOrderResult>;
1226
1227#[derive(Clone, Debug, Serialize, Deserialize)]
1232#[serde(rename_all = "camelCase")]
1233pub struct BybitExecution {
1234 pub symbol: Ustr,
1235 pub order_id: Ustr,
1236 pub order_link_id: Ustr,
1237 pub side: BybitOrderSide,
1238 pub order_price: String,
1239 pub order_qty: String,
1240 pub leaves_qty: String,
1241 pub create_type: Option<BybitCreateType>,
1242 pub order_type: BybitOrderType,
1243 pub stop_order_type: Option<BybitStopOrderType>,
1244 pub exec_fee: String,
1245 pub exec_id: String,
1246 pub exec_price: String,
1247 pub exec_qty: String,
1248 pub exec_type: BybitExecType,
1249 pub exec_value: String,
1250 pub exec_time: String,
1251 pub fee_currency: Ustr,
1252 pub is_maker: bool,
1253 pub fee_rate: String,
1254 pub trade_iv: String,
1255 pub mark_iv: String,
1256 pub mark_price: String,
1257 pub index_price: String,
1258 pub underlying_price: String,
1259 pub block_trade_id: String,
1260 pub closed_size: String,
1261 pub seq: i64,
1262}
1263
1264pub type BybitTradeHistoryResponse = BybitCursorListResponse<BybitExecution>;
1269
1270#[derive(Clone, Debug, Serialize, Deserialize)]
1275#[serde(rename_all = "camelCase")]
1276pub struct BybitPosition {
1277 pub position_idx: BybitPositionIdx,
1278 #[serde(deserialize_with = "deserialize_i32_or_string")]
1279 pub risk_id: i32,
1280 pub risk_limit_value: String,
1281 pub symbol: Ustr,
1282 pub side: BybitPositionSide,
1283 pub size: String,
1284 pub avg_price: String,
1285 pub position_value: String,
1286 #[serde(deserialize_with = "deserialize_i32_or_string")]
1287 pub trade_mode: i32,
1288 pub position_status: BybitPositionStatus,
1289 #[serde(deserialize_with = "deserialize_i32_or_string")]
1290 pub auto_add_margin: i32,
1291 #[serde(deserialize_with = "deserialize_i32_or_string")]
1292 pub adl_rank_indicator: i32,
1293 pub leverage: String,
1294 pub position_balance: String,
1295 pub mark_price: String,
1296 pub liq_price: String,
1297 pub bust_price: String,
1298 #[serde(rename = "positionMM")]
1299 pub position_mm: String,
1300 #[serde(rename = "positionIM")]
1301 pub position_im: String,
1302 pub tpsl_mode: BybitTpSlMode,
1303 pub take_profit: String,
1304 pub stop_loss: String,
1305 pub trailing_stop: String,
1306 pub unrealised_pnl: String,
1307 pub cur_realised_pnl: String,
1308 pub cum_realised_pnl: String,
1309 #[serde(default = "default_position_seq")]
1310 pub seq: i64,
1311 #[serde(default)]
1312 pub is_reduce_only: bool,
1313 #[serde(default)]
1314 pub mmr_sys_updated_time: String,
1315 #[serde(default)]
1316 pub leverage_sys_updated_time: String,
1317 pub created_time: String,
1318 pub updated_time: String,
1319 #[serde(default)]
1320 pub open_time: i64,
1321}
1322
1323const fn default_position_seq() -> i64 {
1324 -1
1325}
1326
1327pub type BybitPositionListResponse = BybitCursorListResponse<BybitPosition>;
1332
1333#[derive(Clone, Debug, Serialize, Deserialize)]
1338#[serde(rename_all = "camelCase")]
1339pub struct BybitSetMarginModeReason {
1340 pub reason_code: String,
1341 pub reason_msg: String,
1342}
1343
1344#[derive(Clone, Debug, Serialize, Deserialize)]
1349#[serde(rename_all = "camelCase")]
1350pub struct BybitSetMarginModeResult {
1351 #[serde(default)]
1352 pub reasons: Vec<BybitSetMarginModeReason>,
1353}
1354
1355pub type BybitSetMarginModeResponse = BybitResponse<BybitSetMarginModeResult>;
1360
1361#[derive(Clone, Debug, Serialize, Deserialize)]
1363pub struct BybitSetLeverageResult {}
1364
1365pub type BybitSetLeverageResponse = BybitResponse<BybitSetLeverageResult>;
1370
1371#[derive(Clone, Debug, Serialize, Deserialize)]
1373pub struct BybitSwitchModeResult {}
1374
1375pub type BybitSwitchModeResponse = BybitResponse<BybitSwitchModeResult>;
1380
1381#[derive(Clone, Debug, Serialize, Deserialize)]
1383pub struct BybitSetTradingStopResult {}
1384
1385pub type BybitSetTradingStopResponse = BybitResponse<BybitSetTradingStopResult>;
1390
1391#[derive(Clone, Debug, Serialize, Deserialize)]
1393#[serde(rename_all = "camelCase")]
1394pub struct BybitBorrowResult {
1395 pub coin: Ustr,
1396 pub amount: String,
1397}
1398
1399pub type BybitBorrowResponse = BybitResponse<BybitBorrowResult>;
1405
1406#[derive(Clone, Debug, Serialize, Deserialize)]
1408#[serde(rename_all = "camelCase")]
1409pub struct BybitNoConvertRepayResult {
1410 pub result_status: BybitRepayStatus,
1411}
1412
1413pub type BybitNoConvertRepayResponse = BybitResponse<BybitNoConvertRepayResult>;
1419
1420#[derive(Clone, Debug, Serialize, Deserialize)]
1422#[serde(rename_all = "camelCase")]
1423pub struct BybitRepayResult {
1424 pub result_status: BybitRepayStatus,
1425}
1426
1427pub type BybitRepayResponse = BybitResponse<BybitRepayResult>;
1433
1434#[derive(Clone, Debug, Serialize, Deserialize)]
1436#[cfg_attr(
1437 feature = "python",
1438 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", from_py_object)
1439)]
1440#[cfg_attr(
1441 feature = "python",
1442 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.bybit")
1443)]
1444#[serde(rename_all = "PascalCase")]
1445pub struct BybitApiKeyPermissions {
1446 #[serde(default)]
1447 pub contract_trade: Vec<String>,
1448 #[serde(default)]
1449 pub spot: Vec<String>,
1450 #[serde(default)]
1451 pub wallet: Vec<String>,
1452 #[serde(default)]
1453 pub options: Vec<String>,
1454 #[serde(default)]
1455 pub derivatives: Vec<String>,
1456 #[serde(default)]
1457 pub exchange: Vec<String>,
1458 #[serde(default)]
1459 pub copy_trading: Vec<String>,
1460 #[serde(default)]
1461 pub block_trade: Vec<String>,
1462 #[serde(rename = "NFT", default)]
1465 pub nft: Vec<String>,
1466 #[serde(default)]
1467 pub affiliate: Vec<String>,
1468 #[serde(default)]
1472 pub earn: Vec<String>,
1473 #[serde(rename = "FiatP2P", default)]
1475 pub fiat_p2p: Vec<String>,
1476 #[serde(default)]
1477 pub fiat_bybit_pay: Vec<String>,
1478 #[serde(default)]
1479 pub fiat_bit_pay: Vec<String>,
1480 #[serde(default)]
1481 pub fiat_global_pay: Vec<String>,
1482 #[serde(default)]
1483 pub fiat_convert_broker: Vec<String>,
1484 #[serde(default)]
1485 pub bit_card: Vec<String>,
1486 #[serde(rename = "ByXPost", default)]
1488 pub byx_post: Vec<String>,
1489}
1490
1491#[derive(Clone, Debug, Serialize, Deserialize)]
1493#[cfg_attr(
1494 feature = "python",
1495 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", from_py_object)
1496)]
1497#[cfg_attr(
1498 feature = "python",
1499 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.bybit")
1500)]
1501#[serde(rename_all = "camelCase")]
1502pub struct BybitAccountDetails {
1503 pub id: String,
1504 pub note: String,
1505 pub api_key: String,
1506 pub read_only: u8,
1507 pub secret: String,
1508 #[serde(rename = "type")]
1509 pub key_type: u8,
1510 pub permissions: BybitApiKeyPermissions,
1511 pub ips: Vec<String>,
1512 #[serde(default)]
1513 pub user_id: Option<u64>,
1514 #[serde(default)]
1515 pub inviter_id: Option<u64>,
1516 pub vip_level: String,
1517 #[serde(deserialize_with = "deserialize_string_to_u8", default)]
1518 pub mkt_maker_level: u8,
1519 #[serde(default)]
1520 pub affiliate_id: Option<u64>,
1521 pub rsa_public_key: String,
1522 pub is_master: bool,
1523 pub parent_uid: String,
1524 pub uta: u8,
1525 pub kyc_level: String,
1526 pub kyc_region: String,
1527 #[serde(default)]
1528 pub unified: Option<i32>,
1529 #[serde(default)]
1530 pub deadline_day: i64,
1531 #[serde(default)]
1532 pub expired_at: Option<String>,
1533 pub created_at: String,
1534}
1535
1536#[cfg(feature = "python")]
1537#[pyo3::pymethods]
1538impl BybitAccountDetails {
1539 #[getter]
1540 #[must_use]
1541 pub fn id(&self) -> &str {
1542 &self.id
1543 }
1544
1545 #[getter]
1546 #[must_use]
1547 pub fn note(&self) -> &str {
1548 &self.note
1549 }
1550
1551 #[getter]
1552 #[must_use]
1553 pub fn api_key(&self) -> &str {
1554 &self.api_key
1555 }
1556
1557 #[getter]
1558 #[must_use]
1559 pub fn read_only(&self) -> u8 {
1560 self.read_only
1561 }
1562
1563 #[getter]
1564 #[must_use]
1565 pub fn key_type(&self) -> u8 {
1566 self.key_type
1567 }
1568
1569 #[getter]
1570 #[must_use]
1571 pub fn user_id(&self) -> Option<u64> {
1572 self.user_id
1573 }
1574
1575 #[getter]
1576 #[must_use]
1577 pub fn inviter_id(&self) -> Option<u64> {
1578 self.inviter_id
1579 }
1580
1581 #[getter]
1582 #[must_use]
1583 pub fn vip_level(&self) -> &str {
1584 &self.vip_level
1585 }
1586
1587 #[getter]
1588 #[must_use]
1589 pub fn mkt_maker_level(&self) -> u8 {
1590 self.mkt_maker_level
1591 }
1592
1593 #[getter]
1594 #[must_use]
1595 pub fn affiliate_id(&self) -> Option<u64> {
1596 self.affiliate_id
1597 }
1598
1599 #[getter]
1600 #[must_use]
1601 pub fn rsa_public_key(&self) -> &str {
1602 &self.rsa_public_key
1603 }
1604
1605 #[getter]
1606 #[must_use]
1607 pub fn is_master(&self) -> bool {
1608 self.is_master
1609 }
1610
1611 #[getter]
1612 #[must_use]
1613 pub fn parent_uid(&self) -> &str {
1614 &self.parent_uid
1615 }
1616
1617 #[getter]
1618 #[must_use]
1619 pub fn uta(&self) -> u8 {
1620 self.uta
1621 }
1622
1623 #[getter]
1624 #[must_use]
1625 pub fn kyc_level(&self) -> &str {
1626 &self.kyc_level
1627 }
1628
1629 #[getter]
1630 #[must_use]
1631 pub fn kyc_region(&self) -> &str {
1632 &self.kyc_region
1633 }
1634
1635 #[getter]
1636 #[must_use]
1637 pub fn deadline_day(&self) -> i64 {
1638 self.deadline_day
1639 }
1640
1641 #[getter]
1642 #[must_use]
1643 pub fn expired_at(&self) -> Option<&str> {
1644 self.expired_at.as_deref()
1645 }
1646
1647 #[getter]
1648 #[must_use]
1649 pub fn created_at(&self) -> &str {
1650 &self.created_at
1651 }
1652}
1653
1654pub type BybitAccountDetailsResponse = BybitResponse<BybitAccountDetails>;
1660
1661#[derive(Clone, Debug, Serialize, Deserialize)]
1673#[serde(rename_all = "camelCase")]
1674pub struct BybitSubMember {
1675 pub uid: String,
1676 pub username: String,
1677 pub member_type: i32,
1678 pub status: i32,
1679 pub account_mode: i32,
1680 #[serde(default)]
1681 pub remark: String,
1682}
1683
1684#[derive(Clone, Debug, Serialize, Deserialize)]
1686#[serde(rename_all = "camelCase")]
1687pub struct BybitSubMembersResult {
1688 #[serde(default)]
1689 pub sub_members: Vec<BybitSubMember>,
1690}
1691
1692pub type BybitSubMembersResponse = BybitResponse<BybitSubMembersResult>;
1698
1699#[derive(Clone, Debug, Serialize, Deserialize)]
1707#[serde(rename_all = "camelCase")]
1708pub struct BybitSubMembersPagedResult {
1709 #[serde(default)]
1710 pub sub_members: Vec<BybitSubMember>,
1711 #[serde(default)]
1712 pub next_cursor: Option<String>,
1713}
1714
1715impl BybitSubMembersPagedResult {
1716 #[must_use]
1723 pub fn continuation_cursor(&self) -> Option<&str> {
1724 match self.next_cursor.as_deref() {
1725 None | Some("" | "0") => None,
1726 Some(cursor) => Some(cursor),
1727 }
1728 }
1729
1730 #[must_use]
1732 pub fn has_more_pages(&self) -> bool {
1733 self.continuation_cursor().is_some()
1734 }
1735}
1736
1737pub type BybitSubMembersPagedResponse = BybitResponse<BybitSubMembersPagedResult>;
1743
1744pub type BybitEscrowSubMembersResponse = BybitResponse<BybitSubMembersPagedResult>;
1751
1752#[derive(Clone, Debug, Serialize, Deserialize)]
1761#[serde(rename_all = "camelCase")]
1762pub struct BybitSubApiKeyInfo {
1763 pub id: String,
1764 #[serde(default)]
1765 pub ips: Vec<String>,
1766 pub api_key: String,
1767 #[serde(default)]
1768 pub note: String,
1769 pub status: i32,
1770 #[serde(default)]
1771 pub expired_at: Option<String>,
1772 pub created_at: String,
1773 #[serde(rename = "type")]
1774 pub key_type: BybitApiKeyType,
1775 #[serde(with = "masked_secret")]
1776 pub secret: Option<String>,
1777 #[serde(with = "bool_or_int")]
1778 pub read_only: bool,
1779 #[serde(default)]
1780 pub deadline_day: Option<i64>,
1781 #[serde(default)]
1782 pub flag: String,
1783 pub permissions: BybitApiKeyPermissions,
1784}
1785
1786#[derive(Clone, Debug, Serialize, Deserialize)]
1792#[serde(rename_all = "camelCase")]
1793pub struct BybitSubApiKeysResult {
1794 #[serde(rename = "result", default)]
1795 pub keys: Vec<BybitSubApiKeyInfo>,
1796 #[serde(default)]
1797 pub next_page_cursor: Option<String>,
1798}
1799
1800impl BybitSubApiKeysResult {
1801 #[must_use]
1807 pub fn continuation_cursor(&self) -> Option<&str> {
1808 match self.next_page_cursor.as_deref() {
1809 None | Some("") => None,
1810 Some(cursor) => Some(cursor),
1811 }
1812 }
1813
1814 #[must_use]
1816 pub fn has_more_pages(&self) -> bool {
1817 self.continuation_cursor().is_some()
1818 }
1819}
1820
1821pub type BybitSubApiKeysResponse = BybitResponse<BybitSubApiKeysResult>;
1827
1828#[derive(Clone, Debug, Serialize, Deserialize)]
1835#[serde(rename_all = "camelCase")]
1836pub struct BybitApiKeyUpdateResult {
1837 pub id: String,
1838 #[serde(default)]
1839 pub note: String,
1840 pub api_key: String,
1841 #[serde(with = "bool_or_int")]
1842 pub read_only: bool,
1843 #[serde(with = "masked_secret")]
1844 pub secret: Option<String>,
1845 pub permissions: BybitApiKeyPermissions,
1846 #[serde(default)]
1847 pub ips: Vec<String>,
1848}
1849
1850pub type BybitUpdateSubApiResponse = BybitResponse<BybitApiKeyUpdateResult>;
1856
1857pub type BybitUpdateMasterApiResponse = BybitResponse<BybitApiKeyUpdateResult>;
1863
1864#[cfg(test)]
1865mod tests {
1866 use nautilus_core::UnixNanos;
1867 use nautilus_model::identifiers::AccountId;
1868 use rstest::rstest;
1869 use rust_decimal::Decimal;
1870 use rust_decimal_macros::dec;
1871
1872 use super::*;
1873 use crate::common::testing::load_test_json;
1874
1875 #[rstest]
1876 fn deserialize_spot_instrument_uses_enums() {
1877 let json = load_test_json("http_get_instruments_spot.json");
1878 let response: BybitInstrumentSpotResponse = serde_json::from_str(&json).unwrap();
1879 let instrument = &response.result.list[0];
1880
1881 assert_eq!(instrument.status, BybitInstrumentStatus::Trading);
1882 assert_eq!(instrument.innovation, BybitInnovationFlag::Standard);
1883 assert_eq!(instrument.margin_trading, BybitMarginTrading::UtaOnly);
1884 }
1885
1886 #[rstest]
1887 fn deserialize_linear_instrument_status() {
1888 let json = load_test_json("http_get_instruments_linear.json");
1889 let response: BybitInstrumentLinearResponse = serde_json::from_str(&json).unwrap();
1890 let instrument = &response.result.list[0];
1891
1892 assert_eq!(instrument.status, BybitInstrumentStatus::Trading);
1893 assert_eq!(instrument.contract_type, BybitContractType::LinearPerpetual);
1894 }
1895
1896 #[rstest]
1897 fn deserialize_spot_instrument_with_xstock_fields() {
1898 let json = load_test_json("http_get_instruments_spot_xstocks.json");
1899 let response: BybitInstrumentSpotResponse = serde_json::from_str(&json).unwrap();
1900 let instrument = &response.result.list[0];
1901
1902 assert_eq!(instrument.symbol_id, Some(42));
1903 assert_eq!(instrument.symbol_type, Some(BybitSymbolType::Xstocks));
1904 assert_eq!(instrument.xstock_multiplier.as_deref(), Some("0.1"));
1905 }
1906
1907 #[rstest]
1908 fn deserialize_linear_instrument_with_symbol_type_and_id() {
1909 let json = load_test_json("http_get_instruments_linear_symbol_type.json");
1910 let response: BybitInstrumentLinearResponse = serde_json::from_str(&json).unwrap();
1911 let instrument = &response.result.list[0];
1912
1913 assert_eq!(instrument.symbol_id, Some(7));
1914 assert_eq!(instrument.symbol_type, Some(BybitSymbolType::Stock));
1915 }
1916
1917 #[derive(Deserialize)]
1918 struct SymbolTypeWrap {
1919 #[serde(rename = "symbolType")]
1920 t: BybitSymbolType,
1921 }
1922
1923 #[rstest]
1924 fn deserialize_symbol_type_falls_back_to_other_for_unknown() {
1925 let json = r#"{"symbolType": "newthing"}"#;
1926 let parsed: SymbolTypeWrap = serde_json::from_str(json).unwrap();
1927 assert_eq!(parsed.t, BybitSymbolType::Other);
1928 }
1929
1930 #[rstest]
1931 fn deserialize_account_info_response() {
1932 let json = load_test_json("http_get_account_info.json");
1933 let response: BybitAccountInfoResponse = serde_json::from_str(&json).unwrap();
1934
1935 assert_eq!(response.result.margin_mode, BybitMarginMode::RegularMargin);
1936 assert_eq!(
1937 response.result.unified_margin_status,
1938 BybitUnifiedMarginStatus::UnifiedTradingAccount10Pro
1939 );
1940 assert!(!response.result.is_master_trader);
1941 assert!(!response.result.spot_hedging_status);
1942 assert!(!response.result.dcp_status);
1943 assert_eq!(response.result.time_window, 10);
1944 assert_eq!(response.result.smp_group, 0);
1945 }
1946
1947 #[rstest]
1948 fn deserialize_account_info_without_deprecated_fields() {
1949 let json = r#"{
1950 "retCode": 0,
1951 "retMsg": "OK",
1952 "result": {
1953 "marginMode": "PORTFOLIO_MARGIN",
1954 "updatedTime": "1697078946000",
1955 "unifiedMarginStatus": 5,
1956 "isMasterTrader": true,
1957 "spotHedgingStatus": "ON"
1958 }
1959 }"#;
1960 let response: BybitAccountInfoResponse = serde_json::from_str(json).unwrap();
1961
1962 assert_eq!(
1963 response.result.margin_mode,
1964 BybitMarginMode::PortfolioMargin
1965 );
1966 assert_eq!(
1967 response.result.unified_margin_status,
1968 BybitUnifiedMarginStatus::UnifiedTradingAccount20
1969 );
1970 assert!(response.result.is_master_trader);
1971 assert!(response.result.spot_hedging_status);
1972 assert!(!response.result.dcp_status);
1973 assert_eq!(response.result.time_window, 0);
1974 assert_eq!(response.result.smp_group, 0);
1975 }
1976
1977 #[rstest]
1978 fn deserialize_account_info_accepts_string_time_window_and_smp_group() {
1979 let mut json: serde_json::Value =
1980 serde_json::from_str(&load_test_json("http_get_account_info.json")).unwrap();
1981 json["result"]["timeWindow"] = serde_json::Value::String("10".to_string());
1982 json["result"]["smpGroup"] = serde_json::Value::String("1234".to_string());
1983
1984 let response: BybitAccountInfoResponse = serde_json::from_value(json).unwrap();
1985
1986 assert_eq!(response.result.time_window, 10);
1987 assert_eq!(response.result.smp_group, 1234);
1988 }
1989
1990 #[rstest]
1991 fn deserialize_order_response_maps_enums() {
1992 let json = load_test_json("http_get_orders_history.json");
1993 let response: BybitOrderHistoryResponse = serde_json::from_str(&json).unwrap();
1994 let order = &response.result.list[0];
1995
1996 assert_eq!(order.cancel_type, BybitCancelType::CancelByUser);
1997 assert_eq!(order.tp_trigger_by, BybitTriggerType::MarkPrice);
1998 assert_eq!(order.sl_trigger_by, BybitTriggerType::LastPrice);
1999 assert_eq!(order.tpsl_mode, Some(BybitTpSlMode::Full));
2000 assert_eq!(order.order_type, BybitOrderType::Limit);
2001 assert_eq!(order.smp_type, BybitSmpType::None);
2002 assert_eq!(order.smp_group, 0);
2003 }
2004
2005 #[rstest]
2006 fn deserialize_order_response_accepts_string_smp_group() {
2007 let mut json: serde_json::Value =
2008 serde_json::from_str(&load_test_json("http_get_orders_history.json")).unwrap();
2009 json["result"]["list"][0]["smpGroup"] = serde_json::Value::String("123456789".to_string());
2010
2011 let response: BybitOrderHistoryResponse = serde_json::from_value(json).unwrap();
2012
2013 assert_eq!(response.result.list[0].smp_group, 123_456_789);
2014 }
2015
2016 #[rstest]
2017 fn deserialize_order_response_accepts_string_position_idx() {
2018 let mut json: serde_json::Value =
2019 serde_json::from_str(&load_test_json("http_get_orders_history.json")).unwrap();
2020 json["result"]["list"][0]["positionIdx"] = serde_json::Value::String("1".to_string());
2021
2022 let response: BybitOrderHistoryResponse = serde_json::from_value(json).unwrap();
2023
2024 assert_eq!(response.result.list[0].position_idx, 1);
2025 }
2026
2027 #[rstest]
2028 #[case::malformed(
2029 "invalid",
2030 "expected i32, received \"invalid\": invalid digit found in string"
2031 )]
2032 #[case::out_of_range(
2033 "2147483648",
2034 "expected i32, received \"2147483648\": number too large to fit in target type"
2035 )]
2036 fn deserialize_order_response_rejects_invalid_string_smp_group(
2037 #[case] value: &str,
2038 #[case] expected: &str,
2039 ) {
2040 let mut json: serde_json::Value =
2041 serde_json::from_str(&load_test_json("http_get_orders_history.json")).unwrap();
2042 json["result"]["list"][0]["smpGroup"] = serde_json::Value::String(value.to_string());
2043
2044 let result: Result<BybitOrderHistoryResponse, _> = serde_json::from_value(json);
2045
2046 assert_eq!(result.unwrap_err().to_string(), expected);
2047 }
2048
2049 #[rstest]
2050 fn deserialize_wallet_balance_without_optional_fields() {
2051 let json = r#"{
2052 "retCode": 0,
2053 "retMsg": "OK",
2054 "result": {
2055 "list": [{
2056 "totalEquity": "1000.00",
2057 "accountIMRate": "0",
2058 "totalMarginBalance": "1000.00",
2059 "totalInitialMargin": "0",
2060 "accountType": "UNIFIED",
2061 "totalAvailableBalance": "1000.00",
2062 "accountMMRate": "0",
2063 "totalPerpUPL": "0",
2064 "totalWalletBalance": "1000.00",
2065 "accountLTV": "0",
2066 "totalMaintenanceMargin": "0",
2067 "coin": [{
2068 "availableToBorrow": "0",
2069 "bonus": "0",
2070 "accruedInterest": "0",
2071 "availableToWithdraw": "1000.00",
2072 "equity": "1000.00",
2073 "usdValue": "1000.00",
2074 "borrowAmount": "0",
2075 "totalPositionIM": "0",
2076 "walletBalance": "1000.00",
2077 "unrealisedPnl": "0",
2078 "cumRealisedPnl": "0",
2079 "locked": "0",
2080 "collateralSwitch": true,
2081 "marginCollateral": true,
2082 "coin": "USDT"
2083 }]
2084 }]
2085 }
2086 }"#;
2087
2088 let response: BybitWalletBalanceResponse = serde_json::from_str(json)
2089 .expect("Failed to parse wallet balance without optional fields");
2090
2091 assert_eq!(response.ret_code, 0);
2092 assert_eq!(response.result.list[0].coin[0].total_order_im, None);
2093 assert_eq!(response.result.list[0].coin[0].total_position_mm, None);
2094 }
2095
2096 #[rstest]
2097 fn deserialize_wallet_balance_from_docs() {
2098 let json = include_str!("../../test_data/http_get_wallet_balance.json");
2099
2100 let response: BybitWalletBalanceResponse = serde_json::from_str(json)
2101 .expect("Failed to parse wallet balance from Bybit docs example");
2102
2103 assert_eq!(response.ret_code, 0);
2104 assert_eq!(response.ret_msg, "OK");
2105
2106 let wallet = &response.result.list[0];
2107 assert_eq!(wallet.total_equity, "3.31216591");
2108 assert_eq!(wallet.account_im_rate, "0");
2109 assert_eq!(wallet.account_mm_rate, "0");
2110 assert_eq!(wallet.total_perp_upl, "0");
2111 assert_eq!(wallet.account_ltv, "0");
2112
2113 let btc = &wallet.coin[0];
2115 assert_eq!(btc.coin.as_str(), "BTC");
2116 assert_eq!(btc.available_to_borrow, "3");
2117 assert_eq!(btc.total_order_im, Some("0".to_string()));
2118 assert_eq!(btc.total_position_mm, Some("0".to_string()));
2119 assert_eq!(btc.total_position_im, Some("0".to_string()));
2120
2121 let usdt = &wallet.coin[1];
2123 assert_eq!(usdt.coin.as_str(), "USDT");
2124 assert_eq!(usdt.wallet_balance, dec!(1000.50));
2125 assert_eq!(usdt.total_order_im, None);
2126 assert_eq!(usdt.total_position_mm, None);
2127 assert_eq!(usdt.total_position_im, None);
2128 assert_eq!(btc.spot_borrow, Decimal::ZERO);
2129 assert_eq!(usdt.spot_borrow, Decimal::ZERO);
2130 }
2131
2132 #[rstest]
2133 fn test_parse_wallet_balance_with_spot_borrow() {
2134 let json = include_str!("../../test_data/http_get_wallet_balance_with_spot_borrow.json");
2135 let response: BybitWalletBalanceResponse =
2136 serde_json::from_str(json).expect("Failed to parse wallet balance with spotBorrow");
2137
2138 let wallet = &response.result.list[0];
2139 let usdt = &wallet.coin[0];
2140
2141 assert_eq!(usdt.coin.as_str(), "USDT");
2142 assert_eq!(usdt.wallet_balance, dec!(1200.00));
2143 assert_eq!(usdt.spot_borrow, dec!(200.00));
2144 assert_eq!(usdt.borrow_amount, "200.00");
2145
2146 let account_id = crate::common::parse::parse_account_state(
2148 wallet,
2149 AccountId::new("BYBIT-001"),
2150 UnixNanos::default(),
2151 )
2152 .expect("Failed to parse account state");
2153
2154 let balance = &account_id.balances[0];
2155 assert_eq!(balance.total.as_f64(), 1000.0);
2156 }
2157
2158 #[rstest]
2159 fn test_parse_wallet_balance_spot_short() {
2160 let json = include_str!("../../test_data/http_get_wallet_balance_spot_short.json");
2161 let response: BybitWalletBalanceResponse = serde_json::from_str(json)
2162 .expect("Failed to parse wallet balance with SHORT SPOT position");
2163
2164 let wallet = &response.result.list[0];
2165 let eth = &wallet.coin[0];
2166
2167 assert_eq!(eth.coin.as_str(), "ETH");
2168 assert_eq!(eth.wallet_balance, dec!(0));
2169 assert_eq!(eth.spot_borrow, dec!(0.06142));
2170 assert_eq!(eth.borrow_amount, "0.06142");
2171
2172 let account_state = crate::common::parse::parse_account_state(
2173 wallet,
2174 AccountId::new("BYBIT-001"),
2175 UnixNanos::default(),
2176 )
2177 .expect("Failed to parse account state");
2178
2179 let eth_balance = account_state
2180 .balances
2181 .iter()
2182 .find(|b| b.currency.code.as_str() == "ETH")
2183 .expect("ETH balance not found");
2184
2185 assert_eq!(eth_balance.total.as_f64(), -0.06142);
2187 }
2188
2189 #[rstest]
2190 fn deserialize_borrow_response() {
2191 let json = r#"{
2192 "retCode": 0,
2193 "retMsg": "success",
2194 "result": {
2195 "coin": "BTC",
2196 "amount": "0.01"
2197 },
2198 "retExtInfo": {},
2199 "time": 1756197991955
2200 }"#;
2201
2202 let response: BybitBorrowResponse = serde_json::from_str(json).unwrap();
2203
2204 assert_eq!(response.ret_code, 0);
2205 assert_eq!(response.ret_msg, "success");
2206 assert_eq!(response.result.coin, "BTC");
2207 assert_eq!(response.result.amount, "0.01");
2208 }
2209
2210 #[rstest]
2211 fn deserialize_no_convert_repay_response() {
2212 let json = r#"{
2213 "retCode": 0,
2214 "retMsg": "OK",
2215 "result": {
2216 "resultStatus": "SU"
2217 },
2218 "retExtInfo": {},
2219 "time": 1234567890
2220 }"#;
2221
2222 let response: BybitNoConvertRepayResponse = serde_json::from_str(json).unwrap();
2223
2224 assert_eq!(response.ret_code, 0);
2225 assert_eq!(response.ret_msg, "OK");
2226 assert_eq!(response.result.result_status, BybitRepayStatus::Success);
2227 }
2228
2229 #[rstest]
2230 fn deserialize_repay_response() {
2231 let json = r#"{
2232 "retCode": 0,
2233 "retMsg": "success",
2234 "result": {
2235 "resultStatus": "P"
2236 },
2237 "retExtInfo": {},
2238 "time": 1756295680801
2239 }"#;
2240
2241 let response: BybitRepayResponse = serde_json::from_str(json).unwrap();
2242
2243 assert_eq!(response.ret_code, 0);
2244 assert_eq!(response.ret_msg, "success");
2245 assert_eq!(response.result.result_status, BybitRepayStatus::Processing);
2246 }
2247
2248 #[rstest]
2249 fn deserialize_position_without_conditional_fields() {
2250 let json = r#"{
2254 "retCode": 0,
2255 "retMsg": "OK",
2256 "result": {
2257 "list": [{
2258 "positionIdx": 0,
2259 "riskId": 1,
2260 "riskLimitValue": "150",
2261 "symbol": "LTCUSDT",
2262 "side": "",
2263 "size": "0",
2264 "avgPrice": "0",
2265 "positionValue": "0",
2266 "tradeMode": 0,
2267 "positionStatus": "Normal",
2268 "autoAddMargin": 0,
2269 "adlRankIndicator": 0,
2270 "leverage": "10",
2271 "positionBalance": "0",
2272 "markPrice": "70.00",
2273 "liqPrice": "",
2274 "bustPrice": "",
2275 "positionMM": "0",
2276 "positionIM": "0",
2277 "tpslMode": "Full",
2278 "takeProfit": "0",
2279 "stopLoss": "0",
2280 "trailingStop": "0",
2281 "unrealisedPnl": "0",
2282 "curRealisedPnl": "0",
2283 "cumRealisedPnl": "0",
2284 "createdTime": "1676538056258",
2285 "updatedTime": "1697673600012"
2286 }],
2287 "nextPageCursor": "",
2288 "category": "linear"
2289 },
2290 "retExtInfo": {},
2291 "time": 1697673900000
2292 }"#;
2293
2294 let response: BybitPositionListResponse = serde_json::from_str(json)
2295 .expect("Failed to parse position list with missing conditional fields");
2296
2297 let position = &response.result.list[0];
2298 assert!(!position.is_reduce_only);
2299 assert_eq!(position.seq, -1);
2300 assert_eq!(position.mmr_sys_updated_time, "");
2301 assert_eq!(position.leverage_sys_updated_time, "");
2302 assert_eq!(position.open_time, 0);
2303 }
2304
2305 #[rstest]
2306 #[case(0_i64)]
2307 #[case(1_700_000_000_123_i64)]
2308 fn deserialize_position_with_open_time_integer(#[case] expected: i64) {
2309 let mut value: serde_json::Value =
2311 serde_json::from_str(&load_test_json("http_get_positions_with_open_time.json"))
2312 .unwrap();
2313 value["result"]["list"][0]["openTime"] = serde_json::json!(expected);
2314
2315 let response: BybitPositionListResponse = serde_json::from_value(value)
2316 .expect("Failed to parse position list with integer openTime");
2317
2318 assert_eq!(response.result.list[0].open_time, expected);
2319 }
2320
2321 #[rstest]
2322 fn deserialize_position_response_accepts_string_integer_fields() {
2323 let mut json: serde_json::Value =
2324 serde_json::from_str(&load_test_json("http_get_positions.json")).unwrap();
2325 let position = &mut json["result"]["list"][0];
2326 position["riskId"] = serde_json::Value::String("1234".to_string());
2327 position["tradeMode"] = serde_json::Value::String("1".to_string());
2328 position["autoAddMargin"] = serde_json::Value::String("2".to_string());
2329 position["adlRankIndicator"] = serde_json::Value::String("35".to_string());
2330
2331 let response: BybitPositionListResponse = serde_json::from_value(json).unwrap();
2332
2333 let position = &response.result.list[0];
2334 assert_eq!(position.risk_id, 1234);
2335 assert_eq!(position.trade_mode, 1);
2336 assert_eq!(position.auto_add_margin, 2);
2337 assert_eq!(position.adl_rank_indicator, 35);
2338 }
2339
2340 #[rstest]
2341 fn deserialize_inverse_instrument_with_symbol_type_and_id() {
2342 let json = load_test_json("http_get_instruments_inverse_symbol_type.json");
2343 let response: BybitInstrumentInverseResponse = serde_json::from_str(&json).unwrap();
2344 let instrument = &response.result.list[0];
2345
2346 assert_eq!(instrument.symbol_id, Some(11));
2347 assert_eq!(instrument.symbol_type, Some(BybitSymbolType::Commodity));
2348 }
2349
2350 #[rstest]
2351 fn deserialize_option_instrument_with_symbol_id() {
2352 let json = load_test_json("http_get_instruments_option_symbol_id.json");
2353 let response: BybitInstrumentOptionResponse = serde_json::from_str(&json).unwrap();
2354 let instrument = &response.result.list[0];
2355
2356 assert_eq!(instrument.symbol_id, Some(99));
2357 }
2358
2359 #[rstest]
2360 fn deserialize_sub_members_response() {
2361 let json = load_test_json("http_get_user_sub_members.json");
2362 let response: BybitSubMembersResponse =
2363 serde_json::from_str(&json).expect("parse sub members");
2364 assert_eq!(response.ret_code, 0);
2365 assert_eq!(response.result.sub_members.len(), 2);
2366 let first = &response.result.sub_members[0];
2367 assert_eq!(first.uid, "106314365");
2368 assert_eq!(first.username, "xxxx02");
2369 assert_eq!(first.member_type, 1);
2370 assert_eq!(first.status, 1);
2371 assert_eq!(first.account_mode, 5);
2372 assert_eq!(first.remark, "");
2373 let second = &response.result.sub_members[1];
2374 assert_eq!(second.uid, "106279879");
2375 assert_eq!(second.account_mode, 6);
2376 }
2377
2378 #[rstest]
2379 fn deserialize_sub_members_paged_response() {
2380 let json = load_test_json("http_get_user_sub_members_paged.json");
2383 let response: BybitSubMembersPagedResponse =
2384 serde_json::from_str(&json).expect("parse paged sub members");
2385 assert_eq!(response.result.sub_members.len(), 2);
2386 assert_eq!(response.result.next_cursor.as_deref(), Some("0"));
2387 assert!(!response.result.has_more_pages());
2388 assert_eq!(response.result.continuation_cursor(), None);
2389 }
2390
2391 #[rstest]
2392 fn deserialize_escrow_sub_members_response_uses_same_shape() {
2393 let json = load_test_json("http_get_user_escrow_sub_members.json");
2396 let response: BybitEscrowSubMembersResponse =
2397 serde_json::from_str(&json).expect("parse escrow sub members");
2398 assert_eq!(response.result.sub_members.len(), 2);
2399 assert_eq!(response.result.sub_members[0].member_type, 12);
2400 assert_eq!(response.result.sub_members[0].remark, "earn fund");
2401 assert_eq!(response.result.next_cursor.as_deref(), Some("344"));
2402 assert!(response.result.has_more_pages());
2403 assert_eq!(response.result.continuation_cursor(), Some("344"));
2404 }
2405
2406 #[rstest]
2407 fn deserialize_sub_api_keys_response() {
2408 let json = load_test_json("http_get_user_sub_apikeys.json");
2411 let response: BybitSubApiKeysResponse =
2412 serde_json::from_str(&json).expect("parse sub apikeys");
2413 assert_eq!(response.result.keys.len(), 1);
2414 let key = &response.result.keys[0];
2415 assert!(!key.read_only);
2416 assert_eq!(key.secret, None);
2417 assert_eq!(key.key_type, BybitApiKeyType::Hmac);
2418 assert_eq!(key.flag, "hmac");
2419 assert_eq!(key.deadline_day, Some(21));
2420 assert_eq!(key.permissions.contract_trade, vec!["Order", "Position"]);
2421 assert_eq!(key.permissions.spot, vec!["SpotTrade"]);
2422 assert!(key.permissions.earn.is_empty());
2423 assert_eq!(response.result.next_page_cursor.as_deref(), Some(""));
2424 assert!(!response.result.has_more_pages());
2425 }
2426
2427 #[rstest]
2428 fn deserialize_update_sub_api_response() {
2429 let json = load_test_json("http_post_user_update_sub_api.json");
2430 let response: BybitUpdateSubApiResponse =
2431 serde_json::from_str(&json).expect("parse update sub api");
2432 assert!(!response.result.read_only);
2433 assert_eq!(response.result.secret, None);
2434 assert_eq!(response.result.ips, vec!["*"]);
2435 assert_eq!(response.result.permissions.spot, vec!["SpotTrade"]);
2436 assert_eq!(response.result.permissions.wallet, vec!["AccountTransfer"]);
2437 }
2438
2439 #[rstest]
2440 fn deserialize_update_master_api_response() {
2441 let json = load_test_json("http_post_user_update_master_api.json");
2446 let response: BybitUpdateMasterApiResponse =
2447 serde_json::from_str(&json).expect("parse update master api");
2448 assert!(!response.result.read_only);
2449 assert_eq!(response.result.ips, vec!["*"]);
2450 let perms = &response.result.permissions;
2451 assert_eq!(perms.contract_trade, vec!["Order", "Position"]);
2452 assert_eq!(perms.copy_trading, vec!["CopyTrading"]);
2453 assert!(perms.earn.is_empty());
2454 assert_eq!(perms.nft, vec!["NFTQueryProductList"]);
2455 }
2456
2457 #[rstest]
2458 fn deserialize_permissions_renamed_buckets_preserve_values() {
2459 let json = r#"{
2465 "NFT": ["NFTQueryProductList"],
2466 "FiatP2P": ["P2PDeposit"],
2467 "ByXPost": ["PostContent"]
2468 }"#;
2469 let perms: BybitApiKeyPermissions =
2470 serde_json::from_str(json).expect("parse renamed buckets");
2471 assert_eq!(perms.nft, vec!["NFTQueryProductList"]);
2472 assert_eq!(perms.fiat_p2p, vec!["P2PDeposit"]);
2473 assert_eq!(perms.byx_post, vec!["PostContent"]);
2474 }
2475
2476 #[rstest]
2477 fn deserialize_account_details_response_with_current_docs_example() {
2478 let json = load_test_json("http_get_user_query_api.json");
2479 let response: BybitAccountDetailsResponse =
2480 serde_json::from_str(&json).expect("parse account details");
2481
2482 assert_eq!(
2483 response.result.permissions.fiat_global_pay,
2484 Vec::<String>::new()
2485 );
2486 assert_eq!(
2487 response.result.permissions.fiat_bit_pay,
2488 vec!["FaitPayOrder"]
2489 );
2490 assert_eq!(response.result.permissions.bit_card, vec!["BitCard"]);
2491 assert_eq!(response.result.permissions.byx_post, vec!["ByXPost"]);
2492 assert_eq!(response.result.unified, Some(0));
2493 }
2494}