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 std::collections::HashMap;
19
20use chrono::{DateTime, Utc};
21use rust_decimal::Decimal;
22use serde::{Deserialize, Serialize};
23use ustr::Ustr;
24
25use crate::common::{
26    enums::{AxCandleWidth, AxInstrumentState, AxOrderSide, AxOrderStatus, AxTimeInForce},
27    parse::{deserialize_decimal_or_zero, deserialize_optional_decimal_from_str},
28};
29
30/// Default instrument state when not provided by API.
31fn default_instrument_state() -> AxInstrumentState {
32    AxInstrumentState::Open
33}
34
35/// Response payload returned by `GET /whoami`.
36///
37/// # References
38/// - <https://docs.sandbox.x.architect.co/api-reference/user-management/whoami>
39#[derive(Clone, Debug, Serialize, Deserialize)]
40#[serde(rename_all = "snake_case")]
41pub struct AxWhoAmI {
42    /// User account UUID.
43    pub id: String,
44    /// Username for the account.
45    pub username: String,
46    /// Account creation timestamp.
47    pub created_at: DateTime<Utc>,
48    /// Whether two-factor authentication is enabled.
49    pub enabled_2fa: bool,
50    /// Whether the user has completed onboarding.
51    pub is_onboarded: bool,
52    /// Whether the account is frozen.
53    pub is_frozen: bool,
54    /// Whether the user has admin privileges.
55    pub is_admin: bool,
56    /// Whether the account is in close-only mode.
57    pub is_close_only: bool,
58    /// Maker fee rate.
59    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
60    pub maker_fee: Decimal,
61    /// Taker fee rate.
62    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
63    pub taker_fee: Decimal,
64}
65
66/// Individual instrument definition.
67///
68/// # References
69/// - <https://docs.sandbox.x.architect.co/api-reference/symbols-instruments/get-instruments>
70#[derive(Clone, Debug, Serialize, Deserialize)]
71#[serde(rename_all = "snake_case")]
72pub struct AxInstrument {
73    /// Trading symbol for the instrument.
74    pub symbol: Ustr,
75    /// Current trading state of the instrument (defaults to Open if not provided).
76    #[serde(default = "default_instrument_state")]
77    pub state: AxInstrumentState,
78    /// Contract multiplier.
79    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
80    pub multiplier: Decimal,
81    /// Minimum order size.
82    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
83    pub minimum_order_size: Decimal,
84    /// Price tick size.
85    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
86    pub tick_size: Decimal,
87    /// Quote currency symbol.
88    pub quote_currency: Ustr,
89    // TODO: Rename to `funding_settlement_currency` once fixed
90    /// Funding settlement currency.
91    #[serde(alias = "funding_settlement_currency")]
92    pub finding_settlement_currency: Ustr,
93    /// Maintenance margin percentage.
94    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
95    pub maintenance_margin_pct: Decimal,
96    /// Initial margin percentage.
97    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
98    pub initial_margin_pct: Decimal,
99    /// Current mark price for the contract (optional).
100    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
101    pub contract_mark_price: Option<Decimal>,
102    /// Contract size (optional).
103    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
104    pub contract_size: Option<Decimal>,
105    /// Instrument description (optional).
106    #[serde(default)]
107    pub description: Option<String>,
108    /// Funding calendar schedule (optional).
109    #[serde(default)]
110    pub funding_calendar_schedule: Option<String>,
111    /// Funding frequency (optional).
112    #[serde(default)]
113    pub funding_frequency: Option<String>,
114    /// Lower cap for funding rate percentage (optional).
115    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
116    pub funding_rate_cap_lower_pct: Option<Decimal>,
117    /// Upper cap for funding rate percentage (optional).
118    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
119    pub funding_rate_cap_upper_pct: Option<Decimal>,
120    /// Lower deviation percentage for price bands (optional).
121    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
122    pub price_band_lower_deviation_pct: Option<Decimal>,
123    /// Upper deviation percentage for price bands (optional).
124    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
125    pub price_band_upper_deviation_pct: Option<Decimal>,
126    /// Price bands configuration (optional).
127    #[serde(default)]
128    pub price_bands: Option<String>,
129    /// Price quotation format (optional).
130    #[serde(default)]
131    pub price_quotation: Option<String>,
132    /// Underlying benchmark price (optional).
133    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
134    pub underlying_benchmark_price: Option<Decimal>,
135}
136
137/// Response payload returned by `GET /instruments`.
138///
139/// # References
140/// - <https://docs.sandbox.x.architect.co/api-reference/symbols-instruments/get-instruments>
141#[derive(Clone, Debug, Serialize, Deserialize)]
142#[serde(rename_all = "snake_case")]
143pub struct AxInstrumentsResponse {
144    /// List of instruments.
145    pub instruments: Vec<AxInstrument>,
146}
147
148/// Individual balance entry.
149///
150/// # References
151/// - <https://docs.sandbox.x.architect.co/api-reference/portfolio-management/get-balances>
152#[derive(Clone, Debug, Serialize, Deserialize)]
153#[serde(rename_all = "snake_case")]
154pub struct AxBalance {
155    /// Asset symbol.
156    pub symbol: Ustr,
157    /// Available balance amount.
158    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
159    pub amount: Decimal,
160}
161
162/// Response payload returned by `GET /balances`.
163///
164/// # References
165/// - <https://docs.sandbox.x.architect.co/api-reference/portfolio-management/get-balances>
166#[derive(Clone, Debug, Serialize, Deserialize)]
167#[serde(rename_all = "snake_case")]
168pub struct AxBalancesResponse {
169    /// List of balances.
170    pub balances: Vec<AxBalance>,
171}
172
173/// Individual position entry.
174///
175/// # References
176/// - <https://docs.sandbox.x.architect.co/api-reference/portfolio-management/get-positions>
177#[derive(Clone, Debug, Serialize, Deserialize)]
178#[serde(rename_all = "snake_case")]
179pub struct AxPosition {
180    /// User account UUID.
181    pub user_id: String,
182    /// Instrument symbol.
183    pub symbol: Ustr,
184    /// Open quantity (positive for long, negative for short).
185    pub open_quantity: i64,
186    /// Open notional value.
187    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
188    pub open_notional: Decimal,
189    /// Position timestamp.
190    pub timestamp: DateTime<Utc>,
191    /// Realized profit and loss.
192    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
193    pub realized_pnl: Decimal,
194}
195
196/// Response payload returned by `GET /positions`.
197///
198/// # References
199/// - <https://docs.sandbox.x.architect.co/api-reference/portfolio-management/get-positions>
200#[derive(Clone, Debug, Serialize, Deserialize)]
201#[serde(rename_all = "snake_case")]
202pub struct AxPositionsResponse {
203    /// List of positions.
204    pub positions: Vec<AxPosition>,
205}
206
207/// Individual ticker entry.
208///
209/// # References
210/// - <https://docs.sandbox.x.architect.co/api-reference/marketdata/get-ticker>
211#[derive(Clone, Debug, Serialize, Deserialize)]
212#[serde(rename_all = "snake_case")]
213pub struct AxTicker {
214    /// Instrument symbol.
215    pub symbol: Ustr,
216    /// Best bid price.
217    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
218    pub bid: Option<Decimal>,
219    /// Best ask price.
220    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
221    pub ask: Option<Decimal>,
222    /// Last trade price.
223    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
224    pub last: Option<Decimal>,
225    /// Mark price.
226    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
227    pub mark: Option<Decimal>,
228    /// Index price.
229    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
230    pub index: Option<Decimal>,
231    /// 24-hour volume.
232    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
233    pub volume_24h: Option<Decimal>,
234    /// 24-hour high price.
235    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
236    pub high_24h: Option<Decimal>,
237    /// 24-hour low price.
238    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
239    pub low_24h: Option<Decimal>,
240    /// Ticker timestamp.
241    #[serde(default)]
242    pub timestamp: Option<DateTime<Utc>>,
243}
244
245/// Response payload returned by `GET /tickers`.
246///
247/// # References
248/// - <https://docs.sandbox.x.architect.co/api-reference/marketdata/get-tickers>
249#[derive(Clone, Debug, Serialize, Deserialize)]
250#[serde(rename_all = "snake_case")]
251pub struct AxTickersResponse {
252    /// List of tickers.
253    pub tickers: Vec<AxTicker>,
254}
255
256/// Response payload returned by `POST /authenticate`.
257///
258/// # References
259/// - <https://docs.sandbox.x.architect.co/api-reference/user-management/get-user-token>
260#[derive(Clone, Debug, Serialize, Deserialize)]
261#[serde(rename_all = "snake_case")]
262pub struct AxAuthenticateResponse {
263    /// Session token for authenticated requests.
264    pub token: String,
265}
266
267/// Response payload returned by `POST /place_order`.
268///
269/// # References
270/// - <https://docs.sandbox.x.architect.co/api-reference/order-management/place-order>
271#[derive(Clone, Debug, Serialize, Deserialize)]
272pub struct AxPlaceOrderResponse {
273    /// Order ID of the placed order.
274    pub oid: String,
275}
276
277/// Response payload returned by `POST /cancel_order`.
278///
279/// # References
280/// - <https://docs.sandbox.x.architect.co/api-reference/order-management/cancel-order>
281#[derive(Clone, Debug, Serialize, Deserialize)]
282pub struct AxCancelOrderResponse {
283    /// Whether the cancel request has been accepted.
284    pub cxl_rx: bool,
285}
286
287/// Individual open order entry.
288///
289/// # References
290/// - <https://docs.sandbox.x.architect.co/api-reference/order-management/get-open-orders>
291#[derive(Clone, Debug, Serialize, Deserialize)]
292pub struct AxOpenOrder {
293    /// Trade number.
294    pub tn: i64,
295    /// Timestamp (Unix epoch).
296    pub ts: i64,
297    /// Order side: "B" (buy) or "S" (sell).
298    pub d: AxOrderSide,
299    /// Order status.
300    pub o: AxOrderStatus,
301    /// Order ID.
302    pub oid: String,
303    /// Price.
304    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
305    pub p: Decimal,
306    /// Quantity.
307    pub q: i64,
308    /// Remaining quantity.
309    pub rq: i64,
310    /// Symbol.
311    pub s: Ustr,
312    /// Time in force.
313    pub tif: AxTimeInForce,
314    /// User ID.
315    pub u: String,
316    /// Executed quantity.
317    pub xq: i64,
318    /// Optional order tag.
319    #[serde(default)]
320    pub tag: Option<String>,
321}
322
323/// Response payload returned by `GET /open_orders`.
324///
325/// Note: The response is a direct array, not wrapped in an object.
326///
327/// # References
328/// - <https://docs.sandbox.x.architect.co/api-reference/order-management/get-open-orders>
329pub type AxOpenOrdersResponse = Vec<AxOpenOrder>;
330
331/// Individual fill/trade entry.
332///
333/// # References
334/// - <https://docs.sandbox.x.architect.co/api-reference/portfolio-management/get-fills>
335#[derive(Clone, Debug, Serialize, Deserialize)]
336#[serde(rename_all = "snake_case")]
337pub struct AxFill {
338    /// Execution ID.
339    pub execution_id: String,
340    /// Fee amount.
341    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
342    pub fee: Decimal,
343    /// Whether this was a taker order.
344    pub is_taker: bool,
345    /// Execution price.
346    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
347    pub price: Decimal,
348    /// Executed quantity.
349    pub quantity: i64,
350    /// Instrument symbol.
351    pub symbol: Ustr,
352    /// Execution timestamp.
353    pub timestamp: DateTime<Utc>,
354    /// User ID.
355    pub user_id: String,
356}
357
358/// Response payload returned by `GET /fills`.
359///
360/// # References
361/// - <https://docs.sandbox.x.architect.co/api-reference/portfolio-management/get-fills>
362#[derive(Clone, Debug, Serialize, Deserialize)]
363#[serde(rename_all = "snake_case")]
364pub struct AxFillsResponse {
365    /// List of fills.
366    pub fills: Vec<AxFill>,
367}
368
369/// Individual candle/OHLCV entry.
370///
371/// # References
372/// - <https://docs.sandbox.x.architect.co/api-reference/marketdata/get-candles>
373#[derive(Clone, Debug, Serialize, Deserialize)]
374#[serde(rename_all = "snake_case")]
375pub struct AxCandle {
376    /// Instrument symbol.
377    pub symbol: Ustr,
378    /// Candle timestamp.
379    pub tn: DateTime<Utc>,
380    /// Open price.
381    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
382    pub open: Decimal,
383    /// High price.
384    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
385    pub high: Decimal,
386    /// Low price.
387    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
388    pub low: Decimal,
389    /// Close price.
390    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
391    pub close: Decimal,
392    /// Buy volume.
393    pub buy_volume: i64,
394    /// Sell volume.
395    pub sell_volume: i64,
396    /// Total volume.
397    pub volume: i64,
398    /// Candle width/interval.
399    pub width: AxCandleWidth,
400}
401
402/// Response payload returned by `GET /candles`.
403///
404/// # References
405/// - <https://docs.sandbox.x.architect.co/api-reference/marketdata/get-candles>
406#[derive(Clone, Debug, Serialize, Deserialize)]
407#[serde(rename_all = "snake_case")]
408pub struct AxCandlesResponse {
409    /// List of candles.
410    pub candles: Vec<AxCandle>,
411}
412
413/// Response payload returned by `GET /candles/current` and `GET /candles/last`.
414///
415/// # References
416/// - <https://docs.sandbox.x.architect.co/api-reference/marketdata/get-current-candle>
417/// - <https://docs.sandbox.x.architect.co/api-reference/marketdata/get-last-candle>
418#[derive(Clone, Debug, Serialize, Deserialize)]
419#[serde(rename_all = "snake_case")]
420pub struct AxCandleResponse {
421    /// The candle data.
422    pub candle: AxCandle,
423}
424
425/// Individual funding rate entry.
426///
427/// # References
428/// - <https://docs.sandbox.x.architect.co/api-reference/marketdata/get-funding-rates>
429#[derive(Clone, Debug, Serialize, Deserialize)]
430#[serde(rename_all = "snake_case")]
431pub struct AxFundingRate {
432    /// Instrument symbol.
433    pub symbol: Ustr,
434    /// Timestamp in nanoseconds.
435    pub timestamp_ns: i64,
436    /// Funding rate.
437    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
438    pub funding_rate: Decimal,
439    /// Funding amount.
440    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
441    pub funding_amount: Decimal,
442    /// Benchmark price.
443    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
444    pub benchmark_price: Decimal,
445    /// Settlement price.
446    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
447    pub settlement_price: Decimal,
448}
449
450/// Response payload returned by `GET /funding-rates`.
451///
452/// # References
453/// - <https://docs.sandbox.x.architect.co/api-reference/marketdata/get-funding-rates>
454#[derive(Clone, Debug, Serialize, Deserialize)]
455#[serde(rename_all = "snake_case")]
456pub struct AxFundingRatesResponse {
457    /// List of funding rates.
458    pub funding_rates: Vec<AxFundingRate>,
459}
460
461/// Per-symbol risk metrics.
462///
463/// # References
464/// - <https://docs.sandbox.x.architect.co/api-reference/portfolio-management/get-risk-snapshot>
465#[derive(Clone, Debug, Serialize, Deserialize)]
466#[serde(rename_all = "snake_case")]
467pub struct AxPerSymbolRisk {
468    /// Open quantity.
469    pub open_quantity: i64,
470    /// Open notional value.
471    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
472    pub open_notional: Decimal,
473    /// Average entry price.
474    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
475    pub average_price: Decimal,
476    /// Liquidation price.
477    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
478    pub liquidation_price: Option<Decimal>,
479    /// Initial margin required.
480    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
481    pub initial_margin_required: Option<Decimal>,
482    /// Maintenance margin required.
483    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
484    pub maintenance_margin_required: Option<Decimal>,
485    /// Unrealized P&L.
486    #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
487    pub unrealized_pnl: Option<Decimal>,
488}
489
490/// Risk snapshot data.
491///
492/// # References
493/// - <https://docs.sandbox.x.architect.co/api-reference/portfolio-management/get-risk-snapshot>
494#[derive(Clone, Debug, Serialize, Deserialize)]
495#[serde(rename_all = "snake_case")]
496pub struct AxRiskSnapshot {
497    /// USD account balance.
498    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
499    pub balance_usd: Decimal,
500    /// Total equity value.
501    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
502    pub equity: Decimal,
503    /// Available initial margin.
504    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
505    pub initial_margin_available: Decimal,
506    /// Margin required for open orders.
507    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
508    pub initial_margin_required_for_open_orders: Decimal,
509    /// Margin required for positions.
510    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
511    pub initial_margin_required_for_positions: Decimal,
512    /// Total initial margin requirement.
513    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
514    pub initial_margin_required_total: Decimal,
515    /// Available maintenance margin.
516    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
517    pub maintenance_margin_available: Decimal,
518    /// Required maintenance margin.
519    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
520    pub maintenance_margin_required: Decimal,
521    /// Unrealized profit/loss.
522    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
523    pub unrealized_pnl: Decimal,
524    /// Snapshot timestamp.
525    pub timestamp_ns: DateTime<Utc>,
526    /// User identifier.
527    pub user_id: String,
528    /// Per-symbol risk data.
529    #[serde(default)]
530    pub per_symbol: HashMap<String, AxPerSymbolRisk>,
531}
532
533/// Response payload returned by `GET /risk-snapshot`.
534///
535/// # References
536/// - <https://docs.sandbox.x.architect.co/api-reference/portfolio-management/get-risk-snapshot>
537#[derive(Clone, Debug, Serialize, Deserialize)]
538#[serde(rename_all = "snake_case")]
539pub struct AxRiskSnapshotResponse {
540    /// The risk snapshot data.
541    pub risk_snapshot: AxRiskSnapshot,
542}
543
544/// Individual transaction entry.
545///
546/// # References
547/// - <https://docs.sandbox.x.architect.co/api-reference/portfolio-management/get-transactions>
548#[derive(Clone, Debug, Serialize, Deserialize)]
549#[serde(rename_all = "snake_case")]
550pub struct AxTransaction {
551    /// Transaction amount.
552    #[serde(deserialize_with = "deserialize_decimal_or_zero")]
553    pub amount: Decimal,
554    /// Unique event identifier.
555    pub event_id: String,
556    /// Asset symbol.
557    pub symbol: Ustr,
558    /// Transaction timestamp.
559    pub timestamp: DateTime<Utc>,
560    /// Type of transaction.
561    pub transaction_type: Ustr,
562    /// User identifier.
563    pub user_id: String,
564    /// Optional reference identifier.
565    #[serde(default)]
566    pub reference_id: Option<String>,
567}
568
569/// Response payload returned by `GET /transactions`.
570///
571/// # References
572/// - <https://docs.sandbox.x.architect.co/api-reference/portfolio-management/get-transactions>
573#[derive(Clone, Debug, Serialize, Deserialize)]
574#[serde(rename_all = "snake_case")]
575pub struct AxTransactionsResponse {
576    /// List of transactions.
577    pub transactions: Vec<AxTransaction>,
578}
579
580/// Request body for `POST /authenticate` using API key and secret.
581///
582/// # References
583/// - <https://docs.sandbox.x.architect.co/api-reference/user-management/get-user-token>
584#[derive(Clone, Debug, Serialize, Deserialize)]
585#[serde(rename_all = "snake_case")]
586pub struct AuthenticateApiKeyRequest {
587    /// API key.
588    pub api_key: String,
589    /// API secret.
590    pub api_secret: String,
591    /// Token expiration in seconds.
592    pub expiration_seconds: i32,
593    /// Optional 2FA code.
594    #[serde(skip_serializing_if = "Option::is_none")]
595    pub totp: Option<String>,
596}
597
598impl AuthenticateApiKeyRequest {
599    /// Creates a new [`AuthenticateApiKeyRequest`].
600    #[must_use]
601    pub fn new(
602        api_key: impl Into<String>,
603        api_secret: impl Into<String>,
604        expiration_seconds: i32,
605    ) -> Self {
606        Self {
607            api_key: api_key.into(),
608            api_secret: api_secret.into(),
609            expiration_seconds,
610            totp: None,
611        }
612    }
613
614    /// Sets the optional 2FA code.
615    #[must_use]
616    pub fn with_totp(mut self, totp: impl Into<String>) -> Self {
617        self.totp = Some(totp.into());
618        self
619    }
620}
621
622/// Request body for `POST /authenticate` using username and password.
623///
624/// # References
625/// - <https://docs.sandbox.x.architect.co/api-reference/user-management/get-user-token>
626#[derive(Clone, Debug, Serialize, Deserialize)]
627#[serde(rename_all = "snake_case")]
628pub struct AuthenticateUserRequest {
629    /// Username.
630    pub username: String,
631    /// Password.
632    pub password: String,
633    /// Token expiration in seconds.
634    pub expiration_seconds: i32,
635    /// Optional 2FA code.
636    #[serde(skip_serializing_if = "Option::is_none")]
637    pub totp: Option<String>,
638}
639
640impl AuthenticateUserRequest {
641    /// Creates a new [`AuthenticateUserRequest`].
642    #[must_use]
643    pub fn new(
644        username: impl Into<String>,
645        password: impl Into<String>,
646        expiration_seconds: i32,
647    ) -> Self {
648        Self {
649            username: username.into(),
650            password: password.into(),
651            expiration_seconds,
652            totp: None,
653        }
654    }
655
656    /// Sets the optional 2FA code.
657    #[must_use]
658    pub fn with_totp(mut self, totp: impl Into<String>) -> Self {
659        self.totp = Some(totp.into());
660        self
661    }
662}
663
664/// Request body for `POST /place_order`.
665///
666/// # References
667/// - <https://docs.sandbox.x.architect.co/api-reference/order-management/place-order>
668#[derive(Clone, Debug, Serialize, Deserialize)]
669pub struct PlaceOrderRequest {
670    /// Order side: "B" (buy) or "S" (sell).
671    pub d: AxOrderSide,
672    /// Order price as decimal string.
673    pub p: String,
674    /// Post-only flag (maker-or-cancel).
675    pub po: bool,
676    /// Order quantity in contracts.
677    pub q: i64,
678    /// Order symbol.
679    pub s: String,
680    /// Time in force.
681    pub tif: AxTimeInForce,
682    /// Optional order tag (max 10 alphanumeric characters).
683    #[serde(skip_serializing_if = "Option::is_none")]
684    pub tag: Option<String>,
685}
686
687impl PlaceOrderRequest {
688    /// Creates a new [`PlaceOrderRequest`].
689    #[must_use]
690    pub fn new(
691        side: AxOrderSide,
692        price: impl Into<String>,
693        quantity: i64,
694        symbol: impl Into<String>,
695        time_in_force: AxTimeInForce,
696        post_only: bool,
697    ) -> Self {
698        Self {
699            d: side,
700            p: price.into(),
701            po: post_only,
702            q: quantity,
703            s: symbol.into(),
704            tif: time_in_force,
705            tag: None,
706        }
707    }
708
709    /// Sets the optional order tag.
710    #[must_use]
711    pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
712        self.tag = Some(tag.into());
713        self
714    }
715}
716
717/// Request body for `POST /cancel_order`.
718///
719/// # References
720/// - <https://docs.sandbox.x.architect.co/api-reference/order-management/cancel-order>
721#[derive(Clone, Debug, Serialize, Deserialize)]
722pub struct CancelOrderRequest {
723    /// Order ID to cancel.
724    pub oid: String,
725}
726
727impl CancelOrderRequest {
728    /// Creates a new [`CancelOrderRequest`].
729    #[must_use]
730    pub fn new(order_id: impl Into<String>) -> Self {
731        Self {
732            oid: order_id.into(),
733        }
734    }
735}