Skip to main content

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(&parameters);
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(&parameters);
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(&parameters);
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(&parameters);
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(&parameters);
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(&parameters);
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(&parameters);
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(&parameters);
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(&parameters);
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(&parameters);
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(&parameters);
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(&parameters);
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(&parameters);
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(&parameters);
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(&parameters);
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(&parameters);
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(&parameters);
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(&parameters);
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(&parameters);
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(&parameters);
1157        let response: OrderPriceLimitResponse = self
1158            .client
1159            .get(API::Market(Market::OrderPriceLimit), Some(request))
1160            .await?;
1161        Ok(response)
1162    }
1163}