exc_binance/http/request/trading/
usd_margin_futures.rs1use 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#[derive(Debug, Clone, Serialize)]
17#[serde(rename_all = "camelCase")]
18pub struct PlaceOrder {
19 pub symbol: String,
21 pub side: OrderSide,
23 pub position_side: PositionSide,
25 #[serde(rename = "type")]
27 pub order_type: OrderType,
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub reduce_only: Option<bool>,
31 #[serde(skip_serializing_if = "Option::is_none")]
33 pub quantity: Option<Decimal>,
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub price: Option<Decimal>,
37 #[serde(skip_serializing_if = "Option::is_none")]
39 pub new_client_order_id: Option<String>,
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub stop_price: Option<Decimal>,
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub close_position: Option<bool>,
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub activation_price: Option<Decimal>,
49 #[serde(skip_serializing_if = "Option::is_none")]
51 pub callback_rate: Option<Decimal>,
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub time_in_force: Option<TimeInForce>,
55 #[serde(skip_serializing_if = "Option::is_none")]
57 pub working_type: Option<String>,
58 #[serde(skip_serializing_if = "Option::is_none")]
60 pub price_protect: Option<String>,
61 #[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}