1use crate::error::KeyError;
2use barter_instrument::{
3 Keyed,
4 asset::{Asset, AssetIndex, ExchangeAsset, name::AssetNameExchange},
5 exchange::{ExchangeId, ExchangeIndex},
6 index::{IndexedInstruments, error::IndexError},
7 instrument::{Instrument, InstrumentIndex, name::InstrumentNameExchange},
8};
9use barter_integration::collection::FnvIndexSet;
10use fnv::FnvHashMap;
11
12#[derive(Debug, Clone, Eq, PartialEq)]
22pub struct ExecutionInstrumentMap {
23 pub exchange: Keyed<ExchangeIndex, ExchangeId>,
25 pub assets: FnvIndexSet<ExchangeAsset<Asset>>,
28 pub instruments: FnvIndexSet<Instrument<Keyed<ExchangeIndex, ExchangeId>, AssetIndex>>,
31 pub asset_names: FnvHashMap<AssetNameExchange, AssetIndex>,
34 pub instrument_names: FnvHashMap<InstrumentNameExchange, InstrumentIndex>,
37}
38
39impl ExecutionInstrumentMap {
40 pub fn new(
42 exchange: Keyed<ExchangeIndex, ExchangeId>,
43 instruments: &IndexedInstruments,
44 ) -> Self {
45 let asset_names = instruments
46 .assets()
47 .iter()
48 .filter_map(|Keyed { key, value }| {
49 (value.exchange == exchange.value)
50 .then_some((value.asset.name_exchange.clone(), *key))
51 })
52 .collect();
53
54 let assets = instruments
55 .assets()
56 .iter()
57 .map(|Keyed { value, .. }| value.clone())
58 .collect();
59
60 let instrument_names = instruments
61 .instruments()
62 .iter()
63 .filter_map(|Keyed { key, value }| {
64 (value.exchange.value == exchange.value)
65 .then_some((value.name_exchange.clone(), *key))
66 })
67 .collect();
68
69 let instruments = instruments
70 .instruments()
71 .iter()
72 .map(|Keyed { value, .. }| value.clone())
73 .collect();
74
75 Self {
76 exchange,
77 asset_names,
78 instrument_names,
79 assets,
80 instruments,
81 }
82 }
83
84 pub fn exchange_assets(&self) -> impl Iterator<Item = &AssetNameExchange> {
85 self.asset_names.keys()
86 }
87
88 pub fn exchange_instruments(&self) -> impl Iterator<Item = &InstrumentNameExchange> {
89 self.instrument_names.keys()
90 }
91
92 pub fn find_exchange_id(&self, exchange: ExchangeIndex) -> Result<ExchangeId, KeyError> {
93 if self.exchange.key == exchange {
94 Ok(self.exchange.value)
95 } else {
96 Err(KeyError::ExchangeId(format!(
97 "ExecutionInstrumentMap does not contain {exchange}"
98 )))
99 }
100 }
101
102 pub fn find_exchange_index(&self, exchange: ExchangeId) -> Result<ExchangeIndex, IndexError> {
103 if self.exchange.value == exchange {
104 Ok(self.exchange.key)
105 } else {
106 Err(IndexError::ExchangeIndex(format!(
107 "ExecutionInstrumentMap does not contain {exchange}"
108 )))
109 }
110 }
111
112 pub fn find_asset_name_exchange(
113 &self,
114 asset: AssetIndex,
115 ) -> Result<&AssetNameExchange, KeyError> {
116 self.assets
117 .get_index(asset.index())
118 .ok_or_else(|| {
119 KeyError::AssetKey(format!("ExecutionInstrumentMap does not contain: {asset}"))
120 })
121 .map(|asset| &asset.asset.name_exchange)
122 }
123
124 pub fn find_asset_index(&self, asset: &AssetNameExchange) -> Result<AssetIndex, IndexError> {
125 self.asset_names.get(asset).copied().ok_or_else(|| {
126 IndexError::AssetIndex(format!("ExecutionInstrumentMap does not contain: {asset}"))
127 })
128 }
129
130 pub fn find_instrument_name_exchange(
131 &self,
132 instrument: InstrumentIndex,
133 ) -> Result<&InstrumentNameExchange, KeyError> {
134 self.instruments
135 .get_index(instrument.index())
136 .ok_or_else(|| {
137 KeyError::InstrumentKey(format!(
138 "ExecutionInstrumentMap does not contain: {instrument}"
139 ))
140 })
141 .map(|instrument| &instrument.name_exchange)
142 }
143
144 pub fn find_instrument_index(
145 &self,
146 instrument: &InstrumentNameExchange,
147 ) -> Result<InstrumentIndex, IndexError> {
148 self.instrument_names
149 .get(instrument)
150 .copied()
151 .ok_or_else(|| {
152 IndexError::InstrumentIndex(format!(
153 "ExecutionInstrumentMap does not contain: {instrument}"
154 ))
155 })
156 }
157}
158
159pub fn generate_execution_instrument_map(
160 instruments: &IndexedInstruments,
161 exchange: ExchangeId,
162) -> Result<ExecutionInstrumentMap, IndexError> {
163 let exchange_index = instruments
164 .exchanges()
165 .iter()
166 .find_map(|keyed_exchange| (keyed_exchange.value == exchange).then_some(keyed_exchange.key))
167 .ok_or_else(|| {
168 IndexError::ExchangeIndex(format!(
169 "IndexedInstrument does not contain index for: {exchange}"
170 ))
171 })?;
172
173 Ok(ExecutionInstrumentMap::new(
174 Keyed::new(exchange_index, exchange),
175 instruments,
176 ))
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182 use barter_instrument::{exchange::ExchangeId, test_utils};
183
184 fn indexed_instruments() -> IndexedInstruments {
185 let instruments = vec![
186 test_utils::instrument(ExchangeId::BinanceSpot, "BTC", "ETH"),
187 test_utils::instrument(ExchangeId::Coinbase, "BTC", "ETH"),
188 test_utils::instrument(ExchangeId::Kraken, "USDC", "USDT"),
189 ];
190
191 IndexedInstruments::new(instruments)
192 }
193
194 #[test]
195 fn test_find_exchange_id() {
196 let instruments = indexed_instruments();
197 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
198
199 let exchange_id = kraken.find_exchange_id(kraken.exchange.key).unwrap();
200 assert_eq!(exchange_id, ExchangeId::Kraken);
201 }
202
203 #[test]
204 fn test_find_exchange_index() {
205 let instruments = indexed_instruments();
206 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
207
208 let exchange_index = kraken.find_exchange_index(ExchangeId::Kraken).unwrap();
209 assert_eq!(exchange_index, kraken.exchange.key);
210 }
211
212 #[test]
213 fn test_find_exchange_id_error() {
214 let instruments = indexed_instruments();
215 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
216
217 let binance_index = instruments
219 .exchanges()
220 .iter()
221 .find(|ex| ex.value == ExchangeId::BinanceSpot)
222 .map(|ex| ex.key)
223 .unwrap();
224
225 let result = kraken.find_exchange_id(binance_index);
226 assert!(result.is_err());
227 assert!(matches!(result, Err(KeyError::ExchangeId(_))));
228 }
229
230 #[test]
231 fn test_find_exchange_index_error() {
232 let instruments = indexed_instruments();
233 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
234
235 let result = kraken.find_exchange_index(ExchangeId::BinanceSpot);
236 assert!(result.is_err());
237 assert!(matches!(result, Err(IndexError::ExchangeIndex(_))));
238 }
239
240 #[test]
241 fn test_find_asset_name_exchange() {
242 let instruments = indexed_instruments();
243 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
244
245 let usdt = test_utils::asset("USDT");
246 let usdt_index = instruments
247 .find_asset_index(ExchangeId::Kraken, &usdt.name_internal)
248 .unwrap();
249
250 let usdt_exchange_name = kraken.find_asset_name_exchange(usdt_index).unwrap();
251 assert_eq!(usdt_exchange_name, &usdt.name_exchange);
252 }
253
254 #[test]
255 fn test_find_asset_index() {
256 let instruments = indexed_instruments();
257 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
258
259 let usdc = test_utils::asset("USDC");
260 let asset_index = kraken.find_asset_index(&usdc.name_exchange).unwrap();
261
262 let expected_index = instruments
263 .find_asset_index(ExchangeId::Kraken, &usdc.name_internal)
264 .unwrap();
265 assert_eq!(asset_index, expected_index);
266 }
267
268 #[test]
269 fn test_find_asset_index_error() {
270 let instruments = indexed_instruments();
271 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
272
273 let btc = test_utils::asset("BTC");
274 let result = kraken.find_asset_index(&btc.name_exchange);
275 assert!(result.is_err());
276 assert!(matches!(result, Err(IndexError::AssetIndex(_))));
277 }
278
279 #[test]
280 fn test_find_asset_name_exchange_error() {
281 let instruments = indexed_instruments();
282 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
283
284 let invalid_index = AssetIndex::new(999);
286 let result = kraken.find_asset_name_exchange(invalid_index);
287 assert!(result.is_err());
288 assert!(matches!(result, Err(KeyError::AssetKey(_))));
289 }
290
291 #[test]
292 fn test_find_instrument_name_exchange() {
293 let instruments = indexed_instruments();
294 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
295 let usdc_usdt = test_utils::instrument(ExchangeId::Kraken, "USDC", "USDT");
296
297 let usdc_usdt_index = instruments
298 .find_instrument_index(ExchangeId::Kraken, &usdc_usdt.name_internal)
299 .unwrap();
300
301 let usdc_usdt_exchange_name = kraken
302 .find_instrument_name_exchange(usdc_usdt_index)
303 .unwrap();
304
305 assert_eq!(usdc_usdt_exchange_name, &usdc_usdt.name_exchange);
306 }
307
308 #[test]
309 fn test_find_instrument_index() {
310 let instruments = indexed_instruments();
311 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
312
313 let usdc_usdt = test_utils::instrument(ExchangeId::Kraken, "USDC", "USDT");
314 let instrument_index = kraken
315 .find_instrument_index(&usdc_usdt.name_exchange)
316 .unwrap();
317
318 let expected_index = instruments
319 .find_instrument_index(ExchangeId::Kraken, &usdc_usdt.name_internal)
320 .unwrap();
321 assert_eq!(instrument_index, expected_index);
322 }
323
324 #[test]
325 fn test_find_instrument_index_error() {
326 let instruments = indexed_instruments();
327 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
328
329 let btc_eth = test_utils::instrument(ExchangeId::Kraken, "BTC", "ETH");
330 let result = kraken.find_instrument_index(&btc_eth.name_exchange);
331 assert!(result.is_err());
332 assert!(matches!(result, Err(IndexError::InstrumentIndex(_))));
333 }
334
335 #[test]
336 fn test_find_instrument_name_exchange_error() {
337 let instruments = indexed_instruments();
338 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
339
340 let invalid_index = InstrumentIndex::new(999);
342 let result = kraken.find_instrument_name_exchange(invalid_index);
343 assert!(result.is_err());
344 assert!(matches!(result, Err(KeyError::InstrumentKey(_))));
345 }
346
347 #[test]
348 fn test_exchange_assets_iterator() {
349 let instruments = indexed_instruments();
350 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
351
352 let exchange_assets: Vec<&AssetNameExchange> = kraken.exchange_assets().collect();
353
354 let expected_assets = vec!["USDC", "USDT"];
356 for expected in &expected_assets {
357 assert!(
358 exchange_assets
359 .iter()
360 .any(|asset| asset.as_ref() == *expected)
361 );
362 }
363 }
364
365 #[test]
366 fn test_exchange_instruments_iterator() {
367 let instruments = indexed_instruments();
368 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
369
370 let exchange_instruments: Vec<&InstrumentNameExchange> =
371 kraken.exchange_instruments().collect();
372
373 assert_eq!(exchange_instruments.len(), 1);
375
376 let usdc_usdt = test_utils::instrument(ExchangeId::Kraken, "USDC", "USDT");
378 assert!(
379 exchange_instruments
380 .iter()
381 .any(|instr| *instr == &usdc_usdt.name_exchange)
382 );
383 }
384
385 #[test]
386 fn test_generate_execution_instrument_map_error() {
387 let instruments = indexed_instruments();
388
389 let result = generate_execution_instrument_map(&instruments, ExchangeId::Bitstamp);
391 assert!(result.is_err());
392 assert!(matches!(result, Err(IndexError::ExchangeIndex(_))));
393 }
394}