df_hyperliquid_core/markets/
candle.rs1use std::{borrow::Cow, fmt, str::FromStr};
4
5use serde::{Deserialize, Deserializer, de};
6
7use crate::utils::dec::Dec;
8
9#[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 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 #[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 #[must_use]
72 pub fn parse(text: &str) -> Option<Interval> {
73 Interval::ALL.into_iter().find(|interval| interval.wire() == text)
74 }
75
76 #[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#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
126pub struct Candle {
127 #[serde(rename = "t")]
129 pub open_time: u64,
130 #[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 #[serde(rename = "v")]
147 pub volume: Dec,
148 #[serde(rename = "n")]
150 pub trades: u64,
151}
152
153#[cfg(test)]
154#[path = "tests/candle.rs"]
155mod tests;