Skip to main content

bybit_api/models/
trade.rs

1//! Trade models for orders.
2
3use crate::error::{BybitError, Result};
4use crate::models::common::*;
5use rust_decimal::Decimal;
6use serde::{Deserialize, Serialize};
7
8/// Place order request parameters.
9#[derive(Debug, Clone, Serialize)]
10#[serde(rename_all = "camelCase")]
11pub struct PlaceOrderParams {
12    /// Product category
13    pub category: Category,
14    /// Symbol name
15    pub symbol: String,
16    /// Order side
17    pub side: Side,
18    /// Order type
19    pub order_type: OrderType,
20    /// Order quantity
21    pub qty: String,
22    /// Order price (required for limit orders)
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub price: Option<String>,
25    /// Time in force
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub time_in_force: Option<TimeInForce>,
28    /// Position index (for hedge mode)
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub position_idx: Option<i32>,
31    /// User-defined order ID
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub order_link_id: Option<String>,
34    /// Take profit price
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub take_profit: Option<String>,
37    /// Stop loss price
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub stop_loss: Option<String>,
40    /// Reduce only flag
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub reduce_only: Option<bool>,
43    /// Close on trigger
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub close_on_trigger: Option<bool>,
46    /// Trigger price
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub trigger_price: Option<String>,
49    /// Trigger price type
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub trigger_by: Option<TriggerBy>,
52}
53
54impl PlaceOrderParams {
55    /// Create a new market order.
56    pub fn market(category: Category, symbol: &str, side: Side, qty: &str) -> Self {
57        Self {
58            category,
59            symbol: symbol.to_string(),
60            side,
61            order_type: OrderType::Market,
62            qty: qty.to_string(),
63            price: None,
64            time_in_force: None,
65            position_idx: None,
66            order_link_id: None,
67            take_profit: None,
68            stop_loss: None,
69            reduce_only: None,
70            close_on_trigger: None,
71            trigger_price: None,
72            trigger_by: None,
73        }
74    }
75
76    /// Create a new limit order.
77    pub fn limit(category: Category, symbol: &str, side: Side, qty: &str, price: &str) -> Self {
78        Self {
79            category,
80            symbol: symbol.to_string(),
81            side,
82            order_type: OrderType::Limit,
83            qty: qty.to_string(),
84            price: Some(price.to_string()),
85            time_in_force: Some(TimeInForce::GTC),
86            position_idx: None,
87            order_link_id: None,
88            take_profit: None,
89            stop_loss: None,
90            reduce_only: None,
91            close_on_trigger: None,
92            trigger_price: None,
93            trigger_by: None,
94        }
95    }
96
97    /// Set position index (for hedge mode).
98    pub fn with_position_idx(mut self, idx: i32) -> Self {
99        self.position_idx = Some(idx);
100        self
101    }
102
103    /// Set user-defined order ID.
104    pub fn with_order_link_id(mut self, id: &str) -> Self {
105        self.order_link_id = Some(id.to_string());
106        self
107    }
108
109    /// Set take profit price.
110    pub fn with_take_profit(mut self, price: &str) -> Self {
111        self.take_profit = Some(price.to_string());
112        self
113    }
114
115    /// Set stop loss price.
116    pub fn with_stop_loss(mut self, price: &str) -> Self {
117        self.stop_loss = Some(price.to_string());
118        self
119    }
120
121    /// Set reduce only flag.
122    pub fn with_reduce_only(mut self, reduce_only: bool) -> Self {
123        self.reduce_only = Some(reduce_only);
124        self
125    }
126
127    /// Validate parameters before sending.
128    pub fn validate(&self) -> Result<()> {
129        if self.symbol.is_empty() {
130            return Err(BybitError::InvalidParam("symbol cannot be empty".into()));
131        }
132
133        if self.qty.is_empty() {
134            return Err(BybitError::InvalidParam("qty cannot be empty".into()));
135        }
136
137        // Parse and validate qty is positive
138        let qty: Decimal = self
139            .qty
140            .parse()
141            .map_err(|_| BybitError::InvalidParam("qty must be a valid number".into()))?;
142        if qty <= Decimal::ZERO {
143            return Err(BybitError::InvalidParam("qty must be positive".into()));
144        }
145
146        // Limit orders require price
147        if self.order_type == OrderType::Limit {
148            match &self.price {
149                None => {
150                    return Err(BybitError::InvalidParam(
151                        "price is required for limit orders".into(),
152                    ))
153                }
154                Some(p) => {
155                    let price: Decimal = p.parse().map_err(|_| {
156                        BybitError::InvalidParam("price must be a valid number".into())
157                    })?;
158                    if price <= Decimal::ZERO {
159                        return Err(BybitError::InvalidParam("price must be positive".into()));
160                    }
161                }
162            }
163        }
164
165        Ok(())
166    }
167}
168
169/// Amend order request parameters.
170#[derive(Debug, Clone, Serialize)]
171#[serde(rename_all = "camelCase")]
172pub struct AmendOrderParams {
173    /// Product category
174    pub category: Category,
175    /// Symbol name
176    pub symbol: String,
177    /// Order ID (either order_id or order_link_id required)
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub order_id: Option<String>,
180    /// User-defined order ID
181    #[serde(skip_serializing_if = "Option::is_none")]
182    pub order_link_id: Option<String>,
183    /// New order quantity
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub qty: Option<String>,
186    /// New order price
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub price: Option<String>,
189    /// New take profit price
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub take_profit: Option<String>,
192    /// New stop loss price
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub stop_loss: Option<String>,
195}
196
197impl AmendOrderParams {
198    /// Create amend params by order ID.
199    pub fn by_order_id(category: Category, symbol: &str, order_id: &str) -> Self {
200        Self {
201            category,
202            symbol: symbol.to_string(),
203            order_id: Some(order_id.to_string()),
204            order_link_id: None,
205            qty: None,
206            price: None,
207            take_profit: None,
208            stop_loss: None,
209        }
210    }
211
212    /// Create amend params by order link ID.
213    pub fn by_order_link_id(category: Category, symbol: &str, order_link_id: &str) -> Self {
214        Self {
215            category,
216            symbol: symbol.to_string(),
217            order_id: None,
218            order_link_id: Some(order_link_id.to_string()),
219            qty: None,
220            price: None,
221            take_profit: None,
222            stop_loss: None,
223        }
224    }
225
226    /// Set new price.
227    pub fn with_price(mut self, price: &str) -> Self {
228        self.price = Some(price.to_string());
229        self
230    }
231
232    /// Set new quantity.
233    pub fn with_qty(mut self, qty: &str) -> Self {
234        self.qty = Some(qty.to_string());
235        self
236    }
237}
238
239/// Cancel order request parameters.
240#[derive(Debug, Clone, Serialize)]
241#[serde(rename_all = "camelCase")]
242pub struct CancelOrderParams {
243    /// Product category
244    pub category: Category,
245    /// Symbol name
246    pub symbol: String,
247    /// Order ID
248    #[serde(skip_serializing_if = "Option::is_none")]
249    pub order_id: Option<String>,
250    /// User-defined order ID
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub order_link_id: Option<String>,
253}
254
255impl CancelOrderParams {
256    /// Create cancel params by order ID.
257    pub fn by_order_id(category: Category, symbol: &str, order_id: &str) -> Self {
258        Self {
259            category,
260            symbol: symbol.to_string(),
261            order_id: Some(order_id.to_string()),
262            order_link_id: None,
263        }
264    }
265
266    /// Create cancel params by order link ID.
267    pub fn by_order_link_id(category: Category, symbol: &str, order_link_id: &str) -> Self {
268        Self {
269            category,
270            symbol: symbol.to_string(),
271            order_id: None,
272            order_link_id: Some(order_link_id.to_string()),
273        }
274    }
275}
276
277/// Cancel all orders request parameters.
278#[derive(Debug, Clone, Serialize)]
279#[serde(rename_all = "camelCase")]
280pub struct CancelAllOrdersParams {
281    /// Product category
282    pub category: Category,
283    /// Symbol name (optional, cancel all if not specified)
284    #[serde(skip_serializing_if = "Option::is_none")]
285    pub symbol: Option<String>,
286    /// Base coin
287    #[serde(skip_serializing_if = "Option::is_none")]
288    pub base_coin: Option<String>,
289    /// Settle coin
290    #[serde(skip_serializing_if = "Option::is_none")]
291    pub settle_coin: Option<String>,
292}
293
294/// Order response.
295#[derive(Debug, Clone, Deserialize)]
296#[serde(rename_all = "camelCase")]
297pub struct OrderResponse {
298    /// Order ID
299    pub order_id: String,
300    /// User-defined order ID
301    #[serde(default)]
302    pub order_link_id: String,
303}
304
305/// Orders list response.
306#[derive(Debug, Clone, Deserialize)]
307#[serde(rename_all = "camelCase")]
308pub struct OrdersList {
309    /// Category
310    pub category: String,
311    /// List of orders
312    pub list: Vec<OrderInfo>,
313    /// Next page cursor
314    #[serde(default)]
315    pub next_page_cursor: String,
316}
317
318/// Order info.
319#[derive(Debug, Clone, Deserialize)]
320#[serde(rename_all = "camelCase")]
321pub struct OrderInfo {
322    /// Order ID
323    pub order_id: String,
324    /// User-defined order ID
325    #[serde(default)]
326    pub order_link_id: String,
327    /// Symbol
328    pub symbol: String,
329    /// Side
330    pub side: String,
331    /// Order type
332    pub order_type: String,
333    /// Price
334    #[serde(default)]
335    pub price: String,
336    /// Quantity
337    pub qty: String,
338    /// Time in force
339    #[serde(default)]
340    pub time_in_force: String,
341    /// Order status
342    pub order_status: String,
343    /// Cumulative executed qty
344    #[serde(default)]
345    pub cum_exec_qty: String,
346    /// Cumulative executed value
347    #[serde(default)]
348    pub cum_exec_value: String,
349    /// Average price
350    #[serde(default)]
351    pub avg_price: String,
352    /// Created time
353    pub created_time: String,
354    /// Updated time
355    pub updated_time: String,
356    /// Take profit price
357    #[serde(default)]
358    pub take_profit: String,
359    /// Stop loss price
360    #[serde(default)]
361    pub stop_loss: String,
362    /// Position index
363    #[serde(default)]
364    pub position_idx: i32,
365    /// Reduce only
366    #[serde(default)]
367    pub reduce_only: bool,
368}
369
370/// Batch order request.
371#[derive(Debug, Clone, Serialize)]
372#[serde(rename_all = "camelCase")]
373pub struct BatchOrderRequest {
374    /// Product category
375    pub category: Category,
376    /// List of orders
377    pub request: Vec<PlaceOrderParams>,
378}
379
380/// Batch order response.
381#[derive(Debug, Clone, Deserialize)]
382#[serde(rename_all = "camelCase")]
383pub struct BatchOrderResponse {
384    /// List of results
385    pub list: Vec<BatchOrderResult>,
386}
387
388/// Single batch order result.
389#[derive(Debug, Clone, Deserialize)]
390#[serde(rename_all = "camelCase")]
391pub struct BatchOrderResult {
392    /// Category
393    pub category: String,
394    /// Symbol
395    pub symbol: String,
396    /// Order ID
397    #[serde(default)]
398    pub order_id: String,
399    /// User-defined order ID
400    #[serde(default)]
401    pub order_link_id: String,
402    /// Create type
403    #[serde(default)]
404    pub create_type: String,
405}
406
407/// Cancel all orders response.
408#[derive(Debug, Clone, Deserialize)]
409#[serde(rename_all = "camelCase")]
410pub struct CancelAllResponse {
411    /// List of cancelled orders
412    pub list: Vec<CancelledOrder>,
413}
414
415/// Cancelled order info.
416#[derive(Debug, Clone, Deserialize)]
417#[serde(rename_all = "camelCase")]
418pub struct CancelledOrder {
419    /// Order ID
420    pub order_id: String,
421    /// User-defined order ID
422    #[serde(default)]
423    pub order_link_id: String,
424}