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}