Skip to main content

nautilus_architect_ax/http/
models.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//! Data transfer objects for deserializing Ax HTTP API payloads.
17
18use ahash::AHashMap;
19use chrono::{DateTime, Utc};
20use rust_decimal::Decimal;
21use serde::{Deserialize, Serialize};
22use ustr::Ustr;
23
24use crate::common::{
25    enums::{
26        AxCandleWidth, AxInstrumentState, AxOrderSide, AxOrderStatus, AxOrderType, AxTimeInForce,
27    },
28    parse::{
29        deserialize_decimal_or_zero, deserialize_optional_decimal_from_str,
30        serialize_decimal_as_str, serialize_optional_decimal_as_str,
31    },
32};
33
34/// Default instrument state when not provided by API.
35fn default_instrument_state() -> AxInstrumentState {
36    AxInstrumentState::Open
37}
38
39/// Response payload returned by `GET /whoami`.
40///
41/// # References
42/// - <https://docs.architect.exchange/api-reference/user-management/whoami>
43#[derive(Clone, Debug, Serialize, Deserialize)]
44#[serde(rename_all = "snake_case")]
45pub struct AxWhoAmI {
46    /// User account UUID.
47    pub id: String,
48    /// Username for the account.
49    pub username: String,
50    /// Account creation timestamp.
51    pub created_at: DateTime<Utc>,
52    /// Whether two-factor authentication is enabled.
53    pub enabled_2fa: bool,
54    /// Whether the user has completed onboarding.
55    pub is_onboarded: bool,
56    /// Whether the account is frozen.
57    pub is_frozen: bool,
58    /// Whether the user has admin privileges.
59    pub is_admin: bool,
60    /// Whether the account is in close-only mode.
61    pub is_close_only: bool,
62    /// Maker fee rate.
63    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
64    pub maker_fee: Decimal,
65    /// Taker fee rate.
66    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
67    pub taker_fee: Decimal,
68}
69
70/// Individual instrument definition.
71///
72/// # References
73/// - <https://docs.architect.exchange/api-reference/symbols-instruments/get-instruments>
74#[derive(Clone, Debug, Serialize, Deserialize)]
75#[serde(rename_all = "snake_case")]
76pub struct AxInstrument {
77    /// Trading symbol for the instrument.
78    pub symbol: Ustr,
79    /// Current trading state of the instrument (defaults to Open if not provided).
80    #[serde(default = "default_instrument_state")]
81    pub state: AxInstrumentState,
82    /// Contract multiplier.
83    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
84    pub multiplier: Decimal,
85    /// Minimum order size.
86    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
87    pub minimum_order_size: Decimal,
88    /// Price tick size.
89    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
90    pub tick_size: Decimal,
91    /// Quote currency symbol.
92    pub quote_currency: Ustr,
93    /// Funding settlement currency.
94    pub funding_settlement_currency: Ustr,
95    /// Maintenance margin percentage.
96    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
97    pub maintenance_margin_pct: Decimal,
98    /// Initial margin percentage.
99    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
100    pub initial_margin_pct: Decimal,
101    /// Contract mark price description (optional).
102    #[serde(default)]
103    pub contract_mark_price: Option<String>,
104    /// Contract size description (optional).
105    #[serde(default)]
106    pub contract_size: Option<String>,
107    /// Instrument description (optional).
108    #[serde(default)]
109    pub description: Option<String>,
110    /// Funding calendar schedule (optional).
111    #[serde(default)]
112    pub funding_calendar_schedule: Option<String>,
113    /// Funding frequency (optional).
114    #[serde(default)]
115    pub funding_frequency: Option<String>,
116    /// Lower cap for funding rate percentage (optional).
117    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
118    pub funding_rate_cap_lower_pct: Option<Decimal>,
119    /// Upper cap for funding rate percentage (optional).
120    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
121    pub funding_rate_cap_upper_pct: Option<Decimal>,
122    /// Lower deviation percentage for price bands (optional).
123    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
124    pub price_band_lower_deviation_pct: Option<Decimal>,
125    /// Upper deviation percentage for price bands (optional).
126    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
127    pub price_band_upper_deviation_pct: Option<Decimal>,
128    /// Price bands configuration (optional).
129    #[serde(default)]
130    pub price_bands: Option<String>,
131    /// Price quotation format (optional).
132    #[serde(default)]
133    pub price_quotation: Option<String>,
134    /// Underlying benchmark price description (optional).
135    #[serde(default)]
136    pub underlying_benchmark_price: Option<String>,
137}
138
139/// Response payload returned by `GET /instruments`.
140///
141/// # References
142/// - <https://docs.architect.exchange/api-reference/symbols-instruments/get-instruments>
143#[derive(Clone, Debug, Serialize, Deserialize)]
144#[serde(rename_all = "snake_case")]
145pub struct AxInstrumentsResponse {
146    /// List of instruments.
147    pub instruments: Vec<AxInstrument>,
148}
149
150/// Individual balance entry.
151///
152/// # References
153/// - <https://docs.architect.exchange/api-reference/portfolio-management/get-balances>
154#[derive(Clone, Debug, Serialize, Deserialize)]
155#[serde(rename_all = "snake_case")]
156pub struct AxBalance {
157    /// Asset symbol.
158    pub symbol: Ustr,
159    /// Available balance amount.
160    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
161    pub amount: Decimal,
162}
163
164/// Response payload returned by `GET /balances`.
165///
166/// # References
167/// - <https://docs.architect.exchange/api-reference/portfolio-management/get-balances>
168#[derive(Clone, Debug, Serialize, Deserialize)]
169#[serde(rename_all = "snake_case")]
170pub struct AxBalancesResponse {
171    /// List of balances.
172    pub balances: Vec<AxBalance>,
173}
174
175/// Individual position entry.
176///
177/// # References
178/// - <https://docs.architect.exchange/api-reference/portfolio-management/get-positions>
179#[derive(Clone, Debug, Serialize, Deserialize)]
180#[serde(rename_all = "snake_case")]
181pub struct AxPosition {
182    /// User account UUID.
183    pub user_id: String,
184    /// Instrument symbol.
185    pub symbol: Ustr,
186    /// Signed quantity (positive for long, negative for short).
187    pub signed_quantity: i64,
188    /// Signed notional value.
189    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
190    pub signed_notional: Decimal,
191    /// Position timestamp.
192    pub timestamp: DateTime<Utc>,
193    /// Realized profit and loss.
194    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
195    pub realized_pnl: Decimal,
196}
197
198/// Response payload returned by `GET /positions`.
199///
200/// # References
201/// - <https://docs.architect.exchange/api-reference/portfolio-management/get-positions>
202#[derive(Clone, Debug, Serialize, Deserialize)]
203#[serde(rename_all = "snake_case")]
204pub struct AxPositionsResponse {
205    /// List of positions.
206    pub positions: Vec<AxPosition>,
207}
208
209/// Individual ticker entry.
210///
211/// # References
212/// - <https://docs.architect.exchange/api-reference/marketdata/get-ticker>
213#[derive(Clone, Debug, Serialize, Deserialize)]
214#[serde(rename_all = "snake_case")]
215pub struct AxTicker {
216    /// Instrument symbol.
217    pub symbol: Ustr,
218    /// Best bid price.
219    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
220    pub bid: Option<Decimal>,
221    /// Best ask price.
222    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
223    pub ask: Option<Decimal>,
224    /// Last trade price.
225    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
226    pub last: Option<Decimal>,
227    /// Mark price.
228    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
229    pub mark: Option<Decimal>,
230    /// Index price.
231    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
232    pub index: Option<Decimal>,
233    /// 24-hour volume.
234    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
235    pub volume_24h: Option<Decimal>,
236    /// 24-hour high price.
237    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
238    pub high_24h: Option<Decimal>,
239    /// 24-hour low price.
240    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
241    pub low_24h: Option<Decimal>,
242    /// Ticker timestamp.
243    #[serde(default)]
244    pub timestamp: Option<DateTime<Utc>>,
245}
246
247/// Response payload returned by `GET /tickers`.
248///
249/// # References
250/// - <https://docs.architect.exchange/api-reference/marketdata/get-tickers>
251#[derive(Clone, Debug, Serialize, Deserialize)]
252#[serde(rename_all = "snake_case")]
253pub struct AxTickersResponse {
254    /// List of tickers.
255    pub tickers: Vec<AxTicker>,
256}
257
258/// Response payload returned by `POST /authenticate`.
259///
260/// # References
261/// - <https://docs.architect.exchange/api-reference/user-management/get-user-token>
262#[derive(Clone, Debug, Serialize, Deserialize)]
263#[serde(rename_all = "snake_case")]
264pub struct AxAuthenticateResponse {
265    /// Session token for authenticated requests.
266    pub token: String,
267}
268
269/// Response payload returned by `POST /place_order`.
270///
271/// # References
272/// - <https://docs.architect.exchange/api-reference/order-management/place-order>
273#[derive(Clone, Debug, Serialize, Deserialize)]
274pub struct AxPlaceOrderResponse {
275    /// Order ID of the placed order.
276    pub oid: String,
277}
278
279/// Response payload returned by `POST /cancel_order`.
280///
281/// # References
282/// - <https://docs.architect.exchange/api-reference/order-management/cancel-order>
283#[derive(Clone, Debug, Serialize, Deserialize)]
284pub struct AxCancelOrderResponse {
285    /// Whether the cancel request has been accepted.
286    pub cxl_rx: bool,
287}
288
289/// Individual trade entry from the REST API.
290///
291/// # References
292/// - <https://docs.architect.exchange/api-reference/market-data/get-trades>
293#[derive(Clone, Debug, Serialize, Deserialize)]
294pub struct AxRestTrade {
295    /// Timestamp (Unix epoch seconds).
296    pub ts: i64,
297    /// Nanosecond component of the timestamp.
298    pub tn: i64,
299    /// Trade price (decimal string).
300    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
301    pub p: Decimal,
302    /// Trade quantity.
303    pub q: i64,
304    /// Symbol.
305    pub s: Ustr,
306    /// Trade direction (aggressor side).
307    pub d: AxOrderSide,
308}
309
310/// Response payload returned by `GET /trades`.
311///
312/// # References
313/// - <https://docs.architect.exchange/api-reference/market-data/get-trades>
314#[derive(Clone, Debug, Serialize, Deserialize)]
315pub struct AxTradesResponse {
316    /// List of trades.
317    pub trades: Vec<AxRestTrade>,
318}
319
320/// Individual price level in the order book.
321///
322/// # References
323/// - <https://docs.architect.exchange/api-reference/market-data/get-book>
324#[derive(Clone, Debug, Serialize, Deserialize)]
325pub struct AxBookLevel {
326    /// Price (decimal string).
327    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
328    pub p: Decimal,
329    /// Quantity at this price level.
330    pub q: i64,
331    /// Individual order IDs (Level 3 only).
332    #[serde(default)]
333    pub o: Option<Vec<i64>>,
334}
335
336/// Order book snapshot.
337///
338/// # References
339/// - <https://docs.architect.exchange/api-reference/market-data/get-book>
340#[derive(Clone, Debug, Serialize, Deserialize)]
341pub struct AxBook {
342    /// Timestamp (Unix epoch seconds).
343    pub ts: i64,
344    /// Nanosecond component of the timestamp.
345    pub tn: i64,
346    /// Symbol.
347    pub s: String,
348    /// Bid levels (best to worst).
349    pub b: Vec<AxBookLevel>,
350    /// Ask levels (best to worst).
351    pub a: Vec<AxBookLevel>,
352}
353
354/// Response payload returned by `GET /book`.
355///
356/// # References
357/// - <https://docs.architect.exchange/api-reference/market-data/get-book>
358#[derive(Clone, Debug, Serialize, Deserialize)]
359pub struct AxBookResponse {
360    /// The order book snapshot.
361    pub book: AxBook,
362}
363
364/// Detailed order status from single-order lookup.
365///
366/// # References
367/// - <https://docs.architect.exchange/api-reference/order-management/get-order-status>
368#[derive(Clone, Debug, Serialize, Deserialize)]
369pub struct AxOrderStatusDetail {
370    /// Trading symbol.
371    pub symbol: Ustr,
372    /// Order ID.
373    pub order_id: String,
374    /// Current order state.
375    pub state: AxOrderStatus,
376    /// Client order ID.
377    #[serde(default)]
378    pub clord_id: Option<u64>,
379    /// Filled quantity.
380    #[serde(default)]
381    pub filled_quantity: Option<i64>,
382    /// Remaining quantity.
383    #[serde(default)]
384    pub remaining_quantity: Option<i64>,
385}
386
387/// Response payload returned by `GET /order-status`.
388///
389/// # References
390/// - <https://docs.architect.exchange/api-reference/order-management/get-order-status>
391#[derive(Clone, Debug, Serialize, Deserialize)]
392pub struct AxOrderStatusQueryResponse {
393    /// The order status detail.
394    pub status: AxOrderStatusDetail,
395}
396
397/// Reason for order rejection from the exchange.
398///
399/// # References
400/// - <https://docs.architect.exchange/api-reference/order-management/get-orders>
401#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
402#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
403pub enum AxOrderRejectReason {
404    CloseOnly,
405    InsufficientMargin,
406    MaxOpenOrdersExceeded,
407    UnknownSymbol,
408    ExchangeClosed,
409    IncorrectQuantity,
410    InvalidPriceIncrement,
411    IncorrectOrderType,
412    PriceOutOfBounds,
413    NoLiquidity,
414    InsufficientCreditLimit,
415    #[serde(other)]
416    Unknown,
417}
418
419/// Detailed order entry from historical orders query.
420///
421/// # References
422/// - <https://docs.architect.exchange/api-reference/order-management/get-orders>
423#[derive(Clone, Debug, Serialize, Deserialize)]
424pub struct AxOrderDetail {
425    /// Timestamp (Unix epoch seconds).
426    pub ts: i64,
427    /// Nanosecond component.
428    #[serde(default)]
429    pub tn: i64,
430    /// Order ID.
431    pub oid: String,
432    /// User ID.
433    pub u: String,
434    /// Symbol.
435    pub s: Ustr,
436    /// Price.
437    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
438    pub p: Decimal,
439    /// Order quantity.
440    pub q: u64,
441    /// Executed quantity.
442    pub xq: u64,
443    /// Remaining quantity.
444    pub rq: u64,
445    /// Order state.
446    pub o: AxOrderStatus,
447    /// Order side.
448    pub d: AxOrderSide,
449    /// Time in force.
450    pub tif: AxTimeInForce,
451    /// Client order ID.
452    #[serde(default)]
453    pub cid: Option<u64>,
454    /// Reject reason.
455    #[serde(default)]
456    pub r: Option<AxOrderRejectReason>,
457    /// Order tag.
458    #[serde(default)]
459    pub tag: Option<String>,
460    /// Text note.
461    #[serde(default)]
462    pub txt: Option<String>,
463}
464
465/// Response payload returned by `GET /orders`.
466///
467/// # References
468/// - <https://docs.architect.exchange/api-reference/order-management/get-orders>
469#[derive(Clone, Debug, Serialize, Deserialize)]
470pub struct AxOrdersResponse {
471    /// List of order details.
472    pub orders: Vec<AxOrderDetail>,
473    /// Total matching records (for pagination).
474    pub total_count: i64,
475    /// Applied limit.
476    pub limit: i32,
477    /// Applied offset.
478    pub offset: i32,
479}
480
481/// Response payload returned by `POST /initial-margin-requirement`.
482///
483/// # References
484/// - <https://docs.architect.exchange/api-reference/portfolio-management/post-initial-margin-requirement>
485#[derive(Clone, Debug, Serialize, Deserialize)]
486pub struct AxInitialMarginRequirementResponse {
487    /// Initial margin requirement.
488    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
489    pub im: Decimal,
490}
491
492/// Individual open order entry.
493///
494/// # References
495/// - <https://docs.architect.exchange/api-reference/order-management/get-open-orders>
496#[derive(Clone, Debug, Serialize, Deserialize)]
497pub struct AxOpenOrder {
498    /// Trade number.
499    pub tn: i64,
500    /// Timestamp (Unix epoch).
501    pub ts: i64,
502    /// Order side: "B" (buy) or "S" (sell).
503    pub d: AxOrderSide,
504    /// Order status.
505    pub o: AxOrderStatus,
506    /// Order ID.
507    pub oid: String,
508    /// Price.
509    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
510    pub p: Decimal,
511    /// Quantity.
512    pub q: u64,
513    /// Remaining quantity.
514    pub rq: u64,
515    /// Symbol.
516    pub s: Ustr,
517    /// Time in force.
518    pub tif: AxTimeInForce,
519    /// User ID.
520    pub u: String,
521    /// Executed quantity.
522    pub xq: u64,
523    /// Optional client ID for order correlation.
524    #[serde(default)]
525    pub cid: Option<u64>,
526    /// Optional order tag.
527    #[serde(default)]
528    pub tag: Option<String>,
529}
530
531/// Response payload returned by `GET /open_orders`.
532///
533/// # References
534/// - <https://docs.architect.exchange/api-reference/order-management/get-open-orders>
535#[derive(Clone, Debug, Serialize, Deserialize)]
536pub struct AxOpenOrdersResponse {
537    /// List of open orders.
538    pub orders: Vec<AxOpenOrder>,
539}
540
541/// Individual fill/trade entry.
542///
543/// # References
544/// - <https://docs.architect.exchange/api-reference/portfolio-management/get-fills>
545#[derive(Clone, Debug, Serialize, Deserialize)]
546#[serde(rename_all = "snake_case")]
547pub struct AxFill {
548    /// Trade ID (execution identifier).
549    pub trade_id: String,
550    /// Order ID.
551    pub order_id: String,
552    /// Fee amount.
553    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
554    pub fee: Decimal,
555    /// Whether this was a taker order.
556    pub is_taker: bool,
557    /// Execution price.
558    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
559    pub price: Decimal,
560    /// Executed quantity (always non-negative).
561    pub quantity: u64,
562    /// Order side.
563    pub side: AxOrderSide,
564    /// Instrument symbol.
565    pub symbol: Ustr,
566    /// Execution timestamp.
567    pub timestamp: DateTime<Utc>,
568    /// User ID.
569    pub user_id: String,
570}
571
572/// Response payload returned by `GET /fills`.
573///
574/// # References
575/// - <https://docs.architect.exchange/api-reference/portfolio-management/get-fills>
576#[derive(Clone, Debug, Serialize, Deserialize)]
577#[serde(rename_all = "snake_case")]
578pub struct AxFillsResponse {
579    /// List of fills.
580    pub fills: Vec<AxFill>,
581}
582
583/// Individual candle/OHLCV entry.
584///
585/// # References
586/// - <https://docs.architect.exchange/api-reference/marketdata/get-candles>
587#[derive(Clone, Debug, Serialize, Deserialize)]
588#[serde(rename_all = "snake_case")]
589pub struct AxCandle {
590    /// Instrument symbol.
591    pub symbol: Ustr,
592    /// Candle timestamp (Unix epoch seconds).
593    pub ts: i64,
594    /// Open price.
595    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
596    pub open: Decimal,
597    /// High price.
598    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
599    pub high: Decimal,
600    /// Low price.
601    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
602    pub low: Decimal,
603    /// Close price.
604    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
605    pub close: Decimal,
606    /// Buy volume.
607    pub buy_volume: u64,
608    /// Sell volume.
609    pub sell_volume: u64,
610    /// Total volume.
611    pub volume: u64,
612    /// Candle width/interval.
613    pub width: AxCandleWidth,
614}
615
616/// Response payload returned by `GET /candles`.
617///
618/// # References
619/// - <https://docs.architect.exchange/api-reference/marketdata/get-candles>
620#[derive(Clone, Debug, Serialize, Deserialize)]
621#[serde(rename_all = "snake_case")]
622pub struct AxCandlesResponse {
623    /// List of candles.
624    pub candles: Vec<AxCandle>,
625}
626
627/// Response payload returned by `GET /candles/current` and `GET /candles/last`.
628///
629/// # References
630/// - <https://docs.architect.exchange/api-reference/marketdata/get-current-candle>
631/// - <https://docs.architect.exchange/api-reference/marketdata/get-last-candle>
632#[derive(Clone, Debug, Serialize, Deserialize)]
633#[serde(rename_all = "snake_case")]
634pub struct AxCandleResponse {
635    /// The candle data.
636    pub candle: AxCandle,
637}
638
639/// Individual funding rate entry.
640///
641/// # References
642/// - <https://docs.architect.exchange/api-reference/marketdata/get-funding-rates>
643#[derive(Clone, Debug, Serialize, Deserialize)]
644#[serde(rename_all = "snake_case")]
645pub struct AxFundingRate {
646    /// Instrument symbol.
647    pub symbol: Ustr,
648    /// Timestamp in nanoseconds.
649    pub timestamp_ns: i64,
650    /// Funding rate.
651    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
652    pub funding_rate: Decimal,
653    /// Funding amount.
654    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
655    pub funding_amount: Decimal,
656    /// Benchmark price.
657    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
658    pub benchmark_price: Decimal,
659    /// Settlement price.
660    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
661    pub settlement_price: Decimal,
662}
663
664/// Response payload returned by `GET /funding-rates`.
665///
666/// # References
667/// - <https://docs.architect.exchange/api-reference/marketdata/get-funding-rates>
668#[derive(Clone, Debug, Serialize, Deserialize)]
669#[serde(rename_all = "snake_case")]
670pub struct AxFundingRatesResponse {
671    /// List of funding rates.
672    pub funding_rates: Vec<AxFundingRate>,
673}
674
675/// Per-symbol risk metrics.
676///
677/// # References
678/// - <https://docs.architect.exchange/api-reference/portfolio-management/get-risk-snapshot>
679#[derive(Clone, Debug, Serialize, Deserialize)]
680#[serde(rename_all = "snake_case")]
681pub struct AxPerSymbolRisk {
682    /// Signed quantity (positive for long, negative for short).
683    pub signed_quantity: i64,
684    /// Signed notional value.
685    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
686    pub signed_notional: Decimal,
687    /// Average entry price.
688    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
689    pub average_price: Decimal,
690    /// Liquidation price.
691    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
692    pub liquidation_price: Option<Decimal>,
693    /// Initial margin required.
694    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
695    pub initial_margin_required: Option<Decimal>,
696    /// Maintenance margin required.
697    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
698    pub maintenance_margin_required: Option<Decimal>,
699    /// Unrealized P&L.
700    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
701    pub unrealized_pnl: Option<Decimal>,
702}
703
704/// Risk snapshot data.
705///
706/// # References
707/// - <https://docs.architect.exchange/api-reference/portfolio-management/get-risk-snapshot>
708#[derive(Clone, Debug, Serialize, Deserialize)]
709#[serde(rename_all = "snake_case")]
710pub struct AxRiskSnapshot {
711    /// USD account balance.
712    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
713    pub balance_usd: Decimal,
714    /// Total equity value.
715    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
716    pub equity: Decimal,
717    /// Available initial margin.
718    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
719    pub initial_margin_available: Decimal,
720    /// Margin required for open orders.
721    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
722    pub initial_margin_required_for_open_orders: Decimal,
723    /// Margin required for positions.
724    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
725    pub initial_margin_required_for_positions: Decimal,
726    /// Total initial margin requirement.
727    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
728    pub initial_margin_required_total: Decimal,
729    /// Available maintenance margin.
730    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
731    pub maintenance_margin_available: Decimal,
732    /// Required maintenance margin.
733    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
734    pub maintenance_margin_required: Decimal,
735    /// Unrealized profit/loss.
736    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
737    pub unrealized_pnl: Decimal,
738    /// Snapshot timestamp.
739    pub timestamp_ns: DateTime<Utc>,
740    /// User identifier.
741    pub user_id: String,
742    /// Per-symbol risk data.
743    #[serde(default)]
744    pub per_symbol: AHashMap<String, AxPerSymbolRisk>,
745}
746
747/// Response payload returned by `GET /risk-snapshot`.
748///
749/// # References
750/// - <https://docs.architect.exchange/api-reference/portfolio-management/get-risk-snapshot>
751#[derive(Clone, Debug, Serialize, Deserialize)]
752#[serde(rename_all = "snake_case")]
753pub struct AxRiskSnapshotResponse {
754    /// The risk snapshot data.
755    pub risk_snapshot: AxRiskSnapshot,
756}
757
758/// Individual transaction entry.
759///
760/// # References
761/// - <https://docs.architect.exchange/api-reference/portfolio-management/get-transactions>
762#[derive(Clone, Debug, Serialize, Deserialize)]
763#[serde(rename_all = "snake_case")]
764pub struct AxTransaction {
765    /// Transaction amount.
766    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
767    pub amount: Decimal,
768    /// Unique event identifier.
769    pub event_id: String,
770    /// Asset symbol.
771    pub symbol: Ustr,
772    /// Transaction timestamp.
773    pub timestamp: DateTime<Utc>,
774    /// Type of transaction.
775    pub transaction_type: Ustr,
776    /// User identifier.
777    pub user_id: String,
778    /// Optional reference identifier.
779    #[serde(default)]
780    pub reference_id: Option<String>,
781}
782
783/// Response payload returned by `GET /transactions`.
784///
785/// # References
786/// - <https://docs.architect.exchange/api-reference/portfolio-management/get-transactions>
787#[derive(Clone, Debug, Serialize, Deserialize)]
788#[serde(rename_all = "snake_case")]
789pub struct AxTransactionsResponse {
790    /// List of transactions.
791    pub transactions: Vec<AxTransaction>,
792}
793
794/// Request body for `POST /authenticate` using API key and secret.
795///
796/// # References
797/// - <https://docs.architect.exchange/api-reference/user-management/get-user-token>
798#[derive(Clone, Debug, Serialize, Deserialize)]
799#[serde(rename_all = "snake_case")]
800pub struct AuthenticateApiKeyRequest {
801    /// API key.
802    pub api_key: String,
803    /// API secret.
804    pub api_secret: String,
805    /// Token expiration in seconds.
806    pub expiration_seconds: i32,
807}
808
809impl AuthenticateApiKeyRequest {
810    /// Creates a new [`AuthenticateApiKeyRequest`].
811    #[must_use]
812    pub fn new(
813        api_key: impl Into<String>,
814        api_secret: impl Into<String>,
815        expiration_seconds: i32,
816    ) -> Self {
817        Self {
818            api_key: api_key.into(),
819            api_secret: api_secret.into(),
820            expiration_seconds,
821        }
822    }
823}
824
825/// Request body for `POST /authenticate` using username and password.
826///
827/// # References
828/// - <https://docs.architect.exchange/api-reference/user-management/get-user-token>
829#[derive(Clone, Debug, Serialize, Deserialize)]
830#[serde(rename_all = "snake_case")]
831pub struct AuthenticateUserRequest {
832    /// Username.
833    pub username: String,
834    /// Password.
835    pub password: String,
836    /// Token expiration in seconds.
837    pub expiration_seconds: i32,
838}
839
840impl AuthenticateUserRequest {
841    /// Creates a new [`AuthenticateUserRequest`].
842    #[must_use]
843    pub fn new(
844        username: impl Into<String>,
845        password: impl Into<String>,
846        expiration_seconds: i32,
847    ) -> Self {
848        Self {
849            username: username.into(),
850            password: password.into(),
851            expiration_seconds,
852        }
853    }
854}
855
856/// Request body for `POST /place_order`.
857///
858/// # References
859/// - <https://docs.architect.co/sdk-reference/order-entry>
860#[derive(Clone, Debug, Serialize, Deserialize)]
861pub struct PlaceOrderRequest {
862    /// Order side: "B" (buy) or "S" (sell).
863    pub d: AxOrderSide,
864    /// Order price (limit price).
865    #[serde(serialize_with = "serialize_decimal_as_str")]
866    pub p: Decimal,
867    /// Post-only flag (maker-or-cancel).
868    pub po: bool,
869    /// Order quantity in contracts.
870    pub q: u64,
871    /// Order symbol.
872    pub s: Ustr,
873    /// Time in force.
874    pub tif: AxTimeInForce,
875    /// Optional order tag (max 10 alphanumeric characters).
876    #[serde(skip_serializing_if = "Option::is_none")]
877    pub tag: Option<String>,
878    /// Order type (defaults to LIMIT if not specified).
879    #[serde(skip_serializing_if = "Option::is_none")]
880    pub order_type: Option<AxOrderType>,
881    /// Trigger price for stop-loss orders (required for STOP_LOSS_LIMIT).
882    #[serde(
883        skip_serializing_if = "Option::is_none",
884        serialize_with = "serialize_optional_decimal_as_str"
885    )]
886    pub trigger_price: Option<Decimal>,
887}
888
889impl PlaceOrderRequest {
890    /// Creates a new [`PlaceOrderRequest`] for a limit order.
891    #[must_use]
892    pub fn new(
893        side: AxOrderSide,
894        price: Decimal,
895        quantity: u64,
896        symbol: Ustr,
897        time_in_force: AxTimeInForce,
898        post_only: bool,
899    ) -> Self {
900        Self {
901            d: side,
902            p: price,
903            po: post_only,
904            q: quantity,
905            s: symbol,
906            tif: time_in_force,
907            tag: None,
908            order_type: None,
909            trigger_price: None,
910        }
911    }
912
913    /// Creates a new [`PlaceOrderRequest`] for a stop-loss limit order.
914    #[must_use]
915    pub fn new_stop_loss(
916        side: AxOrderSide,
917        limit_price: Decimal,
918        trigger_price: Decimal,
919        quantity: u64,
920        symbol: Ustr,
921        time_in_force: AxTimeInForce,
922    ) -> Self {
923        Self {
924            d: side,
925            p: limit_price,
926            po: false,
927            q: quantity,
928            s: symbol,
929            tif: time_in_force,
930            tag: None,
931            order_type: Some(AxOrderType::StopLossLimit),
932            trigger_price: Some(trigger_price),
933        }
934    }
935
936    /// Sets the optional order tag.
937    #[must_use]
938    pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
939        self.tag = Some(tag.into());
940        self
941    }
942
943    /// Sets the order type.
944    #[must_use]
945    pub fn with_order_type(mut self, order_type: AxOrderType) -> Self {
946        self.order_type = Some(order_type);
947        self
948    }
949
950    /// Sets the trigger price for stop orders.
951    #[must_use]
952    pub fn with_trigger_price(mut self, trigger_price: Decimal) -> Self {
953        self.trigger_price = Some(trigger_price);
954        self
955    }
956}
957
958/// Request body for `POST /preview-aggressive-limit-order`.
959///
960/// # References
961/// - <https://docs.architect.exchange/api-reference/marketdata/preview-aggressive-limit-order>
962#[derive(Clone, Debug, Serialize, Deserialize)]
963pub struct PreviewAggressiveLimitOrderRequest {
964    /// Trading symbol.
965    pub symbol: Ustr,
966    /// Order quantity in contracts.
967    pub quantity: u64,
968    /// Order side: "B" (buy) or "S" (sell).
969    pub side: AxOrderSide,
970}
971
972impl PreviewAggressiveLimitOrderRequest {
973    /// Creates a new [`PreviewAggressiveLimitOrderRequest`].
974    #[must_use]
975    pub fn new(symbol: Ustr, quantity: u64, side: AxOrderSide) -> Self {
976        Self {
977            symbol,
978            quantity,
979            side,
980        }
981    }
982}
983
984/// Response payload returned by `POST /preview-aggressive-limit-order`.
985///
986/// # References
987/// - <https://docs.architect.exchange/api-reference/marketdata/preview-aggressive-limit-order>
988#[derive(Clone, Debug, Serialize, Deserialize)]
989pub struct AxPreviewAggressiveLimitOrderResponse {
990    /// Quantity that would be filled at the aggressive price.
991    pub filled_quantity: u64,
992    /// Quantity that cannot be filled (insufficient book depth).
993    pub remaining_quantity: u64,
994    /// The aggressive limit price ("take through" price), or None if no liquidity.
995    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
996    pub limit_price: Option<Decimal>,
997    /// Volume-weighted average price of expected fills.
998    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
999    pub vwap: Option<Decimal>,
1000}
1001
1002/// Request body for `POST /cancel_order`.
1003///
1004/// # References
1005/// - <https://docs.architect.exchange/api-reference/order-management/cancel-order>
1006#[derive(Clone, Debug, Serialize, Deserialize)]
1007pub struct CancelOrderRequest {
1008    /// Order ID to cancel.
1009    pub oid: String,
1010}
1011
1012impl CancelOrderRequest {
1013    /// Creates a new [`CancelOrderRequest`].
1014    #[must_use]
1015    pub fn new(order_id: impl Into<String>) -> Self {
1016        Self {
1017            oid: order_id.into(),
1018        }
1019    }
1020}
1021
1022/// Request body for `POST /cancel_all_orders`.
1023///
1024/// # References
1025/// - <https://docs.architect.co/sdk-reference/order-entry>
1026#[derive(Clone, Debug, Default, Serialize, Deserialize)]
1027pub struct CancelAllOrdersRequest {
1028    /// Optional symbol filter - only cancel orders for this symbol.
1029    #[serde(skip_serializing_if = "Option::is_none")]
1030    pub symbol: Option<Ustr>,
1031    /// Optional execution venue filter.
1032    #[serde(skip_serializing_if = "Option::is_none")]
1033    pub execution_venue: Option<Ustr>,
1034}
1035
1036impl CancelAllOrdersRequest {
1037    /// Creates a new [`CancelAllOrdersRequest`] to cancel all orders.
1038    #[must_use]
1039    pub fn new() -> Self {
1040        Self::default()
1041    }
1042
1043    /// Sets the symbol filter.
1044    #[must_use]
1045    pub fn with_symbol(mut self, symbol: Ustr) -> Self {
1046        self.symbol = Some(symbol);
1047        self
1048    }
1049
1050    /// Sets the execution venue filter.
1051    #[must_use]
1052    pub fn with_venue(mut self, venue: Ustr) -> Self {
1053        self.execution_venue = Some(venue);
1054        self
1055    }
1056}
1057
1058/// Response payload returned by `POST /cancel_all_orders`.
1059///
1060/// # References
1061/// - <https://docs.architect.co/sdk-reference/order-entry>
1062#[derive(Clone, Debug, Serialize, Deserialize)]
1063pub struct AxCancelAllOrdersResponse {
1064    /// Number of orders canceled.
1065    #[serde(default)]
1066    pub canceled_count: i64,
1067}
1068
1069/// Request body for batch cancel orders.
1070///
1071/// # References
1072/// - <https://docs.architect.co/sdk-reference/order-entry>
1073#[derive(Clone, Debug, Serialize, Deserialize)]
1074pub struct BatchCancelOrdersRequest {
1075    /// List of order IDs to cancel.
1076    pub order_ids: Vec<String>,
1077}
1078
1079impl BatchCancelOrdersRequest {
1080    /// Creates a new [`BatchCancelOrdersRequest`].
1081    #[must_use]
1082    pub fn new(order_ids: Vec<String>) -> Self {
1083        Self { order_ids }
1084    }
1085}
1086
1087/// Response payload returned by batch cancel orders.
1088///
1089/// # References
1090/// - <https://docs.architect.co/sdk-reference/order-entry>
1091#[derive(Clone, Debug, Serialize, Deserialize)]
1092pub struct AxBatchCancelOrdersResponse {
1093    /// Number of orders successfully canceled.
1094    #[serde(default)]
1095    pub canceled_count: i64,
1096    /// Order IDs that failed to cancel.
1097    #[serde(default)]
1098    pub failed_order_ids: Vec<String>,
1099}
1100
1101#[cfg(test)]
1102mod tests {
1103    use rstest::rstest;
1104
1105    use super::*;
1106
1107    #[rstest]
1108    fn test_deserialize_authenticate_response() {
1109        let json = include_str!("../../test_data/http_authenticate.json");
1110        let response: AxAuthenticateResponse = serde_json::from_str(json).unwrap();
1111        assert!(response.token.starts_with("test-token"));
1112    }
1113
1114    #[rstest]
1115    fn test_deserialize_whoami_response() {
1116        let json = include_str!("../../test_data/http_get_whoami.json");
1117        let response: AxWhoAmI = serde_json::from_str(json).unwrap();
1118        assert_eq!(response.username, "test_user");
1119        assert!(response.enabled_2fa);
1120    }
1121
1122    #[rstest]
1123    fn test_deserialize_instruments_response() {
1124        let json = include_str!("../../test_data/http_get_instruments.json");
1125        let response: AxInstrumentsResponse = serde_json::from_str(json).unwrap();
1126        assert_eq!(response.instruments.len(), 4);
1127        assert_eq!(response.instruments[0].symbol, "BTCUSD-PERP");
1128    }
1129
1130    #[rstest]
1131    fn test_deserialize_balances_response() {
1132        let json = include_str!("../../test_data/http_get_balances.json");
1133        let response: AxBalancesResponse = serde_json::from_str(json).unwrap();
1134        assert_eq!(response.balances.len(), 3);
1135        assert_eq!(response.balances[0].symbol, "USD");
1136    }
1137
1138    #[rstest]
1139    fn test_deserialize_positions_response() {
1140        let json = include_str!("../../test_data/http_get_positions.json");
1141        let response: AxPositionsResponse = serde_json::from_str(json).unwrap();
1142        assert_eq!(response.positions.len(), 2);
1143        assert_eq!(response.positions[0].symbol, "BTC-PERP");
1144        assert_eq!(response.positions[1].signed_quantity, -5);
1145    }
1146
1147    #[rstest]
1148    fn test_deserialize_tickers_response() {
1149        let json = include_str!("../../test_data/http_get_tickers.json");
1150        let response: AxTickersResponse = serde_json::from_str(json).unwrap();
1151        assert_eq!(response.tickers.len(), 3);
1152        assert_eq!(response.tickers[0].symbol, "EURUSD-PERP");
1153        assert!(response.tickers[0].bid.is_some());
1154        assert!(response.tickers[2].bid.is_none());
1155    }
1156
1157    #[rstest]
1158    fn test_deserialize_funding_rates_response() {
1159        let json = include_str!("../../test_data/http_get_funding_rates.json");
1160        let response: AxFundingRatesResponse = serde_json::from_str(json).unwrap();
1161        assert_eq!(response.funding_rates.len(), 2);
1162        assert_eq!(response.funding_rates[0].symbol, "JPYUSD-PERP");
1163    }
1164
1165    #[rstest]
1166    fn test_deserialize_open_orders_response() {
1167        let json = include_str!("../../test_data/http_get_open_orders.json");
1168        let response: AxOpenOrdersResponse = serde_json::from_str(json).unwrap();
1169        assert_eq!(response.orders.len(), 2);
1170        assert_eq!(response.orders[0].oid, "O-01ARZ3NDEKTSV4RRFFQ69G5FAV");
1171        assert_eq!(response.orders[0].d, AxOrderSide::Buy);
1172        assert_eq!(response.orders[0].o, AxOrderStatus::Accepted);
1173        assert_eq!(response.orders[1].xq, 300);
1174    }
1175
1176    #[rstest]
1177    fn test_deserialize_fills_response() {
1178        let json = include_str!("../../test_data/http_get_fills.json");
1179        let response: AxFillsResponse = serde_json::from_str(json).unwrap();
1180        assert_eq!(response.fills.len(), 2);
1181        assert_eq!(response.fills[0].side, AxOrderSide::Buy);
1182        assert!(response.fills[0].is_taker);
1183        assert!(!response.fills[1].is_taker);
1184    }
1185
1186    #[rstest]
1187    fn test_deserialize_candles_response() {
1188        let json = include_str!("../../test_data/http_get_candles.json");
1189        let response: AxCandlesResponse = serde_json::from_str(json).unwrap();
1190        assert_eq!(response.candles.len(), 2);
1191        assert_eq!(response.candles[0].symbol, "EURUSD-PERP");
1192        assert_eq!(response.candles[0].width, AxCandleWidth::Minutes1);
1193    }
1194
1195    #[rstest]
1196    fn test_deserialize_candle_response() {
1197        let json = include_str!("../../test_data/http_get_candle.json");
1198        let response: AxCandleResponse = serde_json::from_str(json).unwrap();
1199        assert_eq!(response.candle.symbol, "EURUSD-PERP");
1200        assert_eq!(response.candle.width, AxCandleWidth::Minutes1);
1201    }
1202
1203    #[rstest]
1204    fn test_deserialize_risk_snapshot_response() {
1205        let json = include_str!("../../test_data/http_get_risk_snapshot.json");
1206        let response: AxRiskSnapshotResponse = serde_json::from_str(json).unwrap();
1207        assert_eq!(
1208            response.risk_snapshot.user_id,
1209            "3c90c3cc-0d44-4b50-8888-8dd25736052a"
1210        );
1211        assert_eq!(response.risk_snapshot.per_symbol.len(), 2);
1212        assert!(
1213            response
1214                .risk_snapshot
1215                .per_symbol
1216                .contains_key("EURUSD-PERP")
1217        );
1218    }
1219
1220    #[rstest]
1221    fn test_deserialize_transactions_response() {
1222        let json = include_str!("../../test_data/http_get_transactions.json");
1223        let response: AxTransactionsResponse = serde_json::from_str(json).unwrap();
1224        assert_eq!(response.transactions.len(), 2);
1225        assert_eq!(response.transactions[0].transaction_type, "deposit");
1226        assert!(response.transactions[1].reference_id.is_none());
1227    }
1228
1229    #[rstest]
1230    fn test_deserialize_preview_aggressive_limit_order_response() {
1231        let json = include_str!("../../test_data/http_preview_aggressive_limit_order.json");
1232        let response: AxPreviewAggressiveLimitOrderResponse = serde_json::from_str(json).unwrap();
1233        assert_eq!(response.filled_quantity, 1000);
1234        assert_eq!(response.remaining_quantity, 0);
1235        assert!(response.limit_price.is_some());
1236        assert!(response.vwap.is_some());
1237    }
1238
1239    #[rstest]
1240    fn test_deserialize_place_order_response() {
1241        let json = include_str!("../../test_data/http_place_order.json");
1242        let response: AxPlaceOrderResponse = serde_json::from_str(json).unwrap();
1243        assert_eq!(response.oid, "O-01ARZ3NDEKTSV4RRFFQ69G5FAV");
1244    }
1245
1246    #[rstest]
1247    fn test_deserialize_cancel_order_response() {
1248        let json = include_str!("../../test_data/http_cancel_order.json");
1249        let response: AxCancelOrderResponse = serde_json::from_str(json).unwrap();
1250        assert!(response.cxl_rx);
1251    }
1252
1253    #[rstest]
1254    fn test_deserialize_cancel_all_orders_response() {
1255        let json = include_str!("../../test_data/http_cancel_all_orders.json");
1256        let response: AxCancelAllOrdersResponse = serde_json::from_str(json).unwrap();
1257        assert_eq!(response.canceled_count, 3);
1258    }
1259
1260    #[rstest]
1261    fn test_deserialize_batch_cancel_orders_response() {
1262        let json = include_str!("../../test_data/http_batch_cancel_orders.json");
1263        let response: AxBatchCancelOrdersResponse = serde_json::from_str(json).unwrap();
1264        assert_eq!(response.canceled_count, 2);
1265        assert_eq!(response.failed_order_ids.len(), 1);
1266    }
1267
1268    #[rstest]
1269    fn test_deserialize_trades_response() {
1270        let json = include_str!("../../test_data/http_get_trades.json");
1271        let response: AxTradesResponse = serde_json::from_str(json).unwrap();
1272        assert_eq!(response.trades.len(), 2);
1273        assert_eq!(response.trades[0].s, "EURUSD-PERP");
1274        assert_eq!(response.trades[0].d, AxOrderSide::Buy);
1275        assert_eq!(response.trades[0].q, 100);
1276        assert_eq!(response.trades[1].d, AxOrderSide::Sell);
1277    }
1278
1279    #[rstest]
1280    fn test_deserialize_book_response() {
1281        let json = include_str!("../../test_data/http_get_book.json");
1282        let response: AxBookResponse = serde_json::from_str(json).unwrap();
1283        assert_eq!(response.book.s, "EURUSD-PERP");
1284        assert_eq!(response.book.b.len(), 3);
1285        assert_eq!(response.book.a.len(), 3);
1286        assert_eq!(response.book.b[0].q, 500);
1287        assert_eq!(response.book.a[0].q, 400);
1288    }
1289
1290    #[rstest]
1291    fn test_deserialize_order_status_query_response() {
1292        let json = include_str!("../../test_data/http_get_order_status.json");
1293        let response: AxOrderStatusQueryResponse = serde_json::from_str(json).unwrap();
1294        assert_eq!(response.status.symbol, "EURUSD-PERP");
1295        assert_eq!(response.status.order_id, "O-01ARZ3NDEKTSV4RRFFQ69G5FAV");
1296        assert_eq!(response.status.state, AxOrderStatus::PartiallyFilled);
1297        assert_eq!(response.status.clord_id, Some(12345));
1298        assert_eq!(response.status.filled_quantity, Some(300));
1299        assert_eq!(response.status.remaining_quantity, Some(700));
1300    }
1301
1302    #[rstest]
1303    fn test_deserialize_orders_response() {
1304        let json = include_str!("../../test_data/http_get_orders.json");
1305        let response: AxOrdersResponse = serde_json::from_str(json).unwrap();
1306        assert_eq!(response.orders.len(), 2);
1307        assert_eq!(response.total_count, 2);
1308        assert_eq!(response.orders[0].o, AxOrderStatus::PartiallyFilled);
1309        assert_eq!(response.orders[0].xq, 300);
1310        assert_eq!(response.orders[1].o, AxOrderStatus::Filled);
1311        assert_eq!(response.orders[1].d, AxOrderSide::Sell);
1312    }
1313
1314    #[rstest]
1315    fn test_deserialize_initial_margin_requirement_response() {
1316        let json = include_str!("../../test_data/http_initial_margin_requirement.json");
1317        let response: AxInitialMarginRequirementResponse = serde_json::from_str(json).unwrap();
1318        assert_eq!(response.im, Decimal::new(125050, 2));
1319    }
1320}