1use std::fmt::Display;
19
20use jiff::{Timestamp, civil::Date, tz::Offset};
21use nautilus_model::enums::{AggressorSide, 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
451#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
453pub enum BybitPositionSide {
454 #[serde(rename = "")]
455 Flat,
456 #[serde(rename = "Buy")]
457 Buy,
458 #[serde(rename = "Sell")]
459 Sell,
460}
461
462#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
464pub enum BybitWsOrderRequestOp {
465 #[serde(rename = "order.create")]
466 Create,
467 #[serde(rename = "order.amend")]
468 Amend,
469 #[serde(rename = "order.cancel")]
470 Cancel,
471 #[serde(rename = "order.create-batch")]
472 CreateBatch,
473 #[serde(rename = "order.amend-batch")]
474 AmendBatch,
475 #[serde(rename = "order.cancel-batch")]
476 CancelBatch,
477}
478
479#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
481pub enum BybitKlineInterval {
482 #[serde(rename = "1")]
483 Minute1,
484 #[serde(rename = "3")]
485 Minute3,
486 #[serde(rename = "5")]
487 Minute5,
488 #[serde(rename = "15")]
489 Minute15,
490 #[serde(rename = "30")]
491 Minute30,
492 #[serde(rename = "60")]
493 Hour1,
494 #[serde(rename = "120")]
495 Hour2,
496 #[serde(rename = "240")]
497 Hour4,
498 #[serde(rename = "360")]
499 Hour6,
500 #[serde(rename = "720")]
501 Hour12,
502 #[serde(rename = "D")]
503 Day1,
504 #[serde(rename = "W")]
505 Week1,
506 #[serde(rename = "M")]
507 Month1,
508}
509
510impl BybitKlineInterval {
511 #[must_use]
517 pub fn bar_end_time_ms(&self, start_ms: i64) -> i64 {
518 match self {
519 Self::Month1 => {
520 let start_dt = Offset::UTC.to_datetime(
521 Timestamp::from_millisecond(start_ms).unwrap_or(Timestamp::UNIX_EPOCH),
522 );
523 let (year, month) = if start_dt.month() == 12 {
524 (start_dt.year() + 1, 1)
525 } else {
526 (start_dt.year(), start_dt.month() + 1)
527 };
528 Date::new(year, month, 1)
529 .and_then(|date| Offset::UTC.to_timestamp(date.at(0, 0, 0, 0)))
530 .map_or(start_ms + 2_678_400_000, Timestamp::as_millisecond)
531 }
532 _ => start_ms + self.duration_ms(),
533 }
534 }
535
536 #[must_use]
541 pub const fn duration_ms(&self) -> i64 {
542 match self {
543 Self::Minute1 => 60_000,
544 Self::Minute3 => 180_000,
545 Self::Minute5 => 300_000,
546 Self::Minute15 => 900_000,
547 Self::Minute30 => 1_800_000,
548 Self::Hour1 => 3_600_000,
549 Self::Hour2 => 7_200_000,
550 Self::Hour4 => 14_400_000,
551 Self::Hour6 => 21_600_000,
552 Self::Hour12 => 43_200_000,
553 Self::Day1 => 86_400_000,
554 Self::Week1 => 604_800_000,
555 Self::Month1 => 2_678_400_000, }
557 }
558}
559
560impl Display for BybitKlineInterval {
561 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
562 let s = match self {
563 Self::Minute1 => "1",
564 Self::Minute3 => "3",
565 Self::Minute5 => "5",
566 Self::Minute15 => "15",
567 Self::Minute30 => "30",
568 Self::Hour1 => "60",
569 Self::Hour2 => "120",
570 Self::Hour4 => "240",
571 Self::Hour6 => "360",
572 Self::Hour12 => "720",
573 Self::Day1 => "D",
574 Self::Week1 => "W",
575 Self::Month1 => "M",
576 };
577 write!(f, "{s}")
578 }
579}
580
581#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
583#[cfg_attr(
584 feature = "python",
585 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
586)]
587#[cfg_attr(
588 feature = "python",
589 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
590)]
591pub enum BybitOrderStatus {
592 #[serde(rename = "Created")]
593 Created,
594 #[serde(rename = "New")]
595 New,
596 #[serde(rename = "Rejected")]
597 Rejected,
598 #[serde(rename = "PartiallyFilled")]
599 PartiallyFilled,
600 #[serde(rename = "PartiallyFilledCanceled")]
601 PartiallyFilledCanceled,
602 #[serde(rename = "Filled")]
603 Filled,
604 #[serde(rename = "Cancelled")]
605 Canceled,
606 #[serde(rename = "Untriggered")]
607 Untriggered,
608 #[serde(rename = "Triggered")]
609 Triggered,
610 #[serde(rename = "Deactivated")]
611 Deactivated,
612}
613
614#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
616#[cfg_attr(
617 feature = "python",
618 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
619)]
620#[cfg_attr(
621 feature = "python",
622 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
623)]
624pub enum BybitOrderSide {
625 #[serde(rename = "")]
626 Unknown,
627 #[serde(rename = "Buy")]
628 Buy,
629 #[serde(rename = "Sell")]
630 Sell,
631}
632
633impl From<BybitOrderSide> for AggressorSide {
634 fn from(value: BybitOrderSide) -> Self {
635 match value {
636 BybitOrderSide::Buy => Self::Buy,
637 BybitOrderSide::Sell => Self::Sell,
638 BybitOrderSide::Unknown => Self::NoAggressor,
639 }
640 }
641}
642
643impl From<BybitOrderSide> for Option<OrderSide> {
644 fn from(value: BybitOrderSide) -> Self {
645 match value {
646 BybitOrderSide::Buy => Some(OrderSide::Buy),
647 BybitOrderSide::Sell => Some(OrderSide::Sell),
648 BybitOrderSide::Unknown => None,
649 }
650 }
651}
652
653impl TryFrom<BybitOrderSide> for OrderSide {
654 type Error = anyhow::Error;
655
656 fn try_from(value: BybitOrderSide) -> Result<Self, Self::Error> {
657 match value {
658 BybitOrderSide::Buy => Ok(Self::Buy),
659 BybitOrderSide::Sell => Ok(Self::Sell),
660 BybitOrderSide::Unknown => anyhow::bail!("Unspecified Bybit order side"),
661 }
662 }
663}
664
665impl From<OrderSide> for BybitOrderSide {
666 fn from(value: OrderSide) -> Self {
667 match value {
668 OrderSide::Buy => Self::Buy,
669 OrderSide::Sell => Self::Sell,
670 }
671 }
672}
673
674impl From<BybitTriggerType> for TriggerType {
675 fn from(value: BybitTriggerType) -> Self {
676 match value {
677 BybitTriggerType::None => Self::Default,
678 BybitTriggerType::LastPrice => Self::LastPrice,
679 BybitTriggerType::IndexPrice => Self::IndexPrice,
680 BybitTriggerType::MarkPrice => Self::MarkPrice,
681 }
682 }
683}
684
685impl From<TriggerType> for BybitTriggerType {
686 fn from(value: TriggerType) -> Self {
687 match value {
688 TriggerType::Default | TriggerType::LastPrice => Self::LastPrice,
689 TriggerType::IndexPrice => Self::IndexPrice,
690 TriggerType::MarkPrice => Self::MarkPrice,
691 _ => Self::LastPrice,
692 }
693 }
694}
695
696pub fn resolve_trigger_type(trigger_type: Option<TriggerType>) -> BybitTriggerType {
699 trigger_type.map_or(BybitTriggerType::LastPrice, BybitTriggerType::from)
700}
701
702#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
704#[serde(rename_all = "PascalCase")]
705#[cfg_attr(
706 feature = "python",
707 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
708)]
709#[cfg_attr(
710 feature = "python",
711 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
712)]
713pub enum BybitCancelType {
714 CancelByUser,
715 CancelByReduceOnly,
716 CancelByPrepareLiq,
717 CancelAllBeforeLiq,
718 CancelByPrepareAdl,
719 CancelAllBeforeAdl,
720 CancelByPrepareLackOfMargin,
721 CancelByPrepareOrderFilter,
722 CancelByPrepareOrderMarginCheckFailed,
723 CancelByPrepareOrderCommission,
724 CancelByPrepareOrderRms,
725 CancelByPrepareOrderOther,
726 CancelByRiskLimit,
727 CancelOnDisconnect,
728 CancelByStopOrdersExceeded,
729 CancelByPzMarketClose,
730 CancelByMarginCheckFailed,
731 CancelByPzTakeover,
732 CancelByAdmin,
733 CancelBySettle,
734 CancelByTpSlTsClear,
735 CancelByAmendNotModified,
736 CancelByPzCancel,
737 CancelBySmp,
738 #[serde(rename = "CancelByDCP")]
739 CancelByDcp,
740 CancelByRebalance,
741 #[serde(rename = "CancelByOCOTpCanceledBySlTriggered")]
742 CancelByOcoTpCanceledBySlTriggered,
743 #[serde(rename = "CancelByOCOSlCanceledByTpTriggered")]
744 CancelByOcoSlCanceledByTpTriggered,
745 CancelByCannotAffordOrderCost,
746 CancelByPmTrialMmOverEquity,
747 CancelByAccountBlocking,
748 CancelByDelivery,
749 CancelByMmpTriggered,
750 CancelByCrossSelfMuch,
751 CancelByCrossReachMaxTradeNum,
752 CancelByCrossSelfMatch,
753 CancelBySelfMatchPrevention,
754 #[serde(other)]
755 Other,
756}
757
758#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
760#[serde(rename_all = "PascalCase")]
761pub enum BybitCreateType {
762 CreateByUser,
763 CreateByFutureSpread,
764 CreateByAdminClosing,
765 CreateBySettle,
766 CreateByClosing,
767 CreateByTakeProfit,
768 CreateByStopLoss,
769 CreateByTrailingStop,
770 CreateByTrailingProfit,
771 CreateByStopOrder,
772 CreateByPartialTakeProfit,
773 CreateByPartialStopLoss,
774 CreateByAdl,
775 #[serde(rename = "CreateByAdl_PassThrough")]
776 CreateByAdlPassThrough,
777 CreateByLiq,
778 CreateByLiquidate,
779 CreateByTakeover,
780 #[serde(rename = "CreateByTakeOver_PassThrough")]
781 CreateByTakeOverPassThrough,
782 #[serde(rename = "CreateByBlock_PassThrough")]
783 CreateByBlockPassThrough,
784 #[serde(rename = "CreateByBlockTradeMovePosition_PassThrough")]
785 CreateByBlockTradeMovePositionPassThrough,
786 CreateByChaseOrder,
787 #[serde(rename = "CreateByFGridBot")]
788 CreateByFGridBot,
789 #[serde(rename = "CloseByFGridBot")]
790 CloseByFGridBot,
791 #[serde(rename = "CreateByTWAP")]
792 CreateByTwap,
793 CreateByMartingaleBot,
794 CloseByMartingaleBot,
795 #[serde(rename = "CreateByIceBerg")]
796 CreateByIceBerg,
797 CreateByBboOrder,
798 #[serde(rename = "CreateByTVSignal")]
799 CreateByTvSignal,
800 CreateByMmRateClose,
801 CreateByArbitrage,
802 CreateByDdh,
803 CreateByTpsl,
804 #[serde(other)]
805 Other,
806}
807
808#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
810#[serde(rename_all = "PascalCase")]
811pub enum BybitBboSideType {
812 Queue,
813 Counterparty,
814}
815
816#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
818#[cfg_attr(
819 feature = "python",
820 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
821)]
822#[cfg_attr(
823 feature = "python",
824 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
825)]
826pub enum BybitOrderType {
827 #[serde(rename = "Market")]
828 Market,
829 #[serde(rename = "Limit")]
830 Limit,
831 #[serde(rename = "UNKNOWN")]
832 Unknown,
833}
834
835#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
837#[cfg_attr(
838 feature = "python",
839 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
840)]
841#[cfg_attr(
842 feature = "python",
843 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
844)]
845pub enum BybitStopOrderType {
846 #[serde(rename = "")]
847 None,
848 #[serde(rename = "UNKNOWN")]
849 Unknown,
850 #[serde(rename = "TakeProfit")]
851 TakeProfit,
852 #[serde(rename = "StopLoss")]
853 StopLoss,
854 #[serde(rename = "TrailingStop")]
855 TrailingStop,
856 #[serde(rename = "Stop")]
857 Stop,
858 #[serde(rename = "PartialTakeProfit")]
859 PartialTakeProfit,
860 #[serde(rename = "PartialStopLoss")]
861 PartialStopLoss,
862 #[serde(rename = "tpslOrder")]
863 TpslOrder,
864 #[serde(rename = "OcoOrder")]
865 OcoOrder,
866 #[serde(rename = "MmRateClose")]
867 MmRateClose,
868 #[serde(rename = "BidirectionalTpslOrder")]
869 BidirectionalTpslOrder,
870}
871
872#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
874#[cfg_attr(
875 feature = "python",
876 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
877)]
878#[cfg_attr(
879 feature = "python",
880 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
881)]
882pub enum BybitTriggerType {
883 #[serde(rename = "")]
884 None,
885 #[serde(rename = "LastPrice")]
886 LastPrice,
887 #[serde(rename = "IndexPrice")]
888 IndexPrice,
889 #[serde(rename = "MarkPrice")]
890 MarkPrice,
891}
892
893#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize_repr, Deserialize_repr)]
895#[repr(i32)]
896#[cfg_attr(
897 feature = "python",
898 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
899)]
900#[cfg_attr(
901 feature = "python",
902 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
903)]
904pub enum BybitTriggerDirection {
905 None = 0,
906 RisesTo = 1,
907 FallsTo = 2,
908}
909
910#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
912#[serde(rename_all = "PascalCase")]
913#[cfg_attr(
914 feature = "python",
915 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
916)]
917#[cfg_attr(
918 feature = "python",
919 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
920)]
921pub enum BybitTpSlMode {
922 Full,
923 Partial,
924 #[serde(other)]
925 Unknown,
926}
927
928#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
930#[cfg_attr(
931 feature = "python",
932 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
933)]
934#[cfg_attr(
935 feature = "python",
936 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
937)]
938pub enum BybitTimeInForce {
939 #[serde(rename = "GTC")]
940 Gtc,
941 #[serde(rename = "IOC")]
942 Ioc,
943 #[serde(rename = "FOK")]
944 Fok,
945 #[serde(rename = "PostOnly")]
946 PostOnly,
947 #[serde(rename = "RPI")]
948 Rpi,
949}
950
951#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
955pub enum BybitExecType {
956 #[serde(rename = "Trade")]
957 Trade,
958 #[serde(rename = "AdlTrade")]
959 AdlTrade,
960 #[serde(rename = "Funding")]
961 Funding,
962 #[serde(rename = "BustTrade")]
963 BustTrade,
964 #[serde(rename = "Delivery")]
965 Delivery,
966 #[serde(rename = "Settle")]
967 Settle,
968 #[serde(rename = "BlockTrade")]
969 BlockTrade,
970 #[serde(rename = "MovePosition")]
971 MovePosition,
972 #[serde(rename = "FutureSpread")]
973 FutureSpread,
974 #[serde(rename = "CorporateAction")]
978 CorporateAction,
979 #[serde(rename = "ForwardSplitSettle")]
980 ForwardSplitSettle,
981 #[serde(rename = "ReverseSplitSettle")]
982 ReverseSplitSettle,
983 #[serde(rename = "Dividend")]
984 Dividend,
985 #[serde(other)]
986 #[serde(rename = "UNKNOWN")]
987 Unknown,
988}
989
990impl BybitExecType {
991 #[must_use]
997 pub const fn is_exchange_generated(&self) -> bool {
998 matches!(
999 self,
1000 Self::AdlTrade
1001 | Self::BustTrade
1002 | Self::Delivery
1003 | Self::Settle
1004 | Self::CorporateAction
1005 | Self::ForwardSplitSettle
1006 | Self::ReverseSplitSettle
1007 | Self::Dividend
1008 )
1009 }
1010}
1011
1012#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
1014pub enum BybitTransactionType {
1015 #[serde(rename = "TRANSFER_IN")]
1016 TransferIn,
1017 #[serde(rename = "TRANSFER_OUT")]
1018 TransferOut,
1019 #[serde(rename = "TRADE")]
1020 Trade,
1021 #[serde(rename = "SETTLEMENT")]
1022 Settlement,
1023 #[serde(rename = "DELIVERY")]
1024 Delivery,
1025 #[serde(rename = "LIQUIDATION")]
1026 Liquidation,
1027 #[serde(rename = "AIRDRP")]
1028 Airdrop,
1029 #[serde(rename = "ADL")]
1030 Adl,
1031 #[serde(rename = "DIVIDEND_SETTLEMENT")]
1032 DividendSettlement,
1033 #[serde(rename = "FORWARD_SPLIT_SETTLE")]
1034 ForwardSplitSettle,
1035 #[serde(rename = "REVERSE_SPLIT_SETTLE")]
1036 ReverseSplitSettle,
1037 #[serde(other)]
1038 Other,
1039}
1040
1041#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
1043#[serde(rename_all = "UPPERCASE")]
1044pub enum BybitEndpointType {
1045 None,
1046 Asset,
1047 Market,
1048 Account,
1049 Trade,
1050 Position,
1051 User,
1052}
1053
1054#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Serialize_repr, Deserialize_repr)]
1058#[repr(i32)]
1059#[cfg_attr(
1060 feature = "python",
1061 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
1062)]
1063#[cfg_attr(
1064 feature = "python",
1065 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
1066)]
1067pub enum BybitOpenOnly {
1068 #[default]
1070 OpenOnly = 0,
1071 ClosedRecent = 1,
1073}
1074
1075#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize)]
1079#[cfg_attr(
1080 feature = "python",
1081 pyo3::pyclass(module = "nautilus_trader.adapters.bybit", eq, eq_int, from_py_object)
1082)]
1083#[cfg_attr(
1084 feature = "python",
1085 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
1086)]
1087pub enum BybitOrderFilter {
1088 #[default]
1090 Order,
1091 StopOrder,
1093 #[serde(rename = "tpslOrder")]
1095 TpslOrder,
1096 OcoOrder,
1098 BidirectionalTpslOrder,
1100}
1101
1102#[derive(
1104 Clone,
1105 Copy,
1106 Debug,
1107 strum::Display,
1108 Eq,
1109 PartialEq,
1110 Hash,
1111 AsRefStr,
1112 EnumIter,
1113 EnumString,
1114 Serialize,
1115 Deserialize,
1116)]
1117#[serde(rename_all = "snake_case")]
1118#[strum(serialize_all = "snake_case")]
1119#[cfg_attr(
1120 feature = "python",
1121 pyo3::pyclass(
1122 eq,
1123 eq_int,
1124 hash,
1125 frozen,
1126 rename_all = "SCREAMING_SNAKE_CASE",
1127 module = "nautilus_trader.adapters.bybit",
1128 from_py_object,
1129 )
1130)]
1131#[cfg_attr(
1132 feature = "python",
1133 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.adapters.bybit")
1134)]
1135pub enum BybitMarginAction {
1136 Borrow,
1138 Repay,
1140 GetBorrowAmount,
1142}
1143
1144#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, strum::Display, Serialize, Deserialize)]
1146pub enum BybitRepayStatus {
1147 #[serde(rename = "P")]
1149 #[strum(serialize = "P")]
1150 Processing,
1151 #[serde(rename = "SU")]
1153 #[strum(serialize = "SU")]
1154 Success,
1155 #[serde(rename = "FA")]
1157 #[strum(serialize = "FA")]
1158 Failed,
1159}
1160
1161#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
1163#[serde(rename_all = "PascalCase")]
1164pub enum BybitPositionStatus {
1165 Normal,
1166 Liq,
1167 Adl,
1168 Settle,
1169 Delivering,
1170 #[serde(other)]
1171 Other,
1172}
1173
1174#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
1176pub enum BybitMarketUnit {
1177 #[serde(rename = "baseCoin")]
1178 BaseCoin,
1179 #[serde(rename = "quoteCoin")]
1180 QuoteCoin,
1181}
1182
1183#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
1188pub enum BybitSmpType {
1189 None,
1190 CancelMaker,
1191 CancelTaker,
1192 CancelBoth,
1193 #[serde(other)]
1194 Other,
1195}
1196
1197#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, AsRefStr, Serialize, Deserialize)]
1202pub enum BybitOrderSmpType {
1203 None,
1204 CancelMaker,
1205 CancelTaker,
1206 CancelBoth,
1207}
1208
1209#[cfg(test)]
1210mod tests {
1211 use rstest::rstest;
1212
1213 use super::*;
1214
1215 #[rstest]
1216 #[case::minute1(BybitKlineInterval::Minute1, 60_000)]
1217 #[case::minute3(BybitKlineInterval::Minute3, 180_000)]
1218 #[case::minute5(BybitKlineInterval::Minute5, 300_000)]
1219 #[case::minute15(BybitKlineInterval::Minute15, 900_000)]
1220 #[case::minute30(BybitKlineInterval::Minute30, 1_800_000)]
1221 #[case::hour1(BybitKlineInterval::Hour1, 3_600_000)]
1222 #[case::hour2(BybitKlineInterval::Hour2, 7_200_000)]
1223 #[case::hour4(BybitKlineInterval::Hour4, 14_400_000)]
1224 #[case::hour6(BybitKlineInterval::Hour6, 21_600_000)]
1225 #[case::hour12(BybitKlineInterval::Hour12, 43_200_000)]
1226 #[case::day1(BybitKlineInterval::Day1, 86_400_000)]
1227 #[case::week1(BybitKlineInterval::Week1, 604_800_000)]
1228 #[case::month1(BybitKlineInterval::Month1, 2_678_400_000)]
1229 fn test_kline_interval_duration_ms(
1230 #[case] interval: BybitKlineInterval,
1231 #[case] expected_ms: i64,
1232 ) {
1233 assert_eq!(interval.duration_ms(), expected_ms);
1234 }
1235
1236 #[rstest]
1237 fn test_bar_end_time_ms_non_monthly_adds_duration() {
1238 let interval = BybitKlineInterval::Minute1;
1239 let start_ms = 1704067200000i64;
1240 assert_eq!(interval.bar_end_time_ms(start_ms), start_ms + 60_000);
1241 }
1242
1243 #[rstest]
1244 #[case::jan_31_days(1704067200000i64, 1706745600000i64)]
1245 #[case::feb_leap_year_29_days(1706745600000i64, 1709251200000i64)]
1246 #[case::apr_30_days(1711929600000i64, 1714521600000i64)]
1247 #[case::dec_to_next_year(1733011200000i64, 1735689600000i64)]
1248 fn test_bar_end_time_ms_monthly_variable_lengths(
1249 #[case] start_ms: i64,
1250 #[case] expected_end_ms: i64,
1251 ) {
1252 let interval = BybitKlineInterval::Month1;
1253 assert_eq!(interval.bar_end_time_ms(start_ms), expected_end_ms);
1254 }
1255
1256 #[rstest]
1257 #[case(BybitSymbolType::Innovation, "innovation")]
1258 #[case(BybitSymbolType::Adventure, "adventure")]
1259 #[case(BybitSymbolType::Xstocks, "xstocks")]
1260 #[case(BybitSymbolType::Commodity, "commodity")]
1261 #[case(BybitSymbolType::Stock, "stock")]
1262 #[case(BybitSymbolType::Forex, "forex")]
1263 #[case(BybitSymbolType::Etf, "ETF")]
1264 #[case(BybitSymbolType::Mstocks, "mstocks")]
1265 fn test_symbol_type_round_trip(#[case] symbol_type: BybitSymbolType, #[case] wire_value: &str) {
1266 let value = serde_json::Value::String(wire_value.to_string());
1267
1268 assert_eq!(
1269 serde_json::from_value::<BybitSymbolType>(value.clone()).unwrap(),
1270 symbol_type
1271 );
1272 assert_eq!(serde_json::to_value(symbol_type).unwrap(), value);
1273 assert_eq!(symbol_type.as_str(), Some(wire_value));
1274 }
1275
1276 #[rstest]
1277 fn test_unknown_symbol_type_has_no_wire_value() {
1278 assert_eq!(BybitSymbolType::Other.as_str(), None);
1279 }
1280
1281 #[rstest]
1282 #[case(BybitExecType::Trade, false)]
1283 #[case(BybitExecType::AdlTrade, true)]
1284 #[case(BybitExecType::BustTrade, true)]
1285 #[case(BybitExecType::Delivery, true)]
1286 #[case(BybitExecType::Settle, true)]
1287 #[case(BybitExecType::Funding, false)]
1288 #[case(BybitExecType::BlockTrade, false)]
1289 #[case(BybitExecType::MovePosition, false)]
1290 #[case(BybitExecType::FutureSpread, false)]
1291 #[case(BybitExecType::CorporateAction, true)]
1292 #[case(BybitExecType::ForwardSplitSettle, true)]
1293 #[case(BybitExecType::ReverseSplitSettle, true)]
1294 #[case(BybitExecType::Dividend, true)]
1295 #[case(BybitExecType::Unknown, false)]
1296 fn test_exec_type_is_exchange_generated(
1297 #[case] exec_type: BybitExecType,
1298 #[case] expected: bool,
1299 ) {
1300 assert_eq!(exec_type.is_exchange_generated(), expected);
1301 }
1302
1303 #[rstest]
1304 #[case(BybitExecType::Trade, "Trade")]
1305 #[case(BybitExecType::AdlTrade, "AdlTrade")]
1306 #[case(BybitExecType::Funding, "Funding")]
1307 #[case(BybitExecType::BustTrade, "BustTrade")]
1308 #[case(BybitExecType::Delivery, "Delivery")]
1309 #[case(BybitExecType::Settle, "Settle")]
1310 #[case(BybitExecType::BlockTrade, "BlockTrade")]
1311 #[case(BybitExecType::MovePosition, "MovePosition")]
1312 #[case(BybitExecType::FutureSpread, "FutureSpread")]
1313 #[case(BybitExecType::CorporateAction, "CorporateAction")]
1314 #[case(BybitExecType::ForwardSplitSettle, "ForwardSplitSettle")]
1315 #[case(BybitExecType::ReverseSplitSettle, "ReverseSplitSettle")]
1316 #[case(BybitExecType::Dividend, "Dividend")]
1317 #[case(BybitExecType::Unknown, "UNKNOWN")]
1318 fn test_exec_type_round_trip(#[case] exec_type: BybitExecType, #[case] wire_value: &str) {
1319 let value = serde_json::Value::String(wire_value.to_string());
1320
1321 assert_eq!(
1322 serde_json::from_value::<BybitExecType>(value.clone()).unwrap(),
1323 exec_type
1324 );
1325 assert_eq!(serde_json::to_value(exec_type).unwrap(), value);
1326 }
1327
1328 #[rstest]
1329 fn test_exec_type_unrecognized_value_deserializes_to_unknown() {
1330 let value = serde_json::Value::String("StockMerger".to_string());
1331
1332 assert_eq!(
1333 serde_json::from_value::<BybitExecType>(value).unwrap(),
1334 BybitExecType::Unknown
1335 );
1336 }
1337
1338 #[rstest]
1339 #[case(BybitAccountType::Unified, "UNIFIED")]
1340 #[case(BybitAccountType::Fund, "FUND")]
1341 fn test_account_type_round_trip(
1342 #[case] account_type: BybitAccountType,
1343 #[case] wire_value: &str,
1344 ) {
1345 let value = serde_json::Value::String(wire_value.to_string());
1346
1347 assert_eq!(
1348 serde_json::from_value::<BybitAccountType>(value.clone()).unwrap(),
1349 account_type
1350 );
1351 assert_eq!(serde_json::to_value(account_type).unwrap(), value);
1352 }
1353
1354 #[rstest]
1355 #[case(BybitTimeInForce::Gtc, "GTC")]
1356 #[case(BybitTimeInForce::Ioc, "IOC")]
1357 #[case(BybitTimeInForce::Fok, "FOK")]
1358 #[case(BybitTimeInForce::PostOnly, "PostOnly")]
1359 #[case(BybitTimeInForce::Rpi, "RPI")]
1360 fn test_time_in_force_round_trip(#[case] tif: BybitTimeInForce, #[case] wire_value: &str) {
1361 let value = serde_json::Value::String(wire_value.to_string());
1362
1363 assert_eq!(
1364 serde_json::from_value::<BybitTimeInForce>(value.clone()).unwrap(),
1365 tif
1366 );
1367 assert_eq!(serde_json::to_value(tif).unwrap(), value);
1368 }
1369
1370 #[rstest]
1371 #[case("CreateByAdl_PassThrough", BybitCreateType::CreateByAdlPassThrough)]
1372 #[case("CreateByLiq", BybitCreateType::CreateByLiq)]
1373 #[case(
1374 "CreateByTakeOver_PassThrough",
1375 BybitCreateType::CreateByTakeOverPassThrough
1376 )]
1377 #[case("CreateByTWAP", BybitCreateType::CreateByTwap)]
1378 #[case("CreateByFutureSpread", BybitCreateType::CreateByFutureSpread)]
1379 fn test_create_type_venue_wire_values(
1380 #[case] wire_value: &str,
1381 #[case] expected: BybitCreateType,
1382 ) {
1383 let value = serde_json::Value::String(wire_value.to_string());
1384
1385 assert_eq!(
1386 serde_json::from_value::<BybitCreateType>(value.clone()).unwrap(),
1387 expected
1388 );
1389 assert_eq!(serde_json::to_value(expected).unwrap(), value);
1390 }
1391
1392 #[rstest]
1393 #[case("CancelBySettle", BybitCancelType::CancelBySettle)]
1394 #[case("CancelBySmp", BybitCancelType::CancelBySmp)]
1395 #[case("CancelByDCP", BybitCancelType::CancelByDcp)]
1396 #[case("CancelAllBeforeLiq", BybitCancelType::CancelAllBeforeLiq)]
1397 #[case(
1398 "CancelByOCOTpCanceledBySlTriggered",
1399 BybitCancelType::CancelByOcoTpCanceledBySlTriggered
1400 )]
1401 fn test_cancel_type_venue_wire_values(
1402 #[case] wire_value: &str,
1403 #[case] expected: BybitCancelType,
1404 ) {
1405 let value = serde_json::Value::String(wire_value.to_string());
1406
1407 assert_eq!(
1408 serde_json::from_value::<BybitCancelType>(value.clone()).unwrap(),
1409 expected
1410 );
1411 assert_eq!(serde_json::to_value(expected).unwrap(), value);
1412 }
1413
1414 #[rstest]
1415 fn test_pending_open_instrument_status_round_trip() {
1416 let value = serde_json::Value::String("PendingOpen".to_string());
1417
1418 assert_eq!(
1419 serde_json::from_value::<BybitInstrumentStatus>(value.clone()).unwrap(),
1420 BybitInstrumentStatus::PendingOpen
1421 );
1422 assert_eq!(
1423 serde_json::to_value(BybitInstrumentStatus::PendingOpen).unwrap(),
1424 value
1425 );
1426 }
1427
1428 #[rstest]
1429 fn test_normal_spot_only_margin_trading_round_trip() {
1430 let value = serde_json::Value::String("normalSpotOnly".to_string());
1431
1432 assert_eq!(
1433 serde_json::from_value::<BybitMarginTrading>(value.clone()).unwrap(),
1434 BybitMarginTrading::NormalSpotOnly
1435 );
1436 assert_eq!(
1437 serde_json::to_value(BybitMarginTrading::NormalSpotOnly).unwrap(),
1438 value
1439 );
1440 }
1441
1442 #[rstest]
1443 #[case(BybitPositionStatus::Liq, "Liq")]
1444 #[case(BybitPositionStatus::Adl, "Adl")]
1445 fn test_position_status_liq_adl_round_trip(
1446 #[case] status: BybitPositionStatus,
1447 #[case] wire_value: &str,
1448 ) {
1449 let value = serde_json::Value::String(wire_value.to_string());
1450
1451 assert_eq!(
1452 serde_json::from_value::<BybitPositionStatus>(value.clone()).unwrap(),
1453 status
1454 );
1455 assert_eq!(serde_json::to_value(status).unwrap(), value);
1456 }
1457}