1use std::fmt::Display;
19
20use jiff::{Timestamp, civil::Date, tz::Offset};
21use nautilus_model::enums::{AggressorSide, AssetClass, OrderSide, TriggerType};
22use serde::{Deserialize, Serialize};
23use serde_repr::{Deserialize_repr, Serialize_repr};
24use strum::{AsRefStr, EnumIter, EnumString};
25
26#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize_repr, Deserialize_repr)]
28#[repr(i32)]
29pub enum BybitUnifiedMarginStatus {
30 ClassicAccount = 1,
32 UnifiedTradingAccount10 = 3,
34 UnifiedTradingAccount10Pro = 4,
36 UnifiedTradingAccount20 = 5,
38 UnifiedTradingAccount20Pro = 6,
40}
41
42#[derive(
44 Clone,
45 Copy,
46 Debug,
47 strum::Display,
48 Eq,
49 PartialEq,
50 Hash,
51 AsRefStr,
52 EnumIter,
53 EnumString,
54 Serialize,
55 Deserialize,
56)]
57#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
58#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
59#[cfg_attr(
60 feature = "python",
61 pyo3::pyclass(
62 eq,
63 eq_int,
64 rename_all = "SCREAMING_SNAKE_CASE",
65 module = "nautilus_trader.adapters.bybit",
66 from_py_object
67 )
68)]
69#[cfg_attr(
70 feature = "python",
71 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
72)]
73pub enum BybitMarginMode {
74 IsolatedMargin,
75 RegularMargin,
76 PortfolioMargin,
77}
78
79#[derive(
81 Clone,
82 Copy,
83 Debug,
84 strum::Display,
85 Eq,
86 PartialEq,
87 Hash,
88 AsRefStr,
89 EnumIter,
90 EnumString,
91 Serialize_repr,
92 Deserialize_repr,
93)]
94#[repr(i32)]
95#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
96#[cfg_attr(
97 feature = "python",
98 pyo3::pyclass(
99 eq,
100 eq_int,
101 rename_all = "SCREAMING_SNAKE_CASE",
102 module = "nautilus_trader.adapters.bybit",
103 from_py_object
104 )
105)]
106#[cfg_attr(
107 feature = "python",
108 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
109)]
110pub enum BybitPositionMode {
111 MergedSingle = 0,
113 BothSides = 3,
115}
116
117#[derive(
119 Clone,
120 Copy,
121 Debug,
122 strum::Display,
123 Eq,
124 PartialEq,
125 Hash,
126 AsRefStr,
127 EnumIter,
128 EnumString,
129 Serialize_repr,
130 Deserialize_repr,
131)]
132#[repr(i32)]
133#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
134#[cfg_attr(
135 feature = "python",
136 pyo3::pyclass(
137 eq,
138 eq_int,
139 rename_all = "SCREAMING_SNAKE_CASE",
140 module = "nautilus_trader.adapters.bybit",
141 from_py_object
142 )
143)]
144#[cfg_attr(
145 feature = "python",
146 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
147)]
148pub enum BybitPositionIdx {
149 OneWay = 0,
151 BuyHedge = 1,
153 SellHedge = 2,
155}
156
157#[derive(
159 Copy,
160 Clone,
161 Debug,
162 strum::Display,
163 PartialEq,
164 Eq,
165 Hash,
166 AsRefStr,
167 EnumIter,
168 EnumString,
169 Serialize,
170 Deserialize,
171)]
172#[serde(rename_all = "UPPERCASE")]
173#[cfg_attr(
174 feature = "python",
175 pyo3::pyclass(
176 eq,
177 eq_int,
178 rename_all = "SCREAMING_SNAKE_CASE",
179 module = "nautilus_trader.adapters.bybit",
180 from_py_object
181 )
182)]
183#[cfg_attr(
184 feature = "python",
185 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
186)]
187pub enum BybitAccountType {
188 Unified,
189 Fund,
190}
191
192#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize_repr, Deserialize_repr)]
194#[repr(u8)]
195pub enum BybitApiKeyType {
196 Hmac = 1,
198 Rsa = 2,
200}
201
202#[derive(
204 Copy,
205 Clone,
206 Debug,
207 strum::Display,
208 PartialEq,
209 Eq,
210 Hash,
211 AsRefStr,
212 EnumIter,
213 EnumString,
214 Serialize,
215 Deserialize,
216)]
217#[serde(rename_all = "lowercase")]
218#[cfg_attr(
219 feature = "python",
220 pyo3::pyclass(
221 eq,
222 eq_int,
223 rename_all = "SCREAMING_SNAKE_CASE",
224 module = "nautilus_trader.adapters.bybit",
225 from_py_object
226 )
227)]
228#[cfg_attr(
229 feature = "python",
230 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
231)]
232pub enum BybitEnvironment {
233 Mainnet,
235 Demo,
237 Testnet,
239}
240
241#[derive(
243 Copy,
244 Clone,
245 Debug,
246 strum::Display,
247 Default,
248 PartialEq,
249 Eq,
250 Hash,
251 AsRefStr,
252 EnumIter,
253 EnumString,
254 Serialize,
255 Deserialize,
256)]
257#[serde(rename_all = "lowercase")]
258#[cfg_attr(
259 feature = "python",
260 pyo3::pyclass(
261 eq,
262 eq_int,
263 rename_all = "SCREAMING_SNAKE_CASE",
264 module = "nautilus_trader.adapters.bybit",
265 from_py_object
266 )
267)]
268#[cfg_attr(
269 feature = "python",
270 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
271)]
272pub enum BybitProductType {
273 #[default]
274 Spot,
275 Linear,
276 Inverse,
277 Option,
278}
279
280#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
282pub enum BybitMarginTrading {
283 #[serde(rename = "none")]
284 None,
285 #[serde(rename = "utaOnly")]
286 UtaOnly,
287 #[serde(rename = "both")]
288 Both,
289 #[serde(rename = "normalSpotOnly")]
290 NormalSpotOnly,
291 #[serde(other)]
292 Other,
293}
294
295#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
297pub enum BybitInnovationFlag {
298 #[serde(rename = "0")]
299 Standard,
300 #[serde(rename = "1")]
301 Innovation,
302 #[serde(other)]
303 Other,
304}
305
306#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
308#[serde(rename_all = "PascalCase")]
309pub enum BybitInstrumentStatus {
310 PreLaunch,
311 PendingOpen,
312 Trading,
313 Delivering,
314 Closed,
315 #[serde(other)]
316 Other,
317}
318
319impl BybitProductType {
320 #[must_use]
322 pub const fn as_str(self) -> &'static str {
323 match self {
324 Self::Spot => "spot",
325 Self::Linear => "linear",
326 Self::Inverse => "inverse",
327 Self::Option => "option",
328 }
329 }
330
331 #[must_use]
333 pub const fn suffix(self) -> &'static str {
334 match self {
335 Self::Spot => "-SPOT",
336 Self::Linear => "-LINEAR",
337 Self::Inverse => "-INVERSE",
338 Self::Option => "-OPTION",
339 }
340 }
341
342 #[must_use]
344 pub fn from_suffix(symbol: &str) -> Option<Self> {
345 if symbol.ends_with("-SPOT") {
346 Some(Self::Spot)
347 } else if symbol.ends_with("-LINEAR") {
348 Some(Self::Linear)
349 } else if symbol.ends_with("-INVERSE") {
350 Some(Self::Inverse)
351 } else if symbol.ends_with("-OPTION") {
352 Some(Self::Option)
353 } else {
354 None
355 }
356 }
357
358 #[must_use]
360 pub fn is_spot(self) -> bool {
361 matches!(self, Self::Spot)
362 }
363
364 #[must_use]
366 pub fn is_linear(self) -> bool {
367 matches!(self, Self::Linear)
368 }
369
370 #[must_use]
372 pub fn is_inverse(self) -> bool {
373 matches!(self, Self::Inverse)
374 }
375
376 #[must_use]
378 pub fn is_option(self) -> bool {
379 matches!(self, Self::Option)
380 }
381}
382
383#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
385#[serde(rename_all = "PascalCase")]
386pub enum BybitContractType {
387 LinearPerpetual,
388 LinearFutures,
389 InversePerpetual,
390 InverseFutures,
391}
392
393#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
395#[serde(rename_all = "PascalCase")]
396pub enum BybitOptionType {
397 Call,
398 Put,
399}
400
401#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
409#[serde(rename_all = "lowercase")]
410pub enum BybitSymbolType {
411 Innovation,
413 Adventure,
415 Xstocks,
417 Commodity,
419 Stock,
421 Forex,
423 #[serde(rename = "ETF")]
425 Etf,
426 Mstocks,
428 #[serde(other)]
430 Other,
431}
432
433impl BybitSymbolType {
434 #[must_use]
436 pub(crate) const fn as_str(self) -> Option<&'static str> {
437 match self {
438 Self::Innovation => Some("innovation"),
439 Self::Adventure => Some("adventure"),
440 Self::Xstocks => Some("xstocks"),
441 Self::Commodity => Some("commodity"),
442 Self::Stock => Some("stock"),
443 Self::Forex => Some("forex"),
444 Self::Etf => Some("ETF"),
445 Self::Mstocks => Some("mstocks"),
446 Self::Other => None,
447 }
448 }
449}
450
451impl From<BybitSymbolType> for AssetClass {
452 fn from(symbol_type: BybitSymbolType) -> Self {
453 match symbol_type {
454 BybitSymbolType::Innovation | BybitSymbolType::Adventure => Self::Cryptocurrency,
455 BybitSymbolType::Stock
456 | BybitSymbolType::Etf
457 | BybitSymbolType::Xstocks
458 | BybitSymbolType::Mstocks => Self::Equity,
459 BybitSymbolType::Commodity => Self::Commodity,
460 BybitSymbolType::Forex => Self::FX,
461 BybitSymbolType::Other => Self::Alternative,
462 }
463 }
464}
465
466#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
468pub enum BybitPositionSide {
469 #[serde(rename = "")]
470 Flat,
471 #[serde(rename = "Buy")]
472 Buy,
473 #[serde(rename = "Sell")]
474 Sell,
475}
476
477#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
479pub enum BybitWsOrderRequestOp {
480 #[serde(rename = "order.create")]
481 Create,
482 #[serde(rename = "order.amend")]
483 Amend,
484 #[serde(rename = "order.cancel")]
485 Cancel,
486 #[serde(rename = "order.create-batch")]
487 CreateBatch,
488 #[serde(rename = "order.amend-batch")]
489 AmendBatch,
490 #[serde(rename = "order.cancel-batch")]
491 CancelBatch,
492}
493
494#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
496pub enum BybitKlineInterval {
497 #[serde(rename = "1")]
498 Minute1,
499 #[serde(rename = "3")]
500 Minute3,
501 #[serde(rename = "5")]
502 Minute5,
503 #[serde(rename = "15")]
504 Minute15,
505 #[serde(rename = "30")]
506 Minute30,
507 #[serde(rename = "60")]
508 Hour1,
509 #[serde(rename = "120")]
510 Hour2,
511 #[serde(rename = "240")]
512 Hour4,
513 #[serde(rename = "360")]
514 Hour6,
515 #[serde(rename = "720")]
516 Hour12,
517 #[serde(rename = "D")]
518 Day1,
519 #[serde(rename = "W")]
520 Week1,
521 #[serde(rename = "M")]
522 Month1,
523}
524
525impl BybitKlineInterval {
526 #[must_use]
532 pub fn bar_end_time_ms(&self, start_ms: i64) -> i64 {
533 match self {
534 Self::Month1 => {
535 let start_dt = Offset::UTC.to_datetime(
536 Timestamp::from_millisecond(start_ms).unwrap_or(Timestamp::UNIX_EPOCH),
537 );
538 let (year, month) = if start_dt.month() == 12 {
539 (start_dt.year() + 1, 1)
540 } else {
541 (start_dt.year(), start_dt.month() + 1)
542 };
543 Date::new(year, month, 1)
544 .and_then(|date| Offset::UTC.to_timestamp(date.at(0, 0, 0, 0)))
545 .map_or(start_ms + 2_678_400_000, Timestamp::as_millisecond)
546 }
547 _ => start_ms + self.duration_ms(),
548 }
549 }
550
551 #[must_use]
556 pub const fn duration_ms(&self) -> i64 {
557 match self {
558 Self::Minute1 => 60_000,
559 Self::Minute3 => 180_000,
560 Self::Minute5 => 300_000,
561 Self::Minute15 => 900_000,
562 Self::Minute30 => 1_800_000,
563 Self::Hour1 => 3_600_000,
564 Self::Hour2 => 7_200_000,
565 Self::Hour4 => 14_400_000,
566 Self::Hour6 => 21_600_000,
567 Self::Hour12 => 43_200_000,
568 Self::Day1 => 86_400_000,
569 Self::Week1 => 604_800_000,
570 Self::Month1 => 2_678_400_000, }
572 }
573}
574
575impl Display for BybitKlineInterval {
576 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
577 let s = match self {
578 Self::Minute1 => "1",
579 Self::Minute3 => "3",
580 Self::Minute5 => "5",
581 Self::Minute15 => "15",
582 Self::Minute30 => "30",
583 Self::Hour1 => "60",
584 Self::Hour2 => "120",
585 Self::Hour4 => "240",
586 Self::Hour6 => "360",
587 Self::Hour12 => "720",
588 Self::Day1 => "D",
589 Self::Week1 => "W",
590 Self::Month1 => "M",
591 };
592 write!(f, "{s}")
593 }
594}
595
596#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
598#[cfg_attr(
599 feature = "python",
600 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
601)]
602#[cfg_attr(
603 feature = "python",
604 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
605)]
606pub enum BybitOrderStatus {
607 #[serde(rename = "Created")]
608 Created,
609 #[serde(rename = "New")]
610 New,
611 #[serde(rename = "Rejected")]
612 Rejected,
613 #[serde(rename = "PartiallyFilled")]
614 PartiallyFilled,
615 #[serde(rename = "PartiallyFilledCanceled")]
616 PartiallyFilledCanceled,
617 #[serde(rename = "Filled")]
618 Filled,
619 #[serde(rename = "Cancelled")]
620 Canceled,
621 #[serde(rename = "Untriggered")]
622 Untriggered,
623 #[serde(rename = "Triggered")]
624 Triggered,
625 #[serde(rename = "Deactivated")]
626 Deactivated,
627}
628
629#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
631#[cfg_attr(
632 feature = "python",
633 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
634)]
635#[cfg_attr(
636 feature = "python",
637 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
638)]
639pub enum BybitOrderSide {
640 #[serde(rename = "")]
641 Unknown,
642 #[serde(rename = "Buy")]
643 Buy,
644 #[serde(rename = "Sell")]
645 Sell,
646}
647
648impl From<BybitOrderSide> for AggressorSide {
649 fn from(value: BybitOrderSide) -> Self {
650 match value {
651 BybitOrderSide::Buy => Self::Buy,
652 BybitOrderSide::Sell => Self::Sell,
653 BybitOrderSide::Unknown => Self::NoAggressor,
654 }
655 }
656}
657
658impl From<BybitOrderSide> for Option<OrderSide> {
659 fn from(value: BybitOrderSide) -> Self {
660 match value {
661 BybitOrderSide::Buy => Some(OrderSide::Buy),
662 BybitOrderSide::Sell => Some(OrderSide::Sell),
663 BybitOrderSide::Unknown => None,
664 }
665 }
666}
667
668impl TryFrom<BybitOrderSide> for OrderSide {
669 type Error = anyhow::Error;
670
671 fn try_from(value: BybitOrderSide) -> Result<Self, Self::Error> {
672 match value {
673 BybitOrderSide::Buy => Ok(Self::Buy),
674 BybitOrderSide::Sell => Ok(Self::Sell),
675 BybitOrderSide::Unknown => anyhow::bail!("Unspecified Bybit order side"),
676 }
677 }
678}
679
680impl From<OrderSide> for BybitOrderSide {
681 fn from(value: OrderSide) -> Self {
682 match value {
683 OrderSide::Buy => Self::Buy,
684 OrderSide::Sell => Self::Sell,
685 }
686 }
687}
688
689impl From<BybitTriggerType> for TriggerType {
690 fn from(value: BybitTriggerType) -> Self {
691 match value {
692 BybitTriggerType::None => Self::Default,
693 BybitTriggerType::LastPrice => Self::LastPrice,
694 BybitTriggerType::IndexPrice => Self::IndexPrice,
695 BybitTriggerType::MarkPrice => Self::MarkPrice,
696 }
697 }
698}
699
700impl From<TriggerType> for BybitTriggerType {
701 fn from(value: TriggerType) -> Self {
702 match value {
703 TriggerType::Default | TriggerType::LastPrice => Self::LastPrice,
704 TriggerType::IndexPrice => Self::IndexPrice,
705 TriggerType::MarkPrice => Self::MarkPrice,
706 _ => Self::LastPrice,
707 }
708 }
709}
710
711pub fn resolve_trigger_type(trigger_type: Option<TriggerType>) -> BybitTriggerType {
714 trigger_type.map_or(BybitTriggerType::LastPrice, BybitTriggerType::from)
715}
716
717#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
719#[serde(rename_all = "PascalCase")]
720#[cfg_attr(
721 feature = "python",
722 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
723)]
724#[cfg_attr(
725 feature = "python",
726 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
727)]
728pub enum BybitCancelType {
729 CancelByUser,
730 CancelByReduceOnly,
731 CancelByPrepareLiq,
732 CancelAllBeforeLiq,
733 CancelByPrepareAdl,
734 CancelAllBeforeAdl,
735 CancelByPrepareLackOfMargin,
736 CancelByPrepareOrderFilter,
737 CancelByPrepareOrderMarginCheckFailed,
738 CancelByPrepareOrderCommission,
739 CancelByPrepareOrderRms,
740 CancelByPrepareOrderOther,
741 CancelByRiskLimit,
742 CancelOnDisconnect,
743 CancelByStopOrdersExceeded,
744 CancelByPzMarketClose,
745 CancelByMarginCheckFailed,
746 CancelByPzTakeover,
747 CancelByAdmin,
748 CancelBySettle,
749 CancelByTpSlTsClear,
750 CancelByAmendNotModified,
751 CancelByPzCancel,
752 CancelBySmp,
753 #[serde(rename = "CancelByDCP")]
754 CancelByDcp,
755 CancelByRebalance,
756 #[serde(rename = "CancelByOCOTpCanceledBySlTriggered")]
757 CancelByOcoTpCanceledBySlTriggered,
758 #[serde(rename = "CancelByOCOSlCanceledByTpTriggered")]
759 CancelByOcoSlCanceledByTpTriggered,
760 CancelByCannotAffordOrderCost,
761 CancelByPmTrialMmOverEquity,
762 CancelByAccountBlocking,
763 CancelByDelivery,
764 CancelByMmpTriggered,
765 CancelByCrossSelfMuch,
766 CancelByCrossReachMaxTradeNum,
767 CancelByCrossSelfMatch,
768 CancelBySelfMatchPrevention,
769 #[serde(other)]
770 Other,
771}
772
773#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
775#[serde(rename_all = "PascalCase")]
776pub enum BybitCreateType {
777 CreateByUser,
778 CreateByFutureSpread,
779 CreateByAdminClosing,
780 CreateBySettle,
781 CreateByClosing,
782 CreateByTakeProfit,
783 CreateByStopLoss,
784 CreateByTrailingStop,
785 CreateByTrailingProfit,
786 CreateByStopOrder,
787 CreateByPartialTakeProfit,
788 CreateByPartialStopLoss,
789 CreateByAdl,
790 #[serde(rename = "CreateByAdl_PassThrough")]
791 CreateByAdlPassThrough,
792 CreateByLiq,
793 CreateByLiquidate,
794 CreateByTakeover,
795 #[serde(rename = "CreateByTakeOver_PassThrough")]
796 CreateByTakeOverPassThrough,
797 #[serde(rename = "CreateByBlock_PassThrough")]
798 CreateByBlockPassThrough,
799 #[serde(rename = "CreateByBlockTradeMovePosition_PassThrough")]
800 CreateByBlockTradeMovePositionPassThrough,
801 CreateByChaseOrder,
802 #[serde(rename = "CreateByFGridBot")]
803 CreateByFGridBot,
804 #[serde(rename = "CloseByFGridBot")]
805 CloseByFGridBot,
806 #[serde(rename = "CreateByTWAP")]
807 CreateByTwap,
808 CreateByMartingaleBot,
809 CloseByMartingaleBot,
810 #[serde(rename = "CreateByIceBerg")]
811 CreateByIceBerg,
812 CreateByBboOrder,
813 #[serde(rename = "CreateByTVSignal")]
814 CreateByTvSignal,
815 CreateByMmRateClose,
816 CreateByArbitrage,
817 CreateByDdh,
818 CreateByTpsl,
819 #[serde(other)]
820 Other,
821}
822
823#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
825#[serde(rename_all = "PascalCase")]
826pub enum BybitBboSideType {
827 Queue,
828 Counterparty,
829}
830
831#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
833#[cfg_attr(
834 feature = "python",
835 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
836)]
837#[cfg_attr(
838 feature = "python",
839 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
840)]
841pub enum BybitOrderType {
842 #[serde(rename = "Market")]
843 Market,
844 #[serde(rename = "Limit")]
845 Limit,
846 #[serde(rename = "UNKNOWN")]
847 Unknown,
848}
849
850#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
852#[cfg_attr(
853 feature = "python",
854 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
855)]
856#[cfg_attr(
857 feature = "python",
858 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
859)]
860pub enum BybitStopOrderType {
861 #[serde(rename = "")]
862 None,
863 #[serde(rename = "UNKNOWN")]
864 Unknown,
865 #[serde(rename = "TakeProfit")]
866 TakeProfit,
867 #[serde(rename = "StopLoss")]
868 StopLoss,
869 #[serde(rename = "TrailingStop")]
870 TrailingStop,
871 #[serde(rename = "Stop")]
872 Stop,
873 #[serde(rename = "PartialTakeProfit")]
874 PartialTakeProfit,
875 #[serde(rename = "PartialStopLoss")]
876 PartialStopLoss,
877 #[serde(rename = "tpslOrder")]
878 TpslOrder,
879 #[serde(rename = "OcoOrder")]
880 OcoOrder,
881 #[serde(rename = "MmRateClose")]
882 MmRateClose,
883 #[serde(rename = "BidirectionalTpslOrder")]
884 BidirectionalTpslOrder,
885}
886
887#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
889#[cfg_attr(
890 feature = "python",
891 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
892)]
893#[cfg_attr(
894 feature = "python",
895 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
896)]
897pub enum BybitTriggerType {
898 #[serde(rename = "")]
899 None,
900 #[serde(rename = "LastPrice")]
901 LastPrice,
902 #[serde(rename = "IndexPrice")]
903 IndexPrice,
904 #[serde(rename = "MarkPrice")]
905 MarkPrice,
906}
907
908#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize_repr, Deserialize_repr)]
910#[repr(i32)]
911#[cfg_attr(
912 feature = "python",
913 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
914)]
915#[cfg_attr(
916 feature = "python",
917 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
918)]
919pub enum BybitTriggerDirection {
920 None = 0,
921 RisesTo = 1,
922 FallsTo = 2,
923}
924
925#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
927#[serde(rename_all = "PascalCase")]
928#[cfg_attr(
929 feature = "python",
930 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
931)]
932#[cfg_attr(
933 feature = "python",
934 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
935)]
936pub enum BybitTpSlMode {
937 Full,
938 Partial,
939 #[serde(other)]
940 Unknown,
941}
942
943#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
945#[cfg_attr(
946 feature = "python",
947 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
948)]
949#[cfg_attr(
950 feature = "python",
951 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
952)]
953pub enum BybitTimeInForce {
954 #[serde(rename = "GTC")]
955 Gtc,
956 #[serde(rename = "IOC")]
957 Ioc,
958 #[serde(rename = "FOK")]
959 Fok,
960 #[serde(rename = "PostOnly")]
961 PostOnly,
962 #[serde(rename = "RPI")]
963 Rpi,
964}
965
966#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
970pub enum BybitExecType {
971 #[serde(rename = "Trade")]
972 Trade,
973 #[serde(rename = "AdlTrade")]
974 AdlTrade,
975 #[serde(rename = "Funding")]
976 Funding,
977 #[serde(rename = "BustTrade")]
978 BustTrade,
979 #[serde(rename = "Delivery")]
980 Delivery,
981 #[serde(rename = "Settle")]
982 Settle,
983 #[serde(rename = "BlockTrade")]
984 BlockTrade,
985 #[serde(rename = "MovePosition")]
986 MovePosition,
987 #[serde(rename = "FutureSpread")]
988 FutureSpread,
989 #[serde(rename = "CorporateAction")]
993 CorporateAction,
994 #[serde(rename = "ForwardSplitSettle")]
995 ForwardSplitSettle,
996 #[serde(rename = "ReverseSplitSettle")]
997 ReverseSplitSettle,
998 #[serde(rename = "Dividend")]
999 Dividend,
1000 #[serde(other)]
1001 #[serde(rename = "UNKNOWN")]
1002 Unknown,
1003}
1004
1005impl BybitExecType {
1006 #[must_use]
1012 pub const fn is_exchange_generated(&self) -> bool {
1013 matches!(
1014 self,
1015 Self::AdlTrade
1016 | Self::BustTrade
1017 | Self::Delivery
1018 | Self::Settle
1019 | Self::CorporateAction
1020 | Self::ForwardSplitSettle
1021 | Self::ReverseSplitSettle
1022 | Self::Dividend
1023 )
1024 }
1025}
1026
1027#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
1029pub enum BybitTransactionType {
1030 #[serde(rename = "TRANSFER_IN")]
1031 TransferIn,
1032 #[serde(rename = "TRANSFER_OUT")]
1033 TransferOut,
1034 #[serde(rename = "TRADE")]
1035 Trade,
1036 #[serde(rename = "SETTLEMENT")]
1037 Settlement,
1038 #[serde(rename = "DELIVERY")]
1039 Delivery,
1040 #[serde(rename = "LIQUIDATION")]
1041 Liquidation,
1042 #[serde(rename = "AIRDRP")]
1043 Airdrop,
1044 #[serde(rename = "ADL")]
1045 Adl,
1046 #[serde(rename = "DIVIDEND_SETTLEMENT")]
1047 DividendSettlement,
1048 #[serde(rename = "FORWARD_SPLIT_SETTLE")]
1049 ForwardSplitSettle,
1050 #[serde(rename = "REVERSE_SPLIT_SETTLE")]
1051 ReverseSplitSettle,
1052 #[serde(other)]
1053 Other,
1054}
1055
1056#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
1058#[serde(rename_all = "UPPERCASE")]
1059pub enum BybitEndpointType {
1060 None,
1061 Asset,
1062 Market,
1063 Account,
1064 Trade,
1065 Position,
1066 User,
1067}
1068
1069#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Serialize_repr, Deserialize_repr)]
1073#[repr(i32)]
1074#[cfg_attr(
1075 feature = "python",
1076 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
1077)]
1078#[cfg_attr(
1079 feature = "python",
1080 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
1081)]
1082pub enum BybitOpenOnly {
1083 #[default]
1085 OpenOnly = 0,
1086 ClosedRecent = 1,
1088}
1089
1090#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize)]
1094#[cfg_attr(
1095 feature = "python",
1096 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
1097)]
1098#[cfg_attr(
1099 feature = "python",
1100 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
1101)]
1102pub enum BybitOrderFilter {
1103 #[default]
1105 Order,
1106 StopOrder,
1108 #[serde(rename = "tpslOrder")]
1110 TpslOrder,
1111 OcoOrder,
1113 BidirectionalTpslOrder,
1115}
1116
1117#[derive(
1119 Clone,
1120 Copy,
1121 Debug,
1122 strum::Display,
1123 Eq,
1124 PartialEq,
1125 Hash,
1126 AsRefStr,
1127 EnumIter,
1128 EnumString,
1129 Serialize,
1130 Deserialize,
1131)]
1132#[serde(rename_all = "snake_case")]
1133#[strum(serialize_all = "snake_case")]
1134#[cfg_attr(
1135 feature = "python",
1136 pyo3::pyclass(
1137 eq,
1138 eq_int,
1139 hash,
1140 frozen,
1141 rename_all = "SCREAMING_SNAKE_CASE",
1142 module = "nautilus_trader.adapters.bybit",
1143 from_py_object,
1144 )
1145)]
1146#[cfg_attr(
1147 feature = "python",
1148 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
1149)]
1150pub enum BybitMarginAction {
1151 Borrow,
1153 Repay,
1155 GetBorrowAmount,
1157}
1158
1159#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, strum::Display, Serialize, Deserialize)]
1161pub enum BybitRepayStatus {
1162 #[serde(rename = "P")]
1164 #[strum(serialize = "P")]
1165 Processing,
1166 #[serde(rename = "SU")]
1168 #[strum(serialize = "SU")]
1169 Success,
1170 #[serde(rename = "FA")]
1172 #[strum(serialize = "FA")]
1173 Failed,
1174}
1175
1176#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
1178#[serde(rename_all = "PascalCase")]
1179pub enum BybitPositionStatus {
1180 Normal,
1181 Liq,
1182 Adl,
1183 Settle,
1184 Delivering,
1185 #[serde(other)]
1186 Other,
1187}
1188
1189#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
1191pub enum BybitMarketUnit {
1192 #[serde(rename = "baseCoin")]
1193 BaseCoin,
1194 #[serde(rename = "quoteCoin")]
1195 QuoteCoin,
1196}
1197
1198#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
1203pub enum BybitSmpType {
1204 None,
1205 CancelMaker,
1206 CancelTaker,
1207 CancelBoth,
1208 #[serde(other)]
1209 Other,
1210}
1211
1212#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, AsRefStr, Serialize, Deserialize)]
1217pub enum BybitOrderSmpType {
1218 None,
1219 CancelMaker,
1220 CancelTaker,
1221 CancelBoth,
1222}
1223
1224#[cfg(test)]
1225mod tests {
1226 use rstest::rstest;
1227
1228 use super::*;
1229
1230 #[rstest]
1231 #[case::minute1(BybitKlineInterval::Minute1, 60_000)]
1232 #[case::minute3(BybitKlineInterval::Minute3, 180_000)]
1233 #[case::minute5(BybitKlineInterval::Minute5, 300_000)]
1234 #[case::minute15(BybitKlineInterval::Minute15, 900_000)]
1235 #[case::minute30(BybitKlineInterval::Minute30, 1_800_000)]
1236 #[case::hour1(BybitKlineInterval::Hour1, 3_600_000)]
1237 #[case::hour2(BybitKlineInterval::Hour2, 7_200_000)]
1238 #[case::hour4(BybitKlineInterval::Hour4, 14_400_000)]
1239 #[case::hour6(BybitKlineInterval::Hour6, 21_600_000)]
1240 #[case::hour12(BybitKlineInterval::Hour12, 43_200_000)]
1241 #[case::day1(BybitKlineInterval::Day1, 86_400_000)]
1242 #[case::week1(BybitKlineInterval::Week1, 604_800_000)]
1243 #[case::month1(BybitKlineInterval::Month1, 2_678_400_000)]
1244 fn test_kline_interval_duration_ms(
1245 #[case] interval: BybitKlineInterval,
1246 #[case] expected_ms: i64,
1247 ) {
1248 assert_eq!(interval.duration_ms(), expected_ms);
1249 }
1250
1251 #[rstest]
1252 fn test_bar_end_time_ms_non_monthly_adds_duration() {
1253 let interval = BybitKlineInterval::Minute1;
1254 let start_ms = 1704067200000i64;
1255 assert_eq!(interval.bar_end_time_ms(start_ms), start_ms + 60_000);
1256 }
1257
1258 #[rstest]
1259 #[case::jan_31_days(1704067200000i64, 1706745600000i64)]
1260 #[case::feb_leap_year_29_days(1706745600000i64, 1709251200000i64)]
1261 #[case::apr_30_days(1711929600000i64, 1714521600000i64)]
1262 #[case::dec_to_next_year(1733011200000i64, 1735689600000i64)]
1263 fn test_bar_end_time_ms_monthly_variable_lengths(
1264 #[case] start_ms: i64,
1265 #[case] expected_end_ms: i64,
1266 ) {
1267 let interval = BybitKlineInterval::Month1;
1268 assert_eq!(interval.bar_end_time_ms(start_ms), expected_end_ms);
1269 }
1270
1271 #[rstest]
1272 #[case(BybitSymbolType::Innovation, "innovation")]
1273 #[case(BybitSymbolType::Adventure, "adventure")]
1274 #[case(BybitSymbolType::Xstocks, "xstocks")]
1275 #[case(BybitSymbolType::Commodity, "commodity")]
1276 #[case(BybitSymbolType::Stock, "stock")]
1277 #[case(BybitSymbolType::Forex, "forex")]
1278 #[case(BybitSymbolType::Etf, "ETF")]
1279 #[case(BybitSymbolType::Mstocks, "mstocks")]
1280 fn test_symbol_type_round_trip(#[case] symbol_type: BybitSymbolType, #[case] wire_value: &str) {
1281 let value = serde_json::Value::String(wire_value.to_string());
1282
1283 assert_eq!(
1284 serde_json::from_value::<BybitSymbolType>(value.clone()).unwrap(),
1285 symbol_type
1286 );
1287 assert_eq!(serde_json::to_value(symbol_type).unwrap(), value);
1288 assert_eq!(symbol_type.as_str(), Some(wire_value));
1289 }
1290
1291 #[rstest]
1292 fn test_unknown_symbol_type_has_no_wire_value() {
1293 assert_eq!(BybitSymbolType::Other.as_str(), None);
1294 }
1295
1296 #[rstest]
1297 #[case(BybitSymbolType::Innovation, AssetClass::Cryptocurrency)]
1298 #[case(BybitSymbolType::Adventure, AssetClass::Cryptocurrency)]
1299 #[case(BybitSymbolType::Stock, AssetClass::Equity)]
1300 #[case(BybitSymbolType::Etf, AssetClass::Equity)]
1301 #[case(BybitSymbolType::Xstocks, AssetClass::Equity)]
1302 #[case(BybitSymbolType::Mstocks, AssetClass::Equity)]
1303 #[case(BybitSymbolType::Commodity, AssetClass::Commodity)]
1304 #[case(BybitSymbolType::Forex, AssetClass::FX)]
1305 #[case(BybitSymbolType::Other, AssetClass::Alternative)]
1306 fn test_symbol_type_to_asset_class(
1307 #[case] symbol_type: BybitSymbolType,
1308 #[case] expected: AssetClass,
1309 ) {
1310 assert_eq!(AssetClass::from(symbol_type), expected);
1311 }
1312
1313 #[rstest]
1314 #[case(BybitExecType::Trade, false)]
1315 #[case(BybitExecType::AdlTrade, true)]
1316 #[case(BybitExecType::BustTrade, true)]
1317 #[case(BybitExecType::Delivery, true)]
1318 #[case(BybitExecType::Settle, true)]
1319 #[case(BybitExecType::Funding, false)]
1320 #[case(BybitExecType::BlockTrade, false)]
1321 #[case(BybitExecType::MovePosition, false)]
1322 #[case(BybitExecType::FutureSpread, false)]
1323 #[case(BybitExecType::CorporateAction, true)]
1324 #[case(BybitExecType::ForwardSplitSettle, true)]
1325 #[case(BybitExecType::ReverseSplitSettle, true)]
1326 #[case(BybitExecType::Dividend, true)]
1327 #[case(BybitExecType::Unknown, false)]
1328 fn test_exec_type_is_exchange_generated(
1329 #[case] exec_type: BybitExecType,
1330 #[case] expected: bool,
1331 ) {
1332 assert_eq!(exec_type.is_exchange_generated(), expected);
1333 }
1334
1335 #[rstest]
1336 #[case(BybitExecType::Trade, "Trade")]
1337 #[case(BybitExecType::AdlTrade, "AdlTrade")]
1338 #[case(BybitExecType::Funding, "Funding")]
1339 #[case(BybitExecType::BustTrade, "BustTrade")]
1340 #[case(BybitExecType::Delivery, "Delivery")]
1341 #[case(BybitExecType::Settle, "Settle")]
1342 #[case(BybitExecType::BlockTrade, "BlockTrade")]
1343 #[case(BybitExecType::MovePosition, "MovePosition")]
1344 #[case(BybitExecType::FutureSpread, "FutureSpread")]
1345 #[case(BybitExecType::CorporateAction, "CorporateAction")]
1346 #[case(BybitExecType::ForwardSplitSettle, "ForwardSplitSettle")]
1347 #[case(BybitExecType::ReverseSplitSettle, "ReverseSplitSettle")]
1348 #[case(BybitExecType::Dividend, "Dividend")]
1349 #[case(BybitExecType::Unknown, "UNKNOWN")]
1350 fn test_exec_type_round_trip(#[case] exec_type: BybitExecType, #[case] wire_value: &str) {
1351 let value = serde_json::Value::String(wire_value.to_string());
1352
1353 assert_eq!(
1354 serde_json::from_value::<BybitExecType>(value.clone()).unwrap(),
1355 exec_type
1356 );
1357 assert_eq!(serde_json::to_value(exec_type).unwrap(), value);
1358 }
1359
1360 #[rstest]
1361 fn test_exec_type_unrecognized_value_deserializes_to_unknown() {
1362 let value = serde_json::Value::String("StockMerger".to_string());
1363
1364 assert_eq!(
1365 serde_json::from_value::<BybitExecType>(value).unwrap(),
1366 BybitExecType::Unknown
1367 );
1368 }
1369
1370 #[rstest]
1371 #[case(BybitAccountType::Unified, "UNIFIED")]
1372 #[case(BybitAccountType::Fund, "FUND")]
1373 fn test_account_type_round_trip(
1374 #[case] account_type: BybitAccountType,
1375 #[case] wire_value: &str,
1376 ) {
1377 let value = serde_json::Value::String(wire_value.to_string());
1378
1379 assert_eq!(
1380 serde_json::from_value::<BybitAccountType>(value.clone()).unwrap(),
1381 account_type
1382 );
1383 assert_eq!(serde_json::to_value(account_type).unwrap(), value);
1384 }
1385
1386 #[rstest]
1387 #[case(BybitTimeInForce::Gtc, "GTC")]
1388 #[case(BybitTimeInForce::Ioc, "IOC")]
1389 #[case(BybitTimeInForce::Fok, "FOK")]
1390 #[case(BybitTimeInForce::PostOnly, "PostOnly")]
1391 #[case(BybitTimeInForce::Rpi, "RPI")]
1392 fn test_time_in_force_round_trip(#[case] tif: BybitTimeInForce, #[case] wire_value: &str) {
1393 let value = serde_json::Value::String(wire_value.to_string());
1394
1395 assert_eq!(
1396 serde_json::from_value::<BybitTimeInForce>(value.clone()).unwrap(),
1397 tif
1398 );
1399 assert_eq!(serde_json::to_value(tif).unwrap(), value);
1400 }
1401
1402 #[rstest]
1403 #[case("CreateByAdl_PassThrough", BybitCreateType::CreateByAdlPassThrough)]
1404 #[case("CreateByLiq", BybitCreateType::CreateByLiq)]
1405 #[case(
1406 "CreateByTakeOver_PassThrough",
1407 BybitCreateType::CreateByTakeOverPassThrough
1408 )]
1409 #[case("CreateByTWAP", BybitCreateType::CreateByTwap)]
1410 #[case("CreateByFutureSpread", BybitCreateType::CreateByFutureSpread)]
1411 fn test_create_type_venue_wire_values(
1412 #[case] wire_value: &str,
1413 #[case] expected: BybitCreateType,
1414 ) {
1415 let value = serde_json::Value::String(wire_value.to_string());
1416
1417 assert_eq!(
1418 serde_json::from_value::<BybitCreateType>(value.clone()).unwrap(),
1419 expected
1420 );
1421 assert_eq!(serde_json::to_value(expected).unwrap(), value);
1422 }
1423
1424 #[rstest]
1425 #[case("CancelBySettle", BybitCancelType::CancelBySettle)]
1426 #[case("CancelBySmp", BybitCancelType::CancelBySmp)]
1427 #[case("CancelByDCP", BybitCancelType::CancelByDcp)]
1428 #[case("CancelAllBeforeLiq", BybitCancelType::CancelAllBeforeLiq)]
1429 #[case(
1430 "CancelByOCOTpCanceledBySlTriggered",
1431 BybitCancelType::CancelByOcoTpCanceledBySlTriggered
1432 )]
1433 fn test_cancel_type_venue_wire_values(
1434 #[case] wire_value: &str,
1435 #[case] expected: BybitCancelType,
1436 ) {
1437 let value = serde_json::Value::String(wire_value.to_string());
1438
1439 assert_eq!(
1440 serde_json::from_value::<BybitCancelType>(value.clone()).unwrap(),
1441 expected
1442 );
1443 assert_eq!(serde_json::to_value(expected).unwrap(), value);
1444 }
1445
1446 #[rstest]
1447 fn test_pending_open_instrument_status_round_trip() {
1448 let value = serde_json::Value::String("PendingOpen".to_string());
1449
1450 assert_eq!(
1451 serde_json::from_value::<BybitInstrumentStatus>(value.clone()).unwrap(),
1452 BybitInstrumentStatus::PendingOpen
1453 );
1454 assert_eq!(
1455 serde_json::to_value(BybitInstrumentStatus::PendingOpen).unwrap(),
1456 value
1457 );
1458 }
1459
1460 #[rstest]
1461 fn test_normal_spot_only_margin_trading_round_trip() {
1462 let value = serde_json::Value::String("normalSpotOnly".to_string());
1463
1464 assert_eq!(
1465 serde_json::from_value::<BybitMarginTrading>(value.clone()).unwrap(),
1466 BybitMarginTrading::NormalSpotOnly
1467 );
1468 assert_eq!(
1469 serde_json::to_value(BybitMarginTrading::NormalSpotOnly).unwrap(),
1470 value
1471 );
1472 }
1473
1474 #[rstest]
1475 #[case(BybitPositionStatus::Liq, "Liq")]
1476 #[case(BybitPositionStatus::Adl, "Adl")]
1477 fn test_position_status_liq_adl_round_trip(
1478 #[case] status: BybitPositionStatus,
1479 #[case] wire_value: &str,
1480 ) {
1481 let value = serde_json::Value::String(wire_value.to_string());
1482
1483 assert_eq!(
1484 serde_json::from_value::<BybitPositionStatus>(value.clone()).unwrap(),
1485 status
1486 );
1487 assert_eq!(serde_json::to_value(status).unwrap(), value);
1488 }
1489}