rustrade_data/exchange/massive/
market.rs1use super::Massive;
4use crate::{
5 Identifier,
6 exchange::{ExchangeServer, osi},
7 instrument::MarketInstrumentData,
8 subscription::Subscription,
9};
10use rustrade_instrument::{
11 Keyed,
12 asset::name::AssetNameInternal,
13 instrument::market_data::{MarketDataInstrument, kind::MarketDataInstrumentKind},
14};
15use serde::{Deserialize, Serialize};
16use smol_str::{SmolStr, StrExt, format_smolstr};
17
18#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)]
21pub struct MassiveMarket(pub SmolStr);
22
23impl AsRef<str> for MassiveMarket {
24 fn as_ref(&self) -> &str {
25 &self.0
26 }
27}
28
29impl std::fmt::Display for MassiveMarket {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 f.write_str(&self.0)
32 }
33}
34
35#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
41pub enum MassiveSymbols {
42 Ticker,
44 CryptoPair,
46 CurrencyPair,
48 OptionContract,
50}
51
52pub(super) const OPTION_PREFIX: &str = "O:";
54
55impl MassiveSymbols {
56 fn market(
57 self,
58 base: &AssetNameInternal,
59 quote: &AssetNameInternal,
60 kind: &MarketDataInstrumentKind,
61 ) -> MassiveMarket {
62 let base_name = base.name().to_uppercase_smolstr();
63
64 MassiveMarket(match self {
65 Self::Ticker => base_name,
66 Self::CryptoPair => {
67 format_smolstr!("{base_name}-{}", quote.name().to_uppercase_smolstr())
68 }
69 Self::CurrencyPair => {
70 format_smolstr!("{base_name}/{}", quote.name().to_uppercase_smolstr())
71 }
72 Self::OptionContract => {
73 let spelled = match kind {
74 MarketDataInstrumentKind::Option(contract) => {
75 osi::symbol(base.name(), contract)
76 }
77 _ => None,
78 };
79
80 match spelled {
84 Some(symbol) => format_smolstr!("{OPTION_PREFIX}{symbol}"),
85 None => format_smolstr!("{base_name} (no OSI symbol for {kind})"),
86 }
87 }
88 })
89 }
90}
91
92pub(super) fn is_option_contract(market: &str) -> bool {
94 market
95 .strip_prefix(OPTION_PREFIX)
96 .and_then(osi::root)
97 .is_some()
98}
99
100pub trait MassiveServer: ExchangeServer {
108 const SYMBOLS: MassiveSymbols;
110
111 const QUOTES: &'static str;
113
114 const SECOND_AGGREGATES: &'static str;
116
117 const MINUTE_AGGREGATES: &'static str;
119}
120
121pub trait MassiveTradeServer: MassiveServer {
123 const TRADES: &'static str;
125}
126
127pub trait MassiveInstrument {
132 fn massive_market(&self, symbols: MassiveSymbols) -> MassiveMarket;
134}
135
136impl<Server, Instrument, Kind> Identifier<MassiveMarket>
137 for Subscription<Massive<Server>, Instrument, Kind>
138where
139 Server: MassiveServer,
140 Instrument: MassiveInstrument,
141{
142 fn id(&self) -> MassiveMarket {
143 self.instrument.massive_market(Server::SYMBOLS)
144 }
145}
146
147impl MassiveInstrument for MarketDataInstrument {
148 fn massive_market(&self, symbols: MassiveSymbols) -> MassiveMarket {
149 symbols.market(&self.base, &self.quote, &self.kind)
150 }
151}
152
153impl<InstrumentKey> MassiveInstrument for Keyed<InstrumentKey, MarketDataInstrument> {
154 fn massive_market(&self, symbols: MassiveSymbols) -> MassiveMarket {
155 self.value.massive_market(symbols)
156 }
157}
158
159impl<InstrumentKey> MassiveInstrument for MarketInstrumentData<InstrumentKey> {
162 fn massive_market(&self, _symbols: MassiveSymbols) -> MassiveMarket {
163 MassiveMarket(self.name_exchange.name().clone())
164 }
165}
166
167#[cfg(test)]
168#[allow(clippy::unwrap_used)] mod tests {
170 use super::*;
171 use chrono::{DateTime, Utc};
172 use rust_decimal_macros::dec;
173 use rustrade_instrument::instrument::{
174 kind::option::{OptionExercise, OptionKind},
175 market_data::kind::MarketDataOptionContract,
176 };
177
178 fn instrument(base: &str, quote: &str, kind: MarketDataInstrumentKind) -> MarketDataInstrument {
179 MarketDataInstrument::new(base, quote, kind)
180 }
181
182 fn contract() -> MarketDataInstrumentKind {
183 MarketDataInstrumentKind::Option(MarketDataOptionContract {
184 kind: OptionKind::Call,
185 exercise: OptionExercise::American,
186 expiry: "2025-12-19T21:00:00Z".parse::<DateTime<Utc>>().unwrap(),
187 strike: dec!(150),
188 })
189 }
190
191 #[test]
192 fn each_cluster_spells_markets_as_its_messages_do() {
193 let spot = |base, quote, symbols: MassiveSymbols| {
194 instrument(base, quote, MarketDataInstrumentKind::Spot)
195 .massive_market(symbols)
196 .0
197 };
198
199 assert_eq!(spot("aapl", "usd", MassiveSymbols::Ticker), "AAPL");
200 assert_eq!(spot("btc", "usd", MassiveSymbols::CryptoPair), "BTC-USD");
201 assert_eq!(spot("eur", "usd", MassiveSymbols::CurrencyPair), "EUR/USD");
202 assert_eq!(
203 instrument("aapl", "usd", contract())
204 .massive_market(MassiveSymbols::OptionContract)
205 .0,
206 "O:AAPL251219C00150000",
207 );
208 }
209
210 #[test]
211 fn an_instrument_with_no_option_symbol_is_not_mistaken_for_one() {
212 let market = instrument("aapl", "usd", MarketDataInstrumentKind::Spot)
213 .massive_market(MassiveSymbols::OptionContract);
214
215 assert!(!is_option_contract(&market.0), "{market}");
216 assert!(is_option_contract("O:AAPL251219C00150000"));
217 assert!(!is_option_contract("AAPL251219C00150000"));
218 }
219}