df_hyperliquid_core/markets/
candle.rs1use std::{borrow::Cow, fmt, str::FromStr};
5
6use serde::{Deserialize, Deserializer, de};
7
8use crate::client::{ApiError, Transport, info, info_request};
9use crate::utils::Json;
10use crate::utils::dec::Dec;
11
12#[must_use]
15pub fn candle_snapshot_request(coin: &str, interval: Interval, start: u64, end: u64) -> String {
16 let req = Json::Object(vec![
17 ("coin".to_owned(), Json::String(coin.to_owned())),
18 ("interval".to_owned(), Json::String(interval.wire().to_owned())),
19 ("startTime".to_owned(), Json::Number(start.to_string())),
20 ("endTime".to_owned(), Json::Number(end.to_string())),
21 ]);
22 info_request("candleSnapshot", &[("req", req)])
23}
24
25pub async fn candle_snapshot(
27 transport: &impl Transport,
28 coin: &str,
29 interval: Interval,
30 start: u64,
31 end: u64,
32) -> Result<Vec<Candle>, ApiError> {
33 let candles: Option<Vec<Candle>> = info(transport, candle_snapshot_request(coin, interval, start, end)).await?;
34 Ok(candles.unwrap_or_default())
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
39pub enum Interval {
40 OneMinute,
41 ThreeMinutes,
42 FiveMinutes,
43 FifteenMinutes,
44 ThirtyMinutes,
45 OneHour,
46 TwoHours,
47 FourHours,
48 EightHours,
49 TwelveHours,
50 OneDay,
51 ThreeDays,
52 OneWeek,
53 OneMonth,
54}
55
56const MINUTE_MS: u64 = 60_000;
57
58impl Interval {
59 pub const ALL: [Interval; 14] = [
61 Interval::OneMinute,
62 Interval::ThreeMinutes,
63 Interval::FiveMinutes,
64 Interval::FifteenMinutes,
65 Interval::ThirtyMinutes,
66 Interval::OneHour,
67 Interval::TwoHours,
68 Interval::FourHours,
69 Interval::EightHours,
70 Interval::TwelveHours,
71 Interval::OneDay,
72 Interval::ThreeDays,
73 Interval::OneWeek,
74 Interval::OneMonth,
75 ];
76
77 #[must_use]
79 pub const fn wire(self) -> &'static str {
80 match self {
81 Interval::OneMinute => "1m",
82 Interval::ThreeMinutes => "3m",
83 Interval::FiveMinutes => "5m",
84 Interval::FifteenMinutes => "15m",
85 Interval::ThirtyMinutes => "30m",
86 Interval::OneHour => "1h",
87 Interval::TwoHours => "2h",
88 Interval::FourHours => "4h",
89 Interval::EightHours => "8h",
90 Interval::TwelveHours => "12h",
91 Interval::OneDay => "1d",
92 Interval::ThreeDays => "3d",
93 Interval::OneWeek => "1w",
94 Interval::OneMonth => "1M",
95 }
96 }
97
98 #[must_use]
100 pub fn parse(text: &str) -> Option<Interval> {
101 Interval::ALL.into_iter().find(|interval| interval.wire() == text)
102 }
103
104 #[must_use]
106 pub const fn ms(self) -> u64 {
107 let minutes = match self {
108 Interval::OneMinute => 1,
109 Interval::ThreeMinutes => 3,
110 Interval::FiveMinutes => 5,
111 Interval::FifteenMinutes => 15,
112 Interval::ThirtyMinutes => 30,
113 Interval::OneHour => 60,
114 Interval::TwoHours => 120,
115 Interval::FourHours => 240,
116 Interval::EightHours => 480,
117 Interval::TwelveHours => 720,
118 Interval::OneDay => 1_440,
119 Interval::ThreeDays => 4_320,
120 Interval::OneWeek => 10_080,
121 Interval::OneMonth => 43_200,
122 };
123 minutes * MINUTE_MS
124 }
125}
126
127#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
128#[error("`{0}` is not an interval; the venue takes 1m 3m 5m 15m 30m 1h 2h 4h 8h 12h 1d 3d 1w 1M")]
129pub struct UnknownInterval(pub String);
130
131impl FromStr for Interval {
132 type Err = UnknownInterval;
133
134 fn from_str(text: &str) -> Result<Self, Self::Err> {
135 Interval::parse(text).ok_or_else(|| UnknownInterval(text.to_owned()))
136 }
137}
138
139impl fmt::Display for Interval {
140 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141 f.write_str(self.wire())
142 }
143}
144
145impl<'de> Deserialize<'de> for Interval {
146 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
147 let text = <Cow<'de, str>>::deserialize(deserializer)?;
148 text.parse().map_err(de::Error::custom)
149 }
150}
151
152#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
154pub struct Candle {
155 #[serde(rename = "t")]
157 pub open_time: u64,
158 #[serde(rename = "T")]
160 pub close_time: u64,
161 #[serde(rename = "s")]
162 pub coin: String,
163 #[serde(rename = "i")]
164 pub interval: Interval,
165 #[serde(rename = "o")]
166 pub open: Dec,
167 #[serde(rename = "c")]
168 pub close: Dec,
169 #[serde(rename = "h")]
170 pub high: Dec,
171 #[serde(rename = "l")]
172 pub low: Dec,
173 #[serde(rename = "v")]
175 pub volume: Dec,
176 #[serde(rename = "n")]
178 pub trades: u64,
179}
180
181#[cfg(test)]
182#[path = "tests/candle.rs"]
183mod tests;