Skip to main content

nautilus_architect_ax/common/
enums.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Enumerations that model Ax string enums across HTTP and WebSocket payloads.
17
18use nautilus_model::{
19    data::BarSpecification,
20    enums::{
21        AggressorSide, BarAggregation, OrderSide, OrderStatus, OrderType, PositionSide, TimeInForce,
22    },
23};
24use serde::{Deserialize, Serialize};
25use strum::{AsRefStr, Display, EnumIter, EnumString};
26
27use super::consts::{
28    AX_HTTP_SANDBOX_URL, AX_HTTP_URL, AX_ORDERS_SANDBOX_URL, AX_ORDERS_URL, AX_WS_PRIVATE_URL,
29    AX_WS_PUBLIC_URL, AX_WS_SANDBOX_PRIVATE_URL, AX_WS_SANDBOX_PUBLIC_URL,
30};
31
32/// AX Exchange API environment.
33#[derive(
34    Clone,
35    Copy,
36    Debug,
37    Default,
38    Display,
39    Eq,
40    PartialEq,
41    Hash,
42    AsRefStr,
43    EnumIter,
44    EnumString,
45    Serialize,
46    Deserialize,
47)]
48#[strum(ascii_case_insensitive)]
49#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
50#[cfg_attr(
51    feature = "python",
52    pyo3::pyclass(
53        eq,
54        eq_int,
55        frozen,
56        hash,
57        module = "nautilus_trader.core.nautilus_pyo3.architect"
58    )
59)]
60pub enum AxEnvironment {
61    /// Sandbox/test environment.
62    #[default]
63    Sandbox,
64    /// Production/live environment.
65    Production,
66}
67
68impl AxEnvironment {
69    /// Returns the HTTP API base URL for this environment.
70    #[must_use]
71    pub const fn http_url(&self) -> &'static str {
72        match self {
73            Self::Sandbox => AX_HTTP_SANDBOX_URL,
74            Self::Production => AX_HTTP_URL,
75        }
76    }
77
78    /// Returns the Orders API base URL for this environment.
79    #[must_use]
80    pub const fn orders_url(&self) -> &'static str {
81        match self {
82            Self::Sandbox => AX_ORDERS_SANDBOX_URL,
83            Self::Production => AX_ORDERS_URL,
84        }
85    }
86
87    /// Returns the market data WebSocket URL for this environment.
88    #[must_use]
89    pub const fn ws_md_url(&self) -> &'static str {
90        match self {
91            Self::Sandbox => AX_WS_SANDBOX_PUBLIC_URL,
92            Self::Production => AX_WS_PUBLIC_URL,
93        }
94    }
95
96    /// Returns the orders WebSocket URL for this environment.
97    #[must_use]
98    pub const fn ws_orders_url(&self) -> &'static str {
99        match self {
100            Self::Sandbox => AX_WS_SANDBOX_PRIVATE_URL,
101            Self::Production => AX_WS_PRIVATE_URL,
102        }
103    }
104}
105
106/// Instrument state as returned by the AX Exchange API.
107///
108/// # References
109/// - <https://docs.architect.exchange/api-reference/symbols-instruments/get-instruments>
110#[derive(
111    Clone,
112    Copy,
113    Debug,
114    Display,
115    Eq,
116    PartialEq,
117    Hash,
118    AsRefStr,
119    EnumIter,
120    EnumString,
121    Serialize,
122    Deserialize,
123)]
124#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
125#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
126#[cfg_attr(
127    feature = "python",
128    pyo3::pyclass(eq, eq_int, module = "nautilus_trader.core.nautilus_pyo3.architect")
129)]
130pub enum AxInstrumentState {
131    /// Instrument is in pre-open state.
132    PreOpen,
133    /// Instrument is open for trading.
134    Open,
135    /// Instrument trading is suspended.
136    Suspended,
137    /// Instrument has been delisted.
138    Delisted,
139    /// Instrument state is unknown.
140    Unknown,
141}
142
143/// Order side for trading operations.
144///
145/// # References
146/// - <https://docs.architect.exchange/api-reference/order-management/place-order>
147#[derive(
148    Clone,
149    Copy,
150    Debug,
151    Display,
152    Eq,
153    PartialEq,
154    Hash,
155    AsRefStr,
156    EnumIter,
157    EnumString,
158    Serialize,
159    Deserialize,
160)]
161#[cfg_attr(
162    feature = "python",
163    pyo3::pyclass(eq, eq_int, module = "nautilus_trader.core.nautilus_pyo3.architect")
164)]
165pub enum AxOrderSide {
166    /// Buy order.
167    #[serde(rename = "B", alias = "Buy")]
168    #[strum(serialize = "B")]
169    Buy,
170    /// Sell order.
171    #[serde(rename = "S", alias = "Sell")]
172    #[strum(serialize = "S")]
173    Sell,
174}
175
176impl From<AxOrderSide> for AggressorSide {
177    fn from(side: AxOrderSide) -> Self {
178        match side {
179            AxOrderSide::Buy => Self::Buyer,
180            AxOrderSide::Sell => Self::Seller,
181        }
182    }
183}
184
185impl From<AxOrderSide> for OrderSide {
186    fn from(side: AxOrderSide) -> Self {
187        match side {
188            AxOrderSide::Buy => Self::Buy,
189            AxOrderSide::Sell => Self::Sell,
190        }
191    }
192}
193
194impl From<AxOrderSide> for PositionSide {
195    fn from(side: AxOrderSide) -> Self {
196        match side {
197            AxOrderSide::Buy => Self::Long,
198            AxOrderSide::Sell => Self::Short,
199        }
200    }
201}
202
203impl TryFrom<OrderSide> for AxOrderSide {
204    type Error = &'static str;
205
206    fn try_from(side: OrderSide) -> Result<Self, Self::Error> {
207        match side {
208            OrderSide::Buy => Ok(Self::Buy),
209            OrderSide::Sell => Ok(Self::Sell),
210            _ => Err("Invalid order side for AX"),
211        }
212    }
213}
214
215/// Order status as returned by the AX Exchange API.
216///
217/// # References
218/// - <https://docs.architect.exchange/api-reference/order-management/get-open-orders>
219#[derive(
220    Clone,
221    Copy,
222    Debug,
223    Display,
224    Eq,
225    PartialEq,
226    Hash,
227    AsRefStr,
228    EnumIter,
229    EnumString,
230    Serialize,
231    Deserialize,
232)]
233#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
234#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
235#[cfg_attr(
236    feature = "python",
237    pyo3::pyclass(eq, eq_int, module = "nautilus_trader.core.nautilus_pyo3.architect")
238)]
239pub enum AxOrderStatus {
240    /// Order is pending submission.
241    Pending,
242    /// Order has been accepted by the exchange (OPEN state).
243    Accepted,
244    /// Order has been partially filled.
245    PartiallyFilled,
246    /// Order has been completely filled.
247    Filled,
248    /// Order cancellation is in progress.
249    Canceling,
250    /// Order has been canceled.
251    Canceled,
252    /// Order has been rejected.
253    Rejected,
254    /// Order has expired.
255    Expired,
256    /// Order has been replaced.
257    Replaced,
258    /// Order is done for the day.
259    DoneForDay,
260    /// Order is no longer on the orderbook (terminal state).
261    Out,
262    /// Order was reconciled out asynchronously.
263    ReconciledOut,
264    /// Order is in a stale state (expected transitions not occurring).
265    Stale,
266    /// Order status is unknown.
267    Unknown,
268}
269
270impl From<AxOrderStatus> for OrderStatus {
271    fn from(status: AxOrderStatus) -> Self {
272        match status {
273            AxOrderStatus::Pending => Self::Submitted,
274            AxOrderStatus::Accepted => Self::Accepted,
275            AxOrderStatus::PartiallyFilled => Self::PartiallyFilled,
276            AxOrderStatus::Filled => Self::Filled,
277            AxOrderStatus::Canceling => Self::PendingCancel,
278            AxOrderStatus::Canceled => Self::Canceled,
279            AxOrderStatus::Rejected => Self::Rejected,
280            AxOrderStatus::Expired => Self::Expired,
281            AxOrderStatus::Replaced => Self::Accepted,
282            AxOrderStatus::DoneForDay => Self::Canceled,
283            AxOrderStatus::Out => Self::Canceled,
284            AxOrderStatus::ReconciledOut => Self::Canceled,
285            AxOrderStatus::Stale => Self::Accepted,
286            AxOrderStatus::Unknown => Self::Initialized,
287        }
288    }
289}
290
291/// Time in force for order validity.
292///
293/// # References
294/// - <https://docs.architect.exchange/api-reference/order-management/place-order>
295#[derive(
296    Clone,
297    Copy,
298    Debug,
299    Display,
300    Eq,
301    PartialEq,
302    Hash,
303    AsRefStr,
304    EnumIter,
305    EnumString,
306    Serialize,
307    Deserialize,
308)]
309#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
310#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
311#[cfg_attr(
312    feature = "python",
313    pyo3::pyclass(eq, eq_int, module = "nautilus_trader.core.nautilus_pyo3.architect")
314)]
315pub enum AxTimeInForce {
316    /// Good-Till-Canceled: order remains active until filled or canceled.
317    Gtc,
318    /// Good-Till-Date: order remains active until specified datetime.
319    Gtd,
320    /// Day order: valid until end of trading day.
321    Day,
322    /// Immediate-Or-Cancel: fill immediately or cancel unfilled portion.
323    Ioc,
324    /// Fill-Or-Kill: execute entire order immediately or cancel.
325    Fok,
326    /// At-the-Open: execute at market opening or expire.
327    Ato,
328    /// At-the-Close: execute at market close or expire.
329    Atc,
330}
331
332impl From<AxTimeInForce> for TimeInForce {
333    fn from(tif: AxTimeInForce) -> Self {
334        match tif {
335            AxTimeInForce::Gtc => Self::Gtc,
336            AxTimeInForce::Gtd => Self::Gtd,
337            AxTimeInForce::Day => Self::Day,
338            AxTimeInForce::Ioc => Self::Ioc,
339            AxTimeInForce::Fok => Self::Fok,
340            AxTimeInForce::Ato => Self::AtTheOpen,
341            AxTimeInForce::Atc => Self::AtTheClose,
342        }
343    }
344}
345
346impl TryFrom<TimeInForce> for AxTimeInForce {
347    type Error = &'static str;
348
349    fn try_from(tif: TimeInForce) -> Result<Self, Self::Error> {
350        match tif {
351            TimeInForce::Gtc => Ok(Self::Gtc),
352            TimeInForce::Gtd => Ok(Self::Gtd),
353            TimeInForce::Day => Ok(Self::Day),
354            TimeInForce::Ioc => Ok(Self::Ioc),
355            TimeInForce::Fok => Ok(Self::Fok),
356            TimeInForce::AtTheOpen => Ok(Self::Ato),
357            TimeInForce::AtTheClose => Ok(Self::Atc),
358        }
359    }
360}
361
362/// Order type as defined by the AX Exchange API.
363///
364/// # References
365/// - <https://docs.architect.co/sdk-reference/order-entry>
366#[derive(
367    Clone,
368    Copy,
369    Debug,
370    Display,
371    Eq,
372    PartialEq,
373    Hash,
374    AsRefStr,
375    EnumIter,
376    EnumString,
377    Serialize,
378    Deserialize,
379)]
380#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
381#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
382#[cfg_attr(
383    feature = "python",
384    pyo3::pyclass(eq, eq_int, module = "nautilus_trader.core.nautilus_pyo3.architect")
385)]
386pub enum AxOrderType {
387    /// Market order; execute immediately at best available price.
388    Market,
389    /// Limit order; execute no worse than the limit price specified.
390    Limit,
391    /// Stop-limit order; if the trigger price is breached, place a limit order.
392    StopLossLimit,
393    /// Take-profit order; if the trigger price is breached, place a limit order.
394    /// Note: Not currently implemented by Architect.
395    TakeProfitLimit,
396}
397
398impl From<AxOrderType> for OrderType {
399    fn from(order_type: AxOrderType) -> Self {
400        match order_type {
401            AxOrderType::Market => Self::Market,
402            AxOrderType::Limit => Self::Limit,
403            AxOrderType::StopLossLimit => Self::StopLimit,
404            AxOrderType::TakeProfitLimit => Self::LimitIfTouched,
405        }
406    }
407}
408
409impl TryFrom<OrderType> for AxOrderType {
410    type Error = &'static str;
411
412    fn try_from(order_type: OrderType) -> Result<Self, Self::Error> {
413        match order_type {
414            OrderType::Market => Ok(Self::Market),
415            OrderType::Limit => Ok(Self::Limit),
416            OrderType::StopLimit => Ok(Self::StopLossLimit),
417            OrderType::LimitIfTouched => Ok(Self::TakeProfitLimit),
418            _ => Err("Unsupported order type for AX"),
419        }
420    }
421}
422
423/// Market data subscription level.
424///
425/// # References
426/// - <https://docs.architect.exchange/api-reference/marketdata/md-ws>
427#[derive(
428    Clone,
429    Copy,
430    Debug,
431    Display,
432    Eq,
433    PartialEq,
434    Hash,
435    AsRefStr,
436    EnumIter,
437    EnumString,
438    Serialize,
439    Deserialize,
440)]
441#[strum(ascii_case_insensitive)]
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    )
451)]
452pub enum AxMarketDataLevel {
453    /// Level 1: best bid/ask only.
454    #[serde(rename = "LEVEL_1")]
455    #[strum(serialize = "LEVEL_1")]
456    Level1,
457    /// Level 2: aggregated price levels.
458    #[serde(rename = "LEVEL_2")]
459    #[strum(serialize = "LEVEL_2")]
460    Level2,
461    /// Level 3: individual order quantities.
462    #[serde(rename = "LEVEL_3")]
463    #[strum(serialize = "LEVEL_3")]
464    Level3,
465}
466
467/// Candle/bar width for market data subscriptions.
468///
469/// # References
470/// - <https://docs.architect.exchange/api-reference/marketdata/md-ws>
471#[derive(
472    Clone,
473    Copy,
474    Debug,
475    Display,
476    Eq,
477    PartialEq,
478    Hash,
479    AsRefStr,
480    EnumIter,
481    EnumString,
482    Serialize,
483    Deserialize,
484)]
485pub enum AxCandleWidth {
486    /// 1-second candles.
487    #[serde(rename = "1s")]
488    #[strum(serialize = "1s")]
489    Seconds1,
490    /// 5-second candles.
491    #[serde(rename = "5s")]
492    #[strum(serialize = "5s")]
493    Seconds5,
494    /// 1-minute candles.
495    #[serde(rename = "1m")]
496    #[strum(serialize = "1m")]
497    Minutes1,
498    /// 5-minute candles.
499    #[serde(rename = "5m")]
500    #[strum(serialize = "5m")]
501    Minutes5,
502    /// 15-minute candles.
503    #[serde(rename = "15m")]
504    #[strum(serialize = "15m")]
505    Minutes15,
506    /// 1-hour candles.
507    #[serde(rename = "1h")]
508    #[strum(serialize = "1h")]
509    Hours1,
510    /// 1-day candles.
511    #[serde(rename = "1d")]
512    #[strum(serialize = "1d")]
513    Days1,
514}
515
516impl TryFrom<&BarSpecification> for AxCandleWidth {
517    type Error = anyhow::Error;
518
519    fn try_from(spec: &BarSpecification) -> Result<Self, Self::Error> {
520        let step = spec.step.get();
521        match (step, spec.aggregation) {
522            (1, BarAggregation::Second) => Ok(Self::Seconds1),
523            (5, BarAggregation::Second) => Ok(Self::Seconds5),
524            (1, BarAggregation::Minute) => Ok(Self::Minutes1),
525            (5, BarAggregation::Minute) => Ok(Self::Minutes5),
526            (15, BarAggregation::Minute) => Ok(Self::Minutes15),
527            (1, BarAggregation::Hour) => Ok(Self::Hours1),
528            (1, BarAggregation::Day) => Ok(Self::Days1),
529            _ => anyhow::bail!(
530                "Unsupported bar specification for AX: {step}-{:?}",
531                spec.aggregation,
532            ),
533        }
534    }
535}
536
537/// WebSocket market data request type (client to server).
538///
539/// # References
540/// - <https://docs.architect.exchange/api-reference/marketdata/md-ws>
541#[derive(
542    Clone,
543    Copy,
544    Debug,
545    Display,
546    Eq,
547    PartialEq,
548    Hash,
549    AsRefStr,
550    EnumIter,
551    EnumString,
552    Serialize,
553    Deserialize,
554)]
555pub enum AxMdRequestType {
556    /// Subscribe to market data for a symbol.
557    #[serde(rename = "subscribe")]
558    #[strum(serialize = "subscribe")]
559    Subscribe,
560    /// Unsubscribe from market data for a symbol.
561    #[serde(rename = "unsubscribe")]
562    #[strum(serialize = "unsubscribe")]
563    Unsubscribe,
564    /// Subscribe to candle data for a symbol.
565    #[serde(rename = "subscribe_candles")]
566    #[strum(serialize = "subscribe_candles")]
567    SubscribeCandles,
568    /// Unsubscribe from candle data for a symbol.
569    #[serde(rename = "unsubscribe_candles")]
570    #[strum(serialize = "unsubscribe_candles")]
571    UnsubscribeCandles,
572}
573
574/// WebSocket order request type (client to server).
575///
576/// # References
577/// - <https://docs.architect.exchange/api-reference/order-management/orders-ws>
578#[derive(
579    Clone,
580    Copy,
581    Debug,
582    Display,
583    Eq,
584    PartialEq,
585    Hash,
586    AsRefStr,
587    EnumIter,
588    EnumString,
589    Serialize,
590    Deserialize,
591)]
592pub enum AxOrderRequestType {
593    /// Place a new order.
594    #[serde(rename = "p")]
595    #[strum(serialize = "p")]
596    PlaceOrder,
597    /// Cancel an existing order.
598    #[serde(rename = "x")]
599    #[strum(serialize = "x")]
600    CancelOrder,
601    /// Get open orders.
602    #[serde(rename = "o")]
603    #[strum(serialize = "o")]
604    GetOpenOrders,
605}
606
607/// WebSocket market data message type (server to client).
608///
609/// # References
610/// - <https://docs.architect.exchange/api-reference/marketdata/md-ws>
611#[derive(
612    Clone,
613    Copy,
614    Debug,
615    Display,
616    Eq,
617    PartialEq,
618    Hash,
619    AsRefStr,
620    EnumIter,
621    EnumString,
622    Serialize,
623    Deserialize,
624)]
625#[cfg_attr(
626    feature = "python",
627    pyo3::pyclass(eq, eq_int, module = "nautilus_trader.core.nautilus_pyo3.architect")
628)]
629pub enum AxMdWsMessageType {
630    /// Heartbeat event.
631    #[serde(rename = "h")]
632    #[strum(serialize = "h")]
633    Heartbeat,
634    /// Ticker statistics update.
635    #[serde(rename = "s")]
636    #[strum(serialize = "s")]
637    Ticker,
638    /// Trade event.
639    #[serde(rename = "t")]
640    #[strum(serialize = "t")]
641    Trade,
642    /// Candle/OHLCV update.
643    #[serde(rename = "c")]
644    #[strum(serialize = "c")]
645    Candle,
646    /// Level 1 book update (best bid/ask).
647    #[serde(rename = "1")]
648    #[strum(serialize = "1")]
649    BookLevel1,
650    /// Level 2 book update (aggregated levels).
651    #[serde(rename = "2")]
652    #[strum(serialize = "2")]
653    BookLevel2,
654    /// Level 3 book update (individual orders).
655    #[serde(rename = "3")]
656    #[strum(serialize = "3")]
657    BookLevel3,
658}
659
660/// WebSocket order message type (server to client).
661///
662/// # References
663/// - <https://docs.architect.exchange/api-reference/order-management/orders-ws>
664#[derive(
665    Clone,
666    Copy,
667    Debug,
668    Display,
669    Eq,
670    PartialEq,
671    Hash,
672    AsRefStr,
673    EnumIter,
674    EnumString,
675    Serialize,
676    Deserialize,
677)]
678#[cfg_attr(
679    feature = "python",
680    pyo3::pyclass(eq, eq_int, module = "nautilus_trader.core.nautilus_pyo3.architect")
681)]
682pub enum AxOrderWsMessageType {
683    /// Heartbeat event.
684    #[serde(rename = "h")]
685    #[strum(serialize = "h")]
686    Heartbeat,
687    /// Cancel rejected event.
688    #[serde(rename = "e")]
689    #[strum(serialize = "e")]
690    CancelRejected,
691    /// Order acknowledged event.
692    #[serde(rename = "n")]
693    #[strum(serialize = "n")]
694    OrderAcknowledged,
695    /// Order canceled event.
696    #[serde(rename = "c")]
697    #[strum(serialize = "c")]
698    OrderCanceled,
699    /// Order replaced/amended event.
700    #[serde(rename = "r")]
701    #[strum(serialize = "r")]
702    OrderReplaced,
703    /// Order rejected event.
704    #[serde(rename = "j")]
705    #[strum(serialize = "j")]
706    OrderRejected,
707    /// Order expired event.
708    #[serde(rename = "x")]
709    #[strum(serialize = "x")]
710    OrderExpired,
711    /// Order done for day event.
712    #[serde(rename = "d")]
713    #[strum(serialize = "d")]
714    OrderDoneForDay,
715    /// Order partially filled event.
716    #[serde(rename = "p")]
717    #[strum(serialize = "p")]
718    OrderPartiallyFilled,
719    /// Order filled event.
720    #[serde(rename = "f")]
721    #[strum(serialize = "f")]
722    OrderFilled,
723}
724
725/// Reason for order cancellation.
726///
727/// # References
728/// - <https://docs.architect.exchange/api-reference/order-management/orders-ws>
729#[derive(
730    Clone,
731    Copy,
732    Debug,
733    Display,
734    Eq,
735    PartialEq,
736    Hash,
737    AsRefStr,
738    EnumIter,
739    EnumString,
740    Serialize,
741    Deserialize,
742)]
743#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
744#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
745#[cfg_attr(
746    feature = "python",
747    pyo3::pyclass(eq, eq_int, module = "nautilus_trader.core.nautilus_pyo3.architect")
748)]
749pub enum AxCancelReason {
750    /// User requested cancellation.
751    UserRequested,
752    /// Unrecognized or empty reason from the server.
753    #[serde(other)]
754    Unknown,
755}
756
757/// Reason for cancel rejection.
758///
759/// # References
760/// - <https://docs.architect.exchange/api-reference/order-management/orders-ws>
761#[derive(
762    Clone,
763    Copy,
764    Debug,
765    Display,
766    Eq,
767    PartialEq,
768    Hash,
769    AsRefStr,
770    EnumIter,
771    EnumString,
772    Serialize,
773    Deserialize,
774)]
775#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
776#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
777#[cfg_attr(
778    feature = "python",
779    pyo3::pyclass(eq, eq_int, module = "nautilus_trader.core.nautilus_pyo3.architect")
780)]
781pub enum AxCancelRejectionReason {
782    /// Order not found or already canceled.
783    OrderNotFound,
784    /// Unrecognized reason from the server.
785    #[serde(other)]
786    Unknown,
787}
788
789#[cfg(test)]
790mod tests {
791    use rstest::rstest;
792
793    use super::*;
794
795    #[rstest]
796    #[case(AxInstrumentState::Open, "\"OPEN\"")]
797    #[case(AxInstrumentState::PreOpen, "\"PRE_OPEN\"")]
798    #[case(AxInstrumentState::Suspended, "\"SUSPENDED\"")]
799    #[case(AxInstrumentState::Delisted, "\"DELISTED\"")]
800    fn test_instrument_state_serialization(
801        #[case] state: AxInstrumentState,
802        #[case] expected: &str,
803    ) {
804        let json = serde_json::to_string(&state).unwrap();
805        assert_eq!(json, expected);
806
807        let parsed: AxInstrumentState = serde_json::from_str(&json).unwrap();
808        assert_eq!(parsed, state);
809    }
810
811    #[rstest]
812    #[case(AxOrderSide::Buy, "\"B\"")]
813    #[case(AxOrderSide::Sell, "\"S\"")]
814    fn test_order_side_serialization(#[case] side: AxOrderSide, #[case] expected: &str) {
815        let json = serde_json::to_string(&side).unwrap();
816        assert_eq!(json, expected);
817
818        let parsed: AxOrderSide = serde_json::from_str(&json).unwrap();
819        assert_eq!(parsed, side);
820    }
821
822    #[rstest]
823    #[case(AxOrderStatus::Pending, "\"PENDING\"")]
824    #[case(AxOrderStatus::Accepted, "\"ACCEPTED\"")]
825    #[case(AxOrderStatus::PartiallyFilled, "\"PARTIALLY_FILLED\"")]
826    #[case(AxOrderStatus::Filled, "\"FILLED\"")]
827    #[case(AxOrderStatus::Canceling, "\"CANCELING\"")]
828    #[case(AxOrderStatus::Canceled, "\"CANCELED\"")]
829    #[case(AxOrderStatus::Out, "\"OUT\"")]
830    #[case(AxOrderStatus::ReconciledOut, "\"RECONCILED_OUT\"")]
831    #[case(AxOrderStatus::Stale, "\"STALE\"")]
832    fn test_order_status_serialization(#[case] status: AxOrderStatus, #[case] expected: &str) {
833        let json = serde_json::to_string(&status).unwrap();
834        assert_eq!(json, expected);
835
836        let parsed: AxOrderStatus = serde_json::from_str(&json).unwrap();
837        assert_eq!(parsed, status);
838    }
839
840    #[rstest]
841    #[case(AxTimeInForce::Gtc, "\"GTC\"")]
842    #[case(AxTimeInForce::Ioc, "\"IOC\"")]
843    #[case(AxTimeInForce::Day, "\"DAY\"")]
844    #[case(AxTimeInForce::Gtd, "\"GTD\"")]
845    #[case(AxTimeInForce::Fok, "\"FOK\"")]
846    #[case(AxTimeInForce::Ato, "\"ATO\"")]
847    #[case(AxTimeInForce::Atc, "\"ATC\"")]
848    fn test_time_in_force_serialization(#[case] tif: AxTimeInForce, #[case] expected: &str) {
849        let json = serde_json::to_string(&tif).unwrap();
850        assert_eq!(json, expected);
851
852        let parsed: AxTimeInForce = serde_json::from_str(&json).unwrap();
853        assert_eq!(parsed, tif);
854    }
855
856    #[rstest]
857    #[case(AxOrderType::Market, "\"MARKET\"")]
858    #[case(AxOrderType::Limit, "\"LIMIT\"")]
859    #[case(AxOrderType::StopLossLimit, "\"STOP_LOSS_LIMIT\"")]
860    #[case(AxOrderType::TakeProfitLimit, "\"TAKE_PROFIT_LIMIT\"")]
861    fn test_order_type_serialization(#[case] order_type: AxOrderType, #[case] expected: &str) {
862        let json = serde_json::to_string(&order_type).unwrap();
863        assert_eq!(json, expected);
864
865        let parsed: AxOrderType = serde_json::from_str(&json).unwrap();
866        assert_eq!(parsed, order_type);
867    }
868
869    #[rstest]
870    #[case(AxMarketDataLevel::Level1, "\"LEVEL_1\"")]
871    #[case(AxMarketDataLevel::Level2, "\"LEVEL_2\"")]
872    #[case(AxMarketDataLevel::Level3, "\"LEVEL_3\"")]
873    fn test_market_data_level_serialization(
874        #[case] level: AxMarketDataLevel,
875        #[case] expected: &str,
876    ) {
877        let json = serde_json::to_string(&level).unwrap();
878        assert_eq!(json, expected);
879
880        let parsed: AxMarketDataLevel = serde_json::from_str(&json).unwrap();
881        assert_eq!(parsed, level);
882    }
883
884    #[rstest]
885    #[case(AxCandleWidth::Seconds1, "\"1s\"")]
886    #[case(AxCandleWidth::Minutes1, "\"1m\"")]
887    #[case(AxCandleWidth::Minutes5, "\"5m\"")]
888    #[case(AxCandleWidth::Hours1, "\"1h\"")]
889    #[case(AxCandleWidth::Days1, "\"1d\"")]
890    fn test_candle_width_serialization(#[case] width: AxCandleWidth, #[case] expected: &str) {
891        let json = serde_json::to_string(&width).unwrap();
892        assert_eq!(json, expected);
893
894        let parsed: AxCandleWidth = serde_json::from_str(&json).unwrap();
895        assert_eq!(parsed, width);
896    }
897
898    #[rstest]
899    #[case(AxMdWsMessageType::Heartbeat, "\"h\"")]
900    #[case(AxMdWsMessageType::Ticker, "\"s\"")]
901    #[case(AxMdWsMessageType::Trade, "\"t\"")]
902    #[case(AxMdWsMessageType::Candle, "\"c\"")]
903    #[case(AxMdWsMessageType::BookLevel1, "\"1\"")]
904    #[case(AxMdWsMessageType::BookLevel2, "\"2\"")]
905    #[case(AxMdWsMessageType::BookLevel3, "\"3\"")]
906    fn test_md_ws_message_type_serialization(
907        #[case] msg_type: AxMdWsMessageType,
908        #[case] expected: &str,
909    ) {
910        let json = serde_json::to_string(&msg_type).unwrap();
911        assert_eq!(json, expected);
912
913        let parsed: AxMdWsMessageType = serde_json::from_str(&json).unwrap();
914        assert_eq!(parsed, msg_type);
915    }
916
917    #[rstest]
918    #[case(AxOrderWsMessageType::Heartbeat, "\"h\"")]
919    #[case(AxOrderWsMessageType::OrderAcknowledged, "\"n\"")]
920    #[case(AxOrderWsMessageType::OrderCanceled, "\"c\"")]
921    #[case(AxOrderWsMessageType::OrderFilled, "\"f\"")]
922    #[case(AxOrderWsMessageType::OrderPartiallyFilled, "\"p\"")]
923    fn test_order_ws_message_type_serialization(
924        #[case] msg_type: AxOrderWsMessageType,
925        #[case] expected: &str,
926    ) {
927        let json = serde_json::to_string(&msg_type).unwrap();
928        assert_eq!(json, expected);
929
930        let parsed: AxOrderWsMessageType = serde_json::from_str(&json).unwrap();
931        assert_eq!(parsed, msg_type);
932    }
933
934    #[rstest]
935    #[case(AxMdRequestType::Subscribe, "\"subscribe\"")]
936    #[case(AxMdRequestType::Unsubscribe, "\"unsubscribe\"")]
937    #[case(AxMdRequestType::SubscribeCandles, "\"subscribe_candles\"")]
938    #[case(AxMdRequestType::UnsubscribeCandles, "\"unsubscribe_candles\"")]
939    fn test_md_request_type_serialization(
940        #[case] request_type: AxMdRequestType,
941        #[case] expected: &str,
942    ) {
943        let json = serde_json::to_string(&request_type).unwrap();
944        assert_eq!(json, expected);
945
946        let parsed: AxMdRequestType = serde_json::from_str(&json).unwrap();
947        assert_eq!(parsed, request_type);
948    }
949
950    #[rstest]
951    #[case(AxOrderRequestType::PlaceOrder, "\"p\"")]
952    #[case(AxOrderRequestType::CancelOrder, "\"x\"")]
953    #[case(AxOrderRequestType::GetOpenOrders, "\"o\"")]
954    fn test_order_request_type_serialization(
955        #[case] request_type: AxOrderRequestType,
956        #[case] expected: &str,
957    ) {
958        let json = serde_json::to_string(&request_type).unwrap();
959        assert_eq!(json, expected);
960
961        let parsed: AxOrderRequestType = serde_json::from_str(&json).unwrap();
962        assert_eq!(parsed, request_type);
963    }
964}