1use crate::error::{BybitError, Result};
4use crate::models::common::*;
5use rust_decimal::Decimal;
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize)]
10#[serde(rename_all = "camelCase")]
11pub struct PlaceOrderParams {
12 pub category: Category,
14 pub symbol: String,
16 pub side: Side,
18 pub order_type: OrderType,
20 pub qty: String,
22 #[serde(skip_serializing_if = "Option::is_none")]
24 pub price: Option<String>,
25 #[serde(skip_serializing_if = "Option::is_none")]
27 pub time_in_force: Option<TimeInForce>,
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub position_idx: Option<i32>,
31 #[serde(skip_serializing_if = "Option::is_none")]
33 pub order_link_id: Option<String>,
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub take_profit: Option<String>,
37 #[serde(skip_serializing_if = "Option::is_none")]
39 pub stop_loss: Option<String>,
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub reduce_only: Option<bool>,
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub close_on_trigger: Option<bool>,
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub trigger_price: Option<String>,
49 #[serde(skip_serializing_if = "Option::is_none")]
51 pub trigger_by: Option<TriggerBy>,
52}
53
54impl PlaceOrderParams {
55 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 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 pub fn with_position_idx(mut self, idx: i32) -> Self {
99 self.position_idx = Some(idx);
100 self
101 }
102
103 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 pub fn with_take_profit(mut self, price: &str) -> Self {
111 self.take_profit = Some(price.to_string());
112 self
113 }
114
115 pub fn with_stop_loss(mut self, price: &str) -> Self {
117 self.stop_loss = Some(price.to_string());
118 self
119 }
120
121 pub fn with_reduce_only(mut self, reduce_only: bool) -> Self {
123 self.reduce_only = Some(reduce_only);
124 self
125 }
126
127 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 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 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#[derive(Debug, Clone, Serialize)]
171#[serde(rename_all = "camelCase")]
172pub struct AmendOrderParams {
173 pub category: Category,
175 pub symbol: String,
177 #[serde(skip_serializing_if = "Option::is_none")]
179 pub order_id: Option<String>,
180 #[serde(skip_serializing_if = "Option::is_none")]
182 pub order_link_id: Option<String>,
183 #[serde(skip_serializing_if = "Option::is_none")]
185 pub qty: Option<String>,
186 #[serde(skip_serializing_if = "Option::is_none")]
188 pub price: Option<String>,
189 #[serde(skip_serializing_if = "Option::is_none")]
191 pub take_profit: Option<String>,
192 #[serde(skip_serializing_if = "Option::is_none")]
194 pub stop_loss: Option<String>,
195}
196
197impl AmendOrderParams {
198 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 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 pub fn with_price(mut self, price: &str) -> Self {
228 self.price = Some(price.to_string());
229 self
230 }
231
232 pub fn with_qty(mut self, qty: &str) -> Self {
234 self.qty = Some(qty.to_string());
235 self
236 }
237}
238
239#[derive(Debug, Clone, Serialize)]
241#[serde(rename_all = "camelCase")]
242pub struct CancelOrderParams {
243 pub category: Category,
245 pub symbol: String,
247 #[serde(skip_serializing_if = "Option::is_none")]
249 pub order_id: Option<String>,
250 #[serde(skip_serializing_if = "Option::is_none")]
252 pub order_link_id: Option<String>,
253}
254
255impl CancelOrderParams {
256 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 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#[derive(Debug, Clone, Serialize)]
279#[serde(rename_all = "camelCase")]
280pub struct CancelAllOrdersParams {
281 pub category: Category,
283 #[serde(skip_serializing_if = "Option::is_none")]
285 pub symbol: Option<String>,
286 #[serde(skip_serializing_if = "Option::is_none")]
288 pub base_coin: Option<String>,
289 #[serde(skip_serializing_if = "Option::is_none")]
291 pub settle_coin: Option<String>,
292}
293
294#[derive(Debug, Clone, Deserialize)]
296#[serde(rename_all = "camelCase")]
297pub struct OrderResponse {
298 pub order_id: String,
300 #[serde(default)]
302 pub order_link_id: String,
303}
304
305#[derive(Debug, Clone, Deserialize)]
307#[serde(rename_all = "camelCase")]
308pub struct OrdersList {
309 pub category: String,
311 pub list: Vec<OrderInfo>,
313 #[serde(default)]
315 pub next_page_cursor: String,
316}
317
318#[derive(Debug, Clone, Deserialize)]
320#[serde(rename_all = "camelCase")]
321pub struct OrderInfo {
322 pub order_id: String,
324 #[serde(default)]
326 pub order_link_id: String,
327 pub symbol: String,
329 pub side: String,
331 pub order_type: String,
333 #[serde(default)]
335 pub price: String,
336 pub qty: String,
338 #[serde(default)]
340 pub time_in_force: String,
341 pub order_status: String,
343 #[serde(default)]
345 pub cum_exec_qty: String,
346 #[serde(default)]
348 pub cum_exec_value: String,
349 #[serde(default)]
351 pub avg_price: String,
352 pub created_time: String,
354 pub updated_time: String,
356 #[serde(default)]
358 pub take_profit: String,
359 #[serde(default)]
361 pub stop_loss: String,
362 #[serde(default)]
364 pub position_idx: i32,
365 #[serde(default)]
367 pub reduce_only: bool,
368}
369
370#[derive(Debug, Clone, Serialize)]
372#[serde(rename_all = "camelCase")]
373pub struct BatchOrderRequest {
374 pub category: Category,
376 pub request: Vec<PlaceOrderParams>,
378}
379
380#[derive(Debug, Clone, Deserialize)]
382#[serde(rename_all = "camelCase")]
383pub struct BatchOrderResponse {
384 pub list: Vec<BatchOrderResult>,
386}
387
388#[derive(Debug, Clone, Deserialize)]
390#[serde(rename_all = "camelCase")]
391pub struct BatchOrderResult {
392 pub category: String,
394 pub symbol: String,
396 #[serde(default)]
398 pub order_id: String,
399 #[serde(default)]
401 pub order_link_id: String,
402 #[serde(default)]
404 pub create_type: String,
405}
406
407#[derive(Debug, Clone, Deserialize)]
409#[serde(rename_all = "camelCase")]
410pub struct CancelAllResponse {
411 pub list: Vec<CancelledOrder>,
413}
414
415#[derive(Debug, Clone, Deserialize)]
417#[serde(rename_all = "camelCase")]
418pub struct CancelledOrder {
419 pub order_id: String,
421 #[serde(default)]
423 pub order_link_id: String,
424}