Skip to main content

bybit_client/api/
market.rs

1//! Market data API endpoints.
2
3use serde::Serialize;
4
5use crate::error::BybitError;
6use crate::http::HttpClient;
7use crate::types::market::*;
8use crate::types::{Category, KlineInterval, ServerTime};
9
10/// Market data service for public endpoints.
11#[derive(Debug, Clone)]
12pub struct MarketService {
13    http: HttpClient,
14}
15
16impl MarketService {
17    /// Create a new market service.
18    pub fn new(http: HttpClient) -> Self {
19        Self { http }
20    }
21
22    /// Get server time.
23    ///
24    /// # Example
25    ///
26    /// ```no_run
27    /// # use bybit_client::BybitClient;
28    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
29    /// let client = BybitClient::public_only()?;
30    /// let time = client.market().get_server_time().await?;
31    /// println!("Server time: {} ms", time.as_millis());
32    /// # Ok(())
33    /// # }
34    /// ```
35    pub async fn get_server_time(&self) -> Result<ServerTime, BybitError> {
36        self.http.get("/v5/market/time", None::<&()>).await
37    }
38
39    /// Get kline/candlestick data.
40    ///
41    /// # Arguments
42    ///
43    /// * `category` - Product category (spot, linear, inverse)
44    /// * `symbol` - Trading symbol (e.g., "BTCUSDT")
45    /// * `interval` - Kline interval
46    /// * `params` - Optional parameters (start, end, limit)
47    ///
48    /// # Example
49    ///
50    /// ```no_run
51    /// # use bybit_client::{BybitClient, Category, KlineInterval};
52    /// # use bybit_client::api::market::GetKlineParams;
53    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
54    /// let client = BybitClient::public_only()?;
55    /// let params = GetKlineParams::new(Category::Linear, "BTCUSDT", KlineInterval::Hour1)
56    ///     .limit(100);
57    /// let result = client.market().get_kline(&params).await?;
58    /// for kline in result.klines() {
59    ///     println!("{}: O={} H={} L={} C={}",
60    ///         kline.start_time, kline.open_price, kline.high_price,
61    ///         kline.low_price, kline.close_price);
62    /// }
63    /// # Ok(())
64    /// # }
65    /// ```
66    pub async fn get_kline(&self, params: &GetKlineParams) -> Result<KlineResult, BybitError> {
67        self.http.get("/v5/market/kline", Some(params)).await
68    }
69
70    /// Get mark price kline data.
71    pub async fn get_mark_price_kline(
72        &self,
73        params: &GetKlineParams,
74    ) -> Result<KlineResult, BybitError> {
75        self.http
76            .get("/v5/market/mark-price-kline", Some(params))
77            .await
78    }
79
80    /// Get index price kline data.
81    pub async fn get_index_price_kline(
82        &self,
83        params: &GetKlineParams,
84    ) -> Result<KlineResult, BybitError> {
85        self.http
86            .get("/v5/market/index-price-kline", Some(params))
87            .await
88    }
89
90    /// Get premium index price kline data.
91    pub async fn get_premium_index_price_kline(
92        &self,
93        params: &GetKlineParams,
94    ) -> Result<KlineResult, BybitError> {
95        self.http
96            .get("/v5/market/premium-index-price-kline", Some(params))
97            .await
98    }
99
100    /// Get instrument/symbol information.
101    ///
102    /// # Example
103    ///
104    /// ```no_run
105    /// # use bybit_client::{BybitClient, Category};
106    /// # use bybit_client::api::market::GetInstrumentsInfoParams;
107    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
108    /// let client = BybitClient::public_only()?;
109    /// let params = GetInstrumentsInfoParams::new(Category::Linear)
110    ///     .symbol("BTCUSDT");
111    /// let result = client.market().get_instruments_info(&params).await?;
112    /// for inst in &result.list {
113    ///     println!("{}: status={}", inst.symbol, inst.status);
114    /// }
115    /// # Ok(())
116    /// # }
117    /// ```
118    pub async fn get_instruments_info(
119        &self,
120        params: &GetInstrumentsInfoParams,
121    ) -> Result<InstrumentInfoResult, BybitError> {
122        self.http
123            .get("/v5/market/instruments-info", Some(params))
124            .await
125    }
126
127    /// Get order book depth.
128    ///
129    /// # Arguments
130    ///
131    /// * `params` - Order book parameters
132    ///
133    /// # Example
134    ///
135    /// ```no_run
136    /// # use bybit_client::{BybitClient, Category};
137    /// # use bybit_client::api::market::GetOrderbookParams;
138    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
139    /// let client = BybitClient::public_only()?;
140    /// let params = GetOrderbookParams::new(Category::Linear, "BTCUSDT")
141    ///     .limit(25);
142    /// let orderbook = client.market().get_orderbook(&params).await?;
143    /// println!("Best bid: {} @ {}", orderbook.bids[0].size, orderbook.bids[0].price);
144    /// println!("Best ask: {} @ {}", orderbook.asks[0].size, orderbook.asks[0].price);
145    /// # Ok(())
146    /// # }
147    /// ```
148    pub async fn get_orderbook(&self, params: &GetOrderbookParams) -> Result<Orderbook, BybitError> {
149        self.http.get("/v5/market/orderbook", Some(params)).await
150    }
151
152    /// Get tickers for one or all symbols.
153    ///
154    /// # Example
155    ///
156    /// ```no_run
157    /// # use bybit_client::{BybitClient, Category};
158    /// # use bybit_client::api::market::GetTickersParams;
159    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
160    /// let client = BybitClient::public_only()?;
161    /// let params = GetTickersParams::new(Category::Linear)
162    ///     .symbol("BTCUSDT");
163    /// let result = client.market().get_tickers(&params).await?;
164    /// if let Some(ticker) = result.list.first() {
165    ///     println!("{}: last={:?}", ticker.symbol, ticker.last_price);
166    /// }
167    /// # Ok(())
168    /// # }
169    /// ```
170    pub async fn get_tickers(&self, params: &GetTickersParams) -> Result<TickerResult, BybitError> {
171        self.http.get("/v5/market/tickers", Some(params)).await
172    }
173
174    /// Get funding rate history.
175    ///
176    /// # Example
177    ///
178    /// ```no_run
179    /// # use bybit_client::{BybitClient, Category};
180    /// # use bybit_client::api::market::GetFundingRateHistoryParams;
181    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
182    /// let client = BybitClient::public_only()?;
183    /// let params = GetFundingRateHistoryParams::new(Category::Linear, "BTCUSDT")
184    ///     .limit(10);
185    /// let result = client.market().get_funding_rate_history(&params).await?;
186    /// for rate in &result.list {
187    ///     println!("{}: {}", rate.funding_rate_timestamp, rate.funding_rate);
188    /// }
189    /// # Ok(())
190    /// # }
191    /// ```
192    pub async fn get_funding_rate_history(
193        &self,
194        params: &GetFundingRateHistoryParams,
195    ) -> Result<FundingRateHistoryResult, BybitError> {
196        self.http
197            .get("/v5/market/funding/history", Some(params))
198            .await
199    }
200
201    /// Get public trading history (recent trades).
202    ///
203    /// # Example
204    ///
205    /// ```no_run
206    /// # use bybit_client::{BybitClient, Category};
207    /// # use bybit_client::api::market::GetPublicTradingHistoryParams;
208    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
209    /// let client = BybitClient::public_only()?;
210    /// let params = GetPublicTradingHistoryParams::new(Category::Linear, "BTCUSDT")
211    ///     .limit(50);
212    /// let result = client.market().get_public_trading_history(&params).await?;
213    /// for trade in &result.list {
214    ///     println!("{}: {} {} @ {}", trade.time, trade.side, trade.size, trade.price);
215    /// }
216    /// # Ok(())
217    /// # }
218    /// ```
219    pub async fn get_public_trading_history(
220        &self,
221        params: &GetPublicTradingHistoryParams,
222    ) -> Result<PublicTradeResult, BybitError> {
223        self.http
224            .get("/v5/market/recent-trade", Some(params))
225            .await
226    }
227
228    /// Get open interest.
229    pub async fn get_open_interest(
230        &self,
231        params: &GetOpenInterestParams,
232    ) -> Result<OpenInterestResult, BybitError> {
233        self.http
234            .get("/v5/market/open-interest", Some(params))
235            .await
236    }
237
238    /// Get historical volatility (options only).
239    pub async fn get_historical_volatility(
240        &self,
241        params: &GetHistoricalVolatilityParams,
242    ) -> Result<Vec<HistoricalVolatility>, BybitError> {
243        self.http
244            .get("/v5/market/historical-volatility", Some(params))
245            .await
246    }
247
248    /// Get insurance fund data.
249    pub async fn get_insurance(
250        &self,
251        coin: Option<&str>,
252    ) -> Result<InsuranceResult, BybitError> {
253        #[derive(Serialize)]
254        struct Params<'a> {
255            #[serde(skip_serializing_if = "Option::is_none")]
256            coin: Option<&'a str>,
257        }
258        self.http
259            .get("/v5/market/insurance", Some(&Params { coin }))
260            .await
261    }
262
263    /// Get risk limit information.
264    pub async fn get_risk_limit(
265        &self,
266        params: &GetRiskLimitParams,
267    ) -> Result<RiskLimitResult, BybitError> {
268        self.http.get("/v5/market/risk-limit", Some(params)).await
269    }
270
271    /// Get delivery price (futures/options).
272    pub async fn get_delivery_price(
273        &self,
274        params: &GetDeliveryPriceParams,
275    ) -> Result<DeliveryPriceResult, BybitError> {
276        self.http
277            .get("/v5/market/delivery-price", Some(params))
278            .await
279    }
280
281    /// Get long/short ratio.
282    pub async fn get_long_short_ratio(
283        &self,
284        params: &GetLongShortRatioParams,
285    ) -> Result<LongShortRatioResult, BybitError> {
286        self.http
287            .get("/v5/market/account-ratio", Some(params))
288            .await
289    }
290}
291
292
293/// Parameters for getting kline data.
294#[derive(Debug, Clone, Serialize)]
295#[serde(rename_all = "camelCase")]
296pub struct GetKlineParams {
297    /// Product category.
298    pub category: Category,
299    /// Trading symbol.
300    pub symbol: String,
301    /// Kline interval.
302    pub interval: String,
303    /// Start time (ms).
304    #[serde(skip_serializing_if = "Option::is_none")]
305    pub start: Option<u64>,
306    /// End time (ms).
307    #[serde(skip_serializing_if = "Option::is_none")]
308    pub end: Option<u64>,
309    /// Limit (max 1000).
310    #[serde(skip_serializing_if = "Option::is_none")]
311    pub limit: Option<u32>,
312}
313
314impl GetKlineParams {
315    /// Create new kline parameters.
316    pub fn new(category: Category, symbol: impl Into<String>, interval: KlineInterval) -> Self {
317        Self {
318            category,
319            symbol: symbol.into(),
320            interval: interval.as_str().to_string(),
321            start: None,
322            end: None,
323            limit: None,
324        }
325    }
326
327    /// Set start time.
328    pub fn start(mut self, start: u64) -> Self {
329        self.start = Some(start);
330        self
331    }
332
333    /// Set end time.
334    pub fn end(mut self, end: u64) -> Self {
335        self.end = Some(end);
336        self
337    }
338
339    /// Set limit.
340    pub fn limit(mut self, limit: u32) -> Self {
341        self.limit = Some(limit);
342        self
343    }
344}
345
346/// Parameters for getting instruments info.
347#[derive(Debug, Clone, Serialize)]
348#[serde(rename_all = "camelCase")]
349pub struct GetInstrumentsInfoParams {
350    /// Product category.
351    pub category: Category,
352    /// Trading symbol (optional).
353    #[serde(skip_serializing_if = "Option::is_none")]
354    pub symbol: Option<String>,
355    /// Symbol status filter.
356    #[serde(skip_serializing_if = "Option::is_none")]
357    pub status: Option<String>,
358    /// Base coin filter.
359    #[serde(skip_serializing_if = "Option::is_none")]
360    pub base_coin: Option<String>,
361    /// Limit.
362    #[serde(skip_serializing_if = "Option::is_none")]
363    pub limit: Option<u32>,
364    /// Cursor for pagination.
365    #[serde(skip_serializing_if = "Option::is_none")]
366    pub cursor: Option<String>,
367}
368
369impl GetInstrumentsInfoParams {
370    /// Create new parameters.
371    pub fn new(category: Category) -> Self {
372        Self {
373            category,
374            symbol: None,
375            status: None,
376            base_coin: None,
377            limit: None,
378            cursor: None,
379        }
380    }
381
382    /// Set symbol filter.
383    pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
384        self.symbol = Some(symbol.into());
385        self
386    }
387
388    /// Set status filter.
389    pub fn status(mut self, status: impl Into<String>) -> Self {
390        self.status = Some(status.into());
391        self
392    }
393
394    /// Set base coin filter.
395    pub fn base_coin(mut self, base_coin: impl Into<String>) -> Self {
396        self.base_coin = Some(base_coin.into());
397        self
398    }
399
400    /// Set limit.
401    pub fn limit(mut self, limit: u32) -> Self {
402        self.limit = Some(limit);
403        self
404    }
405
406    /// Set cursor.
407    pub fn cursor(mut self, cursor: impl Into<String>) -> Self {
408        self.cursor = Some(cursor.into());
409        self
410    }
411}
412
413/// Parameters for getting order book.
414#[derive(Debug, Clone, Serialize)]
415#[serde(rename_all = "camelCase")]
416pub struct GetOrderbookParams {
417    /// Product category.
418    pub category: Category,
419    /// Trading symbol.
420    pub symbol: String,
421    /// Depth limit (1, 25, 50, 100, 200, 500).
422    #[serde(skip_serializing_if = "Option::is_none")]
423    pub limit: Option<u32>,
424}
425
426impl GetOrderbookParams {
427    /// Create new parameters.
428    pub fn new(category: Category, symbol: impl Into<String>) -> Self {
429        Self {
430            category,
431            symbol: symbol.into(),
432            limit: None,
433        }
434    }
435
436    /// Set depth limit.
437    pub fn limit(mut self, limit: u32) -> Self {
438        self.limit = Some(limit);
439        self
440    }
441}
442
443/// Parameters for getting tickers.
444#[derive(Debug, Clone, Serialize)]
445#[serde(rename_all = "camelCase")]
446pub struct GetTickersParams {
447    /// Product category.
448    pub category: Category,
449    /// Trading symbol (optional - if not set, returns all).
450    #[serde(skip_serializing_if = "Option::is_none")]
451    pub symbol: Option<String>,
452    /// Base coin filter.
453    #[serde(skip_serializing_if = "Option::is_none")]
454    pub base_coin: Option<String>,
455    /// Expiry date filter (options).
456    #[serde(skip_serializing_if = "Option::is_none")]
457    pub exp_date: Option<String>,
458}
459
460impl GetTickersParams {
461    /// Create new parameters.
462    pub fn new(category: Category) -> Self {
463        Self {
464            category,
465            symbol: None,
466            base_coin: None,
467            exp_date: None,
468        }
469    }
470
471    /// Set symbol filter.
472    pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
473        self.symbol = Some(symbol.into());
474        self
475    }
476
477    /// Set base coin filter.
478    pub fn base_coin(mut self, base_coin: impl Into<String>) -> Self {
479        self.base_coin = Some(base_coin.into());
480        self
481    }
482
483    /// Set expiry date filter.
484    pub fn exp_date(mut self, exp_date: impl Into<String>) -> Self {
485        self.exp_date = Some(exp_date.into());
486        self
487    }
488}
489
490/// Parameters for getting funding rate history.
491#[derive(Debug, Clone, Serialize)]
492#[serde(rename_all = "camelCase")]
493pub struct GetFundingRateHistoryParams {
494    /// Product category.
495    pub category: Category,
496    /// Trading symbol.
497    pub symbol: String,
498    /// Start time (ms).
499    #[serde(skip_serializing_if = "Option::is_none")]
500    pub start_time: Option<u64>,
501    /// End time (ms).
502    #[serde(skip_serializing_if = "Option::is_none")]
503    pub end_time: Option<u64>,
504    /// Limit.
505    #[serde(skip_serializing_if = "Option::is_none")]
506    pub limit: Option<u32>,
507}
508
509impl GetFundingRateHistoryParams {
510    /// Create new parameters.
511    pub fn new(category: Category, symbol: impl Into<String>) -> Self {
512        Self {
513            category,
514            symbol: symbol.into(),
515            start_time: None,
516            end_time: None,
517            limit: None,
518        }
519    }
520
521    /// Set start time.
522    pub fn start_time(mut self, start_time: u64) -> Self {
523        self.start_time = Some(start_time);
524        self
525    }
526
527    /// Set end time.
528    pub fn end_time(mut self, end_time: u64) -> Self {
529        self.end_time = Some(end_time);
530        self
531    }
532
533    /// Set limit.
534    pub fn limit(mut self, limit: u32) -> Self {
535        self.limit = Some(limit);
536        self
537    }
538}
539
540/// Parameters for getting public trading history.
541#[derive(Debug, Clone, Serialize)]
542#[serde(rename_all = "camelCase")]
543pub struct GetPublicTradingHistoryParams {
544    /// Product category.
545    pub category: Category,
546    /// Trading symbol.
547    pub symbol: String,
548    /// Base coin filter (option only).
549    #[serde(skip_serializing_if = "Option::is_none")]
550    pub base_coin: Option<String>,
551    /// Option type (option only).
552    #[serde(skip_serializing_if = "Option::is_none")]
553    pub option_type: Option<String>,
554    /// Limit.
555    #[serde(skip_serializing_if = "Option::is_none")]
556    pub limit: Option<u32>,
557}
558
559impl GetPublicTradingHistoryParams {
560    /// Create new parameters.
561    pub fn new(category: Category, symbol: impl Into<String>) -> Self {
562        Self {
563            category,
564            symbol: symbol.into(),
565            base_coin: None,
566            option_type: None,
567            limit: None,
568        }
569    }
570
571    /// Set limit.
572    pub fn limit(mut self, limit: u32) -> Self {
573        self.limit = Some(limit);
574        self
575    }
576}
577
578/// Parameters for getting open interest.
579#[derive(Debug, Clone, Serialize)]
580#[serde(rename_all = "camelCase")]
581pub struct GetOpenInterestParams {
582    /// Product category.
583    pub category: Category,
584    /// Trading symbol.
585    pub symbol: String,
586    /// Interval (5min, 15min, 30min, 1h, 4h, 1d).
587    pub interval_time: String,
588    /// Start time (ms).
589    #[serde(skip_serializing_if = "Option::is_none")]
590    pub start_time: Option<u64>,
591    /// End time (ms).
592    #[serde(skip_serializing_if = "Option::is_none")]
593    pub end_time: Option<u64>,
594    /// Limit.
595    #[serde(skip_serializing_if = "Option::is_none")]
596    pub limit: Option<u32>,
597    /// Cursor for pagination.
598    #[serde(skip_serializing_if = "Option::is_none")]
599    pub cursor: Option<String>,
600}
601
602impl GetOpenInterestParams {
603    /// Create new parameters.
604    pub fn new(
605        category: Category,
606        symbol: impl Into<String>,
607        interval_time: impl Into<String>,
608    ) -> Self {
609        Self {
610            category,
611            symbol: symbol.into(),
612            interval_time: interval_time.into(),
613            start_time: None,
614            end_time: None,
615            limit: None,
616            cursor: None,
617        }
618    }
619
620    /// Set limit.
621    pub fn limit(mut self, limit: u32) -> Self {
622        self.limit = Some(limit);
623        self
624    }
625}
626
627/// Parameters for getting historical volatility.
628#[derive(Debug, Clone, Serialize)]
629#[serde(rename_all = "camelCase")]
630pub struct GetHistoricalVolatilityParams {
631    /// Product category (must be option).
632    pub category: Category,
633    /// Base coin (e.g., "BTC").
634    #[serde(skip_serializing_if = "Option::is_none")]
635    pub base_coin: Option<String>,
636    /// Period (days).
637    #[serde(skip_serializing_if = "Option::is_none")]
638    pub period: Option<i32>,
639    /// Start time (ms).
640    #[serde(skip_serializing_if = "Option::is_none")]
641    pub start_time: Option<u64>,
642    /// End time (ms).
643    #[serde(skip_serializing_if = "Option::is_none")]
644    pub end_time: Option<u64>,
645}
646
647impl GetHistoricalVolatilityParams {
648    /// Create new parameters.
649    pub fn new(category: Category) -> Self {
650        Self {
651            category,
652            base_coin: None,
653            period: None,
654            start_time: None,
655            end_time: None,
656        }
657    }
658}
659
660/// Parameters for getting risk limit.
661#[derive(Debug, Clone, Serialize)]
662#[serde(rename_all = "camelCase")]
663pub struct GetRiskLimitParams {
664    /// Product category.
665    pub category: Category,
666    /// Trading symbol (optional).
667    #[serde(skip_serializing_if = "Option::is_none")]
668    pub symbol: Option<String>,
669}
670
671impl GetRiskLimitParams {
672    /// Create new parameters.
673    pub fn new(category: Category) -> Self {
674        Self {
675            category,
676            symbol: None,
677        }
678    }
679
680    /// Set symbol.
681    pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
682        self.symbol = Some(symbol.into());
683        self
684    }
685}
686
687/// Parameters for getting delivery price.
688#[derive(Debug, Clone, Serialize)]
689#[serde(rename_all = "camelCase")]
690pub struct GetDeliveryPriceParams {
691    /// Product category.
692    pub category: Category,
693    /// Trading symbol (optional).
694    #[serde(skip_serializing_if = "Option::is_none")]
695    pub symbol: Option<String>,
696    /// Base coin filter.
697    #[serde(skip_serializing_if = "Option::is_none")]
698    pub base_coin: Option<String>,
699    /// Limit.
700    #[serde(skip_serializing_if = "Option::is_none")]
701    pub limit: Option<u32>,
702    /// Cursor for pagination.
703    #[serde(skip_serializing_if = "Option::is_none")]
704    pub cursor: Option<String>,
705}
706
707impl GetDeliveryPriceParams {
708    /// Create new parameters.
709    pub fn new(category: Category) -> Self {
710        Self {
711            category,
712            symbol: None,
713            base_coin: None,
714            limit: None,
715            cursor: None,
716        }
717    }
718}
719
720/// Parameters for getting long/short ratio.
721#[derive(Debug, Clone, Serialize)]
722#[serde(rename_all = "camelCase")]
723pub struct GetLongShortRatioParams {
724    /// Product category.
725    pub category: Category,
726    /// Trading symbol.
727    pub symbol: String,
728    /// Data recording period (5min, 15min, 30min, 1h, 4h, 1d).
729    pub period: String,
730    /// Limit.
731    #[serde(skip_serializing_if = "Option::is_none")]
732    pub limit: Option<u32>,
733}
734
735impl GetLongShortRatioParams {
736    /// Create new parameters.
737    pub fn new(category: Category, symbol: impl Into<String>, period: impl Into<String>) -> Self {
738        Self {
739            category,
740            symbol: symbol.into(),
741            period: period.into(),
742            limit: None,
743        }
744    }
745
746    /// Set limit.
747    pub fn limit(mut self, limit: u32) -> Self {
748        self.limit = Some(limit);
749        self
750    }
751}
752
753#[cfg(test)]
754mod tests {
755    use super::*;
756
757    #[test]
758    fn test_kline_params_serialization() {
759        let params =
760            GetKlineParams::new(Category::Linear, "BTCUSDT", KlineInterval::Hour1).limit(100);
761
762        let serialized = match serde_urlencoded::to_string(&params) {
763            Ok(serialized) => serialized,
764            Err(err) => panic!("Failed to serialize kline params: {}", err),
765        };
766        assert!(serialized.contains("category=linear"));
767        assert!(serialized.contains("symbol=BTCUSDT"));
768        assert!(serialized.contains("interval=60"));
769        assert!(serialized.contains("limit=100"));
770    }
771
772    #[test]
773    fn test_orderbook_params() {
774        let params = GetOrderbookParams::new(Category::Spot, "BTCUSDT").limit(25);
775        assert_eq!(params.symbol, "BTCUSDT");
776        assert_eq!(params.limit, Some(25));
777    }
778}