1use std::{cell::RefCell, rc::Rc};
19
20#[cfg(feature = "defi")]
21use nautilus_common::messages::defi::{
22 RequestPoolSnapshot, SubscribeBlocks, SubscribePool, SubscribePoolFeeCollects,
23 SubscribePoolFlashEvents, SubscribePoolLiquidityUpdates, SubscribePoolSwaps, UnsubscribeBlocks,
24 UnsubscribePool, UnsubscribePoolFeeCollects, UnsubscribePoolFlashEvents,
25 UnsubscribePoolLiquidityUpdates, UnsubscribePoolSwaps,
26};
27use nautilus_common::{
28 cache::Cache,
29 clients::DataClient,
30 messages::data::{
31 RequestOptionChainReferencePrice, SubscribeBars, SubscribeBookDeltas, SubscribeBookDepth,
32 SubscribeCustomData, SubscribeIndexPrices, SubscribeInstrument, SubscribeInstrumentClose,
33 SubscribeInstrumentStatus, SubscribeInstruments, SubscribeMarkPrices, SubscribeQuotes,
34 SubscribeTrades, UnsubscribeBars, UnsubscribeBookDeltas, UnsubscribeBookDepth,
35 UnsubscribeCustomData, UnsubscribeIndexPrices, UnsubscribeInstrument,
36 UnsubscribeInstrumentClose, UnsubscribeInstrumentStatus, UnsubscribeInstruments,
37 UnsubscribeMarkPrices, UnsubscribeQuotes, UnsubscribeTrades,
38 },
39};
40use nautilus_model::identifiers::{ClientId, Venue};
41
42#[derive(Debug)]
49pub struct BacktestDataClient {
50 pub client_id: ClientId,
51 pub venue: Venue,
52 _cache: Rc<RefCell<Cache>>,
53}
54
55impl BacktestDataClient {
56 #[must_use]
58 pub const fn new(client_id: ClientId, venue: Venue, cache: Rc<RefCell<Cache>>) -> Self {
59 Self {
60 client_id,
61 venue,
62 _cache: cache,
63 }
64 }
65}
66
67#[async_trait::async_trait(?Send)]
68impl DataClient for BacktestDataClient {
69 fn client_id(&self) -> ClientId {
70 self.client_id
71 }
72
73 fn venue(&self) -> Option<Venue> {
74 Some(self.venue)
75 }
76
77 fn start(&mut self) -> anyhow::Result<()> {
78 Ok(())
79 }
80
81 fn stop(&mut self) -> anyhow::Result<()> {
82 Ok(())
83 }
84
85 fn reset(&mut self) -> anyhow::Result<()> {
86 Ok(())
87 }
88
89 fn dispose(&mut self) -> anyhow::Result<()> {
90 Ok(())
91 }
92
93 fn is_connected(&self) -> bool {
94 true
95 }
96
97 fn is_disconnected(&self) -> bool {
98 false
99 }
100
101 fn subscribe(&mut self, _cmd: SubscribeCustomData) -> anyhow::Result<()> {
102 Ok(())
103 }
104
105 fn subscribe_instruments(&mut self, _cmd: SubscribeInstruments) -> anyhow::Result<()> {
106 Ok(())
107 }
108
109 fn subscribe_instrument(&mut self, _cmd: SubscribeInstrument) -> anyhow::Result<()> {
110 Ok(())
111 }
112
113 fn subscribe_book_deltas(&mut self, _cmd: SubscribeBookDeltas) -> anyhow::Result<()> {
114 Ok(())
115 }
116
117 fn subscribe_book_depth(&mut self, _cmd: SubscribeBookDepth) -> anyhow::Result<()> {
118 Ok(())
119 }
120
121 fn subscribe_quotes(&mut self, _cmd: SubscribeQuotes) -> anyhow::Result<()> {
122 Ok(())
123 }
124
125 fn subscribe_trades(&mut self, _cmd: SubscribeTrades) -> anyhow::Result<()> {
126 Ok(())
127 }
128
129 fn subscribe_bars(&mut self, _cmd: SubscribeBars) -> anyhow::Result<()> {
130 Ok(())
131 }
132
133 fn subscribe_mark_prices(&mut self, _cmd: SubscribeMarkPrices) -> anyhow::Result<()> {
134 Ok(())
135 }
136
137 fn subscribe_index_prices(&mut self, _cmd: SubscribeIndexPrices) -> anyhow::Result<()> {
138 Ok(())
139 }
140
141 fn subscribe_instrument_status(
142 &mut self,
143 _cmd: SubscribeInstrumentStatus,
144 ) -> anyhow::Result<()> {
145 Ok(())
146 }
147
148 fn subscribe_instrument_close(&mut self, _cmd: SubscribeInstrumentClose) -> anyhow::Result<()> {
149 Ok(())
150 }
151
152 #[cfg(feature = "defi")]
155 fn subscribe_blocks(&mut self, _cmd: SubscribeBlocks) -> anyhow::Result<()> {
156 Ok(())
157 }
158
159 #[cfg(feature = "defi")]
160 fn subscribe_pool(&mut self, _cmd: SubscribePool) -> anyhow::Result<()> {
161 Ok(())
162 }
163
164 #[cfg(feature = "defi")]
165 fn subscribe_pool_swaps(&mut self, _cmd: SubscribePoolSwaps) -> anyhow::Result<()> {
166 Ok(())
167 }
168
169 #[cfg(feature = "defi")]
170 fn subscribe_pool_liquidity_updates(
171 &mut self,
172 _cmd: SubscribePoolLiquidityUpdates,
173 ) -> anyhow::Result<()> {
174 Ok(())
175 }
176
177 #[cfg(feature = "defi")]
178 fn subscribe_pool_fee_collects(
179 &mut self,
180 _cmd: SubscribePoolFeeCollects,
181 ) -> anyhow::Result<()> {
182 Ok(())
183 }
184
185 #[cfg(feature = "defi")]
186 fn subscribe_pool_flash_events(
187 &mut self,
188 _cmd: SubscribePoolFlashEvents,
189 ) -> anyhow::Result<()> {
190 Ok(())
191 }
192
193 fn unsubscribe(&mut self, _cmd: &UnsubscribeCustomData) -> anyhow::Result<()> {
194 Ok(())
195 }
196
197 fn unsubscribe_instruments(&mut self, _cmd: &UnsubscribeInstruments) -> anyhow::Result<()> {
198 Ok(())
199 }
200
201 fn unsubscribe_instrument(&mut self, _cmd: &UnsubscribeInstrument) -> anyhow::Result<()> {
202 Ok(())
203 }
204
205 fn unsubscribe_book_deltas(&mut self, _cmd: &UnsubscribeBookDeltas) -> anyhow::Result<()> {
206 Ok(())
207 }
208
209 fn unsubscribe_book_depth(&mut self, _cmd: &UnsubscribeBookDepth) -> anyhow::Result<()> {
210 Ok(())
211 }
212
213 fn unsubscribe_quotes(&mut self, _cmd: &UnsubscribeQuotes) -> anyhow::Result<()> {
214 Ok(())
215 }
216
217 fn unsubscribe_trades(&mut self, _cmd: &UnsubscribeTrades) -> anyhow::Result<()> {
218 Ok(())
219 }
220
221 fn unsubscribe_bars(&mut self, _cmd: &UnsubscribeBars) -> anyhow::Result<()> {
222 Ok(())
223 }
224
225 fn unsubscribe_mark_prices(&mut self, _cmd: &UnsubscribeMarkPrices) -> anyhow::Result<()> {
226 Ok(())
227 }
228
229 fn unsubscribe_index_prices(&mut self, _cmd: &UnsubscribeIndexPrices) -> anyhow::Result<()> {
230 Ok(())
231 }
232
233 fn unsubscribe_instrument_status(
234 &mut self,
235 _cmd: &UnsubscribeInstrumentStatus,
236 ) -> anyhow::Result<()> {
237 Ok(())
238 }
239
240 fn unsubscribe_instrument_close(
241 &mut self,
242 _cmd: &UnsubscribeInstrumentClose,
243 ) -> anyhow::Result<()> {
244 Ok(())
245 }
246
247 #[cfg(feature = "defi")]
248 fn unsubscribe_blocks(&mut self, _cmd: &UnsubscribeBlocks) -> anyhow::Result<()> {
249 Ok(())
250 }
251
252 #[cfg(feature = "defi")]
253 fn unsubscribe_pool(&mut self, _cmd: &UnsubscribePool) -> anyhow::Result<()> {
254 Ok(())
255 }
256
257 #[cfg(feature = "defi")]
258 fn unsubscribe_pool_swaps(&mut self, _cmd: &UnsubscribePoolSwaps) -> anyhow::Result<()> {
259 Ok(())
260 }
261
262 #[cfg(feature = "defi")]
263 fn unsubscribe_pool_liquidity_updates(
264 &mut self,
265 _cmd: &UnsubscribePoolLiquidityUpdates,
266 ) -> anyhow::Result<()> {
267 Ok(())
268 }
269
270 #[cfg(feature = "defi")]
271 fn unsubscribe_pool_fee_collects(
272 &mut self,
273 _cmd: &UnsubscribePoolFeeCollects,
274 ) -> anyhow::Result<()> {
275 Ok(())
276 }
277
278 #[cfg(feature = "defi")]
279 fn unsubscribe_pool_flash_events(
280 &mut self,
281 _cmd: &UnsubscribePoolFlashEvents,
282 ) -> anyhow::Result<()> {
283 Ok(())
284 }
285
286 fn request_option_chain_reference_price(
287 &self,
288 _request: RequestOptionChainReferencePrice,
289 ) -> anyhow::Result<()> {
290 anyhow::bail!("backtest data client cannot fetch option-chain reference prices")
291 }
292
293 #[cfg(feature = "defi")]
297 fn request_pool_snapshot(&self, _request: RequestPoolSnapshot) -> anyhow::Result<()> {
298 Ok(())
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use std::sync::{Mutex, MutexGuard};
305
306 use log::{Level, LevelFilter, Log, Metadata, Record};
307 use nautilus_common::messages::data::RequestInstruments;
308 use nautilus_core::{UUID4, UnixNanos};
309 use nautilus_model::identifiers::{InstrumentId, OptionSeriesId};
310 use rstest::rstest;
311 use ustr::Ustr;
312
313 use super::*;
314
315 struct RequestWarnCapture {
316 messages: Mutex<Vec<String>>,
317 }
318
319 impl RequestWarnCapture {
320 fn clear(&self) {
321 self.messages.lock().unwrap().clear();
322 }
323
324 fn messages(&self) -> Vec<String> {
325 self.messages.lock().unwrap().clone()
326 }
327 }
328
329 impl Log for RequestWarnCapture {
330 fn enabled(&self, metadata: &Metadata<'_>) -> bool {
331 metadata.level() == Level::Warn
332 }
333
334 fn log(&self, record: &Record<'_>) {
335 if self.enabled(record.metadata()) {
336 self.messages
337 .lock()
338 .unwrap()
339 .push(record.args().to_string());
340 }
341 }
342
343 fn flush(&self) {}
344 }
345
346 static REQUEST_WARN_CAPTURE: RequestWarnCapture = RequestWarnCapture {
347 messages: Mutex::new(Vec::new()),
348 };
349 static REQUEST_WARN_TEST_LOCK: Mutex<()> = Mutex::new(());
350
351 fn start_request_warn_capture() -> MutexGuard<'static, ()> {
352 let guard = REQUEST_WARN_TEST_LOCK.lock().unwrap();
353 let _ = log::set_logger(&REQUEST_WARN_CAPTURE);
354 log::set_max_level(LevelFilter::Warn);
355 REQUEST_WARN_CAPTURE.clear();
356 guard
357 }
358
359 #[rstest]
360 fn test_request_instruments_logs_not_implemented_warning() {
361 let _guard = start_request_warn_capture();
362
363 let client_id = ClientId::new("BACKTEST");
364 let venue = Venue::new("BACKTEST");
365 let cache = Rc::new(RefCell::new(Cache::default()));
366 let client = BacktestDataClient::new(client_id, venue, cache);
367
368 let request = RequestInstruments::new(
369 None,
370 None,
371 Some(client_id),
372 Some(venue),
373 UUID4::new(),
374 UnixNanos::default(),
375 None,
376 );
377
378 let result = client.request_instruments(request);
379
380 assert!(result.is_ok());
381 assert!(
382 REQUEST_WARN_CAPTURE
383 .messages()
384 .iter()
385 .any(|message| message.contains("RequestInstruments")
386 && message.contains("handler not implemented")),
387 );
388 }
389 #[rstest]
390 fn test_option_chain_reference_price_is_unsupported() {
391 let client_id = ClientId::new("BACKTEST");
392 let venue = Venue::new("BACKTEST");
393 let cache = Rc::new(RefCell::new(Cache::default()));
394 let client = BacktestDataClient::new(client_id, venue, cache);
395 let series_id = OptionSeriesId::new(
396 venue,
397 Ustr::from("BTC"),
398 Ustr::from("BTC"),
399 UnixNanos::default(),
400 );
401
402 let request = RequestOptionChainReferencePrice::new(
403 series_id,
404 InstrumentId::from("BTC-TEST-50000-C.BACKTEST"),
405 Some(client_id),
406 UUID4::new(),
407 UnixNanos::default(),
408 None,
409 );
410
411 let result = client.request_option_chain_reference_price(request);
412 assert!(result.is_err());
413 let msg = result.unwrap_err().to_string();
414 assert!(msg.contains("backtest data client"));
415 }
416}