Skip to main content

asterdex_sdk/futures/models/
trading.rs

1// Tech Lead mini-task (Wave 3): replaced stubs with full type definitions
2// per 05_detail_design.md §2.3
3
4use serde::{Deserialize, Serialize};
5use crate::futures::models::de;
6
7// --- Type Aliases ---
8
9/// All price values are String to avoid IEEE-754 precision loss (ADR-007).
10pub type Price = String;
11/// All quantity values are String to avoid IEEE-754 precision loss (ADR-007).
12pub type Quantity = String;
13
14// --- Request Params ---
15
16/// Parameters for placing a new order.
17#[derive(Debug, Serialize, Default)]
18pub struct PlaceOrderParams {
19    pub symbol: String,
20    pub side: String,
21    #[serde(rename = "type")]
22    pub order_type: String,
23    pub quantity: Quantity,
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub price: Option<Price>,
26    #[serde(rename = "timeInForce", skip_serializing_if = "Option::is_none")]
27    pub time_in_force: Option<String>,
28    #[serde(rename = "reduceOnly", skip_serializing_if = "Option::is_none")]
29    pub reduce_only: Option<bool>,
30    #[serde(rename = "newClientOrderId", skip_serializing_if = "Option::is_none")]
31    pub new_client_order_id: Option<String>,
32    #[serde(rename = "stopPrice", skip_serializing_if = "Option::is_none")]
33    pub stop_price: Option<Price>,
34    #[serde(rename = "activationPrice", skip_serializing_if = "Option::is_none")]
35    pub activation_price: Option<Price>,
36    #[serde(rename = "callbackRate", skip_serializing_if = "Option::is_none")]
37    pub callback_rate: Option<String>,
38    #[serde(rename = "workingType", skip_serializing_if = "Option::is_none")]
39    pub working_type: Option<String>,
40    #[serde(rename = "priceProtect", skip_serializing_if = "Option::is_none")]
41    pub price_protect: Option<bool>,
42    #[serde(rename = "newOrderRespType", skip_serializing_if = "Option::is_none")]
43    pub new_order_resp_type: Option<String>,
44}
45
46/// Parameters for cancelling an existing order.
47#[derive(Debug, Serialize, Default)]
48pub struct CancelOrderParams {
49    pub symbol: String,
50    #[serde(rename = "orderId")]
51    pub order_id: Option<i64>,
52    #[serde(rename = "origClientOrderId")]
53    pub orig_client_order_id: Option<String>,
54}
55
56/// Parameters for modifying (amending) an existing open LIMIT order.
57///
58/// Either `order_id` or `orig_client_order_id` must be provided.
59/// At least one of `quantity` or `price` must be set.
60/// Uses `PUT /fapi/v3/order`.
61#[derive(Debug, Default)]
62pub struct ModifyOrderParams {
63    pub symbol: String,
64    pub order_id: Option<i64>,
65    pub orig_client_order_id: Option<String>,
66    pub quantity: Option<Quantity>,
67    pub price: Option<Price>,
68}
69
70// --- Response Types ---
71
72/// Response from placing a new order.
73#[derive(Debug, Deserialize)]
74pub struct OrderResponse {
75    #[serde(rename = "orderId")]
76    pub order_id: i64,
77    pub symbol: String,
78    pub status: String,
79    #[serde(rename = "clientOrderId")]
80    pub client_order_id: String,
81    pub price: Price,
82    #[serde(rename = "avgPrice")]
83    pub avg_price: Price,
84    #[serde(rename = "origQty")]
85    pub orig_qty: Quantity,
86    #[serde(rename = "executedQty")]
87    pub executed_qty: Quantity,
88    /// Present in POST /order responses; absent in GET /openOrders responses.
89    #[serde(rename = "cumQty", default)]
90    pub cum_qty: Quantity,
91    #[serde(rename = "cumQuote")]
92    pub cum_quote: Price,
93    #[serde(rename = "timeInForce")]
94    pub time_in_force: String,
95    #[serde(rename = "type")]
96    pub order_type: String,
97    pub side: String,
98    #[serde(rename = "stopPrice")]
99    pub stop_price: Price,
100    #[serde(rename = "workingType")]
101    pub working_type: String,
102    /// Only present for TRAILING_STOP_MARKET orders.
103    #[serde(rename = "activatePrice", default)]
104    pub activate_price: Price,
105    /// Only present for TRAILING_STOP_MARKET orders.
106    #[serde(rename = "priceRate", default)]
107    pub price_rate: Price,
108    #[serde(rename = "updateTime")]
109    pub update_time: u64,
110    #[serde(rename = "origType")]
111    pub orig_type: String,
112    #[serde(rename = "positionSide")]
113    pub position_side: String,
114    #[serde(rename = "priceProtect")]
115    pub price_protect: bool,
116    #[serde(rename = "reduceOnly")]
117    pub reduce_only: bool,
118    #[serde(rename = "closePosition", default)]
119    pub close_position: bool,
120    /// Order creation time — present in GET /order and GET /forceOrders responses, absent in POST.
121    #[serde(default)]
122    pub time: Option<u64>,
123}
124
125/// Cancel order response has the same shape as OrderResponse.
126pub type CancelOrderResponse = OrderResponse;
127
128/// Current position risk information for a symbol.
129///
130/// The testnet (v2 schema) and mainnet (v3 schema) return overlapping but
131/// non-identical field sets.  All schema-variant fields use `#[serde(default)]`
132/// so both responses deserialize without error.
133///
134/// Schema differences handled here:
135/// - `unrealizedProfit` (v3) ↔ `unRealizedProfit` (v2) — both accepted via alias.
136/// - `maxNotional` (v3) ↔ `maxNotionalValue` (v2) — both accepted via alias.
137/// - `initialMargin`, `maintMargin`, `positionInitialMargin`,
138///   `openOrderInitialMargin`, `isolated` — v3-only fields, default when absent.
139/// - `markPrice`, `liquidationPrice`, `marginType`, `isolatedMargin`,
140///   `isAutoAddMargin`, `notional`, `isolatedWallet` — v2-only fields, default
141///   when absent.
142#[derive(Debug, Deserialize)]
143pub struct PositionResponse {
144    pub symbol: String,
145    // ----- v3-only fields (absent in testnet / v2 responses) -----
146    #[serde(rename = "initialMargin", default)]
147    pub initial_margin: String,
148    #[serde(rename = "maintMargin", default)]
149    pub maint_margin: String,
150    #[serde(rename = "positionInitialMargin", default)]
151    pub position_initial_margin: String,
152    #[serde(rename = "openOrderInitialMargin", default)]
153    pub open_order_initial_margin: String,
154    /// `false` when missing (v2 responses use `marginType` instead).
155    #[serde(default)]
156    pub isolated: bool,
157    // ----- shared fields (may differ in key name across schemas) -----
158    /// Accepts both `"unrealizedProfit"` (v3) and `"unRealizedProfit"` (v2).
159    #[serde(rename = "unrealizedProfit", alias = "unRealizedProfit", default)]
160    pub unrealized_profit: String,
161    /// Accepts both `"maxNotional"` (v3) and `"maxNotionalValue"` (v2).
162    #[serde(rename = "maxNotional", alias = "maxNotionalValue", default)]
163    pub max_notional: String,
164    pub leverage: String,
165    #[serde(rename = "entryPrice")]
166    pub entry_price: String,
167    #[serde(rename = "positionSide")]
168    pub position_side: String,
169    #[serde(rename = "positionAmt")]
170    pub position_amt: String,
171    #[serde(rename = "updateTime")]
172    pub update_time: u64,
173    // ----- v2-only fields (absent in v3 responses) -----
174    #[serde(rename = "markPrice", default)]
175    pub mark_price: String,
176    #[serde(rename = "liquidationPrice", default)]
177    pub liquidation_price: String,
178    #[serde(rename = "marginType", default)]
179    pub margin_type: String,
180    #[serde(rename = "isolatedMargin", default)]
181    pub isolated_margin: String,
182    #[serde(rename = "isAutoAddMargin", default)]
183    pub is_auto_add_margin: String,
184    #[serde(default)]
185    pub notional: String,
186    #[serde(rename = "isolatedWallet", default)]
187    pub isolated_wallet: String,
188}
189
190/// Stub for listen key response — fully defined in Wave 7.
191#[derive(Debug, Deserialize)]
192pub struct ListenKeyResponse {
193    #[serde(rename = "listenKey")]
194    pub listen_key: String,
195}
196
197/// Used when the API returns an empty JSON object `{}`.
198#[derive(Debug, Deserialize, Default)]
199pub struct EmptyResponse {}
200
201// --- Tech Lead mini-task (Wave 9): additional request/response types for US-014 ---
202
203/// Position margin operation type: 1 = add, 2 = reduce.
204#[derive(Debug, Clone, Copy)]
205pub enum PositionMarginType {
206    Add = 1,
207    Reduce = 2,
208}
209
210impl std::fmt::Display for PositionMarginType {
211    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
212        write!(f, "{}", *self as u8)
213    }
214}
215
216/// Query a single order by ID or client order ID.
217#[derive(Debug, Default)]
218pub struct GetOrderParams {
219    pub symbol: String,
220    pub order_id: Option<i64>,
221    pub orig_client_order_id: Option<String>,
222}
223
224/// Query all orders (open + historical) for a symbol.
225#[derive(Debug, Default)]
226pub struct AllOrdersParams {
227    pub symbol: String,
228    pub order_id: Option<i64>,
229    pub start_time: Option<u64>,
230    pub end_time: Option<u64>,
231    pub limit: Option<u32>,
232}
233
234/// Query user trade history for a symbol.
235#[derive(Debug, Default)]
236pub struct UserTradesParams {
237    pub symbol: String,
238    pub order_id: Option<i64>,
239    pub start_time: Option<u64>,
240    pub end_time: Option<u64>,
241    pub from_id: Option<i64>,
242    pub limit: Option<u32>,
243}
244
245/// Query income history (funding fees, realized PnL, etc.).
246#[derive(Debug, Default)]
247pub struct IncomeParams {
248    pub symbol: Option<String>,
249    pub income_type: Option<String>,
250    pub start_time: Option<u64>,
251    pub end_time: Option<u64>,
252    pub limit: Option<u32>,
253}
254
255/// Add or reduce isolated position margin.
256#[derive(Debug)]
257pub struct PositionMarginParams {
258    pub symbol: String,
259    pub position_side: Option<String>,
260    pub amount: String,
261    pub margin_type: u8,
262}
263
264/// Set countdown to auto-cancel all open orders.
265#[derive(Debug)]
266pub struct CountdownParams {
267    pub symbol: String,
268    pub countdown_time: i64,
269}
270
271/// User trade record.
272#[derive(Debug, Deserialize)]
273pub struct UserTrade {
274    pub symbol: String,
275    pub id: i64,
276    #[serde(rename = "orderId")] pub order_id: i64,
277    pub price: Price,
278    pub qty: Quantity,
279    #[serde(rename = "realizedPnl")] pub realized_pnl: Price,
280    #[serde(rename = "marginAsset")] pub margin_asset: String,
281    #[serde(rename = "quoteQty")] pub quote_qty: Price,
282    pub commission: Price,
283    #[serde(rename = "commissionAsset")] pub commission_asset: String,
284    pub time: u64,
285    #[serde(rename = "positionSide")] pub position_side: String,
286    pub buyer: bool,
287    pub maker: bool,
288    pub side: String,
289}
290
291/// Income history record.
292#[derive(Debug, Deserialize)]
293pub struct IncomeRecord {
294    pub symbol: String,
295    #[serde(rename = "incomeType")] pub income_type: String,
296    pub income: Price,
297    pub asset: String,
298    pub info: String,
299    pub time: u64,
300    /// May be returned as a JSON integer or a JSON string depending on income type.
301    #[serde(rename = "tranId", default, deserialize_with = "de::opt_string_or_number")]
302    pub tran_id: Option<String>,
303    #[serde(rename = "tradeId")] pub trade_id: Option<String>,
304}
305
306/// Response from `set_position_margin`.
307#[derive(Debug, Deserialize)]
308pub struct PositionMarginResponse {
309    pub amount: Price,
310    pub code: i64,
311    pub msg: String,
312    #[serde(rename = "type")] pub margin_type: u8,
313}
314
315/// Response from `countdown_cancel_all`.
316#[derive(Debug, Deserialize)]
317pub struct CountdownResponse {
318    pub symbol: String,
319    /// Returned as a JSON string on some deployments (`"0"`) and as a number on others.
320    #[serde(rename = "countdownTime", deserialize_with = "de::string_or_number_i64")]
321    pub countdown_time: i64,
322}