use super::{Hyperliquid, HyperliquidSpot};
use crate::{Identifier, instrument::MarketInstrumentData, subscription::Subscription};
use rustrade_instrument::{
Keyed, asset::name::AssetNameInternal, instrument::market_data::MarketDataInstrument,
};
use serde::{Deserialize, Serialize};
use smol_str::{SmolStr, StrExt, format_smolstr};
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)]
pub struct HyperliquidMarket(pub SmolStr);
pub trait HyperliquidInstrument {
fn hyperliquid_perp_market(&self) -> HyperliquidMarket;
fn hyperliquid_spot_market(&self) -> HyperliquidMarket;
}
impl<Instrument, Kind> Identifier<HyperliquidMarket> for Subscription<Hyperliquid, Instrument, Kind>
where
Instrument: HyperliquidInstrument,
{
fn id(&self) -> HyperliquidMarket {
self.instrument.hyperliquid_perp_market()
}
}
impl<Instrument, Kind> Identifier<HyperliquidMarket>
for Subscription<HyperliquidSpot, Instrument, Kind>
where
Instrument: HyperliquidInstrument,
{
fn id(&self) -> HyperliquidMarket {
self.instrument.hyperliquid_spot_market()
}
}
impl HyperliquidInstrument for MarketDataInstrument {
fn hyperliquid_perp_market(&self) -> HyperliquidMarket {
hyperliquid_perp_market(&self.base)
}
fn hyperliquid_spot_market(&self) -> HyperliquidMarket {
hyperliquid_spot_market(&self.base, &self.quote)
}
}
impl<InstrumentKey> HyperliquidInstrument for Keyed<InstrumentKey, MarketDataInstrument> {
fn hyperliquid_perp_market(&self) -> HyperliquidMarket {
self.value.hyperliquid_perp_market()
}
fn hyperliquid_spot_market(&self) -> HyperliquidMarket {
self.value.hyperliquid_spot_market()
}
}
fn hyperliquid_perp_market(base: &AssetNameInternal) -> HyperliquidMarket {
HyperliquidMarket(base.name().to_uppercase_smolstr())
}
fn hyperliquid_spot_market(
base: &AssetNameInternal,
quote: &AssetNameInternal,
) -> HyperliquidMarket {
let base_name = base.name();
if base_name.starts_with('@') {
HyperliquidMarket(SmolStr::new(base_name))
} else {
HyperliquidMarket(format_smolstr!(
"{}/{}",
base_name.to_uppercase_smolstr(),
quote.name().to_uppercase_smolstr()
))
}
}
impl<InstrumentKey> HyperliquidInstrument for MarketInstrumentData<InstrumentKey> {
fn hyperliquid_perp_market(&self) -> HyperliquidMarket {
HyperliquidMarket(self.name_exchange.name().clone())
}
fn hyperliquid_spot_market(&self) -> HyperliquidMarket {
HyperliquidMarket(self.name_exchange.name().clone())
}
}
impl AsRef<str> for HyperliquidMarket {
fn as_ref(&self) -> &str {
&self.0
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use crate::subscription::trade::PublicTrades;
use rustrade_instrument::{
hyperliquid::{Perps, SpotPairs},
instrument::market_data::kind::MarketDataInstrumentKind,
};
fn market<Exchange, Instrument>(exchange: Exchange, instrument: Instrument) -> String
where
Subscription<Exchange, Instrument, PublicTrades>: Identifier<HyperliquidMarket>,
{
Subscription::new(exchange, instrument, PublicTrades)
.id()
.0
.to_string()
}
#[test]
fn a_market_instrument_data_subscribes_by_the_venue_spelling() {
let perps: Perps =
serde_json::from_str(r#"{"universe": [{"name": "kPEPE", "szDecimals": 0}, {"name": "xyz:TSLA", "szDecimals": 3}]}"#)
.unwrap();
let spot_pairs: SpotPairs = serde_json::from_str(
r#"{
"universe": [{"name": "@107", "tokens": [150, 0], "index": 107}],
"tokens": [{"name": "USDC", "szDecimals": 8, "index": 0}, {"name": "HYPE", "szDecimals": 2, "index": 150}]
}"#,
)
.unwrap();
for (name, coin) in [("KPEPE", "kPEPE"), ("XYZ:TSLA", "xyz:TSLA")] {
let perp = MarketInstrumentData::hyperliquid_perp(0, perps.get(name).unwrap());
assert_eq!(perp.kind, MarketDataInstrumentKind::Perpetual);
assert_eq!(market(Hyperliquid, perp), coin);
}
let hype =
MarketInstrumentData::hyperliquid_spot(0, spot_pairs.find("hype", "usdc").unwrap());
assert_eq!(hype.kind, MarketDataInstrumentKind::Spot);
assert_eq!(market(HyperliquidSpot, hype), "@107");
}
#[test]
fn a_market_data_instrument_loses_the_venue_case() {
let perp =
|base| MarketDataInstrument::new(base, "usdc", MarketDataInstrumentKind::Perpetual);
assert_eq!(market(Hyperliquid, perp("btc")), "BTC");
assert_eq!(market(Hyperliquid, perp("kPEPE")), "KPEPE");
let spot = |base| MarketDataInstrument::new(base, "usdc", MarketDataInstrumentKind::Spot);
assert_eq!(market(HyperliquidSpot, spot("purr")), "PURR/USDC");
assert_eq!(market(HyperliquidSpot, spot("@107")), "@107");
}
#[test]
fn a_keyed_instrument_subscribes_as_its_value_does() {
let perp = MarketDataInstrument::new("btc", "usdc", MarketDataInstrumentKind::Perpetual);
assert_eq!(market(Hyperliquid, Keyed::new(0, perp)), "BTC");
let spot = |base| MarketDataInstrument::new(base, "usdc", MarketDataInstrumentKind::Spot);
assert_eq!(
market(HyperliquidSpot, Keyed::new(0, spot("purr"))),
"PURR/USDC"
);
assert_eq!(market(HyperliquidSpot, Keyed::new(0, spot("@107"))), "@107");
}
}