Skip to main content

bybit/http/
market.rs

1use rust_decimal::{Decimal, serde::str_option::deserialize as option_decimal};
2use serde::{Deserialize, Serialize};
3use serde_aux::prelude::{
4    deserialize_number_from_string as number,
5    deserialize_option_number_from_string as option_number,
6};
7
8use crate::{
9    ContractType, CopyTrading, CurAuctionPhase, OptionType, Side, Status, Timestamp,
10    enums::{Category, Interval, IntervalTime},
11    serde::{empty_string_as_none, int_to_bool, string_to_bool},
12};
13
14/// Query parameters of `GET /v5/market/kline` ([`Client::get_index_price_kline`](crate::http::Client::get_index_price_kline), [`Client::get_kline`](crate::http::Client::get_kline), [`Client::get_mark_price_kline`](crate::http::Client::get_mark_price_kline), [`Client::get_premium_index_price_kline`](crate::http::Client::get_premium_index_price_kline)).
15#[derive(Debug, Serialize, Clone)]
16pub struct GetKLinesParams {
17    /// Product type. linear, inverse When category is not passed, use linear by default.
18    pub category: Category,
19    /// Symbol name, like BTCUSDT, uppercase only.
20    pub symbol: String,
21    /// Kline interval. 1, 3, 5, 15, 30, 60, 120, 240, 360, 720, D, W, M.
22    pub interval: Interval,
23    /// The start timestamp (ms).
24    pub start: Option<Timestamp>,
25    /// The end timestamp (ms).
26    pub end: Option<Timestamp>,
27    /// Limit for data size per page. \[ 1, 1000 \]. Default: 200.
28    pub limit: Option<u64>,
29}
30
31impl GetKLinesParams {
32    /// Create with the required fields; set the optional ones with the `with_*` methods.
33    pub fn new(category: Category, symbol: impl Into<String>, interval: Interval) -> Self {
34        Self {
35            category,
36            symbol: symbol.into(),
37            interval,
38            start: None,
39            end: None,
40            limit: None,
41        }
42    }
43
44    /// Set `start`: the start timestamp (ms).
45    pub fn with_start(mut self, v: Timestamp) -> Self {
46        self.start = Some(v);
47        self
48    }
49
50    /// Set `end`: the end timestamp (ms).
51    pub fn with_end(mut self, v: Timestamp) -> Self {
52        self.end = Some(v);
53        self
54    }
55
56    /// Set `limit`: limit for data size per page. \[ 1, 1000 \]. Default: 200.
57    pub fn with_limit(mut self, v: u64) -> Self {
58        self.limit = Some(v);
59        self
60    }
61}
62
63/// Result of `GET /v5/market/kline` ([`Client::get_kline`](crate::http::Client::get_kline)).
64#[derive(Debug, Deserialize, PartialEq)]
65#[serde(tag = "category")]
66pub enum KLine {
67    /// Inverse contracts.
68    #[serde(rename = "inverse")]
69    Inverse {
70        /// Symbol name.
71        symbol: String,
72        /// An string array of individual candle Sort in reverse by startTime.
73        list: Vec<KLineRow>,
74    },
75    /// Linear (USDT/USDC) contracts.
76    #[serde(rename = "linear")]
77    Linear {
78        /// Symbol name.
79        symbol: String,
80        /// An string array of individual candle Sort in reverse by startTime.
81        list: Vec<KLineRow>,
82    },
83    /// Options.
84    #[serde(rename = "option")]
85    Option {
86        /// Symbol name.
87        symbol: String,
88        /// An string array of individual candle Sort in reverse by startTime.
89        list: Vec<KLineRow>,
90    },
91    /// Spot.
92    #[serde(rename = "spot")]
93    Spot {
94        /// Symbol name.
95        symbol: String,
96        /// An string array of individual candle Sort in reverse by startTime.
97        list: Vec<KLineRow>,
98    },
99}
100
101/// Result of the mark, index and premium index price kline endpoints: rows
102/// carry prices only (no volume or turnover).
103#[derive(Debug, Deserialize, PartialEq)]
104pub struct PriceKLine {
105    /// Product type.
106    pub category: Category,
107    /// Symbol name.
108    pub symbol: String,
109    /// Klines, newest first.
110    pub list: Vec<PriceKLineRow>,
111}
112
113/// A price-only kline row: `[startTime, open, high, low, close]`.
114#[derive(Debug, Deserialize, PartialEq)]
115pub struct PriceKLineRow {
116    /// Start time of the candle, milliseconds.
117    #[serde(deserialize_with = "number")]
118    pub start_time: Timestamp,
119    /// Open price.
120    pub open_price: Decimal,
121    /// Highest price.
122    pub high_price: Decimal,
123    /// Lowest price.
124    pub low_price: Decimal,
125    /// Close price; the latest price while the candle is open.
126    pub close_price: Decimal,
127}
128
129/// Part of the response of `/v5/market/kline`.
130#[derive(Debug, Deserialize, PartialEq)]
131pub struct KLineRow {
132    /// Start time of the candle (ms)
133    #[serde(rename = "startTime", deserialize_with = "number")]
134    pub start_time: Timestamp,
135    /// Open price
136    #[serde(rename = "openPrice")]
137    pub open_price: Decimal,
138    /// Highest price
139    #[serde(rename = "highPrice")]
140    pub high_price: Decimal,
141    /// Lowest price
142    #[serde(rename = "lowPrice")]
143    pub low_price: Decimal,
144    /// Close price. Is the last traded price when the candle is not closed
145    #[serde(rename = "closePrice")]
146    pub close_price: Decimal,
147    /// Trade volume. Unit of contract: pieces of contract. Unit of spot: quantity of coins
148    #[serde(rename = "volume")]
149    pub volume: Decimal,
150    /// Turnover. Unit of figure: quantity of quota coin
151    #[serde(rename = "turnover")]
152    pub turnover: Decimal,
153}
154
155/// Query parameters of `GET /v5/market/tickers` ([`Client::get_tickers`](crate::http::Client::get_tickers)).
156#[derive(Debug, Serialize, Clone)]
157#[serde(rename_all = "camelCase")]
158pub struct GetTickersParams {
159    /// Product type. spot, linear, inverse, option.
160    pub category: Category,
161    /// Symbol name, like BTCUSDT, uppercase only.
162    pub symbol: Option<String>,
163    /// Base coin, uppercase only. Apply to option only.
164    pub base_coin: Option<String>,
165    /// Expiry date. e.g., 25DEC22. Apply to option only.
166    pub exp_date: Option<String>,
167}
168
169impl GetTickersParams {
170    /// Create with the required fields; set the optional ones with the `with_*` methods.
171    pub fn new(category: Category) -> Self {
172        Self {
173            category,
174            symbol: None,
175            base_coin: None,
176            exp_date: None,
177        }
178    }
179
180    /// Set `symbol`: symbol name, like BTCUSDT, uppercase only.
181    pub fn with_symbol(mut self, v: impl Into<String>) -> Self {
182        self.symbol = Some(v.into());
183        self
184    }
185
186    /// Set `base_coin`: base coin, uppercase only. Apply to option only.
187    pub fn with_base_coin(mut self, v: impl Into<String>) -> Self {
188        self.base_coin = Some(v.into());
189        self
190    }
191
192    /// Set `exp_date`: expiry date. e.g., 25DEC22. Apply to option only.
193    pub fn with_exp_date(mut self, v: impl Into<String>) -> Self {
194        self.exp_date = Some(v.into());
195        self
196    }
197}
198
199/// Result of `GET /v5/market/tickers` ([`Client::get_tickers`](crate::http::Client::get_tickers)).
200#[derive(Debug, Deserialize, PartialEq)]
201#[serde(tag = "category")]
202pub enum Ticker {
203    /// Inverse contracts.
204    #[serde(rename = "inverse")]
205    Inverse {
206        /// Items.
207        list: Vec<LinearInverseTicker>,
208    },
209    /// Linear (USDT/USDC) contracts.
210    #[serde(rename = "linear")]
211    Linear {
212        /// Items.
213        list: Vec<LinearInverseTicker>,
214    },
215    /// Options.
216    #[serde(rename = "option")]
217    Option {
218        /// Items.
219        list: Vec<OptionTicker>,
220    },
221    /// Spot.
222    #[serde(rename = "spot")]
223    Spot {
224        /// Items.
225        list: Vec<SpotTicker>,
226    },
227}
228
229/// Part of the response of `/v5/market/tickers`.
230#[derive(Debug, Deserialize, PartialEq)]
231#[serde(rename_all = "camelCase")]
232pub struct LinearInverseTicker {
233    /// Symbol name
234    pub symbol: String,
235    /// Last price
236    pub last_price: Decimal,
237    /// Mark price
238    pub mark_price: Decimal,
239    /// Index price
240    pub index_price: Decimal,
241    /// Market price 24 hours ago
242    pub prev_price24h: Decimal,
243    /// Percentage change of market price in the last 24 hours
244    pub price24h_pcnt: Decimal,
245    /// The highest price in the last 24 hours
246    pub high_price24h: Decimal,
247    /// The lowest price in the last 24 hours
248    pub low_price24h: Decimal,
249    /// Market price an hour ago
250    pub prev_price1h: Decimal,
251    /// Open interest size
252    pub open_interest: Decimal,
253    /// Open interest value
254    pub open_interest_value: Decimal,
255    /// Turnover for 24h
256    pub turnover24h: Decimal,
257    /// Volume for 24h
258    pub volume24h: Decimal,
259    /// Funding rate
260    #[serde(default, deserialize_with = "option_decimal")]
261    pub funding_rate: Option<Decimal>,
262    /// Next funding timestamp (ms)
263    #[serde(deserialize_with = "number")]
264    pub next_funding_time: Timestamp,
265    /// Predicated delivery price. It has value when 30 min before delivery
266    #[serde(default, deserialize_with = "option_decimal")]
267    pub predicted_delivery_price: Option<Decimal>,
268    /// Basis rate. Unique field for inverse futures & USDC futures
269    #[serde(default, deserialize_with = "option_decimal")]
270    pub basis_rate: Option<Decimal>,
271    /// Basis. Unique field for inverse futures & USDC futures
272    #[serde(default, deserialize_with = "option_decimal")]
273    pub basis: Option<Decimal>,
274    /// Delivery fee rate. Unique field for inverse futures & USDC futures
275    #[serde(default, deserialize_with = "option_decimal")]
276    pub delivery_fee_rate: Option<Decimal>,
277    /// Delivery date time (UTC+0). Unique field for inverse futures & USDC futures
278    #[serde(deserialize_with = "option_number")]
279    pub delivery_time: Option<Timestamp>,
280    /// Best bid price
281    pub bid1_price: Decimal,
282    /// Best bid size
283    pub bid1_size: Decimal,
284    /// Best ask price
285    pub ask1_price: Decimal,
286    /// Best ask size
287    pub ask1_size: Decimal,
288    /// Estimated pre-market contract open price. The value is meaningless when entering continuous trading phase.
289    #[serde(default, deserialize_with = "option_decimal")]
290    pub pre_open_price: Option<Decimal>,
291    /// Estimated pre-market contract open qty. The value is meaningless when entering continuous trading phase.
292    #[serde(default, deserialize_with = "option_decimal")]
293    pub pre_qty: Option<Decimal>,
294    /// Enum: NotStarted, Finished, CallAuction, CallAuctionNoCancel, CrossMatching, ContinuousTrading.
295    #[serde(default, deserialize_with = "empty_string_as_none")]
296    pub cur_pre_listing_phase: Option<CurAuctionPhase>,
297}
298
299/// Part of the response of `/v5/market/tickers`.
300#[derive(Debug, Deserialize, PartialEq)]
301#[serde(rename_all = "camelCase")]
302pub struct OptionTicker {
303    /// Symbol name
304    pub symbol: String,
305    /// Best bid price
306    pub bid1_price: Decimal,
307    /// Best bid size
308    pub bid1_size: Decimal,
309    /// Best bid iv
310    pub bid1_iv: Decimal,
311    /// Best ask price
312    pub ask1_price: Decimal,
313    /// Best ask size
314    pub ask1_size: Decimal,
315    /// Best ask iv
316    pub ask1_iv: Decimal,
317    /// Last price
318    pub last_price: Decimal,
319    /// The highest price in the last 24 hours
320    pub high_price24h: Decimal,
321    /// The lowest price in the last 24 hours
322    pub low_price24h: Decimal,
323    /// Mark price
324    pub mark_price: Decimal,
325    /// Index price
326    pub index_price: Decimal,
327    /// Mark price iv
328    pub mark_iv: Decimal,
329    /// Underlying price
330    pub underlying_price: Decimal,
331    /// Open interest size
332    pub open_interest: Decimal,
333    /// Turnover for 24h
334    pub turnover24h: Decimal,
335    /// Volume for 24h
336    pub volume24h: Decimal,
337    /// Total volume
338    pub total_volume: Decimal,
339    /// Total turnover
340    pub total_turnover: Decimal,
341    /// Delta
342    pub delta: Decimal,
343    /// Gamma
344    pub gamma: Decimal,
345    /// Vega
346    pub vega: Decimal,
347    /// Theta
348    pub theta: Decimal,
349    /// Predicated delivery price. It has value when 30 min before delivery
350    pub predicted_delivery_price: Decimal,
351    /// The change in the last 24 hours
352    pub change24h: Decimal,
353}
354
355/// Part of the response of `/v5/market/tickers`.
356#[derive(Debug, Deserialize, PartialEq)]
357#[serde(rename_all = "camelCase")]
358pub struct SpotTicker {
359    /// Symbol name
360    pub symbol: String,
361    /// Best bid price
362    pub bid1_price: Decimal,
363    /// Best bid size
364    pub bid1_size: Decimal,
365    /// Best ask price
366    pub ask1_price: Decimal,
367    /// Best ask size
368    pub ask1_size: Decimal,
369    /// Last price
370    pub last_price: Decimal,
371    /// Market price 24 hours ago
372    pub prev_price24h: Decimal,
373    /// Percentage change of market price in the last 24 hours
374    pub price24h_pcnt: Decimal,
375    /// The highest price in the last 24 hours
376    pub high_price24h: Decimal,
377    /// The lowest price in the last 24 hours
378    pub low_price24h: Decimal,
379    /// Turnover for 24h
380    pub turnover24h: Decimal,
381    /// Volume for 24h
382    pub volume24h: Decimal,
383    /// USD index price
384    /// - used to calculate USD value of the assets in Unified account
385    /// - non-collateral margin coin returns ""
386    /// - Only those trading pairs like "XXX/USDT" or "XXX/USDC" have the value
387    #[serde(default, deserialize_with = "option_decimal")]
388    pub usd_index_price: Option<Decimal>,
389}
390
391/// Query parameters of `GET /v5/market/orderbook` ([`Client::get_orderbook`](crate::http::Client::get_orderbook)).
392#[derive(Debug, Serialize, Clone)]
393pub struct GetOrderbookParams {
394    /// Product type. spot, linear, inverse, option.
395    pub category: Category,
396    /// Symbol name, like BTCUSDT, uppercase only.
397    pub symbol: String,
398    /// Limit size for each bid and ask
399    /// - spot: [1, 1000]. Default: 1.
400    /// - linear&inverse: [1, 1000]. Default: 25.
401    /// - option: [1, 25]. Default: 1.
402    pub limit: Option<u64>,
403}
404
405impl GetOrderbookParams {
406    /// Create with the required fields; set the optional ones with the `with_*` methods.
407    pub fn new(category: Category, symbol: impl Into<String>) -> Self {
408        Self {
409            category,
410            symbol: symbol.into(),
411            limit: None,
412        }
413    }
414
415    /// Set `limit`: limit size for each bid and ask - spot: [1, 1000]. Default: 1. - linear&inverse: [1, 1000]. Default: 25. - option: [1, 25]. Default: 1.
416    pub fn with_limit(mut self, v: u64) -> Self {
417        self.limit = Some(v);
418        self
419    }
420}
421
422/// Result of `GET /v5/market/orderbook` ([`Client::get_orderbook`](crate::http::Client::get_orderbook)).
423#[derive(Debug, Deserialize, PartialEq)]
424pub struct Orderbook {
425    /// Symbol name
426    #[serde(rename = "s")]
427    pub symbol: String,
428    /// Bid, buy side. Sorted by price in descending order
429    #[serde(rename = "b")]
430    pub bids: Vec<OrderbookLevel>,
431    /// Ask, sell side. Sorted by price in ascending order
432    #[serde(rename = "a")]
433    pub asks: Vec<OrderbookLevel>,
434    /// The timestamp (ms) that the system generates the data
435    pub ts: Timestamp,
436    /// Update ID, is a sequence. Occasionally, you'll receive "u"=1, which is a snapshot
437    /// data due to the restart of the service. So please overwrite your local orderbook
438    #[serde(rename = "u")]
439    pub update_id: i64,
440    /// Cross sequence. You can use this field to compare different levels orderbook data,
441    /// and for the smaller seq, then it means the data is generated earlier
442    pub seq: i64,
443    /// Cross timestamp (ms). Spot only
444    #[serde(default)]
445    pub cts: Option<Timestamp>,
446}
447
448/// Part of the response of `/v5/market/orderbook`.
449#[derive(Debug, Deserialize, PartialEq)]
450pub struct OrderbookLevel {
451    /// Price
452    pub price: Decimal,
453    /// Size
454    pub size: Decimal,
455}
456
457/// Query parameters of `GET /v5/market/recent-trade` ([`Client::get_public_recent_trading_history`](crate::http::Client::get_public_recent_trading_history)).
458#[derive(Debug, Serialize, Clone)]
459#[serde(rename_all = "camelCase")]
460pub struct GetTradesParams {
461    /// Product type. spot, linear, inverse, option.
462    pub category: Category,
463    /// required for spot/linear/inverse
464    /// optional for option
465    pub symbol: Option<String>,
466    /// Apply to option only
467    /// If the field is not passed, return BTC data by default
468    pub base_coin: Option<String>,
469    /// Option type. Apply to option only
470    pub option_type: Option<OptionType>,
471    /// spot: `[1, 60]`, default: 60
472    /// others: `[1, 1000]`, default: 500
473    pub limit: Option<u64>,
474}
475
476impl GetTradesParams {
477    /// Create with the required fields; set the optional ones with the `with_*` methods.
478    pub fn new(category: Category) -> Self {
479        Self {
480            category,
481            symbol: None,
482            base_coin: None,
483            option_type: None,
484            limit: None,
485        }
486    }
487
488    /// Set `symbol`: required for spot/linear/inverse optional for option.
489    pub fn with_symbol(mut self, v: impl Into<String>) -> Self {
490        self.symbol = Some(v.into());
491        self
492    }
493
494    /// Set `base_coin`: apply to option only If the field is not passed, return BTC data by default.
495    pub fn with_base_coin(mut self, v: impl Into<String>) -> Self {
496        self.base_coin = Some(v.into());
497        self
498    }
499
500    /// Set `option_type`: option type. Apply to option only.
501    pub fn with_option_type(mut self, v: OptionType) -> Self {
502        self.option_type = Some(v);
503        self
504    }
505
506    /// Set `limit`: spot: `[1, 60]`, default: 60 others: `[1, 1000]`, default: 500.
507    pub fn with_limit(mut self, v: u64) -> Self {
508        self.limit = Some(v);
509        self
510    }
511}
512
513/// Result of `GET /v5/market/recent-trade` ([`Client::get_public_recent_trading_history`](crate::http::Client::get_public_recent_trading_history)).
514#[derive(Debug, Deserialize, PartialEq)]
515#[serde(tag = "category")]
516pub enum Trade {
517    /// Inverse contracts.
518    #[serde(rename = "inverse")]
519    Inverse {
520        /// An string array of individual candle Sort in reverse by startTime.
521        list: Vec<InverseLinearSpotTrade>,
522    },
523    /// Linear (USDT/USDC) contracts.
524    #[serde(rename = "linear")]
525    Linear {
526        /// An string array of individual candle Sort in reverse by startTime.
527        list: Vec<InverseLinearSpotTrade>,
528    },
529    /// Options.
530    #[serde(rename = "option")]
531    Option {
532        /// An string array of individual candle Sort in reverse by startTime.
533        list: Vec<OptionTrade>,
534    },
535    /// Spot.
536    #[serde(rename = "spot")]
537    Spot {
538        /// An string array of individual candle Sort in reverse by startTime.
539        list: Vec<InverseLinearSpotTrade>,
540    },
541}
542
543/// Part of the response of `/v5/account/transaction-log`.
544#[derive(Debug, Deserialize, PartialEq)]
545#[serde(rename_all = "camelCase")]
546pub struct InverseLinearSpotTrade {
547    /// Execution ID
548    pub exec_id: String,
549    /// Symbol name
550    pub symbol: String,
551    /// Trade price
552    pub price: Decimal,
553    /// Trade size
554    pub size: Decimal,
555    /// Side of taker Buy, Sell
556    pub side: Side,
557    /// Trade time (ms)
558    #[serde(deserialize_with = "number")]
559    pub time: Timestamp,
560    /// boolean Whether the trade is block trade
561    pub is_block_trade: bool,
562    /// Whether the trade is RPI trade
563    #[serde(rename = "isRPITrade")]
564    pub is_rpi_trade: bool,
565}
566
567/// Part of the response of `/v5/account/transaction-log`.
568#[derive(Debug, Deserialize, PartialEq)]
569#[serde(rename_all = "camelCase")]
570pub struct OptionTrade {
571    /// Execution ID
572    pub exec_id: String,
573    /// Symbol name
574    pub symbol: String,
575    /// Trade price
576    pub price: Decimal,
577    /// Trade size
578    pub size: Decimal,
579    /// Side of taker Buy, Sell
580    pub side: Side,
581    /// Trade time (ms)
582    #[serde(deserialize_with = "number")]
583    pub time: Timestamp,
584    /// boolean Whether the trade is block trade
585    pub is_block_trade: bool,
586    /// Whether the trade is an RPI trade (not sent for options: `false`).
587    #[serde(rename = "isRPITrade", default)]
588    pub is_rpi_trade: bool,
589    /// Mark price
590    #[serde(rename = "mP")]
591    pub mark_price: Decimal,
592    /// Index price
593    #[serde(rename = "iP")]
594    pub index_price: Decimal,
595    /// Mark iv
596    #[serde(rename = "mIv")]
597    pub mark_iv: Decimal,
598    /// iv
599    #[serde(rename = "iv")]
600    pub iv: Decimal,
601}
602
603/// Result of `GET /v5/market/time` ([`Client::get_server_time`](crate::http::Client::get_server_time)).
604#[derive(Debug, Deserialize, PartialEq)]
605#[serde(rename_all = "camelCase")]
606pub struct ServerTime {
607    /// Bybit server timestamp (sec)
608    #[serde(deserialize_with = "number")]
609    pub time_second: u64,
610    /// Bybit server timestamp (nano)
611    #[serde(deserialize_with = "number")]
612    pub time_nano: u64,
613}
614
615/// Query parameters of `GET /v5/market/instruments-info` ([`Client::get_instruments_info`](crate::http::Client::get_instruments_info)).
616#[derive(Debug, Serialize, Clone)]
617#[serde(rename_all = "camelCase")]
618pub struct GetInstrumentsInfoParams {
619    /// Product type. spot, linear, inverse, option.
620    pub category: Category,
621    /// Symbol name, like BTCUSDT, uppercase only.
622    pub symbol: Option<String>,
623    /// Symbol status filter linear & inverse & spot By default returns Trading & PendingOpen symbols option By default returns PreLaunch, Trading, and Delivering Spot has Trading only linear & inverse: when status=PreLaunch, it returns Pre-Market ...
624    pub status: Option<Status>,
625    /// Base coin, uppercase only Applies to linear, inverse, option only option: returns BTC by default Pass All to return all option symbols. Only valid when category=option.
626    pub base_coin: Option<String>,
627    /// Limit for data size per page. \[ 1, 1000 \]. Default: 500.
628    pub limit: Option<i64>,
629    /// Cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set.
630    pub cursor: Option<String>,
631}
632
633impl GetInstrumentsInfoParams {
634    /// Create with the required fields; set the optional ones with the `with_*` methods.
635    pub fn new(category: Category) -> Self {
636        Self {
637            category,
638            symbol: None,
639            status: None,
640            base_coin: None,
641            limit: None,
642            cursor: None,
643        }
644    }
645
646    /// Set `symbol`: symbol name, like BTCUSDT, uppercase only.
647    pub fn with_symbol(mut self, v: impl Into<String>) -> Self {
648        self.symbol = Some(v.into());
649        self
650    }
651
652    /// Set `status`: symbol status filter linear & inverse & spot By default returns Trading & PendingOpen symbols option By default returns PreLaunch, Trading, and Delivering Spot has Trading only linear & inverse: when status=PreLaunch, it returns Pre-Market ...
653    pub fn with_status(mut self, v: Status) -> Self {
654        self.status = Some(v);
655        self
656    }
657
658    /// Set `base_coin`: base coin, uppercase only Applies to linear, inverse, option only option: returns BTC by default Pass All to return all option symbols. Only valid when category=option.
659    pub fn with_base_coin(mut self, v: impl Into<String>) -> Self {
660        self.base_coin = Some(v.into());
661        self
662    }
663
664    /// Set `limit`: limit for data size per page. \[ 1, 1000 \]. Default: 500.
665    pub fn with_limit(mut self, v: i64) -> Self {
666        self.limit = Some(v);
667        self
668    }
669
670    /// Set `cursor`: cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set.
671    pub fn with_cursor(mut self, v: impl Into<String>) -> Self {
672        self.cursor = Some(v.into());
673        self
674    }
675}
676
677/// Result of `GET /v5/market/instruments-info` ([`Client::get_instruments_info`](crate::http::Client::get_instruments_info)).
678#[derive(Debug, Deserialize, PartialEq)]
679#[serde(tag = "category")]
680pub enum InstrumentsInfo {
681    /// Inverse contracts.
682    #[serde(rename = "inverse", rename_all = "camelCase")]
683    Inverse {
684        /// Cursor of the next page; empty on the last page.
685        next_page_cursor: String,
686        /// Items.
687        list: Vec<InverseLinearInstrumentsInfo>,
688    },
689    /// Linear (USDT/USDC) contracts.
690    #[serde(rename = "linear", rename_all = "camelCase")]
691    Linear {
692        /// Cursor of the next page; empty on the last page.
693        next_page_cursor: String,
694        /// Items.
695        list: Vec<InverseLinearInstrumentsInfo>,
696    },
697    /// Options.
698    #[serde(rename = "option", rename_all = "camelCase")]
699    Option {
700        /// Cursor of the next page; empty on the last page.
701        next_page_cursor: String,
702        /// Items.
703        list: Vec<OptionInstrumentsInfo>,
704    },
705    /// Spot.
706    #[serde(rename = "spot", rename_all = "camelCase")]
707    Spot {
708        /// Cursor of the next page; empty on the last page.
709        #[serde(default, deserialize_with = "empty_string_as_none")]
710        next_page_cursor: Option<String>,
711        /// Items.
712        list: Vec<SpotInstrumentsInfo>,
713    },
714}
715
716/// Part of the response of `/v5/market/instruments-info`.
717#[derive(Debug, Deserialize, PartialEq)]
718#[serde(rename_all = "camelCase")]
719pub struct InverseLinearInstrumentsInfo {
720    /// Symbol name.
721    pub symbol: String,
722    /// Contract type.
723    pub contract_type: ContractType,
724    /// Instrument status.
725    pub status: Status,
726    /// Base coin.
727    pub base_coin: String,
728    /// Quote coin.
729    pub quote_coin: String,
730    /// Launch timestamp (ms).
731    #[serde(deserialize_with = "number")]
732    pub launch_time: Timestamp,
733    /// Delivery timestamp (ms) Expired futures delivery time Perpetual delisting time.
734    #[serde(deserialize_with = "number")]
735    pub delivery_time: Timestamp,
736    /// Delivery fee rate.
737    #[serde(deserialize_with = "option_decimal")]
738    pub delivery_fee_rate: Option<Decimal>,
739    /// Price scale.
740    #[serde(deserialize_with = "number")]
741    pub price_scale: i64,
742    /// Leverage attributes.
743    pub leverage_filter: LeverageFilter,
744    /// Price attributes.
745    pub price_filter: PriceFilter,
746    /// Size attributes.
747    pub lot_size_filter: LotSizeFilter,
748    /// Whether to support unified margin trade.
749    pub unified_margin_trade: bool,
750    /// Funding interval (minute).
751    pub funding_interval: i64,
752    /// Settle coin.
753    pub settle_coin: String,
754    /// Copy trade symbol or not.
755    pub copy_trading: CopyTrading,
756    /// Upper limit of funding date.
757    pub upper_funding_rate: Decimal,
758    /// Lower limit of funding date.
759    pub lower_funding_rate: Decimal,
760    /// Risk parameters for limit order price. Note that the formula changed in May 2026.
761    pub risk_parameters: RiskParameters,
762    /// Whether the contract is a pre-market contract When the pre-market contract is converted to official contract, it will be false.
763    pub is_pre_listing: bool,
764    /// If isPreListing=false, preListingInfo=null If isPreListing=true, preListingInfo is an object.
765    pub pre_listing_info: Option<PreListingInfo>,
766}
767
768/// An option of `GET /v5/market/instruments-info`.
769#[derive(Debug, Deserialize, PartialEq)]
770#[serde(rename_all = "camelCase")]
771pub struct OptionInstrumentsInfo {
772    /// Symbol name, e.g. `ETH-25JUN27-7000-P-USDT`.
773    pub symbol: String,
774    /// Display name in the UI.
775    #[serde(default)]
776    pub display_name: Option<String>,
777    /// Trading status.
778    pub status: Status,
779    /// Base coin.
780    pub base_coin: String,
781    /// Quote coin.
782    pub quote_coin: String,
783    /// Settle coin.
784    pub settle_coin: String,
785    /// Call or put.
786    pub options_type: OptionType,
787    /// Launch time, milliseconds.
788    #[serde(deserialize_with = "number")]
789    pub launch_time: Timestamp,
790    /// Delivery time, milliseconds.
791    #[serde(deserialize_with = "number")]
792    pub delivery_time: Timestamp,
793    /// Delivery fee rate.
794    #[serde(default, deserialize_with = "option_decimal")]
795    pub delivery_fee_rate: Option<Decimal>,
796    /// Price limits and tick size.
797    pub price_filter: PriceFilter,
798    /// Order quantity limits.
799    pub lot_size_filter: OptionLotSizeFilter,
800}
801
802/// Order quantity limits of an option.
803#[derive(Debug, Deserialize, PartialEq)]
804#[serde(rename_all = "camelCase")]
805pub struct OptionLotSizeFilter {
806    /// Maximum order quantity.
807    pub max_order_qty: Decimal,
808    /// Minimum order quantity.
809    pub min_order_qty: Decimal,
810    /// Quantity step.
811    pub qty_step: Decimal,
812}
813
814/// Part of the response of `/v5/market/instruments-info`.
815#[derive(Debug, Deserialize, PartialEq)]
816#[serde(rename_all = "camelCase")]
817pub struct SpotInstrumentsInfo {
818    /// Symbol name
819    pub symbol: String,
820    /// Base coin
821    pub base_coin: String,
822    /// Quote coin
823    pub quote_coin: String,
824    /// Whether or not this is an innovation zone token. 0: false, 1: true
825    #[serde(deserialize_with = "string_to_bool")]
826    pub innovation: bool,
827    /// Instrument status
828    pub status: Status,
829    /// Margin trade symbol or not
830    /// This is to identify if the symbol support margin trading under different account modes
831    /// You may find some symbols not supporting margin buy or margin sell, so you need to go to Collateral Info (UTA) to check if that coin is borrowable
832    pub margin_trading: String,
833    /// Whether or not it has an special treatment label. 0: false, 1: true
834    #[serde(deserialize_with = "string_to_bool")]
835    pub st_tag: bool,
836    /// Size attributes
837    pub lot_size_filter: SpotLotSizeFilter,
838    /// Price attributes
839    pub price_filter: SpotPriceFilter,
840    /// Risk parameters for limit order price, refer to announcement
841    pub risk_parameters: RiskParameters,
842}
843
844/// Part of the response of `/v5/market/instruments-info`.
845#[derive(Debug, Deserialize, PartialEq)]
846#[serde(rename_all = "camelCase")]
847pub struct LeverageFilter {
848    /// Minimum leverage.
849    pub min_leverage: Decimal,
850    /// Maximum leverage.
851    pub max_leverage: Decimal,
852    /// The step to increase/reduce leverage.
853    pub leverage_step: Decimal,
854}
855
856/// Part of the response of `/v5/market/instruments-info`.
857#[derive(Debug, Deserialize, PartialEq)]
858#[serde(rename_all = "camelCase")]
859pub struct PriceFilter {
860    /// Minimum order price.
861    pub min_price: Decimal,
862    /// Maximum order price.
863    pub max_price: Decimal,
864    /// The step to increase/reduce order price.
865    pub tick_size: Decimal,
866}
867
868/// Part of the response of `/v5/market/instruments-info`.
869#[derive(Debug, Deserialize, PartialEq)]
870#[serde(rename_all = "camelCase")]
871pub struct SpotPriceFilter {
872    /// The step to increase/reduce order price
873    pub tick_size: Decimal,
874}
875
876/// Part of the response of `/v5/market/instruments-info`.
877#[derive(Debug, Deserialize, PartialEq)]
878#[serde(rename_all = "camelCase")]
879pub struct LotSizeFilter {
880    /// Minimum notional value.
881    pub min_notional_value: Decimal,
882    /// Maximum quantity for Limit and PostOnly order.
883    pub max_order_qty: Decimal,
884    /// Maximum quantity for Market order.
885    pub max_mkt_order_qty: Decimal,
886    /// Minimum order quantity.
887    pub min_order_qty: Decimal,
888    /// The step to increase/reduce order quantity.
889    pub qty_step: Decimal,
890    /// Deprecated, please use maxOrderQty.
891    pub post_only_max_order_qty: Decimal,
892}
893
894/// Part of the response of `/v5/market/instruments-info`.
895#[derive(Debug, Deserialize, PartialEq)]
896#[serde(rename_all = "camelCase")]
897pub struct SpotLotSizeFilter {
898    /// The precision of base coin
899    pub base_precision: Decimal,
900    /// The precision of quote coin
901    pub quote_precision: Decimal,
902    /// Minimum order quantity
903    pub min_order_qty: Decimal,
904    /// Maximum order quantity
905    pub max_order_qty: Decimal,
906    /// Minimum order amount
907    pub min_order_amt: Decimal,
908    /// Maximum order amount
909    pub max_order_amt: Decimal,
910}
911
912/// Part of the response of `/v5/market/instruments-info`.
913#[derive(Debug, Deserialize, PartialEq)]
914#[serde(rename_all = "camelCase")]
915pub struct RiskParameters {
916    /// Ratio X.
917    pub price_limit_ratio_x: Decimal,
918    /// Ratio Y.
919    pub price_limit_ratio_y: Decimal,
920}
921
922/// Part of the response of `/v5/market/instruments-info`.
923#[derive(Debug, Deserialize, PartialEq)]
924#[serde(rename_all = "camelCase")]
925pub struct PreListingInfo {
926    /// The current auction phase.
927    pub cur_auction_phase: CurAuctionPhase,
928    /// Each phase time info.
929    pub phases: Vec<Phase>,
930    /// Action fee info.
931    pub auction_fee_info: AuctionFeeInfo,
932}
933
934/// Part of the response of `/v5/market/instruments-info`.
935#[derive(Debug, Deserialize, PartialEq)]
936#[serde(rename_all = "camelCase")]
937pub struct Phase {
938    /// Pre-market trading phase.
939    pub phase: CurAuctionPhase,
940    /// The start time of the phase, timestamp(ms).
941    #[serde(deserialize_with = "option_number")]
942    pub start_time: Option<Timestamp>,
943    /// The end time of the phase, timestamp(ms).
944    #[serde(deserialize_with = "option_number")]
945    pub end_time: Option<Timestamp>,
946}
947
948/// Part of the response of `/v5/market/instruments-info`.
949#[derive(Debug, Deserialize, PartialEq)]
950#[serde(rename_all = "camelCase")]
951pub struct AuctionFeeInfo {
952    /// The trading fee rate during auction phase There is no trading fee until entering continues trading phase.
953    pub auction_fee_rate: Decimal,
954    /// The taker fee rate during continues trading phase.
955    pub taker_fee_rate: Decimal,
956    /// The maker fee rate during continues trading phase.
957    pub maker_fee_rate: Decimal,
958}
959
960// --- Funding Rate History ---
961
962/// Query params for [`Client::get_funding_rate_history`](crate::http::Client::get_funding_rate_history).
963///
964/// Covers: linear / inverse
965#[derive(Debug, Serialize, Clone)]
966#[serde(rename_all = "camelCase")]
967pub struct GetFundingRateHistoryParams {
968    /// Product type. linear, inverse.
969    pub category: Category,
970    /// Symbol name, like BTCUSDT, uppercase only.
971    pub symbol: String,
972    /// The start timestamp (ms).
973    pub start_time: Option<Timestamp>,
974    /// The end timestamp (ms).
975    pub end_time: Option<Timestamp>,
976    /// Max 200. Default 200.
977    pub limit: Option<u64>,
978}
979
980impl GetFundingRateHistoryParams {
981    /// Create with the required fields; set the optional ones with the `with_*` methods.
982    pub fn new(category: Category, symbol: impl Into<String>) -> Self {
983        Self {
984            category,
985            symbol: symbol.into(),
986            start_time: None,
987            end_time: None,
988            limit: None,
989        }
990    }
991
992    /// Set `start_time`: the start timestamp (ms).
993    pub fn with_start_time(mut self, v: Timestamp) -> Self {
994        self.start_time = Some(v);
995        self
996    }
997
998    /// Set `end_time`: the end timestamp (ms).
999    pub fn with_end_time(mut self, v: Timestamp) -> Self {
1000        self.end_time = Some(v);
1001        self
1002    }
1003
1004    /// Set `limit`: max 200. Default 200.
1005    pub fn with_limit(mut self, v: u64) -> Self {
1006        self.limit = Some(v);
1007        self
1008    }
1009}
1010
1011/// Response for [`Client::get_funding_rate_history`](crate::http::Client::get_funding_rate_history).
1012#[derive(Debug, Deserialize, PartialEq)]
1013#[serde(tag = "category")]
1014pub enum FundingRateHistory {
1015    /// Linear (USDT/USDC) contracts.
1016    #[serde(rename = "linear")]
1017    Linear {
1018        /// Items.
1019        list: Vec<FundingRateEntry>,
1020    },
1021    /// Inverse contracts.
1022    #[serde(rename = "inverse")]
1023    Inverse {
1024        /// Items.
1025        list: Vec<FundingRateEntry>,
1026    },
1027}
1028
1029/// Part of the response of `/v5/market/funding/history`.
1030#[derive(Debug, Deserialize, PartialEq)]
1031#[serde(rename_all = "camelCase")]
1032pub struct FundingRateEntry {
1033    /// Symbol name.
1034    pub symbol: String,
1035    /// Funding rate.
1036    pub funding_rate: Decimal,
1037    /// Funding rate timestamp (ms).
1038    #[serde(deserialize_with = "number")]
1039    pub funding_rate_timestamp: Timestamp,
1040}
1041
1042// --- Open Interest ---
1043
1044/// Query params for [`Client::get_open_interest`](crate::http::Client::get_open_interest).
1045///
1046/// Covers: linear / inverse
1047#[derive(Debug, Serialize, Clone)]
1048#[serde(rename_all = "camelCase")]
1049pub struct GetOpenInterestParams {
1050    /// Product type. linear, inverse.
1051    pub category: Category,
1052    /// Symbol name, like BTCUSDT, uppercase only.
1053    pub symbol: String,
1054    /// Interval time. 5min, 15min, 30min, 1h, 4h, 1d.
1055    pub interval_time: IntervalTime,
1056    /// The start timestamp (ms).
1057    pub start_time: Option<Timestamp>,
1058    /// The end timestamp (ms).
1059    pub end_time: Option<Timestamp>,
1060    /// Max 200. Default 50.
1061    pub limit: Option<u64>,
1062    /// Cursor. Used to paginate.
1063    pub cursor: Option<String>,
1064}
1065
1066impl GetOpenInterestParams {
1067    /// Create with the required fields; set the optional ones with the `with_*` methods.
1068    pub fn new(category: Category, symbol: impl Into<String>, interval_time: IntervalTime) -> Self {
1069        Self {
1070            category,
1071            symbol: symbol.into(),
1072            interval_time,
1073            start_time: None,
1074            end_time: None,
1075            limit: None,
1076            cursor: None,
1077        }
1078    }
1079
1080    /// Set `start_time`: the start timestamp (ms).
1081    pub fn with_start_time(mut self, v: Timestamp) -> Self {
1082        self.start_time = Some(v);
1083        self
1084    }
1085
1086    /// Set `end_time`: the end timestamp (ms).
1087    pub fn with_end_time(mut self, v: Timestamp) -> Self {
1088        self.end_time = Some(v);
1089        self
1090    }
1091
1092    /// Set `limit`: max 200. Default 50.
1093    pub fn with_limit(mut self, v: u64) -> Self {
1094        self.limit = Some(v);
1095        self
1096    }
1097
1098    /// Set `cursor`: cursor. Used to paginate.
1099    pub fn with_cursor(mut self, v: impl Into<String>) -> Self {
1100        self.cursor = Some(v.into());
1101        self
1102    }
1103}
1104
1105/// Response for [`Client::get_open_interest`](crate::http::Client::get_open_interest).
1106#[derive(Debug, Deserialize, PartialEq)]
1107#[serde(rename_all = "camelCase")]
1108pub struct OpenInterest {
1109    /// Symbol name.
1110    pub symbol: String,
1111    /// Product type.
1112    pub category: String,
1113    /// Items.
1114    pub list: Vec<OpenInterestEntry>,
1115    /// Used to paginate.
1116    #[serde(default, deserialize_with = "empty_string_as_none")]
1117    pub next_page_cursor: Option<String>,
1118}
1119
1120/// Part of the response of `/v5/market/open-interest`.
1121#[derive(Debug, Deserialize, PartialEq)]
1122#[serde(rename_all = "camelCase")]
1123pub struct OpenInterestEntry {
1124    /// Open interest. The value is the sum of both sides. The unit of value, e.g., BTCUSD(inverse) is USD, BTCUSDT(linear) is BTC.
1125    pub open_interest: Decimal,
1126    /// The timestamp (ms).
1127    #[serde(deserialize_with = "number")]
1128    pub timestamp: Timestamp,
1129}
1130
1131// --- Historical Volatility ---
1132
1133/// Query params for [`Client::get_historical_volatility`](crate::http::Client::get_historical_volatility).
1134///
1135/// Covers: option only
1136#[derive(Debug, Serialize, Clone)]
1137#[serde(rename_all = "camelCase")]
1138pub struct GetHistoricalVolatilityParams {
1139    /// Product type. option.
1140    pub category: Category,
1141    /// Default: BTC.
1142    pub base_coin: Option<String>,
1143    /// Accepted values: 7, 14, 21, 30, 60, 90, 180, 270.
1144    pub period: Option<u16>,
1145    /// The start timestamp (ms).
1146    pub start_time: Option<Timestamp>,
1147    /// The end timestamp (ms).
1148    pub end_time: Option<Timestamp>,
1149}
1150
1151impl GetHistoricalVolatilityParams {
1152    /// Create with the required fields; set the optional ones with the `with_*` methods.
1153    pub fn new(category: Category) -> Self {
1154        Self {
1155            category,
1156            base_coin: None,
1157            period: None,
1158            start_time: None,
1159            end_time: None,
1160        }
1161    }
1162
1163    /// Set `base_coin`: default: BTC.
1164    pub fn with_base_coin(mut self, v: impl Into<String>) -> Self {
1165        self.base_coin = Some(v.into());
1166        self
1167    }
1168
1169    /// Set `period`: accepted values: 7, 14, 21, 30, 60, 90, 180, 270.
1170    pub fn with_period(mut self, v: u16) -> Self {
1171        self.period = Some(v);
1172        self
1173    }
1174
1175    /// Set `start_time`: the start timestamp (ms).
1176    pub fn with_start_time(mut self, v: Timestamp) -> Self {
1177        self.start_time = Some(v);
1178        self
1179    }
1180
1181    /// Set `end_time`: the end timestamp (ms).
1182    pub fn with_end_time(mut self, v: Timestamp) -> Self {
1183        self.end_time = Some(v);
1184        self
1185    }
1186}
1187
1188/// One data point from
1189/// [`Client::get_historical_volatility`](crate::http::Client::get_historical_volatility).
1190///
1191/// The endpoint returns `result` as a top-level JSON array, so the full
1192/// response type is `Response<Vec<HistoricalVolatilityEntry>>`.
1193#[derive(Debug, Deserialize, PartialEq)]
1194pub struct HistoricalVolatilityEntry {
1195    /// Period.
1196    pub period: u16,
1197    /// Volatility.
1198    pub value: Decimal,
1199    /// Timestamp (ms).
1200    #[serde(deserialize_with = "number")]
1201    pub time: Timestamp,
1202}
1203
1204// --- Insurance ---
1205
1206/// Query params for [`Client::get_insurance`](crate::http::Client::get_insurance).
1207#[derive(Debug, Serialize, Clone, Default)]
1208pub struct GetInsuranceParams {
1209    /// Coin, uppercase only. Default: return all insurance coins.
1210    pub coin: Option<String>,
1211}
1212
1213impl GetInsuranceParams {
1214    /// Create with the required fields; set the optional ones with the `with_*` methods.
1215    pub fn new() -> Self {
1216        Self { coin: None }
1217    }
1218
1219    /// Set `coin`: coin, uppercase only. Default: return all insurance coins.
1220    pub fn with_coin(mut self, v: impl Into<String>) -> Self {
1221        self.coin = Some(v.into());
1222        self
1223    }
1224}
1225
1226/// Response for [`Client::get_insurance`](crate::http::Client::get_insurance).
1227#[derive(Debug, Deserialize, PartialEq)]
1228#[serde(rename_all = "camelCase")]
1229pub struct Insurance {
1230    /// Data updated time (ms).
1231    #[serde(deserialize_with = "number")]
1232    pub updated_time: Timestamp,
1233    /// Items.
1234    pub list: Vec<InsuranceEntry>,
1235}
1236
1237/// Part of the response of `/v5/market/insurance`.
1238#[derive(Debug, Deserialize, PartialEq)]
1239#[serde(rename_all = "camelCase")]
1240pub struct InsuranceEntry {
1241    /// Coin.
1242    pub coin: String,
1243    /// Balance.
1244    pub balance: Decimal,
1245    /// USD value.
1246    pub value: Decimal,
1247}
1248
1249// --- Risk Limit ---
1250
1251/// Query params for [`Client::get_risk_limit`](crate::http::Client::get_risk_limit).
1252///
1253/// Covers: linear / inverse
1254#[derive(Debug, Serialize, Clone)]
1255pub struct GetRiskLimitParams {
1256    /// Product type. linear, inverse.
1257    pub category: Category,
1258    /// Symbol name, like BTCUSDT, uppercase only.
1259    pub symbol: Option<String>,
1260    /// Cursor. Use the nextPageCursor token from the response to retrieve the next page of the data set.
1261    pub cursor: Option<String>,
1262}
1263
1264impl GetRiskLimitParams {
1265    /// Create with the required fields; set the optional ones with the `with_*` methods.
1266    pub fn new(category: Category) -> Self {
1267        Self {
1268            category,
1269            symbol: None,
1270            cursor: None,
1271        }
1272    }
1273
1274    /// Set `symbol`: symbol name, like BTCUSDT, uppercase only.
1275    pub fn with_symbol(mut self, v: impl Into<String>) -> Self {
1276        self.symbol = Some(v.into());
1277        self
1278    }
1279
1280    /// Set `cursor`: cursor. Use the nextPageCursor token from the response to retrieve the next page of the data set.
1281    pub fn with_cursor(mut self, v: impl Into<String>) -> Self {
1282        self.cursor = Some(v.into());
1283        self
1284    }
1285}
1286
1287/// Response for [`Client::get_risk_limit`](crate::http::Client::get_risk_limit).
1288#[derive(Debug, Deserialize, PartialEq)]
1289#[serde(rename_all = "camelCase")]
1290pub struct RiskLimit {
1291    /// Product type.
1292    pub category: String,
1293    /// Items.
1294    pub list: Vec<RiskLimitEntry>,
1295}
1296
1297/// Part of the response of `/v5/market/risk-limit`.
1298#[derive(Debug, Deserialize, PartialEq)]
1299#[serde(rename_all = "camelCase")]
1300pub struct RiskLimitEntry {
1301    /// Risk ID.
1302    pub id: u32,
1303    /// Symbol name.
1304    pub symbol: String,
1305    /// Position limit.
1306    pub risk_limit_value: Decimal,
1307    /// Maintain margin rate.
1308    pub maintenance_margin: Decimal,
1309    /// Initial margin rate.
1310    pub initial_margin: Decimal,
1311    /// 1: true, 0: false.
1312    #[serde(deserialize_with = "int_to_bool")]
1313    pub is_lowest_risk: bool,
1314    /// Allowed max leverage.
1315    pub max_leverage: Decimal,
1316}
1317
1318// --- Delivery Price ---
1319
1320/// Query params for [`Client::get_delivery_price`](crate::http::Client::get_delivery_price).
1321///
1322/// Covers: linear / inverse / option
1323#[derive(Debug, Serialize, Clone)]
1324#[serde(rename_all = "camelCase")]
1325pub struct GetDeliveryPriceParams {
1326    /// Product type. linear, inverse, option.
1327    pub category: Category,
1328    /// Symbol name, like BTCUSDT, uppercase only.
1329    pub symbol: Option<String>,
1330    /// Base coin, uppercase only. Default: BTC. Valid for option only.
1331    pub base_coin: Option<String>,
1332    /// Max 200. Default 50.
1333    pub limit: Option<u64>,
1334    /// Cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set.
1335    pub cursor: Option<String>,
1336}
1337
1338impl GetDeliveryPriceParams {
1339    /// Create with the required fields; set the optional ones with the `with_*` methods.
1340    pub fn new(category: Category) -> Self {
1341        Self {
1342            category,
1343            symbol: None,
1344            base_coin: None,
1345            limit: None,
1346            cursor: None,
1347        }
1348    }
1349
1350    /// Set `symbol`: symbol name, like BTCUSDT, uppercase only.
1351    pub fn with_symbol(mut self, v: impl Into<String>) -> Self {
1352        self.symbol = Some(v.into());
1353        self
1354    }
1355
1356    /// Set `base_coin`: base coin, uppercase only. Default: BTC. Valid for option only.
1357    pub fn with_base_coin(mut self, v: impl Into<String>) -> Self {
1358        self.base_coin = Some(v.into());
1359        self
1360    }
1361
1362    /// Set `limit`: max 200. Default 50.
1363    pub fn with_limit(mut self, v: u64) -> Self {
1364        self.limit = Some(v);
1365        self
1366    }
1367
1368    /// Set `cursor`: cursor. Use the nextPageCursor token from the response to retrieve the next page of the result set.
1369    pub fn with_cursor(mut self, v: impl Into<String>) -> Self {
1370        self.cursor = Some(v.into());
1371        self
1372    }
1373}
1374
1375/// Response for [`Client::get_delivery_price`](crate::http::Client::get_delivery_price).
1376#[derive(Debug, Deserialize, PartialEq)]
1377#[serde(rename_all = "camelCase")]
1378pub struct DeliveryPrice {
1379    /// Product type.
1380    pub category: String,
1381    /// Refer to the cursor request parameter.
1382    #[serde(default, deserialize_with = "empty_string_as_none")]
1383    pub next_page_cursor: Option<String>,
1384    /// Items.
1385    pub list: Vec<DeliveryPriceEntry>,
1386}
1387
1388/// Part of the response of `/v5/market/delivery-price`.
1389#[derive(Debug, Deserialize, PartialEq)]
1390#[serde(rename_all = "camelCase")]
1391pub struct DeliveryPriceEntry {
1392    /// Symbol name.
1393    pub symbol: String,
1394    /// Delivery price.
1395    pub delivery_price: Decimal,
1396    /// Delivery timestamp (ms).
1397    #[serde(deserialize_with = "number")]
1398    pub delivery_time: Timestamp,
1399}
1400
1401#[cfg(test)]
1402mod tests {
1403    use super::*;
1404    use crate::{Category, serde::serialize_query};
1405
1406    #[test]
1407    fn trades_params_serialize_option_type_as_call_or_put() {
1408        let params = GetTradesParams {
1409            category: Category::Option,
1410            symbol: None,
1411            base_coin: Some(String::from("BTC")),
1412            option_type: Some(OptionType::Put),
1413            limit: None,
1414        };
1415
1416        let query = serialize_query(&params).unwrap();
1417
1418        assert_eq!(query, "category=option&baseCoin=BTC&optionType=Put");
1419    }
1420
1421    #[test]
1422    fn base_coin_and_exp_date_are_sent_in_camel_case() {
1423        let tickers = GetTickersParams::new(Category::Option)
1424            .with_base_coin("ETH")
1425            .with_exp_date("25DEC26");
1426        let instruments = GetInstrumentsInfoParams::new(Category::Option).with_base_coin("ETH");
1427
1428        assert_eq!(
1429            serialize_query(&tickers).unwrap(),
1430            "category=option&baseCoin=ETH&expDate=25DEC26"
1431        );
1432        assert_eq!(
1433            serialize_query(&instruments).unwrap(),
1434            "category=option&baseCoin=ETH"
1435        );
1436    }
1437
1438    #[test]
1439    fn option_instruments_info_parses() {
1440        let json = r#"{
1441            "category": "option",
1442            "nextPageCursor": "",
1443            "list": [{
1444                "symbolId": 379919,
1445                "symbol": "ETH-25JUN27-7000-P-USDT",
1446                "status": "Trading",
1447                "baseCoin": "ETH",
1448                "quoteCoin": "USDT",
1449                "settleCoin": "USDT",
1450                "optionsType": "Put",
1451                "launchTime": "1787350800000",
1452                "deliveryTime": "1813910400000",
1453                "deliveryFeeRate": "0.00015",
1454                "priceFilter": {"minPrice": "0.1", "maxPrice": "41000", "tickSize": "0.1"},
1455                "lotSizeFilter": {"maxOrderQty": "5000", "minOrderQty": "0.1", "qtyStep": "0.1"},
1456                "displayName": "ETHUSDT-25JUN27-7000-P"
1457            }]
1458        }"#;
1459
1460        let info: InstrumentsInfo = serde_json::from_str(json).unwrap();
1461
1462        let InstrumentsInfo::Option { list, .. } = info else {
1463            panic!("not an option list");
1464        };
1465        assert_eq!(list[0].options_type, OptionType::Put);
1466        assert_eq!(list[0].lot_size_filter.qty_step, rust_decimal::dec!(0.1));
1467        assert_eq!(list[0].delivery_time, 1813910400000);
1468    }
1469}