1use serde::Serialize;
4
5use crate::error::BybitError;
6use crate::http::HttpClient;
7use crate::types::market::*;
8use crate::types::{Category, KlineInterval, ServerTime};
9
10#[derive(Debug, Clone)]
12pub struct MarketService {
13 http: HttpClient,
14}
15
16impl MarketService {
17 pub fn new(http: HttpClient) -> Self {
19 Self { http }
20 }
21
22 pub async fn get_server_time(&self) -> Result<ServerTime, BybitError> {
36 self.http.get("/v5/market/time", None::<&()>).await
37 }
38
39 pub async fn get_kline(&self, params: &GetKlineParams) -> Result<KlineResult, BybitError> {
67 self.http.get("/v5/market/kline", Some(params)).await
68 }
69
70 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 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 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 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 pub async fn get_orderbook(&self, params: &GetOrderbookParams) -> Result<Orderbook, BybitError> {
149 self.http.get("/v5/market/orderbook", Some(params)).await
150 }
151
152 pub async fn get_tickers(&self, params: &GetTickersParams) -> Result<TickerResult, BybitError> {
171 self.http.get("/v5/market/tickers", Some(params)).await
172 }
173
174 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 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 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 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 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 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 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 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#[derive(Debug, Clone, Serialize)]
295#[serde(rename_all = "camelCase")]
296pub struct GetKlineParams {
297 pub category: Category,
299 pub symbol: String,
301 pub interval: String,
303 #[serde(skip_serializing_if = "Option::is_none")]
305 pub start: Option<u64>,
306 #[serde(skip_serializing_if = "Option::is_none")]
308 pub end: Option<u64>,
309 #[serde(skip_serializing_if = "Option::is_none")]
311 pub limit: Option<u32>,
312}
313
314impl GetKlineParams {
315 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 pub fn start(mut self, start: u64) -> Self {
329 self.start = Some(start);
330 self
331 }
332
333 pub fn end(mut self, end: u64) -> Self {
335 self.end = Some(end);
336 self
337 }
338
339 pub fn limit(mut self, limit: u32) -> Self {
341 self.limit = Some(limit);
342 self
343 }
344}
345
346#[derive(Debug, Clone, Serialize)]
348#[serde(rename_all = "camelCase")]
349pub struct GetInstrumentsInfoParams {
350 pub category: Category,
352 #[serde(skip_serializing_if = "Option::is_none")]
354 pub symbol: Option<String>,
355 #[serde(skip_serializing_if = "Option::is_none")]
357 pub status: Option<String>,
358 #[serde(skip_serializing_if = "Option::is_none")]
360 pub base_coin: Option<String>,
361 #[serde(skip_serializing_if = "Option::is_none")]
363 pub limit: Option<u32>,
364 #[serde(skip_serializing_if = "Option::is_none")]
366 pub cursor: Option<String>,
367}
368
369impl GetInstrumentsInfoParams {
370 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 pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
384 self.symbol = Some(symbol.into());
385 self
386 }
387
388 pub fn status(mut self, status: impl Into<String>) -> Self {
390 self.status = Some(status.into());
391 self
392 }
393
394 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 pub fn limit(mut self, limit: u32) -> Self {
402 self.limit = Some(limit);
403 self
404 }
405
406 pub fn cursor(mut self, cursor: impl Into<String>) -> Self {
408 self.cursor = Some(cursor.into());
409 self
410 }
411}
412
413#[derive(Debug, Clone, Serialize)]
415#[serde(rename_all = "camelCase")]
416pub struct GetOrderbookParams {
417 pub category: Category,
419 pub symbol: String,
421 #[serde(skip_serializing_if = "Option::is_none")]
423 pub limit: Option<u32>,
424}
425
426impl GetOrderbookParams {
427 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 pub fn limit(mut self, limit: u32) -> Self {
438 self.limit = Some(limit);
439 self
440 }
441}
442
443#[derive(Debug, Clone, Serialize)]
445#[serde(rename_all = "camelCase")]
446pub struct GetTickersParams {
447 pub category: Category,
449 #[serde(skip_serializing_if = "Option::is_none")]
451 pub symbol: Option<String>,
452 #[serde(skip_serializing_if = "Option::is_none")]
454 pub base_coin: Option<String>,
455 #[serde(skip_serializing_if = "Option::is_none")]
457 pub exp_date: Option<String>,
458}
459
460impl GetTickersParams {
461 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 pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
473 self.symbol = Some(symbol.into());
474 self
475 }
476
477 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 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#[derive(Debug, Clone, Serialize)]
492#[serde(rename_all = "camelCase")]
493pub struct GetFundingRateHistoryParams {
494 pub category: Category,
496 pub symbol: String,
498 #[serde(skip_serializing_if = "Option::is_none")]
500 pub start_time: Option<u64>,
501 #[serde(skip_serializing_if = "Option::is_none")]
503 pub end_time: Option<u64>,
504 #[serde(skip_serializing_if = "Option::is_none")]
506 pub limit: Option<u32>,
507}
508
509impl GetFundingRateHistoryParams {
510 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 pub fn start_time(mut self, start_time: u64) -> Self {
523 self.start_time = Some(start_time);
524 self
525 }
526
527 pub fn end_time(mut self, end_time: u64) -> Self {
529 self.end_time = Some(end_time);
530 self
531 }
532
533 pub fn limit(mut self, limit: u32) -> Self {
535 self.limit = Some(limit);
536 self
537 }
538}
539
540#[derive(Debug, Clone, Serialize)]
542#[serde(rename_all = "camelCase")]
543pub struct GetPublicTradingHistoryParams {
544 pub category: Category,
546 pub symbol: String,
548 #[serde(skip_serializing_if = "Option::is_none")]
550 pub base_coin: Option<String>,
551 #[serde(skip_serializing_if = "Option::is_none")]
553 pub option_type: Option<String>,
554 #[serde(skip_serializing_if = "Option::is_none")]
556 pub limit: Option<u32>,
557}
558
559impl GetPublicTradingHistoryParams {
560 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 pub fn limit(mut self, limit: u32) -> Self {
573 self.limit = Some(limit);
574 self
575 }
576}
577
578#[derive(Debug, Clone, Serialize)]
580#[serde(rename_all = "camelCase")]
581pub struct GetOpenInterestParams {
582 pub category: Category,
584 pub symbol: String,
586 pub interval_time: String,
588 #[serde(skip_serializing_if = "Option::is_none")]
590 pub start_time: Option<u64>,
591 #[serde(skip_serializing_if = "Option::is_none")]
593 pub end_time: Option<u64>,
594 #[serde(skip_serializing_if = "Option::is_none")]
596 pub limit: Option<u32>,
597 #[serde(skip_serializing_if = "Option::is_none")]
599 pub cursor: Option<String>,
600}
601
602impl GetOpenInterestParams {
603 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 pub fn limit(mut self, limit: u32) -> Self {
622 self.limit = Some(limit);
623 self
624 }
625}
626
627#[derive(Debug, Clone, Serialize)]
629#[serde(rename_all = "camelCase")]
630pub struct GetHistoricalVolatilityParams {
631 pub category: Category,
633 #[serde(skip_serializing_if = "Option::is_none")]
635 pub base_coin: Option<String>,
636 #[serde(skip_serializing_if = "Option::is_none")]
638 pub period: Option<i32>,
639 #[serde(skip_serializing_if = "Option::is_none")]
641 pub start_time: Option<u64>,
642 #[serde(skip_serializing_if = "Option::is_none")]
644 pub end_time: Option<u64>,
645}
646
647impl GetHistoricalVolatilityParams {
648 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#[derive(Debug, Clone, Serialize)]
662#[serde(rename_all = "camelCase")]
663pub struct GetRiskLimitParams {
664 pub category: Category,
666 #[serde(skip_serializing_if = "Option::is_none")]
668 pub symbol: Option<String>,
669}
670
671impl GetRiskLimitParams {
672 pub fn new(category: Category) -> Self {
674 Self {
675 category,
676 symbol: None,
677 }
678 }
679
680 pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
682 self.symbol = Some(symbol.into());
683 self
684 }
685}
686
687#[derive(Debug, Clone, Serialize)]
689#[serde(rename_all = "camelCase")]
690pub struct GetDeliveryPriceParams {
691 pub category: Category,
693 #[serde(skip_serializing_if = "Option::is_none")]
695 pub symbol: Option<String>,
696 #[serde(skip_serializing_if = "Option::is_none")]
698 pub base_coin: Option<String>,
699 #[serde(skip_serializing_if = "Option::is_none")]
701 pub limit: Option<u32>,
702 #[serde(skip_serializing_if = "Option::is_none")]
704 pub cursor: Option<String>,
705}
706
707impl GetDeliveryPriceParams {
708 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#[derive(Debug, Clone, Serialize)]
722#[serde(rename_all = "camelCase")]
723pub struct GetLongShortRatioParams {
724 pub category: Category,
726 pub symbol: String,
728 pub period: String,
730 #[serde(skip_serializing_if = "Option::is_none")]
732 pub limit: Option<u32>,
733}
734
735impl GetLongShortRatioParams {
736 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 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(¶ms) {
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}