exc_binance/http/request/trading/
usd_margin_futures.rs

1use exc_core::types;
2use rust_decimal::Decimal;
3use serde::Serialize;
4
5use crate::{
6    http::{
7        error::RestError,
8        request::{Payload, Rest, RestEndpoint},
9    },
10    types::trading::{OrderSide, OrderType, PositionSide, TimeInForce},
11};
12
13pub use super::RespType;
14
15/// Place order.
16#[derive(Debug, Clone, Serialize)]
17#[serde(rename_all = "camelCase")]
18pub struct PlaceOrder {
19    /// Symbol.
20    pub symbol: String,
21    /// Side.
22    pub side: OrderSide,
23    /// Position side.
24    pub position_side: PositionSide,
25    /// Order type.
26    #[serde(rename = "type")]
27    pub order_type: OrderType,
28    /// Reduce only.
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub reduce_only: Option<bool>,
31    /// Quantity.
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub quantity: Option<Decimal>,
34    /// Price.
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub price: Option<Decimal>,
37    /// Client id.
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub new_client_order_id: Option<String>,
40    /// Stop price.
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub stop_price: Option<Decimal>,
43    /// Close position.
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub close_position: Option<bool>,
46    /// Activation price.
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub activation_price: Option<Decimal>,
49    /// Callback rate.
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub callback_rate: Option<Decimal>,
52    /// Time-In-Force.
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub time_in_force: Option<TimeInForce>,
55    /// Working type.
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub working_type: Option<String>,
58    /// Price protect.
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub price_protect: Option<String>,
61    /// New order response type.
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub new_order_resp_type: Option<RespType>,
64}
65
66impl<'a> TryFrom<&'a types::PlaceOrder> for PlaceOrder {
67    type Error = RestError;
68
69    fn try_from(req: &'a exc_core::types::PlaceOrder) -> Result<Self, Self::Error> {
70        let place = req.place;
71        let side = if place.size.is_zero() {
72            return Err(RestError::PlaceZeroSize);
73        } else if place.size.is_sign_positive() {
74            OrderSide::Buy
75        } else {
76            OrderSide::Sell
77        };
78        let (order_type, price, tif) = match place.kind {
79            types::OrderKind::Market => (OrderType::Market, None, None),
80            types::OrderKind::Limit(price, tif) => {
81                let tif = match tif {
82                    types::TimeInForce::GoodTilCancelled => Some(TimeInForce::Gtc),
83                    types::TimeInForce::FillOrKill => Some(TimeInForce::Fok),
84                    types::TimeInForce::ImmediateOrCancel => Some(TimeInForce::Ioc),
85                };
86                (OrderType::Limit, Some(price), tif)
87            }
88            types::OrderKind::PostOnly(price) => {
89                (OrderType::Limit, Some(price), Some(TimeInForce::Gtx))
90            }
91        };
92        Ok(Self {
93            symbol: req.opts.instrument().to_uppercase(),
94            side,
95            position_side: PositionSide::Both,
96            order_type,
97            reduce_only: None,
98            quantity: Some(place.size.abs()),
99            price,
100            new_client_order_id: req.opts.client_id().map(|s| s.to_string()),
101            stop_price: None,
102            close_position: None,
103            activation_price: None,
104            callback_rate: None,
105            time_in_force: tif,
106            working_type: None,
107            price_protect: None,
108            new_order_resp_type: None,
109        })
110    }
111}
112
113impl Rest for PlaceOrder {
114    fn method(&self, _endpoint: &RestEndpoint) -> Result<http::Method, RestError> {
115        Ok(http::Method::POST)
116    }
117
118    fn to_path(&self, endpoint: &RestEndpoint) -> Result<String, RestError> {
119        match endpoint {
120            RestEndpoint::UsdMarginFutures => Ok("/fapi/v1/order".to_string()),
121            _ => Err(RestError::UnsupportedEndpoint(anyhow::anyhow!(
122                "only support usd-margin futures"
123            ))),
124        }
125    }
126
127    fn need_apikey(&self) -> bool {
128        true
129    }
130
131    fn need_sign(&self) -> bool {
132        true
133    }
134
135    fn serialize(&self, _endpoint: &RestEndpoint) -> Result<serde_json::Value, RestError> {
136        Ok(serde_json::to_value(self)?)
137    }
138
139    fn to_payload(&self) -> Payload {
140        Payload::new(self.clone())
141    }
142}