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, BybitSmpType,
28 BybitStopOrderType, BybitSymbolType, BybitTimeInForce, BybitTpSlMode,
29 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_optional_decimal_or_zero,
38 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.core.nautilus_pyo3.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.core.nautilus_pyo3.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.core.nautilus_pyo3.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.core.nautilus_pyo3.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)]
854 pub time_window: i32,
855 #[serde(default)]
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.core.nautilus_pyo3.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 pub position_idx: i32,
889 pub order_status: BybitOrderStatus,
890 pub cancel_type: BybitCancelType,
891 pub reject_reason: Ustr,
892 pub avg_price: Option<String>,
893 pub leaves_qty: String,
894 pub leaves_value: String,
895 pub cum_exec_qty: String,
896 pub cum_exec_value: String,
897 pub cum_exec_fee: String,
898 pub time_in_force: BybitTimeInForce,
899 pub order_type: BybitOrderType,
900 pub stop_order_type: BybitStopOrderType,
901 pub order_iv: Option<String>,
902 pub trigger_price: String,
903 pub take_profit: String,
904 pub stop_loss: String,
905 pub tp_trigger_by: BybitTriggerType,
906 pub sl_trigger_by: BybitTriggerType,
907 pub trigger_direction: BybitTriggerDirection,
908 pub trigger_by: BybitTriggerType,
909 pub last_price_on_created: String,
910 pub reduce_only: bool,
911 pub close_on_trigger: bool,
912 pub smp_type: BybitSmpType,
913 pub smp_group: i32,
914 pub smp_order_id: Ustr,
915 pub tpsl_mode: Option<BybitTpSlMode>,
916 pub tp_limit_price: String,
917 pub sl_limit_price: String,
918 pub place_type: Ustr,
919 pub created_time: String,
920 pub updated_time: String,
921}
922
923#[cfg(feature = "python")]
924#[pyo3::pymethods]
925impl BybitOrder {
926 #[getter]
927 #[must_use]
928 pub fn order_id(&self) -> &str {
929 self.order_id.as_str()
930 }
931
932 #[getter]
933 #[must_use]
934 pub fn order_link_id(&self) -> &str {
935 self.order_link_id.as_str()
936 }
937
938 #[getter]
939 #[must_use]
940 pub fn block_trade_id(&self) -> Option<&str> {
941 self.block_trade_id.as_ref().map(|s| s.as_str())
942 }
943
944 #[getter]
945 #[must_use]
946 pub fn symbol(&self) -> &str {
947 self.symbol.as_str()
948 }
949
950 #[getter]
951 #[must_use]
952 pub fn price(&self) -> &str {
953 &self.price
954 }
955
956 #[getter]
957 #[must_use]
958 pub fn qty(&self) -> &str {
959 &self.qty
960 }
961
962 #[getter]
963 #[must_use]
964 pub fn side(&self) -> BybitOrderSide {
965 self.side
966 }
967
968 #[getter]
969 #[must_use]
970 pub fn is_leverage(&self) -> &str {
971 &self.is_leverage
972 }
973
974 #[getter]
975 #[must_use]
976 pub fn position_idx(&self) -> i32 {
977 self.position_idx
978 }
979
980 #[getter]
981 #[must_use]
982 pub fn order_status(&self) -> BybitOrderStatus {
983 self.order_status
984 }
985
986 #[getter]
987 #[must_use]
988 pub fn cancel_type(&self) -> BybitCancelType {
989 self.cancel_type
990 }
991
992 #[getter]
993 #[must_use]
994 pub fn reject_reason(&self) -> &str {
995 self.reject_reason.as_str()
996 }
997
998 #[getter]
999 #[must_use]
1000 pub fn avg_price(&self) -> Option<&str> {
1001 self.avg_price.as_deref()
1002 }
1003
1004 #[getter]
1005 #[must_use]
1006 pub fn leaves_qty(&self) -> &str {
1007 &self.leaves_qty
1008 }
1009
1010 #[getter]
1011 #[must_use]
1012 pub fn leaves_value(&self) -> &str {
1013 &self.leaves_value
1014 }
1015
1016 #[getter]
1017 #[must_use]
1018 pub fn cum_exec_qty(&self) -> &str {
1019 &self.cum_exec_qty
1020 }
1021
1022 #[getter]
1023 #[must_use]
1024 pub fn cum_exec_value(&self) -> &str {
1025 &self.cum_exec_value
1026 }
1027
1028 #[getter]
1029 #[must_use]
1030 pub fn cum_exec_fee(&self) -> &str {
1031 &self.cum_exec_fee
1032 }
1033
1034 #[getter]
1035 #[must_use]
1036 pub fn time_in_force(&self) -> BybitTimeInForce {
1037 self.time_in_force
1038 }
1039
1040 #[getter]
1041 #[must_use]
1042 pub fn order_type(&self) -> BybitOrderType {
1043 self.order_type
1044 }
1045
1046 #[getter]
1047 #[must_use]
1048 pub fn stop_order_type(&self) -> BybitStopOrderType {
1049 self.stop_order_type
1050 }
1051
1052 #[getter]
1053 #[must_use]
1054 pub fn order_iv(&self) -> Option<&str> {
1055 self.order_iv.as_deref()
1056 }
1057
1058 #[getter]
1059 #[must_use]
1060 pub fn trigger_price(&self) -> &str {
1061 &self.trigger_price
1062 }
1063
1064 #[getter]
1065 #[must_use]
1066 pub fn take_profit(&self) -> &str {
1067 &self.take_profit
1068 }
1069
1070 #[getter]
1071 #[must_use]
1072 pub fn stop_loss(&self) -> &str {
1073 &self.stop_loss
1074 }
1075
1076 #[getter]
1077 #[must_use]
1078 pub fn tp_trigger_by(&self) -> BybitTriggerType {
1079 self.tp_trigger_by
1080 }
1081
1082 #[getter]
1083 #[must_use]
1084 pub fn sl_trigger_by(&self) -> BybitTriggerType {
1085 self.sl_trigger_by
1086 }
1087
1088 #[getter]
1089 #[must_use]
1090 pub fn trigger_direction(&self) -> BybitTriggerDirection {
1091 self.trigger_direction
1092 }
1093
1094 #[getter]
1095 #[must_use]
1096 pub fn trigger_by(&self) -> BybitTriggerType {
1097 self.trigger_by
1098 }
1099
1100 #[getter]
1101 #[must_use]
1102 pub fn last_price_on_created(&self) -> &str {
1103 &self.last_price_on_created
1104 }
1105
1106 #[getter]
1107 #[must_use]
1108 pub fn reduce_only(&self) -> bool {
1109 self.reduce_only
1110 }
1111
1112 #[getter]
1113 #[must_use]
1114 pub fn close_on_trigger(&self) -> bool {
1115 self.close_on_trigger
1116 }
1117
1118 #[getter]
1119 #[must_use]
1120 #[expect(
1121 clippy::missing_panics_doc,
1122 reason = "serialization of a simple enum cannot fail"
1123 )]
1124 pub fn smp_type(&self) -> String {
1125 serde_json::to_string(&self.smp_type)
1126 .expect("Failed to serialize BybitSmpType")
1127 .trim_matches('"')
1128 .to_string()
1129 }
1130
1131 #[getter]
1132 #[must_use]
1133 pub fn smp_group(&self) -> i32 {
1134 self.smp_group
1135 }
1136
1137 #[getter]
1138 #[must_use]
1139 pub fn smp_order_id(&self) -> &str {
1140 self.smp_order_id.as_str()
1141 }
1142
1143 #[getter]
1144 #[must_use]
1145 pub fn tpsl_mode(&self) -> Option<BybitTpSlMode> {
1146 self.tpsl_mode
1147 }
1148
1149 #[getter]
1150 #[must_use]
1151 pub fn tp_limit_price(&self) -> &str {
1152 &self.tp_limit_price
1153 }
1154
1155 #[getter]
1156 #[must_use]
1157 pub fn sl_limit_price(&self) -> &str {
1158 &self.sl_limit_price
1159 }
1160
1161 #[getter]
1162 #[must_use]
1163 pub fn place_type(&self) -> &str {
1164 self.place_type.as_str()
1165 }
1166
1167 #[getter]
1168 #[must_use]
1169 pub fn created_time(&self) -> &str {
1170 &self.created_time
1171 }
1172
1173 #[getter]
1174 #[must_use]
1175 pub fn updated_time(&self) -> &str {
1176 &self.updated_time
1177 }
1178}
1179
1180pub type BybitOpenOrdersResponse = BybitCursorListResponse<BybitOrder>;
1185pub type BybitOrderHistoryResponse = BybitCursorListResponse<BybitOrder>;
1190
1191#[derive(Clone, Debug, Serialize, Deserialize)]
1196#[serde(rename_all = "camelCase")]
1197pub struct BybitPlaceOrderResult {
1198 pub order_id: Option<Ustr>,
1199 pub order_link_id: Option<Ustr>,
1200}
1201
1202pub type BybitPlaceOrderResponse = BybitResponse<BybitPlaceOrderResult>;
1207
1208#[derive(Clone, Debug, Serialize, Deserialize)]
1213#[serde(rename_all = "camelCase")]
1214pub struct BybitCancelOrderResult {
1215 pub order_id: Option<Ustr>,
1216 pub order_link_id: Option<Ustr>,
1217}
1218
1219pub type BybitCancelOrderResponse = BybitResponse<BybitCancelOrderResult>;
1224
1225#[derive(Clone, Debug, Serialize, Deserialize)]
1230#[serde(rename_all = "camelCase")]
1231pub struct BybitExecution {
1232 pub symbol: Ustr,
1233 pub order_id: Ustr,
1234 pub order_link_id: Ustr,
1235 pub side: BybitOrderSide,
1236 pub order_price: String,
1237 pub order_qty: String,
1238 pub leaves_qty: String,
1239 pub create_type: Option<BybitCreateType>,
1240 pub order_type: BybitOrderType,
1241 pub stop_order_type: Option<BybitStopOrderType>,
1242 pub exec_fee: String,
1243 pub exec_id: String,
1244 pub exec_price: String,
1245 pub exec_qty: String,
1246 pub exec_type: BybitExecType,
1247 pub exec_value: String,
1248 pub exec_time: String,
1249 pub fee_currency: Ustr,
1250 pub is_maker: bool,
1251 pub fee_rate: String,
1252 pub trade_iv: String,
1253 pub mark_iv: String,
1254 pub mark_price: String,
1255 pub index_price: String,
1256 pub underlying_price: String,
1257 pub block_trade_id: String,
1258 pub closed_size: String,
1259 pub seq: i64,
1260}
1261
1262pub type BybitTradeHistoryResponse = BybitCursorListResponse<BybitExecution>;
1267
1268#[derive(Clone, Debug, Serialize, Deserialize)]
1273#[serde(rename_all = "camelCase")]
1274pub struct BybitPosition {
1275 pub position_idx: BybitPositionIdx,
1276 pub risk_id: i32,
1277 pub risk_limit_value: String,
1278 pub symbol: Ustr,
1279 pub side: BybitPositionSide,
1280 pub size: String,
1281 pub avg_price: String,
1282 pub position_value: String,
1283 pub trade_mode: i32,
1284 pub position_status: BybitPositionStatus,
1285 pub auto_add_margin: i32,
1286 pub adl_rank_indicator: i32,
1287 pub leverage: String,
1288 pub position_balance: String,
1289 pub mark_price: String,
1290 pub liq_price: String,
1291 pub bust_price: String,
1292 #[serde(rename = "positionMM")]
1293 pub position_mm: String,
1294 #[serde(rename = "positionIM")]
1295 pub position_im: String,
1296 pub tpsl_mode: BybitTpSlMode,
1297 pub take_profit: String,
1298 pub stop_loss: String,
1299 pub trailing_stop: String,
1300 pub unrealised_pnl: String,
1301 pub cur_realised_pnl: String,
1302 pub cum_realised_pnl: String,
1303 #[serde(default = "default_position_seq")]
1304 pub seq: i64,
1305 #[serde(default)]
1306 pub is_reduce_only: bool,
1307 #[serde(default)]
1308 pub mmr_sys_updated_time: String,
1309 #[serde(default)]
1310 pub leverage_sys_updated_time: String,
1311 pub created_time: String,
1312 pub updated_time: String,
1313 #[serde(default)]
1314 pub open_time: i64,
1315}
1316
1317const fn default_position_seq() -> i64 {
1318 -1
1319}
1320
1321pub type BybitPositionListResponse = BybitCursorListResponse<BybitPosition>;
1326
1327#[derive(Clone, Debug, Serialize, Deserialize)]
1332#[serde(rename_all = "camelCase")]
1333pub struct BybitSetMarginModeReason {
1334 pub reason_code: String,
1335 pub reason_msg: String,
1336}
1337
1338#[derive(Clone, Debug, Serialize, Deserialize)]
1343#[serde(rename_all = "camelCase")]
1344pub struct BybitSetMarginModeResult {
1345 #[serde(default)]
1346 pub reasons: Vec<BybitSetMarginModeReason>,
1347}
1348
1349pub type BybitSetMarginModeResponse = BybitResponse<BybitSetMarginModeResult>;
1354
1355#[derive(Clone, Debug, Serialize, Deserialize)]
1357pub struct BybitSetLeverageResult {}
1358
1359pub type BybitSetLeverageResponse = BybitResponse<BybitSetLeverageResult>;
1364
1365#[derive(Clone, Debug, Serialize, Deserialize)]
1367pub struct BybitSwitchModeResult {}
1368
1369pub type BybitSwitchModeResponse = BybitResponse<BybitSwitchModeResult>;
1374
1375#[derive(Clone, Debug, Serialize, Deserialize)]
1377pub struct BybitSetTradingStopResult {}
1378
1379pub type BybitSetTradingStopResponse = BybitResponse<BybitSetTradingStopResult>;
1384
1385#[derive(Clone, Debug, Serialize, Deserialize)]
1387#[serde(rename_all = "camelCase")]
1388pub struct BybitBorrowResult {
1389 pub coin: Ustr,
1390 pub amount: String,
1391}
1392
1393pub type BybitBorrowResponse = BybitResponse<BybitBorrowResult>;
1399
1400#[derive(Clone, Debug, Serialize, Deserialize)]
1402#[serde(rename_all = "camelCase")]
1403pub struct BybitNoConvertRepayResult {
1404 pub result_status: String,
1405}
1406
1407pub type BybitNoConvertRepayResponse = BybitResponse<BybitNoConvertRepayResult>;
1413
1414#[derive(Clone, Debug, Serialize, Deserialize)]
1416#[cfg_attr(
1417 feature = "python",
1418 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bybit", from_py_object)
1419)]
1420#[cfg_attr(
1421 feature = "python",
1422 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.bybit")
1423)]
1424#[serde(rename_all = "PascalCase")]
1425pub struct BybitApiKeyPermissions {
1426 #[serde(default)]
1427 pub contract_trade: Vec<String>,
1428 #[serde(default)]
1429 pub spot: Vec<String>,
1430 #[serde(default)]
1431 pub wallet: Vec<String>,
1432 #[serde(default)]
1433 pub options: Vec<String>,
1434 #[serde(default)]
1435 pub derivatives: Vec<String>,
1436 #[serde(default)]
1437 pub exchange: Vec<String>,
1438 #[serde(default)]
1439 pub copy_trading: Vec<String>,
1440 #[serde(default)]
1441 pub block_trade: Vec<String>,
1442 #[serde(rename = "NFT", default)]
1445 pub nft: Vec<String>,
1446 #[serde(default)]
1447 pub affiliate: Vec<String>,
1448 #[serde(default)]
1452 pub earn: Vec<String>,
1453 #[serde(rename = "FiatP2P", default)]
1455 pub fiat_p2p: Vec<String>,
1456 #[serde(default)]
1457 pub fiat_bybit_pay: Vec<String>,
1458 #[serde(default)]
1459 pub fiat_bit_pay: Vec<String>,
1460 #[serde(default)]
1461 pub fiat_global_pay: Vec<String>,
1462 #[serde(default)]
1463 pub fiat_convert_broker: Vec<String>,
1464 #[serde(default)]
1465 pub bit_card: Vec<String>,
1466 #[serde(rename = "ByXPost", default)]
1468 pub byx_post: Vec<String>,
1469}
1470
1471#[derive(Clone, Debug, Serialize, Deserialize)]
1473#[cfg_attr(
1474 feature = "python",
1475 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bybit", from_py_object)
1476)]
1477#[cfg_attr(
1478 feature = "python",
1479 pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.bybit")
1480)]
1481#[serde(rename_all = "camelCase")]
1482pub struct BybitAccountDetails {
1483 pub id: String,
1484 pub note: String,
1485 pub api_key: String,
1486 pub read_only: u8,
1487 pub secret: String,
1488 #[serde(rename = "type")]
1489 pub key_type: u8,
1490 pub permissions: BybitApiKeyPermissions,
1491 pub ips: Vec<String>,
1492 #[serde(default)]
1493 pub user_id: Option<u64>,
1494 #[serde(default)]
1495 pub inviter_id: Option<u64>,
1496 pub vip_level: String,
1497 #[serde(deserialize_with = "deserialize_string_to_u8", default)]
1498 pub mkt_maker_level: u8,
1499 #[serde(default)]
1500 pub affiliate_id: Option<u64>,
1501 pub rsa_public_key: String,
1502 pub is_master: bool,
1503 pub parent_uid: String,
1504 pub uta: u8,
1505 pub kyc_level: String,
1506 pub kyc_region: String,
1507 #[serde(default)]
1508 pub unified: Option<i32>,
1509 #[serde(default)]
1510 pub deadline_day: i64,
1511 #[serde(default)]
1512 pub expired_at: Option<String>,
1513 pub created_at: String,
1514}
1515
1516#[cfg(feature = "python")]
1517#[pyo3::pymethods]
1518impl BybitAccountDetails {
1519 #[getter]
1520 #[must_use]
1521 pub fn id(&self) -> &str {
1522 &self.id
1523 }
1524
1525 #[getter]
1526 #[must_use]
1527 pub fn note(&self) -> &str {
1528 &self.note
1529 }
1530
1531 #[getter]
1532 #[must_use]
1533 pub fn api_key(&self) -> &str {
1534 &self.api_key
1535 }
1536
1537 #[getter]
1538 #[must_use]
1539 pub fn read_only(&self) -> u8 {
1540 self.read_only
1541 }
1542
1543 #[getter]
1544 #[must_use]
1545 pub fn key_type(&self) -> u8 {
1546 self.key_type
1547 }
1548
1549 #[getter]
1550 #[must_use]
1551 pub fn user_id(&self) -> Option<u64> {
1552 self.user_id
1553 }
1554
1555 #[getter]
1556 #[must_use]
1557 pub fn inviter_id(&self) -> Option<u64> {
1558 self.inviter_id
1559 }
1560
1561 #[getter]
1562 #[must_use]
1563 pub fn vip_level(&self) -> &str {
1564 &self.vip_level
1565 }
1566
1567 #[getter]
1568 #[must_use]
1569 pub fn mkt_maker_level(&self) -> u8 {
1570 self.mkt_maker_level
1571 }
1572
1573 #[getter]
1574 #[must_use]
1575 pub fn affiliate_id(&self) -> Option<u64> {
1576 self.affiliate_id
1577 }
1578
1579 #[getter]
1580 #[must_use]
1581 pub fn rsa_public_key(&self) -> &str {
1582 &self.rsa_public_key
1583 }
1584
1585 #[getter]
1586 #[must_use]
1587 pub fn is_master(&self) -> bool {
1588 self.is_master
1589 }
1590
1591 #[getter]
1592 #[must_use]
1593 pub fn parent_uid(&self) -> &str {
1594 &self.parent_uid
1595 }
1596
1597 #[getter]
1598 #[must_use]
1599 pub fn uta(&self) -> u8 {
1600 self.uta
1601 }
1602
1603 #[getter]
1604 #[must_use]
1605 pub fn kyc_level(&self) -> &str {
1606 &self.kyc_level
1607 }
1608
1609 #[getter]
1610 #[must_use]
1611 pub fn kyc_region(&self) -> &str {
1612 &self.kyc_region
1613 }
1614
1615 #[getter]
1616 #[must_use]
1617 pub fn deadline_day(&self) -> i64 {
1618 self.deadline_day
1619 }
1620
1621 #[getter]
1622 #[must_use]
1623 pub fn expired_at(&self) -> Option<&str> {
1624 self.expired_at.as_deref()
1625 }
1626
1627 #[getter]
1628 #[must_use]
1629 pub fn created_at(&self) -> &str {
1630 &self.created_at
1631 }
1632}
1633
1634pub type BybitAccountDetailsResponse = BybitResponse<BybitAccountDetails>;
1640
1641#[derive(Clone, Debug, Serialize, Deserialize)]
1653#[serde(rename_all = "camelCase")]
1654pub struct BybitSubMember {
1655 pub uid: String,
1656 pub username: String,
1657 pub member_type: i32,
1658 pub status: i32,
1659 pub account_mode: i32,
1660 #[serde(default)]
1661 pub remark: String,
1662}
1663
1664#[derive(Clone, Debug, Serialize, Deserialize)]
1666#[serde(rename_all = "camelCase")]
1667pub struct BybitSubMembersResult {
1668 #[serde(default)]
1669 pub sub_members: Vec<BybitSubMember>,
1670}
1671
1672pub type BybitSubMembersResponse = BybitResponse<BybitSubMembersResult>;
1678
1679#[derive(Clone, Debug, Serialize, Deserialize)]
1687#[serde(rename_all = "camelCase")]
1688pub struct BybitSubMembersPagedResult {
1689 #[serde(default)]
1690 pub sub_members: Vec<BybitSubMember>,
1691 #[serde(default)]
1692 pub next_cursor: Option<String>,
1693}
1694
1695impl BybitSubMembersPagedResult {
1696 #[must_use]
1703 pub fn continuation_cursor(&self) -> Option<&str> {
1704 match self.next_cursor.as_deref() {
1705 None | Some("" | "0") => None,
1706 Some(cursor) => Some(cursor),
1707 }
1708 }
1709
1710 #[must_use]
1712 pub fn has_more_pages(&self) -> bool {
1713 self.continuation_cursor().is_some()
1714 }
1715}
1716
1717pub type BybitSubMembersPagedResponse = BybitResponse<BybitSubMembersPagedResult>;
1723
1724pub type BybitEscrowSubMembersResponse = BybitResponse<BybitSubMembersPagedResult>;
1731
1732#[derive(Clone, Debug, Serialize, Deserialize)]
1741#[serde(rename_all = "camelCase")]
1742pub struct BybitSubApiKeyInfo {
1743 pub id: String,
1744 #[serde(default)]
1745 pub ips: Vec<String>,
1746 pub api_key: String,
1747 #[serde(default)]
1748 pub note: String,
1749 pub status: i32,
1750 #[serde(default)]
1751 pub expired_at: Option<String>,
1752 pub created_at: String,
1753 #[serde(rename = "type")]
1754 pub key_type: BybitApiKeyType,
1755 #[serde(with = "masked_secret")]
1756 pub secret: Option<String>,
1757 #[serde(with = "bool_or_int")]
1758 pub read_only: bool,
1759 #[serde(default)]
1760 pub deadline_day: Option<i64>,
1761 #[serde(default)]
1762 pub flag: String,
1763 pub permissions: BybitApiKeyPermissions,
1764}
1765
1766#[derive(Clone, Debug, Serialize, Deserialize)]
1772#[serde(rename_all = "camelCase")]
1773pub struct BybitSubApiKeysResult {
1774 #[serde(rename = "result", default)]
1775 pub keys: Vec<BybitSubApiKeyInfo>,
1776 #[serde(default)]
1777 pub next_page_cursor: Option<String>,
1778}
1779
1780impl BybitSubApiKeysResult {
1781 #[must_use]
1787 pub fn continuation_cursor(&self) -> Option<&str> {
1788 match self.next_page_cursor.as_deref() {
1789 None | Some("") => None,
1790 Some(cursor) => Some(cursor),
1791 }
1792 }
1793
1794 #[must_use]
1796 pub fn has_more_pages(&self) -> bool {
1797 self.continuation_cursor().is_some()
1798 }
1799}
1800
1801pub type BybitSubApiKeysResponse = BybitResponse<BybitSubApiKeysResult>;
1807
1808#[derive(Clone, Debug, Serialize, Deserialize)]
1815#[serde(rename_all = "camelCase")]
1816pub struct BybitApiKeyUpdateResult {
1817 pub id: String,
1818 #[serde(default)]
1819 pub note: String,
1820 pub api_key: String,
1821 #[serde(with = "bool_or_int")]
1822 pub read_only: bool,
1823 #[serde(with = "masked_secret")]
1824 pub secret: Option<String>,
1825 pub permissions: BybitApiKeyPermissions,
1826 #[serde(default)]
1827 pub ips: Vec<String>,
1828}
1829
1830pub type BybitUpdateSubApiResponse = BybitResponse<BybitApiKeyUpdateResult>;
1836
1837pub type BybitUpdateMasterApiResponse = BybitResponse<BybitApiKeyUpdateResult>;
1843
1844#[cfg(test)]
1845mod tests {
1846 use nautilus_core::UnixNanos;
1847 use nautilus_model::identifiers::AccountId;
1848 use rstest::rstest;
1849 use rust_decimal::Decimal;
1850 use rust_decimal_macros::dec;
1851
1852 use super::*;
1853 use crate::common::testing::load_test_json;
1854
1855 #[rstest]
1856 fn deserialize_spot_instrument_uses_enums() {
1857 let json = load_test_json("http_get_instruments_spot.json");
1858 let response: BybitInstrumentSpotResponse = serde_json::from_str(&json).unwrap();
1859 let instrument = &response.result.list[0];
1860
1861 assert_eq!(instrument.status, BybitInstrumentStatus::Trading);
1862 assert_eq!(instrument.innovation, BybitInnovationFlag::Standard);
1863 assert_eq!(instrument.margin_trading, BybitMarginTrading::UtaOnly);
1864 }
1865
1866 #[rstest]
1867 fn deserialize_linear_instrument_status() {
1868 let json = load_test_json("http_get_instruments_linear.json");
1869 let response: BybitInstrumentLinearResponse = serde_json::from_str(&json).unwrap();
1870 let instrument = &response.result.list[0];
1871
1872 assert_eq!(instrument.status, BybitInstrumentStatus::Trading);
1873 assert_eq!(instrument.contract_type, BybitContractType::LinearPerpetual);
1874 }
1875
1876 #[rstest]
1877 fn deserialize_spot_instrument_with_xstock_fields() {
1878 let json = load_test_json("http_get_instruments_spot_xstocks.json");
1879 let response: BybitInstrumentSpotResponse = serde_json::from_str(&json).unwrap();
1880 let instrument = &response.result.list[0];
1881
1882 assert_eq!(instrument.symbol_id, Some(42));
1883 assert_eq!(instrument.symbol_type, Some(BybitSymbolType::Xstocks));
1884 assert_eq!(instrument.xstock_multiplier.as_deref(), Some("0.1"));
1885 }
1886
1887 #[rstest]
1888 fn deserialize_linear_instrument_with_symbol_type_and_id() {
1889 let json = load_test_json("http_get_instruments_linear_symbol_type.json");
1890 let response: BybitInstrumentLinearResponse = serde_json::from_str(&json).unwrap();
1891 let instrument = &response.result.list[0];
1892
1893 assert_eq!(instrument.symbol_id, Some(7));
1894 assert_eq!(instrument.symbol_type, Some(BybitSymbolType::Stock));
1895 }
1896
1897 #[derive(Deserialize)]
1898 struct SymbolTypeWrap {
1899 #[serde(rename = "symbolType")]
1900 t: BybitSymbolType,
1901 }
1902
1903 #[rstest]
1904 fn deserialize_symbol_type_falls_back_to_other_for_unknown() {
1905 let json = r#"{"symbolType": "newthing"}"#;
1906 let parsed: SymbolTypeWrap = serde_json::from_str(json).unwrap();
1907 assert_eq!(parsed.t, BybitSymbolType::Other);
1908 }
1909
1910 #[rstest]
1911 fn deserialize_account_info_response() {
1912 let json = load_test_json("http_get_account_info.json");
1913 let response: BybitAccountInfoResponse = serde_json::from_str(&json).unwrap();
1914
1915 assert_eq!(response.result.margin_mode, BybitMarginMode::RegularMargin);
1916 assert_eq!(
1917 response.result.unified_margin_status,
1918 BybitUnifiedMarginStatus::UnifiedTradingAccount10Pro
1919 );
1920 assert!(!response.result.is_master_trader);
1921 assert!(!response.result.spot_hedging_status);
1922 assert!(!response.result.dcp_status);
1923 assert_eq!(response.result.time_window, 10);
1924 assert_eq!(response.result.smp_group, 0);
1925 }
1926
1927 #[rstest]
1928 fn deserialize_account_info_without_deprecated_fields() {
1929 let json = r#"{
1930 "retCode": 0,
1931 "retMsg": "OK",
1932 "result": {
1933 "marginMode": "PORTFOLIO_MARGIN",
1934 "updatedTime": "1697078946000",
1935 "unifiedMarginStatus": 5,
1936 "isMasterTrader": true,
1937 "spotHedgingStatus": "ON"
1938 }
1939 }"#;
1940 let response: BybitAccountInfoResponse = serde_json::from_str(json).unwrap();
1941
1942 assert_eq!(
1943 response.result.margin_mode,
1944 BybitMarginMode::PortfolioMargin
1945 );
1946 assert_eq!(
1947 response.result.unified_margin_status,
1948 BybitUnifiedMarginStatus::UnifiedTradingAccount20
1949 );
1950 assert!(response.result.is_master_trader);
1951 assert!(response.result.spot_hedging_status);
1952 assert!(!response.result.dcp_status);
1953 assert_eq!(response.result.time_window, 0);
1954 assert_eq!(response.result.smp_group, 0);
1955 }
1956
1957 #[rstest]
1958 fn deserialize_order_response_maps_enums() {
1959 let json = load_test_json("http_get_orders_history.json");
1960 let response: BybitOrderHistoryResponse = serde_json::from_str(&json).unwrap();
1961 let order = &response.result.list[0];
1962
1963 assert_eq!(order.cancel_type, BybitCancelType::CancelByUser);
1964 assert_eq!(order.tp_trigger_by, BybitTriggerType::MarkPrice);
1965 assert_eq!(order.sl_trigger_by, BybitTriggerType::LastPrice);
1966 assert_eq!(order.tpsl_mode, Some(BybitTpSlMode::Full));
1967 assert_eq!(order.order_type, BybitOrderType::Limit);
1968 assert_eq!(order.smp_type, BybitSmpType::None);
1969 }
1970
1971 #[rstest]
1972 fn deserialize_wallet_balance_without_optional_fields() {
1973 let json = r#"{
1974 "retCode": 0,
1975 "retMsg": "OK",
1976 "result": {
1977 "list": [{
1978 "totalEquity": "1000.00",
1979 "accountIMRate": "0",
1980 "totalMarginBalance": "1000.00",
1981 "totalInitialMargin": "0",
1982 "accountType": "UNIFIED",
1983 "totalAvailableBalance": "1000.00",
1984 "accountMMRate": "0",
1985 "totalPerpUPL": "0",
1986 "totalWalletBalance": "1000.00",
1987 "accountLTV": "0",
1988 "totalMaintenanceMargin": "0",
1989 "coin": [{
1990 "availableToBorrow": "0",
1991 "bonus": "0",
1992 "accruedInterest": "0",
1993 "availableToWithdraw": "1000.00",
1994 "equity": "1000.00",
1995 "usdValue": "1000.00",
1996 "borrowAmount": "0",
1997 "totalPositionIM": "0",
1998 "walletBalance": "1000.00",
1999 "unrealisedPnl": "0",
2000 "cumRealisedPnl": "0",
2001 "locked": "0",
2002 "collateralSwitch": true,
2003 "marginCollateral": true,
2004 "coin": "USDT"
2005 }]
2006 }]
2007 }
2008 }"#;
2009
2010 let response: BybitWalletBalanceResponse = serde_json::from_str(json)
2011 .expect("Failed to parse wallet balance without optional fields");
2012
2013 assert_eq!(response.ret_code, 0);
2014 assert_eq!(response.result.list[0].coin[0].total_order_im, None);
2015 assert_eq!(response.result.list[0].coin[0].total_position_mm, None);
2016 }
2017
2018 #[rstest]
2019 fn deserialize_wallet_balance_from_docs() {
2020 let json = include_str!("../../test_data/http_get_wallet_balance.json");
2021
2022 let response: BybitWalletBalanceResponse = serde_json::from_str(json)
2023 .expect("Failed to parse wallet balance from Bybit docs example");
2024
2025 assert_eq!(response.ret_code, 0);
2026 assert_eq!(response.ret_msg, "OK");
2027
2028 let wallet = &response.result.list[0];
2029 assert_eq!(wallet.total_equity, "3.31216591");
2030 assert_eq!(wallet.account_im_rate, "0");
2031 assert_eq!(wallet.account_mm_rate, "0");
2032 assert_eq!(wallet.total_perp_upl, "0");
2033 assert_eq!(wallet.account_ltv, "0");
2034
2035 let btc = &wallet.coin[0];
2037 assert_eq!(btc.coin.as_str(), "BTC");
2038 assert_eq!(btc.available_to_borrow, "3");
2039 assert_eq!(btc.total_order_im, Some("0".to_string()));
2040 assert_eq!(btc.total_position_mm, Some("0".to_string()));
2041 assert_eq!(btc.total_position_im, Some("0".to_string()));
2042
2043 let usdt = &wallet.coin[1];
2045 assert_eq!(usdt.coin.as_str(), "USDT");
2046 assert_eq!(usdt.wallet_balance, dec!(1000.50));
2047 assert_eq!(usdt.total_order_im, None);
2048 assert_eq!(usdt.total_position_mm, None);
2049 assert_eq!(usdt.total_position_im, None);
2050 assert_eq!(btc.spot_borrow, Decimal::ZERO);
2051 assert_eq!(usdt.spot_borrow, Decimal::ZERO);
2052 }
2053
2054 #[rstest]
2055 fn test_parse_wallet_balance_with_spot_borrow() {
2056 let json = include_str!("../../test_data/http_get_wallet_balance_with_spot_borrow.json");
2057 let response: BybitWalletBalanceResponse =
2058 serde_json::from_str(json).expect("Failed to parse wallet balance with spotBorrow");
2059
2060 let wallet = &response.result.list[0];
2061 let usdt = &wallet.coin[0];
2062
2063 assert_eq!(usdt.coin.as_str(), "USDT");
2064 assert_eq!(usdt.wallet_balance, dec!(1200.00));
2065 assert_eq!(usdt.spot_borrow, dec!(200.00));
2066 assert_eq!(usdt.borrow_amount, "200.00");
2067
2068 let account_id = crate::common::parse::parse_account_state(
2070 wallet,
2071 AccountId::new("BYBIT-001"),
2072 UnixNanos::default(),
2073 )
2074 .expect("Failed to parse account state");
2075
2076 let balance = &account_id.balances[0];
2077 assert_eq!(balance.total.as_f64(), 1000.0);
2078 }
2079
2080 #[rstest]
2081 fn test_parse_wallet_balance_spot_short() {
2082 let json = include_str!("../../test_data/http_get_wallet_balance_spot_short.json");
2083 let response: BybitWalletBalanceResponse = serde_json::from_str(json)
2084 .expect("Failed to parse wallet balance with SHORT SPOT position");
2085
2086 let wallet = &response.result.list[0];
2087 let eth = &wallet.coin[0];
2088
2089 assert_eq!(eth.coin.as_str(), "ETH");
2090 assert_eq!(eth.wallet_balance, dec!(0));
2091 assert_eq!(eth.spot_borrow, dec!(0.06142));
2092 assert_eq!(eth.borrow_amount, "0.06142");
2093
2094 let account_state = crate::common::parse::parse_account_state(
2095 wallet,
2096 AccountId::new("BYBIT-001"),
2097 UnixNanos::default(),
2098 )
2099 .expect("Failed to parse account state");
2100
2101 let eth_balance = account_state
2102 .balances
2103 .iter()
2104 .find(|b| b.currency.code.as_str() == "ETH")
2105 .expect("ETH balance not found");
2106
2107 assert_eq!(eth_balance.total.as_f64(), -0.06142);
2109 }
2110
2111 #[rstest]
2112 fn deserialize_borrow_response() {
2113 let json = r#"{
2114 "retCode": 0,
2115 "retMsg": "success",
2116 "result": {
2117 "coin": "BTC",
2118 "amount": "0.01"
2119 },
2120 "retExtInfo": {},
2121 "time": 1756197991955
2122 }"#;
2123
2124 let response: BybitBorrowResponse = serde_json::from_str(json).unwrap();
2125
2126 assert_eq!(response.ret_code, 0);
2127 assert_eq!(response.ret_msg, "success");
2128 assert_eq!(response.result.coin, "BTC");
2129 assert_eq!(response.result.amount, "0.01");
2130 }
2131
2132 #[rstest]
2133 fn deserialize_no_convert_repay_response() {
2134 let json = r#"{
2135 "retCode": 0,
2136 "retMsg": "OK",
2137 "result": {
2138 "resultStatus": "SU"
2139 },
2140 "retExtInfo": {},
2141 "time": 1234567890
2142 }"#;
2143
2144 let response: BybitNoConvertRepayResponse = serde_json::from_str(json).unwrap();
2145
2146 assert_eq!(response.ret_code, 0);
2147 assert_eq!(response.ret_msg, "OK");
2148 assert_eq!(response.result.result_status, "SU");
2149 }
2150
2151 #[rstest]
2152 fn deserialize_position_without_conditional_fields() {
2153 let json = r#"{
2157 "retCode": 0,
2158 "retMsg": "OK",
2159 "result": {
2160 "list": [{
2161 "positionIdx": 0,
2162 "riskId": 1,
2163 "riskLimitValue": "150",
2164 "symbol": "LTCUSDT",
2165 "side": "",
2166 "size": "0",
2167 "avgPrice": "0",
2168 "positionValue": "0",
2169 "tradeMode": 0,
2170 "positionStatus": "Normal",
2171 "autoAddMargin": 0,
2172 "adlRankIndicator": 0,
2173 "leverage": "10",
2174 "positionBalance": "0",
2175 "markPrice": "70.00",
2176 "liqPrice": "",
2177 "bustPrice": "",
2178 "positionMM": "0",
2179 "positionIM": "0",
2180 "tpslMode": "Full",
2181 "takeProfit": "0",
2182 "stopLoss": "0",
2183 "trailingStop": "0",
2184 "unrealisedPnl": "0",
2185 "curRealisedPnl": "0",
2186 "cumRealisedPnl": "0",
2187 "createdTime": "1676538056258",
2188 "updatedTime": "1697673600012"
2189 }],
2190 "nextPageCursor": "",
2191 "category": "linear"
2192 },
2193 "retExtInfo": {},
2194 "time": 1697673900000
2195 }"#;
2196
2197 let response: BybitPositionListResponse = serde_json::from_str(json)
2198 .expect("Failed to parse position list with missing conditional fields");
2199
2200 let position = &response.result.list[0];
2201 assert!(!position.is_reduce_only);
2202 assert_eq!(position.seq, -1);
2203 assert_eq!(position.mmr_sys_updated_time, "");
2204 assert_eq!(position.leverage_sys_updated_time, "");
2205 assert_eq!(position.open_time, 0);
2206 }
2207
2208 #[rstest]
2209 #[case(0_i64)]
2210 #[case(1_700_000_000_123_i64)]
2211 fn deserialize_position_with_open_time_integer(#[case] expected: i64) {
2212 let mut value: serde_json::Value =
2214 serde_json::from_str(&load_test_json("http_get_positions_with_open_time.json"))
2215 .unwrap();
2216 value["result"]["list"][0]["openTime"] = serde_json::json!(expected);
2217
2218 let response: BybitPositionListResponse = serde_json::from_value(value)
2219 .expect("Failed to parse position list with integer openTime");
2220
2221 assert_eq!(response.result.list[0].open_time, expected);
2222 }
2223
2224 #[rstest]
2225 fn deserialize_inverse_instrument_with_symbol_type_and_id() {
2226 let json = load_test_json("http_get_instruments_inverse_symbol_type.json");
2227 let response: BybitInstrumentInverseResponse = serde_json::from_str(&json).unwrap();
2228 let instrument = &response.result.list[0];
2229
2230 assert_eq!(instrument.symbol_id, Some(11));
2231 assert_eq!(instrument.symbol_type, Some(BybitSymbolType::Commodity));
2232 }
2233
2234 #[rstest]
2235 fn deserialize_option_instrument_with_symbol_id() {
2236 let json = load_test_json("http_get_instruments_option_symbol_id.json");
2237 let response: BybitInstrumentOptionResponse = serde_json::from_str(&json).unwrap();
2238 let instrument = &response.result.list[0];
2239
2240 assert_eq!(instrument.symbol_id, Some(99));
2241 }
2242
2243 #[rstest]
2244 fn deserialize_sub_members_response() {
2245 let json = load_test_json("http_get_user_sub_members.json");
2246 let response: BybitSubMembersResponse =
2247 serde_json::from_str(&json).expect("parse sub members");
2248 assert_eq!(response.ret_code, 0);
2249 assert_eq!(response.result.sub_members.len(), 2);
2250 let first = &response.result.sub_members[0];
2251 assert_eq!(first.uid, "106314365");
2252 assert_eq!(first.username, "xxxx02");
2253 assert_eq!(first.member_type, 1);
2254 assert_eq!(first.status, 1);
2255 assert_eq!(first.account_mode, 5);
2256 assert_eq!(first.remark, "");
2257 let second = &response.result.sub_members[1];
2258 assert_eq!(second.uid, "106279879");
2259 assert_eq!(second.account_mode, 6);
2260 }
2261
2262 #[rstest]
2263 fn deserialize_sub_members_paged_response() {
2264 let json = load_test_json("http_get_user_sub_members_paged.json");
2267 let response: BybitSubMembersPagedResponse =
2268 serde_json::from_str(&json).expect("parse paged sub members");
2269 assert_eq!(response.result.sub_members.len(), 2);
2270 assert_eq!(response.result.next_cursor.as_deref(), Some("0"));
2271 assert!(!response.result.has_more_pages());
2272 assert_eq!(response.result.continuation_cursor(), None);
2273 }
2274
2275 #[rstest]
2276 fn deserialize_escrow_sub_members_response_uses_same_shape() {
2277 let json = load_test_json("http_get_user_escrow_sub_members.json");
2280 let response: BybitEscrowSubMembersResponse =
2281 serde_json::from_str(&json).expect("parse escrow sub members");
2282 assert_eq!(response.result.sub_members.len(), 2);
2283 assert_eq!(response.result.sub_members[0].member_type, 12);
2284 assert_eq!(response.result.sub_members[0].remark, "earn fund");
2285 assert_eq!(response.result.next_cursor.as_deref(), Some("344"));
2286 assert!(response.result.has_more_pages());
2287 assert_eq!(response.result.continuation_cursor(), Some("344"));
2288 }
2289
2290 #[rstest]
2291 fn deserialize_sub_api_keys_response() {
2292 let json = load_test_json("http_get_user_sub_apikeys.json");
2295 let response: BybitSubApiKeysResponse =
2296 serde_json::from_str(&json).expect("parse sub apikeys");
2297 assert_eq!(response.result.keys.len(), 1);
2298 let key = &response.result.keys[0];
2299 assert!(!key.read_only);
2300 assert_eq!(key.secret, None);
2301 assert_eq!(key.key_type, BybitApiKeyType::Hmac);
2302 assert_eq!(key.flag, "hmac");
2303 assert_eq!(key.deadline_day, Some(21));
2304 assert_eq!(key.permissions.contract_trade, vec!["Order", "Position"]);
2305 assert_eq!(key.permissions.spot, vec!["SpotTrade"]);
2306 assert!(key.permissions.earn.is_empty());
2307 assert_eq!(response.result.next_page_cursor.as_deref(), Some(""));
2308 assert!(!response.result.has_more_pages());
2309 }
2310
2311 #[rstest]
2312 fn deserialize_update_sub_api_response() {
2313 let json = load_test_json("http_post_user_update_sub_api.json");
2314 let response: BybitUpdateSubApiResponse =
2315 serde_json::from_str(&json).expect("parse update sub api");
2316 assert!(!response.result.read_only);
2317 assert_eq!(response.result.secret, None);
2318 assert_eq!(response.result.ips, vec!["*"]);
2319 assert_eq!(response.result.permissions.spot, vec!["SpotTrade"]);
2320 assert_eq!(response.result.permissions.wallet, vec!["AccountTransfer"]);
2321 }
2322
2323 #[rstest]
2324 fn deserialize_update_master_api_response() {
2325 let json = load_test_json("http_post_user_update_master_api.json");
2330 let response: BybitUpdateMasterApiResponse =
2331 serde_json::from_str(&json).expect("parse update master api");
2332 assert!(!response.result.read_only);
2333 assert_eq!(response.result.ips, vec!["*"]);
2334 let perms = &response.result.permissions;
2335 assert_eq!(perms.contract_trade, vec!["Order", "Position"]);
2336 assert_eq!(perms.copy_trading, vec!["CopyTrading"]);
2337 assert!(perms.earn.is_empty());
2338 assert_eq!(perms.nft, vec!["NFTQueryProductList"]);
2339 }
2340
2341 #[rstest]
2342 fn deserialize_permissions_renamed_buckets_preserve_values() {
2343 let json = r#"{
2349 "NFT": ["NFTQueryProductList"],
2350 "FiatP2P": ["P2PDeposit"],
2351 "ByXPost": ["PostContent"]
2352 }"#;
2353 let perms: BybitApiKeyPermissions =
2354 serde_json::from_str(json).expect("parse renamed buckets");
2355 assert_eq!(perms.nft, vec!["NFTQueryProductList"]);
2356 assert_eq!(perms.fiat_p2p, vec!["P2PDeposit"]);
2357 assert_eq!(perms.byx_post, vec!["PostContent"]);
2358 }
2359
2360 #[rstest]
2361 fn deserialize_account_details_response_with_current_docs_example() {
2362 let json = load_test_json("http_get_user_query_api.json");
2363 let response: BybitAccountDetailsResponse =
2364 serde_json::from_str(&json).expect("parse account details");
2365
2366 assert_eq!(
2367 response.result.permissions.fiat_global_pay,
2368 Vec::<String>::new()
2369 );
2370 assert_eq!(
2371 response.result.permissions.fiat_bit_pay,
2372 vec!["FaitPayOrder"]
2373 );
2374 assert_eq!(response.result.permissions.bit_card, vec!["BitCard"]);
2375 assert_eq!(response.result.permissions.byx_post, vec!["ByXPost"]);
2376 assert_eq!(response.result.unified, Some(0));
2377 }
2378}