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 {
43 fn hyperliquid_perp_market(&self) -> HyperliquidMarket;
45
46 fn hyperliquid_spot_market(&self) -> HyperliquidMarket;
48}
49
50impl<Instrument, Kind> Identifier<HyperliquidMarket> for Subscription<Hyperliquid, Instrument, Kind>
51where
52 Instrument: HyperliquidInstrument,
53{
54 fn id(&self) -> HyperliquidMarket {
55 self.instrument.hyperliquid_perp_market()
56 }
57}
58
59impl<Instrument, Kind> Identifier<HyperliquidMarket>
60 for Subscription<HyperliquidSpot, Instrument, Kind>
61where
62 Instrument: HyperliquidInstrument,
63{
64 fn id(&self) -> HyperliquidMarket {
65 self.instrument.hyperliquid_spot_market()
66 }
67}
68
69impl HyperliquidInstrument for MarketDataInstrument {
70 fn hyperliquid_perp_market(&self) -> HyperliquidMarket {
71 hyperliquid_perp_market(&self.base)
72 }
73
74 fn hyperliquid_spot_market(&self) -> HyperliquidMarket {
75 hyperliquid_spot_market(&self.base, &self.quote)
76 }
77}
78
79impl<InstrumentKey> HyperliquidInstrument for Keyed<InstrumentKey, MarketDataInstrument> {
80 fn hyperliquid_perp_market(&self) -> HyperliquidMarket {
81 self.value.hyperliquid_perp_market()
82 }
83
84 fn hyperliquid_spot_market(&self) -> HyperliquidMarket {
85 self.value.hyperliquid_spot_market()
86 }
87}
88
89fn hyperliquid_perp_market(base: &AssetNameInternal) -> HyperliquidMarket {
92 HyperliquidMarket(base.name().to_uppercase_smolstr())
93}
94
95fn hyperliquid_spot_market(
99 base: &AssetNameInternal,
100 quote: &AssetNameInternal,
101) -> HyperliquidMarket {
102 let base_name = base.name();
103 if base_name.starts_with('@') {
104 HyperliquidMarket(SmolStr::new(base_name))
105 } else {
106 HyperliquidMarket(format_smolstr!(
107 "{}/{}",
108 base_name.to_uppercase_smolstr(),
109 quote.name().to_uppercase_smolstr()
110 ))
111 }
112}
113
114impl<InstrumentKey> HyperliquidInstrument for MarketInstrumentData<InstrumentKey> {
117 fn hyperliquid_perp_market(&self) -> HyperliquidMarket {
118 HyperliquidMarket(self.name_exchange.name().clone())
119 }
120
121 fn hyperliquid_spot_market(&self) -> HyperliquidMarket {
122 HyperliquidMarket(self.name_exchange.name().clone())
123 }
124}
125
126impl AsRef<str> for HyperliquidMarket {
127 fn as_ref(&self) -> &str {
128 &self.0
129 }
130}
131
132#[cfg(test)]
133#[allow(clippy::unwrap_used)]
135mod tests {
136 use super::*;
137 use crate::subscription::trade::PublicTrades;
138 use rustrade_instrument::{
139 hyperliquid::{Perps, SpotPairs},
140 instrument::market_data::kind::MarketDataInstrumentKind,
141 };
142
143 fn market<Exchange, Instrument>(exchange: Exchange, instrument: Instrument) -> String
144 where
145 Subscription<Exchange, Instrument, PublicTrades>: Identifier<HyperliquidMarket>,
146 {
147 Subscription::new(exchange, instrument, PublicTrades)
148 .id()
149 .0
150 .to_string()
151 }
152
153 #[test]
154 fn a_market_instrument_data_subscribes_by_the_venue_spelling() {
155 let perps: Perps =
156 serde_json::from_str(r#"{"universe": [{"name": "kPEPE", "szDecimals": 0}, {"name": "xyz:TSLA", "szDecimals": 3}]}"#)
157 .unwrap();
158 let spot_pairs: SpotPairs = serde_json::from_str(
159 r#"{
160 "universe": [{"name": "@107", "tokens": [150, 0], "index": 107}],
161 "tokens": [{"name": "USDC", "szDecimals": 8, "index": 0}, {"name": "HYPE", "szDecimals": 2, "index": 150}]
162 }"#,
163 )
164 .unwrap();
165
166 for (name, coin) in [("KPEPE", "kPEPE"), ("XYZ:TSLA", "xyz:TSLA")] {
167 let perp = MarketInstrumentData::hyperliquid_perp(0, perps.get(name).unwrap());
168 assert_eq!(perp.kind, MarketDataInstrumentKind::Perpetual);
169 assert_eq!(market(Hyperliquid, perp), coin);
170 }
171 let hype =
172 MarketInstrumentData::hyperliquid_spot(0, spot_pairs.find("hype", "usdc").unwrap());
173 assert_eq!(hype.kind, MarketDataInstrumentKind::Spot);
174 assert_eq!(market(HyperliquidSpot, hype), "@107");
175 }
176
177 #[test]
178 fn a_market_data_instrument_loses_the_venue_case() {
179 let perp =
181 |base| MarketDataInstrument::new(base, "usdc", MarketDataInstrumentKind::Perpetual);
182 assert_eq!(market(Hyperliquid, perp("btc")), "BTC");
183 assert_eq!(market(Hyperliquid, perp("kPEPE")), "KPEPE");
184
185 let spot = |base| MarketDataInstrument::new(base, "usdc", MarketDataInstrumentKind::Spot);
186 assert_eq!(market(HyperliquidSpot, spot("purr")), "PURR/USDC");
187 assert_eq!(market(HyperliquidSpot, spot("@107")), "@107");
188 }
189
190 #[test]
191 fn a_keyed_instrument_subscribes_as_its_value_does() {
192 let perp = MarketDataInstrument::new("btc", "usdc", MarketDataInstrumentKind::Perpetual);
193 assert_eq!(market(Hyperliquid, Keyed::new(0, perp)), "BTC");
194
195 let spot = |base| MarketDataInstrument::new(base, "usdc", MarketDataInstrumentKind::Spot);
196 assert_eq!(
197 market(HyperliquidSpot, Keyed::new(0, spot("purr"))),
198 "PURR/USDC"
199 );
200 assert_eq!(market(HyperliquidSpot, Keyed::new(0, spot("@107"))), "@107");
201 }
202}