1use std::{cell::RefCell, fmt::Debug, rc::Rc};
19
20use ahash::AHashMap;
21use async_trait::async_trait;
22use nautilus_common::{
23 cache::Cache,
24 clients::ExecutionClient,
25 clock::Clock,
26 factories::OrderEventFactory,
27 messages::execution::{
28 BatchCancelOrders, CancelAllOrders, CancelOrder, GenerateFillReports,
29 GenerateOrderStatusReport, GenerateOrderStatusReports, GeneratePositionStatusReports,
30 ModifyOrder, QueryAccount, QueryOrder, SubmitOrder, SubmitOrderList,
31 },
32 msgbus::{self, MStr, MessagingSwitchboard, Pattern, TypedHandler},
33};
34use nautilus_core::{UnixNanos, WeakCell};
35use nautilus_execution::{
36 client::core::ExecutionClientCore,
37 matching_engine::adapter::OrderEngineAdapter,
38 models::{
39 fee::{FeeModelAny, MakerTakerFeeModel},
40 fill::FillModelAny,
41 },
42};
43use nautilus_model::{
44 accounts::AccountAny,
45 data::{Bar, OrderBookDeltas, QuoteTick, TradeTick},
46 enums::OmsType,
47 identifiers::{AccountId, ClientId, ClientOrderId, InstrumentId, Venue},
48 instruments::{Instrument, InstrumentAny},
49 orders::{Order, OrderAny},
50 reports::{ExecutionMassStatus, FillReport, OrderStatusReport, PositionStatusReport},
51 types::{AccountBalance, MarginBalance, Money},
52};
53
54use crate::config::SandboxExecutionClientConfig;
55
56struct SandboxInner {
60 clock: Rc<RefCell<dyn Clock>>,
62 cache: Rc<RefCell<Cache>>,
64 config: SandboxExecutionClientConfig,
66 matching_engines: AHashMap<InstrumentId, OrderEngineAdapter>,
68 next_engine_raw_id: u32,
70 balances: AHashMap<String, Money>,
72}
73
74impl SandboxInner {
75 fn ensure_matching_engine(&mut self, instrument: &InstrumentAny) {
77 let instrument_id = instrument.id();
78
79 if !self.matching_engines.contains_key(&instrument_id) {
80 let engine_config = self.config.to_matching_engine_config();
81 let fill_model = FillModelAny::default();
82 let fee_model = FeeModelAny::MakerTaker(MakerTakerFeeModel);
83 let raw_id = self.next_engine_raw_id;
84 self.next_engine_raw_id = self.next_engine_raw_id.wrapping_add(1);
85
86 let engine = OrderEngineAdapter::new(
87 instrument.clone(),
88 raw_id,
89 fill_model,
90 fee_model,
91 self.config.book_type,
92 self.config.oms_type,
93 self.config.account_type,
94 self.clock.clone(),
95 self.cache.clone(),
96 engine_config,
97 );
98
99 self.matching_engines.insert(instrument_id, engine);
100 }
101 }
102
103 fn process_quote_tick(&mut self, quote: &QuoteTick) {
105 let instrument_id = quote.instrument_id;
106
107 let instrument = self.cache.borrow().instrument(&instrument_id).cloned();
109 if let Some(instrument) = instrument {
110 self.ensure_matching_engine(&instrument);
111
112 if let Some(engine) = self.matching_engines.get_mut(&instrument_id) {
113 engine.get_engine_mut().process_quote_tick(quote);
114 }
115 }
116 }
117
118 fn process_trade_tick(&mut self, trade: &TradeTick) {
120 if !self.config.trade_execution {
121 return;
122 }
123
124 let instrument_id = trade.instrument_id;
125
126 let instrument = self.cache.borrow().instrument(&instrument_id).cloned();
127 if let Some(instrument) = instrument {
128 self.ensure_matching_engine(&instrument);
129
130 if let Some(engine) = self.matching_engines.get_mut(&instrument_id) {
131 engine.get_engine_mut().process_trade_tick(trade);
132 }
133 }
134 }
135
136 fn process_bar(&mut self, bar: &Bar) {
138 if !self.config.bar_execution {
139 return;
140 }
141
142 let instrument_id = bar.bar_type.instrument_id();
143
144 let instrument = self.cache.borrow().instrument(&instrument_id).cloned();
145 if let Some(instrument) = instrument {
146 self.ensure_matching_engine(&instrument);
147
148 if let Some(engine) = self.matching_engines.get_mut(&instrument_id) {
149 engine.get_engine_mut().process_bar(bar);
150 }
151 }
152 }
153
154 fn process_order_book_deltas(&mut self, deltas: &OrderBookDeltas) {
155 let instrument_id = deltas.instrument_id;
156
157 let instrument = self.cache.borrow().instrument(&instrument_id).cloned();
158 if let Some(instrument) = instrument {
159 self.ensure_matching_engine(&instrument);
160
161 if let Some(engine) = self.matching_engines.get_mut(&instrument_id)
162 && let Err(e) = engine.get_engine_mut().process_order_book_deltas(deltas)
163 {
164 log::error!("Error processing order book deltas: {e}");
165 }
166 }
167 }
168}
169
170struct RegisteredHandlers {
172 deltas_pattern: MStr<Pattern>,
173 deltas_handler: TypedHandler<OrderBookDeltas>,
174 quote_pattern: MStr<Pattern>,
175 quote_handler: TypedHandler<QuoteTick>,
176 trade_pattern: MStr<Pattern>,
177 trade_handler: TypedHandler<TradeTick>,
178 bar_pattern: MStr<Pattern>,
179 bar_handler: TypedHandler<Bar>,
180}
181
182pub struct SandboxExecutionClient {
188 core: RefCell<ExecutionClientCore>,
190 factory: OrderEventFactory,
192 config: SandboxExecutionClientConfig,
194 inner: Rc<RefCell<SandboxInner>>,
196 handlers: RefCell<Option<RegisteredHandlers>>,
198 clock: Rc<RefCell<dyn Clock>>,
200 cache: Rc<RefCell<Cache>>,
202}
203
204impl Debug for SandboxExecutionClient {
205 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206 f.debug_struct(stringify!(SandboxExecutionClient))
207 .field("venue", &self.config.venue)
208 .field("account_id", &self.core.borrow().account_id)
209 .field("connected", &self.core.borrow().is_connected())
210 .field(
211 "matching_engines",
212 &self.inner.borrow().matching_engines.len(),
213 )
214 .finish()
215 }
216}
217
218impl SandboxExecutionClient {
219 #[must_use]
221 pub fn new(
222 core: ExecutionClientCore,
223 config: SandboxExecutionClientConfig,
224 clock: Rc<RefCell<dyn Clock>>,
225 cache: Rc<RefCell<Cache>>,
226 ) -> Self {
227 let mut balances = AHashMap::new();
228 for money in &config.starting_balances {
229 balances.insert(money.currency.code.to_string(), *money);
230 }
231
232 let inner = Rc::new(RefCell::new(SandboxInner {
233 clock: clock.clone(),
234 cache: cache.clone(),
235 config: config.clone(),
236 matching_engines: AHashMap::new(),
237 next_engine_raw_id: 0,
238 balances,
239 }));
240
241 let factory = OrderEventFactory::new(
242 core.trader_id,
243 core.account_id,
244 core.account_type,
245 core.base_currency,
246 );
247
248 Self {
249 core: RefCell::new(core),
250 factory,
251 config,
252 inner,
253 handlers: RefCell::new(None),
254 clock,
255 cache,
256 }
257 }
258
259 #[must_use]
261 pub const fn config(&self) -> &SandboxExecutionClientConfig {
262 &self.config
263 }
264
265 #[must_use]
267 pub fn matching_engine_count(&self) -> usize {
268 self.inner.borrow().matching_engines.len()
269 }
270
271 fn register_message_handlers(&self) {
276 if self.handlers.borrow().is_some() {
277 log::warn!("Sandbox message handlers already registered");
278 return;
279 }
280
281 let inner_weak = WeakCell::from(Rc::downgrade(&self.inner));
282 let venue = self.config.venue;
283
284 let deltas_handler = {
286 let inner = inner_weak.clone();
287 TypedHandler::from(move |deltas: &OrderBookDeltas| {
288 if deltas.instrument_id.venue == venue
289 && let Some(inner_rc) = inner.upgrade()
290 {
291 inner_rc.borrow_mut().process_order_book_deltas(deltas);
292 }
293 })
294 };
295
296 let quote_handler = {
298 let inner = inner_weak.clone();
299 TypedHandler::from(move |quote: &QuoteTick| {
300 if quote.instrument_id.venue == venue
301 && let Some(inner_rc) = inner.upgrade()
302 {
303 inner_rc.borrow_mut().process_quote_tick(quote);
304 }
305 })
306 };
307
308 let trade_handler = {
310 let inner = inner_weak.clone();
311 TypedHandler::from(move |trade: &TradeTick| {
312 if trade.instrument_id.venue == venue
313 && let Some(inner_rc) = inner.upgrade()
314 {
315 inner_rc.borrow_mut().process_trade_tick(trade);
316 }
317 })
318 };
319
320 let bar_handler = {
322 let inner = inner_weak;
323 TypedHandler::from(move |bar: &Bar| {
324 if bar.bar_type.instrument_id().venue == venue
325 && let Some(inner_rc) = inner.upgrade()
326 {
327 inner_rc.borrow_mut().process_bar(bar);
328 }
329 })
330 };
331
332 let deltas_pattern: MStr<Pattern> = format!("data.book.deltas.{venue}.*").into();
334 let quote_pattern: MStr<Pattern> = format!("data.quotes.{venue}.*").into();
335 let trade_pattern: MStr<Pattern> = format!("data.trades.{venue}.*").into();
336 let bar_pattern: MStr<Pattern> = "data.bars.*".into();
337
338 msgbus::subscribe_book_deltas(deltas_pattern, deltas_handler.clone(), Some(10));
339 msgbus::subscribe_quotes(quote_pattern, quote_handler.clone(), Some(10));
340 msgbus::subscribe_trades(trade_pattern, trade_handler.clone(), Some(10));
341 msgbus::subscribe_bars(bar_pattern, bar_handler.clone(), Some(10));
342
343 *self.handlers.borrow_mut() = Some(RegisteredHandlers {
345 deltas_pattern,
346 deltas_handler,
347 quote_pattern,
348 quote_handler,
349 trade_pattern,
350 trade_handler,
351 bar_pattern,
352 bar_handler,
353 });
354
355 log::info!(
356 "Sandbox registered message handlers for venue={}",
357 self.config.venue
358 );
359 }
360
361 fn deregister_message_handlers(&self) {
363 if let Some(handlers) = self.handlers.borrow_mut().take() {
364 msgbus::unsubscribe_book_deltas(handlers.deltas_pattern, &handlers.deltas_handler);
365 msgbus::unsubscribe_quotes(handlers.quote_pattern, &handlers.quote_handler);
366 msgbus::unsubscribe_trades(handlers.trade_pattern, &handlers.trade_handler);
367 msgbus::unsubscribe_bars(handlers.bar_pattern, &handlers.bar_handler);
368
369 log::info!(
370 "Sandbox deregistered message handlers for venue={}",
371 self.config.venue
372 );
373 }
374 }
375
376 fn get_current_account_balances(&self) -> Vec<AccountBalance> {
378 let account_id = self.core.borrow().account_id;
379 let cache = self.cache.borrow();
380
381 if let Some(account) = cache.account(&account_id) {
383 return account.balances().into_values().collect();
384 }
385
386 self.get_account_balances()
388 }
389
390 pub fn process_quote_tick(&self, quote: &QuoteTick) -> anyhow::Result<()> {
396 let instrument_id = quote.instrument_id;
397 let instrument = self
398 .cache
399 .borrow()
400 .instrument(&instrument_id)
401 .cloned()
402 .ok_or_else(|| anyhow::anyhow!("Instrument not found: {instrument_id}"))?;
403
404 let mut inner = self.inner.borrow_mut();
405 inner.ensure_matching_engine(&instrument);
406 if let Some(engine) = inner.matching_engines.get_mut(&instrument_id) {
407 engine.get_engine_mut().process_quote_tick(quote);
408 }
409 Ok(())
410 }
411
412 pub fn process_trade_tick(&self, trade: &TradeTick) -> anyhow::Result<()> {
418 if !self.config.trade_execution {
419 return Ok(());
420 }
421
422 let instrument_id = trade.instrument_id;
423 let instrument = self
424 .cache
425 .borrow()
426 .instrument(&instrument_id)
427 .cloned()
428 .ok_or_else(|| anyhow::anyhow!("Instrument not found: {instrument_id}"))?;
429
430 let mut inner = self.inner.borrow_mut();
431 inner.ensure_matching_engine(&instrument);
432 if let Some(engine) = inner.matching_engines.get_mut(&instrument_id) {
433 engine.get_engine_mut().process_trade_tick(trade);
434 }
435 Ok(())
436 }
437
438 pub fn process_bar(&self, bar: &Bar) -> anyhow::Result<()> {
444 if !self.config.bar_execution {
445 return Ok(());
446 }
447
448 let instrument_id = bar.bar_type.instrument_id();
449 let instrument = self
450 .cache
451 .borrow()
452 .instrument(&instrument_id)
453 .cloned()
454 .ok_or_else(|| anyhow::anyhow!("Instrument not found: {instrument_id}"))?;
455
456 let mut inner = self.inner.borrow_mut();
457 inner.ensure_matching_engine(&instrument);
458 if let Some(engine) = inner.matching_engines.get_mut(&instrument_id) {
459 engine.get_engine_mut().process_bar(bar);
460 }
461 Ok(())
462 }
463
464 pub fn process_order_book_deltas(&self, deltas: &OrderBookDeltas) -> anyhow::Result<()> {
470 let instrument_id = deltas.instrument_id;
471 let instrument = self
472 .cache
473 .borrow()
474 .instrument(&instrument_id)
475 .cloned()
476 .ok_or_else(|| anyhow::anyhow!("Instrument not found: {instrument_id}"))?;
477
478 let mut inner = self.inner.borrow_mut();
479 inner.ensure_matching_engine(&instrument);
480 if let Some(engine) = inner.matching_engines.get_mut(&instrument_id) {
481 engine.get_engine_mut().process_order_book_deltas(deltas)?;
482 }
483 Ok(())
484 }
485
486 pub fn reset(&self) {
488 let mut inner = self.inner.borrow_mut();
489 for engine in inner.matching_engines.values_mut() {
490 engine.get_engine_mut().reset();
491 }
492
493 inner.balances.clear();
494 for money in &self.config.starting_balances {
495 inner
496 .balances
497 .insert(money.currency.code.to_string(), *money);
498 }
499
500 log::info!(
501 "Sandbox execution client reset: venue={}",
502 self.config.venue
503 );
504 }
505
506 fn get_account_balances(&self) -> Vec<AccountBalance> {
508 self.inner
509 .borrow()
510 .balances
511 .values()
512 .map(|money| AccountBalance::new(*money, Money::new(0.0, money.currency), *money))
513 .collect()
514 }
515
516 fn get_order(&self, client_order_id: &ClientOrderId) -> anyhow::Result<OrderAny> {
517 self.cache
518 .borrow()
519 .order(client_order_id)
520 .cloned()
521 .ok_or_else(|| anyhow::anyhow!("Order not found in cache for {client_order_id}"))
522 }
523}
524
525#[async_trait(?Send)]
526impl ExecutionClient for SandboxExecutionClient {
527 fn is_connected(&self) -> bool {
528 self.core.borrow().is_connected()
529 }
530
531 fn client_id(&self) -> ClientId {
532 self.core.borrow().client_id
533 }
534
535 fn account_id(&self) -> AccountId {
536 self.core.borrow().account_id
537 }
538
539 fn venue(&self) -> Venue {
540 self.core.borrow().venue
541 }
542
543 fn oms_type(&self) -> OmsType {
544 self.config.oms_type
545 }
546
547 fn get_account(&self) -> Option<AccountAny> {
548 let account_id = self.core.borrow().account_id;
549 self.cache.borrow().account(&account_id).cloned()
550 }
551
552 fn generate_account_state(
553 &self,
554 balances: Vec<AccountBalance>,
555 margins: Vec<MarginBalance>,
556 reported: bool,
557 ts_event: UnixNanos,
558 ) -> anyhow::Result<()> {
559 let ts_init = self.clock.borrow().timestamp_ns();
560 let state = self
561 .factory
562 .generate_account_state(balances, margins, reported, ts_event, ts_init);
563 let endpoint = MessagingSwitchboard::portfolio_update_account();
564 msgbus::send_account_state(endpoint, &state);
565 Ok(())
566 }
567
568 fn start(&mut self) -> anyhow::Result<()> {
569 if self.core.borrow().is_started() {
570 return Ok(());
571 }
572
573 self.register_message_handlers();
575
576 self.core.borrow().set_started();
577 let core = self.core.borrow();
578 log::info!(
579 "Sandbox execution client started: venue={}, account_id={}, oms_type={:?}, account_type={:?}",
580 self.config.venue,
581 core.account_id,
582 self.config.oms_type,
583 self.config.account_type,
584 );
585 Ok(())
586 }
587
588 fn stop(&mut self) -> anyhow::Result<()> {
589 if self.core.borrow().is_stopped() {
590 return Ok(());
591 }
592
593 self.deregister_message_handlers();
595
596 self.core.borrow().set_stopped();
597 self.core.borrow().set_disconnected();
598 log::info!(
599 "Sandbox execution client stopped: venue={}",
600 self.config.venue
601 );
602 Ok(())
603 }
604
605 async fn connect(&mut self) -> anyhow::Result<()> {
606 if self.core.borrow().is_connected() {
607 return Ok(());
608 }
609
610 let balances = self.get_account_balances();
611 let ts_event = self.clock.borrow().timestamp_ns();
612 self.generate_account_state(balances, vec![], false, ts_event)?;
613
614 self.core.borrow().set_connected();
615 log::info!(
616 "Sandbox execution client connected: venue={}",
617 self.config.venue
618 );
619 Ok(())
620 }
621
622 async fn disconnect(&mut self) -> anyhow::Result<()> {
623 if self.core.borrow().is_disconnected() {
624 return Ok(());
625 }
626
627 self.core.borrow().set_disconnected();
628 log::info!(
629 "Sandbox execution client disconnected: venue={}",
630 self.config.venue
631 );
632 Ok(())
633 }
634
635 fn submit_order(&self, cmd: &SubmitOrder) -> anyhow::Result<()> {
636 let mut order = self.get_order(&cmd.client_order_id)?;
637
638 if order.is_closed() {
639 log::warn!("Cannot submit closed order {}", order.client_order_id());
640 return Ok(());
641 }
642
643 let ts_init = self.clock.borrow().timestamp_ns();
644 let event = self.factory.generate_order_submitted(&order, ts_init);
645 let endpoint = MessagingSwitchboard::exec_engine_process();
646 msgbus::send_order_event(endpoint, event);
647
648 let instrument_id = order.instrument_id();
649 let instrument = self
650 .cache
651 .borrow()
652 .instrument(&instrument_id)
653 .cloned()
654 .ok_or_else(|| anyhow::anyhow!("Instrument not found: {instrument_id}"))?;
655
656 let mut inner = self.inner.borrow_mut();
657 inner.ensure_matching_engine(&instrument);
658
659 let cache = self.cache.borrow();
661
662 if let Some(engine) = inner.matching_engines.get_mut(&instrument_id) {
663 if let Some(quote) = cache.quote(&instrument_id) {
664 engine.get_engine_mut().process_quote_tick(quote);
665 }
666
667 if self.config.trade_execution
668 && let Some(trade) = cache.trade(&instrument_id)
669 {
670 engine.get_engine_mut().process_trade_tick(trade);
671 }
672 }
673 drop(cache);
674
675 let account_id = self.core.borrow().account_id;
676
677 if let Some(engine) = inner.matching_engines.get_mut(&instrument_id) {
678 engine
679 .get_engine_mut()
680 .process_order(&mut order, account_id);
681 }
682
683 Ok(())
684 }
685
686 fn submit_order_list(&self, cmd: &SubmitOrderList) -> anyhow::Result<()> {
687 let ts_init = self.clock.borrow().timestamp_ns();
688 let endpoint = MessagingSwitchboard::exec_engine_process();
689
690 let orders: Vec<OrderAny> = self
691 .cache
692 .borrow()
693 .orders_for_ids(&cmd.order_list.client_order_ids, cmd);
694
695 for order in &orders {
696 if order.is_closed() {
697 log::warn!("Cannot submit closed order {}", order.client_order_id());
698 continue;
699 }
700
701 let event = self.factory.generate_order_submitted(order, ts_init);
702 msgbus::send_order_event(endpoint, event);
703 }
704
705 let account_id = self.core.borrow().account_id;
706 for order in &orders {
707 if order.is_closed() {
708 continue;
709 }
710
711 let instrument_id = order.instrument_id();
712 let instrument = self.cache.borrow().instrument(&instrument_id).cloned();
713
714 if let Some(instrument) = instrument {
715 let mut inner = self.inner.borrow_mut();
716 inner.ensure_matching_engine(&instrument);
717
718 let cache = self.cache.borrow();
720
721 if let Some(engine) = inner.matching_engines.get_mut(&instrument_id) {
722 if let Some(quote) = cache.quote(&instrument_id) {
723 engine.get_engine_mut().process_quote_tick(quote);
724 }
725
726 if self.config.trade_execution
727 && let Some(trade) = cache.trade(&instrument_id)
728 {
729 engine.get_engine_mut().process_trade_tick(trade);
730 }
731 }
732 drop(cache);
733
734 if let Some(engine) = inner.matching_engines.get_mut(&instrument_id) {
735 let mut order_clone = order.clone();
736 engine
737 .get_engine_mut()
738 .process_order(&mut order_clone, account_id);
739 }
740 }
741 }
742
743 Ok(())
744 }
745
746 fn modify_order(&self, cmd: &ModifyOrder) -> anyhow::Result<()> {
747 let instrument_id = cmd.instrument_id;
748 let account_id = self.core.borrow().account_id;
749
750 let mut inner = self.inner.borrow_mut();
751 if let Some(engine) = inner.matching_engines.get_mut(&instrument_id) {
752 engine.get_engine_mut().process_modify(cmd, account_id);
753 }
754 Ok(())
755 }
756
757 fn cancel_order(&self, cmd: &CancelOrder) -> anyhow::Result<()> {
758 let instrument_id = cmd.instrument_id;
759 let account_id = self.core.borrow().account_id;
760
761 let mut inner = self.inner.borrow_mut();
762 if let Some(engine) = inner.matching_engines.get_mut(&instrument_id) {
763 engine.get_engine_mut().process_cancel(cmd, account_id);
764 }
765 Ok(())
766 }
767
768 fn cancel_all_orders(&self, cmd: &CancelAllOrders) -> anyhow::Result<()> {
769 let instrument_id = cmd.instrument_id;
770 let account_id = self.core.borrow().account_id;
771
772 let mut inner = self.inner.borrow_mut();
773 if let Some(engine) = inner.matching_engines.get_mut(&instrument_id) {
774 engine.get_engine_mut().process_cancel_all(cmd, account_id);
775 }
776 Ok(())
777 }
778
779 fn batch_cancel_orders(&self, cmd: &BatchCancelOrders) -> anyhow::Result<()> {
780 let instrument_id = cmd.instrument_id;
781 let account_id = self.core.borrow().account_id;
782
783 let mut inner = self.inner.borrow_mut();
784 if let Some(engine) = inner.matching_engines.get_mut(&instrument_id) {
785 engine
786 .get_engine_mut()
787 .process_batch_cancel(cmd, account_id);
788 }
789 Ok(())
790 }
791
792 fn query_account(&self, _cmd: &QueryAccount) -> anyhow::Result<()> {
793 let balances = self.get_current_account_balances();
794 let ts_event = self.clock.borrow().timestamp_ns();
795 self.generate_account_state(balances, vec![], false, ts_event)?;
796 Ok(())
797 }
798
799 fn query_order(&self, _cmd: &QueryOrder) -> anyhow::Result<()> {
800 Ok(())
802 }
803
804 async fn generate_order_status_report(
805 &self,
806 _cmd: &GenerateOrderStatusReport,
807 ) -> anyhow::Result<Option<OrderStatusReport>> {
808 Ok(None)
810 }
811
812 async fn generate_order_status_reports(
813 &self,
814 _cmd: &GenerateOrderStatusReports,
815 ) -> anyhow::Result<Vec<OrderStatusReport>> {
816 Ok(Vec::new())
818 }
819
820 async fn generate_fill_reports(
821 &self,
822 _cmd: GenerateFillReports,
823 ) -> anyhow::Result<Vec<FillReport>> {
824 Ok(Vec::new())
826 }
827
828 async fn generate_position_status_reports(
829 &self,
830 _cmd: &GeneratePositionStatusReports,
831 ) -> anyhow::Result<Vec<PositionStatusReport>> {
832 Ok(Vec::new())
834 }
835
836 async fn generate_mass_status(
837 &self,
838 _lookback_mins: Option<u64>,
839 ) -> anyhow::Result<Option<ExecutionMassStatus>> {
840 Ok(None)
842 }
843}