1use nautilus_model::{
19 data::BarSpecification,
20 enums::{
21 AggressorSide, AssetClass, BarAggregation, MarketStatusAction, OrderSide, OrderStatus,
22 OrderType, PositionSide, TimeInForce,
23 },
24};
25use serde::{Deserialize, Deserializer, Serialize};
26use strum::{AsRefStr, Display, EnumIter, EnumString};
27
28use super::consts::{
29 AX_HTTP_SANDBOX_URL, AX_HTTP_URL, AX_ORDERS_SANDBOX_URL, AX_ORDERS_URL, AX_WS_PRIVATE_URL,
30 AX_WS_PUBLIC_URL, AX_WS_SANDBOX_PRIVATE_URL, AX_WS_SANDBOX_PUBLIC_URL,
31};
32
33#[derive(
35 Clone,
36 Copy,
37 Debug,
38 Default,
39 Display,
40 Eq,
41 PartialEq,
42 Hash,
43 AsRefStr,
44 EnumIter,
45 EnumString,
46 Serialize,
47 Deserialize,
48)]
49#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
50#[strum(ascii_case_insensitive)]
51#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
52#[cfg_attr(
53 feature = "python",
54 pyo3::pyclass(
55 eq,
56 eq_int,
57 frozen,
58 hash,
59 module = "nautilus_trader.core.nautilus_pyo3.architect",
60 from_py_object,
61 rename_all = "SCREAMING_SNAKE_CASE",
62 )
63)]
64#[cfg_attr(
65 feature = "python",
66 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.architect_ax")
67)]
68pub enum AxEnvironment {
69 #[default]
71 Sandbox,
72 Production,
74}
75
76impl AxEnvironment {
77 #[must_use]
79 pub const fn http_url(&self) -> &'static str {
80 match self {
81 Self::Sandbox => AX_HTTP_SANDBOX_URL,
82 Self::Production => AX_HTTP_URL,
83 }
84 }
85
86 #[must_use]
88 pub const fn orders_url(&self) -> &'static str {
89 match self {
90 Self::Sandbox => AX_ORDERS_SANDBOX_URL,
91 Self::Production => AX_ORDERS_URL,
92 }
93 }
94
95 #[must_use]
97 pub const fn ws_md_url(&self) -> &'static str {
98 match self {
99 Self::Sandbox => AX_WS_SANDBOX_PUBLIC_URL,
100 Self::Production => AX_WS_PUBLIC_URL,
101 }
102 }
103
104 #[must_use]
106 pub const fn ws_orders_url(&self) -> &'static str {
107 match self {
108 Self::Sandbox => AX_WS_SANDBOX_PRIVATE_URL,
109 Self::Production => AX_WS_PRIVATE_URL,
110 }
111 }
112}
113
114#[derive(
119 Clone,
120 Copy,
121 Debug,
122 Display,
123 Eq,
124 PartialEq,
125 Hash,
126 AsRefStr,
127 EnumIter,
128 EnumString,
129 Serialize,
130 Deserialize,
131)]
132#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
133#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
134#[cfg_attr(
135 feature = "python",
136 pyo3::pyclass(
137 eq,
138 eq_int,
139 frozen,
140 hash,
141 module = "nautilus_trader.core.nautilus_pyo3.architect",
142 from_py_object,
143 rename_all = "SCREAMING_SNAKE_CASE",
144 )
145)]
146#[cfg_attr(
147 feature = "python",
148 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.architect_ax")
149)]
150pub enum AxInstrumentState {
151 PreOpen,
153 Open,
155 Closed,
157 ClosedFrozen,
159 Halted,
161 MatchAndCloseAuction,
163 Suspended,
165 Delisted,
167 #[serde(other)]
169 Unknown,
170}
171
172impl AxInstrumentState {
173 #[must_use]
175 pub fn is_tradeable(self) -> bool {
176 matches!(self, Self::Open | Self::PreOpen)
177 }
178}
179
180impl From<AxInstrumentState> for MarketStatusAction {
181 fn from(state: AxInstrumentState) -> Self {
182 match state {
183 AxInstrumentState::PreOpen => Self::PreOpen,
184 AxInstrumentState::Open => Self::Trading,
185 AxInstrumentState::Closed | AxInstrumentState::ClosedFrozen => Self::Close,
186 AxInstrumentState::Halted => Self::Halt,
187 AxInstrumentState::MatchAndCloseAuction => Self::Cross,
188 AxInstrumentState::Suspended => Self::Suspend,
189 AxInstrumentState::Delisted | AxInstrumentState::Unknown => {
190 Self::NotAvailableForTrading
191 }
192 }
193 }
194}
195
196#[derive(
203 Clone, Copy, Debug, Display, Eq, PartialEq, Hash, AsRefStr, EnumIter, EnumString, Serialize,
204)]
205#[strum(serialize_all = "lowercase")]
206pub enum AxCategory {
207 Fx,
208 Equities,
209 Metals,
210 Energy,
211 Crypto,
212 Rates,
213 Indexes,
214 Unknown,
215}
216
217impl<'de> Deserialize<'de> for AxCategory {
218 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
219 where
220 D: Deserializer<'de>,
221 {
222 let s = String::deserialize(deserializer)?;
223 Ok(match s.to_ascii_lowercase().as_str() {
224 "fx" => Self::Fx,
225 "equities" => Self::Equities,
226 "metals" => Self::Metals,
227 "energy" => Self::Energy,
228 "crypto" => Self::Crypto,
229 "rates" => Self::Rates,
230 "indexes" => Self::Indexes,
231 _ => Self::Unknown,
232 })
233 }
234}
235
236impl From<AxCategory> for AssetClass {
237 fn from(category: AxCategory) -> Self {
238 match category {
239 AxCategory::Fx => Self::FX,
240 AxCategory::Equities => Self::Equity,
241 AxCategory::Metals | AxCategory::Energy => Self::Commodity,
242 AxCategory::Crypto => Self::Cryptocurrency,
243 AxCategory::Rates => Self::Debt,
244 AxCategory::Indexes => Self::Index,
245 AxCategory::Unknown => Self::Alternative,
246 }
247 }
248}
249
250#[derive(
255 Clone,
256 Copy,
257 Debug,
258 Display,
259 Eq,
260 PartialEq,
261 Hash,
262 AsRefStr,
263 EnumIter,
264 EnumString,
265 Serialize,
266 Deserialize,
267)]
268#[cfg_attr(
269 feature = "python",
270 pyo3::pyclass(
271 eq,
272 eq_int,
273 frozen,
274 hash,
275 module = "nautilus_trader.core.nautilus_pyo3.architect",
276 from_py_object,
277 rename_all = "SCREAMING_SNAKE_CASE",
278 )
279)]
280#[cfg_attr(
281 feature = "python",
282 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.architect_ax")
283)]
284pub enum AxOrderSide {
285 #[serde(rename = "B", alias = "Buy")]
287 #[strum(serialize = "B")]
288 Buy,
289 #[serde(rename = "S", alias = "Sell")]
291 #[strum(serialize = "S")]
292 Sell,
293}
294
295impl From<AxOrderSide> for AggressorSide {
296 fn from(side: AxOrderSide) -> Self {
297 match side {
298 AxOrderSide::Buy => Self::Buyer,
299 AxOrderSide::Sell => Self::Seller,
300 }
301 }
302}
303
304impl From<AxOrderSide> for OrderSide {
305 fn from(side: AxOrderSide) -> Self {
306 match side {
307 AxOrderSide::Buy => Self::Buy,
308 AxOrderSide::Sell => Self::Sell,
309 }
310 }
311}
312
313impl From<AxOrderSide> for PositionSide {
314 fn from(side: AxOrderSide) -> Self {
315 match side {
316 AxOrderSide::Buy => Self::Long,
317 AxOrderSide::Sell => Self::Short,
318 }
319 }
320}
321
322impl TryFrom<OrderSide> for AxOrderSide {
323 type Error = &'static str;
324
325 fn try_from(side: OrderSide) -> Result<Self, Self::Error> {
326 match side {
327 OrderSide::Buy => Ok(Self::Buy),
328 OrderSide::Sell => Ok(Self::Sell),
329 _ => Err("Invalid order side for AX"),
330 }
331 }
332}
333
334#[derive(
339 Clone,
340 Copy,
341 Debug,
342 Display,
343 Eq,
344 PartialEq,
345 Hash,
346 AsRefStr,
347 EnumIter,
348 EnumString,
349 Serialize,
350 Deserialize,
351)]
352#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
353#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
354#[cfg_attr(
355 feature = "python",
356 pyo3::pyclass(
357 eq,
358 eq_int,
359 frozen,
360 hash,
361 module = "nautilus_trader.core.nautilus_pyo3.architect",
362 from_py_object,
363 rename_all = "SCREAMING_SNAKE_CASE",
364 )
365)]
366#[cfg_attr(
367 feature = "python",
368 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.architect_ax")
369)]
370pub enum AxOrderStatus {
371 Pending,
373 Accepted,
375 PartiallyFilled,
377 Filled,
379 Canceling,
381 Canceled,
383 Rejected,
385 Expired,
387 Replaced,
389 DoneForDay,
391 Out,
393 ReconciledOut,
395 Stale,
397 Unknown,
399}
400
401impl From<AxOrderStatus> for OrderStatus {
402 fn from(status: AxOrderStatus) -> Self {
403 match status {
404 AxOrderStatus::Pending => Self::Submitted,
405 AxOrderStatus::Accepted => Self::Accepted,
406 AxOrderStatus::PartiallyFilled => Self::PartiallyFilled,
407 AxOrderStatus::Filled => Self::Filled,
408 AxOrderStatus::Canceling => Self::PendingCancel,
409 AxOrderStatus::Canceled => Self::Canceled,
410 AxOrderStatus::Rejected => Self::Rejected,
411 AxOrderStatus::Expired => Self::Expired,
412 AxOrderStatus::Replaced => Self::Accepted,
413 AxOrderStatus::DoneForDay => Self::Canceled,
414 AxOrderStatus::Out => Self::Canceled,
415 AxOrderStatus::ReconciledOut => Self::Canceled,
416 AxOrderStatus::Stale => Self::Accepted,
417 AxOrderStatus::Unknown => Self::Initialized,
418 }
419 }
420}
421
422#[derive(
427 Clone,
428 Copy,
429 Debug,
430 Display,
431 Eq,
432 PartialEq,
433 Hash,
434 AsRefStr,
435 EnumIter,
436 EnumString,
437 Serialize,
438 Deserialize,
439)]
440#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
441#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
442#[cfg_attr(
443 feature = "python",
444 pyo3::pyclass(
445 eq,
446 eq_int,
447 frozen,
448 hash,
449 module = "nautilus_trader.core.nautilus_pyo3.architect",
450 from_py_object,
451 rename_all = "SCREAMING_SNAKE_CASE",
452 )
453)]
454#[cfg_attr(
455 feature = "python",
456 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.architect_ax")
457)]
458pub enum AxTimeInForce {
459 Gtc,
461 Gtd,
463 Day,
465 Ioc,
467 Fok,
469 Ato,
471 Atc,
473}
474
475impl From<AxTimeInForce> for TimeInForce {
476 fn from(tif: AxTimeInForce) -> Self {
477 match tif {
478 AxTimeInForce::Gtc => Self::Gtc,
479 AxTimeInForce::Gtd => Self::Gtd,
480 AxTimeInForce::Day => Self::Day,
481 AxTimeInForce::Ioc => Self::Ioc,
482 AxTimeInForce::Fok => Self::Fok,
483 AxTimeInForce::Ato => Self::AtTheOpen,
484 AxTimeInForce::Atc => Self::AtTheClose,
485 }
486 }
487}
488
489impl TryFrom<TimeInForce> for AxTimeInForce {
490 type Error = &'static str;
491
492 fn try_from(tif: TimeInForce) -> Result<Self, Self::Error> {
493 match tif {
494 TimeInForce::Gtc => Ok(Self::Gtc),
495 TimeInForce::Gtd => Ok(Self::Gtd),
496 TimeInForce::Day => Ok(Self::Day),
497 TimeInForce::Ioc => Ok(Self::Ioc),
498 TimeInForce::Fok => Ok(Self::Fok),
499 TimeInForce::AtTheOpen => Ok(Self::Ato),
500 TimeInForce::AtTheClose => Ok(Self::Atc),
501 }
502 }
503}
504
505#[derive(
510 Clone,
511 Copy,
512 Debug,
513 Display,
514 Eq,
515 PartialEq,
516 Hash,
517 AsRefStr,
518 EnumIter,
519 EnumString,
520 Serialize,
521 Deserialize,
522)]
523#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
524#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
525#[cfg_attr(
526 feature = "python",
527 pyo3::pyclass(
528 eq,
529 eq_int,
530 frozen,
531 hash,
532 module = "nautilus_trader.core.nautilus_pyo3.architect",
533 from_py_object,
534 rename_all = "SCREAMING_SNAKE_CASE",
535 )
536)]
537#[cfg_attr(
538 feature = "python",
539 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.architect_ax")
540)]
541pub enum AxOrderType {
542 Market,
544 Limit,
546 StopLossLimit,
548 TakeProfitLimit,
551}
552
553impl From<AxOrderType> for OrderType {
554 fn from(order_type: AxOrderType) -> Self {
555 match order_type {
556 AxOrderType::Market => Self::Market,
557 AxOrderType::Limit => Self::Limit,
558 AxOrderType::StopLossLimit => Self::StopLimit,
559 AxOrderType::TakeProfitLimit => Self::LimitIfTouched,
560 }
561 }
562}
563
564impl TryFrom<OrderType> for AxOrderType {
565 type Error = &'static str;
566
567 fn try_from(order_type: OrderType) -> Result<Self, Self::Error> {
568 match order_type {
569 OrderType::Market => Ok(Self::Market),
570 OrderType::Limit => Ok(Self::Limit),
571 OrderType::StopLimit => Ok(Self::StopLossLimit),
572 OrderType::LimitIfTouched => Ok(Self::TakeProfitLimit),
573 _ => Err("Unsupported order type for AX"),
574 }
575 }
576}
577
578#[derive(
589 Clone,
590 Copy,
591 Debug,
592 Display,
593 Eq,
594 PartialEq,
595 Hash,
596 AsRefStr,
597 EnumIter,
598 EnumString,
599 Serialize,
600 Deserialize,
601)]
602#[strum(ascii_case_insensitive)]
603#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
604#[cfg_attr(
605 feature = "python",
606 pyo3::pyclass(
607 eq,
608 eq_int,
609 frozen,
610 hash,
611 module = "nautilus_trader.core.nautilus_pyo3.architect",
612 from_py_object,
613 rename_all = "SCREAMING_SNAKE_CASE",
614 )
615)]
616#[cfg_attr(
617 feature = "python",
618 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.architect_ax")
619)]
620pub enum AxMarketDataLevel {
621 #[serde(rename = "LEVEL_1")]
623 #[strum(serialize = "LEVEL_1")]
624 Level1,
625 #[serde(rename = "LEVEL_2")]
627 #[strum(serialize = "LEVEL_2")]
628 Level2,
629 #[serde(rename = "LEVEL_3")]
631 #[strum(serialize = "LEVEL_3")]
632 Level3,
633}
634
635#[derive(
640 Clone,
641 Copy,
642 Debug,
643 Display,
644 Eq,
645 PartialEq,
646 Hash,
647 AsRefStr,
648 EnumIter,
649 EnumString,
650 Serialize,
651 Deserialize,
652)]
653pub enum AxCandleWidth {
654 #[serde(rename = "1s")]
656 #[strum(serialize = "1s")]
657 Seconds1,
658 #[serde(rename = "5s")]
660 #[strum(serialize = "5s")]
661 Seconds5,
662 #[serde(rename = "1m")]
664 #[strum(serialize = "1m")]
665 Minutes1,
666 #[serde(rename = "5m")]
668 #[strum(serialize = "5m")]
669 Minutes5,
670 #[serde(rename = "15m")]
672 #[strum(serialize = "15m")]
673 Minutes15,
674 #[serde(rename = "1h")]
676 #[strum(serialize = "1h")]
677 Hours1,
678 #[serde(rename = "1d")]
680 #[strum(serialize = "1d")]
681 Days1,
682}
683
684impl TryFrom<&BarSpecification> for AxCandleWidth {
685 type Error = anyhow::Error;
686
687 fn try_from(spec: &BarSpecification) -> Result<Self, Self::Error> {
688 let step = spec.step.get();
689 match (step, spec.aggregation) {
690 (1, BarAggregation::Second) => Ok(Self::Seconds1),
691 (5, BarAggregation::Second) => Ok(Self::Seconds5),
692 (1, BarAggregation::Minute) => Ok(Self::Minutes1),
693 (5, BarAggregation::Minute) => Ok(Self::Minutes5),
694 (15, BarAggregation::Minute) => Ok(Self::Minutes15),
695 (1, BarAggregation::Hour) => Ok(Self::Hours1),
696 (1, BarAggregation::Day) => Ok(Self::Days1),
697 _ => anyhow::bail!(
698 "Unsupported bar specification for AX: {step}-{:?}",
699 spec.aggregation,
700 ),
701 }
702 }
703}
704
705#[derive(
710 Clone,
711 Copy,
712 Debug,
713 Display,
714 Eq,
715 PartialEq,
716 Hash,
717 AsRefStr,
718 EnumIter,
719 EnumString,
720 Serialize,
721 Deserialize,
722)]
723#[serde(rename_all = "snake_case")]
724#[strum(serialize_all = "snake_case")]
725pub enum AxMdRequestType {
726 Subscribe,
728 Unsubscribe,
730 SubscribeCandles,
732 UnsubscribeCandles,
734}
735
736#[derive(
741 Clone,
742 Copy,
743 Debug,
744 Display,
745 Eq,
746 PartialEq,
747 Hash,
748 AsRefStr,
749 EnumIter,
750 EnumString,
751 Serialize,
752 Deserialize,
753)]
754pub enum AxOrderRequestType {
755 #[serde(rename = "p")]
757 #[strum(serialize = "p")]
758 PlaceOrder,
759 #[serde(rename = "x")]
761 #[strum(serialize = "x")]
762 CancelOrder,
763 #[serde(rename = "o")]
765 #[strum(serialize = "o")]
766 GetOpenOrders,
767}
768
769#[derive(
774 Clone,
775 Copy,
776 Debug,
777 Display,
778 Eq,
779 PartialEq,
780 Hash,
781 AsRefStr,
782 EnumIter,
783 EnumString,
784 Serialize,
785 Deserialize,
786)]
787#[cfg_attr(
788 feature = "python",
789 pyo3::pyclass(
790 eq,
791 eq_int,
792 frozen,
793 hash,
794 module = "nautilus_trader.core.nautilus_pyo3.architect",
795 from_py_object,
796 rename_all = "SCREAMING_SNAKE_CASE",
797 )
798)]
799#[cfg_attr(
800 feature = "python",
801 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.architect_ax")
802)]
803pub enum AxMdWsMessageType {
804 #[serde(rename = "h")]
806 #[strum(serialize = "h")]
807 Heartbeat,
808 #[serde(rename = "s")]
810 #[strum(serialize = "s")]
811 Ticker,
812 #[serde(rename = "t")]
814 #[strum(serialize = "t")]
815 Trade,
816 #[serde(rename = "c")]
818 #[strum(serialize = "c")]
819 Candle,
820 #[serde(rename = "1")]
822 #[strum(serialize = "1")]
823 BookLevel1,
824 #[serde(rename = "2")]
826 #[strum(serialize = "2")]
827 BookLevel2,
828 #[serde(rename = "3")]
830 #[strum(serialize = "3")]
831 BookLevel3,
832}
833
834#[derive(
839 Clone,
840 Copy,
841 Debug,
842 Display,
843 Eq,
844 PartialEq,
845 Hash,
846 AsRefStr,
847 EnumIter,
848 EnumString,
849 Serialize,
850 Deserialize,
851)]
852#[cfg_attr(
853 feature = "python",
854 pyo3::pyclass(
855 eq,
856 eq_int,
857 frozen,
858 hash,
859 module = "nautilus_trader.core.nautilus_pyo3.architect",
860 from_py_object,
861 rename_all = "SCREAMING_SNAKE_CASE",
862 )
863)]
864#[cfg_attr(
865 feature = "python",
866 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.architect_ax")
867)]
868pub enum AxOrderWsMessageType {
869 #[serde(rename = "h")]
871 #[strum(serialize = "h")]
872 Heartbeat,
873 #[serde(rename = "e")]
875 #[strum(serialize = "e")]
876 CancelRejected,
877 #[serde(rename = "n")]
879 #[strum(serialize = "n")]
880 OrderAcknowledged,
881 #[serde(rename = "c")]
883 #[strum(serialize = "c")]
884 OrderCanceled,
885 #[serde(rename = "r")]
887 #[strum(serialize = "r")]
888 OrderReplaced,
889 #[serde(rename = "j")]
891 #[strum(serialize = "j")]
892 OrderRejected,
893 #[serde(rename = "x")]
895 #[strum(serialize = "x")]
896 OrderExpired,
897 #[serde(rename = "d")]
899 #[strum(serialize = "d")]
900 OrderDoneForDay,
901 #[serde(rename = "p")]
903 #[strum(serialize = "p")]
904 OrderPartiallyFilled,
905 #[serde(rename = "f")]
907 #[strum(serialize = "f")]
908 OrderFilled,
909}
910
911#[derive(
916 Clone,
917 Copy,
918 Debug,
919 Display,
920 Eq,
921 PartialEq,
922 Hash,
923 AsRefStr,
924 EnumIter,
925 EnumString,
926 Serialize,
927 Deserialize,
928)]
929#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
930#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
931#[cfg_attr(
932 feature = "python",
933 pyo3::pyclass(
934 eq,
935 eq_int,
936 frozen,
937 hash,
938 module = "nautilus_trader.core.nautilus_pyo3.architect",
939 from_py_object,
940 rename_all = "SCREAMING_SNAKE_CASE",
941 )
942)]
943#[cfg_attr(
944 feature = "python",
945 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.architect_ax")
946)]
947pub enum AxCancelReason {
948 UserRequested,
950 #[serde(other)]
952 Unknown,
953}
954
955#[derive(
960 Clone,
961 Copy,
962 Debug,
963 Display,
964 Eq,
965 PartialEq,
966 Hash,
967 AsRefStr,
968 EnumIter,
969 EnumString,
970 Serialize,
971 Deserialize,
972)]
973#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
974#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
975#[cfg_attr(
976 feature = "python",
977 pyo3::pyclass(
978 eq,
979 eq_int,
980 frozen,
981 hash,
982 module = "nautilus_trader.core.nautilus_pyo3.architect",
983 from_py_object,
984 rename_all = "SCREAMING_SNAKE_CASE",
985 )
986)]
987#[cfg_attr(
988 feature = "python",
989 pyo3_stub_gen::derive::gen_stub_pyclass_enum(module = "nautilus_trader.architect_ax")
990)]
991pub enum AxCancelRejectionReason {
992 OrderNotFound,
994 #[serde(other)]
996 Unknown,
997}
998
999#[cfg(test)]
1000mod tests {
1001 use rstest::rstest;
1002
1003 use super::*;
1004
1005 #[rstest]
1006 #[case(AxInstrumentState::Open, "\"OPEN\"")]
1007 #[case(AxInstrumentState::PreOpen, "\"PRE_OPEN\"")]
1008 #[case(AxInstrumentState::Closed, "\"CLOSED\"")]
1009 #[case(AxInstrumentState::ClosedFrozen, "\"CLOSED_FROZEN\"")]
1010 #[case(AxInstrumentState::Halted, "\"HALTED\"")]
1011 #[case(AxInstrumentState::MatchAndCloseAuction, "\"MATCH_AND_CLOSE_AUCTION\"")]
1012 #[case(AxInstrumentState::Suspended, "\"SUSPENDED\"")]
1013 #[case(AxInstrumentState::Delisted, "\"DELISTED\"")]
1014 fn test_instrument_state_serialization(
1015 #[case] state: AxInstrumentState,
1016 #[case] expected: &str,
1017 ) {
1018 let json = serde_json::to_string(&state).unwrap();
1019 assert_eq!(json, expected);
1020
1021 let parsed: AxInstrumentState = serde_json::from_str(&json).unwrap();
1022 assert_eq!(parsed, state);
1023 }
1024
1025 #[rstest]
1026 fn test_instrument_state_unknown_string_deserializes_as_unknown() {
1027 let parsed: AxInstrumentState = serde_json::from_str("\"SOME_FUTURE_STATE\"").unwrap();
1028 assert_eq!(parsed, AxInstrumentState::Unknown);
1029 }
1030
1031 #[rstest]
1032 #[case(AxInstrumentState::PreOpen, true)]
1033 #[case(AxInstrumentState::Open, true)]
1034 #[case(AxInstrumentState::Closed, false)]
1035 #[case(AxInstrumentState::ClosedFrozen, false)]
1036 #[case(AxInstrumentState::Halted, false)]
1037 #[case(AxInstrumentState::MatchAndCloseAuction, false)]
1038 #[case(AxInstrumentState::Suspended, false)]
1039 #[case(AxInstrumentState::Delisted, false)]
1040 #[case(AxInstrumentState::Unknown, false)]
1041 fn test_instrument_state_is_tradeable(
1042 #[case] state: AxInstrumentState,
1043 #[case] expected: bool,
1044 ) {
1045 assert_eq!(state.is_tradeable(), expected);
1046 }
1047
1048 #[rstest]
1049 #[case(AxInstrumentState::PreOpen, MarketStatusAction::PreOpen)]
1050 #[case(AxInstrumentState::Open, MarketStatusAction::Trading)]
1051 #[case(AxInstrumentState::Closed, MarketStatusAction::Close)]
1052 #[case(AxInstrumentState::ClosedFrozen, MarketStatusAction::Close)]
1053 #[case(AxInstrumentState::Halted, MarketStatusAction::Halt)]
1054 #[case(AxInstrumentState::MatchAndCloseAuction, MarketStatusAction::Cross)]
1055 #[case(AxInstrumentState::Suspended, MarketStatusAction::Suspend)]
1056 #[case(
1057 AxInstrumentState::Delisted,
1058 MarketStatusAction::NotAvailableForTrading
1059 )]
1060 #[case(AxInstrumentState::Unknown, MarketStatusAction::NotAvailableForTrading)]
1061 fn test_instrument_state_to_market_status_action(
1062 #[case] state: AxInstrumentState,
1063 #[case] expected: MarketStatusAction,
1064 ) {
1065 assert_eq!(MarketStatusAction::from(state), expected);
1066 }
1067
1068 #[rstest]
1069 #[case(AxOrderSide::Buy, "\"B\"")]
1070 #[case(AxOrderSide::Sell, "\"S\"")]
1071 fn test_order_side_serialization(#[case] side: AxOrderSide, #[case] expected: &str) {
1072 let json = serde_json::to_string(&side).unwrap();
1073 assert_eq!(json, expected);
1074
1075 let parsed: AxOrderSide = serde_json::from_str(&json).unwrap();
1076 assert_eq!(parsed, side);
1077 }
1078
1079 #[rstest]
1080 #[case(AxOrderStatus::Pending, "\"PENDING\"")]
1081 #[case(AxOrderStatus::Accepted, "\"ACCEPTED\"")]
1082 #[case(AxOrderStatus::PartiallyFilled, "\"PARTIALLY_FILLED\"")]
1083 #[case(AxOrderStatus::Filled, "\"FILLED\"")]
1084 #[case(AxOrderStatus::Canceling, "\"CANCELING\"")]
1085 #[case(AxOrderStatus::Canceled, "\"CANCELED\"")]
1086 #[case(AxOrderStatus::Out, "\"OUT\"")]
1087 #[case(AxOrderStatus::ReconciledOut, "\"RECONCILED_OUT\"")]
1088 #[case(AxOrderStatus::Stale, "\"STALE\"")]
1089 fn test_order_status_serialization(#[case] status: AxOrderStatus, #[case] expected: &str) {
1090 let json = serde_json::to_string(&status).unwrap();
1091 assert_eq!(json, expected);
1092
1093 let parsed: AxOrderStatus = serde_json::from_str(&json).unwrap();
1094 assert_eq!(parsed, status);
1095 }
1096
1097 #[rstest]
1098 #[case(AxTimeInForce::Gtc, "\"GTC\"")]
1099 #[case(AxTimeInForce::Ioc, "\"IOC\"")]
1100 #[case(AxTimeInForce::Day, "\"DAY\"")]
1101 #[case(AxTimeInForce::Gtd, "\"GTD\"")]
1102 #[case(AxTimeInForce::Fok, "\"FOK\"")]
1103 #[case(AxTimeInForce::Ato, "\"ATO\"")]
1104 #[case(AxTimeInForce::Atc, "\"ATC\"")]
1105 fn test_time_in_force_serialization(#[case] tif: AxTimeInForce, #[case] expected: &str) {
1106 let json = serde_json::to_string(&tif).unwrap();
1107 assert_eq!(json, expected);
1108
1109 let parsed: AxTimeInForce = serde_json::from_str(&json).unwrap();
1110 assert_eq!(parsed, tif);
1111 }
1112
1113 #[rstest]
1114 #[case(AxOrderType::Market, "\"MARKET\"")]
1115 #[case(AxOrderType::Limit, "\"LIMIT\"")]
1116 #[case(AxOrderType::StopLossLimit, "\"STOP_LOSS_LIMIT\"")]
1117 #[case(AxOrderType::TakeProfitLimit, "\"TAKE_PROFIT_LIMIT\"")]
1118 fn test_order_type_serialization(#[case] order_type: AxOrderType, #[case] expected: &str) {
1119 let json = serde_json::to_string(&order_type).unwrap();
1120 assert_eq!(json, expected);
1121
1122 let parsed: AxOrderType = serde_json::from_str(&json).unwrap();
1123 assert_eq!(parsed, order_type);
1124 }
1125
1126 #[rstest]
1127 #[case(AxMarketDataLevel::Level1, "\"LEVEL_1\"")]
1128 #[case(AxMarketDataLevel::Level2, "\"LEVEL_2\"")]
1129 #[case(AxMarketDataLevel::Level3, "\"LEVEL_3\"")]
1130 fn test_market_data_level_serialization(
1131 #[case] level: AxMarketDataLevel,
1132 #[case] expected: &str,
1133 ) {
1134 let json = serde_json::to_string(&level).unwrap();
1135 assert_eq!(json, expected);
1136
1137 let parsed: AxMarketDataLevel = serde_json::from_str(&json).unwrap();
1138 assert_eq!(parsed, level);
1139 }
1140
1141 #[rstest]
1142 #[case(AxCandleWidth::Seconds1, "\"1s\"")]
1143 #[case(AxCandleWidth::Minutes1, "\"1m\"")]
1144 #[case(AxCandleWidth::Minutes5, "\"5m\"")]
1145 #[case(AxCandleWidth::Hours1, "\"1h\"")]
1146 #[case(AxCandleWidth::Days1, "\"1d\"")]
1147 fn test_candle_width_serialization(#[case] width: AxCandleWidth, #[case] expected: &str) {
1148 let json = serde_json::to_string(&width).unwrap();
1149 assert_eq!(json, expected);
1150
1151 let parsed: AxCandleWidth = serde_json::from_str(&json).unwrap();
1152 assert_eq!(parsed, width);
1153 }
1154
1155 #[rstest]
1156 #[case(AxMdWsMessageType::Heartbeat, "\"h\"")]
1157 #[case(AxMdWsMessageType::Ticker, "\"s\"")]
1158 #[case(AxMdWsMessageType::Trade, "\"t\"")]
1159 #[case(AxMdWsMessageType::Candle, "\"c\"")]
1160 #[case(AxMdWsMessageType::BookLevel1, "\"1\"")]
1161 #[case(AxMdWsMessageType::BookLevel2, "\"2\"")]
1162 #[case(AxMdWsMessageType::BookLevel3, "\"3\"")]
1163 fn test_md_ws_message_type_serialization(
1164 #[case] msg_type: AxMdWsMessageType,
1165 #[case] expected: &str,
1166 ) {
1167 let json = serde_json::to_string(&msg_type).unwrap();
1168 assert_eq!(json, expected);
1169
1170 let parsed: AxMdWsMessageType = serde_json::from_str(&json).unwrap();
1171 assert_eq!(parsed, msg_type);
1172 }
1173
1174 #[rstest]
1175 #[case(AxOrderWsMessageType::Heartbeat, "\"h\"")]
1176 #[case(AxOrderWsMessageType::OrderAcknowledged, "\"n\"")]
1177 #[case(AxOrderWsMessageType::OrderCanceled, "\"c\"")]
1178 #[case(AxOrderWsMessageType::OrderFilled, "\"f\"")]
1179 #[case(AxOrderWsMessageType::OrderPartiallyFilled, "\"p\"")]
1180 fn test_order_ws_message_type_serialization(
1181 #[case] msg_type: AxOrderWsMessageType,
1182 #[case] expected: &str,
1183 ) {
1184 let json = serde_json::to_string(&msg_type).unwrap();
1185 assert_eq!(json, expected);
1186
1187 let parsed: AxOrderWsMessageType = serde_json::from_str(&json).unwrap();
1188 assert_eq!(parsed, msg_type);
1189 }
1190
1191 #[rstest]
1192 #[case(AxMdRequestType::Subscribe, "\"subscribe\"")]
1193 #[case(AxMdRequestType::Unsubscribe, "\"unsubscribe\"")]
1194 #[case(AxMdRequestType::SubscribeCandles, "\"subscribe_candles\"")]
1195 #[case(AxMdRequestType::UnsubscribeCandles, "\"unsubscribe_candles\"")]
1196 fn test_md_request_type_serialization(
1197 #[case] request_type: AxMdRequestType,
1198 #[case] expected: &str,
1199 ) {
1200 let json = serde_json::to_string(&request_type).unwrap();
1201 assert_eq!(json, expected);
1202
1203 let parsed: AxMdRequestType = serde_json::from_str(&json).unwrap();
1204 assert_eq!(parsed, request_type);
1205 }
1206
1207 #[rstest]
1208 #[case(AxOrderRequestType::PlaceOrder, "\"p\"")]
1209 #[case(AxOrderRequestType::CancelOrder, "\"x\"")]
1210 #[case(AxOrderRequestType::GetOpenOrders, "\"o\"")]
1211 fn test_order_request_type_serialization(
1212 #[case] request_type: AxOrderRequestType,
1213 #[case] expected: &str,
1214 ) {
1215 let json = serde_json::to_string(&request_type).unwrap();
1216 assert_eq!(json, expected);
1217
1218 let parsed: AxOrderRequestType = serde_json::from_str(&json).unwrap();
1219 assert_eq!(parsed, request_type);
1220 }
1221
1222 #[rstest]
1223 #[case("\"fx\"", AxCategory::Fx)]
1224 #[case("\"FX\"", AxCategory::Fx)]
1225 #[case("\"Fx\"", AxCategory::Fx)]
1226 #[case("\"equities\"", AxCategory::Equities)]
1227 #[case("\"EQUITIES\"", AxCategory::Equities)]
1228 #[case("\"metals\"", AxCategory::Metals)]
1229 #[case("\"Metals\"", AxCategory::Metals)]
1230 #[case("\"energy\"", AxCategory::Energy)]
1231 #[case("\"crypto\"", AxCategory::Crypto)]
1232 #[case("\"rates\"", AxCategory::Rates)]
1233 #[case("\"indexes\"", AxCategory::Indexes)]
1234 #[case("\"something_new\"", AxCategory::Unknown)]
1235 fn test_category_deserialization_case_insensitive(
1236 #[case] json: &str,
1237 #[case] expected: AxCategory,
1238 ) {
1239 let parsed: AxCategory = serde_json::from_str(json).unwrap();
1240 assert_eq!(parsed, expected);
1241 }
1242}