Skip to main content

ccxt_exchanges/bitget/rest/
trading.rs

1//! Trading operations for Bitget REST API.
2
3use super::super::{Bitget, parser};
4use ccxt_core::{
5    Error, ParseError, Result,
6    types::{Amount, Order, OrderRequest, OrderSide, OrderType, Price, TimeInForce},
7};
8use tracing::warn;
9
10impl Bitget {
11    /// Create a new order.
12    pub async fn create_order_v2(&self, request: OrderRequest) -> Result<Order> {
13        let market = self.base().market(&request.symbol).await?;
14
15        let path = self.build_api_path("/trade/place-order");
16
17        let mut map = serde_json::Map::new();
18        map.insert(
19            "symbol".to_string(),
20            serde_json::Value::String(market.id.clone()),
21        );
22        map.insert(
23            "side".to_string(),
24            serde_json::Value::String(match request.side {
25                OrderSide::Buy => "buy".to_string(),
26                OrderSide::Sell => "sell".to_string(),
27            }),
28        );
29        map.insert(
30            "orderType".to_string(),
31            serde_json::Value::String(match request.order_type {
32                OrderType::LimitMaker => "limit_maker".to_string(),
33                OrderType::Market
34                | OrderType::StopLoss
35                | OrderType::StopMarket
36                | OrderType::TakeProfit
37                | OrderType::TrailingStop => "market".to_string(),
38                _ => "limit".to_string(),
39            }),
40        );
41        map.insert(
42            "size".to_string(),
43            serde_json::Value::String(request.amount.to_string()),
44        );
45
46        let force = if let Some(tif) = request.time_in_force {
47            match tif {
48                TimeInForce::GTC => "gtc",
49                TimeInForce::IOC => "ioc",
50                TimeInForce::FOK => "fok",
51                TimeInForce::PO => "post_only",
52            }
53        } else if request.post_only == Some(true) {
54            "post_only"
55        } else {
56            "gtc"
57        };
58        map.insert(
59            "force".to_string(),
60            serde_json::Value::String(force.to_string()),
61        );
62
63        if let Some(p) = request.price {
64            if request.order_type == OrderType::Limit || request.order_type == OrderType::LimitMaker
65            {
66                map.insert(
67                    "price".to_string(),
68                    serde_json::Value::String(p.to_string()),
69                );
70            }
71        }
72
73        if let Some(client_id) = request.client_order_id {
74            map.insert(
75                "clientOid".to_string(),
76                serde_json::Value::String(client_id),
77            );
78        }
79
80        if let Some(reduce_only) = request.reduce_only {
81            map.insert(
82                "reduceOnly".to_string(),
83                serde_json::Value::Bool(reduce_only),
84            );
85        }
86
87        if let Some(trigger) = request.trigger_price.or(request.stop_price) {
88            map.insert(
89                "triggerPrice".to_string(),
90                serde_json::Value::String(trigger.to_string()),
91            );
92        }
93
94        if let Some(tp) = request.take_profit_price {
95            map.insert(
96                "presetTakeProfitPrice".to_string(),
97                serde_json::Value::String(tp.to_string()),
98            );
99        }
100
101        if let Some(sl) = request.stop_loss_price {
102            map.insert(
103                "presetStopLossPrice".to_string(),
104                serde_json::Value::String(sl.to_string()),
105            );
106        }
107
108        let body = serde_json::Value::Object(map);
109
110        let response = self
111            .signed_request(&path)
112            .method(crate::bitget::signed_request::HttpMethod::Post)
113            .body(body)
114            .execute()
115            .await?;
116
117        let data = response
118            .get("data")
119            .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
120
121        parser::parse_order(data, Some(&market))
122    }
123
124    /// Create a new order (deprecated).
125    #[deprecated(
126        since = "0.2.0",
127        note = "Use create_order_v2 with OrderRequest::builder() instead"
128    )]
129    pub async fn create_order(
130        &self,
131        symbol: &str,
132        order_type: OrderType,
133        side: OrderSide,
134        amount: Amount,
135        price: Option<Price>,
136    ) -> Result<Order> {
137        let market = self.base().market(symbol).await?;
138
139        let path = self.build_api_path("/trade/place-order");
140
141        let mut map = serde_json::Map::new();
142        map.insert(
143            "symbol".to_string(),
144            serde_json::Value::String(market.id.clone()),
145        );
146        map.insert(
147            "side".to_string(),
148            serde_json::Value::String(match side {
149                OrderSide::Buy => "buy".to_string(),
150                OrderSide::Sell => "sell".to_string(),
151            }),
152        );
153        map.insert(
154            "orderType".to_string(),
155            serde_json::Value::String(match order_type {
156                OrderType::Market => "market".to_string(),
157                OrderType::LimitMaker => "limit_maker".to_string(),
158                _ => "limit".to_string(),
159            }),
160        );
161        map.insert(
162            "size".to_string(),
163            serde_json::Value::String(amount.to_string()),
164        );
165        map.insert(
166            "force".to_string(),
167            serde_json::Value::String("gtc".to_string()),
168        );
169
170        if let Some(p) = price {
171            if order_type == OrderType::Limit || order_type == OrderType::LimitMaker {
172                map.insert(
173                    "price".to_string(),
174                    serde_json::Value::String(p.to_string()),
175                );
176            }
177        }
178        let body = serde_json::Value::Object(map);
179
180        let response = self
181            .signed_request(&path)
182            .method(crate::bitget::signed_request::HttpMethod::Post)
183            .body(body)
184            .execute()
185            .await?;
186
187        let data = response
188            .get("data")
189            .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
190
191        parser::parse_order(data, Some(&market))
192    }
193
194    /// Cancel an existing order.
195    pub async fn cancel_order(&self, id: &str, symbol: &str) -> Result<Order> {
196        let market = self.base().market(symbol).await?;
197
198        let path = self.build_api_path("/trade/cancel-order");
199
200        let mut map = serde_json::Map::new();
201        map.insert(
202            "symbol".to_string(),
203            serde_json::Value::String(market.id.clone()),
204        );
205        map.insert(
206            "orderId".to_string(),
207            serde_json::Value::String(id.to_string()),
208        );
209        let body = serde_json::Value::Object(map);
210
211        let response = self
212            .signed_request(&path)
213            .method(crate::bitget::signed_request::HttpMethod::Post)
214            .body(body)
215            .execute()
216            .await?;
217
218        let data = response
219            .get("data")
220            .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
221
222        parser::parse_order(data, Some(&market))
223    }
224
225    /// Fetch a single order by ID.
226    pub async fn fetch_order(&self, id: &str, symbol: &str) -> Result<Order> {
227        let market = self.base().market(symbol).await?;
228
229        let path = self.build_api_path("/trade/orderInfo");
230
231        let response = self
232            .signed_request(&path)
233            .param("symbol", &market.id)
234            .param("orderId", id)
235            .execute()
236            .await?;
237
238        let data = response
239            .get("data")
240            .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
241
242        let order_data = if data.is_array() {
243            data.as_array()
244                .and_then(|arr| arr.first())
245                .ok_or_else(|| Error::exchange("40007", "Order not found"))?
246        } else {
247            data
248        };
249
250        parser::parse_order(order_data, Some(&market))
251    }
252
253    /// Fetch open orders.
254    pub async fn fetch_open_orders(
255        &self,
256        symbol: Option<&str>,
257        since: Option<i64>,
258        limit: Option<u32>,
259    ) -> Result<Vec<Order>> {
260        let path = self.build_api_path("/trade/unfilled-orders");
261
262        let market = if let Some(sym) = symbol {
263            Some(self.base().market(sym).await?)
264        } else {
265            None
266        };
267
268        let actual_limit = limit.map_or(100, |l| l.min(500));
269
270        let mut builder = self.signed_request(&path).param("limit", actual_limit);
271
272        if let Some(m) = &market {
273            builder = builder.param("symbol", &m.id);
274        }
275
276        if let Some(start_time) = since {
277            builder = builder.param("startTime", start_time);
278        }
279
280        let response = builder.execute().await?;
281
282        let data = response
283            .get("data")
284            .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
285
286        let orders_array = data.as_array().ok_or_else(|| {
287            Error::from(ParseError::invalid_format(
288                "data",
289                "Expected array of orders",
290            ))
291        })?;
292
293        let mut orders = Vec::new();
294        for order_data in orders_array {
295            match parser::parse_order(order_data, market.as_deref()) {
296                Ok(order) => orders.push(order),
297                Err(e) => {
298                    warn!(error = %e, "Failed to parse open order");
299                }
300            }
301        }
302
303        Ok(orders)
304    }
305
306    /// Fetch closed orders.
307    pub async fn fetch_closed_orders(
308        &self,
309        symbol: Option<&str>,
310        since: Option<i64>,
311        limit: Option<u32>,
312    ) -> Result<Vec<Order>> {
313        let path = self.build_api_path("/trade/history-orders");
314
315        let market = if let Some(sym) = symbol {
316            Some(self.base().market(sym).await?)
317        } else {
318            None
319        };
320
321        let actual_limit = limit.map_or(100, |l| l.min(500));
322
323        let mut builder = self.signed_request(&path).param("limit", actual_limit);
324
325        if let Some(m) = &market {
326            builder = builder.param("symbol", &m.id);
327        }
328
329        if let Some(start_time) = since {
330            builder = builder.param("startTime", start_time);
331        }
332
333        let response = builder.execute().await?;
334
335        let data = response
336            .get("data")
337            .ok_or_else(|| Error::from(ParseError::missing_field("data")))?;
338
339        let orders_array = data.as_array().ok_or_else(|| {
340            Error::from(ParseError::invalid_format(
341                "data",
342                "Expected array of orders",
343            ))
344        })?;
345
346        let mut orders = Vec::new();
347        for order_data in orders_array {
348            match parser::parse_order(order_data, market.as_deref()) {
349                Ok(order) => orders.push(order),
350                Err(e) => {
351                    warn!(error = %e, "Failed to parse closed order");
352                }
353            }
354        }
355
356        Ok(orders)
357    }
358}