nautilus_hyperliquid/
data_types.rs1use std::collections::HashMap;
22
23use nautilus_core::UnixNanos;
24use nautilus_model::{identifiers::InstrumentId, types::Price};
25use nautilus_persistence_macros::custom_data;
26use rust_decimal::Decimal;
27use serde::{Deserialize, Serialize};
28
29#[cfg_attr(
31 feature = "arrow",
32 custom_data(pyo3, stub_module = "nautilus_trader.adapters.hyperliquid")
33)]
34#[cfg_attr(
35 not(feature = "arrow"),
36 custom_data(pyo3, no_arrow, stub_module = "nautilus_trader.adapters.hyperliquid")
37)]
38pub struct HyperliquidAllMids {
39 #[custom_data_field(serde)]
41 pub mids: HashMap<InstrumentId, Price>,
42 pub ts_event: UnixNanos,
44 pub ts_init: UnixNanos,
46}
47
48#[cfg_attr(
53 feature = "arrow",
54 custom_data(pyo3, stub_module = "nautilus_trader.adapters.hyperliquid")
55)]
56#[cfg_attr(
57 not(feature = "arrow"),
58 custom_data(pyo3, no_arrow, stub_module = "nautilus_trader.adapters.hyperliquid")
59)]
60pub struct HyperliquidOpenInterest {
61 pub instrument_id: InstrumentId,
63 #[custom_data_field(serde)]
65 pub open_interest: Decimal,
66 pub ts_event: UnixNanos,
68 pub ts_init: UnixNanos,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
74pub struct HyperliquidImpactPrices {
75 pub bid: Price,
77 pub ask: Price,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
83pub struct HyperliquidDexAssetCtx {
84 pub dex: String,
86 pub instrument_id: InstrumentId,
88 pub mark_price: Price,
90 pub oracle_price: Price,
92 pub prev_day_price: Price,
94 pub mid_price: Option<Price>,
96 pub impact_prices: Option<HyperliquidImpactPrices>,
98 pub funding_rate: Decimal,
100 pub open_interest: Decimal,
102 pub premium: Option<Decimal>,
104 pub day_ntl_volume: Decimal,
106 pub day_base_volume: Decimal,
108}
109
110#[custom_data(pyo3, no_arrow, stub_module = "nautilus_trader.adapters.hyperliquid")]
114pub struct HyperliquidAllDexsAssetCtxs {
115 #[custom_data_field(serde)]
117 pub entries: Vec<HyperliquidDexAssetCtx>,
118 pub ts_event: UnixNanos,
120 pub ts_init: UnixNanos,
122}
123
124pub fn register_hyperliquid_custom_data() {
128 #[cfg(feature = "arrow")]
129 {
130 nautilus_serialization::ensure_custom_data_registered::<HyperliquidAllMids>();
131 nautilus_serialization::ensure_custom_data_registered::<HyperliquidOpenInterest>();
132 }
133
134 #[cfg(not(feature = "arrow"))]
135 {
136 let _ = nautilus_model::data::ensure_custom_data_json_registered::<HyperliquidAllMids>();
137 let _ =
138 nautilus_model::data::ensure_custom_data_json_registered::<HyperliquidOpenInterest>();
139 }
140
141 let _ =
142 nautilus_model::data::ensure_custom_data_json_registered::<HyperliquidAllDexsAssetCtxs>();
143}
144
145#[cfg(test)]
146mod tests {
147 use rstest::rstest;
148
149 use super::*;
150
151 #[rstest]
152 fn test_register_hyperliquid_custom_data_is_idempotent() {
153 register_hyperliquid_custom_data();
154 register_hyperliquid_custom_data();
155 }
156
157 #[cfg(feature = "arrow")]
158 #[rstest]
159 fn test_hyperliquid_all_mids_arrow_schema() {
160 use arrow::datatypes::DataType;
161 use nautilus_serialization::arrow::ArrowSchemaProvider;
162
163 let schema = HyperliquidAllMids::get_schema(None);
164
165 assert_eq!(schema.fields().len(), 3);
166 assert_eq!(schema.field(0).name(), "mids");
167 assert_eq!(schema.field(0).data_type(), &DataType::Utf8);
168 assert_eq!(schema.field(1).name(), "ts_event");
169 assert_eq!(schema.field(1).data_type(), &DataType::UInt64);
170 assert_eq!(schema.field(2).name(), "ts_init");
171 assert_eq!(schema.field(2).data_type(), &DataType::UInt64);
172 }
173
174 #[cfg(feature = "arrow")]
175 #[rstest]
176 fn test_hyperliquid_open_interest_arrow_schema() {
177 use arrow::datatypes::DataType;
178 use nautilus_serialization::arrow::ArrowSchemaProvider;
179
180 let schema = HyperliquidOpenInterest::get_schema(None);
181
182 assert_eq!(schema.fields().len(), 4);
183 assert_eq!(schema.field(0).name(), "instrument_id");
184 assert!(matches!(
185 schema.field(0).data_type(),
186 DataType::Utf8 | DataType::Utf8View
187 ));
188 assert_eq!(schema.field(1).name(), "open_interest");
189 assert!(matches!(
190 schema.field(1).data_type(),
191 DataType::Utf8 | DataType::Utf8View
192 ));
193 assert_eq!(schema.field(2).name(), "ts_event");
194 assert_eq!(schema.field(2).data_type(), &DataType::UInt64);
195 assert_eq!(schema.field(3).name(), "ts_init");
196 assert_eq!(schema.field(3).data_type(), &DataType::UInt64);
197 }
198
199 #[cfg(feature = "arrow")]
200 #[rstest]
201 fn test_hyperliquid_open_interest_arrow_round_trip_preserves_decimal() {
202 use std::str::FromStr;
203
204 use nautilus_model::data::Data;
205 use nautilus_serialization::arrow::{DecodeDataFromRecordBatch, EncodeToRecordBatch};
206
207 let original = HyperliquidOpenInterest::new(
208 InstrumentId::from("BTC-USD-PERP.HYPERLIQUID"),
209 Decimal::from_str("123456.789012345678").unwrap(),
210 UnixNanos::from(1),
211 UnixNanos::from(2),
212 );
213 let metadata = EncodeToRecordBatch::metadata(&original);
214 let batch =
215 HyperliquidOpenInterest::encode_batch(&metadata, std::slice::from_ref(&original))
216 .unwrap();
217 let decoded = HyperliquidOpenInterest::decode_data_batch(&metadata, batch).unwrap();
218
219 assert_eq!(decoded.len(), 1);
220 match &decoded[0] {
221 Data::Custom(custom) => {
222 let open_interest = custom
223 .data
224 .as_any()
225 .downcast_ref::<HyperliquidOpenInterest>()
226 .expect("expected HyperliquidOpenInterest");
227 assert_eq!(open_interest.instrument_id, original.instrument_id);
228 assert_eq!(open_interest.open_interest, original.open_interest);
229 assert_eq!(open_interest.ts_event, original.ts_event);
230 assert_eq!(open_interest.ts_init, original.ts_init);
231 }
232 other => panic!("Expected Data::Custom, was {other:?}"),
233 }
234 }
235}