1#[cfg(feature = "defi")]
17use nautilus_common::defi::cache::IntegrityError;
18
19use super::{
20 Bar, Cache, CustomData, DataEngine, Display, FundingRateUpdate, IndexPriceUpdate, Instrument,
21 InstrumentAny, InstrumentClose, InstrumentStatus, MarkPriceUpdate, MarketStatusAction,
22 OptionGreeks, OrderBookDelta, OrderBookDeltas, OrderBookDepth, QuoteTick, Rc, RecordFlag,
23 RefCell, TradeTick, book, mem, msgbus, switchboard,
24};
25
26impl DataEngine {
27 #[inline]
28 fn pipeline_cache_writes_allowed(&self) -> bool {
29 !self.config.disable_historical_cache
30 }
31
32 pub(crate) fn handle_instrument(&mut self, instrument: &InstrumentAny) {
33 if self.cache_instrument(instrument) {
34 self.publish_instrument(instrument);
35 }
36 }
37
38 pub(crate) fn cache_instrument(&self, instrument: &InstrumentAny) -> bool {
39 log::debug!("Handling instrument: {}", instrument.id());
40
41 if let Err(e) = self
42 .cache
43 .as_ref()
44 .borrow_mut()
45 .add_instrument(instrument.clone())
46 {
47 log_error_on_cache_insert(&e);
48
49 #[cfg(feature = "defi")]
50 if e.is::<IntegrityError>()
51 || nautilus_model::defi::currency::instrument_codes(instrument)
52 .map_or(true, |codes| !codes.is_empty())
53 {
54 return false;
55 }
56 }
57
58 true
59 }
60
61 pub(crate) fn publish_instrument(&mut self, instrument: &InstrumentAny) {
62 let topic = switchboard::get_instrument_topic(instrument.id());
63 log::debug!("Publishing instrument to topic: {topic}");
64 msgbus::publish_instrument(topic, instrument);
65
66 self.update_option_chains(instrument);
67 }
68
69 pub(super) fn handle_delta(&mut self, delta: OrderBookDelta) {
70 let mut deltas = if self.config.buffer_deltas {
71 self.buffer_delta(delta);
72
73 if !RecordFlag::F_LAST.matches(delta.flags) {
74 return; }
76
77 self.buffered_deltas_map
78 .remove(&delta.instrument_id)
79 .expect("buffered deltas exist")
80 } else {
81 self.single_delta_batch(delta)
82 };
83
84 let topic = switchboard::get_book_deltas_topic(deltas.instrument_id);
85 msgbus::publish_deltas(topic, &deltas);
86 self.reclaim_deltas_frame(mem::take(&mut deltas.deltas));
87 }
88
89 pub(super) fn handle_deltas(&mut self, deltas: &OrderBookDeltas) {
90 if self.config.buffer_deltas {
91 let instrument_id = deltas.instrument_id;
92
93 for delta in &deltas.deltas {
94 let is_last = RecordFlag::F_LAST.matches(delta.flags);
95 self.buffer_delta(*delta);
96
97 if is_last {
98 let mut deltas_to_publish = self
99 .buffered_deltas_map
100 .remove(&instrument_id)
101 .expect("buffered deltas exist");
102 let topic = switchboard::get_book_deltas_topic(instrument_id);
103 msgbus::publish_deltas(topic, &deltas_to_publish);
104 self.reclaim_deltas_frame(mem::take(&mut deltas_to_publish.deltas));
105 }
106 }
107 } else {
108 let topic = switchboard::get_book_deltas_topic(deltas.instrument_id);
109 msgbus::publish_deltas(topic, deltas);
110 }
111 }
112
113 pub(super) fn handle_depth(&self, depth: &OrderBookDepth) {
114 let topic = switchboard::get_book_depth_topic(depth.instrument_id);
115 msgbus::publish_depth(topic, depth);
116
117 if self.config.emit_quotes_from_book_depths
118 && let Some(quote) = derive_quote_from_depth(depth)
119 {
120 book::publish_quote_if_changed(&self.cache, quote);
121 }
122 }
123
124 pub(super) fn handle_quote(&self, quote: QuoteTick) {
125 if let Err(e) = self.cache.as_ref().borrow_mut().add_quote(quote) {
126 log_error_on_cache_insert(&e);
127 }
128
129 for synthetic_quote in self.synthetic_quotes_from_quote(quote) {
130 let topic = switchboard::get_quotes_topic(synthetic_quote.instrument_id);
131 msgbus::publish_quote(topic, &synthetic_quote);
132 }
133
134 let topic = switchboard::get_quotes_topic(quote.instrument_id);
135 msgbus::publish_quote(topic, "e);
136 }
137
138 pub(super) fn handle_trade(&self, trade: TradeTick) {
139 if let Err(e) = self.cache.as_ref().borrow_mut().add_trade(trade) {
140 log_error_on_cache_insert(&e);
141 }
142
143 for synthetic_trade in self.synthetic_trades_from_trade(trade) {
144 let topic = switchboard::get_trades_topic(synthetic_trade.instrument_id);
145 msgbus::publish_trade(topic, &synthetic_trade);
146 }
147
148 let topic = switchboard::get_trades_topic(trade.instrument_id);
149 msgbus::publish_trade(topic, &trade);
150 }
151
152 pub(super) fn handle_bar(&self, bar: Bar) {
153 process_engine_bar(&self.cache, self.config.validate_data_sequence, true, bar);
154 }
155
156 pub(super) fn handle_mark_price(&self, mark_price: MarkPriceUpdate) {
157 if let Err(e) = self.cache.as_ref().borrow_mut().add_mark_price(mark_price) {
158 log_error_on_cache_insert(&e);
159 }
160
161 let topic = switchboard::get_mark_price_topic(mark_price.instrument_id);
162 msgbus::publish_mark_price(topic, &mark_price);
163 }
164
165 pub(super) fn handle_index_price(&self, index_price: IndexPriceUpdate) {
166 if let Err(e) = self
167 .cache
168 .as_ref()
169 .borrow_mut()
170 .add_index_price(index_price)
171 {
172 log_error_on_cache_insert(&e);
173 }
174
175 let topic = switchboard::get_index_price_topic(index_price.instrument_id);
176 msgbus::publish_index_price(topic, &index_price);
177 }
178
179 pub fn handle_funding_rate(&mut self, funding_rate: FundingRateUpdate) {
181 if let Err(e) = self
182 .cache
183 .as_ref()
184 .borrow_mut()
185 .add_funding_rate(funding_rate)
186 {
187 log_error_on_cache_insert(&e);
188 }
189
190 let topic = switchboard::get_funding_rate_topic(funding_rate.instrument_id);
191 msgbus::publish_funding_rate(topic, &funding_rate);
192 }
193
194 pub(super) fn handle_instrument_status(&mut self, status: InstrumentStatus) {
195 if let Err(e) = self
196 .cache
197 .as_ref()
198 .borrow_mut()
199 .add_instrument_status(status)
200 {
201 log_error_on_cache_insert(&e);
202 }
203
204 let topic = switchboard::get_instrument_status_topic(status.instrument_id);
205 msgbus::publish_any(topic, &status);
206
207 if self
208 .option_chain_instrument_index
209 .contains_key(&status.instrument_id)
210 && matches!(
211 status.action,
212 MarketStatusAction::Close | MarketStatusAction::NotAvailableForTrading
213 )
214 {
215 self.expire_option_chain_instrument(status.instrument_id);
216 }
217 }
218
219 pub(super) fn handle_instrument_close(&self, close: InstrumentClose) {
220 let topic = switchboard::get_instrument_close_topic(close.instrument_id);
221 msgbus::publish_any(topic, &close);
222 }
223
224 pub(super) fn handle_custom_data(&self, custom: &CustomData) {
225 log::debug!("Processing custom data: {}", custom.data.type_name());
226 let topic = switchboard::get_custom_topic(&custom.data_type);
227 msgbus::publish_any(topic, custom);
228 }
229
230 pub(super) fn handle_delta_pipeline(&mut self, delta: OrderBookDelta) {
231 let mut deltas = self.single_delta_batch(delta);
233 let topic = switchboard::get_pipeline_book_deltas_topic(deltas.instrument_id);
234 msgbus::publish_deltas(topic, &deltas);
235 self.reclaim_deltas_frame(mem::take(&mut deltas.deltas));
236 }
237
238 fn buffer_delta(&mut self, delta: OrderBookDelta) {
239 if let Some(buffered_deltas) = self.buffered_deltas_map.get_mut(&delta.instrument_id) {
240 buffered_deltas.deltas.push(delta);
241 buffered_deltas.flags = delta.flags;
242 buffered_deltas.sequence = delta.sequence;
243 buffered_deltas.ts_event = delta.ts_event;
244 buffered_deltas.ts_init = delta.ts_init;
245 return;
246 }
247
248 let instrument_id = delta.instrument_id;
249 let buffered_deltas = self.single_delta_batch(delta);
250 self.buffered_deltas_map
251 .insert(instrument_id, buffered_deltas);
252 }
253
254 fn single_delta_batch(&mut self, delta: OrderBookDelta) -> OrderBookDeltas {
255 let instrument_id = delta.instrument_id;
256 let mut frame = mem::take(&mut self.deltas_frame);
257 frame.clear();
258 frame.push(delta);
259 OrderBookDeltas::new(instrument_id, frame)
260 }
261
262 fn reclaim_deltas_frame(&mut self, mut frame: Vec<OrderBookDelta>) {
263 frame.clear();
264 self.deltas_frame = frame;
265 }
266
267 pub(super) fn handle_deltas_pipeline(&self, deltas: &OrderBookDeltas) {
268 let topic = switchboard::get_pipeline_book_deltas_topic(deltas.instrument_id);
269 msgbus::publish_deltas(topic, deltas);
270 }
271
272 pub(super) fn handle_depth_pipeline(&self, depth: &OrderBookDepth) {
273 let topic = switchboard::get_pipeline_book_depth_topic(depth.instrument_id);
274 msgbus::publish_depth(topic, depth);
275 }
276
277 pub(super) fn handle_quote_pipeline(&self, quote: QuoteTick) {
278 if self.pipeline_cache_writes_allowed()
279 && let Err(e) = self.cache.as_ref().borrow_mut().add_quote(quote)
280 {
281 log_error_on_cache_insert(&e);
282 }
283
284 let topic = switchboard::get_pipeline_quotes_topic(quote.instrument_id);
285 msgbus::publish_quote(topic, "e);
286 }
287
288 pub(super) fn handle_trade_pipeline(&self, trade: TradeTick) {
289 if self.pipeline_cache_writes_allowed()
290 && let Err(e) = self.cache.as_ref().borrow_mut().add_trade(trade)
291 {
292 log_error_on_cache_insert(&e);
293 }
294
295 let topic = switchboard::get_pipeline_trades_topic(trade.instrument_id);
296 msgbus::publish_trade(topic, &trade);
297 }
298
299 pub(super) fn handle_bar_pipeline(&self, bar: Bar) {
300 if !validate_bar_sequence(&self.cache, self.config.validate_data_sequence, &bar) {
301 return;
302 }
303
304 if self.pipeline_cache_writes_allowed()
305 && let Err(e) = self.cache.as_ref().borrow_mut().add_bar(bar)
306 {
307 log_error_on_cache_insert(&e);
308 }
309
310 let topic = switchboard::get_pipeline_bars_topic(bar.bar_type);
311 msgbus::publish_bar(topic, &bar);
312 }
313
314 pub(super) fn handle_mark_price_pipeline(&self, mark_price: MarkPriceUpdate) {
315 if self.pipeline_cache_writes_allowed()
316 && let Err(e) = self.cache.as_ref().borrow_mut().add_mark_price(mark_price)
317 {
318 log_error_on_cache_insert(&e);
319 }
320
321 let topic = switchboard::get_pipeline_mark_price_topic(mark_price.instrument_id);
322 msgbus::publish_mark_price(topic, &mark_price);
323 }
324
325 pub(super) fn handle_index_price_pipeline(&self, index_price: IndexPriceUpdate) {
326 if self.pipeline_cache_writes_allowed()
327 && let Err(e) = self
328 .cache
329 .as_ref()
330 .borrow_mut()
331 .add_index_price(index_price)
332 {
333 log_error_on_cache_insert(&e);
334 }
335
336 let topic = switchboard::get_pipeline_index_price_topic(index_price.instrument_id);
337 msgbus::publish_index_price(topic, &index_price);
338 }
339
340 pub(super) fn handle_funding_rate_pipeline(&self, funding_rate: FundingRateUpdate) {
341 if self.pipeline_cache_writes_allowed()
342 && let Err(e) = self
343 .cache
344 .as_ref()
345 .borrow_mut()
346 .add_funding_rate(funding_rate)
347 {
348 log_error_on_cache_insert(&e);
349 }
350
351 let topic = switchboard::get_pipeline_funding_rate_topic(funding_rate.instrument_id);
352 msgbus::publish_funding_rate(topic, &funding_rate);
353 }
354
355 pub(super) fn handle_instrument_status_pipeline(&self, status: InstrumentStatus) {
356 if self.pipeline_cache_writes_allowed()
357 && let Err(e) = self
358 .cache
359 .as_ref()
360 .borrow_mut()
361 .add_instrument_status(status)
362 {
363 log_error_on_cache_insert(&e);
364 }
365
366 let topic = switchboard::get_pipeline_instrument_status_topic(status.instrument_id);
367 msgbus::publish_any(topic, &status);
368 }
369
370 pub(super) fn handle_option_greeks_pipeline(&self, greeks: OptionGreeks) {
371 if self.pipeline_cache_writes_allowed() {
372 self.cache.borrow_mut().add_option_greeks(greeks);
373 }
374
375 let topic = switchboard::get_pipeline_option_greeks_topic(greeks.instrument_id);
376 msgbus::publish_option_greeks(topic, &greeks);
377 }
378
379 pub(super) fn handle_instrument_close_pipeline(&self, close: InstrumentClose) {
380 let topic = switchboard::get_pipeline_instrument_close_topic(close.instrument_id);
381 msgbus::publish_any(topic, &close);
382 }
383
384 pub(super) fn handle_custom_data_pipeline(&self, custom: &CustomData) {
385 log::debug!("Pipeline custom data: {}", custom.data.type_name());
386 let topic = switchboard::get_pipeline_custom_topic(&custom.data_type);
387 msgbus::publish_any(topic, custom);
388 }
389
390 pub(super) fn handle_quotes(&self, quotes: &[QuoteTick]) {
391 if let Err(e) = self.cache.as_ref().borrow_mut().add_quotes(quotes) {
392 log_error_on_cache_insert(&e);
393 }
394 }
395
396 pub(super) fn handle_trades(&self, trades: &[TradeTick]) {
397 if let Err(e) = self.cache.as_ref().borrow_mut().add_trades(trades) {
398 log_error_on_cache_insert(&e);
399 }
400 }
401
402 pub(super) fn handle_funding_rates(&self, funding_rates: &[FundingRateUpdate]) {
403 if let Err(e) = self
404 .cache
405 .as_ref()
406 .borrow_mut()
407 .add_funding_rates(funding_rates)
408 {
409 log_error_on_cache_insert(&e);
410 }
411 }
412
413 pub(super) fn handle_bars(&self, bars: &[Bar]) {
414 if let Err(e) = self.cache.as_ref().borrow_mut().add_bars(bars) {
415 log_error_on_cache_insert(&e);
416 }
417 }
418}
419
420#[inline(always)]
421pub(super) fn log_error_on_cache_insert<T: Display>(e: &T) {
422 log::error!("Error on cache insert: {e}");
423}
424
425fn derive_quote_from_depth(depth: &OrderBookDepth) -> Option<QuoteTick> {
428 let bid = depth.bids.first()?;
429 let ask = depth.asks.first()?;
430
431 if bid.side.is_none() || ask.side.is_none() || bid.size.is_zero() || ask.size.is_zero() {
432 return None;
433 }
434
435 Some(QuoteTick::new(
436 depth.instrument_id,
437 bid.price,
438 ask.price,
439 bid.size,
440 ask.size,
441 depth.ts_event,
442 depth.ts_init,
443 ))
444}
445
446pub(super) fn process_engine_bar(
450 cache: &Rc<RefCell<Cache>>,
451 validate_sequence: bool,
452 publish: bool,
453 bar: Bar,
454) {
455 debug_assert!(
456 bar.bar_type.is_standard(),
457 "bars must be published and cached under the standard bar type"
458 );
459
460 if !validate_bar_sequence(cache, validate_sequence, &bar) {
461 return;
462 }
463
464 if let Err(e) = cache.as_ref().borrow_mut().add_bar(bar) {
465 log_error_on_cache_insert(&e);
466 }
467
468 if publish {
469 let topic = switchboard::get_bars_topic(bar.bar_type);
470 msgbus::publish_bar(topic, &bar);
471 }
472}
473
474fn validate_bar_sequence(cache: &Rc<RefCell<Cache>>, validate_sequence: bool, bar: &Bar) -> bool {
475 if !validate_sequence {
476 return true;
477 }
478
479 let Some(last_bar) = cache.as_ref().borrow().bar(&bar.bar_type).copied() else {
480 return true;
481 };
482
483 if bar.ts_event < last_bar.ts_event {
484 log::warn!(
485 "Bar {bar} was prior to last bar `ts_event` {}",
486 last_bar.ts_event,
487 );
488 return false;
489 }
490
491 if bar.ts_init < last_bar.ts_init {
492 log::warn!(
493 "Bar {bar} was prior to last bar `ts_init` {}",
494 last_bar.ts_init,
495 );
496 return false;
497 }
498
499 true
500}