rustrade_data/exchange/hyperliquid/
mod.rs1use self::{
65 book::HyperliquidL2Book, channel::HyperliquidChannel, market::HyperliquidMarket,
66 subscription::HyperliquidSubResponse, trade::HyperliquidTrade,
67};
68use crate::{
69 ExchangeWsStream, NoInitialSnapshots,
70 exchange::{Connector, ExchangeSub, PingInterval, StreamSelector},
71 instrument::InstrumentData,
72 subscriber::{WebSocketSubscriber, validator::WebSocketSubValidator},
73 subscription::{book::OrderBooksL2, trade::PublicTrades},
74 transformer::stateless::StatelessTransformer,
75};
76use derive_more::Display;
77use rustrade_instrument::exchange::ExchangeId;
78use rustrade_integration::{
79 protocol::websocket::{WebSocketSerdeParser, WsMessage},
80 subscription::SubscriptionId,
81};
82use rustrade_macro::{DeExchange, SerExchange};
83use serde_json::json;
84use std::time::Duration;
85use url::Url;
86
87pub mod book;
88pub mod channel;
89pub mod historical;
90pub mod market;
91pub mod meta;
92pub mod subscription;
93pub mod trade;
94
95pub use meta::{HyperliquidMeta, HyperliquidMetaError};
96pub use rustrade_instrument::hyperliquid::Network;
97
98pub const BASE_URL_HYPERLIQUID: &str = "wss://api.hyperliquid.xyz/ws";
100
101const PING_INTERVAL_SECS: u64 = 50;
103
104pub type HyperliquidWsStream<Transformer> = ExchangeWsStream<WebSocketSerdeParser, Transformer>;
106
107fn build_subscribe_messages(
109 exchange_subs: Vec<ExchangeSub<HyperliquidChannel, HyperliquidMarket>>,
110) -> Vec<WsMessage> {
111 exchange_subs
112 .into_iter()
113 .map(|ExchangeSub { channel, market }| {
114 WsMessage::text(
115 json!({
116 "method": "subscribe",
117 "subscription": channel.subscription_payload(market.as_ref())
118 })
119 .to_string(),
120 )
121 })
122 .collect()
123}
124
125#[derive(
127 Copy,
128 Clone,
129 Eq,
130 PartialEq,
131 Ord,
132 PartialOrd,
133 Hash,
134 Debug,
135 Default,
136 Display,
137 DeExchange,
138 SerExchange,
139)]
140pub struct Hyperliquid;
141
142impl Connector for Hyperliquid {
143 const ID: ExchangeId = ExchangeId::HyperliquidPerp;
144 type Channel = HyperliquidChannel;
145 type Market = HyperliquidMarket;
146 type Subscriber = WebSocketSubscriber;
147 type SubValidator = WebSocketSubValidator;
148 type SubResponse = HyperliquidSubResponse;
149
150 fn url() -> Result<Url, url::ParseError> {
151 Url::parse(BASE_URL_HYPERLIQUID)
152 }
153
154 fn ping_interval() -> Option<PingInterval> {
155 Some(PingInterval {
156 interval: tokio::time::interval(Duration::from_secs(PING_INTERVAL_SECS)),
157 ping: || WsMessage::text(r#"{"method":"ping"}"#),
158 })
159 }
160
161 fn requests(exchange_subs: Vec<ExchangeSub<Self::Channel, Self::Market>>) -> Vec<WsMessage> {
162 build_subscribe_messages(exchange_subs)
163 }
164
165 fn acknowledged_subscription(response: &Self::SubResponse) -> Option<SubscriptionId> {
166 response.subscription_id()
167 }
168}
169
170impl<Instrument> StreamSelector<Instrument, PublicTrades> for Hyperliquid
171where
172 Instrument: InstrumentData,
173{
174 type SnapFetcher = NoInitialSnapshots;
175 type Stream = HyperliquidWsStream<
176 StatelessTransformer<Self, Instrument::Key, PublicTrades, HyperliquidTrade>,
177 >;
178}
179
180impl<Instrument> StreamSelector<Instrument, OrderBooksL2> for Hyperliquid
181where
182 Instrument: InstrumentData,
183{
184 type SnapFetcher = NoInitialSnapshots;
185 type Stream = HyperliquidWsStream<
186 StatelessTransformer<Self, Instrument::Key, OrderBooksL2, HyperliquidL2Book>,
187 >;
188}
189
190#[derive(
202 Copy,
203 Clone,
204 Eq,
205 PartialEq,
206 Ord,
207 PartialOrd,
208 Hash,
209 Debug,
210 Default,
211 Display,
212 DeExchange,
213 SerExchange,
214)]
215pub struct HyperliquidSpot;
216
217impl Connector for HyperliquidSpot {
218 const ID: ExchangeId = ExchangeId::HyperliquidSpot;
219 type Channel = HyperliquidChannel;
220 type Market = HyperliquidMarket;
221 type Subscriber = WebSocketSubscriber;
222 type SubValidator = WebSocketSubValidator;
223 type SubResponse = HyperliquidSubResponse;
224
225 fn url() -> Result<Url, url::ParseError> {
226 Url::parse(BASE_URL_HYPERLIQUID)
227 }
228
229 fn ping_interval() -> Option<PingInterval> {
230 Some(PingInterval {
231 interval: tokio::time::interval(Duration::from_secs(PING_INTERVAL_SECS)),
232 ping: || WsMessage::text(r#"{"method":"ping"}"#),
233 })
234 }
235
236 fn requests(exchange_subs: Vec<ExchangeSub<Self::Channel, Self::Market>>) -> Vec<WsMessage> {
237 build_subscribe_messages(exchange_subs)
238 }
239
240 fn acknowledged_subscription(response: &Self::SubResponse) -> Option<SubscriptionId> {
241 response.subscription_id()
242 }
243}
244
245impl<Instrument> StreamSelector<Instrument, PublicTrades> for HyperliquidSpot
246where
247 Instrument: InstrumentData,
248{
249 type SnapFetcher = NoInitialSnapshots;
250 type Stream = HyperliquidWsStream<
251 StatelessTransformer<Self, Instrument::Key, PublicTrades, HyperliquidTrade>,
252 >;
253}
254
255impl<Instrument> StreamSelector<Instrument, OrderBooksL2> for HyperliquidSpot
256where
257 Instrument: InstrumentData,
258{
259 type SnapFetcher = NoInitialSnapshots;
260 type Stream = HyperliquidWsStream<
261 StatelessTransformer<Self, Instrument::Key, OrderBooksL2, HyperliquidL2Book>,
262 >;
263}