bybit/market.rs
1use crate::prelude::*;
2
3#[derive(Clone)]
4pub struct MarketData {
5 pub client: Client,
6 pub recv_window: u16,
7}
8
9/// Market Data endpoints
10
11impl MarketData {
12 /// Retrieves historical price klines.
13 ///
14 /// This method fetches historical klines (candlestick data) for a specified category, trading pair,
15
16 /// and interval. It supports additional parameters to define a date range and to limit the response size.
17 ///
18 /// Suitable for USDT perpetual, USDC contract, and Inverse contract categories.
19 ///
20 /// # Arguments
21 ///
22 /// * `category` - The market category for which to retrieve klines (optional).
23 /// * `symbol` - The trading pair or symbol for which to retrieve klines.
24 /// * `interval` - The time interval between klines.
25 /// * `start` - The start date for the kline data retrieval in `DDMMYY` format (optional).
26 /// * `end` - The end date for the kline data retrieval in `DDMMYY` format (optional).
27 /// * `limit` - The maximum number of klines to return (optional).
28 ///
29 /// # Returns
30 ///
31 /// A `Result<Vec<KlineData>, Error>` containing the requested kline data if successful, or an error otherwise.
32 /// Retrieves historical kline (candlestick) data for a trading pair.
33 ///
34 /// Kline data represents price movements over fixed time intervals and is essential
35 /// for technical analysis in trading strategies. This endpoint supports spot,
36 /// linear (USDT-margined), and inverse (coin-margined) perpetual contracts.
37 ///
38 /// # Arguments
39 ///
40 /// * `req` - A `KlineRequest` containing the query parameters
41 ///
42 /// # Returns
43 ///
44 /// Returns a `Result` containing `KlineResponse` if successful, or `BybitError` if an error occurs.
45 ///
46 /// # Examples
47 ///
48 /// ```no_run
49 /// use bybit::prelude::*;
50 ///
51 /// #[tokio::main]
52 /// async fn main() -> Result<(), BybitError> {
53 /// let market = MarketData::new(None, None);
54 ///
55 /// // Using builder pattern
56 /// let request = KlineRequest::builder()
57 /// .category(Category::Linear)
58 /// .symbol("BTCUSDT")
59 /// .interval(Interval::H1)
60 /// .limit(100)
61 /// .build()
62 /// .unwrap();
63 ///
64 /// let response = market.get_klines(request).await?;
65 /// println!("Retrieved {} klines", response.result.list.len());
66 /// Ok(())
67 /// }
68 /// ```
69 ///
70 /// # Errors
71 ///
72 /// Returns `BybitError` if:
73 /// - Request parameters are invalid (e.g., limit out of range)
74 /// - API returns an error response
75 /// - Network or parsing errors occur
76 pub async fn get_klines<'b>(&self, req: KlineRequest<'_>) -> Result<KlineResponse, BybitError> {
77 // Validate request parameters
78 req.validate().map_err(|e| BybitError::Base(e))?;
79
80 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
81
82 // Set category (default to Linear if not specified)
83 let category = req.category.unwrap_or(Category::Linear);
84 parameters.insert("category".to_owned(), category.as_str().to_owned());
85
86 parameters.insert("symbol".into(), req.symbol.into());
87 parameters.insert("interval".into(), req.interval.as_str().to_owned());
88
89 if let Some(start_str) = req.start.as_ref().map(|s| s.as_ref()) {
90 let start_millis = date_to_milliseconds(start_str);
91 parameters
92 .entry("start".to_owned())
93 .or_insert_with(|| start_millis.to_string());
94 }
95 if let Some(end_str) = req.end.as_ref().map(|s| s.as_ref()) {
96 let end_millis = date_to_milliseconds(end_str);
97 parameters
98 .entry("end".to_owned())
99 .or_insert_with(|| end_millis.to_string());
100 }
101 if let Some(l) = req.limit {
102 parameters
103 .entry("limit".to_owned())
104 .or_insert_with(|| l.to_string());
105 }
106
107 let request = build_request(¶meters);
108 let response: KlineResponse = self
109 .client
110 .get(API::Market(Market::Kline), Some(request))
111 .await?;
112 Ok(response)
113 }
114 /// Retrieves historical mark price klines.
115 ///
116 /// Provides historical kline data for mark prices based on the specified category, symbol, and interval.
117 /// Optional parameters can be used to define the range of the data with start and end times, as well as
118 /// to limit the number of kline entries returned. This function supports queries for USDT perpetual,
119
120 /// USDC contract, and Inverse contract categories.
121 ///
122 /// # Arguments
123 ///
124 /// * `category` - An optional category of the contract, if specified.
125 /// * `symbol` - The trading pair or contract symbol.
126 /// * `interval` - The interval between klines (e.g., "5m" for five minutes).
127 /// * `start` - An optional start time for filtering the data, formatted as "DDMMYY".
128 /// * `end` - An optional end time for filtering the data, formatted as "DDMMYY".
129 /// * `limit` - An optional limit to the number of kline entries to be returned.
130 ///
131 /// # Returns
132 ///
133 /// A `Result<Vec<MarkPriceKline>, Error>` containing the historical mark price kline data if successful,
134
135 /// or an error otherwise.
136
137 /// Retrieves historical mark price kline data for perpetual contracts.
138 ///
139 /// Mark price is a reference price used to calculate funding rates and trigger
140 /// liquidations in perpetual futures contracts. This endpoint supports only
141 /// linear (USDT-margined) and inverse (coin-margined) perpetual contracts.
142 ///
143 /// # Arguments
144 ///
145 /// * `req` - A `KlineRequest` containing the query parameters
146 ///
147 /// # Returns
148 ///
149 /// Returns a `Result` containing `MarkPriceKlineResponse` if successful, or `BybitError` if an error occurs.
150 ///
151 /// # Examples
152 ///
153 /// ```no_run
154 /// use bybit::prelude::*;
155 ///
156 /// #[tokio::main]
157 /// async fn main() -> Result<(), BybitError> {
158 /// let market = MarketData::new(None, None);
159 ///
160 /// let request = KlineRequest::builder()
161 /// .category(Category::Linear)
162 /// .symbol("BTCUSDT")
163 /// .interval(Interval::M15)
164 /// .limit(50)
165 /// .build()
166 /// .unwrap();
167 ///
168 /// let response = market.get_mark_price_klines(request).await?;
169 /// println!("Retrieved {} mark price klines", response.result.list.len());
170 /// Ok(())
171 /// }
172 /// ```
173 ///
174 /// # Errors
175 ///
176 /// Returns `BybitError` if:
177 /// - Category is not Linear or Inverse
178 /// - Request parameters are invalid
179 /// - API returns an error response
180 /// - Network or parsing errors occur
181 pub async fn get_mark_price_klines<'b>(
182 &self,
183 req: KlineRequest<'_>,
184 ) -> Result<MarkPriceKlineResponse, BybitError> {
185 // Validate request parameters
186 req.validate().map_err(|e| BybitError::Base(e))?;
187
188 // Validate category (must be Linear or Inverse for mark price klines)
189 let category = req.category.unwrap_or(Category::Linear);
190 match category {
191 Category::Linear | Category::Inverse => {
192 // Valid category
193 }
194 _ => {
195 return Err(BybitError::Base(
196 "Category must be either Linear or Inverse for mark price klines".to_string(),
197 ))
198 }
199 }
200
201 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
202 parameters.insert("category".to_owned(), category.as_str().to_owned());
203 parameters.insert("symbol".into(), req.symbol.into());
204 parameters.insert("interval".into(), req.interval.as_str().to_owned());
205
206 if let Some(start_str) = req.start.as_ref().map(|s| s.as_ref()) {
207 let start_millis = date_to_milliseconds(start_str);
208 parameters
209 .entry("start".to_owned())
210 .or_insert_with(|| start_millis.to_string());
211 }
212 if let Some(end_str) = req.end.as_ref().map(|s| s.as_ref()) {
213 let end_millis = date_to_milliseconds(end_str);
214 parameters
215 .entry("end".to_owned())
216 .or_insert_with(|| end_millis.to_string());
217 }
218
219 if let Some(l) = req.limit {
220 parameters
221 .entry("limit".to_owned())
222 .or_insert_with(|| l.to_string());
223 }
224
225 let request = build_request(¶meters);
226 let response: MarkPriceKlineResponse = self
227 .client
228 .get(API::Market(Market::MarkPriceKline), Some(request))
229 .await?;
230 Ok(response)
231 }
232 /// Fetches index price klines based on specified criteria.
233 ///
234 /// Retrieves klines (candlestick data) for index prices given a category, symbol, interval, and optional date range.
235 /// The `start` and `end` parameters can define a specific time range for the data, and `limit` controls the number
236 /// of klines returned. If `start`, `end`, or `limit` are `None`, they are omitted from the query.
237 ///
238 /// # Arguments
239 ///
240 /// * `category` - An optional `Category` determining the contract category.
241 /// * `symbol` - The trading pair or symbol for the klines.
242 /// * `interval` - The duration between individual klines.
243 /// * `start` - Optional start time for the kline data as a string slice.
244 /// * `end` - Optional end time for the kline data as a string slice.
245 /// * `limit` - Optional maximum number of klines to return.
246 ///
247 /// # Returns
248 ///
249 /// Returns a `Result<Vec<Kline>, Error>` with the kline data if the query is successful, or an error detailing
250 /// the problem if the query fails.
251 /// Retrieves historical index price kline data for perpetual contracts.
252 ///
253 /// Index price tracks the underlying asset's spot price across multiple exchanges
254 /// and is used to anchor the mark price in perpetual futures contracts.
255 /// This endpoint supports only linear (USDT-margined) and inverse (coin-margined) perpetual contracts.
256 ///
257 /// # Arguments
258 ///
259 /// * `req` - A `KlineRequest` containing the query parameters
260 ///
261 /// # Returns
262 ///
263 /// Returns a `Result` containing `IndexPriceKlineResponse` if successful, or `BybitError` if an error occurs.
264 ///
265 /// # Examples
266 ///
267 /// ```no_run
268 /// use bybit::prelude::*;
269 ///
270 /// #[tokio::main]
271 /// async fn main() -> Result<(), BybitError> {
272 /// let market = MarketData::new(None, None);
273 ///
274 /// let request = KlineRequest::builder()
275 /// .category(Category::Inverse)
276 /// .symbol("BTCUSD")
277 /// .interval(Interval::H4)
278 /// .limit(200)
279 /// .build()
280 /// .unwrap();
281 ///
282 /// let response = market.get_index_price_klines(request).await?;
283 /// println!("Retrieved {} index price klines", response.result.list.len());
284 /// Ok(())
285 /// }
286 /// ```
287 ///
288 /// # Errors
289 ///
290 /// Returns `BybitError` if:
291 /// - Category is not Linear or Inverse
292 /// - Request parameters are invalid
293 /// - API returns an error response
294 /// - Network or parsing errors occur
295 pub async fn get_index_price_klines<'b>(
296 &self,
297 req: KlineRequest<'_>,
298 ) -> Result<IndexPriceKlineResponse, BybitError> {
299 // Validate request parameters
300 req.validate().map_err(|e| BybitError::Base(e))?;
301
302 // Validate category (must be Linear or Inverse for index price klines)
303 let category = req.category.unwrap_or(Category::Linear);
304 match category {
305 Category::Linear | Category::Inverse => {
306 // Valid category
307 }
308 _ => {
309 return Err(BybitError::Base(
310 "Category must be either Linear or Inverse for index price klines".to_string(),
311 ))
312 }
313 }
314
315 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
316 parameters.insert("category".to_owned(), category.as_str().to_owned());
317 parameters.insert("symbol".into(), req.symbol.into());
318 parameters.insert("interval".into(), req.interval.as_str().to_owned());
319
320 if let Some(start_str) = req.start.as_ref().map(|s| s.as_ref()) {
321 let start_millis = date_to_milliseconds(start_str);
322 parameters
323 .entry("start".to_owned())
324 .or_insert_with(|| start_millis.to_string());
325 }
326 if let Some(end_str) = req.end.as_ref().map(|s| s.as_ref()) {
327 let end_millis = date_to_milliseconds(end_str);
328 parameters
329 .entry("end".to_owned())
330 .or_insert_with(|| end_millis.to_string());
331 }
332
333 if let Some(l) = req.limit {
334 parameters
335 .entry("limit".to_owned())
336 .or_insert_with(|| l.to_string());
337 }
338
339 let request = build_request(¶meters);
340 let response: IndexPriceKlineResponse = self
341 .client
342 .get(API::Market(Market::IndexPriceKline), Some(request))
343 .await?;
344 Ok(response)
345 }
346 /// Retrieves premium index price klines based on specified criteria.
347 ///
348 /// Given a `symbol` and an `interval`, this function fetches the premium index price klines. It also
349 /// accepts optional parameters `start` and `end` to define a specific time range, and `limit` to
350 /// Retrieves historical premium index price kline data for perpetual contracts.
351 ///
352 /// Premium index price reflects the premium or discount of the perpetual futures price
353 /// relative to the spot index price. This is key for understanding funding rate dynamics.
354 /// This endpoint supports only linear (USDT-margined) perpetual contracts.
355 ///
356 /// # Arguments
357 ///
358 /// * `req` - A `KlineRequest` containing the query parameters
359 ///
360 /// # Returns
361 ///
362 /// Returns a `Result` containing `PremiumIndexPriceKlineResponse` if successful, or `BybitError` if an error occurs.
363 ///
364 /// # Examples
365 ///
366 /// ```no_run
367 /// use bybit::prelude::*;
368 ///
369 /// #[tokio::main]
370 /// async fn main() -> Result<(), BybitError> {
371 /// let market = MarketData::new(None, None);
372 ///
373 /// let request = KlineRequest::builder()
374 /// .category(Category::Linear)
375 /// .symbol("BTCUSDT")
376 /// .interval(Interval::D1)
377 /// .limit(30)
378 /// .build()
379 /// .unwrap();
380 ///
381 /// let response = market.get_premium_index_price_klines(request).await?;
382 /// println!("Retrieved {} premium index klines", response.result.list.len());
383 /// Ok(())
384 /// }
385 /// ```
386 ///
387 /// # Errors
388 ///
389 /// Returns `BybitError` if:
390 /// - Category is not Linear (premium index only supports linear contracts)
391 /// - Request parameters are invalid
392 /// - API returns an error response
393 /// - Network or parsing errors occur
394 pub async fn get_premium_index_price_klines<'b>(
395 &self,
396 req: KlineRequest<'_>,
397 ) -> Result<PremiumIndexPriceKlineResponse, BybitError> {
398 // Validate request parameters
399 req.validate().map_err(|e| BybitError::Base(e))?;
400
401 // Validate category (must be Linear for premium index klines)
402 let category = req.category.unwrap_or(Category::Linear);
403 match category {
404 Category::Linear => {
405 // Valid category
406 }
407 _ => {
408 return Err(BybitError::Base(
409 "Category must be Linear for premium index price klines".to_string(),
410 ))
411 }
412 }
413
414 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
415 parameters.insert("category".to_owned(), category.as_str().to_owned());
416 parameters.insert("symbol".into(), req.symbol.into());
417 parameters.insert("interval".into(), req.interval.as_str().to_owned());
418 if let Some(start_str) = req.start.as_ref().map(|s| s.as_ref()) {
419 let start_millis = date_to_milliseconds(start_str);
420 parameters
421 .entry("start".to_owned())
422 .or_insert_with(|| start_millis.to_string());
423 }
424 if let Some(end_str) = req.end.as_ref().map(|s| s.as_ref()) {
425 let end_millis = date_to_milliseconds(end_str);
426 parameters
427 .entry("end".to_owned())
428 .or_insert_with(|| end_millis.to_string());
429 }
430 if let Some(l) = req.limit {
431 parameters
432 .entry("limit".to_owned())
433 .or_insert_with(|| l.to_string());
434 }
435
436 let request = build_request(¶meters);
437 let response: PremiumIndexPriceKlineResponse = self
438 .client
439 .get(API::Market(Market::PremiumIndexPriceKline), Some(request))
440 .await?;
441 Ok(response)
442 }
443 /// Retrieves a list of instruments (Futures or Spot) based on the specified filters.
444 ///
445 /// This function queries the exchange for instruments, optionally filtered by the provided
446 /// symbol, status, base coin, and result count limit. It supports both Futures and Spot instruments,
447
448 /// returning results encapsulated in the `InstrumentInfo` enum.
449 ///
450 /// # Arguments
451 ///
452 /// * `symbol` - An optional filter to specify the symbol of the instruments.
453 /// * `status` - An optional boolean to indicate if only instruments with trading status should be retrieved.
454 /// * `base_coin` - An optional filter for the base coin of the instruments.
455 /// * `limit` - An optional limit on the number of instruments to be retrieved.
456 ///
457 /// # Returns
458 ///
459 /// A `Result<InstrumentInfoResponse, Error>` where the `Ok` variant contains the filtered list of
460 /// instruments (Futures or Spot), and the `Err` variant contains an error if the request fails or if the response
461 /// parsing encounters an issue.
462 pub async fn get_instrument_info<'b>(
463 &self,
464 req: InstrumentRequest<'b>,
465 ) -> Result<InstrumentInfoResponse, BybitError> {
466 // Validate request parameters
467 if let Err(err) = req.validate() {
468 return Err(BybitError::from(err));
469 }
470
471 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
472
473 // Category is required
474 let category_value = match req.category {
475 Category::Linear => "linear",
476 Category::Inverse => "inverse",
477 Category::Spot => "spot",
478 Category::Option => "option",
479 };
480 parameters.insert("category".into(), category_value.into());
481
482 // Optional parameters
483 if let Some(symbol) = req.symbol {
484 parameters.insert("symbol".into(), symbol.into());
485 }
486
487 if let Some(symbol_type) = req.symbol_type {
488 parameters.insert("symbolType".into(), symbol_type.into());
489 }
490
491 if let Some(status) = req.status {
492 parameters.insert("status".into(), status.into());
493 }
494
495 // Base coin only applies to linear, inverse, and option categories
496 if let Some(base_coin) = req.base_coin {
497 parameters.insert("baseCoin".into(), base_coin.into());
498 }
499
500 if let Some(limit) = req.limit {
501 parameters.insert("limit".into(), limit.to_string());
502 }
503
504 if let Some(cursor) = req.cursor {
505 parameters.insert("cursor".into(), cursor.into());
506 }
507
508 let request = build_request(¶meters);
509 let response: InstrumentInfoResponse = self
510 .client
511 .get(API::Market(Market::InstrumentsInfo), Some(request))
512 .await?;
513 Ok(response)
514 }
515
516 /// Asynchronously fetches the order book depth for a specified symbol within a certain category.
517 /// Optionally, the number of order book entries returned can be limited.
518 ///
519 /// # Arguments
520 ///
521 /// * `req` - An `OrderbookRequest` containing:
522 /// * `symbol`: The symbol string to query the order book for.
523 /// * `category`: The market category to filter the order book by.
524 /// * `limit`: An optional usize to restrict the number of entries in the order book.
525 ///
526 /// # Returns
527 ///
528 /// A `Result<OrderBook, Error>` which is Ok if the order book is successfully retrieved,
529
530 /// or an Err with a detailed error message otherwise.
531 pub async fn get_depth<'b>(
532 &self,
533 req: OrderbookRequest<'_>,
534 ) -> Result<OrderBookResponse, BybitError> {
535 // Validate request parameters
536 if let Err(err) = req.validate() {
537 return Err(BybitError::from(err));
538 }
539
540 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
541 parameters.insert("category".into(), req.category.as_str().into());
542 parameters.insert("symbol".into(), req.symbol.clone().into());
543
544 // Use effective limit (either specified or default for category)
545 let limit = req.effective_limit();
546 parameters.insert("limit".to_string(), limit.to_string());
547
548 let request = build_request(¶meters);
549 let response: OrderBookResponse = self
550 .client
551 .get(API::Market(Market::OrderBook), Some(request))
552 .await?;
553
554 Ok(response)
555 }
556
557 /// Asynchronously retrieves RPI (Real-time Price Improvement) order book data.
558 ///
559 /// This method fetches the RPI order book for a specified trading pair, which includes
560 /// both regular orders and RPI orders. RPI orders can provide price improvement for takers
561 /// when they cross with non-RPI orders.
562 ///
563 /// # Arguments
564 ///
565 /// * `req` - The RPI order book request parameters containing symbol, optional category, and limit.
566 ///
567 /// # Returns
568 ///
569 /// A `Result<RPIOrderbookResponse, BybitError>` which is Ok if the RPI order book is successfully retrieved,
570 /// or an Err with a detailed error message otherwise.
571 pub async fn get_rpi_orderbook<'b>(
572 &self,
573 req: RPIOrderbookRequest<'_>,
574 ) -> Result<RPIOrderbookResponse, BybitError> {
575 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
576
577 // Symbol is required
578 parameters.insert("symbol".into(), req.symbol.into());
579
580 // Category is optional
581 if let Some(category) = req.category {
582 parameters.insert("category".into(), category.as_str().into());
583 }
584
585 // Limit is required (1-50)
586 parameters.insert("limit".to_string(), req.limit.to_string());
587
588 let request = build_request(¶meters);
589 let response: RPIOrderbookResponse = self
590 .client
591 .get(API::Market(Market::RPIOrderbook), Some(request))
592 .await?;
593
594 Ok(response)
595 }
596 /// Asynchronously retrieves tickers based on the provided symbol and category.
597 ///
598 /// # Arguments
599 ///
600 /// * `symbol` - An optional reference to a string representing the symbol.
601 /// * `category` - The market category (e.g., Linear, Inverse, Spot) for which tickers are to be retrieved.
602 ///
603 /// # Returns
604 ///
605 /// A Result containing a vector of Ticker objects, or an error if the retrieval fails.
606 pub async fn get_tickers<'b>(
607 &self,
608 req: TickerRequest<'b>,
609 ) -> Result<TickerResponse, BybitError> {
610 // Validate request parameters
611 if let Err(err) = req.validate() {
612 return Err(BybitError::from(err));
613 }
614
615 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
616
617 // Category is required
618 parameters.insert("category".into(), req.category.as_str().into());
619
620 // Optional parameters
621 if let Some(symbol) = req.symbol {
622 parameters.insert("symbol".into(), symbol.into());
623 }
624
625 // Base coin only applies to option category
626 if let Some(base_coin) = req.base_coin {
627 parameters.insert("baseCoin".into(), base_coin.into());
628 }
629
630 // Expiry date only applies to option category
631 if let Some(exp_date) = req.exp_date {
632 parameters.insert("expDate".into(), exp_date.into());
633 }
634
635 let request = build_request(¶meters);
636 let response: TickerResponse = self
637 .client
638 .get(API::Market(Market::Ticker), Some(request))
639 .await?;
640 Ok(response)
641 }
642
643 /// Asynchronously retrieves the funding history based on specified criteria.
644 ///
645 /// This function obtains historical funding rates for futures contracts given a category,
646
647 /// symbol, and an optional time range and limit. Only Linear or Inverse categories are supported.
648 ///
649 /// # Arguments
650 ///
651 /// * `category` - Specifies the contract category (Linear or Inverse).
652 /// * `symbol` - The trading pair or contract symbol.
653 /// * `start` - An optional parameter indicating the start time for the funding history.
654 /// * `end` - An optional parameter indicating the end time for the funding history.
655 /// * `limit` - An optional parameter specifying the maximum number of funding rates to return.
656 ///
657 /// # Returns
658 ///
659 /// A `Result<Vec<FundingRate>, Error>` representing the historical funding rates if the request is successful,
660
661 /// otherwise an error.
662 ///
663 /// # Errors
664 ///
665 /// Returns an error if the specified category is invalid or if there is a failure during the API request.
666 pub async fn get_funding_history<'b>(
667 &self,
668 req: FundingHistoryRequest<'_>,
669 ) -> Result<FundingRateResponse, BybitError> {
670 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
671 let category_value = match req.category {
672 Category::Linear => "linear",
673 Category::Inverse => "inverse",
674 _ => {
675 return Err(BybitError::from(
676 "Category must be either Linear or Inverse".to_string(),
677 ))
678 }
679 };
680 parameters.insert("category".into(), category_value.into());
681 parameters.insert("symbol".into(), req.symbol.into());
682
683 if let Some(start_time) = req.start_time {
684 parameters
685 .entry("startTime".to_owned())
686 .or_insert_with(|| start_time.to_string());
687 }
688
689 if let Some(end_time) = req.end_time {
690 parameters
691 .entry("endTime".to_owned())
692 .or_insert_with(|| end_time.to_string());
693 }
694
695 if let Some(l) = req.limit {
696 parameters
697 .entry("limit".to_owned())
698 .or_insert_with(|| l.to_string());
699 }
700 let request = build_request(¶meters);
701 let response: FundingRateResponse = self
702 .client
703 .get(API::Market(Market::FundingRate), Some(request))
704 .await?;
705 Ok(response)
706 }
707 /// Retrieves a list of the most recent trades for a specified market category.
708 /// Filtering by symbol and basecoin is supported, and the number of trades returned can be limited.
709 ///
710 /// # Parameters
711 ///
712 /// * `category`: The market category to filter trades.
713 /// * `symbol`: A specific symbol to filter trades (optional).
714 /// * `basecoin`: A specific basecoin to filter trades (optional).
715 /// * `limit`: The maximum number of trades to return (optional).
716 ///
717 /// # Returns
718 ///
719 /// Returns `Ok(Vec<Trade>)` containing the recent trades if the operation is successful,
720
721 /// or an `Err` with an error message if it fails.
722 pub async fn get_recent_trades<'b>(
723 &self,
724 req: RecentTradesRequest<'_>,
725 ) -> Result<RecentTradesResponse, BybitError> {
726 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
727 parameters.insert("category".into(), req.category.as_str().into());
728 if let Some(s) = req.symbol {
729 parameters.insert("symbol".into(), s.into());
730 }
731 if let Some(b) = req.base_coin {
732 parameters.insert("baseCoin".into(), b.into());
733 }
734 if let Some(l) = req.limit {
735 parameters.insert("limit".into(), l.to_string());
736 }
737 let request = build_request(¶meters);
738 let response: RecentTradesResponse = self
739 .client
740 .get(API::Market(Market::RecentTrades), Some(request))
741 .await?;
742
743 Ok(response)
744 }
745
746 /// Retrieves open interest for a specific market category and symbol over a defined time interval.
747 ///
748 /// Open interest is the total number of outstanding derivative contracts, such as futures or options,
749
750 /// that have not been settled. This function provides a summary of such open interests.
751 ///
752 /// # Arguments
753 ///
754 /// * `category`: The market category to query for open interest data.
755 /// * `symbol`: The trading symbol for which open interest is to be retrieved.
756 /// * `interval_time`: The duration over which open interest data should be aggregated.
757 /// * `start`: The starting point of the time interval (optional).
758 /// * `end`: The endpoint of the time interval (optional).
759 /// * `limit`: A cap on the number of data points to return (optional).
760 ///
761 /// # Returns
762 ///
763 /// A `Result<OpenInterestSummary, Error>` representing either:
764 /// - An `OpenInterestSummary` on success, encapsulating the open interest data.
765 /// - An `Error`, if the retrieval fails.
766 pub async fn get_open_interest<'b>(
767 &self,
768 req: OpenInterestRequest<'_>,
769 ) -> Result<OpenInterestResponse, BybitError> {
770 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
771 let category_value = match req.category {
772 Category::Linear => "linear",
773 Category::Inverse => "inverse",
774 _ => {
775 return Err(BybitError::from(
776 "Category must be either Linear or Inverse".to_string(),
777 ))
778 }
779 };
780 parameters.insert("category".into(), category_value.into());
781 parameters.insert("symbol".into(), req.symbol.into());
782 parameters.insert("intervalTime".into(), req.interval.into());
783 if let Some(start_time) = req.start_time {
784 parameters
785 .entry("startTime".to_owned())
786 .or_insert_with(|| start_time.to_string());
787 }
788 if let Some(end_time) = req.end_time {
789 parameters
790 .entry("endTime".to_owned())
791 .or_insert_with(|| end_time.to_string());
792 }
793 if let Some(l) = req.limit {
794 parameters
795 .entry("limit".to_owned())
796 .or_insert_with(|| l.to_string());
797 }
798 let request = build_request(¶meters);
799 let response: OpenInterestResponse = self
800 .client
801 .get(API::Market(Market::OpenInterest), Some(request))
802 .await?;
803 Ok(response)
804 }
805 /// Fetches historical volatility data for a specified base coin.
806 ///
807 /// This function queries historical volatility based on the given base coin and optional
808 /// parameters for the period, start, and end times to filter the results.
809 ///
810 /// # Arguments
811 ///
812 /// * `base_coin` - The base coin identifier for which volatility data is being requested.
813 /// * `period` - (Optional) A string specifying the period over which to calculate volatility.
814 /// * `start` - (Optional) A string indicating the start time for the data range.
815 /// * `end` - (Optional) A string indicating the end time for the data range.
816 ///
817 /// # Returns
818 ///
819 /// A `Result<Vec<HistoricalVolatility>, Error>` which is either:
820 /// - A vector of `HistoricalVolatility` instances within the specified time range on success.
821 /// - An `Error` if the request fails or if invalid arguments are provided.
822 pub async fn get_historical_volatility<'b>(
823 &self,
824 req: HistoricalVolatilityRequest<'_>,
825 ) -> Result<HistoricalVolatilityResponse, BybitError> {
826 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
827 parameters.insert("category".into(), Category::Option.as_str().into());
828 if let Some(b) = req.base_coin {
829 parameters.insert("baseCoin".into(), b.into());
830 }
831 if let Some(p) = req.period {
832 parameters.insert("period".into(), p.into());
833 }
834 if let Some(s) = req.start {
835 let start_millis = date_to_milliseconds(s.as_ref());
836 parameters.insert("startTime".into(), start_millis.to_string());
837 }
838 if let Some(e) = req.end {
839 let end_millis = date_to_milliseconds(e.as_ref());
840 parameters.insert("endTime".into(), end_millis.to_string());
841 }
842 let request = build_request(¶meters);
843 let response: HistoricalVolatilityResponse = self
844 .client
845 .get(API::Market(Market::HistoricalVolatility), Some(request))
846 .await?;
847 Ok(response)
848 }
849
850 /// Fetches insurance information for a specific coin.
851 ///
852 /// # Arguments
853 ///
854 /// * `coin` - An optional parameter representing the coin for which insurance information is to be fetched.
855 ///
856 /// # Returns
857 ///
858 /// Returns a `Result` containing the insurance summary if successful, or an error if not.
859 pub async fn get_insurance(&self, coin: Option<&str>) -> Result<InsuranceResponse, BybitError> {
860 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
861 parameters.insert("category".into(), Category::Option.as_str().into());
862 if let Some(c) = coin {
863 parameters.insert("coin".into(), c.into());
864 }
865 let request = build_request(¶meters);
866 let response: InsuranceResponse = self
867 .client
868 .get(API::Market(Market::Insurance), Some(request))
869 .await?;
870 Ok(response)
871 }
872
873 /// Retrieves the risk limit information based on market category and specific symbol if provided.
874 ///
875 /// # Parameters
876 ///
877 /// * `category` - Market category to query for risk limits.
878 /// * `symbol` - Optional symbol to further filter the risk limit results.
879 ///
880 /// # Returns
881 ///
882 /// A `Result<RiskLimitSummary>` which is either the risk limit details on success or an error on failure.
883 pub async fn get_risk_limit<'b>(
884 &self,
885 req: RiskLimitRequest<'_>,
886 ) -> Result<RiskLimitResponse, BybitError> {
887 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
888 let category_value = match req.category {
889 Category::Linear => "linear",
890 Category::Inverse => "inverse",
891 _ => {
892 return Err(BybitError::from(
893 "Category must be either Linear or Inverse".to_string(),
894 ))
895 }
896 };
897 parameters.insert("category".into(), category_value.into());
898 if let Some(s) = req.symbol {
899 parameters.insert("symbol".into(), s.into());
900 }
901 let request = build_request(¶meters);
902 let response: RiskLimitResponse = self
903 .client
904 .get(API::Market(Market::RiskLimit), Some(request))
905 .await?;
906 Ok(response)
907 }
908
909 /// Retrieves the delivery price for a given category, symbol, base coin, and limit.
910 ///
911 /// # Arguments
912 ///
913 /// * `category` - The market category to fetch the delivery price from.
914 /// * `symbol` - Optional symbol filter for the delivery price.
915 /// * `base_coin` - Optional base coin filter for the delivery price.
916 /// * `limit` - Optional limit for the delivery price.
917 ///
918 /// # Returns
919 ///
920 /// A `Result` type containing either a `DeliveryPriceSummary` upon success or an error message.
921 pub async fn get_delivery_price(
922 &self,
923 category: Category,
924 symbol: Option<&str>,
925 base_coin: Option<&str>,
926 limit: Option<u64>,
927 ) -> Result<DeliveryPriceResponse, BybitError> {
928 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
929 parameters.insert("category".into(), category.as_str().into());
930 if let Some(s) = symbol {
931 parameters.insert("symbol".into(), s.into());
932 }
933 if let Some(b) = base_coin {
934 parameters.insert("baseCoin".into(), b.into());
935 }
936 if let Some(l) = limit {
937 parameters.insert("limit".into(), l.to_string());
938 }
939 let request = build_request(¶meters);
940 let response: DeliveryPriceResponse = self
941 .client
942 .get(API::Market(Market::DeliveryPrice), Some(request))
943 .await?;
944 Ok(response)
945 }
946
947 /// Retrieves new delivery price data for options contracts.
948 ///
949 /// This method fetches historical option delivery prices from the `/v5/market/new-delivery-price` endpoint.
950 /// This endpoint is specifically for options contracts and returns the most recent 50 records
951 /// in reverse order of "deliveryTime" by default.
952 ///
953 /// # Important Notes
954 /// - This endpoint only supports options contracts (`category` must be `option`)
955 /// - It is recommended to query this endpoint 1 minute after settlement is completed,
956 /// because the data returned by this endpoint may be delayed by 1 minute.
957 ///
958 /// # Arguments
959 ///
960 /// * `req` - The new delivery price request parameters containing category, base coin, and optional settle coin.
961 ///
962 /// # Returns
963 ///
964 /// A `Result` type containing either a `NewDeliveryPriceSummary` upon success or an error message.
965 pub async fn get_new_delivery_price<'b>(
966 &self,
967 req: NewDeliveryPriceRequest<'_>,
968 ) -> Result<NewDeliveryPriceResponse, BybitError> {
969 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
970
971 // Category is required and must be "option"
972 parameters.insert("category".into(), req.category.as_str().into());
973
974 // Base coin is required
975 parameters.insert("baseCoin".into(), req.base_coin.into());
976
977 // Settle coin is optional (defaults to USDT if not specified)
978 if let Some(settle_coin) = req.settle_coin {
979 parameters.insert("settleCoin".into(), settle_coin.into());
980 }
981
982 let request = build_request(¶meters);
983 let response: NewDeliveryPriceResponse = self
984 .client
985 .get(API::Market(Market::NewDeliveryPrice), Some(request))
986 .await?;
987
988 Ok(response)
989 }
990
991 /// Retrieves ADL (Auto-Deleveraging) alert data.
992 ///
993 /// This method fetches ADL alert information and insurance pool data from the
994 /// `/v5/market/adlAlert` endpoint. ADL is a risk management mechanism that
995 /// automatically closes positions when the insurance pool balance reaches
996 /// certain thresholds to prevent systemic risk.
997 ///
998 /// # Important Notes
999 /// - Data update frequency is every 1 minute
1000 /// - Covers: USDT Perpetual, USDT Delivery, USDC Perpetual, USDC Delivery, Inverse Contracts
1001 /// - The `symbol` parameter is optional; if not provided, returns all symbols
1002 ///
1003 /// # Arguments
1004 ///
1005 /// * `req` - The ADL alert request parameters containing optional symbol filter.
1006 ///
1007 /// # Returns
1008 ///
1009 /// A `Result` type containing either an `ADLAlertSummary` upon success or an error message.
1010 pub async fn get_adl_alert<'b>(
1011 &self,
1012 req: ADLAlertRequest<'_>,
1013 ) -> Result<ADLAlertResponse, BybitError> {
1014 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
1015
1016 // Symbol is optional - if provided, filter by symbol
1017 if let Some(symbol) = req.symbol {
1018 parameters.insert("symbol".into(), symbol.into());
1019 }
1020
1021 let request = build_request(¶meters);
1022 let response: ADLAlertResponse = self
1023 .client
1024 .get(API::Market(Market::ADLAlert), Some(request))
1025 .await?;
1026
1027 Ok(response)
1028 }
1029
1030 /// Retrieves the long/short ratio for a given market category, symbol, period, and limit.
1031 ///
1032 /// The long/short ratio represents the total long position volume divided by the total
1033 /// short position volume, aggregated from all users. This can provide insight into market
1034 /// sentiment for a given trading pair during the specified time period.
1035 ///
1036 /// # Arguments
1037 ///
1038 /// * `category` - The market category (Linear or Inverse) to fetch the long/short ratio from.
1039 /// * `symbol` - The trading symbol to fetch the long/short ratio for.
1040 /// * `period` - The period for which to fetch the ratio (e.g., "5min", "15min", "1h").
1041 /// * `limit` - Optional limit for the number of data points to retrieve.
1042 ///
1043 /// # Returns
1044 ///
1045 /// A `Result` type containing either a `LongShortRatioSummary` upon success or an error message.
1046
1047 pub async fn get_longshort_ratio(
1048 &self,
1049 category: Category,
1050 symbol: &str,
1051 period: &str,
1052 limit: Option<u64>,
1053 ) -> Result<LongShortRatioResponse, BybitError> {
1054 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
1055 match category {
1056 Category::Linear | Category::Inverse => {
1057 parameters.insert("category".into(), category.as_str().into())
1058 }
1059 _ => {
1060 return Err(BybitError::from(
1061 "Category must be either Linear or Inverse".to_string(),
1062 ))
1063 }
1064 };
1065 parameters.insert("symbol".into(), symbol.into());
1066 parameters.insert("period".into(), period.into());
1067 if let Some(l) = limit {
1068 parameters.insert("limit".into(), l.to_string());
1069 }
1070 let request = build_request(¶meters);
1071 let response: LongShortRatioResponse = self
1072 .client
1073 .get(API::Market(Market::LongShortRatio), Some(request))
1074 .await?;
1075 Ok(response)
1076 }
1077
1078 /// Retrieves fee group structure and fee rates.
1079 ///
1080 /// This method fetches the fee group structure and fee rates for contract products.
1081 /// The new grouped fee structure only applies to Pro-level and Market Maker clients
1082 /// and does not apply to retail traders.
1083 ///
1084 /// # Arguments
1085 ///
1086 /// * `req` - The fee group info request parameters
1087 ///
1088 /// # Returns
1089 ///
1090 /// * `Ok(FeeGroupInfoResponse)` - Contains the fee group information
1091 /// * `Err(BybitError)` - If the request fails
1092 ///
1093 /// # Example
1094 ///
1095 /// ```rust
1096 /// use rs_bybit::prelude::*;
1097 ///
1098 /// let client = Client::new("api_key", "api_secret");
1099 /// let market = MarketData::new(client);
1100 /// let request = FeeGroupInfoRequest::default();
1101 /// let response = market.get_fee_group_info(request).await?;
1102 /// ```
1103 pub async fn get_fee_group_info<'b>(
1104 &self,
1105 req: FeeGroupInfoRequest<'_>,
1106 ) -> Result<FeeGroupInfoResponse, BybitError> {
1107 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
1108 parameters.insert("productType".into(), req.product_type.into());
1109 if let Some(group_id) = req.group_id {
1110 parameters.insert("groupId".into(), group_id.into());
1111 }
1112 let request = build_request(¶meters);
1113 let response: FeeGroupInfoResponse = self
1114 .client
1115 .get(API::Market(Market::FeeGroupInfo), Some(request))
1116 .await?;
1117 Ok(response)
1118 }
1119
1120 /// Retrieves order price limits for a trading symbol.
1121 ///
1122 /// This method fetches the highest bid price (buyLmt) and lowest ask price (sellLmt)
1123 /// for a given symbol, which define the order price limits for derivative or spot trading.
1124 /// These limits are important for risk management and order validation.
1125 ///
1126 /// # Arguments
1127 ///
1128 /// * `req` - The order price limit request parameters
1129 ///
1130 /// # Returns
1131 ///
1132 /// * `Ok(OrderPriceLimitResponse)` - Contains the order price limit information
1133 /// * `Err(BybitError)` - If the request fails
1134 ///
1135 /// # Example
1136 ///
1137 /// ```rust
1138 /// use rs_bybit::prelude::*;
1139 ///
1140 /// let client = Client::new("api_key", "api_secret");
1141 /// let market = MarketData::new(client);
1142 /// let request = OrderPriceLimitRequest::linear("BTCUSDT");
1143 /// let response = market.get_order_price_limit(request).await?;
1144 /// ```
1145 pub async fn get_order_price_limit<'b>(
1146 &self,
1147 req: OrderPriceLimitRequest<'_>,
1148 ) -> Result<OrderPriceLimitResponse, BybitError> {
1149 let mut parameters: BTreeMap<String, String> = BTreeMap::new();
1150 if let Some(cat) = req.category {
1151 parameters
1152 .entry("category".to_owned())
1153 .or_insert_with(|| cat.as_str().to_owned());
1154 }
1155 parameters.insert("symbol".into(), req.symbol.into());
1156 let request = build_request(¶meters);
1157 let response: OrderPriceLimitResponse = self
1158 .client
1159 .get(API::Market(Market::OrderPriceLimit), Some(request))
1160 .await?;
1161 Ok(response)
1162 }
1163}