1use super::{Bybit, BybitAuth, error, parser};
6use ccxt_core::{
7 Error, ParseError, Result,
8 types::{
9 Amount, Balance, Market, OHLCV, OhlcvRequest, Order, OrderBook, OrderRequest, OrderSide,
10 OrderType, Price, Ticker, TimeInForce, Trade,
11 },
12};
13use reqwest::header::{HeaderMap, HeaderValue};
14use serde_json::Value;
15use std::{collections::HashMap, sync::Arc};
16use tracing::{debug, info, warn};
17
18impl Bybit {
19 #[deprecated(
30 since = "0.1.0",
31 note = "Use `signed_request()` builder instead which handles timestamps internally"
32 )]
33 #[allow(dead_code)]
34 fn get_timestamp() -> String {
35 chrono::Utc::now().timestamp_millis().to_string()
36 }
37
38 pub fn get_auth(&self) -> Result<BybitAuth> {
40 let config = &self.base().config;
41
42 let api_key = config
43 .api_key
44 .as_ref()
45 .ok_or_else(|| Error::authentication("API key is required"))?;
46 let secret = config
47 .secret
48 .as_ref()
49 .ok_or_else(|| Error::authentication("API secret is required"))?;
50
51 Ok(BybitAuth::new(
52 api_key.expose_secret().to_string(),
53 secret.expose_secret().to_string(),
54 ))
55 }
56
57 pub fn check_required_credentials(&self) -> Result<()> {
59 self.base().check_required_credentials()
60 }
61
62 fn build_api_path(endpoint: &str) -> String {
64 format!("/v5{}", endpoint)
65 }
66
67 fn get_category(&self) -> &str {
69 match self.options().account_type.as_str() {
70 "CONTRACT" | "LINEAR" => "linear",
71 "INVERSE" => "inverse",
72 "OPTION" => "option",
73 _ => "spot",
74 }
75 }
76
77 async fn public_request(
79 &self,
80 method: &str,
81 path: &str,
82 params: Option<&HashMap<String, String>>,
83 ) -> Result<Value> {
84 let urls = self.urls();
85 let mut url = format!("{}{}", urls.rest, path);
86
87 if let Some(p) = params {
88 if !p.is_empty() {
89 let query: Vec<String> = p
90 .iter()
91 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
92 .collect();
93 url = format!("{}?{}", url, query.join("&"));
94 }
95 }
96
97 debug!("Bybit public request: {} {}", method, url);
98
99 let response = match method.to_uppercase().as_str() {
100 "GET" => self.base().http_client.get(&url, None).await?,
101 "POST" => self.base().http_client.post(&url, None, None).await?,
102 _ => {
103 return Err(Error::invalid_request(format!(
104 "Unsupported HTTP method: {}",
105 method
106 )));
107 }
108 };
109
110 if error::is_error_response(&response) {
112 return Err(error::parse_error(&response));
113 }
114
115 Ok(response)
116 }
117
118 #[deprecated(
126 since = "0.1.0",
127 note = "Use `signed_request()` builder instead for cleaner, more maintainable code"
128 )]
129 #[allow(dead_code)]
130 #[allow(deprecated)]
131 async fn private_request(
132 &self,
133 method: &str,
134 path: &str,
135 params: Option<&HashMap<String, String>>,
136 body: Option<&Value>,
137 ) -> Result<Value> {
138 self.check_required_credentials()?;
139
140 let auth = self.get_auth()?;
141 let urls = self.urls();
142 let timestamp = Self::get_timestamp();
143 let recv_window = self.options().recv_window;
144
145 let query_string = if let Some(p) = params {
147 if p.is_empty() {
148 String::new()
149 } else {
150 let query: Vec<String> = p
151 .iter()
152 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
153 .collect();
154 query.join("&")
155 }
156 } else {
157 String::new()
158 };
159
160 let body_string = body
162 .map(|b| serde_json::to_string(b).unwrap_or_default())
163 .unwrap_or_default();
164
165 let sign_params = if method.to_uppercase() == "GET" {
167 &query_string
168 } else {
169 &body_string
170 };
171 let signature = auth.sign(×tamp, recv_window, sign_params);
172
173 let mut headers = HeaderMap::new();
175 auth.add_auth_headers(&mut headers, ×tamp, &signature, recv_window);
176 headers.insert("Content-Type", HeaderValue::from_static("application/json"));
177
178 let url = if query_string.is_empty() {
179 format!("{}{}", urls.rest, path)
180 } else {
181 format!("{}{}?{}", urls.rest, path, query_string)
182 };
183 debug!("Bybit private request: {} {}", method, url);
184
185 let response = match method.to_uppercase().as_str() {
186 "GET" => self.base().http_client.get(&url, Some(headers)).await?,
187 "POST" => {
188 let body_value = body.cloned();
189 self.base()
190 .http_client
191 .post(&url, Some(headers), body_value)
192 .await?
193 }
194 "DELETE" => {
195 self.base()
196 .http_client
197 .delete(&url, Some(headers), None)
198 .await?
199 }
200 _ => {
201 return Err(Error::invalid_request(format!(
202 "Unsupported HTTP method: {}",
203 method
204 )));
205 }
206 };
207
208 if error::is_error_response(&response) {
210 return Err(error::parse_error(&response));
211 }
212
213 Ok(response)
214 }
215
216 pub async fn fetch_markets(&self) -> Result<Arc<HashMap<String, Arc<Market>>>> {
242 let path = Self::build_api_path("/market/instruments-info");
243 let mut params = HashMap::new();
244 params.insert("category".to_string(), self.get_category().to_string());
245
246 let response = self.public_request("GET", &path, Some(¶ms)).await?;
247
248 let result = response
249 .get("result")
250 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
251
252 let list = result
253 .get("list")
254 .ok_or_else(|| Error::from(ParseError::missing_field("list")))?;
255
256 let instruments = list.as_array().ok_or_else(|| {
257 Error::from(ParseError::invalid_format(
258 "list",
259 "Expected array of instruments",
260 ))
261 })?;
262
263 let mut markets = Vec::new();
264 for instrument in instruments {
265 match parser::parse_market(instrument) {
266 Ok(market) => markets.push(market),
267 Err(e) => {
268 warn!(error = %e, "Failed to parse market");
269 }
270 }
271 }
272
273 let markets = self.base().set_markets(markets, None).await?;
275
276 info!("Loaded {} markets for Bybit", markets.len());
277 Ok(markets)
278 }
279
280 pub async fn load_markets(&self, reload: bool) -> Result<Arc<HashMap<String, Arc<Market>>>> {
292 let _loading_guard = self.base().market_loading_lock.lock().await;
295
296 {
298 let cache = self.base().market_cache.read().await;
299 if cache.loaded && !reload {
300 debug!(
301 "Returning cached markets for Bybit ({} markets)",
302 cache.markets.len()
303 );
304 return Ok(cache.markets.clone());
305 }
306 }
307
308 info!("Loading markets for Bybit (reload: {})", reload);
309 let _markets = self.fetch_markets().await?;
310
311 let cache = self.base().market_cache.read().await;
312 Ok(cache.markets.clone())
313 }
314
315 pub async fn fetch_ticker(&self, symbol: &str) -> Result<Ticker> {
325 let market = self.base().market(symbol).await?;
326
327 let path = Self::build_api_path("/market/tickers");
328 let mut params = HashMap::new();
329 params.insert("category".to_string(), self.get_category().to_string());
330 params.insert("symbol".to_string(), market.id.clone());
331
332 let response = self.public_request("GET", &path, Some(¶ms)).await?;
333
334 let result = response
335 .get("result")
336 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
337
338 let list = result
339 .get("list")
340 .ok_or_else(|| Error::from(ParseError::missing_field("list")))?;
341
342 let tickers = list.as_array().ok_or_else(|| {
343 Error::from(ParseError::invalid_format(
344 "list",
345 "Expected array of tickers",
346 ))
347 })?;
348
349 if tickers.is_empty() {
350 return Err(Error::bad_symbol(format!("No ticker data for {}", symbol)));
351 }
352
353 parser::parse_ticker(&tickers[0], Some(&market))
354 }
355
356 pub async fn fetch_tickers(&self, symbols: Option<Vec<String>>) -> Result<Vec<Ticker>> {
366 let cache = self.base().market_cache.read().await;
367 if !cache.loaded {
368 drop(cache);
369 return Err(Error::exchange(
370 "-1",
371 "Markets not loaded. Call load_markets() first.",
372 ));
373 }
374 drop(cache);
375
376 let path = Self::build_api_path("/market/tickers");
377 let mut params = HashMap::new();
378 params.insert("category".to_string(), self.get_category().to_string());
379
380 let response = self.public_request("GET", &path, Some(¶ms)).await?;
381
382 let result = response
383 .get("result")
384 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
385
386 let list = result
387 .get("list")
388 .ok_or_else(|| Error::from(ParseError::missing_field("list")))?;
389
390 let tickers_array = list.as_array().ok_or_else(|| {
391 Error::from(ParseError::invalid_format(
392 "list",
393 "Expected array of tickers",
394 ))
395 })?;
396
397 let mut tickers = Vec::new();
398 for ticker_data in tickers_array {
399 if let Some(symbol_id) = ticker_data["symbol"].as_str() {
400 let cache = self.base().market_cache.read().await;
401 if let Some(market) = cache.markets_by_id.get(symbol_id) {
402 let market_clone = market.clone();
403 drop(cache);
404
405 match parser::parse_ticker(ticker_data, Some(&market_clone)) {
406 Ok(ticker) => {
407 if let Some(ref syms) = symbols {
408 if syms.contains(&ticker.symbol) {
409 tickers.push(ticker);
410 }
411 } else {
412 tickers.push(ticker);
413 }
414 }
415 Err(e) => {
416 warn!(
417 error = %e,
418 symbol = %symbol_id,
419 "Failed to parse ticker"
420 );
421 }
422 }
423 } else {
424 drop(cache);
425 }
426 }
427 }
428
429 Ok(tickers)
430 }
431
432 pub async fn fetch_order_book(&self, symbol: &str, limit: Option<u32>) -> Result<OrderBook> {
447 let market = self.base().market(symbol).await?;
448
449 let path = Self::build_api_path("/market/orderbook");
450 let mut params = HashMap::new();
451 params.insert("category".to_string(), self.get_category().to_string());
452 params.insert("symbol".to_string(), market.id.clone());
453
454 let actual_limit = limit.map_or(25, |l| l.min(500));
457 params.insert("limit".to_string(), actual_limit.to_string());
458
459 let response = self.public_request("GET", &path, Some(¶ms)).await?;
460
461 let result = response
462 .get("result")
463 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
464
465 parser::parse_orderbook(result, market.symbol.clone())
466 }
467
468 pub async fn fetch_trades(&self, symbol: &str, limit: Option<u32>) -> Result<Vec<Trade>> {
479 let market = self.base().market(symbol).await?;
480
481 let path = Self::build_api_path("/market/recent-trade");
482 let mut params = HashMap::new();
483 params.insert("category".to_string(), self.get_category().to_string());
484 params.insert("symbol".to_string(), market.id.clone());
485
486 let actual_limit = limit.map_or(60, |l| l.min(1000));
488 params.insert("limit".to_string(), actual_limit.to_string());
489
490 let response = self.public_request("GET", &path, Some(¶ms)).await?;
491
492 let result = response
493 .get("result")
494 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
495
496 let list = result
497 .get("list")
498 .ok_or_else(|| Error::from(ParseError::missing_field("list")))?;
499
500 let trades_array = list.as_array().ok_or_else(|| {
501 Error::from(ParseError::invalid_format(
502 "list",
503 "Expected array of trades",
504 ))
505 })?;
506
507 let mut trades = Vec::new();
508 for trade_data in trades_array {
509 match parser::parse_trade(trade_data, Some(&market)) {
510 Ok(trade) => trades.push(trade),
511 Err(e) => {
512 warn!(error = %e, "Failed to parse trade");
513 }
514 }
515 }
516
517 trades.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
519
520 Ok(trades)
521 }
522
523 pub async fn fetch_ohlcv_v2(&self, request: OhlcvRequest) -> Result<Vec<OHLCV>> {
542 let market = self.base().market(&request.symbol).await?;
543
544 let timeframes = self.timeframes();
546 let bybit_timeframe = timeframes.get(&request.timeframe).ok_or_else(|| {
547 Error::invalid_request(format!("Unsupported timeframe: {}", request.timeframe))
548 })?;
549
550 let path = Self::build_api_path("/market/kline");
551 let mut params = HashMap::new();
552 params.insert("category".to_string(), self.get_category().to_string());
553 params.insert("symbol".to_string(), market.id.clone());
554 params.insert("interval".to_string(), bybit_timeframe.clone());
555
556 let actual_limit = request.limit.map_or(200, |l| l.min(1000));
558 params.insert("limit".to_string(), actual_limit.to_string());
559
560 if let Some(start_time) = request.since {
561 params.insert("start".to_string(), start_time.to_string());
562 }
563
564 if let Some(end_time) = request.until {
565 params.insert("end".to_string(), end_time.to_string());
566 }
567
568 let response = self.public_request("GET", &path, Some(¶ms)).await?;
569
570 let result = response
571 .get("result")
572 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
573
574 let list = result
575 .get("list")
576 .ok_or_else(|| Error::from(ParseError::missing_field("list")))?;
577
578 let candles_array = list.as_array().ok_or_else(|| {
579 Error::from(ParseError::invalid_format(
580 "list",
581 "Expected array of candles",
582 ))
583 })?;
584
585 let mut ohlcv = Vec::new();
586 for candle_data in candles_array {
587 match parser::parse_ohlcv(candle_data) {
588 Ok(candle) => ohlcv.push(candle),
589 Err(e) => {
590 warn!(error = %e, "Failed to parse OHLCV");
591 }
592 }
593 }
594
595 ohlcv.sort_by(|a, b| a.timestamp.cmp(&b.timestamp));
597
598 Ok(ohlcv)
599 }
600
601 #[deprecated(
619 since = "0.2.0",
620 note = "Use fetch_ohlcv_v2 with OhlcvRequest::builder() instead"
621 )]
622 pub async fn fetch_ohlcv(
623 &self,
624 symbol: &str,
625 timeframe: &str,
626 since: Option<i64>,
627 limit: Option<u32>,
628 ) -> Result<Vec<OHLCV>> {
629 let market = self.base().market(symbol).await?;
630
631 let timeframes = self.timeframes();
633 let bybit_timeframe = timeframes.get(timeframe).ok_or_else(|| {
634 Error::invalid_request(format!("Unsupported timeframe: {}", timeframe))
635 })?;
636
637 let path = Self::build_api_path("/market/kline");
638 let mut params = HashMap::new();
639 params.insert("category".to_string(), self.get_category().to_string());
640 params.insert("symbol".to_string(), market.id.clone());
641 params.insert("interval".to_string(), bybit_timeframe.clone());
642
643 let actual_limit = limit.map_or(200, |l| l.min(1000));
645 params.insert("limit".to_string(), actual_limit.to_string());
646
647 if let Some(start_time) = since {
648 params.insert("start".to_string(), start_time.to_string());
649 }
650
651 let response = self.public_request("GET", &path, Some(¶ms)).await?;
652
653 let result = response
654 .get("result")
655 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
656
657 let list = result
658 .get("list")
659 .ok_or_else(|| Error::from(ParseError::missing_field("list")))?;
660
661 let candles_array = list.as_array().ok_or_else(|| {
662 Error::from(ParseError::invalid_format(
663 "list",
664 "Expected array of candles",
665 ))
666 })?;
667
668 let mut ohlcv = Vec::new();
669 for candle_data in candles_array {
670 match parser::parse_ohlcv(candle_data) {
671 Ok(candle) => ohlcv.push(candle),
672 Err(e) => {
673 warn!(error = %e, "Failed to parse OHLCV");
674 }
675 }
676 }
677
678 ohlcv.sort_by(|a, b| a.timestamp.cmp(&b.timestamp));
680
681 Ok(ohlcv)
682 }
683
684 pub async fn fetch_balance(&self) -> Result<Balance> {
698 let path = Self::build_api_path("/account/wallet-balance");
699
700 let response = self
701 .signed_request(&path)
702 .param("accountType", &self.options().account_type)
703 .execute()
704 .await?;
705
706 let result = response
707 .get("result")
708 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
709
710 parser::parse_balance(result)
711 }
712
713 pub async fn fetch_my_trades(
725 &self,
726 symbol: &str,
727 since: Option<i64>,
728 limit: Option<u32>,
729 ) -> Result<Vec<Trade>> {
730 let market = self.base().market(symbol).await?;
731
732 let path = Self::build_api_path("/execution/list");
733
734 let actual_limit = limit.map_or(50, |l| l.min(100));
736
737 let response = self
738 .signed_request(&path)
739 .param("category", self.get_category())
740 .param("symbol", &market.id)
741 .param("limit", actual_limit)
742 .optional_param("startTime", since)
743 .execute()
744 .await?;
745
746 let result = response
747 .get("result")
748 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
749
750 let list = result
751 .get("list")
752 .ok_or_else(|| Error::from(ParseError::missing_field("list")))?;
753
754 let trades_array = list.as_array().ok_or_else(|| {
755 Error::from(ParseError::invalid_format(
756 "list",
757 "Expected array of trades",
758 ))
759 })?;
760
761 let mut trades = Vec::new();
762 for trade_data in trades_array {
763 match parser::parse_trade(trade_data, Some(&market)) {
764 Ok(trade) => trades.push(trade),
765 Err(e) => {
766 warn!(error = %e, "Failed to parse my trade");
767 }
768 }
769 }
770
771 Ok(trades)
772 }
773
774 pub async fn create_order_v2(&self, request: OrderRequest) -> Result<Order> {
794 use crate::bybit::signed_request::HttpMethod;
795
796 let market = self.base().market(&request.symbol).await?;
797
798 let path = Self::build_api_path("/order/create");
799
800 let mut map = serde_json::Map::new();
802 map.insert(
803 "category".to_string(),
804 serde_json::Value::String(self.get_category().to_string()),
805 );
806 map.insert(
807 "symbol".to_string(),
808 serde_json::Value::String(market.id.clone()),
809 );
810 map.insert(
811 "side".to_string(),
812 serde_json::Value::String(match request.side {
813 OrderSide::Buy => "Buy".to_string(),
814 OrderSide::Sell => "Sell".to_string(),
815 }),
816 );
817 map.insert(
818 "orderType".to_string(),
819 serde_json::Value::String(match request.order_type {
820 OrderType::Market
821 | OrderType::StopLoss
822 | OrderType::StopMarket
823 | OrderType::TakeProfit
824 | OrderType::TrailingStop => "Market".to_string(),
825 _ => "Limit".to_string(),
826 }),
827 );
828 map.insert(
829 "qty".to_string(),
830 serde_json::Value::String(request.amount.to_string()),
831 );
832
833 if let Some(p) = request.price {
835 if request.order_type == OrderType::Limit || request.order_type == OrderType::LimitMaker
836 {
837 map.insert(
838 "price".to_string(),
839 serde_json::Value::String(p.to_string()),
840 );
841 }
842 }
843
844 if let Some(tif) = request.time_in_force {
846 let tif_str = match tif {
847 TimeInForce::GTC => "GTC",
848 TimeInForce::IOC => "IOC",
849 TimeInForce::FOK => "FOK",
850 TimeInForce::PO => "PostOnly",
851 };
852 map.insert(
853 "timeInForce".to_string(),
854 serde_json::Value::String(tif_str.to_string()),
855 );
856 } else if request.order_type == OrderType::LimitMaker || request.post_only == Some(true) {
857 map.insert(
858 "timeInForce".to_string(),
859 serde_json::Value::String("PostOnly".to_string()),
860 );
861 }
862
863 if let Some(client_id) = request.client_order_id {
865 map.insert(
866 "orderLinkId".to_string(),
867 serde_json::Value::String(client_id),
868 );
869 }
870
871 if let Some(reduce_only) = request.reduce_only {
873 map.insert(
874 "reduceOnly".to_string(),
875 serde_json::Value::Bool(reduce_only),
876 );
877 }
878
879 if let Some(trigger) = request.trigger_price.or(request.stop_price) {
881 map.insert(
882 "triggerPrice".to_string(),
883 serde_json::Value::String(trigger.to_string()),
884 );
885 }
886
887 if let Some(tp) = request.take_profit_price {
889 map.insert(
890 "takeProfit".to_string(),
891 serde_json::Value::String(tp.to_string()),
892 );
893 }
894
895 if let Some(sl) = request.stop_loss_price {
897 map.insert(
898 "stopLoss".to_string(),
899 serde_json::Value::String(sl.to_string()),
900 );
901 }
902
903 if let Some(pos_side) = request.position_side {
905 map.insert(
906 "positionIdx".to_string(),
907 serde_json::Value::String(match pos_side.as_str() {
908 "LONG" => "1".to_string(),
909 "SHORT" => "2".to_string(),
910 _ => "0".to_string(), }),
912 );
913 }
914
915 let body = serde_json::Value::Object(map);
916
917 let response = self
918 .signed_request(&path)
919 .method(HttpMethod::Post)
920 .body(body)
921 .execute()
922 .await?;
923
924 let result = response
925 .get("result")
926 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
927
928 parser::parse_order(result, Some(&market))
929 }
930
931 #[deprecated(
950 since = "0.2.0",
951 note = "Use create_order_v2 with OrderRequest::builder() instead"
952 )]
953 pub async fn create_order(
954 &self,
955 symbol: &str,
956 order_type: OrderType,
957 side: OrderSide,
958 amount: Amount,
959 price: Option<Price>,
960 ) -> Result<Order> {
961 use crate::bybit::signed_request::HttpMethod;
962
963 let market = self.base().market(symbol).await?;
964
965 let path = Self::build_api_path("/order/create");
966
967 let mut map = serde_json::Map::new();
969 map.insert(
970 "category".to_string(),
971 serde_json::Value::String(self.get_category().to_string()),
972 );
973 map.insert(
974 "symbol".to_string(),
975 serde_json::Value::String(market.id.clone()),
976 );
977 map.insert(
978 "side".to_string(),
979 serde_json::Value::String(match side {
980 OrderSide::Buy => "Buy".to_string(),
981 OrderSide::Sell => "Sell".to_string(),
982 }),
983 );
984 map.insert(
985 "orderType".to_string(),
986 serde_json::Value::String(match order_type {
987 OrderType::Market => "Market".to_string(),
988 _ => "Limit".to_string(),
989 }),
990 );
991 map.insert(
992 "qty".to_string(),
993 serde_json::Value::String(amount.to_string()),
994 );
995
996 if let Some(p) = price {
998 if order_type == OrderType::Limit || order_type == OrderType::LimitMaker {
999 map.insert(
1000 "price".to_string(),
1001 serde_json::Value::String(p.to_string()),
1002 );
1003 }
1004 }
1005
1006 if order_type == OrderType::LimitMaker {
1008 map.insert(
1009 "timeInForce".to_string(),
1010 serde_json::Value::String("PostOnly".to_string()),
1011 );
1012 }
1013
1014 let body = serde_json::Value::Object(map);
1015
1016 let response = self
1017 .signed_request(&path)
1018 .method(HttpMethod::Post)
1019 .body(body)
1020 .execute()
1021 .await?;
1022
1023 let result = response
1024 .get("result")
1025 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
1026
1027 parser::parse_order(result, Some(&market))
1028 }
1029
1030 pub async fn cancel_order(&self, id: &str, symbol: &str) -> Result<Order> {
1041 use crate::bybit::signed_request::HttpMethod;
1042
1043 let market = self.base().market(symbol).await?;
1044
1045 let path = Self::build_api_path("/order/cancel");
1046
1047 let mut map = serde_json::Map::new();
1048 map.insert(
1049 "category".to_string(),
1050 serde_json::Value::String(self.get_category().to_string()),
1051 );
1052 map.insert(
1053 "symbol".to_string(),
1054 serde_json::Value::String(market.id.clone()),
1055 );
1056 map.insert(
1057 "orderId".to_string(),
1058 serde_json::Value::String(id.to_string()),
1059 );
1060 let body = serde_json::Value::Object(map);
1061
1062 let response = self
1063 .signed_request(&path)
1064 .method(HttpMethod::Post)
1065 .body(body)
1066 .execute()
1067 .await?;
1068
1069 let result = response
1070 .get("result")
1071 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
1072
1073 parser::parse_order(result, Some(&market))
1074 }
1075
1076 pub async fn fetch_order(&self, id: &str, symbol: &str) -> Result<Order> {
1087 let market = self.base().market(symbol).await?;
1088
1089 let path = Self::build_api_path("/order/realtime");
1090
1091 let response = self
1092 .signed_request(&path)
1093 .param("category", self.get_category())
1094 .param("symbol", &market.id)
1095 .param("orderId", id)
1096 .execute()
1097 .await?;
1098
1099 let result = response
1100 .get("result")
1101 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
1102
1103 let list = result
1104 .get("list")
1105 .ok_or_else(|| Error::from(ParseError::missing_field("list")))?;
1106
1107 let orders = list.as_array().ok_or_else(|| {
1108 Error::from(ParseError::invalid_format(
1109 "list",
1110 "Expected array of orders",
1111 ))
1112 })?;
1113
1114 if orders.is_empty() {
1115 return Err(Error::exchange("110008", "Order not found"));
1116 }
1117
1118 parser::parse_order(&orders[0], Some(&market))
1119 }
1120
1121 pub async fn fetch_open_orders(
1133 &self,
1134 symbol: Option<&str>,
1135 since: Option<i64>,
1136 limit: Option<u32>,
1137 ) -> Result<Vec<Order>> {
1138 let path = Self::build_api_path("/order/realtime");
1139
1140 let actual_limit = limit.map_or(50, |l| l.min(50));
1142
1143 let market = if let Some(sym) = symbol {
1144 Some(self.base().market(sym).await?)
1145 } else {
1146 None
1147 };
1148
1149 let mut builder = self
1150 .signed_request(&path)
1151 .param("category", self.get_category())
1152 .param("limit", actual_limit)
1153 .optional_param("startTime", since);
1154
1155 if let Some(ref m) = market {
1156 builder = builder.param("symbol", &m.id);
1157 }
1158
1159 let response = builder.execute().await?;
1160
1161 let result = response
1162 .get("result")
1163 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
1164
1165 let list = result
1166 .get("list")
1167 .ok_or_else(|| Error::from(ParseError::missing_field("list")))?;
1168
1169 let orders_array = list.as_array().ok_or_else(|| {
1170 Error::from(ParseError::invalid_format(
1171 "list",
1172 "Expected array of orders",
1173 ))
1174 })?;
1175
1176 let mut orders = Vec::new();
1177 for order_data in orders_array {
1178 match parser::parse_order(order_data, market.as_deref()) {
1179 Ok(order) => orders.push(order),
1180 Err(e) => {
1181 warn!(error = %e, "Failed to parse open order");
1182 }
1183 }
1184 }
1185
1186 Ok(orders)
1187 }
1188
1189 pub async fn fetch_closed_orders(
1201 &self,
1202 symbol: Option<&str>,
1203 since: Option<i64>,
1204 limit: Option<u32>,
1205 ) -> Result<Vec<Order>> {
1206 let path = Self::build_api_path("/order/history");
1207
1208 let actual_limit = limit.map_or(50, |l| l.min(50));
1210
1211 let market = if let Some(sym) = symbol {
1212 Some(self.base().market(sym).await?)
1213 } else {
1214 None
1215 };
1216
1217 let mut builder = self
1218 .signed_request(&path)
1219 .param("category", self.get_category())
1220 .param("limit", actual_limit)
1221 .optional_param("startTime", since);
1222
1223 if let Some(ref m) = market {
1224 builder = builder.param("symbol", &m.id);
1225 }
1226
1227 let response = builder.execute().await?;
1228
1229 let result = response
1230 .get("result")
1231 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
1232
1233 let list = result
1234 .get("list")
1235 .ok_or_else(|| Error::from(ParseError::missing_field("list")))?;
1236
1237 let orders_array = list.as_array().ok_or_else(|| {
1238 Error::from(ParseError::invalid_format(
1239 "list",
1240 "Expected array of orders",
1241 ))
1242 })?;
1243
1244 let mut orders = Vec::new();
1245 for order_data in orders_array {
1246 match parser::parse_order(order_data, market.as_deref()) {
1247 Ok(order) => orders.push(order),
1248 Err(e) => {
1249 warn!(error = %e, "Failed to parse closed order");
1250 }
1251 }
1252 }
1253
1254 Ok(orders)
1255 }
1256}
1257
1258#[cfg(test)]
1259mod tests {
1260 use super::*;
1261
1262 #[test]
1263 fn test_build_api_path() {
1264 let path = Bybit::build_api_path("/market/instruments-info");
1265 assert_eq!(path, "/v5/market/instruments-info");
1266 }
1267
1268 #[test]
1269 fn test_get_category_spot() {
1270 let bybit = Bybit::builder().build().unwrap();
1271 let category = bybit.get_category();
1272 assert_eq!(category, "spot");
1273 }
1274
1275 #[test]
1276 fn test_get_category_linear() {
1277 let bybit = Bybit::builder().account_type("LINEAR").build().unwrap();
1278 let category = bybit.get_category();
1279 assert_eq!(category, "linear");
1280 }
1281
1282 #[test]
1283 fn test_get_timestamp() {
1284 let _bybit = Bybit::builder().build().unwrap();
1285 let ts = Bybit::get_timestamp();
1286
1287 assert!(!ts.is_empty());
1289 let parsed: i64 = ts.parse().unwrap();
1290 assert!(parsed > 0);
1291 }
1292}