Skip to main content

nautilus_architect_ax/websocket/
messages.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//! WebSocket message types for the AX Exchange API.
17//!
18//! This module contains request and response message structures for both
19//! market data and order management WebSocket streams.
20
21use nautilus_core::{
22    UnixNanos,
23    serialization::{
24        deserialize_optional_decimal_str, serialize_decimal_as_str,
25        serialize_optional_decimal_as_str,
26    },
27};
28use nautilus_model::{
29    data::{Bar, Data, OrderBookDeltas},
30    events::{
31        OrderAccepted, OrderCancelRejected, OrderCanceled, OrderExpired, OrderFilled, OrderRejected,
32    },
33    identifiers::{ClientOrderId, InstrumentId, StrategyId, TraderId, VenueOrderId},
34    reports::{FillReport, OrderStatusReport},
35    types::Currency,
36};
37use rust_decimal::Decimal;
38use serde::{Deserialize, Serialize};
39use ustr::Ustr;
40
41use super::error::AxWsErrorResponse;
42use crate::common::{
43    enums::{
44        AxCancelReason, AxCancelRejectionReason, AxCandleWidth, AxMarketDataLevel, AxMdRequestType,
45        AxOrderRequestType, AxOrderSide, AxOrderStatus, AxOrderType, AxOrderWsMessageType,
46        AxTimeInForce,
47    },
48    parse::{deserialize_decimal_or_zero, deserialize_optional_decimal_or_zero},
49};
50
51/// Nautilus domain message emitted after parsing Ax WebSocket events.
52///
53/// This enum contains fully-parsed Nautilus domain objects ready for consumption
54/// by the DataClient without additional processing.
55#[derive(Debug, Clone)]
56pub enum NautilusDataWsMessage {
57    /// Market data (trades, quotes).
58    Data(Vec<Data>),
59    /// Order book deltas.
60    Deltas(OrderBookDeltas),
61    /// Bar/candle data.
62    Bar(Bar),
63    /// Heartbeat message.
64    Heartbeat,
65    /// Error from venue or client.
66    Error(AxWsError),
67    /// WebSocket reconnected notification.
68    Reconnected,
69}
70
71/// Nautilus domain messages for the Ax orders WebSocket.
72///
73/// This enum contains parsed messages from the WebSocket stream.
74/// Variants contain fully-parsed Nautilus domain objects.
75#[derive(Debug, Clone)]
76pub enum NautilusExecWsMessage {
77    /// Order accepted by the venue.
78    OrderAccepted(OrderAccepted),
79    /// Order filled (partial or complete).
80    OrderFilled(Box<OrderFilled>),
81    /// Order canceled.
82    OrderCanceled(OrderCanceled),
83    /// Order expired.
84    OrderExpired(OrderExpired),
85    /// Order rejected by venue.
86    OrderRejected(OrderRejected),
87    /// Order cancel rejected by venue.
88    OrderCancelRejected(OrderCancelRejected),
89    /// Order status reports from order updates.
90    OrderStatusReports(Vec<OrderStatusReport>),
91    /// Fill reports from executions.
92    FillReports(Vec<FillReport>),
93}
94
95/// Subscribe request for market data.
96///
97/// # References
98/// - <https://docs.architect.exchange/api-reference/marketdata/md-ws>
99#[derive(Clone, Debug, Serialize, Deserialize)]
100pub struct AxMdSubscribe {
101    /// Client request ID for correlation.
102    pub rid: i64,
103    /// Request type (always "subscribe").
104    #[serde(rename = "type")]
105    pub msg_type: AxMdRequestType,
106    /// Instrument symbol.
107    pub symbol: Ustr,
108    /// Market data level (LEVEL_1, LEVEL_2, LEVEL_3).
109    pub level: AxMarketDataLevel,
110}
111
112/// Unsubscribe request for market data.
113///
114/// # References
115/// - <https://docs.architect.exchange/api-reference/marketdata/md-ws>
116#[derive(Clone, Debug, Serialize, Deserialize)]
117pub struct AxMdUnsubscribe {
118    /// Client request ID for correlation.
119    pub rid: i64,
120    /// Request type (always "unsubscribe").
121    #[serde(rename = "type")]
122    pub msg_type: AxMdRequestType,
123    /// Instrument symbol.
124    pub symbol: Ustr,
125}
126
127/// Subscribe request for candle data.
128///
129/// # References
130/// - <https://docs.architect.exchange/api-reference/marketdata/md-ws>
131#[derive(Clone, Debug, Serialize, Deserialize)]
132pub struct AxMdSubscribeCandles {
133    /// Client request ID for correlation.
134    pub rid: i64,
135    /// Request type (always "subscribe_candles").
136    #[serde(rename = "type")]
137    pub msg_type: AxMdRequestType,
138    /// Instrument symbol.
139    pub symbol: Ustr,
140    /// Candle width/interval.
141    pub width: AxCandleWidth,
142}
143
144/// Unsubscribe request for candle data.
145///
146/// # References
147/// - <https://docs.architect.exchange/api-reference/marketdata/md-ws>
148#[derive(Clone, Debug, Serialize, Deserialize)]
149pub struct AxMdUnsubscribeCandles {
150    /// Client request ID for correlation.
151    pub rid: i64,
152    /// Request type (always "unsubscribe_candles").
153    #[serde(rename = "type")]
154    pub msg_type: AxMdRequestType,
155    /// Instrument symbol.
156    pub symbol: Ustr,
157    /// Candle width/interval.
158    pub width: AxCandleWidth,
159}
160
161/// Heartbeat message from market data WebSocket.
162///
163/// # References
164/// - <https://docs.architect.exchange/api-reference/marketdata/md-ws>
165#[derive(Clone, Debug, Serialize, Deserialize)]
166pub struct AxMdHeartbeat {
167    /// Timestamp (Unix epoch seconds).
168    pub ts: i64,
169    /// Transaction number.
170    pub tn: i64,
171}
172
173/// Incoming market data WebSocket message.
174///
175/// Deserializes directly from JSON using the "t" field as discriminator.
176#[derive(Clone, Debug)]
177pub enum AxMdMessage {
178    BookL1(AxMdBookL1),
179    BookL2(AxMdBookL2),
180    BookL3(AxMdBookL3),
181    Ticker(AxMdTicker),
182    Trade(AxMdTrade),
183    Candle(AxMdCandle),
184    Heartbeat(AxMdHeartbeat),
185    /// Subscription response (success or already subscribed).
186    SubscriptionResponse(AxMdSubscriptionResponse),
187    Error(AxWsError),
188}
189
190/// Subscription response from market data WebSocket.
191#[derive(Clone, Debug, Deserialize)]
192pub struct AxMdSubscriptionResponse {
193    /// Request ID for correlation.
194    pub rid: i64,
195    /// Result payload (contains subscribed symbol or candle info).
196    pub result: AxMdSubscriptionResult,
197}
198
199/// Result payload for subscription response.
200#[derive(Clone, Debug, Deserialize)]
201pub struct AxMdSubscriptionResult {
202    /// Subscribed symbol (for regular subscriptions).
203    #[serde(default)]
204    pub subscribed: Option<String>,
205    /// Subscribed candle info (for candle subscriptions).
206    #[serde(default)]
207    pub subscribed_candle: Option<String>,
208    /// Unsubscribed symbol (for unsubscription responses).
209    #[serde(default)]
210    pub unsubscribed: Option<String>,
211    /// Unsubscribed candle info (for candle unsubscription responses).
212    #[serde(default)]
213    pub unsubscribed_candle: Option<String>,
214}
215
216/// Error response from market data WebSocket with nested error object.
217#[derive(Clone, Debug, Deserialize)]
218pub struct AxMdErrorResponse {
219    /// Request ID for correlation.
220    pub rid: Option<i64>,
221    /// Nested error object containing code and message.
222    pub error: AxMdErrorInner,
223}
224
225/// Inner error object for market data WebSocket errors.
226#[derive(Clone, Debug, Deserialize)]
227pub struct AxMdErrorInner {
228    /// Error code.
229    pub code: i32,
230    /// Error message.
231    pub message: String,
232}
233
234impl From<AxMdErrorResponse> for AxWsError {
235    fn from(resp: AxMdErrorResponse) -> Self {
236        Self {
237            code: Some(resp.error.code.to_string()),
238            message: resp.error.message,
239            request_id: resp.rid,
240        }
241    }
242}
243
244/// Ticker/statistics message from market data WebSocket.
245///
246/// # References
247/// - <https://docs.architect.exchange/api-reference/marketdata/md-ws>
248#[derive(Clone, Debug, Serialize, Deserialize)]
249pub struct AxMdTicker {
250    /// Timestamp (Unix epoch seconds).
251    pub ts: i64,
252    /// Transaction number.
253    pub tn: i64,
254    /// Instrument symbol.
255    pub s: Ustr,
256    /// Last price.
257    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
258    pub p: Decimal,
259    /// Last quantity.
260    pub q: u64,
261    /// Open price (24h), null before first session open.
262    #[serde(deserialize_with = "deserialize_optional_decimal_or_zero")]
263    pub o: Decimal,
264    /// Low price (24h).
265    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
266    pub l: Decimal,
267    /// High price (24h).
268    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
269    pub h: Decimal,
270    /// Volume (24h).
271    pub v: u64,
272    /// Open interest.
273    #[serde(default)]
274    pub oi: Option<i64>,
275}
276
277/// Trade message from market data WebSocket.
278///
279/// # References
280/// - <https://docs.architect.exchange/api-reference/marketdata/md-ws>
281#[derive(Clone, Debug, Serialize, Deserialize)]
282pub struct AxMdTrade {
283    /// Timestamp (Unix epoch seconds).
284    pub ts: i64,
285    /// Transaction number.
286    pub tn: i64,
287    /// Instrument symbol.
288    pub s: Ustr,
289    /// Trade price.
290    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
291    pub p: Decimal,
292    /// Trade quantity.
293    pub q: u64,
294    /// Trade direction: "B" (buy) or "S" (sell). Optional for some message types.
295    #[serde(default)]
296    pub d: Option<AxOrderSide>,
297}
298
299/// Candle/OHLCV message from market data WebSocket.
300///
301/// # References
302/// - <https://docs.architect.exchange/api-reference/marketdata/md-ws>
303#[derive(Clone, Debug, Serialize, Deserialize)]
304pub struct AxMdCandle {
305    /// Instrument symbol.
306    pub symbol: Ustr,
307    /// Candle timestamp (Unix epoch).
308    pub ts: i64,
309    /// Open price.
310    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
311    pub open: Decimal,
312    /// Low price.
313    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
314    pub low: Decimal,
315    /// High price.
316    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
317    pub high: Decimal,
318    /// Close price.
319    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
320    pub close: Decimal,
321    /// Total volume.
322    pub volume: u64,
323    /// Buy volume.
324    pub buy_volume: u64,
325    /// Sell volume.
326    pub sell_volume: u64,
327    /// Candle width/interval.
328    pub width: AxCandleWidth,
329}
330
331/// Price level entry in order book.
332#[derive(Clone, Debug, Serialize, Deserialize)]
333pub struct AxBookLevel {
334    /// Price at this level.
335    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
336    pub p: Decimal,
337    /// Quantity at this level.
338    pub q: u64,
339}
340
341/// Price level entry with individual order breakdown (L3).
342#[derive(Clone, Debug, Serialize, Deserialize)]
343pub struct AxBookLevelL3 {
344    /// Price at this level.
345    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
346    pub p: Decimal,
347    /// Total quantity at this level.
348    pub q: u64,
349    /// Individual order quantities at this price.
350    pub o: Vec<u64>,
351}
352
353/// Level 1 order book update (best bid/ask).
354///
355/// # References
356/// - <https://docs.architect.exchange/api-reference/marketdata/md-ws>
357#[derive(Clone, Debug, Serialize, Deserialize)]
358pub struct AxMdBookL1 {
359    /// Timestamp (Unix epoch seconds).
360    pub ts: i64,
361    /// Transaction number.
362    pub tn: i64,
363    /// Instrument symbol.
364    pub s: Ustr,
365    /// Bid levels (typically just best bid).
366    pub b: Vec<AxBookLevel>,
367    /// Ask levels (typically just best ask).
368    pub a: Vec<AxBookLevel>,
369}
370
371/// Level 2 order book update (aggregated price levels).
372///
373/// # References
374/// - <https://docs.architect.exchange/api-reference/marketdata/md-ws>
375#[derive(Clone, Debug, Serialize, Deserialize)]
376pub struct AxMdBookL2 {
377    /// Timestamp (Unix epoch seconds).
378    pub ts: i64,
379    /// Transaction number.
380    pub tn: i64,
381    /// Instrument symbol.
382    pub s: Ustr,
383    /// Bid levels.
384    pub b: Vec<AxBookLevel>,
385    /// Ask levels.
386    pub a: Vec<AxBookLevel>,
387}
388
389/// Level 3 order book update (individual order quantities).
390///
391/// # References
392/// - <https://docs.architect.exchange/api-reference/marketdata/md-ws>
393#[derive(Clone, Debug, Serialize, Deserialize)]
394pub struct AxMdBookL3 {
395    /// Timestamp (Unix epoch seconds).
396    pub ts: i64,
397    /// Transaction number.
398    pub tn: i64,
399    /// Instrument symbol.
400    pub s: Ustr,
401    /// Bid levels with order breakdown.
402    pub b: Vec<AxBookLevelL3>,
403    /// Ask levels with order breakdown.
404    pub a: Vec<AxBookLevelL3>,
405}
406
407/// Place order request via WebSocket.
408///
409/// # References
410/// - <https://docs.architect.exchange/sdk-reference/order-entry>
411#[derive(Clone, Debug, Serialize, Deserialize)]
412pub struct AxWsPlaceOrder {
413    /// Request ID for correlation.
414    pub rid: i64,
415    /// Message type (always "p").
416    pub t: AxOrderRequestType,
417    /// Instrument symbol.
418    pub s: Ustr,
419    /// Order side: "B" (buy) or "S" (sell).
420    pub d: AxOrderSide,
421    /// Order quantity.
422    pub q: u64,
423    /// Order price (limit price).
424    #[serde(
425        serialize_with = "serialize_decimal_as_str",
426        deserialize_with = "deserialize_decimal_or_zero"
427    )]
428    pub p: Decimal,
429    /// Time in force.
430    pub tif: AxTimeInForce,
431    /// Post-only flag (maker-or-cancel).
432    pub po: bool,
433    /// Optional client order ID.
434    #[serde(skip_serializing_if = "Option::is_none")]
435    pub cid: Option<u64>,
436    /// Optional order tag (max 10 alphanumeric characters).
437    #[serde(skip_serializing_if = "Option::is_none")]
438    pub tag: Option<String>,
439    /// Order type (defaults to LIMIT if not specified).
440    #[serde(skip_serializing_if = "Option::is_none")]
441    pub order_type: Option<AxOrderType>,
442    /// Trigger price for stop-loss orders.
443    #[serde(
444        skip_serializing_if = "Option::is_none",
445        serialize_with = "serialize_optional_decimal_as_str",
446        deserialize_with = "deserialize_optional_decimal_str",
447        default
448    )]
449    pub trigger_price: Option<Decimal>,
450}
451
452/// Cancel order request via WebSocket.
453///
454/// # References
455/// - <https://docs.architect.exchange/api-reference/order-management/orders-ws>
456#[derive(Clone, Debug, Serialize, Deserialize)]
457pub struct AxWsCancelOrder {
458    /// Request ID for correlation.
459    pub rid: i64,
460    /// Message type (always "x").
461    pub t: AxOrderRequestType,
462    /// Order ID to cancel.
463    pub oid: String,
464}
465
466/// Get open orders request via WebSocket.
467///
468/// # References
469/// - <https://docs.architect.exchange/api-reference/order-management/orders-ws>
470#[derive(Clone, Debug, Serialize, Deserialize)]
471pub struct AxWsGetOpenOrders {
472    /// Request ID for correlation.
473    pub rid: i64,
474    /// Message type (always "o").
475    pub t: AxOrderRequestType,
476}
477
478/// Place order response from WebSocket.
479///
480/// # References
481/// - <https://docs.architect.exchange/api-reference/order-management/orders-ws>
482#[derive(Clone, Debug, Serialize, Deserialize)]
483pub struct AxWsPlaceOrderResponse {
484    /// Request ID matching the original request.
485    pub rid: i64,
486    /// Response result.
487    pub res: AxWsPlaceOrderResult,
488}
489
490/// Result payload for place order response.
491#[derive(Clone, Debug, Serialize, Deserialize)]
492pub struct AxWsPlaceOrderResult {
493    /// Order ID of the placed order.
494    pub oid: String,
495}
496
497/// Cancel order response from WebSocket.
498///
499/// # References
500/// - <https://docs.architect.exchange/api-reference/order-management/orders-ws>
501#[derive(Clone, Debug, Serialize, Deserialize)]
502pub struct AxWsCancelOrderResponse {
503    /// Request ID matching the original request.
504    pub rid: i64,
505    /// Response result.
506    pub res: AxWsCancelOrderResult,
507}
508
509/// Result payload for cancel order response.
510#[derive(Clone, Debug, Serialize, Deserialize)]
511pub struct AxWsCancelOrderResult {
512    /// Whether the cancel request was received.
513    pub cxl_rx: bool,
514}
515
516/// Open orders response from WebSocket.
517///
518/// # References
519/// - <https://docs.architect.exchange/api-reference/order-management/orders-ws>
520#[derive(Clone, Debug, Serialize, Deserialize)]
521pub struct AxWsOpenOrdersResponse {
522    /// Request ID matching the original request.
523    pub rid: i64,
524    /// List of open orders.
525    pub res: Vec<AxWsOrder>,
526}
527
528/// Error response from the Ax orders WebSocket.
529///
530/// Returned when a request fails (e.g., insufficient margin, invalid order).
531#[derive(Clone, Debug, Deserialize)]
532pub struct AxWsOrderErrorResponse {
533    /// Request ID matching the original request.
534    pub rid: i64,
535    /// Error details.
536    pub err: AxWsOrderError,
537}
538
539/// Error details in an error response.
540#[derive(Clone, Debug, Deserialize)]
541pub struct AxWsOrderError {
542    /// Error code (e.g., 400).
543    pub code: i64,
544    /// Error message.
545    pub msg: String,
546}
547
548/// List subscription response from the Ax orders WebSocket.
549///
550/// Returned when subscribing to order updates, contains a list ID for the subscription.
551#[derive(Clone, Debug, Deserialize)]
552pub struct AxWsListResponse {
553    /// Request ID matching the original request.
554    pub rid: i64,
555    /// Response result.
556    pub res: AxWsListResult,
557}
558
559/// List subscription result payload.
560#[derive(Clone, Debug, Deserialize)]
561pub struct AxWsListResult {
562    /// List subscription ID.
563    pub li: String,
564    /// Order data (null on initial subscription, array of orders otherwise).
565    #[serde(default)]
566    pub o: Option<Vec<AxWsOrder>>,
567}
568
569/// Order details in WebSocket messages.
570#[derive(Clone, Debug, Serialize, Deserialize)]
571pub struct AxWsOrder {
572    /// Order ID.
573    pub oid: String,
574    /// User ID.
575    pub u: String,
576    /// Instrument symbol.
577    pub s: Ustr,
578    /// Order price.
579    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
580    pub p: Decimal,
581    /// Order quantity.
582    pub q: u64,
583    /// Executed quantity.
584    pub xq: u64,
585    /// Remaining quantity.
586    pub rq: u64,
587    /// Order status.
588    pub o: AxOrderStatus,
589    /// Order side.
590    pub d: AxOrderSide,
591    /// Time in force.
592    pub tif: AxTimeInForce,
593    /// Timestamp (Unix epoch seconds).
594    pub ts: i64,
595    /// Transaction number.
596    pub tn: i64,
597    /// Optional client order ID.
598    #[serde(default)]
599    pub cid: Option<u64>,
600    /// Optional order tag.
601    #[serde(default)]
602    pub tag: Option<String>,
603    /// Optional text/description.
604    #[serde(default)]
605    pub txt: Option<String>,
606}
607
608/// Heartbeat event from orders WebSocket.
609///
610/// # References
611/// - <https://docs.architect.exchange/api-reference/order-management/orders-ws>
612#[derive(Clone, Debug, Serialize, Deserialize)]
613pub struct AxWsHeartbeat {
614    /// Message type (always "h").
615    pub t: AxOrderWsMessageType,
616    /// Timestamp (Unix epoch seconds).
617    pub ts: i64,
618    /// Transaction number.
619    pub tn: i64,
620}
621
622/// Order acknowledged event.
623///
624/// # References
625/// - <https://docs.architect.exchange/api-reference/order-management/orders-ws>
626#[derive(Clone, Debug, Serialize, Deserialize)]
627pub struct AxWsOrderAcknowledged {
628    /// Timestamp (Unix epoch seconds).
629    pub ts: i64,
630    /// Transaction number.
631    pub tn: i64,
632    /// Event ID.
633    pub eid: String,
634    /// Order details.
635    pub o: AxWsOrder,
636}
637
638/// Trade execution details for fill events.
639#[derive(Clone, Debug, Serialize, Deserialize)]
640pub struct AxWsTradeExecution {
641    /// Trade ID.
642    pub tid: String,
643    /// Instrument symbol.
644    pub s: Ustr,
645    /// Executed quantity.
646    pub q: u64,
647    /// Execution price.
648    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
649    pub p: Decimal,
650    /// Trade direction.
651    pub d: AxOrderSide,
652    /// Whether this was an aggressor (taker) order.
653    pub agg: bool,
654}
655
656/// Order partially filled event.
657///
658/// # References
659/// - <https://docs.architect.exchange/api-reference/order-management/orders-ws>
660#[derive(Clone, Debug, Serialize, Deserialize)]
661pub struct AxWsOrderPartiallyFilled {
662    /// Timestamp (Unix epoch seconds).
663    pub ts: i64,
664    /// Transaction number.
665    pub tn: i64,
666    /// Event ID.
667    pub eid: String,
668    /// Order details.
669    pub o: AxWsOrder,
670    /// Trade execution details.
671    pub xs: AxWsTradeExecution,
672}
673
674/// Order filled event.
675///
676/// # References
677/// - <https://docs.architect.exchange/api-reference/order-management/orders-ws>
678#[derive(Clone, Debug, Serialize, Deserialize)]
679pub struct AxWsOrderFilled {
680    /// Timestamp (Unix epoch seconds).
681    pub ts: i64,
682    /// Transaction number.
683    pub tn: i64,
684    /// Event ID.
685    pub eid: String,
686    /// Order details.
687    pub o: AxWsOrder,
688    /// Trade execution details.
689    pub xs: AxWsTradeExecution,
690}
691
692/// Order canceled event.
693///
694/// # References
695/// - <https://docs.architect.exchange/api-reference/order-management/orders-ws>
696#[derive(Clone, Debug, Serialize, Deserialize)]
697pub struct AxWsOrderCanceled {
698    /// Timestamp (Unix epoch seconds).
699    pub ts: i64,
700    /// Transaction number.
701    pub tn: i64,
702    /// Event ID.
703    pub eid: String,
704    /// Order details.
705    pub o: AxWsOrder,
706    /// Cancellation reason.
707    pub xr: AxCancelReason,
708    /// Cancellation text/description.
709    #[serde(default)]
710    pub txt: Option<String>,
711}
712
713/// Order rejected event.
714///
715/// # References
716/// - <https://docs.architect.exchange/api-reference/order-management/orders-ws>
717#[derive(Clone, Debug, Serialize, Deserialize)]
718pub struct AxWsOrderRejected {
719    /// Timestamp (Unix epoch seconds).
720    pub ts: i64,
721    /// Transaction number.
722    pub tn: i64,
723    /// Event ID.
724    pub eid: String,
725    /// Order details.
726    pub o: AxWsOrder,
727    /// Rejection reason code (can be null, defaults to txt or "UNKNOWN").
728    #[serde(default)]
729    pub r: Option<String>,
730    /// Rejection text/description.
731    #[serde(default)]
732    pub txt: Option<String>,
733}
734
735/// Order expired event.
736///
737/// # References
738/// - <https://docs.architect.exchange/api-reference/order-management/orders-ws>
739#[derive(Clone, Debug, Serialize, Deserialize)]
740pub struct AxWsOrderExpired {
741    /// Timestamp (Unix epoch seconds).
742    pub ts: i64,
743    /// Transaction number.
744    pub tn: i64,
745    /// Event ID.
746    pub eid: String,
747    /// Order details.
748    pub o: AxWsOrder,
749}
750
751/// Order replaced/amended event.
752///
753/// # References
754/// - <https://docs.architect.exchange/api-reference/order-management/orders-ws>
755#[derive(Clone, Debug, Serialize, Deserialize)]
756pub struct AxWsOrderReplaced {
757    /// Timestamp (Unix epoch seconds).
758    pub ts: i64,
759    /// Transaction number.
760    pub tn: i64,
761    /// Event ID.
762    pub eid: String,
763    /// Order details.
764    pub o: AxWsOrder,
765}
766
767/// Order done for day event.
768///
769/// # References
770/// - <https://docs.architect.exchange/api-reference/order-management/orders-ws>
771#[derive(Clone, Debug, Serialize, Deserialize)]
772pub struct AxWsOrderDoneForDay {
773    /// Timestamp (Unix epoch seconds).
774    pub ts: i64,
775    /// Transaction number.
776    pub tn: i64,
777    /// Event ID.
778    pub eid: String,
779    /// Order details.
780    pub o: AxWsOrder,
781}
782
783/// Cancel rejected event.
784///
785/// # References
786/// - <https://docs.architect.exchange/api-reference/order-management/orders-ws>
787#[derive(Clone, Debug, Serialize, Deserialize)]
788pub struct AxWsCancelRejected {
789    /// Timestamp (Unix epoch seconds).
790    pub ts: i64,
791    /// Transaction number.
792    pub tn: i64,
793    /// Order ID that failed to cancel.
794    pub oid: String,
795    /// Rejection reason code.
796    pub r: AxCancelRejectionReason,
797    /// Rejection text/description.
798    #[serde(default)]
799    pub txt: Option<String>,
800}
801
802/// Internal raw message from the Ax orders WebSocket.
803///
804/// This enum uses serde's tagged deserialization to automatically
805/// discriminate between different event types based on the "t" field.
806#[derive(Debug, Clone, Deserialize)]
807#[serde(tag = "t")]
808pub(crate) enum AxWsOrderEvent {
809    /// Heartbeat message.
810    #[serde(rename = "h")]
811    Heartbeat,
812    /// Order acknowledged.
813    #[serde(rename = "n")]
814    Acknowledged(AxWsOrderAcknowledged),
815    /// Order partially filled.
816    #[serde(rename = "p")]
817    PartiallyFilled(AxWsOrderPartiallyFilled),
818    /// Order filled.
819    #[serde(rename = "f")]
820    Filled(AxWsOrderFilled),
821    /// Order canceled.
822    #[serde(rename = "c")]
823    Canceled(AxWsOrderCanceled),
824    /// Order rejected.
825    #[serde(rename = "j")]
826    Rejected(AxWsOrderRejected),
827    /// Order expired.
828    #[serde(rename = "x")]
829    Expired(AxWsOrderExpired),
830    /// Order replaced.
831    #[serde(rename = "r")]
832    Replaced(AxWsOrderReplaced),
833    /// Order done for day.
834    #[serde(rename = "d")]
835    DoneForDay(AxWsOrderDoneForDay),
836    /// Cancel rejected.
837    #[serde(rename = "e")]
838    CancelRejected(AxWsCancelRejected),
839}
840
841/// Internal raw response from the Ax orders WebSocket.
842///
843/// Response messages have "rid" and "res" fields.
844#[derive(Debug, Clone)]
845pub(crate) enum AxWsOrderResponse {
846    /// Place order response (res has "oid").
847    PlaceOrder(AxWsPlaceOrderResponse),
848    /// Cancel order response (res has "cxl_rx").
849    CancelOrder(AxWsCancelOrderResponse),
850    /// Open orders response (res is array).
851    OpenOrders(AxWsOpenOrdersResponse),
852    /// List subscription response (res has "li").
853    List(AxWsListResponse),
854}
855
856/// Internal raw message from the Ax orders WebSocket.
857#[derive(Debug, Clone)]
858pub(crate) enum AxWsRawMessage {
859    /// Error response message (has "rid" and "err").
860    Error(AxWsOrderErrorResponse),
861    /// Response message (has "rid" and "res").
862    Response(AxWsOrderResponse),
863    /// Event message (has "t" field).
864    Event(Box<AxWsOrderEvent>),
865}
866
867/// Ax-specific messages for the orders WebSocket.
868///
869/// This enum contains response and control messages from the WebSocket stream.
870#[derive(Debug, Clone)]
871pub enum AxOrdersWsMessage {
872    /// Nautilus domain messages parsed from order events.
873    Nautilus(NautilusExecWsMessage),
874    /// Place order response.
875    PlaceOrderResponse(AxWsPlaceOrderResponse),
876    /// Cancel order response.
877    CancelOrderResponse(AxWsCancelOrderResponse),
878    /// Open orders response.
879    OpenOrdersResponse(AxWsOpenOrdersResponse),
880    /// Error from venue or client.
881    Error(AxWsError),
882    /// WebSocket reconnected notification.
883    Reconnected,
884    /// Authentication successful notification.
885    Authenticated,
886}
887
888/// Represents an error event surfaced by the WebSocket client.
889#[derive(Debug, Clone)]
890pub struct AxWsError {
891    /// Error code from Ax.
892    pub code: Option<String>,
893    /// Human readable message.
894    pub message: String,
895    /// Optional request ID related to the failure.
896    pub request_id: Option<i64>,
897}
898
899impl AxWsError {
900    /// Creates a new error with the provided message.
901    #[must_use]
902    pub fn new(message: impl Into<String>) -> Self {
903        Self {
904            code: None,
905            message: message.into(),
906            request_id: None,
907        }
908    }
909
910    /// Creates a new error with code and message.
911    #[must_use]
912    pub fn with_code(code: impl Into<String>, message: impl Into<String>) -> Self {
913        Self {
914            code: Some(code.into()),
915            message: message.into(),
916            request_id: None,
917        }
918    }
919}
920
921impl From<AxWsOrderErrorResponse> for AxWsError {
922    fn from(resp: AxWsOrderErrorResponse) -> Self {
923        Self {
924            code: Some(resp.err.code.to_string()),
925            message: resp.err.msg,
926            request_id: Some(resp.rid),
927        }
928    }
929}
930
931impl From<AxWsErrorResponse> for AxWsError {
932    fn from(resp: AxWsErrorResponse) -> Self {
933        Self {
934            code: resp.code,
935            message: resp.message.unwrap_or_else(|| "Unknown error".to_string()),
936            request_id: resp.rid,
937        }
938    }
939}
940
941/// Metadata for pending order operations.
942///
943/// Used to correlate order responses with the original request.
944#[derive(Debug, Clone)]
945pub struct OrderMetadata {
946    /// Trader ID for event generation.
947    pub trader_id: TraderId,
948    /// Strategy ID for event generation.
949    pub strategy_id: StrategyId,
950    /// Instrument ID for event generation.
951    pub instrument_id: InstrumentId,
952    /// Client order ID for correlation.
953    pub client_order_id: ClientOrderId,
954    /// Venue order ID (populated after acknowledgment).
955    pub venue_order_id: Option<VenueOrderId>,
956    /// Original order timestamp.
957    pub ts_init: UnixNanos,
958    /// Instrument size precision for quantity conversion.
959    pub size_precision: u8,
960    /// Instrument price precision for price conversion.
961    pub price_precision: u8,
962    /// Quote currency for the instrument.
963    pub quote_currency: Currency,
964}
965
966#[cfg(test)]
967mod tests {
968    use rstest::rstest;
969    use rust_decimal_macros::dec;
970
971    use super::{
972        super::parse::{parse_md_message, parse_order_message},
973        *,
974    };
975
976    #[rstest]
977    fn test_md_subscribe_serialization() {
978        let msg = AxMdSubscribe {
979            rid: 2,
980            msg_type: AxMdRequestType::Subscribe,
981            symbol: Ustr::from("BTCUSD-PERP"),
982            level: AxMarketDataLevel::Level2,
983        };
984        let json = serde_json::to_string(&msg).unwrap();
985        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
986
987        assert_eq!(parsed["rid"], 2);
988        assert_eq!(parsed["type"], "subscribe");
989        assert_eq!(parsed["symbol"], "BTCUSD-PERP");
990        assert_eq!(parsed["level"], "LEVEL_2");
991    }
992
993    #[rstest]
994    fn test_md_unsubscribe_serialization() {
995        let msg = AxMdUnsubscribe {
996            rid: 3,
997            msg_type: AxMdRequestType::Unsubscribe,
998            symbol: Ustr::from("BTCUSD-PERP"),
999        };
1000        let json = serde_json::to_string(&msg).unwrap();
1001        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1002
1003        assert_eq!(parsed["rid"], 3);
1004        assert_eq!(parsed["type"], "unsubscribe");
1005        assert_eq!(parsed["symbol"], "BTCUSD-PERP");
1006    }
1007
1008    #[rstest]
1009    fn test_md_subscribe_candles_serialization() {
1010        let msg = AxMdSubscribeCandles {
1011            rid: 4,
1012            msg_type: AxMdRequestType::SubscribeCandles,
1013            symbol: Ustr::from("BTCUSD-PERP"),
1014            width: AxCandleWidth::Minutes1,
1015        };
1016        let json = serde_json::to_string(&msg).unwrap();
1017        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1018
1019        assert_eq!(parsed["rid"], 4);
1020        assert_eq!(parsed["type"], "subscribe_candles");
1021        assert_eq!(parsed["symbol"], "BTCUSD-PERP");
1022        assert_eq!(parsed["width"], "1m");
1023    }
1024
1025    #[rstest]
1026    fn test_md_unsubscribe_candles_serialization() {
1027        let msg = AxMdUnsubscribeCandles {
1028            rid: 5,
1029            msg_type: AxMdRequestType::UnsubscribeCandles,
1030            symbol: Ustr::from("BTCUSD-PERP"),
1031            width: AxCandleWidth::Minutes1,
1032        };
1033        let json = serde_json::to_string(&msg).unwrap();
1034        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1035
1036        assert_eq!(parsed["rid"], 5);
1037        assert_eq!(parsed["type"], "unsubscribe_candles");
1038        assert_eq!(parsed["symbol"], "BTCUSD-PERP");
1039        assert_eq!(parsed["width"], "1m");
1040    }
1041
1042    #[rstest]
1043    fn test_ws_place_order_serialization() {
1044        let msg = AxWsPlaceOrder {
1045            rid: 1,
1046            t: AxOrderRequestType::PlaceOrder,
1047            s: Ustr::from("BTCUSD-PERP"),
1048            d: AxOrderSide::Buy,
1049            q: 100,
1050            p: dec!(50000.50),
1051            tif: AxTimeInForce::Gtc,
1052            po: false,
1053            tag: Some("Nautilus".to_string()),
1054            cid: Some(1234567890),
1055            order_type: None,
1056            trigger_price: None,
1057        };
1058
1059        let json = serde_json::to_string(&msg).unwrap();
1060        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1061
1062        assert_eq!(parsed["rid"], 1);
1063        assert_eq!(parsed["t"], "p");
1064        assert_eq!(parsed["s"], "BTCUSD-PERP");
1065        assert_eq!(parsed["d"], "B");
1066        assert_eq!(parsed["q"], 100);
1067        assert_eq!(parsed["p"], "50000.50");
1068        assert_eq!(parsed["tif"], "GTC");
1069        assert_eq!(parsed["po"], false);
1070        assert_eq!(parsed["tag"], "Nautilus");
1071        assert_eq!(parsed["cid"], 1234567890);
1072        assert!(parsed.get("order_type").is_none());
1073        assert!(parsed.get("trigger_price").is_none());
1074    }
1075
1076    #[rstest]
1077    fn test_ws_place_stop_loss_order_serialization() {
1078        let msg = AxWsPlaceOrder {
1079            rid: 2,
1080            t: AxOrderRequestType::PlaceOrder,
1081            s: Ustr::from("BTCUSD-PERP"),
1082            d: AxOrderSide::Sell,
1083            q: 50,
1084            p: dec!(48000.00),
1085            tif: AxTimeInForce::Gtc,
1086            po: false,
1087            tag: None,
1088            cid: None,
1089            order_type: Some(AxOrderType::StopLossLimit),
1090            trigger_price: Some(dec!(49000.00)),
1091        };
1092
1093        let json = serde_json::to_string(&msg).unwrap();
1094        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1095
1096        assert_eq!(parsed["rid"], 2);
1097        assert_eq!(parsed["order_type"], "STOP_LOSS_LIMIT");
1098        assert_eq!(parsed["trigger_price"], "49000.00");
1099    }
1100
1101    #[rstest]
1102    fn test_ws_cancel_order_serialization() {
1103        let msg = AxWsCancelOrder {
1104            rid: 2,
1105            t: AxOrderRequestType::CancelOrder,
1106            oid: "O-01ARZ3NDEKTSV4RRFFQ69G5FAV".to_string(),
1107        };
1108        let json = serde_json::to_string(&msg).unwrap();
1109        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1110
1111        assert_eq!(parsed["rid"], 2);
1112        assert_eq!(parsed["t"], "x");
1113        assert_eq!(parsed["oid"], "O-01ARZ3NDEKTSV4RRFFQ69G5FAV");
1114    }
1115
1116    #[rstest]
1117    fn test_ws_get_open_orders_serialization() {
1118        let msg = AxWsGetOpenOrders {
1119            rid: 3,
1120            t: AxOrderRequestType::GetOpenOrders,
1121        };
1122        let json = serde_json::to_string(&msg).unwrap();
1123        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1124
1125        assert_eq!(parsed["rid"], 3);
1126        assert_eq!(parsed["t"], "o");
1127    }
1128
1129    #[rstest]
1130    fn test_load_md_heartbeat_from_file() {
1131        let json = include_str!("../../test_data/ws_md_heartbeat.json");
1132        let msg = parse_md_message(json).unwrap();
1133        assert!(matches!(msg, AxMdMessage::Heartbeat(_)));
1134    }
1135
1136    #[rstest]
1137    fn test_load_md_ticker_from_file() {
1138        let json = include_str!("../../test_data/ws_md_ticker.json");
1139        let msg: AxMdTicker = serde_json::from_str(json).unwrap();
1140        assert_eq!(msg.s.as_str(), "BTCUSD-PERP");
1141    }
1142
1143    #[rstest]
1144    fn test_load_md_trade_from_file() {
1145        let json = include_str!("../../test_data/ws_md_trade.json");
1146        let msg: AxMdTrade = serde_json::from_str(json).unwrap();
1147        assert_eq!(msg.d, Some(AxOrderSide::Buy));
1148    }
1149
1150    #[rstest]
1151    fn test_load_md_candle_from_file() {
1152        let json = include_str!("../../test_data/ws_md_candle.json");
1153        let msg: AxMdCandle = serde_json::from_str(json).unwrap();
1154        assert_eq!(msg.width, AxCandleWidth::Minutes1);
1155    }
1156
1157    #[rstest]
1158    fn test_load_md_book_l1_from_file() {
1159        let json = include_str!("../../test_data/ws_md_book_l1.json");
1160        let msg: AxMdBookL1 = serde_json::from_str(json).unwrap();
1161        assert_eq!(msg.b.len(), 1);
1162        assert_eq!(msg.a.len(), 1);
1163    }
1164
1165    #[rstest]
1166    fn test_load_md_book_l2_from_file() {
1167        let json = include_str!("../../test_data/ws_md_book_l2.json");
1168        let msg: AxMdBookL2 = serde_json::from_str(json).unwrap();
1169        assert_eq!(msg.b.len(), 3);
1170        assert_eq!(msg.a.len(), 3);
1171    }
1172
1173    #[rstest]
1174    fn test_load_md_book_l3_from_file() {
1175        let json = include_str!("../../test_data/ws_md_book_l3.json");
1176        let msg: AxMdBookL3 = serde_json::from_str(json).unwrap();
1177        assert_eq!(msg.b.len(), 2);
1178        assert!(!msg.b[0].o.is_empty());
1179    }
1180
1181    #[rstest]
1182    fn test_load_order_place_response_from_file() {
1183        let json = include_str!("../../test_data/ws_order_place_response.json");
1184        let msg: AxWsPlaceOrderResponse = serde_json::from_str(json).unwrap();
1185        assert_eq!(msg.res.oid, "O-01ARZ3NDEKTSV4RRFFQ69G5FAV");
1186    }
1187
1188    #[rstest]
1189    fn test_load_order_cancel_response_from_file() {
1190        let json = include_str!("../../test_data/ws_order_cancel_response.json");
1191        let msg: AxWsCancelOrderResponse = serde_json::from_str(json).unwrap();
1192        assert!(msg.res.cxl_rx);
1193    }
1194
1195    #[rstest]
1196    fn test_load_order_open_orders_response_from_file() {
1197        let json = include_str!("../../test_data/ws_order_open_orders_response.json");
1198        let msg: AxWsOpenOrdersResponse = serde_json::from_str(json).unwrap();
1199        assert_eq!(msg.res.len(), 1);
1200    }
1201
1202    #[rstest]
1203    fn test_load_order_heartbeat_from_file() {
1204        let json = include_str!("../../test_data/ws_order_heartbeat.json");
1205        let msg: AxWsHeartbeat = serde_json::from_str(json).unwrap();
1206        assert_eq!(msg.ts, 1609459200);
1207    }
1208
1209    #[rstest]
1210    fn test_load_order_acknowledged_from_file() {
1211        let json = include_str!("../../test_data/ws_order_acknowledged.json");
1212        let msg: AxWsOrderAcknowledged = serde_json::from_str(json).unwrap();
1213        assert_eq!(msg.o.oid, "O-01ARZ3NDEKTSV4RRFFQ69G5FAV");
1214    }
1215
1216    #[rstest]
1217    fn test_load_order_filled_from_file() {
1218        let json = include_str!("../../test_data/ws_order_filled.json");
1219        let msg: AxWsOrderFilled = serde_json::from_str(json).unwrap();
1220        assert_eq!(msg.o.o, AxOrderStatus::Filled);
1221    }
1222
1223    #[rstest]
1224    fn test_load_order_partially_filled_from_file() {
1225        let json = include_str!("../../test_data/ws_order_partially_filled.json");
1226        let msg: AxWsOrderPartiallyFilled = serde_json::from_str(json).unwrap();
1227        assert_eq!(msg.xs.q, 50);
1228    }
1229
1230    #[rstest]
1231    fn test_load_order_canceled_from_file() {
1232        let json = include_str!("../../test_data/ws_order_canceled.json");
1233        let msg: AxWsOrderCanceled = serde_json::from_str(json).unwrap();
1234        assert_eq!(msg.xr, AxCancelReason::UserRequested);
1235    }
1236
1237    #[rstest]
1238    fn test_load_order_rejected_from_file() {
1239        let json = include_str!("../../test_data/ws_order_rejected.json");
1240        let msg: AxWsOrderRejected = serde_json::from_str(json).unwrap();
1241        assert_eq!(msg.r, Some("INSUFFICIENT_MARGIN".to_string()));
1242    }
1243
1244    #[rstest]
1245    fn test_load_order_expired_from_file() {
1246        let json = include_str!("../../test_data/ws_order_expired.json");
1247        let msg: AxWsOrderExpired = serde_json::from_str(json).unwrap();
1248        assert_eq!(msg.o.tif, AxTimeInForce::Ioc);
1249    }
1250
1251    #[rstest]
1252    fn test_load_order_replaced_from_file() {
1253        let json = include_str!("../../test_data/ws_order_replaced.json");
1254        let msg: AxWsOrderReplaced = serde_json::from_str(json).unwrap();
1255        assert_eq!(msg.o.p, dec!(50500.00));
1256    }
1257
1258    #[rstest]
1259    fn test_load_order_done_for_day_from_file() {
1260        let json = include_str!("../../test_data/ws_order_done_for_day.json");
1261        let msg: AxWsOrderDoneForDay = serde_json::from_str(json).unwrap();
1262        assert_eq!(msg.o.xq, 50);
1263    }
1264
1265    #[rstest]
1266    fn test_load_cancel_rejected_from_file() {
1267        let json = include_str!("../../test_data/ws_cancel_rejected.json");
1268        let msg: AxWsCancelRejected = serde_json::from_str(json).unwrap();
1269        assert_eq!(msg.r, AxCancelRejectionReason::OrderNotFound);
1270    }
1271
1272    #[rstest]
1273    fn test_load_order_error_response_from_file() {
1274        let json = include_str!("../../test_data/ws_order_error_response.json");
1275        let msg: AxWsOrderErrorResponse = serde_json::from_str(json).unwrap();
1276        assert_eq!(msg.rid, 1);
1277        assert_eq!(msg.err.code, 400);
1278        assert!(msg.err.msg.contains("initial margin"));
1279    }
1280
1281    #[rstest]
1282    fn test_load_order_list_response_from_file() {
1283        let json = include_str!("../../test_data/ws_order_list_response.json");
1284        let msg: AxWsListResponse = serde_json::from_str(json).unwrap();
1285        assert_eq!(msg.rid, 0);
1286        assert_eq!(msg.res.li, "01KCQM-4WP1-0000");
1287        assert!(msg.res.o.is_none());
1288    }
1289
1290    #[rstest]
1291    fn test_load_order_list_response_with_orders_from_file() {
1292        let json = include_str!("../../test_data/ws_order_list_response_with_orders.json");
1293        let msg: AxWsListResponse = serde_json::from_str(json).unwrap();
1294        assert_eq!(msg.rid, 0);
1295        assert_eq!(msg.res.li, "01KCQM-4WP1-0000");
1296        let orders = msg.res.o.unwrap();
1297        assert_eq!(orders.len(), 2);
1298        assert_eq!(orders[0].oid, "O-01KF4QM3VVJEDH98ZVNS1PCSBB");
1299        assert_eq!(orders[1].oid, "O-01KF4QM3K9FJZWYA02JF9Y1FJA");
1300    }
1301
1302    #[rstest]
1303    fn test_raw_message_error_variant() {
1304        let json = include_str!("../../test_data/ws_order_error_response.json");
1305        let msg = parse_order_message(json).unwrap();
1306        assert!(matches!(msg, AxWsRawMessage::Error(_)));
1307    }
1308
1309    #[rstest]
1310    fn test_raw_message_list_response_variant() {
1311        let json = include_str!("../../test_data/ws_order_list_response.json");
1312        let msg = parse_order_message(json).unwrap();
1313        assert!(matches!(
1314            msg,
1315            AxWsRawMessage::Response(AxWsOrderResponse::List(_))
1316        ));
1317    }
1318
1319    #[rstest]
1320    fn test_raw_message_event_variant() {
1321        let json = include_str!("../../test_data/ws_order_acknowledged.json");
1322        let msg = parse_order_message(json).unwrap();
1323        assert!(matches!(
1324            msg,
1325            AxWsRawMessage::Event(ref e) if matches!(**e, AxWsOrderEvent::Acknowledged(_))
1326        ));
1327    }
1328
1329    #[rstest]
1330    fn test_raw_message_place_response_variant() {
1331        let json = include_str!("../../test_data/ws_order_place_response.json");
1332        let msg = parse_order_message(json).unwrap();
1333        assert!(matches!(
1334            msg,
1335            AxWsRawMessage::Response(AxWsOrderResponse::PlaceOrder(_))
1336        ));
1337    }
1338
1339    #[rstest]
1340    fn test_raw_message_cancel_response_variant() {
1341        let json = include_str!("../../test_data/ws_order_cancel_response.json");
1342        let msg = parse_order_message(json).unwrap();
1343        assert!(matches!(
1344            msg,
1345            AxWsRawMessage::Response(AxWsOrderResponse::CancelOrder(_))
1346        ));
1347    }
1348
1349    #[rstest]
1350    fn test_raw_message_open_orders_response_variant() {
1351        let json = include_str!("../../test_data/ws_order_open_orders_response.json");
1352        let msg = parse_order_message(json).unwrap();
1353        assert!(matches!(
1354            msg,
1355            AxWsRawMessage::Response(AxWsOrderResponse::OpenOrders(_))
1356        ));
1357    }
1358}