Skip to main content

barter_execution/
map.rs

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/// Indexed instrument map used to associate the internal Barter representation of instruments and
13/// assets with the [`ExecutionClient`](super::client::ExecutionClient) representation.
14///
15/// Similarly, when the execution manager received an [`AccountEvent`](super::AccountEvent)
16/// from the execution API, it needs to determine the internal representation of the associated
17/// assets and instruments.
18///
19/// eg/ `InstrumentNameExchange("XBT-USDT")` <--> `InstrumentIndex(1)` <br>
20/// eg/ `AssetNameExchange("XBT")` <--> `AssetIndex(1)`
21#[derive(Debug, Clone, Eq, PartialEq)]
22pub struct ExecutionInstrumentMap {
23    /// The exchange associated with this execution map.
24    pub exchange: Keyed<ExchangeIndex, ExchangeId>,
25    /// Collection of assets available by the engine with their
26    /// exchange-specific representations. This holds all indexed assets.
27    pub assets: FnvIndexSet<ExchangeAsset<Asset>>,
28    /// Collection of instruments available by the engine. This holds all
29    /// indexed instruments.
30    pub instruments: FnvIndexSet<Instrument<Keyed<ExchangeIndex, ExchangeId>, AssetIndex>>,
31    /// Map from exchange-specific asset names to internal asset indices for
32    /// fast lookups.
33    pub asset_names: FnvHashMap<AssetNameExchange, AssetIndex>,
34    /// Map from exchange-specific instrument names to internal instrument
35    /// indices for fast lookups.
36    pub instrument_names: FnvHashMap<InstrumentNameExchange, InstrumentIndex>,
37}
38
39impl ExecutionInstrumentMap {
40    /// Construct a new [`Self`] using the provided indexed assets and instruments.
41    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        // Create a different exchange index that doesn't match
218        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        // Try to find asset with invalid index
285        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        // Try to find instrument with invalid index
341        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        // Verify that the iterator returns the expected assets for Kraken
355        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        // Should have exactly one instrument for Kraken
374        assert_eq!(exchange_instruments.len(), 1);
375
376        // Verify it contains the USDC-USDT instrument
377        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        // Try to generate map for exchange not in indexed instruments
390        let result = generate_execution_instrument_map(&instruments, ExchangeId::Bitstamp);
391        assert!(result.is_err());
392        assert!(matches!(result, Err(IndexError::ExchangeIndex(_))));
393    }
394}