use std::collections::HashMap;
use nautilus_core::UnixNanos;
use nautilus_model::{identifiers::InstrumentId, types::Price};
use nautilus_persistence_macros::custom_data;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
#[cfg_attr(
feature = "arrow",
custom_data(pyo3, stub_module = "nautilus_trader.adapters.hyperliquid")
)]
#[cfg_attr(
not(feature = "arrow"),
custom_data(pyo3, no_arrow, stub_module = "nautilus_trader.adapters.hyperliquid")
)]
pub struct HyperliquidAllMids {
#[custom_data_field(serde)]
pub mids: HashMap<InstrumentId, Price>,
pub ts_event: UnixNanos,
pub ts_init: UnixNanos,
}
#[cfg_attr(
feature = "arrow",
custom_data(pyo3, stub_module = "nautilus_trader.adapters.hyperliquid")
)]
#[cfg_attr(
not(feature = "arrow"),
custom_data(pyo3, no_arrow, stub_module = "nautilus_trader.adapters.hyperliquid")
)]
pub struct HyperliquidOpenInterest {
pub instrument_id: InstrumentId,
#[custom_data_field(serde)]
pub open_interest: Decimal,
pub ts_event: UnixNanos,
pub ts_init: UnixNanos,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct HyperliquidImpactPrices {
pub bid: Price,
pub ask: Price,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct HyperliquidDexAssetCtx {
pub dex: String,
pub instrument_id: InstrumentId,
pub mark_price: Price,
pub oracle_price: Price,
pub prev_day_price: Price,
pub mid_price: Option<Price>,
pub impact_prices: Option<HyperliquidImpactPrices>,
pub funding_rate: Decimal,
pub open_interest: Decimal,
pub premium: Option<Decimal>,
pub day_ntl_volume: Decimal,
pub day_base_volume: Decimal,
}
#[custom_data(pyo3, no_arrow, stub_module = "nautilus_trader.adapters.hyperliquid")]
pub struct HyperliquidAllDexsAssetCtxs {
#[custom_data_field(serde)]
pub entries: Vec<HyperliquidDexAssetCtx>,
pub ts_event: UnixNanos,
pub ts_init: UnixNanos,
}
pub fn register_hyperliquid_custom_data() {
#[cfg(feature = "arrow")]
{
nautilus_serialization::ensure_custom_data_registered::<HyperliquidAllMids>();
nautilus_serialization::ensure_custom_data_registered::<HyperliquidOpenInterest>();
}
#[cfg(not(feature = "arrow"))]
{
let _ = nautilus_model::data::ensure_custom_data_json_registered::<HyperliquidAllMids>();
let _ =
nautilus_model::data::ensure_custom_data_json_registered::<HyperliquidOpenInterest>();
}
let _ =
nautilus_model::data::ensure_custom_data_json_registered::<HyperliquidAllDexsAssetCtxs>();
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
fn test_register_hyperliquid_custom_data_is_idempotent() {
register_hyperliquid_custom_data();
register_hyperliquid_custom_data();
}
#[cfg(feature = "arrow")]
#[rstest]
fn test_hyperliquid_all_mids_arrow_schema() {
use arrow::datatypes::DataType;
use nautilus_serialization::arrow::ArrowSchemaProvider;
let schema = HyperliquidAllMids::get_schema(None);
assert_eq!(schema.fields().len(), 3);
assert_eq!(schema.field(0).name(), "mids");
assert_eq!(schema.field(0).data_type(), &DataType::Utf8);
assert_eq!(schema.field(1).name(), "ts_event");
assert_eq!(schema.field(1).data_type(), &DataType::UInt64);
assert_eq!(schema.field(2).name(), "ts_init");
assert_eq!(schema.field(2).data_type(), &DataType::UInt64);
}
#[cfg(feature = "arrow")]
#[rstest]
fn test_hyperliquid_open_interest_arrow_schema() {
use arrow::datatypes::DataType;
use nautilus_serialization::arrow::ArrowSchemaProvider;
let schema = HyperliquidOpenInterest::get_schema(None);
assert_eq!(schema.fields().len(), 4);
assert_eq!(schema.field(0).name(), "instrument_id");
assert!(matches!(
schema.field(0).data_type(),
DataType::Utf8 | DataType::Utf8View
));
assert_eq!(schema.field(1).name(), "open_interest");
assert!(matches!(
schema.field(1).data_type(),
DataType::Utf8 | DataType::Utf8View
));
assert_eq!(schema.field(2).name(), "ts_event");
assert_eq!(schema.field(2).data_type(), &DataType::UInt64);
assert_eq!(schema.field(3).name(), "ts_init");
assert_eq!(schema.field(3).data_type(), &DataType::UInt64);
}
#[cfg(feature = "arrow")]
#[rstest]
fn test_hyperliquid_open_interest_arrow_round_trip_preserves_decimal() {
use std::str::FromStr;
use nautilus_model::data::Data;
use nautilus_serialization::arrow::{DecodeDataFromRecordBatch, EncodeToRecordBatch};
let original = HyperliquidOpenInterest::new(
InstrumentId::from("BTC-USD-PERP.HYPERLIQUID"),
Decimal::from_str("123456.789012345678").unwrap(),
UnixNanos::from(1),
UnixNanos::from(2),
);
let metadata = EncodeToRecordBatch::metadata(&original);
let batch =
HyperliquidOpenInterest::encode_batch(&metadata, std::slice::from_ref(&original))
.unwrap();
let decoded = HyperliquidOpenInterest::decode_data_batch(&metadata, batch).unwrap();
assert_eq!(decoded.len(), 1);
match &decoded[0] {
Data::Custom(custom) => {
let open_interest = custom
.data
.as_any()
.downcast_ref::<HyperliquidOpenInterest>()
.expect("expected HyperliquidOpenInterest");
assert_eq!(open_interest.instrument_id, original.instrument_id);
assert_eq!(open_interest.open_interest, original.open_interest);
assert_eq!(open_interest.ts_event, original.ts_event);
assert_eq!(open_interest.ts_init, original.ts_init);
}
other => panic!("Expected Data::Custom, was {other:?}"),
}
}
}