rustrade_data/exchange/hyperliquid/
market.rs1use super::{Hyperliquid, HyperliquidSpot};
22use crate::{Identifier, instrument::MarketInstrumentData, subscription::Subscription};
23use rustrade_instrument::{
24 Keyed, asset::name::AssetNameInternal, instrument::market_data::MarketDataInstrument,
25};
26use serde::{Deserialize, Serialize};
27use smol_str::{SmolStr, StrExt, format_smolstr};
28
29#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)]
32pub struct HyperliquidMarket(pub SmolStr);
33
34pub trait HyperliquidInstrument {
42 fn hyperliquid_market(&self) -> HyperliquidMarket;
44}
45
46impl<Instrument, Kind> Identifier<HyperliquidMarket> for Subscription<Hyperliquid, Instrument, Kind>
47where
48 Instrument: HyperliquidInstrument,
49{
50 fn id(&self) -> HyperliquidMarket {
51 self.instrument.hyperliquid_market()
52 }
53}
54
55impl HyperliquidInstrument for MarketDataInstrument {
56 fn hyperliquid_market(&self) -> HyperliquidMarket {
57 hyperliquid_market(&self.base)
58 }
59}
60
61impl<InstrumentKey> HyperliquidInstrument for Keyed<InstrumentKey, MarketDataInstrument> {
62 fn hyperliquid_market(&self) -> HyperliquidMarket {
63 self.value.hyperliquid_market()
64 }
65}
66
67fn hyperliquid_market(base: &AssetNameInternal) -> HyperliquidMarket {
70 HyperliquidMarket(base.name().to_uppercase_smolstr())
71}
72
73impl<InstrumentKey> HyperliquidInstrument for MarketInstrumentData<InstrumentKey> {
74 fn hyperliquid_market(&self) -> HyperliquidMarket {
75 HyperliquidMarket(self.name_exchange.name().clone())
76 }
77}
78
79impl AsRef<str> for HyperliquidMarket {
80 fn as_ref(&self) -> &str {
81 &self.0
82 }
83}
84
85impl<Kind> Identifier<HyperliquidMarket>
88 for Subscription<HyperliquidSpot, MarketDataInstrument, Kind>
89{
90 fn id(&self) -> HyperliquidMarket {
91 hyperliquid_spot_market(&self.instrument.base, &self.instrument.quote)
92 }
93}
94
95impl<InstrumentKey, Kind> Identifier<HyperliquidMarket>
96 for Subscription<HyperliquidSpot, Keyed<InstrumentKey, MarketDataInstrument>, Kind>
97{
98 fn id(&self) -> HyperliquidMarket {
99 hyperliquid_spot_market(&self.instrument.value.base, &self.instrument.value.quote)
100 }
101}
102
103fn hyperliquid_spot_market(
107 base: &AssetNameInternal,
108 quote: &AssetNameInternal,
109) -> HyperliquidMarket {
110 let base_name = base.name();
111 if base_name.starts_with('@') {
112 HyperliquidMarket(SmolStr::new(base_name))
113 } else {
114 HyperliquidMarket(format_smolstr!(
115 "{}/{}",
116 base_name.to_uppercase_smolstr(),
117 quote.name().to_uppercase_smolstr()
118 ))
119 }
120}
121
122impl<InstrumentKey, Kind> Identifier<HyperliquidMarket>
123 for Subscription<HyperliquidSpot, MarketInstrumentData<InstrumentKey>, Kind>
124{
125 fn id(&self) -> HyperliquidMarket {
126 HyperliquidMarket(self.instrument.name_exchange.name().clone())
127 }
128}
129
130#[cfg(test)]
131#[allow(clippy::unwrap_used)]
133mod tests {
134 use super::*;
135 use crate::subscription::trade::PublicTrades;
136 use rustrade_instrument::{
137 hyperliquid::{Perps, SpotPairs},
138 instrument::market_data::kind::MarketDataInstrumentKind,
139 };
140
141 fn market<Exchange, Instrument>(exchange: Exchange, instrument: Instrument) -> String
142 where
143 Subscription<Exchange, Instrument, PublicTrades>: Identifier<HyperliquidMarket>,
144 {
145 Subscription::new(exchange, instrument, PublicTrades)
146 .id()
147 .0
148 .to_string()
149 }
150
151 #[test]
152 fn a_market_instrument_data_subscribes_by_the_venue_spelling() {
153 let perps: Perps =
154 serde_json::from_str(r#"{"universe": [{"name": "kPEPE"}, {"name": "xyz:TSLA"}]}"#)
155 .unwrap();
156 let spot_pairs: SpotPairs = serde_json::from_str(
157 r#"{
158 "universe": [{"name": "@107", "tokens": [150, 0], "index": 107}],
159 "tokens": [{"name": "USDC", "szDecimals": 8, "index": 0}, {"name": "HYPE", "szDecimals": 2, "index": 150}]
160 }"#,
161 )
162 .unwrap();
163
164 for (name, coin) in [("KPEPE", "kPEPE"), ("XYZ:TSLA", "xyz:TSLA")] {
165 let perp = MarketInstrumentData::hyperliquid_perp(0, perps.get(name).unwrap());
166 assert_eq!(perp.kind, MarketDataInstrumentKind::Perpetual);
167 assert_eq!(market(Hyperliquid, perp), coin);
168 }
169 let hype =
170 MarketInstrumentData::hyperliquid_spot(0, spot_pairs.find("hype", "usdc").unwrap());
171 assert_eq!(hype.kind, MarketDataInstrumentKind::Spot);
172 assert_eq!(market(HyperliquidSpot, hype), "@107");
173 }
174
175 #[test]
176 fn a_market_data_instrument_loses_the_venue_case() {
177 let perp =
179 |base| MarketDataInstrument::new(base, "usdc", MarketDataInstrumentKind::Perpetual);
180 assert_eq!(market(Hyperliquid, perp("btc")), "BTC");
181 assert_eq!(market(Hyperliquid, perp("kPEPE")), "KPEPE");
182
183 let spot = |base| MarketDataInstrument::new(base, "usdc", MarketDataInstrumentKind::Spot);
184 assert_eq!(market(HyperliquidSpot, spot("purr")), "PURR/USDC");
185 assert_eq!(market(HyperliquidSpot, spot("@107")), "@107");
186 }
187}