Skip to main content

bybit/models/
kline_request.rs

1use crate::prelude::*;
2
3/// Valid kline intervals supported by Bybit API.
4///
5/// This enum provides type-safe interval specification for kline requests.
6/// Each variant corresponds to a valid interval string accepted by the Bybit API.
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum Interval {
9    /// 1 minute
10    M1,
11    /// 3 minutes
12    M3,
13    /// 5 minutes
14    M5,
15    /// 15 minutes
16    M15,
17    /// 30 minutes
18    M30,
19    /// 1 hour
20    H1,
21    /// 2 hours
22    H2,
23    /// 4 hours
24    H4,
25    /// 6 hours
26    H6,
27    /// 12 hours
28    H12,
29    /// 1 day
30    D1,
31    /// 1 week
32    W1,
33    /// 1 month
34    M,
35}
36
37impl Default for Interval {
38    /// Returns the default interval (1 hour).
39    fn default() -> Self {
40        Interval::H1
41    }
42}
43
44impl Interval {
45    /// Returns the string representation of the interval as expected by the Bybit API.
46    pub fn as_str(&self) -> &'static str {
47        match self {
48            Interval::M1 => "1",
49            Interval::M3 => "3",
50            Interval::M5 => "5",
51            Interval::M15 => "15",
52            Interval::M30 => "30",
53            Interval::H1 => "60",
54            Interval::H2 => "120",
55            Interval::H4 => "240",
56            Interval::H6 => "360",
57            Interval::H12 => "720",
58            Interval::D1 => "D",
59            Interval::W1 => "W",
60            Interval::M => "M",
61        }
62    }
63
64    /// Parses a string into an Interval enum.
65    ///
66    /// Returns `Some(Interval)` if the string is a valid interval, `None` otherwise.
67    pub fn from_str(s: &str) -> Option<Self> {
68        match s {
69            "1" | "1m" => Some(Interval::M1),
70            "3" | "3m" => Some(Interval::M3),
71            "5" | "5m" => Some(Interval::M5),
72            "15" | "15m" => Some(Interval::M15),
73            "30" | "30m" => Some(Interval::M30),
74            "60" | "1h" => Some(Interval::H1),
75            "120" | "2h" => Some(Interval::H2),
76            "240" | "4h" => Some(Interval::H4),
77            "360" | "6h" => Some(Interval::H6),
78            "720" | "12h" => Some(Interval::H12),
79            "D" | "1d" => Some(Interval::D1),
80            "W" | "1w" => Some(Interval::W1),
81            "M" | "1M" => Some(Interval::M),
82            _ => None,
83        }
84    }
85
86    /// Returns the duration of the interval in seconds.
87    pub fn duration_seconds(&self) -> u64 {
88        match self {
89            Interval::M1 => 60,
90            Interval::M3 => 180,
91            Interval::M5 => 300,
92            Interval::M15 => 900,
93            Interval::M30 => 1800,
94            Interval::H1 => 3600,
95            Interval::H2 => 7200,
96            Interval::H4 => 14400,
97            Interval::H6 => 21600,
98            Interval::H12 => 43200,
99            Interval::D1 => 86400,
100            Interval::W1 => 604800,
101            Interval::M => 2592000, // Approximate 30 days
102        }
103    }
104
105    /// Returns the duration of the interval in milliseconds.
106    pub fn duration_millis(&self) -> u64 {
107        self.duration_seconds() * 1000
108    }
109}
110
111impl std::fmt::Display for Interval {
112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        write!(f, "{}", self.as_str())
114    }
115}
116
117impl From<Interval> for String {
118    fn from(interval: Interval) -> Self {
119        interval.as_str().to_string()
120    }
121}
122
123impl<'a> From<&'a Interval> for Cow<'a, str> {
124    fn from(interval: &'a Interval) -> Self {
125        Cow::Borrowed(interval.as_str())
126    }
127}
128
129/// Parameters for requesting Kline (candlestick) data.
130///
131/// Kline data represents price movements over fixed time intervals (e.g., 1-minute, 1-hour) and is used for technical analysis in trading. This struct defines the parameters for querying Kline data via Bybit's `/v5/market/kline` endpoint. Perpetual futures on Bybit, unlike traditional futures, have no expiry date and are funded via a funding rate mechanism, making Kline data critical for analyzing price trends in these instruments.
132///
133/// # Examples
134///
135/// ```rust
136/// use bybit::prelude::*;
137///
138/// // Create a request using the builder pattern
139/// let request = KlineRequest::builder()
140///     .category(Category::Linear)
141///     .symbol("BTCUSDT")
142///     .interval(Interval::H1)
143///     .limit(100)
144///     .build();
145///
146/// // Or create a simple request
147/// let simple_request = KlineRequest::simple("BTCUSDT", Interval::D1);
148/// ```
149#[derive(Clone, Default)]
150pub struct KlineRequest<'a> {
151    /// The product category (e.g., Spot, Linear, Inverse, Option).
152    ///
153    /// Specifies the type of instrument for the Kline data. For perpetual futures, `Linear` (USDT-margined) or `Inverse` (coin-margined) are most relevant. Bots should set this to match the trading pair (e.g., `Linear` for `BTCUSDT`). If unset, the API may return an error or default to an unexpected category.
154    pub category: Option<Category>,
155
156    /// The trading pair symbol (e.g., "BTCUSDT").
157    ///
158    /// Identifies the specific perpetual futures contract or other instrument. For perpetuals, this is typically a USDT pair (e.g., `BTCUSDT`) for linear contracts or a coin pair (e.g., `BTCUSD`) for inverse contracts. Bots must ensure the symbol is valid for the chosen `category` to avoid errors.
159    pub symbol: Cow<'a, str>,
160
161    /// The time interval for each candlestick.
162    ///
163    /// Specifies the granularity of the Kline data. The choice depends on the trading strategy:
164    /// - Short-term bots may use smaller intervals (e.g., `Interval::M1` for 1 minute)
165    /// - Long-term strategies may use larger intervals (e.g., `Interval::D1` for 1 day)
166    pub interval: Interval,
167
168    /// The start time for the Kline data (Unix timestamp in milliseconds).
169    ///
170    /// Defines the beginning of the time range for the Kline data. For perpetual futures, this is useful for fetching historical data to backtest trading strategies. If unset, the API may return data from the most recent period, which may not suit historical analysis needs.
171    pub start: Option<Cow<'a, str>>,
172
173    /// The end time for the Kline data (Unix timestamp in milliseconds).
174    ///
175    /// Defines the end of the time range for the Kline data. Bots should set this to limit the data to a specific period, especially for performance optimization when processing large datasets. If unset, the API typically returns data up to the current time.
176    pub end: Option<Cow<'a, str>>,
177
178    /// The maximum number of Kline records to return (1-1000, default: 200).
179    ///
180    /// Controls the number of candlesticks returned in the response. For trading bots, setting a lower limit can reduce latency and memory usage, especially for high-frequency strategies. However, for comprehensive analysis, bots may need to paginate through multiple requests to fetch all desired data.
181    pub limit: Option<u64>,
182}
183
184impl<'a> KlineRequest<'a> {
185    /// Creates a default Kline request with preset values.
186    ///
187    /// Returns a `KlineRequest` with `symbol` set to `"BTCUSDT"`, `interval` set to `Interval::H1` (1 hour),
188    /// and other fields as defaults. Useful for quick testing or prototyping trading bots but should be
189    /// customized for production to match specific trading pairs and intervals.
190    pub fn default() -> KlineRequest<'a> {
191        KlineRequest {
192            category: None,
193            symbol: Cow::Borrowed("BTCUSDT"),
194            interval: Interval::H1,
195            start: None,
196            end: None,
197            limit: None,
198        }
199    }
200
201    /// Constructs a new Kline request with specified parameters.
202    ///
203    /// Allows full customization of the Kline request. Trading bots should use this to specify exact
204    /// parameters for their strategy, ensuring the `symbol`, `interval`, and `category` align with
205    /// the perpetual futures contract being traded.
206    ///
207    /// # Arguments
208    ///
209    /// * `category` - The product category (Spot, Linear, Inverse, Option)
210    /// * `symbol` - The trading pair symbol (e.g., "BTCUSDT")
211    /// * `interval` - The time interval for each candlestick
212    /// * `start` - Optional start time (Unix timestamp in milliseconds as string)
213    /// * `end` - Optional end time (Unix timestamp in milliseconds as string)
214    /// * `limit` - Optional maximum number of records (1-1000)
215    ///
216    /// # Examples
217    ///
218    /// ```rust
219    /// use bybit::prelude::*;
220    ///
221    /// let request = KlineRequest::new(
222    ///     Some(Category::Linear),
223    ///     "ETHUSDT",
224    ///     Interval::M15,
225    ///     Some("1670601600000"),
226    ///     Some("1670608800000"),
227    ///     Some(100),
228    /// );
229    /// ```
230    pub fn new(
231        category: Option<Category>,
232        symbol: &'a str,
233        interval: Interval,
234        start: Option<&'a str>,
235        end: Option<&'a str>,
236        limit: Option<u64>,
237    ) -> KlineRequest<'a> {
238        KlineRequest {
239            category,
240            symbol: Cow::Borrowed(symbol),
241            interval,
242            start: start.map(Cow::Borrowed),
243            end: end.map(Cow::Borrowed),
244            limit,
245        }
246    }
247
248    /// Creates a simple Kline request with just symbol and interval.
249    ///
250    /// This is a convenience method for common use cases where only the symbol
251    /// and interval are specified, with defaults for other parameters.
252    ///
253    /// # Arguments
254    ///
255    /// * `symbol` - The trading pair symbol (e.g., "BTCUSDT")
256    /// * `interval` - The time interval for each candlestick
257    ///
258    /// # Examples
259    ///
260    /// ```rust
261    /// use bybit::prelude::*;
262    ///
263    /// let request = KlineRequest::simple("BTCUSDT", Interval::D1);
264    /// ```
265    pub fn simple(symbol: &'a str, interval: Interval) -> Self {
266        KlineRequest {
267            category: None,
268            symbol: Cow::Borrowed(symbol),
269            interval,
270            start: None,
271            end: None,
272            limit: None,
273        }
274    }
275
276    /// Creates a builder for constructing KlineRequest with a fluent interface.
277    ///
278    /// # Examples
279    ///
280    /// ```rust
281    /// use bybit::prelude::*;
282    ///
283    /// let request = KlineRequest::builder()
284    ///     .category(Category::Linear)
285    ///     .symbol("BTCUSDT")
286    ///     .interval(Interval::H1)
287    ///     .start("1670601600000")
288    ///     .end("1670608800000")
289    ///     .limit(100)
290    ///     .build();
291    /// ```
292    pub fn builder() -> KlineRequestBuilder<'a> {
293        KlineRequestBuilder::default()
294    }
295
296    /// Validates the request parameters.
297    ///
298    /// Checks that all parameters are within valid ranges and combinations.
299    /// Returns `Ok(())` if valid, or an error message if invalid.
300    ///
301    /// # Errors
302    ///
303    /// Returns an error string if:
304    /// - Limit is outside the range 1-1000
305    /// - Start time is after end time (if both are specified)
306    /// - Category is invalid for the specific kline type (checked by API methods)
307    pub fn validate(&self) -> Result<(), String> {
308        // Validate limit range
309        if let Some(limit) = self.limit {
310            if limit < 1 || limit > 1000 {
311                return Err(format!("Limit must be between 1 and 1000, got {}", limit));
312            }
313        }
314
315        // Validate start <= end if both are specified
316        if let (Some(start_str), Some(end_str)) = (&self.start, &self.end) {
317            if let (Ok(start), Ok(end)) = (start_str.parse::<u64>(), end_str.parse::<u64>()) {
318                if start > end {
319                    return Err(format!(
320                        "Start time ({}) cannot be after end time ({})",
321                        start, end
322                    ));
323                }
324            }
325        }
326
327        Ok(())
328    }
329}
330
331/// Builder for constructing `KlineRequest` with a fluent interface.
332///
333/// This builder provides a convenient way to construct `KlineRequest` objects
334/// with method chaining. It validates parameters at build time to ensure
335/// the request is valid before sending it to the API.
336///
337/// # Examples
338///
339/// ```rust
340/// use bybit::prelude::*;
341///
342/// let request = KlineRequest::builder()
343///     .category(Category::Linear)
344///     .symbol("BTCUSDT")
345///     .interval(Interval::H1)
346///     .start("1670601600000")
347///     .end("1670608800000")
348///     .limit(100)
349///     .build();
350/// ```
351#[derive(Default)]
352pub struct KlineRequestBuilder<'a> {
353    category: Option<Category>,
354    symbol: Option<Cow<'a, str>>,
355    interval: Option<Interval>,
356    start: Option<Cow<'a, str>>,
357    end: Option<Cow<'a, str>>,
358    limit: Option<u64>,
359}
360
361impl<'a> KlineRequestBuilder<'a> {
362    /// Sets the product category.
363    pub fn category(mut self, category: Category) -> Self {
364        self.category = Some(category);
365        self
366    }
367
368    /// Sets the trading pair symbol.
369    pub fn symbol(mut self, symbol: &'a str) -> Self {
370        self.symbol = Some(Cow::Borrowed(symbol));
371        self
372    }
373
374    /// Sets the time interval.
375    pub fn interval(mut self, interval: Interval) -> Self {
376        self.interval = Some(interval);
377        self
378    }
379
380    /// Sets the start time (Unix timestamp in milliseconds as string).
381    pub fn start(mut self, start: &'a str) -> Self {
382        self.start = Some(Cow::Borrowed(start));
383        self
384    }
385
386    /// Sets the end time (Unix timestamp in milliseconds as string).
387    pub fn end(mut self, end: &'a str) -> Self {
388        self.end = Some(Cow::Borrowed(end));
389        self
390    }
391
392    /// Sets the maximum number of records to return (1-1000).
393    pub fn limit(mut self, limit: u64) -> Self {
394        self.limit = Some(limit);
395        self
396    }
397
398    /// Builds the `KlineRequest` and validates the parameters.
399    ///
400    /// # Returns
401    ///
402    /// Returns `Ok(KlineRequest)` if all required parameters are set and valid,
403    /// or an error string if validation fails.
404    ///
405    /// # Errors
406    ///
407    /// Returns an error if:
408    /// - Symbol is not set
409    /// - Interval is not set
410    /// - Limit is outside valid range (1-1000)
411    /// - Start time is after end time
412    pub fn build(self) -> Result<KlineRequest<'a>, String> {
413        let symbol = self
414            .symbol
415            .ok_or_else(|| "Symbol is required".to_string())?;
416        let interval = self
417            .interval
418            .ok_or_else(|| "Interval is required".to_string())?;
419
420        let request = KlineRequest {
421            category: self.category,
422            symbol,
423            interval,
424            start: self.start,
425            end: self.end,
426            limit: self.limit,
427        };
428
429        request.validate()?;
430        Ok(request)
431    }
432}