Skip to main content

binance/margin/http/
api.rs

1use rust_decimal::Decimal;
2use serde::{Deserialize, Serialize};
3
4use crate::{
5    Timestamp,
6    margin::{
7        IsIsolated, MarginLevelStatus, OrderResponseType, OrderSide, OrderStatus, OrderType,
8        STPMode, SideEffectType, TimeInForce,
9    },
10};
11
12#[derive(Debug, PartialEq)]
13pub struct Response<T> {
14    pub result: T,
15    pub headers: Headers,
16}
17
18#[derive(Debug, PartialEq)]
19pub struct Headers {
20    pub retry_after: Option<Timestamp>,
21}
22
23// ===== Margin metadata =====
24
25#[derive(Debug, Serialize, PartialEq, Default)]
26#[serde(rename_all = "camelCase")]
27pub struct GetAllMarginAssetsParams {
28    asset: Option<String>,
29    recv_window: Option<i64>,
30}
31
32impl GetAllMarginAssetsParams {
33    pub fn new() -> Self {
34        Self::default()
35    }
36
37    pub fn asset(mut self, value: impl Into<String>) -> Self {
38        self.asset = Some(value.into());
39        self
40    }
41
42    pub fn recv_window(mut self, value: i64) -> Self {
43        self.recv_window = Some(value);
44        self
45    }
46}
47
48#[derive(Debug, Deserialize, PartialEq)]
49#[serde(rename_all = "camelCase")]
50pub struct MarginAsset {
51    pub asset_full_name: String,
52    pub asset_name: String,
53    pub is_borrowable: bool,
54    pub is_mortgageable: bool,
55    pub user_min_borrow: Decimal,
56    pub user_min_repay: Decimal,
57}
58
59// ===== Cross margin account =====
60
61#[derive(Debug, Serialize, PartialEq, Default)]
62#[serde(rename_all = "camelCase")]
63pub struct GetMarginAccountParams {
64    recv_window: Option<i64>,
65}
66
67impl GetMarginAccountParams {
68    pub fn new() -> Self {
69        Self::default()
70    }
71
72    pub fn recv_window(mut self, value: i64) -> Self {
73        self.recv_window = Some(value);
74        self
75    }
76}
77
78#[derive(Debug, Deserialize, PartialEq)]
79#[serde(rename_all = "camelCase")]
80pub struct MarginAccount {
81    pub created: bool,
82    pub borrow_enabled: bool,
83    pub margin_level: Decimal,
84    pub collateral_margin_level: Decimal,
85    pub total_asset_of_btc: Decimal,
86    pub total_liability_of_btc: Decimal,
87    pub total_net_asset_of_btc: Decimal,
88    pub total_collateral_value_in_usdt: Decimal,
89    pub trade_enabled: bool,
90    pub transfer_in_enabled: bool,
91    pub transfer_out_enabled: bool,
92    pub account_type: String,
93    pub margin_level_status: MarginLevelStatus,
94    pub user_assets: Vec<MarginUserAsset>,
95}
96
97#[derive(Debug, Deserialize, PartialEq)]
98#[serde(rename_all = "camelCase")]
99pub struct MarginUserAsset {
100    pub asset: String,
101    pub borrowed: Decimal,
102    pub free: Decimal,
103    pub interest: Decimal,
104    pub locked: Decimal,
105    pub net_asset: Decimal,
106}
107
108// ===== Margin trading =====
109
110#[derive(Debug, Serialize, PartialEq)]
111#[serde(rename_all = "camelCase")]
112pub struct NewOrderRequest {
113    symbol: String,
114    is_isolated: Option<IsIsolated>,
115    side: OrderSide,
116    #[serde(rename = "type")]
117    order_type: OrderType,
118    quantity: Option<Decimal>,
119    quote_order_qty: Option<Decimal>,
120    price: Option<Decimal>,
121    stop_price: Option<Decimal>,
122    /// A unique id among open orders. Automatically generated if not sent.
123    new_client_order_id: Option<String>,
124    /// Used with LIMIT, STOP_LOSS_LIMIT, and TAKE_PROFIT_LIMIT to create an iceberg order.
125    iceberg_qty: Option<Decimal>,
126    /// Default ACK; MARKET and LIMIT default to FULL.
127    new_order_resp_type: Option<OrderResponseType>,
128    side_effect_type: Option<SideEffectType>,
129    time_in_force: Option<TimeInForce>,
130    self_trade_prevention_mode: Option<STPMode>,
131    /// Max 60000.
132    recv_window: Option<i64>,
133}
134
135impl NewOrderRequest {
136    pub fn new(symbol: impl Into<String>, side: OrderSide, order_type: OrderType) -> Self {
137        Self {
138            symbol: symbol.into(),
139            side,
140            order_type,
141            is_isolated: None,
142            quantity: None,
143            quote_order_qty: None,
144            price: None,
145            stop_price: None,
146            new_client_order_id: None,
147            iceberg_qty: None,
148            new_order_resp_type: None,
149            side_effect_type: None,
150            time_in_force: None,
151            self_trade_prevention_mode: None,
152            recv_window: None,
153        }
154    }
155
156    pub fn is_isolated(mut self, value: IsIsolated) -> Self {
157        self.is_isolated = Some(value);
158        self
159    }
160    pub fn quantity(mut self, value: Decimal) -> Self {
161        self.quantity = Some(value);
162        self
163    }
164    pub fn quote_order_qty(mut self, value: Decimal) -> Self {
165        self.quote_order_qty = Some(value);
166        self
167    }
168    pub fn price(mut self, value: Decimal) -> Self {
169        self.price = Some(value);
170        self
171    }
172    pub fn stop_price(mut self, value: Decimal) -> Self {
173        self.stop_price = Some(value);
174        self
175    }
176    pub fn new_client_order_id(mut self, value: impl Into<String>) -> Self {
177        self.new_client_order_id = Some(value.into());
178        self
179    }
180    pub fn iceberg_qty(mut self, value: Decimal) -> Self {
181        self.iceberg_qty = Some(value);
182        self
183    }
184    pub fn new_order_resp_type(mut self, value: OrderResponseType) -> Self {
185        self.new_order_resp_type = Some(value);
186        self
187    }
188    pub fn side_effect_type(mut self, value: SideEffectType) -> Self {
189        self.side_effect_type = Some(value);
190        self
191    }
192    pub fn time_in_force(mut self, value: TimeInForce) -> Self {
193        self.time_in_force = Some(value);
194        self
195    }
196    pub fn self_trade_prevention_mode(mut self, value: STPMode) -> Self {
197        self.self_trade_prevention_mode = Some(value);
198        self
199    }
200    pub fn recv_window(mut self, value: i64) -> Self {
201        self.recv_window = Some(value);
202        self
203    }
204}
205
206#[derive(Debug, Deserialize, PartialEq)]
207#[serde(untagged)]
208pub enum NewOrderResponse {
209    Full(NewOrderResponseFull),
210    Result(NewOrderResponseResult),
211    Ack(NewOrderResponseAck),
212}
213
214#[derive(Debug, Deserialize, PartialEq)]
215#[serde(rename_all = "camelCase")]
216pub struct NewOrderResponseAck {
217    pub symbol: String,
218    pub order_id: i64,
219    pub client_order_id: String,
220    pub transact_time: Timestamp,
221    pub is_isolated: bool,
222}
223
224#[derive(Debug, Deserialize, PartialEq)]
225#[serde(rename_all = "camelCase")]
226pub struct NewOrderResponseResult {
227    pub symbol: String,
228    pub order_id: i64,
229    pub client_order_id: String,
230    pub transact_time: Timestamp,
231    pub price: Decimal,
232    pub orig_qty: Decimal,
233    pub executed_qty: Decimal,
234    pub cummulative_quote_qty: Decimal,
235    pub status: OrderStatus,
236    pub time_in_force: TimeInForce,
237    #[serde(rename = "type")]
238    pub order_type: OrderType,
239    pub side: OrderSide,
240    pub margin_buy_borrow_amount: Option<Decimal>,
241    pub margin_buy_borrow_asset: Option<String>,
242    pub is_isolated: bool,
243    pub self_trade_prevention_mode: STPMode,
244}
245
246#[derive(Debug, Deserialize, PartialEq)]
247#[serde(rename_all = "camelCase")]
248pub struct NewOrderResponseFull {
249    pub symbol: String,
250    pub order_id: i64,
251    pub client_order_id: String,
252    pub transact_time: Timestamp,
253    pub price: Decimal,
254    pub orig_qty: Decimal,
255    pub executed_qty: Decimal,
256    pub cummulative_quote_qty: Decimal,
257    pub status: OrderStatus,
258    pub time_in_force: TimeInForce,
259    #[serde(rename = "type")]
260    pub order_type: OrderType,
261    pub side: OrderSide,
262    pub fills: Vec<OrderFill>,
263    pub margin_buy_borrow_amount: Option<Decimal>,
264    pub margin_buy_borrow_asset: Option<String>,
265    pub is_isolated: bool,
266    pub self_trade_prevention_mode: STPMode,
267}
268
269#[derive(Debug, Deserialize, PartialEq)]
270#[serde(rename_all = "camelCase")]
271pub struct OrderFill {
272    pub price: Decimal,
273    pub qty: Decimal,
274    pub commission: Decimal,
275    pub commission_asset: String,
276}
277
278// ===== Query order =====
279
280#[derive(Debug, Serialize, PartialEq)]
281#[serde(rename_all = "camelCase")]
282pub struct QueryOrderParams {
283    symbol: String,
284    is_isolated: Option<IsIsolated>,
285    order_id: Option<i64>,
286    orig_client_order_id: Option<String>,
287    recv_window: Option<i64>,
288}
289
290impl QueryOrderParams {
291    pub fn new(symbol: impl Into<String>) -> Self {
292        Self {
293            symbol: symbol.into(),
294            is_isolated: None,
295            order_id: None,
296            orig_client_order_id: None,
297            recv_window: None,
298        }
299    }
300
301    pub fn is_isolated(mut self, value: IsIsolated) -> Self {
302        self.is_isolated = Some(value);
303        self
304    }
305    pub fn order_id(mut self, value: i64) -> Self {
306        self.order_id = Some(value);
307        self
308    }
309    pub fn orig_client_order_id(mut self, value: impl Into<String>) -> Self {
310        self.orig_client_order_id = Some(value.into());
311        self
312    }
313    pub fn recv_window(mut self, value: i64) -> Self {
314        self.recv_window = Some(value);
315        self
316    }
317}
318
319#[derive(Debug, Deserialize, PartialEq)]
320#[serde(rename_all = "camelCase")]
321pub struct Order {
322    pub symbol: String,
323    pub order_id: i64,
324    pub client_order_id: String,
325    pub price: Decimal,
326    pub orig_qty: Decimal,
327    pub executed_qty: Decimal,
328    pub cummulative_quote_qty: Decimal,
329    pub status: OrderStatus,
330    pub time_in_force: TimeInForce,
331    #[serde(rename = "type")]
332    pub order_type: OrderType,
333    pub side: OrderSide,
334    pub stop_price: Option<Decimal>,
335    pub iceberg_qty: Option<Decimal>,
336    pub time: Timestamp,
337    pub update_time: Timestamp,
338    pub is_working: bool,
339    pub is_isolated: bool,
340    pub self_trade_prevention_mode: STPMode,
341}
342
343// ===== Max borrowable =====
344
345#[derive(Debug, Serialize, PartialEq)]
346#[serde(rename_all = "camelCase")]
347pub struct GetMaxBorrowableParams {
348    asset: String,
349    /// Required for isolated margin: the symbol whose isolated account to query.
350    isolated_symbol: Option<String>,
351    recv_window: Option<i64>,
352}
353
354impl GetMaxBorrowableParams {
355    pub fn new(asset: impl Into<String>) -> Self {
356        Self {
357            asset: asset.into(),
358            isolated_symbol: None,
359            recv_window: None,
360        }
361    }
362
363    pub fn isolated_symbol(mut self, value: impl Into<String>) -> Self {
364        self.isolated_symbol = Some(value.into());
365        self
366    }
367    pub fn recv_window(mut self, value: i64) -> Self {
368        self.recv_window = Some(value);
369        self
370    }
371}
372
373#[derive(Debug, Deserialize, PartialEq)]
374#[serde(rename_all = "camelCase")]
375pub struct MaxBorrowable {
376    pub amount: Decimal,
377    /// Account's current borrow limit for the asset.
378    pub borrow_limit: Decimal,
379}
380
381// ===== User data stream =====
382
383/// Response from `POST /sapi/v1/userDataStream{,/isolated}`.
384///
385/// Use the returned `listen_key` to connect to
386/// `wss://stream.binance.com:9443/ws/<listen_key>` and consume margin user
387/// data events. Keys live for 60 minutes from creation/keepalive — call
388/// `keepalive_listen_key` every 30 minutes to extend.
389#[derive(Debug, Deserialize, PartialEq)]
390#[serde(rename_all = "camelCase")]
391pub struct ListenKey {
392    pub listen_key: String,
393}
394
395/// Returned by keepalive and close operations on the user data stream
396/// (`PUT` / `DELETE`). The body is an empty JSON object `{}`.
397#[derive(Debug, Deserialize, PartialEq, Default)]
398pub struct EmptyResponse {}
399
400#[cfg(test)]
401mod tests {
402    use super::*;
403    use crate::serde::deserialize_json;
404
405    #[test]
406    fn deserialize_listen_key() {
407        let json =
408            r#"{"listenKey":"pqia91ma19a5s61cv6a81va65sdf19v8a65a1a5s61cv6a81va65sdf19v8a65a1"}"#;
409        let parsed: ListenKey = deserialize_json(json).unwrap();
410        assert_eq!(parsed.listen_key.len(), 64);
411    }
412
413    #[test]
414    fn deserialize_empty_response() {
415        let json = r#"{}"#;
416        let parsed: EmptyResponse = deserialize_json(json).unwrap();
417        assert_eq!(parsed, EmptyResponse {});
418    }
419}