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.adapters")
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.adapters")
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 BybitInstrumentSpot {
529 pub symbol: Ustr,
530 pub base_coin: Ustr,
531 pub quote_coin: Ustr,
532 pub innovation: BybitInnovationFlag,
533 pub status: BybitInstrumentStatus,
534 pub margin_trading: BybitMarginTrading,
535 pub lot_size_filter: SpotLotSizeFilter,
536 pub price_filter: SpotPriceFilter,
537}
538
539#[derive(Clone, Debug, Serialize, Deserialize)]
544#[serde(rename_all = "camelCase")]
545pub struct BybitInstrumentLinear {
546 pub symbol: Ustr,
547 pub contract_type: BybitContractType,
548 pub status: BybitInstrumentStatus,
549 pub base_coin: Ustr,
550 pub quote_coin: Ustr,
551 pub launch_time: String,
552 pub delivery_time: String,
553 pub delivery_fee_rate: String,
554 pub price_scale: String,
555 pub leverage_filter: LeverageFilter,
556 pub price_filter: LinearPriceFilter,
557 pub lot_size_filter: LinearLotSizeFilter,
558 pub unified_margin_trade: bool,
559 pub funding_interval: i64,
560 pub settle_coin: Ustr,
561}
562
563#[derive(Clone, Debug, Serialize, Deserialize)]
568#[serde(rename_all = "camelCase")]
569pub struct BybitInstrumentInverse {
570 pub symbol: Ustr,
571 pub contract_type: BybitContractType,
572 pub status: BybitInstrumentStatus,
573 pub base_coin: Ustr,
574 pub quote_coin: Ustr,
575 pub launch_time: String,
576 pub delivery_time: String,
577 pub delivery_fee_rate: String,
578 pub price_scale: String,
579 pub leverage_filter: LeverageFilter,
580 pub price_filter: LinearPriceFilter,
581 pub lot_size_filter: LinearLotSizeFilter,
582 pub unified_margin_trade: bool,
583 pub funding_interval: i64,
584 pub settle_coin: Ustr,
585}
586
587#[derive(Clone, Debug, Serialize, Deserialize)]
592#[serde(rename_all = "camelCase")]
593pub struct BybitInstrumentOption {
594 pub symbol: Ustr,
595 pub status: BybitInstrumentStatus,
596 pub base_coin: Ustr,
597 pub quote_coin: Ustr,
598 pub settle_coin: Ustr,
599 pub options_type: BybitOptionType,
600 pub launch_time: String,
601 pub delivery_time: String,
602 pub delivery_fee_rate: String,
603 pub price_filter: LinearPriceFilter,
604 pub lot_size_filter: OptionLotSizeFilter,
605}
606
607pub type BybitInstrumentSpotResponse = BybitCursorListResponse<BybitInstrumentSpot>;
612pub type BybitInstrumentLinearResponse = BybitCursorListResponse<BybitInstrumentLinear>;
617pub type BybitInstrumentInverseResponse = BybitCursorListResponse<BybitInstrumentInverse>;
622pub type BybitInstrumentOptionResponse = BybitCursorListResponse<BybitInstrumentOption>;
627
628#[derive(Clone, Debug, Serialize, Deserialize)]
633#[serde(rename_all = "camelCase")]
634#[cfg_attr(
635 feature = "python",
636 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.adapters")
637)]
638pub struct BybitFeeRate {
639 pub symbol: Ustr,
640 pub taker_fee_rate: String,
641 pub maker_fee_rate: String,
642 #[serde(default)]
643 pub base_coin: Option<Ustr>,
644}
645
646#[cfg(feature = "python")]
647#[pyo3::pymethods]
648impl BybitFeeRate {
649 #[getter]
650 #[must_use]
651 pub fn symbol(&self) -> &str {
652 self.symbol.as_str()
653 }
654
655 #[getter]
656 #[must_use]
657 pub fn taker_fee_rate(&self) -> &str {
658 &self.taker_fee_rate
659 }
660
661 #[getter]
662 #[must_use]
663 pub fn maker_fee_rate(&self) -> &str {
664 &self.maker_fee_rate
665 }
666
667 #[getter]
668 #[must_use]
669 pub fn base_coin(&self) -> Option<&str> {
670 self.base_coin.as_ref().map(|u| u.as_str())
671 }
672}
673
674pub type BybitFeeRateResponse = BybitListResponse<BybitFeeRate>;
679
680#[derive(Clone, Debug, Serialize, Deserialize)]
685#[serde(rename_all = "camelCase")]
686pub struct BybitCoinBalance {
687 pub available_to_borrow: String,
688 pub bonus: String,
689 pub accrued_interest: String,
690 pub available_to_withdraw: String,
691 #[serde(default, rename = "totalOrderIM")]
692 pub total_order_im: Option<String>,
693 pub equity: String,
694 pub usd_value: String,
695 pub borrow_amount: String,
696 #[serde(default, rename = "totalPositionMM")]
697 pub total_position_mm: Option<String>,
698 #[serde(default, rename = "totalPositionIM")]
699 pub total_position_im: Option<String>,
700 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
701 pub wallet_balance: Decimal,
702 pub unrealised_pnl: String,
703 pub cum_realised_pnl: String,
704 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
705 pub locked: Decimal,
706 pub collateral_switch: bool,
707 pub margin_collateral: bool,
708 pub coin: Ustr,
709 #[serde(default)]
710 pub spot_hedging_qty: Option<String>,
711 #[serde(default, deserialize_with = "deserialize_optional_decimal_or_zero")]
712 pub spot_borrow: Decimal,
713}
714
715#[derive(Clone, Debug, Serialize, Deserialize)]
720#[serde(rename_all = "camelCase")]
721pub struct BybitWalletBalance {
722 pub total_equity: String,
723 #[serde(rename = "accountIMRate")]
724 pub account_im_rate: String,
725 pub total_margin_balance: String,
726 pub total_initial_margin: String,
727 pub account_type: BybitAccountType,
728 pub total_available_balance: String,
729 #[serde(rename = "accountMMRate")]
730 pub account_mm_rate: String,
731 #[serde(rename = "totalPerpUPL")]
732 pub total_perp_upl: String,
733 pub total_wallet_balance: String,
734 #[serde(rename = "accountLTV")]
735 pub account_ltv: String,
736 pub total_maintenance_margin: String,
737 pub coin: Vec<BybitCoinBalance>,
738}
739
740pub type BybitWalletBalanceResponse = BybitListResponse<BybitWalletBalance>;
745
746#[derive(Clone, Debug, Serialize, Deserialize)]
751#[cfg_attr(
752 feature = "python",
753 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.adapters")
754)]
755#[serde(rename_all = "camelCase")]
756pub struct BybitOrder {
757 pub order_id: Ustr,
758 pub order_link_id: Ustr,
759 pub block_trade_id: Option<Ustr>,
760 pub symbol: Ustr,
761 pub price: String,
762 pub qty: String,
763 pub side: BybitOrderSide,
764 pub is_leverage: String,
765 pub position_idx: i32,
766 pub order_status: BybitOrderStatus,
767 pub cancel_type: BybitCancelType,
768 pub reject_reason: Ustr,
769 pub avg_price: Option<String>,
770 pub leaves_qty: String,
771 pub leaves_value: String,
772 pub cum_exec_qty: String,
773 pub cum_exec_value: String,
774 pub cum_exec_fee: String,
775 pub time_in_force: BybitTimeInForce,
776 pub order_type: BybitOrderType,
777 pub stop_order_type: BybitStopOrderType,
778 pub order_iv: Option<String>,
779 pub trigger_price: String,
780 pub take_profit: String,
781 pub stop_loss: String,
782 pub tp_trigger_by: BybitTriggerType,
783 pub sl_trigger_by: BybitTriggerType,
784 pub trigger_direction: BybitTriggerDirection,
785 pub trigger_by: BybitTriggerType,
786 pub last_price_on_created: String,
787 pub reduce_only: bool,
788 pub close_on_trigger: bool,
789 pub smp_type: Ustr,
790 pub smp_group: i32,
791 pub smp_order_id: Ustr,
792 pub tpsl_mode: Option<BybitTpSlMode>,
793 pub tp_limit_price: String,
794 pub sl_limit_price: String,
795 pub place_type: Ustr,
796 pub created_time: String,
797 pub updated_time: String,
798}
799
800#[cfg(feature = "python")]
801#[pyo3::pymethods]
802impl BybitOrder {
803 #[getter]
804 #[must_use]
805 pub fn order_id(&self) -> &str {
806 self.order_id.as_str()
807 }
808
809 #[getter]
810 #[must_use]
811 pub fn order_link_id(&self) -> &str {
812 self.order_link_id.as_str()
813 }
814
815 #[getter]
816 #[must_use]
817 pub fn block_trade_id(&self) -> Option<&str> {
818 self.block_trade_id.as_ref().map(|s| s.as_str())
819 }
820
821 #[getter]
822 #[must_use]
823 pub fn symbol(&self) -> &str {
824 self.symbol.as_str()
825 }
826
827 #[getter]
828 #[must_use]
829 pub fn price(&self) -> &str {
830 &self.price
831 }
832
833 #[getter]
834 #[must_use]
835 pub fn qty(&self) -> &str {
836 &self.qty
837 }
838
839 #[getter]
840 #[must_use]
841 pub fn side(&self) -> BybitOrderSide {
842 self.side
843 }
844
845 #[getter]
846 #[must_use]
847 pub fn is_leverage(&self) -> &str {
848 &self.is_leverage
849 }
850
851 #[getter]
852 #[must_use]
853 pub fn position_idx(&self) -> i32 {
854 self.position_idx
855 }
856
857 #[getter]
858 #[must_use]
859 pub fn order_status(&self) -> BybitOrderStatus {
860 self.order_status
861 }
862
863 #[getter]
864 #[must_use]
865 pub fn cancel_type(&self) -> BybitCancelType {
866 self.cancel_type
867 }
868
869 #[getter]
870 #[must_use]
871 pub fn reject_reason(&self) -> &str {
872 self.reject_reason.as_str()
873 }
874
875 #[getter]
876 #[must_use]
877 pub fn avg_price(&self) -> Option<&str> {
878 self.avg_price.as_deref()
879 }
880
881 #[getter]
882 #[must_use]
883 pub fn leaves_qty(&self) -> &str {
884 &self.leaves_qty
885 }
886
887 #[getter]
888 #[must_use]
889 pub fn leaves_value(&self) -> &str {
890 &self.leaves_value
891 }
892
893 #[getter]
894 #[must_use]
895 pub fn cum_exec_qty(&self) -> &str {
896 &self.cum_exec_qty
897 }
898
899 #[getter]
900 #[must_use]
901 pub fn cum_exec_value(&self) -> &str {
902 &self.cum_exec_value
903 }
904
905 #[getter]
906 #[must_use]
907 pub fn cum_exec_fee(&self) -> &str {
908 &self.cum_exec_fee
909 }
910
911 #[getter]
912 #[must_use]
913 pub fn time_in_force(&self) -> BybitTimeInForce {
914 self.time_in_force
915 }
916
917 #[getter]
918 #[must_use]
919 pub fn order_type(&self) -> BybitOrderType {
920 self.order_type
921 }
922
923 #[getter]
924 #[must_use]
925 pub fn stop_order_type(&self) -> BybitStopOrderType {
926 self.stop_order_type
927 }
928
929 #[getter]
930 #[must_use]
931 pub fn order_iv(&self) -> Option<&str> {
932 self.order_iv.as_deref()
933 }
934
935 #[getter]
936 #[must_use]
937 pub fn trigger_price(&self) -> &str {
938 &self.trigger_price
939 }
940
941 #[getter]
942 #[must_use]
943 pub fn take_profit(&self) -> &str {
944 &self.take_profit
945 }
946
947 #[getter]
948 #[must_use]
949 pub fn stop_loss(&self) -> &str {
950 &self.stop_loss
951 }
952
953 #[getter]
954 #[must_use]
955 pub fn tp_trigger_by(&self) -> BybitTriggerType {
956 self.tp_trigger_by
957 }
958
959 #[getter]
960 #[must_use]
961 pub fn sl_trigger_by(&self) -> BybitTriggerType {
962 self.sl_trigger_by
963 }
964
965 #[getter]
966 #[must_use]
967 pub fn trigger_direction(&self) -> BybitTriggerDirection {
968 self.trigger_direction
969 }
970
971 #[getter]
972 #[must_use]
973 pub fn trigger_by(&self) -> BybitTriggerType {
974 self.trigger_by
975 }
976
977 #[getter]
978 #[must_use]
979 pub fn last_price_on_created(&self) -> &str {
980 &self.last_price_on_created
981 }
982
983 #[getter]
984 #[must_use]
985 pub fn reduce_only(&self) -> bool {
986 self.reduce_only
987 }
988
989 #[getter]
990 #[must_use]
991 pub fn close_on_trigger(&self) -> bool {
992 self.close_on_trigger
993 }
994
995 #[getter]
996 #[must_use]
997 pub fn smp_type(&self) -> &str {
998 self.smp_type.as_str()
999 }
1000
1001 #[getter]
1002 #[must_use]
1003 pub fn smp_group(&self) -> i32 {
1004 self.smp_group
1005 }
1006
1007 #[getter]
1008 #[must_use]
1009 pub fn smp_order_id(&self) -> &str {
1010 self.smp_order_id.as_str()
1011 }
1012
1013 #[getter]
1014 #[must_use]
1015 pub fn tpsl_mode(&self) -> Option<BybitTpSlMode> {
1016 self.tpsl_mode
1017 }
1018
1019 #[getter]
1020 #[must_use]
1021 pub fn tp_limit_price(&self) -> &str {
1022 &self.tp_limit_price
1023 }
1024
1025 #[getter]
1026 #[must_use]
1027 pub fn sl_limit_price(&self) -> &str {
1028 &self.sl_limit_price
1029 }
1030
1031 #[getter]
1032 #[must_use]
1033 pub fn place_type(&self) -> &str {
1034 self.place_type.as_str()
1035 }
1036
1037 #[getter]
1038 #[must_use]
1039 pub fn created_time(&self) -> &str {
1040 &self.created_time
1041 }
1042
1043 #[getter]
1044 #[must_use]
1045 pub fn updated_time(&self) -> &str {
1046 &self.updated_time
1047 }
1048}
1049
1050pub type BybitOpenOrdersResponse = BybitCursorListResponse<BybitOrder>;
1055pub type BybitOrderHistoryResponse = BybitCursorListResponse<BybitOrder>;
1060
1061#[derive(Clone, Debug, Serialize, Deserialize)]
1066#[serde(rename_all = "camelCase")]
1067pub struct BybitPlaceOrderResult {
1068 pub order_id: Option<Ustr>,
1069 pub order_link_id: Option<Ustr>,
1070}
1071
1072pub type BybitPlaceOrderResponse = BybitResponse<BybitPlaceOrderResult>;
1077
1078#[derive(Clone, Debug, Serialize, Deserialize)]
1083#[serde(rename_all = "camelCase")]
1084pub struct BybitCancelOrderResult {
1085 pub order_id: Option<Ustr>,
1086 pub order_link_id: Option<Ustr>,
1087}
1088
1089pub type BybitCancelOrderResponse = BybitResponse<BybitCancelOrderResult>;
1094
1095#[derive(Clone, Debug, Serialize, Deserialize)]
1100#[serde(rename_all = "camelCase")]
1101pub struct BybitExecution {
1102 pub symbol: Ustr,
1103 pub order_id: Ustr,
1104 pub order_link_id: Ustr,
1105 pub side: BybitOrderSide,
1106 pub order_price: String,
1107 pub order_qty: String,
1108 pub leaves_qty: String,
1109 pub create_type: Option<String>,
1110 pub order_type: BybitOrderType,
1111 pub stop_order_type: Option<BybitStopOrderType>,
1112 pub exec_fee: String,
1113 pub exec_id: String,
1114 pub exec_price: String,
1115 pub exec_qty: String,
1116 pub exec_type: BybitExecType,
1117 pub exec_value: String,
1118 pub exec_time: String,
1119 pub fee_currency: Ustr,
1120 pub is_maker: bool,
1121 pub fee_rate: String,
1122 pub trade_iv: String,
1123 pub mark_iv: String,
1124 pub mark_price: String,
1125 pub index_price: String,
1126 pub underlying_price: String,
1127 pub block_trade_id: String,
1128 pub closed_size: String,
1129 pub seq: i64,
1130}
1131
1132pub type BybitTradeHistoryResponse = BybitCursorListResponse<BybitExecution>;
1137
1138#[derive(Clone, Debug, Serialize, Deserialize)]
1143#[serde(rename_all = "camelCase")]
1144pub struct BybitPosition {
1145 pub position_idx: BybitPositionIdx,
1146 pub risk_id: i32,
1147 pub risk_limit_value: String,
1148 pub symbol: Ustr,
1149 pub side: BybitPositionSide,
1150 pub size: String,
1151 pub avg_price: String,
1152 pub position_value: String,
1153 pub trade_mode: i32,
1154 pub position_status: String,
1155 pub auto_add_margin: i32,
1156 pub adl_rank_indicator: i32,
1157 pub leverage: String,
1158 pub position_balance: String,
1159 pub mark_price: String,
1160 pub liq_price: String,
1161 pub bust_price: String,
1162 #[serde(rename = "positionMM")]
1163 pub position_mm: String,
1164 #[serde(rename = "positionIM")]
1165 pub position_im: String,
1166 pub tpsl_mode: String,
1167 pub take_profit: String,
1168 pub stop_loss: String,
1169 pub trailing_stop: String,
1170 pub unrealised_pnl: String,
1171 pub cur_realised_pnl: String,
1172 pub cum_realised_pnl: String,
1173 pub seq: i64,
1174 pub is_reduce_only: bool,
1175 pub mmr_sys_updated_time: String,
1176 pub leverage_sys_updated_time: String,
1177 pub created_time: String,
1178 pub updated_time: String,
1179}
1180
1181pub type BybitPositionListResponse = BybitCursorListResponse<BybitPosition>;
1186
1187#[derive(Clone, Debug, Serialize, Deserialize)]
1192#[serde(rename_all = "camelCase")]
1193pub struct BybitSetMarginModeReason {
1194 pub reason_code: String,
1195 pub reason_msg: String,
1196}
1197
1198#[derive(Clone, Debug, Serialize, Deserialize)]
1203#[serde(rename_all = "camelCase")]
1204pub struct BybitSetMarginModeResult {
1205 #[serde(default)]
1206 pub reasons: Vec<BybitSetMarginModeReason>,
1207}
1208
1209pub type BybitSetMarginModeResponse = BybitResponse<BybitSetMarginModeResult>;
1214
1215#[derive(Clone, Debug, Serialize, Deserialize)]
1217pub struct BybitSetLeverageResult {}
1218
1219pub type BybitSetLeverageResponse = BybitResponse<BybitSetLeverageResult>;
1224
1225#[derive(Clone, Debug, Serialize, Deserialize)]
1227pub struct BybitSwitchModeResult {}
1228
1229pub type BybitSwitchModeResponse = BybitResponse<BybitSwitchModeResult>;
1234
1235#[derive(Clone, Debug, Serialize, Deserialize)]
1237pub struct BybitSetTradingStopResult {}
1238
1239pub type BybitSetTradingStopResponse = BybitResponse<BybitSetTradingStopResult>;
1244
1245#[derive(Clone, Debug, Serialize, Deserialize)]
1247#[serde(rename_all = "camelCase")]
1248pub struct BybitBorrowResult {
1249 pub coin: String,
1250 pub amount: String,
1251}
1252
1253pub type BybitBorrowResponse = BybitResponse<BybitBorrowResult>;
1259
1260#[derive(Clone, Debug, Serialize, Deserialize)]
1262#[serde(rename_all = "camelCase")]
1263pub struct BybitNoConvertRepayResult {
1264 pub result_status: String,
1265}
1266
1267pub type BybitNoConvertRepayResponse = BybitResponse<BybitNoConvertRepayResult>;
1273
1274#[derive(Clone, Debug, Serialize, Deserialize)]
1276#[cfg_attr(
1277 feature = "python",
1278 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.adapters")
1279)]
1280#[serde(rename_all = "PascalCase")]
1281pub struct BybitApiKeyPermissions {
1282 #[serde(default)]
1283 pub contract_trade: Vec<String>,
1284 #[serde(default)]
1285 pub spot: Vec<String>,
1286 #[serde(default)]
1287 pub wallet: Vec<String>,
1288 #[serde(default)]
1289 pub options: Vec<String>,
1290 #[serde(default)]
1291 pub derivatives: Vec<String>,
1292 #[serde(default)]
1293 pub exchange: Vec<String>,
1294 #[serde(default)]
1295 pub copy_trading: Vec<String>,
1296 #[serde(default)]
1297 pub block_trade: Vec<String>,
1298 #[serde(default)]
1299 pub nft: Vec<String>,
1300 #[serde(default)]
1301 pub affiliate: Vec<String>,
1302}
1303
1304#[derive(Clone, Debug, Serialize, Deserialize)]
1306#[cfg_attr(
1307 feature = "python",
1308 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.adapters")
1309)]
1310#[serde(rename_all = "camelCase")]
1311pub struct BybitAccountDetails {
1312 pub id: String,
1313 pub note: String,
1314 pub api_key: String,
1315 pub read_only: u8,
1316 pub secret: String,
1317 #[serde(rename = "type")]
1318 pub key_type: u8,
1319 pub permissions: BybitApiKeyPermissions,
1320 pub ips: Vec<String>,
1321 #[serde(default)]
1322 pub user_id: Option<u64>,
1323 #[serde(default)]
1324 pub inviter_id: Option<u64>,
1325 pub vip_level: String,
1326 #[serde(deserialize_with = "deserialize_string_to_u8", default)]
1327 pub mkt_maker_level: u8,
1328 #[serde(default)]
1329 pub affiliate_id: Option<u64>,
1330 pub rsa_public_key: String,
1331 pub is_master: bool,
1332 pub parent_uid: String,
1333 pub uta: u8,
1334 pub kyc_level: String,
1335 pub kyc_region: String,
1336 #[serde(default)]
1337 pub deadline_day: i64,
1338 #[serde(default)]
1339 pub expired_at: Option<String>,
1340 pub created_at: String,
1341}
1342
1343#[cfg(feature = "python")]
1344#[pyo3::pymethods]
1345impl BybitAccountDetails {
1346 #[getter]
1347 #[must_use]
1348 pub fn id(&self) -> &str {
1349 &self.id
1350 }
1351
1352 #[getter]
1353 #[must_use]
1354 pub fn note(&self) -> &str {
1355 &self.note
1356 }
1357
1358 #[getter]
1359 #[must_use]
1360 pub fn api_key(&self) -> &str {
1361 &self.api_key
1362 }
1363
1364 #[getter]
1365 #[must_use]
1366 pub fn read_only(&self) -> u8 {
1367 self.read_only
1368 }
1369
1370 #[getter]
1371 #[must_use]
1372 pub fn key_type(&self) -> u8 {
1373 self.key_type
1374 }
1375
1376 #[getter]
1377 #[must_use]
1378 pub fn user_id(&self) -> Option<u64> {
1379 self.user_id
1380 }
1381
1382 #[getter]
1383 #[must_use]
1384 pub fn inviter_id(&self) -> Option<u64> {
1385 self.inviter_id
1386 }
1387
1388 #[getter]
1389 #[must_use]
1390 pub fn vip_level(&self) -> &str {
1391 &self.vip_level
1392 }
1393
1394 #[getter]
1395 #[must_use]
1396 pub fn mkt_maker_level(&self) -> u8 {
1397 self.mkt_maker_level
1398 }
1399
1400 #[getter]
1401 #[must_use]
1402 pub fn affiliate_id(&self) -> Option<u64> {
1403 self.affiliate_id
1404 }
1405
1406 #[getter]
1407 #[must_use]
1408 pub fn rsa_public_key(&self) -> &str {
1409 &self.rsa_public_key
1410 }
1411
1412 #[getter]
1413 #[must_use]
1414 pub fn is_master(&self) -> bool {
1415 self.is_master
1416 }
1417
1418 #[getter]
1419 #[must_use]
1420 pub fn parent_uid(&self) -> &str {
1421 &self.parent_uid
1422 }
1423
1424 #[getter]
1425 #[must_use]
1426 pub fn uta(&self) -> u8 {
1427 self.uta
1428 }
1429
1430 #[getter]
1431 #[must_use]
1432 pub fn kyc_level(&self) -> &str {
1433 &self.kyc_level
1434 }
1435
1436 #[getter]
1437 #[must_use]
1438 pub fn kyc_region(&self) -> &str {
1439 &self.kyc_region
1440 }
1441
1442 #[getter]
1443 #[must_use]
1444 pub fn deadline_day(&self) -> i64 {
1445 self.deadline_day
1446 }
1447
1448 #[getter]
1449 #[must_use]
1450 pub fn expired_at(&self) -> Option<&str> {
1451 self.expired_at.as_deref()
1452 }
1453
1454 #[getter]
1455 #[must_use]
1456 pub fn created_at(&self) -> &str {
1457 &self.created_at
1458 }
1459}
1460
1461pub type BybitAccountDetailsResponse = BybitResponse<BybitAccountDetails>;
1467
1468#[cfg(test)]
1469mod tests {
1470 use nautilus_core::UnixNanos;
1471 use nautilus_model::identifiers::AccountId;
1472 use rstest::rstest;
1473 use rust_decimal::Decimal;
1474 use rust_decimal_macros::dec;
1475
1476 use super::*;
1477 use crate::common::testing::load_test_json;
1478
1479 #[rstest]
1480 fn deserialize_spot_instrument_uses_enums() {
1481 let json = load_test_json("http_get_instruments_spot.json");
1482 let response: BybitInstrumentSpotResponse = serde_json::from_str(&json).unwrap();
1483 let instrument = &response.result.list[0];
1484
1485 assert_eq!(instrument.status, BybitInstrumentStatus::Trading);
1486 assert_eq!(instrument.innovation, BybitInnovationFlag::Standard);
1487 assert_eq!(instrument.margin_trading, BybitMarginTrading::UtaOnly);
1488 }
1489
1490 #[rstest]
1491 fn deserialize_linear_instrument_status() {
1492 let json = load_test_json("http_get_instruments_linear.json");
1493 let response: BybitInstrumentLinearResponse = serde_json::from_str(&json).unwrap();
1494 let instrument = &response.result.list[0];
1495
1496 assert_eq!(instrument.status, BybitInstrumentStatus::Trading);
1497 assert_eq!(instrument.contract_type, BybitContractType::LinearPerpetual);
1498 }
1499
1500 #[rstest]
1501 fn deserialize_order_response_maps_enums() {
1502 let json = load_test_json("http_get_orders_history.json");
1503 let response: BybitOrderHistoryResponse = serde_json::from_str(&json).unwrap();
1504 let order = &response.result.list[0];
1505
1506 assert_eq!(order.cancel_type, BybitCancelType::CancelByUser);
1507 assert_eq!(order.tp_trigger_by, BybitTriggerType::MarkPrice);
1508 assert_eq!(order.sl_trigger_by, BybitTriggerType::LastPrice);
1509 assert_eq!(order.tpsl_mode, Some(BybitTpSlMode::Full));
1510 assert_eq!(order.order_type, BybitOrderType::Limit);
1511 }
1512
1513 #[rstest]
1514 fn deserialize_wallet_balance_without_optional_fields() {
1515 let json = r#"{
1516 "retCode": 0,
1517 "retMsg": "OK",
1518 "result": {
1519 "list": [{
1520 "totalEquity": "1000.00",
1521 "accountIMRate": "0",
1522 "totalMarginBalance": "1000.00",
1523 "totalInitialMargin": "0",
1524 "accountType": "UNIFIED",
1525 "totalAvailableBalance": "1000.00",
1526 "accountMMRate": "0",
1527 "totalPerpUPL": "0",
1528 "totalWalletBalance": "1000.00",
1529 "accountLTV": "0",
1530 "totalMaintenanceMargin": "0",
1531 "coin": [{
1532 "availableToBorrow": "0",
1533 "bonus": "0",
1534 "accruedInterest": "0",
1535 "availableToWithdraw": "1000.00",
1536 "equity": "1000.00",
1537 "usdValue": "1000.00",
1538 "borrowAmount": "0",
1539 "totalPositionIM": "0",
1540 "walletBalance": "1000.00",
1541 "unrealisedPnl": "0",
1542 "cumRealisedPnl": "0",
1543 "locked": "0",
1544 "collateralSwitch": true,
1545 "marginCollateral": true,
1546 "coin": "USDT"
1547 }]
1548 }]
1549 }
1550 }"#;
1551
1552 let response: BybitWalletBalanceResponse = serde_json::from_str(json)
1553 .expect("Failed to parse wallet balance without optional fields");
1554
1555 assert_eq!(response.ret_code, 0);
1556 assert_eq!(response.result.list[0].coin[0].total_order_im, None);
1557 assert_eq!(response.result.list[0].coin[0].total_position_mm, None);
1558 }
1559
1560 #[rstest]
1561 fn deserialize_wallet_balance_from_docs() {
1562 let json = include_str!("../../test_data/http_get_wallet_balance.json");
1563
1564 let response: BybitWalletBalanceResponse = serde_json::from_str(json)
1565 .expect("Failed to parse wallet balance from Bybit docs example");
1566
1567 assert_eq!(response.ret_code, 0);
1568 assert_eq!(response.ret_msg, "OK");
1569
1570 let wallet = &response.result.list[0];
1571 assert_eq!(wallet.total_equity, "3.31216591");
1572 assert_eq!(wallet.account_im_rate, "0");
1573 assert_eq!(wallet.account_mm_rate, "0");
1574 assert_eq!(wallet.total_perp_upl, "0");
1575 assert_eq!(wallet.account_ltv, "0");
1576
1577 let btc = &wallet.coin[0];
1579 assert_eq!(btc.coin.as_str(), "BTC");
1580 assert_eq!(btc.available_to_borrow, "3");
1581 assert_eq!(btc.total_order_im, Some("0".to_string()));
1582 assert_eq!(btc.total_position_mm, Some("0".to_string()));
1583 assert_eq!(btc.total_position_im, Some("0".to_string()));
1584
1585 let usdt = &wallet.coin[1];
1587 assert_eq!(usdt.coin.as_str(), "USDT");
1588 assert_eq!(usdt.wallet_balance, dec!(1000.50));
1589 assert_eq!(usdt.total_order_im, None);
1590 assert_eq!(usdt.total_position_mm, None);
1591 assert_eq!(usdt.total_position_im, None);
1592 assert_eq!(btc.spot_borrow, Decimal::ZERO);
1593 assert_eq!(usdt.spot_borrow, Decimal::ZERO);
1594 }
1595
1596 #[rstest]
1597 fn test_parse_wallet_balance_with_spot_borrow() {
1598 let json = include_str!("../../test_data/http_get_wallet_balance_with_spot_borrow.json");
1599 let response: BybitWalletBalanceResponse =
1600 serde_json::from_str(json).expect("Failed to parse wallet balance with spotBorrow");
1601
1602 let wallet = &response.result.list[0];
1603 let usdt = &wallet.coin[0];
1604
1605 assert_eq!(usdt.coin.as_str(), "USDT");
1606 assert_eq!(usdt.wallet_balance, dec!(1200.00));
1607 assert_eq!(usdt.spot_borrow, dec!(200.00));
1608 assert_eq!(usdt.borrow_amount, "200.00");
1609
1610 let account_id = crate::common::parse::parse_account_state(
1612 wallet,
1613 AccountId::new("BYBIT-001"),
1614 UnixNanos::default(),
1615 )
1616 .expect("Failed to parse account state");
1617
1618 let balance = &account_id.balances[0];
1619 assert_eq!(balance.total.as_f64(), 1000.0);
1620 }
1621
1622 #[rstest]
1623 fn test_parse_wallet_balance_spot_short() {
1624 let json = include_str!("../../test_data/http_get_wallet_balance_spot_short.json");
1625 let response: BybitWalletBalanceResponse = serde_json::from_str(json)
1626 .expect("Failed to parse wallet balance with SHORT SPOT position");
1627
1628 let wallet = &response.result.list[0];
1629 let eth = &wallet.coin[0];
1630
1631 assert_eq!(eth.coin.as_str(), "ETH");
1632 assert_eq!(eth.wallet_balance, dec!(0));
1633 assert_eq!(eth.spot_borrow, dec!(0.06142));
1634 assert_eq!(eth.borrow_amount, "0.06142");
1635
1636 let account_state = crate::common::parse::parse_account_state(
1637 wallet,
1638 AccountId::new("BYBIT-001"),
1639 UnixNanos::default(),
1640 )
1641 .expect("Failed to parse account state");
1642
1643 let eth_balance = account_state
1644 .balances
1645 .iter()
1646 .find(|b| b.currency.code.as_str() == "ETH")
1647 .expect("ETH balance not found");
1648
1649 assert_eq!(eth_balance.total.as_f64(), -0.06142);
1651 }
1652
1653 #[rstest]
1654 fn deserialize_borrow_response() {
1655 let json = r#"{
1656 "retCode": 0,
1657 "retMsg": "success",
1658 "result": {
1659 "coin": "BTC",
1660 "amount": "0.01"
1661 },
1662 "retExtInfo": {},
1663 "time": 1756197991955
1664 }"#;
1665
1666 let response: BybitBorrowResponse = serde_json::from_str(json).unwrap();
1667
1668 assert_eq!(response.ret_code, 0);
1669 assert_eq!(response.ret_msg, "success");
1670 assert_eq!(response.result.coin, "BTC");
1671 assert_eq!(response.result.amount, "0.01");
1672 }
1673
1674 #[rstest]
1675 fn deserialize_no_convert_repay_response() {
1676 let json = r#"{
1677 "retCode": 0,
1678 "retMsg": "OK",
1679 "result": {
1680 "resultStatus": "SU"
1681 },
1682 "retExtInfo": {},
1683 "time": 1234567890
1684 }"#;
1685
1686 let response: BybitNoConvertRepayResponse = serde_json::from_str(json).unwrap();
1687
1688 assert_eq!(response.ret_code, 0);
1689 assert_eq!(response.ret_msg, "OK");
1690 assert_eq!(response.result.result_status, "SU");
1691 }
1692}