1use rust_decimal::Decimal;
19use serde::{Deserialize, Serialize};
20use ustr::Ustr;
21
22use crate::common::{
23 enums::{
24 BybitAccountType, BybitCancelType, BybitContractType, BybitExecType, BybitInnovationFlag,
25 BybitInstrumentStatus, BybitMarginTrading, BybitOptionType, BybitOrderSide,
26 BybitOrderStatus, BybitOrderType, BybitPositionIdx, BybitPositionSide, BybitProductType,
27 BybitStopOrderType, BybitTimeInForce, BybitTpSlMode, BybitTriggerDirection,
28 BybitTriggerType,
29 },
30 models::{
31 BybitCursorList, BybitCursorListResponse, BybitListResponse, BybitResponse, LeverageFilter,
32 LinearLotSizeFilter, LinearPriceFilter, OptionLotSizeFilter, SpotLotSizeFilter,
33 SpotPriceFilter,
34 },
35 parse::{
36 deserialize_decimal_or_zero, deserialize_optional_decimal_or_zero, deserialize_string_to_u8,
37 },
38};
39
40#[derive(Clone, Debug, Default, Serialize, Deserialize)]
42#[cfg_attr(
43 feature = "python",
44 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bybit")
45)]
46pub struct BybitOrderCursorList {
47 pub list: Vec<BybitOrder>,
49 pub next_page_cursor: Option<String>,
51 #[serde(default)]
53 pub category: Option<BybitProductType>,
54}
55
56impl From<BybitCursorList<BybitOrder>> for BybitOrderCursorList {
57 fn from(cursor_list: BybitCursorList<BybitOrder>) -> Self {
58 Self {
59 list: cursor_list.list,
60 next_page_cursor: cursor_list.next_page_cursor,
61 category: cursor_list.category,
62 }
63 }
64}
65
66#[cfg(feature = "python")]
67#[pyo3::pymethods]
68impl BybitOrderCursorList {
69 #[getter]
70 #[must_use]
71 pub fn list(&self) -> Vec<BybitOrder> {
72 self.list.clone()
73 }
74
75 #[getter]
76 #[must_use]
77 pub fn next_page_cursor(&self) -> Option<&str> {
78 self.next_page_cursor.as_deref()
79 }
80
81 #[getter]
82 #[must_use]
83 pub fn category(&self) -> Option<BybitProductType> {
84 self.category
85 }
86}
87
88#[derive(Clone, Debug, Serialize, Deserialize)]
93#[cfg_attr(
94 feature = "python",
95 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bybit")
96)]
97#[serde(rename_all = "camelCase")]
98pub struct BybitServerTime {
99 pub time_second: String,
101 pub time_nano: String,
103}
104
105#[cfg(feature = "python")]
106#[pyo3::pymethods]
107impl BybitServerTime {
108 #[getter]
109 #[must_use]
110 pub fn time_second(&self) -> &str {
111 &self.time_second
112 }
113
114 #[getter]
115 #[must_use]
116 pub fn time_nano(&self) -> &str {
117 &self.time_nano
118 }
119}
120
121pub type BybitServerTimeResponse = BybitResponse<BybitServerTime>;
126
127#[derive(Clone, Debug, Serialize, Deserialize)]
132#[serde(rename_all = "camelCase")]
133pub struct BybitTickerSpot {
134 pub symbol: Ustr,
135 pub bid1_price: String,
136 pub bid1_size: String,
137 pub ask1_price: String,
138 pub ask1_size: String,
139 pub last_price: String,
140 pub prev_price24h: String,
141 pub price24h_pcnt: String,
142 pub high_price24h: String,
143 pub low_price24h: String,
144 pub turnover24h: String,
145 pub volume24h: String,
146 #[serde(default)]
147 pub usd_index_price: String,
148}
149
150#[derive(Clone, Debug, Serialize, Deserialize)]
155#[serde(rename_all = "camelCase")]
156pub struct BybitTickerLinear {
157 pub symbol: Ustr,
158 pub last_price: String,
159 pub index_price: String,
160 pub mark_price: String,
161 pub prev_price24h: String,
162 pub price24h_pcnt: String,
163 pub high_price24h: String,
164 pub low_price24h: String,
165 pub prev_price1h: String,
166 pub open_interest: String,
167 pub open_interest_value: String,
168 pub turnover24h: String,
169 pub volume24h: String,
170 pub funding_rate: String,
171 pub next_funding_time: String,
172 pub predicted_delivery_price: String,
173 pub basis_rate: String,
174 pub delivery_fee_rate: String,
175 pub delivery_time: String,
176 pub ask1_size: String,
177 pub bid1_price: String,
178 pub ask1_price: String,
179 pub bid1_size: String,
180 pub basis: String,
181}
182
183#[derive(Clone, Debug, Serialize, Deserialize)]
188#[serde(rename_all = "camelCase")]
189pub struct BybitTickerOption {
190 pub symbol: Ustr,
191 pub bid1_price: String,
192 pub bid1_size: String,
193 pub bid1_iv: String,
194 pub ask1_price: String,
195 pub ask1_size: String,
196 pub ask1_iv: String,
197 pub last_price: String,
198 pub high_price24h: String,
199 pub low_price24h: String,
200 pub mark_price: String,
201 pub index_price: String,
202 pub mark_iv: String,
203 pub underlying_price: String,
204 pub open_interest: String,
205 pub turnover24h: String,
206 pub volume24h: String,
207 pub total_volume: String,
208 pub total_turnover: String,
209 pub delta: String,
210 pub gamma: String,
211 pub vega: String,
212 pub theta: String,
213 pub predicted_delivery_price: String,
214 pub change24h: String,
215}
216
217pub type BybitTickersSpotResponse = BybitListResponse<BybitTickerSpot>;
222pub type BybitTickersLinearResponse = BybitListResponse<BybitTickerLinear>;
227pub type BybitTickersOptionResponse = BybitListResponse<BybitTickerOption>;
232
233#[derive(Clone, Debug, Serialize, Deserialize)]
238#[serde(rename_all = "camelCase")]
239#[cfg_attr(
240 feature = "python",
241 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bybit")
242)]
243pub struct BybitTickerData {
244 pub symbol: Ustr,
245 pub bid1_price: String,
246 pub bid1_size: String,
247 pub ask1_price: String,
248 pub ask1_size: String,
249 pub last_price: String,
250 pub high_price24h: String,
251 pub low_price24h: String,
252 pub turnover24h: String,
253 pub volume24h: String,
254 #[serde(default)]
255 pub open_interest: Option<String>,
256 #[serde(default)]
257 pub funding_rate: Option<String>,
258 #[serde(default)]
259 pub next_funding_time: Option<String>,
260 #[serde(default)]
261 pub mark_price: Option<String>,
262 #[serde(default)]
263 pub index_price: Option<String>,
264}
265
266#[cfg(feature = "python")]
267#[pyo3::pymethods]
268impl BybitTickerData {
269 #[getter]
270 #[must_use]
271 pub fn symbol(&self) -> &str {
272 self.symbol.as_str()
273 }
274
275 #[getter]
276 #[must_use]
277 pub fn bid1_price(&self) -> &str {
278 &self.bid1_price
279 }
280
281 #[getter]
282 #[must_use]
283 pub fn bid1_size(&self) -> &str {
284 &self.bid1_size
285 }
286
287 #[getter]
288 #[must_use]
289 pub fn ask1_price(&self) -> &str {
290 &self.ask1_price
291 }
292
293 #[getter]
294 #[must_use]
295 pub fn ask1_size(&self) -> &str {
296 &self.ask1_size
297 }
298
299 #[getter]
300 #[must_use]
301 pub fn last_price(&self) -> &str {
302 &self.last_price
303 }
304
305 #[getter]
306 #[must_use]
307 pub fn high_price24h(&self) -> &str {
308 &self.high_price24h
309 }
310
311 #[getter]
312 #[must_use]
313 pub fn low_price24h(&self) -> &str {
314 &self.low_price24h
315 }
316
317 #[getter]
318 #[must_use]
319 pub fn turnover24h(&self) -> &str {
320 &self.turnover24h
321 }
322
323 #[getter]
324 #[must_use]
325 pub fn volume24h(&self) -> &str {
326 &self.volume24h
327 }
328
329 #[getter]
330 #[must_use]
331 pub fn open_interest(&self) -> Option<&str> {
332 self.open_interest.as_deref()
333 }
334
335 #[getter]
336 #[must_use]
337 pub fn funding_rate(&self) -> Option<&str> {
338 self.funding_rate.as_deref()
339 }
340
341 #[getter]
342 #[must_use]
343 pub fn next_funding_time(&self) -> Option<&str> {
344 self.next_funding_time.as_deref()
345 }
346
347 #[getter]
348 #[must_use]
349 pub fn mark_price(&self) -> Option<&str> {
350 self.mark_price.as_deref()
351 }
352
353 #[getter]
354 #[must_use]
355 pub fn index_price(&self) -> Option<&str> {
356 self.index_price.as_deref()
357 }
358}
359
360impl From<BybitTickerSpot> for BybitTickerData {
361 fn from(ticker: BybitTickerSpot) -> Self {
362 Self {
363 symbol: ticker.symbol,
364 bid1_price: ticker.bid1_price,
365 bid1_size: ticker.bid1_size,
366 ask1_price: ticker.ask1_price,
367 ask1_size: ticker.ask1_size,
368 last_price: ticker.last_price,
369 high_price24h: ticker.high_price24h,
370 low_price24h: ticker.low_price24h,
371 turnover24h: ticker.turnover24h,
372 volume24h: ticker.volume24h,
373 open_interest: None,
374 funding_rate: None,
375 next_funding_time: None,
376 mark_price: None,
377 index_price: None,
378 }
379 }
380}
381
382impl From<BybitTickerLinear> for BybitTickerData {
383 fn from(ticker: BybitTickerLinear) -> Self {
384 Self {
385 symbol: ticker.symbol,
386 bid1_price: ticker.bid1_price,
387 bid1_size: ticker.bid1_size,
388 ask1_price: ticker.ask1_price,
389 ask1_size: ticker.ask1_size,
390 last_price: ticker.last_price,
391 high_price24h: ticker.high_price24h,
392 low_price24h: ticker.low_price24h,
393 turnover24h: ticker.turnover24h,
394 volume24h: ticker.volume24h,
395 open_interest: Some(ticker.open_interest),
396 funding_rate: Some(ticker.funding_rate),
397 next_funding_time: Some(ticker.next_funding_time),
398 mark_price: Some(ticker.mark_price),
399 index_price: Some(ticker.index_price),
400 }
401 }
402}
403
404impl From<BybitTickerOption> for BybitTickerData {
405 fn from(ticker: BybitTickerOption) -> Self {
406 Self {
407 symbol: ticker.symbol,
408 bid1_price: ticker.bid1_price,
409 bid1_size: ticker.bid1_size,
410 ask1_price: ticker.ask1_price,
411 ask1_size: ticker.ask1_size,
412 last_price: ticker.last_price,
413 high_price24h: ticker.high_price24h,
414 low_price24h: ticker.low_price24h,
415 turnover24h: ticker.turnover24h,
416 volume24h: ticker.volume24h,
417 open_interest: Some(ticker.open_interest),
418 funding_rate: None,
419 next_funding_time: None,
420 mark_price: Some(ticker.mark_price),
421 index_price: Some(ticker.index_price),
422 }
423 }
424}
425
426#[derive(Clone, Debug, Serialize)]
434pub struct BybitKline {
435 pub start: String,
436 pub open: String,
437 pub high: String,
438 pub low: String,
439 pub close: String,
440 pub volume: String,
441 pub turnover: String,
442}
443
444impl<'de> Deserialize<'de> for BybitKline {
445 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
446 where
447 D: serde::Deserializer<'de>,
448 {
449 let [start, open, high, low, close, volume, turnover]: [String; 7] =
450 Deserialize::deserialize(deserializer)?;
451 Ok(Self {
452 start,
453 open,
454 high,
455 low,
456 close,
457 volume,
458 turnover,
459 })
460 }
461}
462
463#[derive(Clone, Debug, Serialize, Deserialize)]
468#[serde(rename_all = "camelCase")]
469pub struct BybitKlineResult {
470 pub category: BybitProductType,
471 pub symbol: Ustr,
472 pub list: Vec<BybitKline>,
473}
474
475pub type BybitKlinesResponse = BybitResponse<BybitKlineResult>;
480
481#[derive(Clone, Debug, Serialize, Deserialize)]
486#[serde(rename_all = "camelCase")]
487pub struct BybitTrade {
488 pub exec_id: String,
489 pub symbol: Ustr,
490 pub price: String,
491 pub size: String,
492 pub side: BybitOrderSide,
493 pub time: String,
494 pub is_block_trade: bool,
495 #[serde(default)]
496 pub m_p: Option<String>,
497 #[serde(default)]
498 pub i_p: Option<String>,
499 #[serde(default)]
500 pub mlv: Option<String>,
501 #[serde(default)]
502 pub iv: Option<String>,
503}
504
505#[derive(Clone, Debug, Serialize, Deserialize)]
510#[serde(rename_all = "camelCase")]
511pub struct BybitTradeResult {
512 pub category: BybitProductType,
513 pub list: Vec<BybitTrade>,
514}
515
516pub type BybitTradesResponse = BybitResponse<BybitTradeResult>;
521
522#[derive(Clone, Debug, Serialize, Deserialize)]
527#[serde(rename_all = "camelCase")]
528pub struct BybitFunding {
529 pub symbol: Ustr,
530 pub funding_rate: String,
531 pub funding_rate_timestamp: String,
532}
533
534#[derive(Clone, Debug, Serialize, Deserialize)]
539#[serde(rename_all = "camelCase")]
540pub struct BybitFundingResult {
541 pub category: BybitProductType,
542 pub list: Vec<BybitFunding>,
543}
544
545pub type BybitFundingResponse = BybitResponse<BybitFundingResult>;
550
551#[derive(Clone, Debug, Serialize, Deserialize)]
556#[serde(rename_all = "camelCase")]
557pub struct BybitOrderbookResult {
558 pub s: Ustr,
560 pub b: Vec<[String; 2]>,
562 pub a: Vec<[String; 2]>,
564 pub ts: i64,
565 pub u: i64,
567 pub seq: i64,
569 pub cts: i64,
570}
571
572pub type BybitOrderbookResponse = BybitResponse<BybitOrderbookResult>;
577
578#[derive(Clone, Debug, Serialize, Deserialize)]
583#[serde(rename_all = "camelCase")]
584pub struct BybitInstrumentSpot {
585 pub symbol: Ustr,
586 pub base_coin: Ustr,
587 pub quote_coin: Ustr,
588 pub innovation: BybitInnovationFlag,
589 pub status: BybitInstrumentStatus,
590 pub margin_trading: BybitMarginTrading,
591 pub lot_size_filter: SpotLotSizeFilter,
592 pub price_filter: SpotPriceFilter,
593}
594
595#[derive(Clone, Debug, Serialize, Deserialize)]
600#[serde(rename_all = "camelCase")]
601pub struct BybitInstrumentLinear {
602 pub symbol: Ustr,
603 pub contract_type: BybitContractType,
604 pub status: BybitInstrumentStatus,
605 pub base_coin: Ustr,
606 pub quote_coin: Ustr,
607 pub launch_time: String,
608 pub delivery_time: String,
609 pub delivery_fee_rate: String,
610 pub price_scale: String,
611 pub leverage_filter: LeverageFilter,
612 pub price_filter: LinearPriceFilter,
613 pub lot_size_filter: LinearLotSizeFilter,
614 pub unified_margin_trade: bool,
615 pub funding_interval: i64,
616 pub settle_coin: Ustr,
617}
618
619#[derive(Clone, Debug, Serialize, Deserialize)]
624#[serde(rename_all = "camelCase")]
625pub struct BybitInstrumentInverse {
626 pub symbol: Ustr,
627 pub contract_type: BybitContractType,
628 pub status: BybitInstrumentStatus,
629 pub base_coin: Ustr,
630 pub quote_coin: Ustr,
631 pub launch_time: String,
632 pub delivery_time: String,
633 pub delivery_fee_rate: String,
634 pub price_scale: String,
635 pub leverage_filter: LeverageFilter,
636 pub price_filter: LinearPriceFilter,
637 pub lot_size_filter: LinearLotSizeFilter,
638 pub unified_margin_trade: bool,
639 pub funding_interval: i64,
640 pub settle_coin: Ustr,
641}
642
643#[derive(Clone, Debug, Serialize, Deserialize)]
648#[serde(rename_all = "camelCase")]
649pub struct BybitInstrumentOption {
650 pub symbol: Ustr,
651 pub status: BybitInstrumentStatus,
652 pub base_coin: Ustr,
653 pub quote_coin: Ustr,
654 pub settle_coin: Ustr,
655 pub options_type: BybitOptionType,
656 pub launch_time: String,
657 pub delivery_time: String,
658 pub delivery_fee_rate: String,
659 pub price_filter: LinearPriceFilter,
660 pub lot_size_filter: OptionLotSizeFilter,
661}
662
663pub type BybitInstrumentSpotResponse = BybitCursorListResponse<BybitInstrumentSpot>;
668pub type BybitInstrumentLinearResponse = BybitCursorListResponse<BybitInstrumentLinear>;
673pub type BybitInstrumentInverseResponse = BybitCursorListResponse<BybitInstrumentInverse>;
678pub type BybitInstrumentOptionResponse = BybitCursorListResponse<BybitInstrumentOption>;
683
684#[derive(Clone, Debug, Serialize, Deserialize)]
689#[serde(rename_all = "camelCase")]
690#[cfg_attr(
691 feature = "python",
692 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bybit")
693)]
694pub struct BybitFeeRate {
695 pub symbol: Ustr,
696 pub taker_fee_rate: String,
697 pub maker_fee_rate: String,
698 #[serde(default)]
699 pub base_coin: Option<Ustr>,
700}
701
702#[cfg(feature = "python")]
703#[pyo3::pymethods]
704impl BybitFeeRate {
705 #[getter]
706 #[must_use]
707 pub fn symbol(&self) -> &str {
708 self.symbol.as_str()
709 }
710
711 #[getter]
712 #[must_use]
713 pub fn taker_fee_rate(&self) -> &str {
714 &self.taker_fee_rate
715 }
716
717 #[getter]
718 #[must_use]
719 pub fn maker_fee_rate(&self) -> &str {
720 &self.maker_fee_rate
721 }
722
723 #[getter]
724 #[must_use]
725 pub fn base_coin(&self) -> Option<&str> {
726 self.base_coin.as_ref().map(|u| u.as_str())
727 }
728}
729
730pub type BybitFeeRateResponse = BybitListResponse<BybitFeeRate>;
735
736#[derive(Clone, Debug, Serialize, Deserialize)]
741#[serde(rename_all = "camelCase")]
742pub struct BybitCoinBalance {
743 pub available_to_borrow: String,
744 pub bonus: String,
745 pub accrued_interest: String,
746 pub available_to_withdraw: String,
747 #[serde(default, rename = "totalOrderIM")]
748 pub total_order_im: Option<String>,
749 pub equity: String,
750 pub usd_value: String,
751 pub borrow_amount: String,
752 #[serde(default, rename = "totalPositionMM")]
753 pub total_position_mm: Option<String>,
754 #[serde(default, rename = "totalPositionIM")]
755 pub total_position_im: Option<String>,
756 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
757 pub wallet_balance: Decimal,
758 pub unrealised_pnl: String,
759 pub cum_realised_pnl: String,
760 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
761 pub locked: Decimal,
762 pub collateral_switch: bool,
763 pub margin_collateral: bool,
764 pub coin: Ustr,
765 #[serde(default)]
766 pub spot_hedging_qty: Option<String>,
767 #[serde(default, deserialize_with = "deserialize_optional_decimal_or_zero")]
768 pub spot_borrow: Decimal,
769}
770
771#[derive(Clone, Debug, Serialize, Deserialize)]
776#[serde(rename_all = "camelCase")]
777pub struct BybitWalletBalance {
778 pub total_equity: String,
779 #[serde(rename = "accountIMRate")]
780 pub account_im_rate: String,
781 pub total_margin_balance: String,
782 pub total_initial_margin: String,
783 pub account_type: BybitAccountType,
784 pub total_available_balance: String,
785 #[serde(rename = "accountMMRate")]
786 pub account_mm_rate: String,
787 #[serde(rename = "totalPerpUPL")]
788 pub total_perp_upl: String,
789 pub total_wallet_balance: String,
790 #[serde(rename = "accountLTV")]
791 pub account_ltv: String,
792 pub total_maintenance_margin: String,
793 pub coin: Vec<BybitCoinBalance>,
794}
795
796pub type BybitWalletBalanceResponse = BybitListResponse<BybitWalletBalance>;
801
802#[derive(Clone, Debug, Serialize, Deserialize)]
807#[cfg_attr(
808 feature = "python",
809 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bybit")
810)]
811#[serde(rename_all = "camelCase")]
812pub struct BybitOrder {
813 pub order_id: Ustr,
814 pub order_link_id: Ustr,
815 pub block_trade_id: Option<Ustr>,
816 pub symbol: Ustr,
817 pub price: String,
818 pub qty: String,
819 pub side: BybitOrderSide,
820 pub is_leverage: String,
821 pub position_idx: i32,
822 pub order_status: BybitOrderStatus,
823 pub cancel_type: BybitCancelType,
824 pub reject_reason: Ustr,
825 pub avg_price: Option<String>,
826 pub leaves_qty: String,
827 pub leaves_value: String,
828 pub cum_exec_qty: String,
829 pub cum_exec_value: String,
830 pub cum_exec_fee: String,
831 pub time_in_force: BybitTimeInForce,
832 pub order_type: BybitOrderType,
833 pub stop_order_type: BybitStopOrderType,
834 pub order_iv: Option<String>,
835 pub trigger_price: String,
836 pub take_profit: String,
837 pub stop_loss: String,
838 pub tp_trigger_by: BybitTriggerType,
839 pub sl_trigger_by: BybitTriggerType,
840 pub trigger_direction: BybitTriggerDirection,
841 pub trigger_by: BybitTriggerType,
842 pub last_price_on_created: String,
843 pub reduce_only: bool,
844 pub close_on_trigger: bool,
845 pub smp_type: Ustr,
846 pub smp_group: i32,
847 pub smp_order_id: Ustr,
848 pub tpsl_mode: Option<BybitTpSlMode>,
849 pub tp_limit_price: String,
850 pub sl_limit_price: String,
851 pub place_type: Ustr,
852 pub created_time: String,
853 pub updated_time: String,
854}
855
856#[cfg(feature = "python")]
857#[pyo3::pymethods]
858impl BybitOrder {
859 #[getter]
860 #[must_use]
861 pub fn order_id(&self) -> &str {
862 self.order_id.as_str()
863 }
864
865 #[getter]
866 #[must_use]
867 pub fn order_link_id(&self) -> &str {
868 self.order_link_id.as_str()
869 }
870
871 #[getter]
872 #[must_use]
873 pub fn block_trade_id(&self) -> Option<&str> {
874 self.block_trade_id.as_ref().map(|s| s.as_str())
875 }
876
877 #[getter]
878 #[must_use]
879 pub fn symbol(&self) -> &str {
880 self.symbol.as_str()
881 }
882
883 #[getter]
884 #[must_use]
885 pub fn price(&self) -> &str {
886 &self.price
887 }
888
889 #[getter]
890 #[must_use]
891 pub fn qty(&self) -> &str {
892 &self.qty
893 }
894
895 #[getter]
896 #[must_use]
897 pub fn side(&self) -> BybitOrderSide {
898 self.side
899 }
900
901 #[getter]
902 #[must_use]
903 pub fn is_leverage(&self) -> &str {
904 &self.is_leverage
905 }
906
907 #[getter]
908 #[must_use]
909 pub fn position_idx(&self) -> i32 {
910 self.position_idx
911 }
912
913 #[getter]
914 #[must_use]
915 pub fn order_status(&self) -> BybitOrderStatus {
916 self.order_status
917 }
918
919 #[getter]
920 #[must_use]
921 pub fn cancel_type(&self) -> BybitCancelType {
922 self.cancel_type
923 }
924
925 #[getter]
926 #[must_use]
927 pub fn reject_reason(&self) -> &str {
928 self.reject_reason.as_str()
929 }
930
931 #[getter]
932 #[must_use]
933 pub fn avg_price(&self) -> Option<&str> {
934 self.avg_price.as_deref()
935 }
936
937 #[getter]
938 #[must_use]
939 pub fn leaves_qty(&self) -> &str {
940 &self.leaves_qty
941 }
942
943 #[getter]
944 #[must_use]
945 pub fn leaves_value(&self) -> &str {
946 &self.leaves_value
947 }
948
949 #[getter]
950 #[must_use]
951 pub fn cum_exec_qty(&self) -> &str {
952 &self.cum_exec_qty
953 }
954
955 #[getter]
956 #[must_use]
957 pub fn cum_exec_value(&self) -> &str {
958 &self.cum_exec_value
959 }
960
961 #[getter]
962 #[must_use]
963 pub fn cum_exec_fee(&self) -> &str {
964 &self.cum_exec_fee
965 }
966
967 #[getter]
968 #[must_use]
969 pub fn time_in_force(&self) -> BybitTimeInForce {
970 self.time_in_force
971 }
972
973 #[getter]
974 #[must_use]
975 pub fn order_type(&self) -> BybitOrderType {
976 self.order_type
977 }
978
979 #[getter]
980 #[must_use]
981 pub fn stop_order_type(&self) -> BybitStopOrderType {
982 self.stop_order_type
983 }
984
985 #[getter]
986 #[must_use]
987 pub fn order_iv(&self) -> Option<&str> {
988 self.order_iv.as_deref()
989 }
990
991 #[getter]
992 #[must_use]
993 pub fn trigger_price(&self) -> &str {
994 &self.trigger_price
995 }
996
997 #[getter]
998 #[must_use]
999 pub fn take_profit(&self) -> &str {
1000 &self.take_profit
1001 }
1002
1003 #[getter]
1004 #[must_use]
1005 pub fn stop_loss(&self) -> &str {
1006 &self.stop_loss
1007 }
1008
1009 #[getter]
1010 #[must_use]
1011 pub fn tp_trigger_by(&self) -> BybitTriggerType {
1012 self.tp_trigger_by
1013 }
1014
1015 #[getter]
1016 #[must_use]
1017 pub fn sl_trigger_by(&self) -> BybitTriggerType {
1018 self.sl_trigger_by
1019 }
1020
1021 #[getter]
1022 #[must_use]
1023 pub fn trigger_direction(&self) -> BybitTriggerDirection {
1024 self.trigger_direction
1025 }
1026
1027 #[getter]
1028 #[must_use]
1029 pub fn trigger_by(&self) -> BybitTriggerType {
1030 self.trigger_by
1031 }
1032
1033 #[getter]
1034 #[must_use]
1035 pub fn last_price_on_created(&self) -> &str {
1036 &self.last_price_on_created
1037 }
1038
1039 #[getter]
1040 #[must_use]
1041 pub fn reduce_only(&self) -> bool {
1042 self.reduce_only
1043 }
1044
1045 #[getter]
1046 #[must_use]
1047 pub fn close_on_trigger(&self) -> bool {
1048 self.close_on_trigger
1049 }
1050
1051 #[getter]
1052 #[must_use]
1053 pub fn smp_type(&self) -> &str {
1054 self.smp_type.as_str()
1055 }
1056
1057 #[getter]
1058 #[must_use]
1059 pub fn smp_group(&self) -> i32 {
1060 self.smp_group
1061 }
1062
1063 #[getter]
1064 #[must_use]
1065 pub fn smp_order_id(&self) -> &str {
1066 self.smp_order_id.as_str()
1067 }
1068
1069 #[getter]
1070 #[must_use]
1071 pub fn tpsl_mode(&self) -> Option<BybitTpSlMode> {
1072 self.tpsl_mode
1073 }
1074
1075 #[getter]
1076 #[must_use]
1077 pub fn tp_limit_price(&self) -> &str {
1078 &self.tp_limit_price
1079 }
1080
1081 #[getter]
1082 #[must_use]
1083 pub fn sl_limit_price(&self) -> &str {
1084 &self.sl_limit_price
1085 }
1086
1087 #[getter]
1088 #[must_use]
1089 pub fn place_type(&self) -> &str {
1090 self.place_type.as_str()
1091 }
1092
1093 #[getter]
1094 #[must_use]
1095 pub fn created_time(&self) -> &str {
1096 &self.created_time
1097 }
1098
1099 #[getter]
1100 #[must_use]
1101 pub fn updated_time(&self) -> &str {
1102 &self.updated_time
1103 }
1104}
1105
1106pub type BybitOpenOrdersResponse = BybitCursorListResponse<BybitOrder>;
1111pub type BybitOrderHistoryResponse = BybitCursorListResponse<BybitOrder>;
1116
1117#[derive(Clone, Debug, Serialize, Deserialize)]
1122#[serde(rename_all = "camelCase")]
1123pub struct BybitPlaceOrderResult {
1124 pub order_id: Option<Ustr>,
1125 pub order_link_id: Option<Ustr>,
1126}
1127
1128pub type BybitPlaceOrderResponse = BybitResponse<BybitPlaceOrderResult>;
1133
1134#[derive(Clone, Debug, Serialize, Deserialize)]
1139#[serde(rename_all = "camelCase")]
1140pub struct BybitCancelOrderResult {
1141 pub order_id: Option<Ustr>,
1142 pub order_link_id: Option<Ustr>,
1143}
1144
1145pub type BybitCancelOrderResponse = BybitResponse<BybitCancelOrderResult>;
1150
1151#[derive(Clone, Debug, Serialize, Deserialize)]
1156#[serde(rename_all = "camelCase")]
1157pub struct BybitExecution {
1158 pub symbol: Ustr,
1159 pub order_id: Ustr,
1160 pub order_link_id: Ustr,
1161 pub side: BybitOrderSide,
1162 pub order_price: String,
1163 pub order_qty: String,
1164 pub leaves_qty: String,
1165 pub create_type: Option<String>,
1166 pub order_type: BybitOrderType,
1167 pub stop_order_type: Option<BybitStopOrderType>,
1168 pub exec_fee: String,
1169 pub exec_id: String,
1170 pub exec_price: String,
1171 pub exec_qty: String,
1172 pub exec_type: BybitExecType,
1173 pub exec_value: String,
1174 pub exec_time: String,
1175 pub fee_currency: Ustr,
1176 pub is_maker: bool,
1177 pub fee_rate: String,
1178 pub trade_iv: String,
1179 pub mark_iv: String,
1180 pub mark_price: String,
1181 pub index_price: String,
1182 pub underlying_price: String,
1183 pub block_trade_id: String,
1184 pub closed_size: String,
1185 pub seq: i64,
1186}
1187
1188pub type BybitTradeHistoryResponse = BybitCursorListResponse<BybitExecution>;
1193
1194#[derive(Clone, Debug, Serialize, Deserialize)]
1199#[serde(rename_all = "camelCase")]
1200pub struct BybitPosition {
1201 pub position_idx: BybitPositionIdx,
1202 pub risk_id: i32,
1203 pub risk_limit_value: String,
1204 pub symbol: Ustr,
1205 pub side: BybitPositionSide,
1206 pub size: String,
1207 pub avg_price: String,
1208 pub position_value: String,
1209 pub trade_mode: i32,
1210 pub position_status: String,
1211 pub auto_add_margin: i32,
1212 pub adl_rank_indicator: i32,
1213 pub leverage: String,
1214 pub position_balance: String,
1215 pub mark_price: String,
1216 pub liq_price: String,
1217 pub bust_price: String,
1218 #[serde(rename = "positionMM")]
1219 pub position_mm: String,
1220 #[serde(rename = "positionIM")]
1221 pub position_im: String,
1222 pub tpsl_mode: String,
1223 pub take_profit: String,
1224 pub stop_loss: String,
1225 pub trailing_stop: String,
1226 pub unrealised_pnl: String,
1227 pub cur_realised_pnl: String,
1228 pub cum_realised_pnl: String,
1229 pub seq: i64,
1230 pub is_reduce_only: bool,
1231 pub mmr_sys_updated_time: String,
1232 pub leverage_sys_updated_time: String,
1233 pub created_time: String,
1234 pub updated_time: String,
1235}
1236
1237pub type BybitPositionListResponse = BybitCursorListResponse<BybitPosition>;
1242
1243#[derive(Clone, Debug, Serialize, Deserialize)]
1248#[serde(rename_all = "camelCase")]
1249pub struct BybitSetMarginModeReason {
1250 pub reason_code: String,
1251 pub reason_msg: String,
1252}
1253
1254#[derive(Clone, Debug, Serialize, Deserialize)]
1259#[serde(rename_all = "camelCase")]
1260pub struct BybitSetMarginModeResult {
1261 #[serde(default)]
1262 pub reasons: Vec<BybitSetMarginModeReason>,
1263}
1264
1265pub type BybitSetMarginModeResponse = BybitResponse<BybitSetMarginModeResult>;
1270
1271#[derive(Clone, Debug, Serialize, Deserialize)]
1273pub struct BybitSetLeverageResult {}
1274
1275pub type BybitSetLeverageResponse = BybitResponse<BybitSetLeverageResult>;
1280
1281#[derive(Clone, Debug, Serialize, Deserialize)]
1283pub struct BybitSwitchModeResult {}
1284
1285pub type BybitSwitchModeResponse = BybitResponse<BybitSwitchModeResult>;
1290
1291#[derive(Clone, Debug, Serialize, Deserialize)]
1293pub struct BybitSetTradingStopResult {}
1294
1295pub type BybitSetTradingStopResponse = BybitResponse<BybitSetTradingStopResult>;
1300
1301#[derive(Clone, Debug, Serialize, Deserialize)]
1303#[serde(rename_all = "camelCase")]
1304pub struct BybitBorrowResult {
1305 pub coin: String,
1306 pub amount: String,
1307}
1308
1309pub type BybitBorrowResponse = BybitResponse<BybitBorrowResult>;
1315
1316#[derive(Clone, Debug, Serialize, Deserialize)]
1318#[serde(rename_all = "camelCase")]
1319pub struct BybitNoConvertRepayResult {
1320 pub result_status: String,
1321}
1322
1323pub type BybitNoConvertRepayResponse = BybitResponse<BybitNoConvertRepayResult>;
1329
1330#[derive(Clone, Debug, Serialize, Deserialize)]
1332#[cfg_attr(
1333 feature = "python",
1334 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bybit")
1335)]
1336#[serde(rename_all = "PascalCase")]
1337pub struct BybitApiKeyPermissions {
1338 #[serde(default)]
1339 pub contract_trade: Vec<String>,
1340 #[serde(default)]
1341 pub spot: Vec<String>,
1342 #[serde(default)]
1343 pub wallet: Vec<String>,
1344 #[serde(default)]
1345 pub options: Vec<String>,
1346 #[serde(default)]
1347 pub derivatives: Vec<String>,
1348 #[serde(default)]
1349 pub exchange: Vec<String>,
1350 #[serde(default)]
1351 pub copy_trading: Vec<String>,
1352 #[serde(default)]
1353 pub block_trade: Vec<String>,
1354 #[serde(default)]
1355 pub nft: Vec<String>,
1356 #[serde(default)]
1357 pub affiliate: Vec<String>,
1358}
1359
1360#[derive(Clone, Debug, Serialize, Deserialize)]
1362#[cfg_attr(
1363 feature = "python",
1364 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.bybit")
1365)]
1366#[serde(rename_all = "camelCase")]
1367pub struct BybitAccountDetails {
1368 pub id: String,
1369 pub note: String,
1370 pub api_key: String,
1371 pub read_only: u8,
1372 pub secret: String,
1373 #[serde(rename = "type")]
1374 pub key_type: u8,
1375 pub permissions: BybitApiKeyPermissions,
1376 pub ips: Vec<String>,
1377 #[serde(default)]
1378 pub user_id: Option<u64>,
1379 #[serde(default)]
1380 pub inviter_id: Option<u64>,
1381 pub vip_level: String,
1382 #[serde(deserialize_with = "deserialize_string_to_u8", default)]
1383 pub mkt_maker_level: u8,
1384 #[serde(default)]
1385 pub affiliate_id: Option<u64>,
1386 pub rsa_public_key: String,
1387 pub is_master: bool,
1388 pub parent_uid: String,
1389 pub uta: u8,
1390 pub kyc_level: String,
1391 pub kyc_region: String,
1392 #[serde(default)]
1393 pub deadline_day: i64,
1394 #[serde(default)]
1395 pub expired_at: Option<String>,
1396 pub created_at: String,
1397}
1398
1399#[cfg(feature = "python")]
1400#[pyo3::pymethods]
1401impl BybitAccountDetails {
1402 #[getter]
1403 #[must_use]
1404 pub fn id(&self) -> &str {
1405 &self.id
1406 }
1407
1408 #[getter]
1409 #[must_use]
1410 pub fn note(&self) -> &str {
1411 &self.note
1412 }
1413
1414 #[getter]
1415 #[must_use]
1416 pub fn api_key(&self) -> &str {
1417 &self.api_key
1418 }
1419
1420 #[getter]
1421 #[must_use]
1422 pub fn read_only(&self) -> u8 {
1423 self.read_only
1424 }
1425
1426 #[getter]
1427 #[must_use]
1428 pub fn key_type(&self) -> u8 {
1429 self.key_type
1430 }
1431
1432 #[getter]
1433 #[must_use]
1434 pub fn user_id(&self) -> Option<u64> {
1435 self.user_id
1436 }
1437
1438 #[getter]
1439 #[must_use]
1440 pub fn inviter_id(&self) -> Option<u64> {
1441 self.inviter_id
1442 }
1443
1444 #[getter]
1445 #[must_use]
1446 pub fn vip_level(&self) -> &str {
1447 &self.vip_level
1448 }
1449
1450 #[getter]
1451 #[must_use]
1452 pub fn mkt_maker_level(&self) -> u8 {
1453 self.mkt_maker_level
1454 }
1455
1456 #[getter]
1457 #[must_use]
1458 pub fn affiliate_id(&self) -> Option<u64> {
1459 self.affiliate_id
1460 }
1461
1462 #[getter]
1463 #[must_use]
1464 pub fn rsa_public_key(&self) -> &str {
1465 &self.rsa_public_key
1466 }
1467
1468 #[getter]
1469 #[must_use]
1470 pub fn is_master(&self) -> bool {
1471 self.is_master
1472 }
1473
1474 #[getter]
1475 #[must_use]
1476 pub fn parent_uid(&self) -> &str {
1477 &self.parent_uid
1478 }
1479
1480 #[getter]
1481 #[must_use]
1482 pub fn uta(&self) -> u8 {
1483 self.uta
1484 }
1485
1486 #[getter]
1487 #[must_use]
1488 pub fn kyc_level(&self) -> &str {
1489 &self.kyc_level
1490 }
1491
1492 #[getter]
1493 #[must_use]
1494 pub fn kyc_region(&self) -> &str {
1495 &self.kyc_region
1496 }
1497
1498 #[getter]
1499 #[must_use]
1500 pub fn deadline_day(&self) -> i64 {
1501 self.deadline_day
1502 }
1503
1504 #[getter]
1505 #[must_use]
1506 pub fn expired_at(&self) -> Option<&str> {
1507 self.expired_at.as_deref()
1508 }
1509
1510 #[getter]
1511 #[must_use]
1512 pub fn created_at(&self) -> &str {
1513 &self.created_at
1514 }
1515}
1516
1517pub type BybitAccountDetailsResponse = BybitResponse<BybitAccountDetails>;
1523
1524#[cfg(test)]
1525mod tests {
1526 use nautilus_core::UnixNanos;
1527 use nautilus_model::identifiers::AccountId;
1528 use rstest::rstest;
1529 use rust_decimal::Decimal;
1530 use rust_decimal_macros::dec;
1531
1532 use super::*;
1533 use crate::common::testing::load_test_json;
1534
1535 #[rstest]
1536 fn deserialize_spot_instrument_uses_enums() {
1537 let json = load_test_json("http_get_instruments_spot.json");
1538 let response: BybitInstrumentSpotResponse = serde_json::from_str(&json).unwrap();
1539 let instrument = &response.result.list[0];
1540
1541 assert_eq!(instrument.status, BybitInstrumentStatus::Trading);
1542 assert_eq!(instrument.innovation, BybitInnovationFlag::Standard);
1543 assert_eq!(instrument.margin_trading, BybitMarginTrading::UtaOnly);
1544 }
1545
1546 #[rstest]
1547 fn deserialize_linear_instrument_status() {
1548 let json = load_test_json("http_get_instruments_linear.json");
1549 let response: BybitInstrumentLinearResponse = serde_json::from_str(&json).unwrap();
1550 let instrument = &response.result.list[0];
1551
1552 assert_eq!(instrument.status, BybitInstrumentStatus::Trading);
1553 assert_eq!(instrument.contract_type, BybitContractType::LinearPerpetual);
1554 }
1555
1556 #[rstest]
1557 fn deserialize_order_response_maps_enums() {
1558 let json = load_test_json("http_get_orders_history.json");
1559 let response: BybitOrderHistoryResponse = serde_json::from_str(&json).unwrap();
1560 let order = &response.result.list[0];
1561
1562 assert_eq!(order.cancel_type, BybitCancelType::CancelByUser);
1563 assert_eq!(order.tp_trigger_by, BybitTriggerType::MarkPrice);
1564 assert_eq!(order.sl_trigger_by, BybitTriggerType::LastPrice);
1565 assert_eq!(order.tpsl_mode, Some(BybitTpSlMode::Full));
1566 assert_eq!(order.order_type, BybitOrderType::Limit);
1567 }
1568
1569 #[rstest]
1570 fn deserialize_wallet_balance_without_optional_fields() {
1571 let json = r#"{
1572 "retCode": 0,
1573 "retMsg": "OK",
1574 "result": {
1575 "list": [{
1576 "totalEquity": "1000.00",
1577 "accountIMRate": "0",
1578 "totalMarginBalance": "1000.00",
1579 "totalInitialMargin": "0",
1580 "accountType": "UNIFIED",
1581 "totalAvailableBalance": "1000.00",
1582 "accountMMRate": "0",
1583 "totalPerpUPL": "0",
1584 "totalWalletBalance": "1000.00",
1585 "accountLTV": "0",
1586 "totalMaintenanceMargin": "0",
1587 "coin": [{
1588 "availableToBorrow": "0",
1589 "bonus": "0",
1590 "accruedInterest": "0",
1591 "availableToWithdraw": "1000.00",
1592 "equity": "1000.00",
1593 "usdValue": "1000.00",
1594 "borrowAmount": "0",
1595 "totalPositionIM": "0",
1596 "walletBalance": "1000.00",
1597 "unrealisedPnl": "0",
1598 "cumRealisedPnl": "0",
1599 "locked": "0",
1600 "collateralSwitch": true,
1601 "marginCollateral": true,
1602 "coin": "USDT"
1603 }]
1604 }]
1605 }
1606 }"#;
1607
1608 let response: BybitWalletBalanceResponse = serde_json::from_str(json)
1609 .expect("Failed to parse wallet balance without optional fields");
1610
1611 assert_eq!(response.ret_code, 0);
1612 assert_eq!(response.result.list[0].coin[0].total_order_im, None);
1613 assert_eq!(response.result.list[0].coin[0].total_position_mm, None);
1614 }
1615
1616 #[rstest]
1617 fn deserialize_wallet_balance_from_docs() {
1618 let json = include_str!("../../test_data/http_get_wallet_balance.json");
1619
1620 let response: BybitWalletBalanceResponse = serde_json::from_str(json)
1621 .expect("Failed to parse wallet balance from Bybit docs example");
1622
1623 assert_eq!(response.ret_code, 0);
1624 assert_eq!(response.ret_msg, "OK");
1625
1626 let wallet = &response.result.list[0];
1627 assert_eq!(wallet.total_equity, "3.31216591");
1628 assert_eq!(wallet.account_im_rate, "0");
1629 assert_eq!(wallet.account_mm_rate, "0");
1630 assert_eq!(wallet.total_perp_upl, "0");
1631 assert_eq!(wallet.account_ltv, "0");
1632
1633 let btc = &wallet.coin[0];
1635 assert_eq!(btc.coin.as_str(), "BTC");
1636 assert_eq!(btc.available_to_borrow, "3");
1637 assert_eq!(btc.total_order_im, Some("0".to_string()));
1638 assert_eq!(btc.total_position_mm, Some("0".to_string()));
1639 assert_eq!(btc.total_position_im, Some("0".to_string()));
1640
1641 let usdt = &wallet.coin[1];
1643 assert_eq!(usdt.coin.as_str(), "USDT");
1644 assert_eq!(usdt.wallet_balance, dec!(1000.50));
1645 assert_eq!(usdt.total_order_im, None);
1646 assert_eq!(usdt.total_position_mm, None);
1647 assert_eq!(usdt.total_position_im, None);
1648 assert_eq!(btc.spot_borrow, Decimal::ZERO);
1649 assert_eq!(usdt.spot_borrow, Decimal::ZERO);
1650 }
1651
1652 #[rstest]
1653 fn test_parse_wallet_balance_with_spot_borrow() {
1654 let json = include_str!("../../test_data/http_get_wallet_balance_with_spot_borrow.json");
1655 let response: BybitWalletBalanceResponse =
1656 serde_json::from_str(json).expect("Failed to parse wallet balance with spotBorrow");
1657
1658 let wallet = &response.result.list[0];
1659 let usdt = &wallet.coin[0];
1660
1661 assert_eq!(usdt.coin.as_str(), "USDT");
1662 assert_eq!(usdt.wallet_balance, dec!(1200.00));
1663 assert_eq!(usdt.spot_borrow, dec!(200.00));
1664 assert_eq!(usdt.borrow_amount, "200.00");
1665
1666 let account_id = crate::common::parse::parse_account_state(
1668 wallet,
1669 AccountId::new("BYBIT-001"),
1670 UnixNanos::default(),
1671 )
1672 .expect("Failed to parse account state");
1673
1674 let balance = &account_id.balances[0];
1675 assert_eq!(balance.total.as_f64(), 1000.0);
1676 }
1677
1678 #[rstest]
1679 fn test_parse_wallet_balance_spot_short() {
1680 let json = include_str!("../../test_data/http_get_wallet_balance_spot_short.json");
1681 let response: BybitWalletBalanceResponse = serde_json::from_str(json)
1682 .expect("Failed to parse wallet balance with SHORT SPOT position");
1683
1684 let wallet = &response.result.list[0];
1685 let eth = &wallet.coin[0];
1686
1687 assert_eq!(eth.coin.as_str(), "ETH");
1688 assert_eq!(eth.wallet_balance, dec!(0));
1689 assert_eq!(eth.spot_borrow, dec!(0.06142));
1690 assert_eq!(eth.borrow_amount, "0.06142");
1691
1692 let account_state = crate::common::parse::parse_account_state(
1693 wallet,
1694 AccountId::new("BYBIT-001"),
1695 UnixNanos::default(),
1696 )
1697 .expect("Failed to parse account state");
1698
1699 let eth_balance = account_state
1700 .balances
1701 .iter()
1702 .find(|b| b.currency.code.as_str() == "ETH")
1703 .expect("ETH balance not found");
1704
1705 assert_eq!(eth_balance.total.as_f64(), -0.06142);
1707 }
1708
1709 #[rstest]
1710 fn deserialize_borrow_response() {
1711 let json = r#"{
1712 "retCode": 0,
1713 "retMsg": "success",
1714 "result": {
1715 "coin": "BTC",
1716 "amount": "0.01"
1717 },
1718 "retExtInfo": {},
1719 "time": 1756197991955
1720 }"#;
1721
1722 let response: BybitBorrowResponse = serde_json::from_str(json).unwrap();
1723
1724 assert_eq!(response.ret_code, 0);
1725 assert_eq!(response.ret_msg, "success");
1726 assert_eq!(response.result.coin, "BTC");
1727 assert_eq!(response.result.amount, "0.01");
1728 }
1729
1730 #[rstest]
1731 fn deserialize_no_convert_repay_response() {
1732 let json = r#"{
1733 "retCode": 0,
1734 "retMsg": "OK",
1735 "result": {
1736 "resultStatus": "SU"
1737 },
1738 "retExtInfo": {},
1739 "time": 1234567890
1740 }"#;
1741
1742 let response: BybitNoConvertRepayResponse = serde_json::from_str(json).unwrap();
1743
1744 assert_eq!(response.ret_code, 0);
1745 assert_eq!(response.ret_msg, "OK");
1746 assert_eq!(response.result.result_status, "SU");
1747 }
1748}