rustrade_data/exchange/alpaca/
market.rs1use super::{Alpaca, AlpacaServerCrypto, AlpacaServerIex, AlpacaServerSip};
2use crate::{Identifier, instrument::MarketInstrumentData, subscription::Subscription};
3use rustrade_instrument::{
4 Keyed, asset::name::AssetNameInternal, instrument::market_data::MarketDataInstrument,
5};
6use serde::{Deserialize, Serialize};
7use smol_str::{SmolStr, StrExt, format_smolstr};
8
9#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Deserialize, Serialize)]
14pub struct AlpacaMarket(pub SmolStr);
15
16impl AsRef<str> for AlpacaMarket {
17 fn as_ref(&self) -> &str {
18 &self.0
19 }
20}
21
22fn equities_market(base: &AssetNameInternal) -> AlpacaMarket {
23 AlpacaMarket(base.name().to_uppercase_smolstr())
24}
25
26fn crypto_market(base: &AssetNameInternal, quote: &AssetNameInternal) -> AlpacaMarket {
27 AlpacaMarket(format_smolstr!(
28 "{}/{}",
29 base.name().to_uppercase_smolstr(),
30 quote.name().to_uppercase_smolstr()
31 ))
32}
33
34#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
38pub enum AlpacaSymbolShape {
39 Ticker,
41 Pair,
43}
44
45impl AlpacaSymbolShape {
46 fn market(self, instrument: &MarketDataInstrument) -> AlpacaMarket {
47 match self {
48 Self::Ticker => equities_market(&instrument.base),
49 Self::Pair => crypto_market(&instrument.base, &instrument.quote),
50 }
51 }
52}
53
54pub trait AlpacaServer: crate::exchange::ExchangeServer {
60 const SYMBOL_SHAPE: AlpacaSymbolShape;
62}
63
64impl AlpacaServer for AlpacaServerIex {
65 const SYMBOL_SHAPE: AlpacaSymbolShape = AlpacaSymbolShape::Ticker;
66}
67
68impl AlpacaServer for AlpacaServerSip {
69 const SYMBOL_SHAPE: AlpacaSymbolShape = AlpacaSymbolShape::Ticker;
70}
71
72impl AlpacaServer for AlpacaServerCrypto {
73 const SYMBOL_SHAPE: AlpacaSymbolShape = AlpacaSymbolShape::Pair;
74}
75
76pub trait AlpacaInstrument {
87 fn alpaca_market(&self, shape: AlpacaSymbolShape) -> AlpacaMarket;
89}
90
91impl<Server, Instrument, Kind> Identifier<AlpacaMarket>
92 for Subscription<Alpaca<Server>, Instrument, Kind>
93where
94 Server: AlpacaServer,
95 Instrument: AlpacaInstrument,
96{
97 fn id(&self) -> AlpacaMarket {
98 self.instrument.alpaca_market(Server::SYMBOL_SHAPE)
99 }
100}
101
102impl AlpacaInstrument for MarketDataInstrument {
103 fn alpaca_market(&self, shape: AlpacaSymbolShape) -> AlpacaMarket {
104 shape.market(self)
105 }
106}
107
108impl<InstrumentKey> AlpacaInstrument for Keyed<InstrumentKey, MarketDataInstrument> {
109 fn alpaca_market(&self, shape: AlpacaSymbolShape) -> AlpacaMarket {
110 self.value.alpaca_market(shape)
111 }
112}
113
114impl<InstrumentKey> AlpacaInstrument for MarketInstrumentData<InstrumentKey> {
122 fn alpaca_market(&self, _shape: AlpacaSymbolShape) -> AlpacaMarket {
123 AlpacaMarket(self.name_exchange.name().to_uppercase_smolstr())
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use rustrade_instrument::asset::name::AssetNameInternal;
131
132 #[test]
133 fn test_equities_market_format() {
134 let base = AssetNameInternal::new("aapl");
135 let market = equities_market(&base);
136 assert_eq!(market.as_ref(), "AAPL");
137 }
138
139 #[test]
140 fn test_crypto_market_format() {
141 let base = AssetNameInternal::new("btc");
142 let quote = AssetNameInternal::new("usd");
143 let market = crypto_market(&base, "e);
144 assert_eq!(market.as_ref(), "BTC/USD");
145 }
146
147 #[test]
148 fn each_feed_spells_its_symbols_with_its_own_shape() {
149 use crate::subscription::trade::PublicTrades;
150 use rustrade_instrument::instrument::market_data::kind::MarketDataInstrumentKind;
151
152 fn market<Server: AlpacaServer>(base: &str, quote: &str) -> AlpacaMarket {
153 let subscription: Subscription<Alpaca<Server>, MarketDataInstrument, PublicTrades> =
154 Subscription::from((
155 Alpaca::<Server>::default(),
156 base,
157 quote,
158 MarketDataInstrumentKind::Spot,
159 PublicTrades,
160 ));
161 subscription.id()
162 }
163
164 assert_eq!(market::<AlpacaServerIex>("aapl", "usd").as_ref(), "AAPL");
165 assert_eq!(market::<AlpacaServerSip>("aapl", "usd").as_ref(), "AAPL");
166 assert_eq!(
167 market::<AlpacaServerCrypto>("btc", "usd").as_ref(),
168 "BTC/USD"
169 );
170 }
171
172 #[test]
173 fn an_exchange_named_instrument_is_taken_verbatim() {
174 assert_eq!(
175 market_of_exchange_named::<AlpacaServerCrypto>("BTC/USD").as_ref(),
176 "BTC/USD"
177 );
178 assert_eq!(
179 market_of_exchange_named::<AlpacaServerIex>("AAPL").as_ref(),
180 "AAPL"
181 );
182 }
183
184 #[test]
187 fn an_exchange_named_instrument_is_uppercased_like_a_reconstructed_one() {
188 assert_eq!(
189 market_of_exchange_named::<AlpacaServerCrypto>("btc/usd").as_ref(),
190 "BTC/USD"
191 );
192 assert_eq!(
193 market_of_exchange_named::<AlpacaServerSip>("brk.b").as_ref(),
194 "BRK.B"
195 );
196 }
197
198 fn market_of_exchange_named<Server: AlpacaServer>(name_exchange: &str) -> AlpacaMarket {
199 use crate::subscription::trade::PublicTrades;
200 use rustrade_instrument::instrument::{
201 market_data::kind::MarketDataInstrumentKind, name::InstrumentNameExchange,
202 };
203
204 let subscription = Subscription {
205 exchange: Alpaca::<Server>::default(),
206 instrument: MarketInstrumentData {
207 key: 0_usize,
208 name_exchange: InstrumentNameExchange::new(name_exchange),
209 kind: MarketDataInstrumentKind::Spot,
210 },
211 kind: PublicTrades,
212 };
213
214 subscription.id()
215 }
216}