1use crate::error::KeyError;
2use fnv::FnvHashMap;
3use rustrade_instrument::{
4 Keyed,
5 asset::{Asset, AssetIndex, ExchangeAsset, name::AssetNameExchange},
6 exchange::{ExchangeId, ExchangeIndex},
7 index::{IndexedInstruments, error::IndexError},
8 instrument::{Instrument, InstrumentIndex, name::InstrumentNameExchange},
9};
10use rustrade_integration::collection::FnvIndexSet;
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)]
180#[allow(clippy::unwrap_used)] mod tests {
182 use super::*;
183 use rustrade_instrument::{exchange::ExchangeId, test_utils};
184
185 fn indexed_instruments() -> IndexedInstruments {
186 let instruments = vec![
187 test_utils::instrument(ExchangeId::BinanceSpot, "BTC", "ETH"),
188 test_utils::instrument(ExchangeId::Coinbase, "BTC", "ETH"),
189 test_utils::instrument(ExchangeId::Kraken, "USDC", "USDT"),
190 ];
191
192 IndexedInstruments::new(instruments)
193 }
194
195 #[test]
196 fn test_find_exchange_id() {
197 let instruments = indexed_instruments();
198 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
199
200 let exchange_id = kraken.find_exchange_id(kraken.exchange.key).unwrap();
201 assert_eq!(exchange_id, ExchangeId::Kraken);
202 }
203
204 #[test]
205 fn test_find_exchange_index() {
206 let instruments = indexed_instruments();
207 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
208
209 let exchange_index = kraken.find_exchange_index(ExchangeId::Kraken).unwrap();
210 assert_eq!(exchange_index, kraken.exchange.key);
211 }
212
213 #[test]
214 fn test_find_exchange_id_error() {
215 let instruments = indexed_instruments();
216 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
217
218 let binance_index = instruments
220 .exchanges()
221 .iter()
222 .find(|ex| ex.value == ExchangeId::BinanceSpot)
223 .map(|ex| ex.key)
224 .unwrap();
225
226 let result = kraken.find_exchange_id(binance_index);
227 assert!(result.is_err());
228 assert!(matches!(result, Err(KeyError::ExchangeId(_))));
229 }
230
231 #[test]
232 fn test_find_exchange_index_error() {
233 let instruments = indexed_instruments();
234 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
235
236 let result = kraken.find_exchange_index(ExchangeId::BinanceSpot);
237 assert!(result.is_err());
238 assert!(matches!(result, Err(IndexError::ExchangeIndex(_))));
239 }
240
241 #[test]
242 fn test_find_asset_name_exchange() {
243 let instruments = indexed_instruments();
244 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
245
246 let usdt = test_utils::asset("USDT");
247 let usdt_index = instruments
248 .find_asset_index(ExchangeId::Kraken, &usdt.name_internal)
249 .unwrap();
250
251 let usdt_exchange_name = kraken.find_asset_name_exchange(usdt_index).unwrap();
252 assert_eq!(usdt_exchange_name, &usdt.name_exchange);
253 }
254
255 #[test]
256 fn test_find_asset_index() {
257 let instruments = indexed_instruments();
258 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
259
260 let usdc = test_utils::asset("USDC");
261 let asset_index = kraken.find_asset_index(&usdc.name_exchange).unwrap();
262
263 let expected_index = instruments
264 .find_asset_index(ExchangeId::Kraken, &usdc.name_internal)
265 .unwrap();
266 assert_eq!(asset_index, expected_index);
267 }
268
269 #[test]
270 fn test_find_asset_index_error() {
271 let instruments = indexed_instruments();
272 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
273
274 let btc = test_utils::asset("BTC");
275 let result = kraken.find_asset_index(&btc.name_exchange);
276 assert!(result.is_err());
277 assert!(matches!(result, Err(IndexError::AssetIndex(_))));
278 }
279
280 #[test]
281 fn test_find_asset_name_exchange_error() {
282 let instruments = indexed_instruments();
283 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
284
285 let invalid_index = AssetIndex::new(999);
287 let result = kraken.find_asset_name_exchange(invalid_index);
288 assert!(result.is_err());
289 assert!(matches!(result, Err(KeyError::AssetKey(_))));
290 }
291
292 #[test]
293 fn test_find_instrument_name_exchange() {
294 let instruments = indexed_instruments();
295 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
296 let usdc_usdt = test_utils::instrument(ExchangeId::Kraken, "USDC", "USDT");
297
298 let usdc_usdt_index = instruments
299 .find_instrument_index(ExchangeId::Kraken, &usdc_usdt.name_internal)
300 .unwrap();
301
302 let usdc_usdt_exchange_name = kraken
303 .find_instrument_name_exchange(usdc_usdt_index)
304 .unwrap();
305
306 assert_eq!(usdc_usdt_exchange_name, &usdc_usdt.name_exchange);
307 }
308
309 #[test]
310 fn test_find_instrument_index() {
311 let instruments = indexed_instruments();
312 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
313
314 let usdc_usdt = test_utils::instrument(ExchangeId::Kraken, "USDC", "USDT");
315 let instrument_index = kraken
316 .find_instrument_index(&usdc_usdt.name_exchange)
317 .unwrap();
318
319 let expected_index = instruments
320 .find_instrument_index(ExchangeId::Kraken, &usdc_usdt.name_internal)
321 .unwrap();
322 assert_eq!(instrument_index, expected_index);
323 }
324
325 #[test]
326 fn test_find_instrument_index_error() {
327 let instruments = indexed_instruments();
328 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
329
330 let btc_eth = test_utils::instrument(ExchangeId::Kraken, "BTC", "ETH");
331 let result = kraken.find_instrument_index(&btc_eth.name_exchange);
332 assert!(result.is_err());
333 assert!(matches!(result, Err(IndexError::InstrumentIndex(_))));
334 }
335
336 #[test]
337 fn test_find_instrument_name_exchange_error() {
338 let instruments = indexed_instruments();
339 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
340
341 let invalid_index = InstrumentIndex::new(999);
343 let result = kraken.find_instrument_name_exchange(invalid_index);
344 assert!(result.is_err());
345 assert!(matches!(result, Err(KeyError::InstrumentKey(_))));
346 }
347
348 #[test]
349 fn test_exchange_assets_iterator() {
350 let instruments = indexed_instruments();
351 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
352
353 let exchange_assets: Vec<&AssetNameExchange> = kraken.exchange_assets().collect();
354
355 let expected_assets = vec!["USDC", "USDT"];
357 for expected in &expected_assets {
358 assert!(
359 exchange_assets
360 .iter()
361 .any(|asset| asset.as_ref() == *expected)
362 );
363 }
364 }
365
366 #[test]
367 fn test_exchange_instruments_iterator() {
368 let instruments = indexed_instruments();
369 let kraken = generate_execution_instrument_map(&instruments, ExchangeId::Kraken).unwrap();
370
371 let exchange_instruments: Vec<&InstrumentNameExchange> =
372 kraken.exchange_instruments().collect();
373
374 assert_eq!(exchange_instruments.len(), 1);
376
377 let usdc_usdt = test_utils::instrument(ExchangeId::Kraken, "USDC", "USDT");
379 assert!(exchange_instruments.contains(&&usdc_usdt.name_exchange));
380 }
381
382 #[test]
383 fn test_generate_execution_instrument_map_error() {
384 let instruments = indexed_instruments();
385
386 let result = generate_execution_instrument_map(&instruments, ExchangeId::Bitstamp);
388 assert!(result.is_err());
389 assert!(matches!(result, Err(IndexError::ExchangeIndex(_))));
390 }
391}