1use nautilus_model::{
19 data::BarSpecification,
20 enums::{BarAggregation, LiquiditySide, OrderSide, OrderStatus, OrderType, PositionSide},
21};
22use serde::{Deserialize, Serialize};
23use strum::{AsRefStr, Display, EnumIter, EnumString, IntoStaticStr};
24
25use crate::{error::DydxError, grpc::types::ChainId};
26
27#[derive(
29 Copy,
30 Clone,
31 Debug,
32 Display,
33 PartialEq,
34 Eq,
35 Hash,
36 AsRefStr,
37 EnumIter,
38 EnumString,
39 Serialize,
40 Deserialize,
41)]
42#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
43pub enum DydxOrderStatus {
44 Open,
46 Filled,
48 Canceled,
50 BestEffortCanceled,
52 PartiallyFilled,
54 BestEffortOpened,
56 Untriggered,
58}
59
60impl From<DydxOrderStatus> for OrderStatus {
61 fn from(value: DydxOrderStatus) -> Self {
62 match value {
63 DydxOrderStatus::Open | DydxOrderStatus::BestEffortOpened => Self::Accepted,
64 DydxOrderStatus::PartiallyFilled => Self::PartiallyFilled,
65 DydxOrderStatus::Filled => Self::Filled,
66 DydxOrderStatus::Canceled | DydxOrderStatus::BestEffortCanceled => Self::Canceled,
67 DydxOrderStatus::Untriggered => Self::PendingUpdate,
68 }
69 }
70}
71
72#[derive(
74 Copy,
75 Clone,
76 Debug,
77 Display,
78 PartialEq,
79 Eq,
80 Hash,
81 AsRefStr,
82 EnumIter,
83 EnumString,
84 Serialize,
85 Deserialize,
86)]
87#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
88pub enum DydxTimeInForce {
89 Gtt,
91 Fok,
93 Ioc,
95}
96
97#[derive(
99 Copy,
100 Clone,
101 Debug,
102 Display,
103 PartialEq,
104 Eq,
105 Hash,
106 AsRefStr,
107 EnumIter,
108 EnumString,
109 Serialize,
110 Deserialize,
111)]
112#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
113#[cfg_attr(
114 feature = "python",
115 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.dydx", eq, eq_int)
116)]
117pub enum DydxOrderSide {
118 Buy,
120 Sell,
122}
123
124impl TryFrom<OrderSide> for DydxOrderSide {
125 type Error = DydxError;
126
127 fn try_from(value: OrderSide) -> Result<Self, Self::Error> {
128 match value {
129 OrderSide::Buy => Ok(Self::Buy),
130 OrderSide::Sell => Ok(Self::Sell),
131 _ => Err(DydxError::InvalidOrderSide(format!("{value:?}"))),
132 }
133 }
134}
135
136impl DydxOrderSide {
137 pub fn try_from_order_side(value: OrderSide) -> anyhow::Result<Self> {
143 Self::try_from(value).map_err(|e| anyhow::anyhow!("{e}"))
144 }
145}
146
147impl From<DydxOrderSide> for OrderSide {
148 fn from(side: DydxOrderSide) -> Self {
149 match side {
150 DydxOrderSide::Buy => Self::Buy,
151 DydxOrderSide::Sell => Self::Sell,
152 }
153 }
154}
155
156#[derive(
158 Copy,
159 Clone,
160 Debug,
161 Display,
162 PartialEq,
163 Eq,
164 Hash,
165 AsRefStr,
166 EnumIter,
167 EnumString,
168 Serialize,
169 Deserialize,
170)]
171#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
172#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
173#[cfg_attr(
174 feature = "python",
175 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.dydx", eq, eq_int)
176)]
177pub enum DydxOrderType {
178 Limit,
180 Market,
182 StopLimit,
184 StopMarket,
186 TakeProfitLimit,
188 TakeProfitMarket,
190 TrailingStop,
192}
193
194impl TryFrom<OrderType> for DydxOrderType {
195 type Error = DydxError;
196
197 fn try_from(value: OrderType) -> Result<Self, Self::Error> {
198 match value {
199 OrderType::Market => Ok(Self::Market),
200 OrderType::Limit => Ok(Self::Limit),
201 OrderType::StopMarket => Ok(Self::StopMarket),
202 OrderType::StopLimit => Ok(Self::StopLimit),
203 OrderType::MarketIfTouched => Ok(Self::TakeProfitMarket),
204 OrderType::LimitIfTouched => Ok(Self::TakeProfitLimit),
205 OrderType::TrailingStopMarket | OrderType::TrailingStopLimit => Ok(Self::TrailingStop),
206 OrderType::MarketToLimit => Err(DydxError::UnsupportedOrderType(format!("{value:?}"))),
207 }
208 }
209}
210
211impl DydxOrderType {
212 pub fn try_from_order_type(value: OrderType) -> anyhow::Result<Self> {
218 Self::try_from(value).map_err(|e| anyhow::anyhow!("{e}"))
219 }
220
221 #[must_use]
223 pub const fn is_conditional(&self) -> bool {
224 matches!(
225 self,
226 Self::StopLimit
227 | Self::StopMarket
228 | Self::TakeProfitLimit
229 | Self::TakeProfitMarket
230 | Self::TrailingStop
231 )
232 }
233
234 #[must_use]
236 pub const fn condition_type(&self) -> DydxConditionType {
237 match self {
238 Self::StopLimit | Self::StopMarket => DydxConditionType::StopLoss,
239 Self::TakeProfitLimit | Self::TakeProfitMarket => DydxConditionType::TakeProfit,
240 _ => DydxConditionType::Unspecified,
241 }
242 }
243
244 #[must_use]
246 pub const fn is_market_execution(&self) -> bool {
247 matches!(
248 self,
249 Self::Market | Self::StopMarket | Self::TakeProfitMarket
250 )
251 }
252}
253
254impl From<DydxOrderType> for OrderType {
255 fn from(value: DydxOrderType) -> Self {
256 match value {
257 DydxOrderType::Market => Self::Market,
258 DydxOrderType::Limit => Self::Limit,
259 DydxOrderType::StopMarket => Self::StopMarket,
260 DydxOrderType::StopLimit => Self::StopLimit,
261 DydxOrderType::TakeProfitMarket => Self::MarketIfTouched,
262 DydxOrderType::TakeProfitLimit => Self::LimitIfTouched,
263 DydxOrderType::TrailingStop => Self::TrailingStopMarket,
264 }
265 }
266}
267
268#[derive(
270 Copy,
271 Clone,
272 Debug,
273 Display,
274 PartialEq,
275 Eq,
276 Hash,
277 AsRefStr,
278 EnumIter,
279 EnumString,
280 Serialize,
281 Deserialize,
282)]
283#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
284pub enum DydxOrderExecution {
285 Default,
287 Ioc,
289 Fok,
291 PostOnly,
293}
294
295#[derive(
297 Copy, Clone, Debug, Display, PartialEq, Eq, Hash, AsRefStr, EnumIter, Serialize, Deserialize,
298)]
299pub enum DydxOrderFlags {
300 ShortTerm = 0,
302 Conditional = 32,
304 LongTerm = 64,
306}
307
308#[derive(
314 Copy,
315 Clone,
316 Debug,
317 Display,
318 PartialEq,
319 Eq,
320 Hash,
321 AsRefStr,
322 EnumIter,
323 EnumString,
324 Serialize,
325 Deserialize,
326)]
327#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
328pub enum DydxConditionType {
329 Unspecified,
331 StopLoss,
333 TakeProfit,
335}
336
337#[derive(
339 Copy,
340 Clone,
341 Debug,
342 Display,
343 PartialEq,
344 Eq,
345 Hash,
346 AsRefStr,
347 EnumIter,
348 EnumString,
349 Serialize,
350 Deserialize,
351)]
352#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
353pub enum DydxPositionSide {
354 Long,
356 Short,
358}
359
360impl From<DydxPositionSide> for PositionSide {
361 fn from(value: DydxPositionSide) -> Self {
362 match value {
363 DydxPositionSide::Long => Self::Long,
364 DydxPositionSide::Short => Self::Short,
365 }
366 }
367}
368
369#[derive(
371 Copy,
372 Clone,
373 Debug,
374 Display,
375 PartialEq,
376 Eq,
377 Hash,
378 AsRefStr,
379 EnumIter,
380 EnumString,
381 Serialize,
382 Deserialize,
383)]
384#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
385pub enum DydxPositionStatus {
386 Open,
388 Closed,
390 Liquidated,
392}
393
394impl From<DydxPositionStatus> for PositionSide {
395 fn from(value: DydxPositionStatus) -> Self {
396 match value {
397 DydxPositionStatus::Open => Self::Long, DydxPositionStatus::Closed => Self::Flat,
399 DydxPositionStatus::Liquidated => Self::Flat,
400 }
401 }
402}
403
404#[derive(
406 Copy,
407 Clone,
408 Debug,
409 Display,
410 PartialEq,
411 Eq,
412 Hash,
413 AsRefStr,
414 EnumIter,
415 EnumString,
416 Serialize,
417 Deserialize,
418)]
419#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
420pub enum DydxMarketStatus {
421 Active,
423 Paused,
425 CancelOnly,
427 PostOnly,
429 Initializing,
431 FinalSettlement,
433}
434
435#[derive(
437 Copy,
438 Clone,
439 Debug,
440 Display,
441 PartialEq,
442 Eq,
443 Hash,
444 AsRefStr,
445 EnumIter,
446 EnumString,
447 Serialize,
448 Deserialize,
449)]
450#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
451pub enum DydxFillType {
452 Limit,
454 Liquidated,
456 Liquidation,
458 Deleveraged,
460 Offsetting,
462}
463
464#[derive(
466 Copy,
467 Clone,
468 Debug,
469 Display,
470 PartialEq,
471 Eq,
472 Hash,
473 AsRefStr,
474 EnumIter,
475 EnumString,
476 Serialize,
477 Deserialize,
478)]
479#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
480pub enum DydxLiquidity {
481 Maker,
483 Taker,
485}
486
487impl From<DydxLiquidity> for LiquiditySide {
488 fn from(value: DydxLiquidity) -> Self {
489 match value {
490 DydxLiquidity::Maker => Self::Maker,
491 DydxLiquidity::Taker => Self::Taker,
492 }
493 }
494}
495
496impl From<LiquiditySide> for DydxLiquidity {
497 fn from(value: LiquiditySide) -> Self {
498 match value {
499 LiquiditySide::Maker => Self::Maker,
500 LiquiditySide::Taker => Self::Taker,
501 LiquiditySide::NoLiquiditySide => Self::Taker, }
503 }
504}
505
506#[derive(
508 Copy,
509 Clone,
510 Debug,
511 Display,
512 PartialEq,
513 Eq,
514 Hash,
515 AsRefStr,
516 EnumIter,
517 EnumString,
518 Serialize,
519 Deserialize,
520)]
521#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
522pub enum DydxTickerType {
523 Perpetual,
525}
526
527#[derive(
531 Copy,
532 Clone,
533 Debug,
534 Display,
535 PartialEq,
536 Eq,
537 Hash,
538 AsRefStr,
539 EnumIter,
540 EnumString,
541 Serialize,
542 Deserialize,
543)]
544#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
545pub enum DydxTradeType {
546 Limit,
548 Market,
550 Liquidated,
552 TwapSuborder,
554 StopLimit,
556 TakeProfitLimit,
558}
559
560#[derive(
562 Copy,
563 Clone,
564 Debug,
565 Display,
566 PartialEq,
567 Eq,
568 Hash,
569 AsRefStr,
570 EnumIter,
571 EnumString,
572 Serialize,
573 Deserialize,
574)]
575#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
576#[cfg_attr(
577 feature = "python",
578 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.dydx", eq, eq_int)
579)]
580pub enum DydxTransferType {
581 TransferIn,
583 TransferOut,
585 Deposit,
587 Withdrawal,
589}
590
591#[derive(
593 Copy,
594 Clone,
595 Debug,
596 Display,
597 PartialEq,
598 Eq,
599 Hash,
600 AsRefStr,
601 IntoStaticStr,
602 EnumIter,
603 EnumString,
604 Serialize,
605 Deserialize,
606)]
607#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
608#[derive(Default)]
609#[cfg_attr(
610 feature = "python",
611 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.dydx", eq, eq_int)
612)]
613pub enum DydxCandleResolution {
614 #[serde(rename = "1MIN")]
616 #[strum(serialize = "1MIN")]
617 #[default]
618 OneMinute,
619 #[serde(rename = "5MINS")]
621 #[strum(serialize = "5MINS")]
622 FiveMinutes,
623 #[serde(rename = "15MINS")]
625 #[strum(serialize = "15MINS")]
626 FifteenMinutes,
627 #[serde(rename = "30MINS")]
629 #[strum(serialize = "30MINS")]
630 ThirtyMinutes,
631 #[serde(rename = "1HOUR")]
633 #[strum(serialize = "1HOUR")]
634 OneHour,
635 #[serde(rename = "4HOURS")]
637 #[strum(serialize = "4HOURS")]
638 FourHours,
639 #[serde(rename = "1DAY")]
641 #[strum(serialize = "1DAY")]
642 OneDay,
643}
644
645impl DydxCandleResolution {
646 pub fn from_bar_spec(spec: &BarSpecification) -> anyhow::Result<Self> {
652 match spec.step.get() {
653 1 => match spec.aggregation {
654 BarAggregation::Minute => Ok(Self::OneMinute),
655 BarAggregation::Hour => Ok(Self::OneHour),
656 BarAggregation::Day => Ok(Self::OneDay),
657 _ => anyhow::bail!("Unsupported bar aggregation: {:?}", spec.aggregation),
658 },
659 5 if spec.aggregation == BarAggregation::Minute => Ok(Self::FiveMinutes),
660 15 if spec.aggregation == BarAggregation::Minute => Ok(Self::FifteenMinutes),
661 30 if spec.aggregation == BarAggregation::Minute => Ok(Self::ThirtyMinutes),
662 4 if spec.aggregation == BarAggregation::Hour => Ok(Self::FourHours),
663 step => anyhow::bail!(
664 "Unsupported bar step: {step} with aggregation {:?}",
665 spec.aggregation
666 ),
667 }
668 }
669}
670
671#[cfg(test)]
672mod tests {
673 use rstest::rstest;
674
675 use super::*;
676
677 #[rstest]
678 fn test_order_status_conversion() {
679 assert_eq!(
680 OrderStatus::from(DydxOrderStatus::Open),
681 OrderStatus::Accepted
682 );
683 assert_eq!(
684 OrderStatus::from(DydxOrderStatus::Filled),
685 OrderStatus::Filled
686 );
687 assert_eq!(
688 OrderStatus::from(DydxOrderStatus::Canceled),
689 OrderStatus::Canceled
690 );
691 }
692
693 #[rstest]
694 fn test_liquidity_conversion() {
695 assert_eq!(
696 LiquiditySide::from(DydxLiquidity::Maker),
697 LiquiditySide::Maker
698 );
699 assert_eq!(
700 LiquiditySide::from(DydxLiquidity::Taker),
701 LiquiditySide::Taker
702 );
703 }
704
705 #[rstest]
706 fn test_order_type_is_conditional() {
707 assert!(DydxOrderType::StopLimit.is_conditional());
708 assert!(DydxOrderType::StopMarket.is_conditional());
709 assert!(DydxOrderType::TakeProfitLimit.is_conditional());
710 assert!(DydxOrderType::TakeProfitMarket.is_conditional());
711 assert!(DydxOrderType::TrailingStop.is_conditional());
712 assert!(!DydxOrderType::Limit.is_conditional());
713 assert!(!DydxOrderType::Market.is_conditional());
714 }
715
716 #[rstest]
717 fn test_condition_type_mapping() {
718 assert_eq!(
719 DydxOrderType::StopLimit.condition_type(),
720 DydxConditionType::StopLoss
721 );
722 assert_eq!(
723 DydxOrderType::StopMarket.condition_type(),
724 DydxConditionType::StopLoss
725 );
726 assert_eq!(
727 DydxOrderType::TakeProfitLimit.condition_type(),
728 DydxConditionType::TakeProfit
729 );
730 assert_eq!(
731 DydxOrderType::TakeProfitMarket.condition_type(),
732 DydxConditionType::TakeProfit
733 );
734 assert_eq!(
735 DydxOrderType::Limit.condition_type(),
736 DydxConditionType::Unspecified
737 );
738 }
739
740 #[rstest]
741 fn test_is_market_execution() {
742 assert!(DydxOrderType::Market.is_market_execution());
743 assert!(DydxOrderType::StopMarket.is_market_execution());
744 assert!(DydxOrderType::TakeProfitMarket.is_market_execution());
745 assert!(!DydxOrderType::Limit.is_market_execution());
746 assert!(!DydxOrderType::StopLimit.is_market_execution());
747 assert!(!DydxOrderType::TakeProfitLimit.is_market_execution());
748 }
749
750 #[rstest]
751 fn test_order_type_to_nautilus() {
752 assert_eq!(OrderType::from(DydxOrderType::Market), OrderType::Market);
753 assert_eq!(OrderType::from(DydxOrderType::Limit), OrderType::Limit);
754 assert_eq!(
755 OrderType::from(DydxOrderType::StopMarket),
756 OrderType::StopMarket
757 );
758 assert_eq!(
759 OrderType::from(DydxOrderType::StopLimit),
760 OrderType::StopLimit
761 );
762 }
763
764 #[rstest]
765 fn test_order_side_conversion_from_nautilus() {
766 assert_eq!(
767 DydxOrderSide::try_from(OrderSide::Buy).unwrap(),
768 DydxOrderSide::Buy
769 );
770 assert_eq!(
771 DydxOrderSide::try_from(OrderSide::Sell).unwrap(),
772 DydxOrderSide::Sell
773 );
774 assert!(DydxOrderSide::try_from(OrderSide::NoOrderSide).is_err());
775 }
776
777 #[rstest]
778 fn test_order_side_conversion_to_nautilus() {
779 assert_eq!(OrderSide::from(DydxOrderSide::Buy), OrderSide::Buy);
780 assert_eq!(OrderSide::from(DydxOrderSide::Sell), OrderSide::Sell);
781 }
782
783 #[rstest]
784 fn test_order_type_conversion_from_nautilus() {
785 assert_eq!(
786 DydxOrderType::try_from(OrderType::Market).unwrap(),
787 DydxOrderType::Market
788 );
789 assert_eq!(
790 DydxOrderType::try_from(OrderType::Limit).unwrap(),
791 DydxOrderType::Limit
792 );
793 assert_eq!(
794 DydxOrderType::try_from(OrderType::StopMarket).unwrap(),
795 DydxOrderType::StopMarket
796 );
797 assert_eq!(
798 DydxOrderType::try_from(OrderType::StopLimit).unwrap(),
799 DydxOrderType::StopLimit
800 );
801 assert!(DydxOrderType::try_from(OrderType::MarketToLimit).is_err());
802 }
803
804 #[rstest]
805 fn test_order_type_conversion_to_nautilus() {
806 assert_eq!(OrderType::from(DydxOrderType::Market), OrderType::Market);
807 assert_eq!(OrderType::from(DydxOrderType::Limit), OrderType::Limit);
808 assert_eq!(
809 OrderType::from(DydxOrderType::StopMarket),
810 OrderType::StopMarket
811 );
812 assert_eq!(
813 OrderType::from(DydxOrderType::StopLimit),
814 OrderType::StopLimit
815 );
816 }
817
818 #[rstest]
819 fn test_dydx_network_chain_id_mapping() {
820 assert_eq!(DydxNetwork::Mainnet.chain_id(), ChainId::Mainnet1);
822 assert_eq!(DydxNetwork::Testnet.chain_id(), ChainId::Testnet4);
823 }
824
825 #[rstest]
826 fn test_dydx_network_as_str() {
827 assert_eq!(DydxNetwork::Mainnet.as_str(), "mainnet");
829 assert_eq!(DydxNetwork::Testnet.as_str(), "testnet");
830 }
831
832 #[rstest]
833 fn test_dydx_network_default() {
834 assert_eq!(DydxNetwork::default(), DydxNetwork::Mainnet);
836 }
837
838 #[rstest]
839 fn test_dydx_network_serde_lowercase() {
840 let mainnet = DydxNetwork::Mainnet;
842 let json = serde_json::to_string(&mainnet).unwrap();
843 assert_eq!(json, "\"mainnet\"");
844
845 let deserialized: DydxNetwork = serde_json::from_str("\"mainnet\"").unwrap();
846 assert_eq!(deserialized, DydxNetwork::Mainnet);
847
848 let testnet = DydxNetwork::Testnet;
849 let json = serde_json::to_string(&testnet).unwrap();
850 assert_eq!(json, "\"testnet\"");
851
852 let deserialized: DydxNetwork = serde_json::from_str("\"testnet\"").unwrap();
853 assert_eq!(deserialized, DydxNetwork::Testnet);
854 }
855}
856
857#[derive(
861 Copy,
862 Clone,
863 Debug,
864 Default,
865 Display,
866 PartialEq,
867 Eq,
868 Hash,
869 AsRefStr,
870 EnumString,
871 Serialize,
872 Deserialize,
873)]
874#[strum(serialize_all = "lowercase")]
875#[serde(rename_all = "lowercase")]
876#[cfg_attr(
877 feature = "python",
878 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.dydx")
879)]
880pub enum DydxNetwork {
881 #[default]
883 Mainnet,
884 Testnet,
886}
887
888impl DydxNetwork {
889 #[must_use]
891 pub const fn chain_id(self) -> ChainId {
892 match self {
893 Self::Mainnet => ChainId::Mainnet1,
894 Self::Testnet => ChainId::Testnet4,
895 }
896 }
897
898 #[must_use]
900 pub const fn as_str(self) -> &'static str {
901 match self {
902 Self::Mainnet => "mainnet",
903 Self::Testnet => "testnet",
904 }
905 }
906}