1use nautilus_model::enums::{LiquiditySide, OrderSide, OrderStatus, OrderType, PositionSide};
19use serde::{Deserialize, Serialize};
20use strum::{AsRefStr, Display, EnumIter, EnumString};
21
22use crate::{error::DydxError, grpc::types::ChainId};
23
24#[derive(
26 Copy,
27 Clone,
28 Debug,
29 Display,
30 PartialEq,
31 Eq,
32 Hash,
33 AsRefStr,
34 EnumIter,
35 EnumString,
36 Serialize,
37 Deserialize,
38)]
39#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
40pub enum DydxOrderStatus {
41 Open,
43 Filled,
45 Canceled,
47 BestEffortCanceled,
49 PartiallyFilled,
51 BestEffortOpened,
53 Untriggered,
55}
56
57impl From<DydxOrderStatus> for OrderStatus {
58 fn from(value: DydxOrderStatus) -> Self {
59 match value {
60 DydxOrderStatus::Open | DydxOrderStatus::BestEffortOpened => Self::Accepted,
61 DydxOrderStatus::PartiallyFilled => Self::PartiallyFilled,
62 DydxOrderStatus::Filled => Self::Filled,
63 DydxOrderStatus::Canceled | DydxOrderStatus::BestEffortCanceled => Self::Canceled,
64 DydxOrderStatus::Untriggered => Self::PendingUpdate,
65 }
66 }
67}
68
69#[derive(
71 Copy,
72 Clone,
73 Debug,
74 Display,
75 PartialEq,
76 Eq,
77 Hash,
78 AsRefStr,
79 EnumIter,
80 EnumString,
81 Serialize,
82 Deserialize,
83)]
84#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
85pub enum DydxTimeInForce {
86 Gtt,
88 Fok,
90 Ioc,
92}
93
94#[derive(
96 Copy,
97 Clone,
98 Debug,
99 Display,
100 PartialEq,
101 Eq,
102 Hash,
103 AsRefStr,
104 EnumIter,
105 EnumString,
106 Serialize,
107 Deserialize,
108)]
109#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
110#[cfg_attr(
111 feature = "python",
112 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.dydx", eq, eq_int)
113)]
114pub enum DydxOrderSide {
115 Buy,
117 Sell,
119}
120
121impl TryFrom<OrderSide> for DydxOrderSide {
122 type Error = DydxError;
123
124 fn try_from(value: OrderSide) -> Result<Self, Self::Error> {
125 match value {
126 OrderSide::Buy => Ok(Self::Buy),
127 OrderSide::Sell => Ok(Self::Sell),
128 _ => Err(DydxError::InvalidOrderSide(format!("{value:?}"))),
129 }
130 }
131}
132
133impl DydxOrderSide {
134 pub fn try_from_order_side(value: OrderSide) -> anyhow::Result<Self> {
140 Self::try_from(value).map_err(|e| anyhow::anyhow!("{e}"))
141 }
142}
143
144impl From<DydxOrderSide> for OrderSide {
145 fn from(side: DydxOrderSide) -> Self {
146 match side {
147 DydxOrderSide::Buy => Self::Buy,
148 DydxOrderSide::Sell => Self::Sell,
149 }
150 }
151}
152
153#[derive(
155 Copy,
156 Clone,
157 Debug,
158 Display,
159 PartialEq,
160 Eq,
161 Hash,
162 AsRefStr,
163 EnumIter,
164 EnumString,
165 Serialize,
166 Deserialize,
167)]
168#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
169#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
170#[cfg_attr(
171 feature = "python",
172 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.dydx", eq, eq_int)
173)]
174pub enum DydxOrderType {
175 Limit,
177 Market,
179 StopLimit,
181 StopMarket,
183 TakeProfitLimit,
185 TakeProfitMarket,
187 TrailingStop,
189}
190
191impl TryFrom<OrderType> for DydxOrderType {
192 type Error = DydxError;
193
194 fn try_from(value: OrderType) -> Result<Self, Self::Error> {
195 match value {
196 OrderType::Market => Ok(Self::Market),
197 OrderType::Limit => Ok(Self::Limit),
198 OrderType::StopMarket => Ok(Self::StopMarket),
199 OrderType::StopLimit => Ok(Self::StopLimit),
200 OrderType::MarketIfTouched => Ok(Self::TakeProfitMarket),
201 OrderType::LimitIfTouched => Ok(Self::TakeProfitLimit),
202 OrderType::TrailingStopMarket | OrderType::TrailingStopLimit => Ok(Self::TrailingStop),
203 OrderType::MarketToLimit => Err(DydxError::UnsupportedOrderType(format!("{value:?}"))),
204 }
205 }
206}
207
208impl DydxOrderType {
209 pub fn try_from_order_type(value: OrderType) -> anyhow::Result<Self> {
215 Self::try_from(value).map_err(|e| anyhow::anyhow!("{e}"))
216 }
217
218 #[must_use]
220 pub const fn is_conditional(&self) -> bool {
221 matches!(
222 self,
223 Self::StopLimit
224 | Self::StopMarket
225 | Self::TakeProfitLimit
226 | Self::TakeProfitMarket
227 | Self::TrailingStop
228 )
229 }
230
231 #[must_use]
233 pub const fn condition_type(&self) -> DydxConditionType {
234 match self {
235 Self::StopLimit | Self::StopMarket => DydxConditionType::StopLoss,
236 Self::TakeProfitLimit | Self::TakeProfitMarket => DydxConditionType::TakeProfit,
237 _ => DydxConditionType::Unspecified,
238 }
239 }
240
241 #[must_use]
243 pub const fn is_market_execution(&self) -> bool {
244 matches!(
245 self,
246 Self::Market | Self::StopMarket | Self::TakeProfitMarket
247 )
248 }
249}
250
251impl From<DydxOrderType> for OrderType {
252 fn from(value: DydxOrderType) -> Self {
253 match value {
254 DydxOrderType::Market => Self::Market,
255 DydxOrderType::Limit => Self::Limit,
256 DydxOrderType::StopMarket => Self::StopMarket,
257 DydxOrderType::StopLimit => Self::StopLimit,
258 DydxOrderType::TakeProfitMarket => Self::MarketIfTouched,
259 DydxOrderType::TakeProfitLimit => Self::LimitIfTouched,
260 DydxOrderType::TrailingStop => Self::TrailingStopMarket,
261 }
262 }
263}
264
265#[derive(
267 Copy,
268 Clone,
269 Debug,
270 Display,
271 PartialEq,
272 Eq,
273 Hash,
274 AsRefStr,
275 EnumIter,
276 EnumString,
277 Serialize,
278 Deserialize,
279)]
280#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
281pub enum DydxOrderExecution {
282 Default,
284 Ioc,
286 Fok,
288 PostOnly,
290}
291
292#[derive(
294 Copy, Clone, Debug, Display, PartialEq, Eq, Hash, AsRefStr, EnumIter, Serialize, Deserialize,
295)]
296pub enum DydxOrderFlags {
297 ShortTerm = 0,
299 Conditional = 32,
301 LongTerm = 64,
303}
304
305#[derive(
311 Copy,
312 Clone,
313 Debug,
314 Display,
315 PartialEq,
316 Eq,
317 Hash,
318 AsRefStr,
319 EnumIter,
320 EnumString,
321 Serialize,
322 Deserialize,
323)]
324#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
325pub enum DydxConditionType {
326 Unspecified,
328 StopLoss,
330 TakeProfit,
332}
333
334#[derive(
336 Copy,
337 Clone,
338 Debug,
339 Display,
340 PartialEq,
341 Eq,
342 Hash,
343 AsRefStr,
344 EnumIter,
345 EnumString,
346 Serialize,
347 Deserialize,
348)]
349#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
350pub enum DydxPositionSide {
351 Long,
353 Short,
355}
356
357impl From<DydxPositionSide> for PositionSide {
358 fn from(value: DydxPositionSide) -> Self {
359 match value {
360 DydxPositionSide::Long => Self::Long,
361 DydxPositionSide::Short => Self::Short,
362 }
363 }
364}
365
366#[derive(
368 Copy,
369 Clone,
370 Debug,
371 Display,
372 PartialEq,
373 Eq,
374 Hash,
375 AsRefStr,
376 EnumIter,
377 EnumString,
378 Serialize,
379 Deserialize,
380)]
381#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
382pub enum DydxPositionStatus {
383 Open,
385 Closed,
387 Liquidated,
389}
390
391impl From<DydxPositionStatus> for PositionSide {
392 fn from(value: DydxPositionStatus) -> Self {
393 match value {
394 DydxPositionStatus::Open => Self::Long, DydxPositionStatus::Closed => Self::Flat,
396 DydxPositionStatus::Liquidated => Self::Flat,
397 }
398 }
399}
400
401#[derive(
403 Copy,
404 Clone,
405 Debug,
406 Display,
407 PartialEq,
408 Eq,
409 Hash,
410 AsRefStr,
411 EnumIter,
412 EnumString,
413 Serialize,
414 Deserialize,
415)]
416#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
417pub enum DydxMarketStatus {
418 Active,
420 Paused,
422 CancelOnly,
424 PostOnly,
426 Initializing,
428 FinalSettlement,
430}
431
432#[derive(
434 Copy,
435 Clone,
436 Debug,
437 Display,
438 PartialEq,
439 Eq,
440 Hash,
441 AsRefStr,
442 EnumIter,
443 EnumString,
444 Serialize,
445 Deserialize,
446)]
447#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
448pub enum DydxFillType {
449 Limit,
451 Liquidated,
453 Liquidation,
455 Deleveraged,
457 Offsetting,
459}
460
461#[derive(
463 Copy,
464 Clone,
465 Debug,
466 Display,
467 PartialEq,
468 Eq,
469 Hash,
470 AsRefStr,
471 EnumIter,
472 EnumString,
473 Serialize,
474 Deserialize,
475)]
476#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
477pub enum DydxLiquidity {
478 Maker,
480 Taker,
482}
483
484impl From<DydxLiquidity> for LiquiditySide {
485 fn from(value: DydxLiquidity) -> Self {
486 match value {
487 DydxLiquidity::Maker => Self::Maker,
488 DydxLiquidity::Taker => Self::Taker,
489 }
490 }
491}
492
493impl From<LiquiditySide> for DydxLiquidity {
494 fn from(value: LiquiditySide) -> Self {
495 match value {
496 LiquiditySide::Maker => Self::Maker,
497 LiquiditySide::Taker => Self::Taker,
498 LiquiditySide::NoLiquiditySide => Self::Taker, }
500 }
501}
502
503#[derive(
505 Copy,
506 Clone,
507 Debug,
508 Display,
509 PartialEq,
510 Eq,
511 Hash,
512 AsRefStr,
513 EnumIter,
514 EnumString,
515 Serialize,
516 Deserialize,
517)]
518#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
519pub enum DydxTickerType {
520 Perpetual,
522}
523
524#[derive(
528 Copy,
529 Clone,
530 Debug,
531 Display,
532 PartialEq,
533 Eq,
534 Hash,
535 AsRefStr,
536 EnumIter,
537 EnumString,
538 Serialize,
539 Deserialize,
540)]
541#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
542pub enum DydxTradeType {
543 Limit,
545 Market,
547 Liquidated,
549 TwapSuborder,
551 StopLimit,
553 TakeProfitLimit,
555}
556
557#[derive(
559 Copy,
560 Clone,
561 Debug,
562 Display,
563 PartialEq,
564 Eq,
565 Hash,
566 AsRefStr,
567 EnumIter,
568 EnumString,
569 Serialize,
570 Deserialize,
571)]
572#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
573#[cfg_attr(
574 feature = "python",
575 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.dydx", eq, eq_int)
576)]
577pub enum DydxTransferType {
578 TransferIn,
580 TransferOut,
582 Deposit,
584 Withdrawal,
586}
587
588#[derive(
590 Copy,
591 Clone,
592 Debug,
593 Display,
594 PartialEq,
595 Eq,
596 Hash,
597 AsRefStr,
598 EnumIter,
599 EnumString,
600 Serialize,
601 Deserialize,
602)]
603#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
604#[derive(Default)]
605#[cfg_attr(
606 feature = "python",
607 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.dydx", eq, eq_int)
608)]
609pub enum DydxCandleResolution {
610 #[serde(rename = "1MIN")]
612 #[strum(serialize = "1MIN")]
613 #[default]
614 OneMinute,
615 #[serde(rename = "5MINS")]
617 #[strum(serialize = "5MINS")]
618 FiveMinutes,
619 #[serde(rename = "15MINS")]
621 #[strum(serialize = "15MINS")]
622 FifteenMinutes,
623 #[serde(rename = "30MINS")]
625 #[strum(serialize = "30MINS")]
626 ThirtyMinutes,
627 #[serde(rename = "1HOUR")]
629 #[strum(serialize = "1HOUR")]
630 OneHour,
631 #[serde(rename = "4HOURS")]
633 #[strum(serialize = "4HOURS")]
634 FourHours,
635 #[serde(rename = "1DAY")]
637 #[strum(serialize = "1DAY")]
638 OneDay,
639}
640
641#[cfg(test)]
642mod tests {
643 use rstest::rstest;
644
645 use super::*;
646
647 #[rstest]
648 fn test_order_status_conversion() {
649 assert_eq!(
650 OrderStatus::from(DydxOrderStatus::Open),
651 OrderStatus::Accepted
652 );
653 assert_eq!(
654 OrderStatus::from(DydxOrderStatus::Filled),
655 OrderStatus::Filled
656 );
657 assert_eq!(
658 OrderStatus::from(DydxOrderStatus::Canceled),
659 OrderStatus::Canceled
660 );
661 }
662
663 #[rstest]
664 fn test_liquidity_conversion() {
665 assert_eq!(
666 LiquiditySide::from(DydxLiquidity::Maker),
667 LiquiditySide::Maker
668 );
669 assert_eq!(
670 LiquiditySide::from(DydxLiquidity::Taker),
671 LiquiditySide::Taker
672 );
673 }
674
675 #[rstest]
676 fn test_order_type_is_conditional() {
677 assert!(DydxOrderType::StopLimit.is_conditional());
678 assert!(DydxOrderType::StopMarket.is_conditional());
679 assert!(DydxOrderType::TakeProfitLimit.is_conditional());
680 assert!(DydxOrderType::TakeProfitMarket.is_conditional());
681 assert!(DydxOrderType::TrailingStop.is_conditional());
682 assert!(!DydxOrderType::Limit.is_conditional());
683 assert!(!DydxOrderType::Market.is_conditional());
684 }
685
686 #[rstest]
687 fn test_condition_type_mapping() {
688 assert_eq!(
689 DydxOrderType::StopLimit.condition_type(),
690 DydxConditionType::StopLoss
691 );
692 assert_eq!(
693 DydxOrderType::StopMarket.condition_type(),
694 DydxConditionType::StopLoss
695 );
696 assert_eq!(
697 DydxOrderType::TakeProfitLimit.condition_type(),
698 DydxConditionType::TakeProfit
699 );
700 assert_eq!(
701 DydxOrderType::TakeProfitMarket.condition_type(),
702 DydxConditionType::TakeProfit
703 );
704 assert_eq!(
705 DydxOrderType::Limit.condition_type(),
706 DydxConditionType::Unspecified
707 );
708 }
709
710 #[rstest]
711 fn test_is_market_execution() {
712 assert!(DydxOrderType::Market.is_market_execution());
713 assert!(DydxOrderType::StopMarket.is_market_execution());
714 assert!(DydxOrderType::TakeProfitMarket.is_market_execution());
715 assert!(!DydxOrderType::Limit.is_market_execution());
716 assert!(!DydxOrderType::StopLimit.is_market_execution());
717 assert!(!DydxOrderType::TakeProfitLimit.is_market_execution());
718 }
719
720 #[rstest]
721 fn test_order_type_to_nautilus() {
722 assert_eq!(OrderType::from(DydxOrderType::Market), OrderType::Market);
723 assert_eq!(OrderType::from(DydxOrderType::Limit), OrderType::Limit);
724 assert_eq!(
725 OrderType::from(DydxOrderType::StopMarket),
726 OrderType::StopMarket
727 );
728 assert_eq!(
729 OrderType::from(DydxOrderType::StopLimit),
730 OrderType::StopLimit
731 );
732 }
733
734 #[rstest]
735 fn test_order_side_conversion_from_nautilus() {
736 assert_eq!(
737 DydxOrderSide::try_from(OrderSide::Buy).unwrap(),
738 DydxOrderSide::Buy
739 );
740 assert_eq!(
741 DydxOrderSide::try_from(OrderSide::Sell).unwrap(),
742 DydxOrderSide::Sell
743 );
744 assert!(DydxOrderSide::try_from(OrderSide::NoOrderSide).is_err());
745 }
746
747 #[rstest]
748 fn test_order_side_conversion_to_nautilus() {
749 assert_eq!(OrderSide::from(DydxOrderSide::Buy), OrderSide::Buy);
750 assert_eq!(OrderSide::from(DydxOrderSide::Sell), OrderSide::Sell);
751 }
752
753 #[rstest]
754 fn test_order_type_conversion_from_nautilus() {
755 assert_eq!(
756 DydxOrderType::try_from(OrderType::Market).unwrap(),
757 DydxOrderType::Market
758 );
759 assert_eq!(
760 DydxOrderType::try_from(OrderType::Limit).unwrap(),
761 DydxOrderType::Limit
762 );
763 assert_eq!(
764 DydxOrderType::try_from(OrderType::StopMarket).unwrap(),
765 DydxOrderType::StopMarket
766 );
767 assert_eq!(
768 DydxOrderType::try_from(OrderType::StopLimit).unwrap(),
769 DydxOrderType::StopLimit
770 );
771 assert!(DydxOrderType::try_from(OrderType::MarketToLimit).is_err());
772 }
773
774 #[rstest]
775 fn test_order_type_conversion_to_nautilus() {
776 assert_eq!(OrderType::from(DydxOrderType::Market), OrderType::Market);
777 assert_eq!(OrderType::from(DydxOrderType::Limit), OrderType::Limit);
778 assert_eq!(
779 OrderType::from(DydxOrderType::StopMarket),
780 OrderType::StopMarket
781 );
782 assert_eq!(
783 OrderType::from(DydxOrderType::StopLimit),
784 OrderType::StopLimit
785 );
786 }
787
788 #[rstest]
789 fn test_dydx_network_chain_id_mapping() {
790 assert_eq!(DydxNetwork::Mainnet.chain_id(), ChainId::Mainnet1);
792 assert_eq!(DydxNetwork::Testnet.chain_id(), ChainId::Testnet4);
793 }
794
795 #[rstest]
796 fn test_dydx_network_as_str() {
797 assert_eq!(DydxNetwork::Mainnet.as_str(), "mainnet");
799 assert_eq!(DydxNetwork::Testnet.as_str(), "testnet");
800 }
801
802 #[rstest]
803 fn test_dydx_network_default() {
804 assert_eq!(DydxNetwork::default(), DydxNetwork::Mainnet);
806 }
807
808 #[rstest]
809 fn test_dydx_network_serde_lowercase() {
810 let mainnet = DydxNetwork::Mainnet;
812 let json = serde_json::to_string(&mainnet).unwrap();
813 assert_eq!(json, "\"mainnet\"");
814
815 let deserialized: DydxNetwork = serde_json::from_str("\"mainnet\"").unwrap();
816 assert_eq!(deserialized, DydxNetwork::Mainnet);
817
818 let testnet = DydxNetwork::Testnet;
819 let json = serde_json::to_string(&testnet).unwrap();
820 assert_eq!(json, "\"testnet\"");
821
822 let deserialized: DydxNetwork = serde_json::from_str("\"testnet\"").unwrap();
823 assert_eq!(deserialized, DydxNetwork::Testnet);
824 }
825}
826
827#[derive(
831 Copy,
832 Clone,
833 Debug,
834 Default,
835 Display,
836 PartialEq,
837 Eq,
838 Hash,
839 AsRefStr,
840 EnumString,
841 Serialize,
842 Deserialize,
843)]
844#[strum(serialize_all = "lowercase")]
845#[serde(rename_all = "lowercase")]
846#[cfg_attr(
847 feature = "python",
848 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.dydx")
849)]
850pub enum DydxNetwork {
851 #[default]
853 Mainnet,
854 Testnet,
856}
857
858impl DydxNetwork {
859 #[must_use]
861 pub const fn chain_id(self) -> ChainId {
862 match self {
863 Self::Mainnet => ChainId::Mainnet1,
864 Self::Testnet => ChainId::Testnet4,
865 }
866 }
867
868 #[must_use]
870 pub const fn as_str(self) -> &'static str {
871 match self {
872 Self::Mainnet => "mainnet",
873 Self::Testnet => "testnet",
874 }
875 }
876}