Skip to main content

df_hyperliquid_core/markets/
candle.rs

1//! Candles and their intervals, the same shape from `candleSnapshot` and from the `candle` feed.
2
3use std::{borrow::Cow, fmt, str::FromStr};
4
5use serde::{Deserialize, Deserializer, de};
6
7use crate::utils::dec::Dec;
8
9/// A candle's width; every interval the venue accepts.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub enum Interval {
12    OneMinute,
13    ThreeMinutes,
14    FiveMinutes,
15    FifteenMinutes,
16    ThirtyMinutes,
17    OneHour,
18    TwoHours,
19    FourHours,
20    EightHours,
21    TwelveHours,
22    OneDay,
23    ThreeDays,
24    OneWeek,
25    OneMonth,
26}
27
28const MINUTE_MS: u64 = 60_000;
29
30impl Interval {
31    /// Every interval the venue accepts, finest first.
32    pub const ALL: [Interval; 14] = [
33        Interval::OneMinute,
34        Interval::ThreeMinutes,
35        Interval::FiveMinutes,
36        Interval::FifteenMinutes,
37        Interval::ThirtyMinutes,
38        Interval::OneHour,
39        Interval::TwoHours,
40        Interval::FourHours,
41        Interval::EightHours,
42        Interval::TwelveHours,
43        Interval::OneDay,
44        Interval::ThreeDays,
45        Interval::OneWeek,
46        Interval::OneMonth,
47    ];
48
49    /// As the venue spells it: `1m` is a minute, `1M` a month.
50    #[must_use]
51    pub const fn wire(self) -> &'static str {
52        match self {
53            Interval::OneMinute => "1m",
54            Interval::ThreeMinutes => "3m",
55            Interval::FiveMinutes => "5m",
56            Interval::FifteenMinutes => "15m",
57            Interval::ThirtyMinutes => "30m",
58            Interval::OneHour => "1h",
59            Interval::TwoHours => "2h",
60            Interval::FourHours => "4h",
61            Interval::EightHours => "8h",
62            Interval::TwelveHours => "12h",
63            Interval::OneDay => "1d",
64            Interval::ThreeDays => "3d",
65            Interval::OneWeek => "1w",
66            Interval::OneMonth => "1M",
67        }
68    }
69
70    /// Accepts only the venue's exact spellings.
71    #[must_use]
72    pub fn parse(text: &str) -> Option<Interval> {
73        Interval::ALL.into_iter().find(|interval| interval.wire() == text)
74    }
75
76    /// Its width in milliseconds; a month counts thirty days.
77    #[must_use]
78    pub const fn ms(self) -> u64 {
79        let minutes = match self {
80            Interval::OneMinute => 1,
81            Interval::ThreeMinutes => 3,
82            Interval::FiveMinutes => 5,
83            Interval::FifteenMinutes => 15,
84            Interval::ThirtyMinutes => 30,
85            Interval::OneHour => 60,
86            Interval::TwoHours => 120,
87            Interval::FourHours => 240,
88            Interval::EightHours => 480,
89            Interval::TwelveHours => 720,
90            Interval::OneDay => 1_440,
91            Interval::ThreeDays => 4_320,
92            Interval::OneWeek => 10_080,
93            Interval::OneMonth => 43_200,
94        };
95        minutes * MINUTE_MS
96    }
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
100#[error("`{0}` is not an interval; the venue takes 1m 3m 5m 15m 30m 1h 2h 4h 8h 12h 1d 3d 1w 1M")]
101pub struct UnknownInterval(pub String);
102
103impl FromStr for Interval {
104    type Err = UnknownInterval;
105
106    fn from_str(text: &str) -> Result<Self, Self::Err> {
107        Interval::parse(text).ok_or_else(|| UnknownInterval(text.to_owned()))
108    }
109}
110
111impl fmt::Display for Interval {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        f.write_str(self.wire())
114    }
115}
116
117impl<'de> Deserialize<'de> for Interval {
118    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
119        let text = <Cow<'de, str>>::deserialize(deserializer)?;
120        text.parse().map_err(de::Error::custom)
121    }
122}
123
124/// One candle, `t` to `T` inclusive. The feed resends it as it forms.
125#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
126pub struct Candle {
127    /// Open time, milliseconds.
128    #[serde(rename = "t")]
129    pub open_time: u64,
130    /// The last millisecond inside it, not the next one's open.
131    #[serde(rename = "T")]
132    pub close_time: u64,
133    #[serde(rename = "s")]
134    pub coin: String,
135    #[serde(rename = "i")]
136    pub interval: Interval,
137    #[serde(rename = "o")]
138    pub open: Dec,
139    #[serde(rename = "c")]
140    pub close: Dec,
141    #[serde(rename = "h")]
142    pub high: Dec,
143    #[serde(rename = "l")]
144    pub low: Dec,
145    /// In the base asset.
146    #[serde(rename = "v")]
147    pub volume: Dec,
148    /// Number of trades in the bar.
149    #[serde(rename = "n")]
150    pub trades: u64,
151}
152
153#[cfg(test)]
154#[path = "tests/candle.rs"]
155mod tests;