Skip to main content

asterdex_sdk/futures/endpoints/
trading.rs

1// US-001: Trading endpoints stub — implemented in US-004, US-005
2// US-004: place_order endpoint
3// US-005: cancel_order, get_open_orders, get_position_risk endpoints
4
5use crate::futures::models::trading::{
6    CancelOrderParams, CancelOrderResponse, ModifyOrderParams, OrderResponse, PlaceOrderParams,
7    PositionResponse,
8};
9use crate::rest::client::RestClient;
10use crate::rest::error::AsterDexError;
11use crate::rest::response::ApiResponse;
12
13impl RestClient {
14    /// Place a new futures order.
15    ///
16    /// # Errors
17    /// - `ApiError { code: -1102 }` — required parameter missing (e.g., price for LIMIT)
18    /// - `ApiError { code: -1121 }` — invalid symbol
19    /// - `RateLimited` — HTTP 429
20    /// - `IpBanned` — HTTP 418
21    pub async fn place_order(
22        &self,
23        params: PlaceOrderParams,
24    ) -> Result<ApiResponse<OrderResponse>, AsterDexError> {
25        // Convert bool → &'static str once, outside the Vec, so we don't need
26        // String conversions per call.
27        let reduce_only_s = params.reduce_only.map(bool_as_str);
28        let price_protect_s = params.price_protect.map(bool_as_str);
29
30        let mut pairs: Vec<(&str, &str)> = Vec::with_capacity(12);
31        pairs.push(("symbol", &params.symbol));
32        pairs.push(("side", &params.side));
33        pairs.push(("type", &params.order_type));
34        pairs.push(("quantity", &params.quantity));
35        if let Some(p) = params.price.as_deref() {
36            pairs.push(("price", p));
37        }
38        if let Some(tif) = params.time_in_force.as_deref() {
39            pairs.push(("timeInForce", tif));
40        }
41        if let Some(s) = reduce_only_s {
42            pairs.push(("reduceOnly", s));
43        }
44        if let Some(id) = params.new_client_order_id.as_deref() {
45            pairs.push(("newClientOrderId", id));
46        }
47        if let Some(sp) = params.stop_price.as_deref() {
48            pairs.push(("stopPrice", sp));
49        }
50        if let Some(ap) = params.activation_price.as_deref() {
51            pairs.push(("activationPrice", ap));
52        }
53        if let Some(cr) = params.callback_rate.as_deref() {
54            pairs.push(("callbackRate", cr));
55        }
56        if let Some(wt) = params.working_type.as_deref() {
57            pairs.push(("workingType", wt));
58        }
59        if let Some(s) = price_protect_s {
60            pairs.push(("priceProtect", s));
61        }
62        if let Some(ort) = params.new_order_resp_type.as_deref() {
63            pairs.push(("newOrderRespType", ort));
64        }
65        self.signed_post("/fapi/v3/order", &pairs).await
66    }
67
68    /// Cancel an open order.
69    ///
70    /// One of `params.order_id` or `params.orig_client_order_id` must be set.
71    ///
72    /// # Errors
73    /// - `ApiError { code: -2011 }` — order not found
74    /// - `ApiError { code: -1102 }` — neither orderId nor origClientOrderId provided
75    pub async fn cancel_order(
76        &self,
77        params: CancelOrderParams,
78    ) -> Result<ApiResponse<CancelOrderResponse>, AsterDexError> {
79        // `order_id` is numeric → one allocation; bind outside the Vec so the
80        // string lives long enough.
81        let order_id_str = params.order_id.map(|id| id.to_string());
82        let mut pairs: Vec<(&str, &str)> = Vec::with_capacity(3);
83        pairs.push(("symbol", &params.symbol));
84        if let Some(s) = order_id_str.as_deref() {
85            pairs.push(("orderId", s));
86        }
87        if let Some(cid) = params.orig_client_order_id.as_deref() {
88            pairs.push(("origClientOrderId", cid));
89        }
90        self.signed_delete("/fapi/v3/order", &pairs).await
91    }
92
93    /// Modify an existing open LIMIT order in-place (price and/or quantity).
94    ///
95    /// Calls `PUT /fapi/v3/order` directly. The original `order_id` is preserved —
96    /// this is a true amendment, not a cancel + re-place. At least one of
97    /// `params.quantity` or `params.price` must be set. Either `params.order_id`
98    /// or `params.orig_client_order_id` must be provided to identify the order.
99    ///
100    /// Only LIMIT orders can be modified. If the new qty/price fails exchange
101    /// filters the modification is rejected and the original order remains unchanged.
102    ///
103    /// # Errors
104    /// - `ApiError { code: -2011 }` — order not found
105    /// - `ApiError { code: -1102 }` — required parameter missing
106    pub async fn modify_order(
107        &self,
108        params: ModifyOrderParams,
109    ) -> Result<ApiResponse<OrderResponse>, AsterDexError> {
110        let order_id_str = params.order_id.map(|id| id.to_string());
111        let mut pairs: Vec<(&str, &str)> = Vec::with_capacity(5);
112        pairs.push(("symbol", &params.symbol));
113        if let Some(s) = order_id_str.as_deref() {
114            pairs.push(("orderId", s));
115        }
116        if let Some(cid) = params.orig_client_order_id.as_deref() {
117            pairs.push(("origClientOrderId", cid));
118        }
119        if let Some(q) = params.quantity.as_deref() {
120            pairs.push(("quantity", q));
121        }
122        if let Some(p) = params.price.as_deref() {
123            pairs.push(("price", p));
124        }
125        self.signed_put("/fapi/v3/order", &pairs).await
126    }
127
128    /// Get all open orders. Pass `None` for all symbols or `Some("BTCUSDT")` for one symbol.
129    pub async fn get_open_orders(
130        &self,
131        symbol: Option<&str>,
132    ) -> Result<ApiResponse<Vec<OrderResponse>>, AsterDexError> {
133        let mut kv: Vec<(&str, &str)> = vec![];
134        if let Some(s) = symbol {
135            kv.push(("symbol", s));
136        }
137        self.signed_get("/fapi/v3/openOrders", &kv).await
138    }
139
140    /// Get position risk information. Pass `None` for all symbols or `Some("BTCUSDT")` for one symbol.
141    pub async fn get_position_risk(
142        &self,
143        symbol: Option<&str>,
144    ) -> Result<ApiResponse<Vec<PositionResponse>>, AsterDexError> {
145        let mut kv: Vec<(&str, &str)> = vec![];
146        if let Some(s) = symbol {
147            kv.push(("symbol", s));
148        }
149        self.signed_get("/fapi/v3/positionRisk", &kv).await
150    }
151}
152
153/// Zero-alloc bool → form-value string.
154#[inline]
155fn bool_as_str(b: bool) -> &'static str {
156    if b { "true" } else { "false" }
157}