1use super::{Bybit, BybitAuth, error, parser};
6use ccxt_core::{
7 Error, ParseError, Result,
8 types::{Balance, Market, OHLCV, Order, OrderBook, OrderSide, OrderType, Ticker, Trade},
9};
10use reqwest::header::{HeaderMap, HeaderValue};
11use serde_json::Value;
12use std::collections::HashMap;
13use tracing::{debug, info, warn};
14
15impl Bybit {
16 fn get_timestamp(&self) -> String {
22 chrono::Utc::now().timestamp_millis().to_string()
23 }
24
25 fn get_auth(&self) -> Result<BybitAuth> {
27 let config = &self.base().config;
28
29 let api_key = config
30 .api_key
31 .as_ref()
32 .ok_or_else(|| Error::authentication("API key is required"))?;
33 let secret = config
34 .secret
35 .as_ref()
36 .ok_or_else(|| Error::authentication("API secret is required"))?;
37
38 Ok(BybitAuth::new(api_key.clone(), secret.clone()))
39 }
40
41 pub fn check_required_credentials(&self) -> Result<()> {
43 self.base().check_required_credentials()
44 }
45
46 fn build_api_path(&self, endpoint: &str) -> String {
48 format!("/v5{}", endpoint)
49 }
50
51 fn get_category(&self) -> &str {
53 match self.options().account_type.as_str() {
54 "SPOT" => "spot",
55 "CONTRACT" | "LINEAR" => "linear",
56 "INVERSE" => "inverse",
57 "OPTION" => "option",
58 _ => "spot",
59 }
60 }
61
62 async fn public_request(
64 &self,
65 method: &str,
66 path: &str,
67 params: Option<&HashMap<String, String>>,
68 ) -> Result<Value> {
69 let urls = self.urls();
70 let mut url = format!("{}{}", urls.rest, path);
71
72 if let Some(p) = params {
73 if !p.is_empty() {
74 let query: Vec<String> = p
75 .iter()
76 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
77 .collect();
78 url = format!("{}?{}", url, query.join("&"));
79 }
80 }
81
82 debug!("Bybit public request: {} {}", method, url);
83
84 let response = match method.to_uppercase().as_str() {
85 "GET" => self.base().http_client.get(&url, None).await?,
86 "POST" => self.base().http_client.post(&url, None, None).await?,
87 _ => {
88 return Err(Error::invalid_request(format!(
89 "Unsupported HTTP method: {}",
90 method
91 )));
92 }
93 };
94
95 if error::is_error_response(&response) {
97 return Err(error::parse_error(&response));
98 }
99
100 Ok(response)
101 }
102
103 async fn private_request(
105 &self,
106 method: &str,
107 path: &str,
108 params: Option<&HashMap<String, String>>,
109 body: Option<&Value>,
110 ) -> Result<Value> {
111 self.check_required_credentials()?;
112
113 let auth = self.get_auth()?;
114 let urls = self.urls();
115 let timestamp = self.get_timestamp();
116 let recv_window = self.options().recv_window;
117
118 let query_string = if let Some(p) = params {
120 if !p.is_empty() {
121 let query: Vec<String> = p
122 .iter()
123 .map(|(k, v)| format!("{}={}", k, urlencoding::encode(v)))
124 .collect();
125 query.join("&")
126 } else {
127 String::new()
128 }
129 } else {
130 String::new()
131 };
132
133 let body_string = body
135 .map(|b| serde_json::to_string(b).unwrap_or_default())
136 .unwrap_or_default();
137
138 let sign_params = if method.to_uppercase() == "GET" {
140 &query_string
141 } else {
142 &body_string
143 };
144 let signature = auth.sign(×tamp, recv_window, sign_params);
145
146 let mut headers = HeaderMap::new();
148 auth.add_auth_headers(&mut headers, ×tamp, &signature, recv_window);
149 headers.insert("Content-Type", HeaderValue::from_static("application/json"));
150
151 let url = if query_string.is_empty() {
152 format!("{}{}", urls.rest, path)
153 } else {
154 format!("{}{}?{}", urls.rest, path, query_string)
155 };
156 debug!("Bybit private request: {} {}", method, url);
157
158 let response = match method.to_uppercase().as_str() {
159 "GET" => self.base().http_client.get(&url, Some(headers)).await?,
160 "POST" => {
161 let body_value = body.cloned();
162 self.base()
163 .http_client
164 .post(&url, Some(headers), body_value)
165 .await?
166 }
167 "DELETE" => {
168 self.base()
169 .http_client
170 .delete(&url, Some(headers), None)
171 .await?
172 }
173 _ => {
174 return Err(Error::invalid_request(format!(
175 "Unsupported HTTP method: {}",
176 method
177 )));
178 }
179 };
180
181 if error::is_error_response(&response) {
183 return Err(error::parse_error(&response));
184 }
185
186 Ok(response)
187 }
188
189 pub async fn fetch_markets(&self) -> Result<Vec<Market>> {
215 let path = self.build_api_path("/market/instruments-info");
216 let mut params = HashMap::new();
217 params.insert("category".to_string(), self.get_category().to_string());
218
219 let response = self.public_request("GET", &path, Some(¶ms)).await?;
220
221 let result = response
222 .get("result")
223 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
224
225 let list = result
226 .get("list")
227 .ok_or_else(|| Error::from(ParseError::missing_field("list")))?;
228
229 let instruments = list.as_array().ok_or_else(|| {
230 Error::from(ParseError::invalid_format(
231 "list",
232 "Expected array of instruments",
233 ))
234 })?;
235
236 let mut markets = Vec::new();
237 for instrument in instruments {
238 match parser::parse_market(instrument) {
239 Ok(market) => markets.push(market),
240 Err(e) => {
241 warn!(error = %e, "Failed to parse market");
242 }
243 }
244 }
245
246 let markets = self.base().set_markets(markets, None).await?;
248
249 info!("Loaded {} markets for Bybit", markets.len());
250 Ok(markets)
251 }
252
253 pub async fn load_markets(&self, reload: bool) -> Result<HashMap<String, Market>> {
265 {
266 let cache = self.base().market_cache.read().await;
267 if cache.loaded && !reload {
268 debug!(
269 "Returning cached markets for Bybit ({} markets)",
270 cache.markets.len()
271 );
272 return Ok(cache.markets.clone());
273 }
274 }
275
276 info!("Loading markets for Bybit (reload: {})", reload);
277 let _markets = self.fetch_markets().await?;
278
279 let cache = self.base().market_cache.read().await;
280 Ok(cache.markets.clone())
281 }
282
283 pub async fn fetch_ticker(&self, symbol: &str) -> Result<Ticker> {
293 let market = self.base().market(symbol).await?;
294
295 let path = self.build_api_path("/market/tickers");
296 let mut params = HashMap::new();
297 params.insert("category".to_string(), self.get_category().to_string());
298 params.insert("symbol".to_string(), market.id.clone());
299
300 let response = self.public_request("GET", &path, Some(¶ms)).await?;
301
302 let result = response
303 .get("result")
304 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
305
306 let list = result
307 .get("list")
308 .ok_or_else(|| Error::from(ParseError::missing_field("list")))?;
309
310 let tickers = list.as_array().ok_or_else(|| {
311 Error::from(ParseError::invalid_format(
312 "list",
313 "Expected array of tickers",
314 ))
315 })?;
316
317 if tickers.is_empty() {
318 return Err(Error::bad_symbol(format!("No ticker data for {}", symbol)));
319 }
320
321 parser::parse_ticker(&tickers[0], Some(&market))
322 }
323
324 pub async fn fetch_tickers(&self, symbols: Option<Vec<String>>) -> Result<Vec<Ticker>> {
334 let cache = self.base().market_cache.read().await;
335 if !cache.loaded {
336 drop(cache);
337 return Err(Error::exchange(
338 "-1",
339 "Markets not loaded. Call load_markets() first.",
340 ));
341 }
342 drop(cache);
343
344 let path = self.build_api_path("/market/tickers");
345 let mut params = HashMap::new();
346 params.insert("category".to_string(), self.get_category().to_string());
347
348 let response = self.public_request("GET", &path, Some(¶ms)).await?;
349
350 let result = response
351 .get("result")
352 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
353
354 let list = result
355 .get("list")
356 .ok_or_else(|| Error::from(ParseError::missing_field("list")))?;
357
358 let tickers_array = list.as_array().ok_or_else(|| {
359 Error::from(ParseError::invalid_format(
360 "list",
361 "Expected array of tickers",
362 ))
363 })?;
364
365 let mut tickers = Vec::new();
366 for ticker_data in tickers_array {
367 if let Some(symbol_id) = ticker_data["symbol"].as_str() {
368 let cache = self.base().market_cache.read().await;
369 if let Some(market) = cache.markets_by_id.get(symbol_id) {
370 let market_clone = market.clone();
371 drop(cache);
372
373 match parser::parse_ticker(ticker_data, Some(&market_clone)) {
374 Ok(ticker) => {
375 if let Some(ref syms) = symbols {
376 if syms.contains(&ticker.symbol) {
377 tickers.push(ticker);
378 }
379 } else {
380 tickers.push(ticker);
381 }
382 }
383 Err(e) => {
384 warn!(
385 error = %e,
386 symbol = %symbol_id,
387 "Failed to parse ticker"
388 );
389 }
390 }
391 } else {
392 drop(cache);
393 }
394 }
395 }
396
397 Ok(tickers)
398 }
399
400 pub async fn fetch_order_book(&self, symbol: &str, limit: Option<u32>) -> Result<OrderBook> {
415 let market = self.base().market(symbol).await?;
416
417 let path = self.build_api_path("/market/orderbook");
418 let mut params = HashMap::new();
419 params.insert("category".to_string(), self.get_category().to_string());
420 params.insert("symbol".to_string(), market.id.clone());
421
422 let actual_limit = limit.map(|l| l.min(500)).unwrap_or(25);
425 params.insert("limit".to_string(), actual_limit.to_string());
426
427 let response = self.public_request("GET", &path, Some(¶ms)).await?;
428
429 let result = response
430 .get("result")
431 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
432
433 parser::parse_orderbook(result, market.symbol.clone())
434 }
435
436 pub async fn fetch_trades(&self, symbol: &str, limit: Option<u32>) -> Result<Vec<Trade>> {
447 let market = self.base().market(symbol).await?;
448
449 let path = self.build_api_path("/market/recent-trade");
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(|l| l.min(1000)).unwrap_or(60);
456 params.insert("limit".to_string(), actual_limit.to_string());
457
458 let response = self.public_request("GET", &path, Some(¶ms)).await?;
459
460 let result = response
461 .get("result")
462 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
463
464 let list = result
465 .get("list")
466 .ok_or_else(|| Error::from(ParseError::missing_field("list")))?;
467
468 let trades_array = list.as_array().ok_or_else(|| {
469 Error::from(ParseError::invalid_format(
470 "list",
471 "Expected array of trades",
472 ))
473 })?;
474
475 let mut trades = Vec::new();
476 for trade_data in trades_array {
477 match parser::parse_trade(trade_data, Some(&market)) {
478 Ok(trade) => trades.push(trade),
479 Err(e) => {
480 warn!(error = %e, "Failed to parse trade");
481 }
482 }
483 }
484
485 trades.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
487
488 Ok(trades)
489 }
490
491 pub async fn fetch_ohlcv(
504 &self,
505 symbol: &str,
506 timeframe: &str,
507 since: Option<i64>,
508 limit: Option<u32>,
509 ) -> Result<Vec<OHLCV>> {
510 let market = self.base().market(symbol).await?;
511
512 let timeframes = self.timeframes();
514 let bybit_timeframe = timeframes.get(timeframe).ok_or_else(|| {
515 Error::invalid_request(format!("Unsupported timeframe: {}", timeframe))
516 })?;
517
518 let path = self.build_api_path("/market/kline");
519 let mut params = HashMap::new();
520 params.insert("category".to_string(), self.get_category().to_string());
521 params.insert("symbol".to_string(), market.id.clone());
522 params.insert("interval".to_string(), bybit_timeframe.clone());
523
524 let actual_limit = limit.map(|l| l.min(1000)).unwrap_or(200);
526 params.insert("limit".to_string(), actual_limit.to_string());
527
528 if let Some(start_time) = since {
529 params.insert("start".to_string(), start_time.to_string());
530 }
531
532 let response = self.public_request("GET", &path, Some(¶ms)).await?;
533
534 let result = response
535 .get("result")
536 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
537
538 let list = result
539 .get("list")
540 .ok_or_else(|| Error::from(ParseError::missing_field("list")))?;
541
542 let candles_array = list.as_array().ok_or_else(|| {
543 Error::from(ParseError::invalid_format(
544 "list",
545 "Expected array of candles",
546 ))
547 })?;
548
549 let mut ohlcv = Vec::new();
550 for candle_data in candles_array {
551 match parser::parse_ohlcv(candle_data) {
552 Ok(candle) => ohlcv.push(candle),
553 Err(e) => {
554 warn!(error = %e, "Failed to parse OHLCV");
555 }
556 }
557 }
558
559 ohlcv.sort_by(|a, b| a.timestamp.cmp(&b.timestamp));
561
562 Ok(ohlcv)
563 }
564
565 pub async fn fetch_balance(&self) -> Result<Balance> {
579 let path = self.build_api_path("/account/wallet-balance");
580 let mut params = HashMap::new();
581 params.insert(
582 "accountType".to_string(),
583 self.options().account_type.clone(),
584 );
585
586 let response = self
587 .private_request("GET", &path, Some(¶ms), None)
588 .await?;
589
590 let result = response
591 .get("result")
592 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
593
594 parser::parse_balance(result)
595 }
596
597 pub async fn fetch_my_trades(
609 &self,
610 symbol: &str,
611 since: Option<i64>,
612 limit: Option<u32>,
613 ) -> Result<Vec<Trade>> {
614 let market = self.base().market(symbol).await?;
615
616 let path = self.build_api_path("/execution/list");
617 let mut params = HashMap::new();
618 params.insert("category".to_string(), self.get_category().to_string());
619 params.insert("symbol".to_string(), market.id.clone());
620
621 let actual_limit = limit.map(|l| l.min(100)).unwrap_or(50);
623 params.insert("limit".to_string(), actual_limit.to_string());
624
625 if let Some(start_time) = since {
626 params.insert("startTime".to_string(), start_time.to_string());
627 }
628
629 let response = self
630 .private_request("GET", &path, Some(¶ms), None)
631 .await?;
632
633 let result = response
634 .get("result")
635 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
636
637 let list = result
638 .get("list")
639 .ok_or_else(|| Error::from(ParseError::missing_field("list")))?;
640
641 let trades_array = list.as_array().ok_or_else(|| {
642 Error::from(ParseError::invalid_format(
643 "list",
644 "Expected array of trades",
645 ))
646 })?;
647
648 let mut trades = Vec::new();
649 for trade_data in trades_array {
650 match parser::parse_trade(trade_data, Some(&market)) {
651 Ok(trade) => trades.push(trade),
652 Err(e) => {
653 warn!(error = %e, "Failed to parse my trade");
654 }
655 }
656 }
657
658 Ok(trades)
659 }
660
661 pub async fn create_order(
679 &self,
680 symbol: &str,
681 order_type: OrderType,
682 side: OrderSide,
683 amount: f64,
684 price: Option<f64>,
685 ) -> Result<Order> {
686 let market = self.base().market(symbol).await?;
687
688 let path = self.build_api_path("/order/create");
689
690 let mut map = serde_json::Map::new();
692 map.insert(
693 "category".to_string(),
694 serde_json::Value::String(self.get_category().to_string()),
695 );
696 map.insert(
697 "symbol".to_string(),
698 serde_json::Value::String(market.id.clone()),
699 );
700 map.insert(
701 "side".to_string(),
702 serde_json::Value::String(match side {
703 OrderSide::Buy => "Buy".to_string(),
704 OrderSide::Sell => "Sell".to_string(),
705 }),
706 );
707 map.insert(
708 "orderType".to_string(),
709 serde_json::Value::String(match order_type {
710 OrderType::Market => "Market".to_string(),
711 OrderType::Limit => "Limit".to_string(),
712 OrderType::LimitMaker => "Limit".to_string(),
713 _ => "Limit".to_string(),
714 }),
715 );
716 map.insert(
717 "qty".to_string(),
718 serde_json::Value::String(amount.to_string()),
719 );
720
721 if let Some(p) = price {
723 if order_type == OrderType::Limit || order_type == OrderType::LimitMaker {
724 map.insert(
725 "price".to_string(),
726 serde_json::Value::String(p.to_string()),
727 );
728 }
729 }
730
731 if order_type == OrderType::LimitMaker {
733 map.insert(
734 "timeInForce".to_string(),
735 serde_json::Value::String("PostOnly".to_string()),
736 );
737 }
738
739 let body = serde_json::Value::Object(map);
740
741 let response = self
742 .private_request("POST", &path, None, Some(&body))
743 .await?;
744
745 let result = response
746 .get("result")
747 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
748
749 parser::parse_order(result, Some(&market))
750 }
751
752 pub async fn cancel_order(&self, id: &str, symbol: &str) -> Result<Order> {
763 let market = self.base().market(symbol).await?;
764
765 let path = self.build_api_path("/order/cancel");
766
767 let mut map = serde_json::Map::new();
768 map.insert(
769 "category".to_string(),
770 serde_json::Value::String(self.get_category().to_string()),
771 );
772 map.insert(
773 "symbol".to_string(),
774 serde_json::Value::String(market.id.clone()),
775 );
776 map.insert(
777 "orderId".to_string(),
778 serde_json::Value::String(id.to_string()),
779 );
780 let body = serde_json::Value::Object(map);
781
782 let response = self
783 .private_request("POST", &path, None, Some(&body))
784 .await?;
785
786 let result = response
787 .get("result")
788 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
789
790 parser::parse_order(result, Some(&market))
791 }
792
793 pub async fn fetch_order(&self, id: &str, symbol: &str) -> Result<Order> {
804 let market = self.base().market(symbol).await?;
805
806 let path = self.build_api_path("/order/realtime");
807 let mut params = HashMap::new();
808 params.insert("category".to_string(), self.get_category().to_string());
809 params.insert("symbol".to_string(), market.id.clone());
810 params.insert("orderId".to_string(), id.to_string());
811
812 let response = self
813 .private_request("GET", &path, Some(¶ms), None)
814 .await?;
815
816 let result = response
817 .get("result")
818 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
819
820 let list = result
821 .get("list")
822 .ok_or_else(|| Error::from(ParseError::missing_field("list")))?;
823
824 let orders = list.as_array().ok_or_else(|| {
825 Error::from(ParseError::invalid_format(
826 "list",
827 "Expected array of orders",
828 ))
829 })?;
830
831 if orders.is_empty() {
832 return Err(Error::exchange("110008", "Order not found"));
833 }
834
835 parser::parse_order(&orders[0], Some(&market))
836 }
837
838 pub async fn fetch_open_orders(
850 &self,
851 symbol: Option<&str>,
852 since: Option<i64>,
853 limit: Option<u32>,
854 ) -> Result<Vec<Order>> {
855 let path = self.build_api_path("/order/realtime");
856 let mut params = HashMap::new();
857 params.insert("category".to_string(), self.get_category().to_string());
858
859 let market = if let Some(sym) = symbol {
860 let m = self.base().market(sym).await?;
861 params.insert("symbol".to_string(), m.id.clone());
862 Some(m)
863 } else {
864 None
865 };
866
867 let actual_limit = limit.map(|l| l.min(50)).unwrap_or(50);
869 params.insert("limit".to_string(), actual_limit.to_string());
870
871 if let Some(start_time) = since {
872 params.insert("startTime".to_string(), start_time.to_string());
873 }
874
875 let response = self
876 .private_request("GET", &path, Some(¶ms), None)
877 .await?;
878
879 let result = response
880 .get("result")
881 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
882
883 let list = result
884 .get("list")
885 .ok_or_else(|| Error::from(ParseError::missing_field("list")))?;
886
887 let orders_array = list.as_array().ok_or_else(|| {
888 Error::from(ParseError::invalid_format(
889 "list",
890 "Expected array of orders",
891 ))
892 })?;
893
894 let mut orders = Vec::new();
895 for order_data in orders_array {
896 match parser::parse_order(order_data, market.as_ref()) {
897 Ok(order) => orders.push(order),
898 Err(e) => {
899 warn!(error = %e, "Failed to parse open order");
900 }
901 }
902 }
903
904 Ok(orders)
905 }
906
907 pub async fn fetch_closed_orders(
919 &self,
920 symbol: Option<&str>,
921 since: Option<i64>,
922 limit: Option<u32>,
923 ) -> Result<Vec<Order>> {
924 let path = self.build_api_path("/order/history");
925 let mut params = HashMap::new();
926 params.insert("category".to_string(), self.get_category().to_string());
927
928 let market = if let Some(sym) = symbol {
929 let m = self.base().market(sym).await?;
930 params.insert("symbol".to_string(), m.id.clone());
931 Some(m)
932 } else {
933 None
934 };
935
936 let actual_limit = limit.map(|l| l.min(50)).unwrap_or(50);
938 params.insert("limit".to_string(), actual_limit.to_string());
939
940 if let Some(start_time) = since {
941 params.insert("startTime".to_string(), start_time.to_string());
942 }
943
944 let response = self
945 .private_request("GET", &path, Some(¶ms), None)
946 .await?;
947
948 let result = response
949 .get("result")
950 .ok_or_else(|| Error::from(ParseError::missing_field("result")))?;
951
952 let list = result
953 .get("list")
954 .ok_or_else(|| Error::from(ParseError::missing_field("list")))?;
955
956 let orders_array = list.as_array().ok_or_else(|| {
957 Error::from(ParseError::invalid_format(
958 "list",
959 "Expected array of orders",
960 ))
961 })?;
962
963 let mut orders = Vec::new();
964 for order_data in orders_array {
965 match parser::parse_order(order_data, market.as_ref()) {
966 Ok(order) => orders.push(order),
967 Err(e) => {
968 warn!(error = %e, "Failed to parse closed order");
969 }
970 }
971 }
972
973 Ok(orders)
974 }
975}
976
977#[cfg(test)]
978mod tests {
979 use super::*;
980
981 #[test]
982 fn test_build_api_path() {
983 let bybit = Bybit::builder().build().unwrap();
984 let path = bybit.build_api_path("/market/instruments-info");
985 assert_eq!(path, "/v5/market/instruments-info");
986 }
987
988 #[test]
989 fn test_get_category_spot() {
990 let bybit = Bybit::builder().build().unwrap();
991 let category = bybit.get_category();
992 assert_eq!(category, "spot");
993 }
994
995 #[test]
996 fn test_get_category_linear() {
997 let bybit = Bybit::builder().account_type("LINEAR").build().unwrap();
998 let category = bybit.get_category();
999 assert_eq!(category, "linear");
1000 }
1001
1002 #[test]
1003 fn test_get_timestamp() {
1004 let bybit = Bybit::builder().build().unwrap();
1005 let ts = bybit.get_timestamp();
1006
1007 assert!(!ts.is_empty());
1009 let parsed: i64 = ts.parse().unwrap();
1010 assert!(parsed > 0);
1011 }
1012}