1use serde_json::{json, Value};
2
3use crate::api::{Trade, API};
4use crate::client::Client;
5use crate::errors::BybitError;
6use crate::model::{
7 AmendOrderRequest, AmendOrderResponse, BatchAmendRequest, BatchAmendResponse,
8 BatchCancelRequest, BatchCancelResponse, BatchPlaceRequest, BatchPlaceResponse,
9 CancelOrderRequest, CancelOrderResponse, CancelallRequest, CancelallResponse, Category,
10 OpenOrdersRequest, OpenOrdersResponse, OrderHistoryRequest, OrderHistoryResponse, OrderRequest,
11 OrderResponse, OrderType, RequestType, Side, TradeHistoryRequest, TradeHistoryResponse,
12};
13use crate::util::{build_json_request, build_request, date_to_milliseconds, generate_random_uid};
14
15use std::borrow::Cow;
16use std::collections::BTreeMap;
17
18#[derive(Clone)]
19pub struct Trader {
20 pub client: Client,
21 pub recv_window: u16,
22}
23
24pub enum Action<'a> {
68 Order(OrderRequest<'a>, bool),
69 Amend(AmendOrderRequest<'a>, bool),
70 Cancel(CancelOrderRequest<'a>, bool),
71}
72
73impl Trader {
74 pub async fn place_custom_order<'b>(
75 &self,
76 req: OrderRequest<'_>,
77 ) -> Result<OrderResponse, BybitError> {
78 let action = Action::Order(req, false);
79 let parameters = Self::build_orders(action);
80
81 let request = build_json_request(¶meters);
82 let response: OrderResponse = self
83 .client
84 .post_signed(
85 API::Trade(Trade::Place),
86 self.recv_window.into(),
87 Some(request),
88 )
89 .await?;
90 Ok(response)
91 }
92
93 pub async fn place_futures_limit_order(
94 &self,
95 category: Category,
96 symbol: &str,
97 side: Side,
98 qty: f64,
99 price: f64,
100 mode: u8,
101 ) -> Result<OrderResponse, BybitError> {
102 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
103 let req = OrderRequest {
104 category,
105 symbol: Cow::Borrowed(symbol),
106 side,
107 qty,
108 order_type: OrderType::Limit,
109 position_idx: Some(mode),
110 order_link_id: Some(generate_random_uid(36).into()),
111 price: Some(price),
112 ..Default::default()
113 };
114 parameters.insert("category".into(), req.category.as_str().into());
115 parameters.insert("symbol".into(), req.symbol.into_owned());
116 parameters.insert("orderType".into(), req.order_type.as_str().into());
117 parameters.insert("side".into(), req.side.as_str().into());
118 if let Some(v) = req.order_link_id {
119 parameters.insert("orderLinkId".into(), v.into());
120 }
121 parameters.insert("qty".into(), req.qty.to_string());
122 if let Some(v) = req.position_idx {
123 match v {
124 0 | 1 | 2 => {
125 parameters.insert("positionIdx".into(), v.to_string());
126 }
127 _ => return Err(BybitError::from("Invalid position index".to_string())),
128 }
129 }
130 if let Some(v) = req.price {
131 parameters.insert("price".into(), v.to_string());
132 }
133 parameters.insert("timeInForce".into(), "GTC".into());
134 let request = build_json_request(¶meters);
135 let response: OrderResponse = self
136 .client
137 .post_signed(
138 API::Trade(Trade::Place),
139 self.recv_window.into(),
140 Some(request),
141 )
142 .await?;
143 Ok(response)
144 }
145
146 pub async fn amend_order<'b>(
147 &self,
148 req: AmendOrderRequest<'_>,
149 ) -> Result<AmendOrderResponse, BybitError> {
150 let action = Action::Amend(req, false);
151 let parameters = Self::build_orders(action);
152 let request = build_json_request(¶meters);
153 let response: AmendOrderResponse = self
154 .client
155 .post_signed(
156 API::Trade(Trade::Amend),
157 self.recv_window.into(),
158 Some(request),
159 )
160 .await?;
161 Ok(response)
162 }
163 pub async fn cancel_order<'b>(
164 &self,
165 req: CancelOrderRequest<'_>,
166 ) -> Result<CancelOrderResponse, BybitError> {
167 let action = Action::Cancel(req, false);
168 let parameters = Self::build_orders(action);
169 let request = build_json_request(¶meters);
170 let response: CancelOrderResponse = self
171 .client
172 .post_signed(
173 API::Trade(Trade::Cancel),
174 self.recv_window.into(),
175 Some(request),
176 )
177 .await?;
178 Ok(response)
179 }
180 pub async fn get_open_orders<'b>(
181 &self,
182 req: OpenOrdersRequest<'_>,
183 ) -> Result<OpenOrdersResponse, BybitError> {
184 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
185
186 parameters.insert("category".into(), req.category.as_str().into());
187 parameters.insert("symbol".into(), req.symbol.into());
188
189 if let Some(base_coin) = req.base_coin {
190 parameters.insert("baseCoin".into(), base_coin.into());
191 }
192 if let Some(settle_coin) = req.settle_coin {
193 parameters.insert("settleCoin".into(), settle_coin.into());
194 }
195 if let Some(order_id) = req.order_id {
196 parameters.insert("orderId".into(), order_id.into());
197 }
198 if let Some(order_link_id) = req.order_link_id {
199 parameters.insert("orderLinkId".into(), order_link_id.into());
200 }
201 if let Some(open_only) = req.open_only {
202 if matches!(open_only, 0 | 1 | 2) {
203 parameters.insert("openOnly".into(), open_only.to_string().into());
204 }
205 }
206 if let Some(order_filter) = req.order_filter {
207 parameters.insert("orderFilter".into(), order_filter.into());
208 }
209 if let Some(limit) = req.limit {
210 parameters.insert("limit".into(), limit.to_string().into());
211 }
212
213 let request = build_request(¶meters);
214 let response: OpenOrdersResponse = self
215 .client
216 .get_signed(API::Trade(Trade::OpenOrders), 5000, Some(request))
217 .await?;
218
219 Ok(response)
220 }
221 pub async fn cancel_all_orders<'b>(
222 &self,
223 req: CancelallRequest<'_>,
224 ) -> Result<CancelallResponse, BybitError> {
225 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
226 parameters.insert("category".into(), req.category.as_str().into());
227 parameters.insert("symbol".into(), req.symbol.into());
228 if let Some(base_coin) = req.base_coin {
229 parameters.insert("baseCoin".into(), base_coin.into());
230 }
231 if let Some(settle_coin) = req.settle_coin {
232 parameters.insert("settleCoin".into(), settle_coin.into());
233 }
234 if let Some(order_filter) = req.order_filter {
235 parameters.insert("orderFilter".into(), order_filter.into());
236 }
237 if let Some(stop_order_type) = req.stop_order_type {
238 parameters.insert("stopOrderType".into(), stop_order_type.into());
239 }
240 let request = build_json_request(¶meters);
241 let response: CancelallResponse = self
242 .client
243 .post_signed(
244 API::Trade(Trade::CancelAll),
245 self.recv_window.into(),
246 Some(request),
247 )
248 .await?;
249 Ok(response)
250 }
251
252 pub async fn get_order_history<'b>(
262 &self,
263 req: OrderHistoryRequest<'_>,
264 ) -> Result<OrderHistoryResponse, BybitError> {
265 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
266 parameters.insert("category".into(), req.category.as_str().into());
267 req.symbol
268 .map(|symbol| parameters.insert("symbol".into(), symbol.into()));
269 req.base_coin
270 .map(|base_coin| parameters.insert("baseCoin".into(), base_coin.into()));
271 req.settle_coin
272 .map(|settle_coin| parameters.insert("settleCoin".into(), settle_coin.into()));
273 req.order_id
274 .map(|order_id| parameters.insert("orderId".into(), order_id.into()));
275 req.order_link_id
276 .map(|order_link_id| parameters.insert("orderLinkId".into(), order_link_id.into()));
277 req.order_filter
278 .map(|order_filter| parameters.insert("orderFilter".into(), order_filter.into()));
279 req.order_status
280 .map(|order_status| parameters.insert("orderStatus".into(), order_status.into()));
281 req.start_time
282 .and_then(|start_time| Some(date_to_milliseconds(start_time.as_ref())))
283 .map(|start_millis| parameters.insert("startTime".into(), start_millis.to_string()));
284 req.end_time
285 .and_then(|end_time| Some(date_to_milliseconds(end_time.as_ref())))
286 .map(|end_millis| parameters.insert("endTime".into(), end_millis.to_string()));
287 req.limit
288 .map(|limit| parameters.insert("limit".into(), limit.to_string()));
289
290 let request = build_request(¶meters);
291 let response: OrderHistoryResponse = self
292 .client
293 .get_signed(
294 API::Trade(Trade::History),
295 self.recv_window.into(),
296 Some(request),
297 )
298 .await?;
299 Ok(response)
300 }
301
302 pub async fn get_trade_history<'b>(
312 &self,
313 req: TradeHistoryRequest<'_>,
314 ) -> Result<TradeHistoryResponse, BybitError> {
315 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
317
318 parameters.insert("category".into(), req.category.as_str().into());
320
321 req.symbol
323 .map(|symbol| parameters.insert("symbol".into(), symbol.into()));
324
325 req.order_id
327 .map(|order_id| parameters.insert("orderId".into(), order_id.into()));
328
329 req.order_link_id
331 .map(|order_link_id| parameters.insert("orderLinkId".into(), order_link_id.into()));
332
333 req.base_coin
335 .map(|base_coin| parameters.insert("baseCoin".into(), base_coin.into()));
336
337 req.start_time
339 .and_then(|start_time| Some(date_to_milliseconds(start_time.as_ref())))
340 .map(|start_millis| parameters.insert("startTime".into(), start_millis.to_string()));
341
342 req.end_time
344 .and_then(|end_time| Some(date_to_milliseconds(end_time.as_ref())))
345 .map(|end_millis| parameters.insert("endTime".into(), end_millis.to_string()));
346
347 req.limit
349 .map(|limit| parameters.insert("limit".into(), limit.to_string()));
350
351 req.exec_type
353 .map(|exec_type| parameters.insert("execType".into(), exec_type.into()));
354
355 let request = build_request(¶meters);
357
358 let response: TradeHistoryResponse = self
360 .client
361 .get_signed(
362 API::Trade(Trade::TradeHistory),
363 self.recv_window.into(),
364 Some(request),
365 )
366 .await?;
367
368 Ok(response)
370 }
371
372 pub async fn batch_place_order<'b>(
382 &self,
383 req: BatchPlaceRequest<'_>,
384 ) -> Result<BatchPlaceResponse, BybitError> {
385 let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
387
388 match req.category {
390 Category::Linear | Category::Inverse | Category::Option => {
391 parameters.insert("category".into(), req.category.as_str().into());
392 }
393 _ => {
395 println!("Invalid category");
396 }
397 }
398
399 let mut requests_array: Vec<Value> = Vec::new();
401
402 for value in req.requests {
404 let action = Action::Order(value, true);
406
407 let order_object = Self::build_orders(action);
409
410 let built_orders = json!(order_object);
412
413 requests_array.push(built_orders);
415 }
416
417 parameters.insert("request".into(), Value::Array(requests_array));
419
420 let request = build_json_request(¶meters);
422
423 let response: BatchPlaceResponse = self
425 .client
426 .post_signed(
427 API::Trade(Trade::BatchPlace),
428 self.recv_window.into(),
429 Some(request),
430 )
431 .await?;
432
433 Ok(response)
435 }
436
437 pub async fn batch_amend_order<'b>(
447 &self,
448 req: BatchAmendRequest<'_>,
449 ) -> Result<BatchAmendResponse, BybitError> {
450 let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
452
453 match req.category {
455 Category::Linear | Category::Inverse | Category::Option => {
456 parameters.insert("category".into(), req.category.as_str().into());
457 }
458 _ => {
459 println!("Invalid category");
461 }
462 }
463
464 let mut requests_array: Vec<Value> = Vec::new();
466
467 for value in req.requests {
469 let action = Action::Amend(value, true);
471
472 let amend_object = Self::build_orders(action); let built_amends = json!(amend_object);
477
478 requests_array.push(built_amends);
480 }
481
482 parameters.insert("request".into(), Value::Array(requests_array));
484
485 let request = build_json_request(¶meters);
487
488 let response: BatchAmendResponse = self
490 .client
491 .post_signed(
492 API::Trade(Trade::BatchAmend),
493 self.recv_window.into(),
494 Some(request),
495 )
496 .await?;
497
498 Ok(response)
500 }
501
502 pub async fn batch_cancel_order<'b>(
522 &self,
523 req: BatchCancelRequest<'_>,
524 ) -> Result<BatchCancelResponse, BybitError> {
525 let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
526 match req.category {
527 Category::Linear | Category::Inverse | Category::Option => {
528 parameters.insert("category".into(), req.category.as_str().into());
529 }
530 _ => {
531 println!("Invalid category");
532 }
533 }
534 let mut requests_array: Vec<Value> = Vec::new();
535 for value in req.requests {
536 let action = Action::Cancel(value, true);
537 let cancel_object = Self::build_orders(action); let built_cancels = json!(cancel_object);
539 requests_array.push(built_cancels);
540 }
541 parameters.insert("request".into(), Value::Array(requests_array));
542 let request = build_json_request(¶meters);
543 let response: BatchCancelResponse = self
544 .client
545 .post_signed(
546 API::Trade(Trade::BatchCancel),
547 self.recv_window.into(),
548 Some(request),
549 )
550 .await?;
551 Ok(response)
552 }
553
554 pub async fn get_borrow_quota_spot(&self) {
555 todo!("This function has not yet been implemented");
557 }
558
559 pub async fn set_dcp_options(&self) {
560 todo!("This function has not yet been implemented");
562 }
563
564 pub fn build_orders<'b>(action: Action<'_>) -> BTreeMap<String, Value> {
565 let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
566 match action {
567 Action::Order(req, batch) => {
568 if batch == false {
569 parameters.insert("category".into(), req.category.as_str().into());
570 }
571 parameters.insert("symbol".into(), req.symbol.into());
572 if let Some(leverage) = req.is_leverage {
573 if leverage {
574 parameters.insert("leverage".into(), 1.into());
576 }
577 }
578 parameters.insert("side".into(), req.side.as_str().into());
579 parameters.insert("orderType".into(), req.order_type.as_str().into());
580
581 parameters.insert("qty".into(), req.qty.to_string().into());
582 if let Some(market_unit) = req.market_unit {
583 parameters.insert("marketUnit".into(), market_unit.to_string().into());
584 }
585 if let Some(price) = req.price {
586 parameters.insert("price".into(), price.to_string().into());
587 }
588 if let Some(trigger_direction) = req.trigger_direction {
589 if trigger_direction {
590 parameters.insert("triggerDirection".into(), 1.into());
591 } else {
592 parameters.insert("triggerDirection".into(), 2.into());
593 }
594 }
595 if let Some(order_filter) = req.order_filter {
596 parameters.insert("orderFilter".into(), order_filter.into());
597 }
598 if let Some(trigger_price) = req.trigger_price {
599 parameters.insert("triggerPrice".into(), trigger_price.to_string().into());
600 }
601 if let Some(trigger) = req.trigger_by {
602 parameters.insert("triggerBy".into(), trigger.into());
603 }
604 if let Some(iv) = req.order_iv {
605 parameters.insert("orderIv".into(), iv.to_string().into());
606 }
607 if let Some(time_in_force) = req.time_in_force {
608 parameters.insert("timeInForce".into(), time_in_force.into());
609 }
610 if let Some(v) = req.position_idx {
611 match v {
612 0 | 1 | 2 => {
613 parameters.insert("positionIdx".into(), v.to_string().into());
614 }
615 _ => println!("Invalid position idx"),
616 }
617 }
618 if let Some(order_link_id) = req.order_link_id {
619 parameters.insert("orderLinkId".into(), order_link_id.into());
620 } else {
621 let uuid = generate_random_uid(36);
622 parameters.insert("orderLinkId".into(), uuid.into());
623 }
624 if let Some(price) = req.take_profit {
625 parameters.insert("takeProfit".into(), price.to_string().into());
626 }
627 if let Some(price) = req.stop_loss {
628 parameters.insert("stopLoss".into(), price.to_string().into());
629 }
630 if let Some(kind) = req.tp_trigger_by {
631 parameters.insert("tpTriggerBy".into(), kind.into());
632 }
633 if let Some(kind) = req.sl_trigger_by {
634 parameters.insert("slTriggerBy".into(), kind.into());
635 }
636 if let Some(reduce) = req.reduce_only {
637 parameters.insert("reduceOnly".into(), reduce.into());
638 }
639 if let Some(close) = req.close_on_trigger {
640 parameters.insert("closeOnTrigger".into(), close.into());
641 }
642 if let Some(v) = req.mmp {
643 parameters.insert("mmp".into(), v.into());
644 }
645 if let Some(v) = req.tpsl_mode {
646 parameters.insert("tpslMode".into(), v.into());
647 }
648 if let Some(v) = req.tp_limit_price {
649 parameters.insert("tpTriggerPrice".into(), v.to_string().into());
650 }
651 if let Some(v) = req.sl_limit_price {
652 parameters.insert("slTriggerPrice".into(), v.to_string().into());
653 }
654 if let Some(v) = req.tp_order_type {
655 parameters.insert("tpOrderType".into(), v.into());
656 }
657 if let Some(v) = req.sl_order_type {
658 parameters.insert("slOrderType".into(), v.into());
659 }
660 }
661 Action::Amend(req, batch) => {
662 if batch == false {
663 parameters.insert("category".into(), req.category.as_str().into());
664 }
665 parameters.insert("symbol".into(), req.symbol.into());
666 if let Some(v) = req.order_id {
667 parameters.insert("orderId".into(), v.into());
668 }
669 if let Some(v) = req.order_link_id {
670 parameters.insert("orderLinkId".into(), v.into());
671 }
672 if let Some(v) = req.order_iv {
673 parameters.insert("orderIv".into(), v.to_string().into());
674 }
675 if let Some(v) = req.trigger_price {
676 parameters.insert("triggerPrice".into(), v.to_string().into());
677 }
678 parameters.insert("qty".into(), req.qty.into());
679 if let Some(v) = req.price {
680 parameters.insert("price".into(), v.to_string().into());
681 }
682 if let Some(v) = req.tpsl_mode {
683 parameters.insert("tpslMode".into(), v.into());
684 }
685 if let Some(v) = req.take_profit {
686 parameters.insert("takeProfit".into(), v.to_string().into());
687 }
688 if let Some(v) = req.stop_loss {
689 parameters.insert("stopLoss".into(), v.to_string().into());
690 }
691 if let Some(v) = req.tp_trigger_by {
692 parameters.insert("tpTriggerBy".into(), v.into());
693 }
694 if let Some(v) = req.sl_trigger_by {
695 parameters.insert("slTriggerBy".into(), v.into());
696 }
697 if let Some(v) = req.trigger_by {
698 parameters.insert("triggerBy".into(), v.into());
699 }
700 if let Some(v) = req.tp_limit_price {
701 parameters.insert("tpLimitPrice".into(), v.to_string().into());
702 }
703 if let Some(v) = req.sl_limit_price {
704 parameters.insert("slLimitPrice".into(), v.to_string().into());
705 }
706 }
707 Action::Cancel(req, batch) => {
708 if batch == false {
709 parameters.insert("category".into(), req.category.as_str().into());
710 }
711 parameters.insert("symbol".into(), req.symbol.into());
712 if let Some(v) = req.order_id {
713 parameters.insert("orderId".into(), v.into());
714 }
715 if let Some(v) = req.order_link_id {
716 parameters.insert("orderLinkId".into(), v.into());
717 }
718 if let Some(v) = req.order_filter {
719 parameters.insert("orderFilter".into(), v.into());
720 }
721 }
722 }
723 parameters
724 }
725}
726
727pub fn build_ws_orders<'a>(orders: RequestType) -> Value {
728 let mut order_array = Vec::new();
729 match orders {
730 RequestType::Create(req) => {
731 for v in req.requests {
732 let action = Action::Order(v, false);
733 let order_object = Trader::build_orders(action); let built_order = json!(order_object);
735 order_array.push(built_order);
736 }
737 Value::Array(order_array)
738 }
739 RequestType::Amend(req) => {
740 for v in req.requests {
741 let action = Action::Amend(v, false);
742 let order_object = Trader::build_orders(action); let built_order = json!(order_object);
744 order_array.push(built_order);
745 }
746 Value::Array(order_array)
747 }
748 RequestType::Cancel(req) => {
749 for v in req.requests {
750 let action = Action::Cancel(v, false);
751 let order_object = Trader::build_orders(action); let built_order = json!(order_object);
753 order_array.push(built_order);
754 }
755 Value::Array(order_array)
756 }
757 }
758}