nautilus_common/
providers.rs1use std::collections::HashMap;
22
23use ahash::AHashMap;
24use async_trait::async_trait;
25use nautilus_model::{
26 identifiers::InstrumentId,
27 instruments::{Instrument, InstrumentAny},
28};
29
30#[derive(Debug, Default)]
36pub struct InstrumentStore {
37 instruments: AHashMap<InstrumentId, InstrumentAny>,
38 initialized: bool,
39}
40
41impl InstrumentStore {
42 #[must_use]
44 pub fn new() -> Self {
45 Self::default()
46 }
47
48 pub fn add(&mut self, instrument: InstrumentAny) {
50 self.instruments.insert(instrument.id(), instrument);
51 }
52
53 pub fn add_bulk(&mut self, instruments: Vec<InstrumentAny>) {
55 for instrument in instruments {
56 self.add(instrument);
57 }
58 }
59
60 #[must_use]
62 pub fn find(&self, instrument_id: &InstrumentId) -> Option<&InstrumentAny> {
63 self.instruments.get(instrument_id)
64 }
65
66 #[must_use]
68 pub fn contains(&self, instrument_id: &InstrumentId) -> bool {
69 self.instruments.contains_key(instrument_id)
70 }
71
72 #[must_use]
74 pub fn get_all(&self) -> &AHashMap<InstrumentId, InstrumentAny> {
75 &self.instruments
76 }
77
78 #[must_use]
80 pub fn list_all(&self) -> Vec<&InstrumentAny> {
81 self.instruments.values().collect()
82 }
83
84 #[must_use]
86 pub fn count(&self) -> usize {
87 self.instruments.len()
88 }
89
90 #[must_use]
92 pub fn is_empty(&self) -> bool {
93 self.instruments.is_empty()
94 }
95
96 #[must_use]
98 pub fn is_initialized(&self) -> bool {
99 self.initialized
100 }
101
102 pub fn set_initialized(&mut self) {
104 self.initialized = true;
105 }
106
107 pub fn clear(&mut self) {
109 self.instruments.clear();
110 self.initialized = false;
111 }
112}
113
114#[async_trait(?Send)]
125pub trait InstrumentProvider {
126 fn store(&self) -> &InstrumentStore;
128
129 fn store_mut(&mut self) -> &mut InstrumentStore;
131
132 async fn load_all(&mut self, filters: Option<&HashMap<String, String>>) -> anyhow::Result<()>;
140
141 async fn load_ids(
150 &mut self,
151 instrument_ids: &[InstrumentId],
152 filters: Option<&HashMap<String, String>>,
153 ) -> anyhow::Result<()> {
154 for instrument_id in instrument_ids {
155 self.load(instrument_id, filters).await?;
156 }
157 Ok(())
158 }
159
160 async fn load(
166 &mut self,
167 instrument_id: &InstrumentId,
168 filters: Option<&HashMap<String, String>>,
169 ) -> anyhow::Result<()>;
170}
171
172#[cfg(test)]
173mod tests {
174 use nautilus_model::instruments::{InstrumentAny, stubs::crypto_perpetual_ethusdt};
175 use rstest::rstest;
176
177 use super::*;
178
179 #[rstest]
180 fn test_instrument_store_default_is_empty() {
181 let store = InstrumentStore::new();
182 assert!(store.is_empty());
183 assert_eq!(store.count(), 0);
184 assert!(!store.is_initialized());
185 }
186
187 #[rstest]
188 fn test_instrument_store_add_and_find() {
189 let mut store = InstrumentStore::new();
190 let instrument = InstrumentAny::CryptoPerpetual(crypto_perpetual_ethusdt());
191 let id = instrument.id();
192
193 store.add(instrument);
194
195 assert_eq!(store.count(), 1);
196 assert!(!store.is_empty());
197 assert!(store.contains(&id));
198 assert!(store.find(&id).is_some());
199 }
200
201 #[rstest]
202 fn test_instrument_store_add_bulk() {
203 let mut store = InstrumentStore::new();
204 let instrument = InstrumentAny::CryptoPerpetual(crypto_perpetual_ethusdt());
205 let id = instrument.id();
206
207 store.add_bulk(vec![instrument]);
208
209 assert_eq!(store.count(), 1);
210 assert!(store.contains(&id));
211 }
212
213 #[rstest]
214 fn test_instrument_store_get_all() {
215 let mut store = InstrumentStore::new();
216 let instrument = InstrumentAny::CryptoPerpetual(crypto_perpetual_ethusdt());
217
218 store.add(instrument);
219
220 let all = store.get_all();
221 assert_eq!(all.len(), 1);
222 }
223
224 #[rstest]
225 fn test_instrument_store_list_all() {
226 let mut store = InstrumentStore::new();
227 let instrument = InstrumentAny::CryptoPerpetual(crypto_perpetual_ethusdt());
228
229 store.add(instrument);
230
231 let list = store.list_all();
232 assert_eq!(list.len(), 1);
233 }
234
235 #[rstest]
236 fn test_instrument_store_clear() {
237 let mut store = InstrumentStore::new();
238 let instrument = InstrumentAny::CryptoPerpetual(crypto_perpetual_ethusdt());
239
240 store.add(instrument);
241 store.set_initialized();
242 assert!(store.is_initialized());
243 assert_eq!(store.count(), 1);
244
245 store.clear();
246 assert!(!store.is_initialized());
247 assert!(store.is_empty());
248 }
249
250 #[rstest]
251 fn test_instrument_store_find_missing_returns_none() {
252 let store = InstrumentStore::new();
253 let id = InstrumentId::from("UNKNOWN-UNKNOWN.VENUE");
254 assert!(store.find(&id).is_none());
255 assert!(!store.contains(&id));
256 }
257
258 #[rstest]
259 fn test_instrument_store_add_replaces_existing() {
260 let mut store = InstrumentStore::new();
261 let instrument1 = InstrumentAny::CryptoPerpetual(crypto_perpetual_ethusdt());
262 let instrument2 = InstrumentAny::CryptoPerpetual(crypto_perpetual_ethusdt());
263 let id = instrument1.id();
264
265 store.add(instrument1);
266 store.add(instrument2);
267
268 assert_eq!(store.count(), 1);
269 assert!(store.contains(&id));
270 }
271}