1use crate::prelude::*;
2
3#[derive(Clone)]
4pub struct Trader {
5 pub client: Client,
6 pub recv_window: u16,
7}
8
9pub enum Action<'a> {
53 Order(OrderRequest<'a>, bool),
54 Amend(AmendOrderRequest<'a>, bool),
55 Cancel(CancelOrderRequest<'a>, bool),
56}
57
58impl Trader {
59 pub async fn place_custom_order<'b>(
60 &self,
61 req: OrderRequest<'_>,
62 ) -> Result<OrderResponse, BybitError> {
63 let action = Action::Order(req, false);
64 let parameters = Self::build_orders(action);
65
66 let request = build_json_request(¶meters);
67 let response: OrderResponse = self
68 .client
69 .post_signed(API::Trade(Trade::Place), self.recv_window, Some(request))
70 .await?;
71 Ok(response)
72 }
73
74 pub async fn place_futures_limit_order(
75 &self,
76 category: Category,
77 symbol: &str,
78 side: Side,
79 qty: f64,
80 price: f64,
81 mode: u8,
82 ) -> Result<OrderResponse, BybitError> {
83 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
84 let req = OrderRequest {
85 category,
86 symbol: Cow::Borrowed(symbol),
87 side,
88 qty,
89 order_type: OrderType::Limit,
90 position_idx: Some(mode),
91 order_link_id: Some(generate_random_uid(36).into()),
92 price: Some(price),
93 ..Default::default()
94 };
95 parameters.insert("category".into(), req.category.as_str().into());
96 parameters.insert("symbol".into(), req.symbol.into_owned());
97 parameters.insert("orderType".into(), req.order_type.as_str().into());
98 parameters.insert("side".into(), req.side.as_str().into());
99 if let Some(v) = req.order_link_id {
100 parameters.insert("orderLinkId".into(), v.into());
101 }
102 parameters.insert("qty".into(), req.qty.to_string());
103 if let Some(v) = req.position_idx {
104 match v {
105 0..=2 => {
106 parameters.insert("positionIdx".into(), v.to_string());
107 }
108 _ => return Err(BybitError::from("Invalid position index".to_string())),
109 }
110 }
111 if let Some(v) = req.price {
112 parameters.insert("price".into(), v.to_string());
113 }
114 parameters.insert("timeInForce".into(), "GTC".into());
115 let request = build_json_request(¶meters);
116 let response: OrderResponse = self
117 .client
118 .post_signed(API::Trade(Trade::Place), self.recv_window, Some(request))
119 .await?;
120 Ok(response)
121 }
122
123 pub async fn amend_order<'b>(
124 &self,
125 req: AmendOrderRequest<'_>,
126 ) -> Result<AmendOrderResponse, BybitError> {
127 let action = Action::Amend(req, false);
128 let parameters = Self::build_orders(action);
129 let request = build_json_request(¶meters);
130 let response: AmendOrderResponse = self
131 .client
132 .post_signed(API::Trade(Trade::Amend), self.recv_window, Some(request))
133 .await?;
134 Ok(response)
135 }
136 pub async fn cancel_order<'b>(
137 &self,
138 req: CancelOrderRequest<'_>,
139 ) -> Result<CancelOrderResponse, BybitError> {
140 let action = Action::Cancel(req, false);
141 let parameters = Self::build_orders(action);
142 let request = build_json_request(¶meters);
143 let response: CancelOrderResponse = self
144 .client
145 .post_signed(API::Trade(Trade::Cancel), self.recv_window, Some(request))
146 .await?;
147 Ok(response)
148 }
149 pub async fn get_open_orders<'b>(
150 &self,
151 req: OpenOrdersRequest<'_>,
152 ) -> Result<OpenOrdersResponse, BybitError> {
153 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
154
155 parameters.insert("category".into(), req.category.as_str().into());
156 parameters.insert("symbol".into(), req.symbol.into());
157
158 if let Some(base_coin) = req.base_coin {
159 parameters.insert("baseCoin".into(), base_coin.into());
160 }
161 if let Some(settle_coin) = req.settle_coin {
162 parameters.insert("settleCoin".into(), settle_coin.into());
163 }
164 if let Some(order_id) = req.order_id {
165 parameters.insert("orderId".into(), order_id.into());
166 }
167 if let Some(order_link_id) = req.order_link_id {
168 parameters.insert("orderLinkId".into(), order_link_id.into());
169 }
170 if let Some(open_only) = req.open_only {
171 if matches!(open_only, 0..=2) {
172 parameters.insert("openOnly".into(), open_only.to_string());
173 }
174 }
175 if let Some(order_filter) = req.order_filter {
176 parameters.insert("orderFilter".into(), order_filter.into());
177 }
178 if let Some(limit) = req.limit {
179 parameters.insert("limit".into(), limit.to_string());
180 }
181
182 let request = build_request(¶meters);
183 let response: OpenOrdersResponse = self
184 .client
185 .get_signed(API::Trade(Trade::OpenOrders), 5000, Some(request))
186 .await?;
187
188 Ok(response)
189 }
190 pub async fn cancel_all_orders<'b>(
191 &self,
192 req: CancelAllRequest<'_>,
193 ) -> Result<CancelAllResponse, BybitError> {
194 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
195 parameters.insert("category".into(), req.category.as_str().into());
196 parameters.insert("symbol".into(), req.symbol.into());
197 if let Some(base_coin) = req.base_coin {
198 parameters.insert("baseCoin".into(), base_coin.into());
199 }
200 if let Some(settle_coin) = req.settle_coin {
201 parameters.insert("settleCoin".into(), settle_coin.into());
202 }
203 if let Some(order_filter) = req.order_filter {
204 parameters.insert("orderFilter".into(), order_filter.into());
205 }
206 if let Some(stop_order_type) = req.stop_order_type {
207 parameters.insert("stopOrderType".into(), stop_order_type.into());
208 }
209 let request = build_json_request(¶meters);
210 let response: CancelAllResponse = self
211 .client
212 .post_signed(
213 API::Trade(Trade::CancelAll),
214 self.recv_window,
215 Some(request),
216 )
217 .await?;
218 Ok(response)
219 }
220
221 pub async fn get_order_history<'b>(
231 &self,
232 req: OrderHistoryRequest<'_>,
233 ) -> Result<OrderHistoryResponse, BybitError> {
234 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
235 parameters.insert("category".into(), req.category.as_str().into());
236 req.symbol
237 .map(|symbol| parameters.insert("symbol".into(), symbol.into()));
238 req.base_coin
239 .map(|base_coin| parameters.insert("baseCoin".into(), base_coin.into()));
240 req.settle_coin
241 .map(|settle_coin| parameters.insert("settleCoin".into(), settle_coin.into()));
242 req.order_id
243 .map(|order_id| parameters.insert("orderId".into(), order_id.into()));
244 req.order_link_id
245 .map(|order_link_id| parameters.insert("orderLinkId".into(), order_link_id.into()));
246 req.order_filter
247 .map(|order_filter| parameters.insert("orderFilter".into(), order_filter.into()));
248 req.order_status
249 .map(|order_status| parameters.insert("orderStatus".into(), order_status.into()));
250 req.start_time
251 .map(|start_time| parameters.insert("startTime".into(), start_time.to_string()));
252 req.end_time
253 .map(|end_time| parameters.insert("endTime".into(), end_time.to_string()));
254 req.limit
255 .map(|limit| parameters.insert("limit".into(), limit.to_string()));
256
257 let request = build_request(¶meters);
258 let response: OrderHistoryResponse = self
259 .client
260 .get_signed(API::Trade(Trade::History), self.recv_window, Some(request))
261 .await?;
262 Ok(response)
263 }
264
265 pub async fn get_trade_history<'b>(
275 &self,
276 req: TradeHistoryRequest<'_>,
277 ) -> Result<TradeHistoryResponse, BybitError> {
278 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
280
281 parameters.insert("category".into(), req.category.as_str().into());
283
284 req.symbol
286 .map(|symbol| parameters.insert("symbol".into(), symbol.into()));
287
288 req.order_id
290 .map(|order_id| parameters.insert("orderId".into(), order_id.into()));
291
292 req.order_link_id
294 .map(|order_link_id| parameters.insert("orderLinkId".into(), order_link_id.into()));
295
296 req.base_coin
298 .map(|base_coin| parameters.insert("baseCoin".into(), base_coin.into()));
299
300 req.start_time
302 .map(|start_time| parameters.insert("startTime".into(), start_time.to_string()));
303
304 req.end_time
306 .map(|end_time| parameters.insert("endTime".into(), end_time.to_string()));
307
308 req.limit
310 .map(|limit| parameters.insert("limit".into(), limit.to_string()));
311
312 req.exec_type
314 .map(|exec_type| parameters.insert("execType".into(), exec_type.into()));
315
316 let request = build_request(¶meters);
318
319 let response: TradeHistoryResponse = self
321 .client
322 .get_signed(
323 API::Trade(Trade::TradeHistory),
324 self.recv_window,
325 Some(request),
326 )
327 .await?;
328
329 Ok(response)
331 }
332
333 pub async fn batch_place_order<'b>(
343 &self,
344 req: BatchPlaceRequest<'_>,
345 ) -> Result<BatchPlaceResponse, BybitError> {
346 let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
348
349 match req.category {
351 Category::Linear | Category::Inverse | Category::Option => {
352 parameters.insert("category".into(), req.category.as_str().into());
353 }
354 _ => {
356 error!("Invalid category");
357 }
358 }
359
360 let mut requests_array: Vec<Value> = Vec::new();
362
363 for value in req.requests {
365 let action = Action::Order(value, true);
367
368 let order_object = Self::build_orders(action);
370
371 let built_orders = json!(order_object);
373
374 requests_array.push(built_orders);
376 }
377
378 parameters.insert("request".into(), Value::Array(requests_array));
380
381 let request = build_json_request(¶meters);
383
384 let response: BatchPlaceResponse = self
386 .client
387 .post_signed(
388 API::Trade(Trade::BatchPlace),
389 self.recv_window,
390 Some(request),
391 )
392 .await?;
393
394 Ok(response)
396 }
397
398 pub async fn batch_amend_order<'b>(
408 &self,
409 req: BatchAmendRequest<'_>,
410 ) -> Result<BatchAmendResponse, BybitError> {
411 let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
413
414 match req.category {
416 Category::Linear | Category::Inverse | Category::Option => {
417 parameters.insert("category".into(), req.category.as_str().into());
418 }
419 _ => {
420 error!("Invalid category");
422 }
423 }
424
425 let mut requests_array: Vec<Value> = Vec::new();
427
428 for value in req.requests {
430 let action = Action::Amend(value, true);
432
433 let amend_object = Self::build_orders(action); let built_amends = json!(amend_object);
438
439 requests_array.push(built_amends);
441 }
442
443 parameters.insert("request".into(), Value::Array(requests_array));
445
446 let request = build_json_request(¶meters);
448
449 let response: BatchAmendResponse = self
451 .client
452 .post_signed(
453 API::Trade(Trade::BatchAmend),
454 self.recv_window,
455 Some(request),
456 )
457 .await?;
458
459 Ok(response)
461 }
462
463 pub async fn batch_cancel_order<'b>(
484 &self,
485 req: BatchCancelRequest<'_>,
486 ) -> Result<BatchCancelResponse, BybitError> {
487 let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
488 match req.category {
489 Category::Linear | Category::Inverse | Category::Option => {
490 parameters.insert("category".into(), req.category.as_str().into());
491 }
492 _ => {
493 error!("Invalid category");
494 }
495 }
496 let mut requests_array: Vec<Value> = Vec::new();
497 for value in req.requests {
498 let action = Action::Cancel(value, true);
499 let cancel_object = Self::build_orders(action); let built_cancels = json!(cancel_object);
501 requests_array.push(built_cancels);
502 }
503 parameters.insert("request".into(), Value::Array(requests_array));
504 let request = build_json_request(¶meters);
505 let response: BatchCancelResponse = self
506 .client
507 .post_signed(
508 API::Trade(Trade::BatchCancel),
509 self.recv_window,
510 Some(request),
511 )
512 .await?;
513 Ok(response)
514 }
515
516 pub async fn get_borrow_quota_spot<'b>(
537 &self,
538 req: BorrowQuotaRequest<'_>,
539 ) -> Result<BorrowQuotaResponse, BybitError> {
540 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
541
542 parameters.insert("category".into(), req.category.as_str().into());
543 parameters.insert("symbol".into(), req.symbol.into());
544 parameters.insert("side".into(), req.side.as_str().into());
545
546 let request = build_request(¶meters);
547 let response: BorrowQuotaResponse = self
548 .client
549 .get_signed(
550 API::Trade(Trade::SpotBorrowCheck),
551 self.recv_window,
552 Some(request),
553 )
554 .await?;
555 Ok(response)
556 }
557
558 pub async fn set_dcp_options<'b>(
581 &self,
582 req: DcpRequest<'_>,
583 ) -> Result<DcpResponse, BybitError> {
584 if let Err(e) = req.validate() {
586 return Err(BybitError::from(e));
587 }
588
589 let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
590
591 parameters.insert("timeWindow".into(), req.time_window.into());
593
594 if let Some(product) = req.product {
596 parameters.insert("product".into(), product.into());
597 }
598
599 let request = build_json_request(¶meters);
600 let response: DcpResponse = self
601 .client
602 .post_signed(
603 API::Trade(Trade::SetDisconnectCancelall),
604 self.recv_window,
605 Some(request),
606 )
607 .await?;
608 Ok(response)
609 }
610
611 pub async fn pre_check_order<'b>(
633 &self,
634 req: OrderRequest<'_>,
635 ) -> Result<PreCheckOrderResponse, BybitError> {
636 let action = Action::Order(req, false);
637 let parameters = Self::build_orders(action);
638
639 let request = build_json_request(¶meters);
640 let response: PreCheckOrderResponse = self
641 .client
642 .post_signed(API::Trade(Trade::PreCheck), self.recv_window, Some(request))
643 .await?;
644 Ok(response)
645 }
646
647 pub fn build_orders<'b>(action: Action<'_>) -> BTreeMap<String, Value> {
648 let mut parameters: BTreeMap<String, Value> = BTreeMap::new();
649 match action {
650 Action::Order(req, batch) => {
651 if !batch {
652 parameters.insert("category".into(), req.category.as_str().into());
653 }
654 parameters.insert("symbol".into(), req.symbol.into());
655 if let Some(leverage) = req.is_leverage {
656 if leverage {
657 parameters.insert("leverage".into(), 1.into());
659 }
660 }
661 parameters.insert("side".into(), req.side.as_str().into());
662 parameters.insert("orderType".into(), req.order_type.as_str().into());
663
664 parameters.insert("qty".into(), req.qty.to_string().into());
665 if let Some(market_unit) = req.market_unit {
666 parameters.insert("marketUnit".into(), market_unit.to_string().into());
667 }
668 if let Some(price) = req.price {
669 parameters.insert("price".into(), price.to_string().into());
670 }
671 if let Some(trigger_direction) = req.trigger_direction {
672 if trigger_direction {
673 parameters.insert("triggerDirection".into(), 1.into());
674 } else {
675 parameters.insert("triggerDirection".into(), 2.into());
676 }
677 }
678 if let Some(order_filter) = req.order_filter {
679 parameters.insert("orderFilter".into(), order_filter.into());
680 }
681 if let Some(trigger_price) = req.trigger_price {
682 parameters.insert("triggerPrice".into(), trigger_price.to_string().into());
683 }
684 if let Some(trigger) = req.trigger_by {
685 parameters.insert("triggerBy".into(), trigger.into());
686 }
687 if let Some(iv) = req.order_iv {
688 parameters.insert("orderIv".into(), iv.to_string().into());
689 }
690 if let Some(time_in_force) = req.time_in_force {
691 parameters.insert("timeInForce".into(), time_in_force.into());
692 }
693 if let Some(v) = req.position_idx {
694 match v {
695 0..=2 => {
696 parameters.insert("positionIdx".into(), v.to_string().into());
697 }
698 _ => error!("Invalid position idx"),
699 }
700 }
701 if let Some(order_link_id) = req.order_link_id {
702 parameters.insert("orderLinkId".into(), order_link_id.into());
703 } else {
704 let uuid = generate_random_uid(36);
705 parameters.insert("orderLinkId".into(), uuid.into());
706 }
707 if let Some(price) = req.take_profit {
708 parameters.insert("takeProfit".into(), price.to_string().into());
709 }
710 if let Some(price) = req.stop_loss {
711 parameters.insert("stopLoss".into(), price.to_string().into());
712 }
713 if let Some(kind) = req.tp_trigger_by {
714 parameters.insert("tpTriggerBy".into(), kind.into());
715 }
716 if let Some(kind) = req.sl_trigger_by {
717 parameters.insert("slTriggerBy".into(), kind.into());
718 }
719 if let Some(reduce) = req.reduce_only {
720 parameters.insert("reduceOnly".into(), reduce.into());
721 }
722 if let Some(close) = req.close_on_trigger {
723 parameters.insert("closeOnTrigger".into(), close.into());
724 }
725 if let Some(v) = req.mmp {
726 parameters.insert("mmp".into(), v.into());
727 }
728 if let Some(v) = req.tpsl_mode {
729 parameters.insert("tpslMode".into(), v.into());
730 }
731 if let Some(v) = req.tp_limit_price {
732 parameters.insert("tpTriggerPrice".into(), v.to_string().into());
733 }
734 if let Some(v) = req.sl_limit_price {
735 parameters.insert("slTriggerPrice".into(), v.to_string().into());
736 }
737 if let Some(v) = req.tp_order_type {
738 parameters.insert("tpOrderType".into(), v.into());
739 }
740 if let Some(v) = req.sl_order_type {
741 parameters.insert("slOrderType".into(), v.into());
742 }
743 if let Some(v) = req.slippage_tolerance_type {
744 parameters.insert("slippageToleranceType".into(), v.into());
745 }
746 if let Some(v) = req.slippage_tolerance {
747 parameters.insert("slippageTolerance".into(), v.to_string().into());
748 }
749 if let Some(v) = req.bbo_side_type {
750 parameters.insert("bboSideType".into(), v.into());
751 }
752 if let Some(v) = req.bbo_level {
753 parameters.insert("bboLevel".into(), v.to_string().into());
754 }
755 }
756 Action::Amend(req, batch) => {
757 if !batch {
758 parameters.insert("category".into(), req.category.as_str().into());
759 }
760 parameters.insert("symbol".into(), req.symbol.into());
761 if let Some(v) = req.order_id {
762 parameters.insert("orderId".into(), v.into());
763 }
764 if let Some(v) = req.order_link_id {
765 parameters.insert("orderLinkId".into(), v.into());
766 }
767 if let Some(v) = req.order_iv {
768 parameters.insert("orderIv".into(), v.to_string().into());
769 }
770 if let Some(v) = req.trigger_price {
771 parameters.insert("triggerPrice".into(), v.to_string().into());
772 }
773 parameters.insert("qty".into(), req.qty.into());
774 if let Some(v) = req.price {
775 parameters.insert("price".into(), v.to_string().into());
776 }
777 if let Some(v) = req.tpsl_mode {
778 parameters.insert("tpslMode".into(), v.into());
779 }
780 if let Some(v) = req.take_profit {
781 parameters.insert("takeProfit".into(), v.to_string().into());
782 }
783 if let Some(v) = req.stop_loss {
784 parameters.insert("stopLoss".into(), v.to_string().into());
785 }
786 if let Some(v) = req.tp_trigger_by {
787 parameters.insert("tpTriggerBy".into(), v.into());
788 }
789 if let Some(v) = req.sl_trigger_by {
790 parameters.insert("slTriggerBy".into(), v.into());
791 }
792 if let Some(v) = req.trigger_by {
793 parameters.insert("triggerBy".into(), v.into());
794 }
795 if let Some(v) = req.tp_limit_price {
796 parameters.insert("tpLimitPrice".into(), v.to_string().into());
797 }
798 if let Some(v) = req.sl_limit_price {
799 parameters.insert("slLimitPrice".into(), v.to_string().into());
800 }
801 }
802 Action::Cancel(req, batch) => {
803 if !batch {
804 parameters.insert("category".into(), req.category.as_str().into());
805 }
806 parameters.insert("symbol".into(), req.symbol.into());
807 if let Some(v) = req.order_id {
808 parameters.insert("orderId".into(), v.into());
809 }
810 if let Some(v) = req.order_link_id {
811 parameters.insert("orderLinkId".into(), v.into());
812 }
813 if let Some(v) = req.order_filter {
814 parameters.insert("orderFilter".into(), v.into());
815 }
816 }
817 }
818 parameters
819 }
820}
821
822pub fn build_ws_orders<'a>(orders: RequestType) -> Value {
823 let mut order_array = Vec::new();
824 match orders {
825 RequestType::Create(req) => {
826 let action = Action::Order(req, false);
827 let order_object = Trader::build_orders(action); let built_order = json!(order_object);
829 order_array.push(built_order);
830 Value::Array(order_array)
831 }
832 RequestType::CreateBatch(req) => {
833 for v in req.requests {
834 let action = Action::Order(v, true);
835 let order_object = Trader::build_orders(action); let built_order = json!(order_object);
837 order_array.push(built_order);
838 }
839 Value::Array(order_array)
840 }
841 RequestType::Amend(req) => {
842 let action = Action::Amend(req, false);
843 let order_object = Trader::build_orders(action); let built_order = json!(order_object);
845 order_array.push(built_order);
846 Value::Array(order_array)
847 }
848 RequestType::AmendBatch(req) => {
849 for v in req.requests {
850 let action = Action::Amend(v, true);
851 let order_object = Trader::build_orders(action); let built_order = json!(order_object);
853 order_array.push(built_order);
854 }
855 Value::Array(order_array)
856 }
857 RequestType::Cancel(req) => {
858 let action = Action::Cancel(req, false);
859 let order_object = Trader::build_orders(action); let built_order = json!(order_object);
861 order_array.push(built_order);
862 Value::Array(order_array)
863 }
864 RequestType::CancelBatch(req) => {
865 for v in req.requests {
866 let action = Action::Cancel(v, true);
867 let order_object = Trader::build_orders(action); let built_order = json!(order_object);
869 order_array.push(built_order);
870 }
871 Value::Array(order_array)
872 }
873 }
874}