nautilus_backtest/
execution_client.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16// Under development
17#![allow(dead_code)]
18#![allow(unused_variables)]
19
20//! Provides a `BacktestExecutionClient` implementation for backtesting.
21
22use std::{cell::RefCell, fmt::Debug, rc::Rc};
23
24use nautilus_common::{
25    cache::Cache,
26    clock::Clock,
27    messages::execution::{
28        BatchCancelOrders, CancelAllOrders, CancelOrder, ModifyOrder, QueryOrder, SubmitOrder,
29        SubmitOrderList, TradingCommand,
30    },
31};
32use nautilus_core::UnixNanos;
33use nautilus_execution::client::{ExecutionClient, base::BaseExecutionClient};
34use nautilus_model::{
35    accounts::AccountAny,
36    enums::OmsType,
37    identifiers::{AccountId, ClientId, TraderId, Venue},
38    orders::Order,
39    types::{AccountBalance, MarginBalance},
40};
41
42use crate::exchange::SimulatedExchange;
43
44/// Execution client implementation for backtesting trading operations.
45///
46/// The `BacktestExecutionClient` provides an execution client interface for
47/// backtesting environments, handling order management and trade execution
48/// through simulated exchanges. It processes trading commands and coordinates
49/// with the simulation infrastructure to provide realistic execution behavior.
50pub struct BacktestExecutionClient {
51    base: BaseExecutionClient,
52    exchange: Rc<RefCell<SimulatedExchange>>,
53    clock: Rc<RefCell<dyn Clock>>,
54    is_connected: bool,
55    routing: bool,
56    frozen_account: bool,
57}
58
59impl Debug for BacktestExecutionClient {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        f.debug_struct(stringify!(BacktestExecutionClient))
62            .field("client_id", &self.base.client_id)
63            .field("routing", &self.routing)
64            .finish()
65    }
66}
67
68impl BacktestExecutionClient {
69    #[allow(clippy::too_many_arguments)]
70    pub fn new(
71        trader_id: TraderId,
72        account_id: AccountId,
73        exchange: Rc<RefCell<SimulatedExchange>>,
74        cache: Rc<RefCell<Cache>>,
75        clock: Rc<RefCell<dyn Clock>>,
76        routing: Option<bool>,
77        frozen_account: Option<bool>,
78    ) -> Self {
79        let routing = routing.unwrap_or(false);
80        let frozen_account = frozen_account.unwrap_or(false);
81        let exchange_id = exchange.borrow().id;
82        let base_client = BaseExecutionClient::new(
83            trader_id,
84            ClientId::from(exchange_id.as_str()),
85            Venue::from(exchange_id.as_str()),
86            exchange.borrow().oms_type,
87            account_id,
88            exchange.borrow().account_type,
89            exchange.borrow().base_currency,
90            clock.clone(),
91            cache,
92        );
93
94        if !frozen_account {
95            // TODO Register calculated account
96        }
97
98        Self {
99            exchange,
100            clock,
101            base: base_client,
102            is_connected: false,
103            routing,
104            frozen_account,
105        }
106    }
107}
108
109impl ExecutionClient for BacktestExecutionClient {
110    fn is_connected(&self) -> bool {
111        self.is_connected
112    }
113
114    fn client_id(&self) -> ClientId {
115        self.base.client_id
116    }
117
118    fn account_id(&self) -> AccountId {
119        self.base.account_id
120    }
121
122    fn venue(&self) -> Venue {
123        self.base.venue
124    }
125
126    fn oms_type(&self) -> OmsType {
127        self.base.oms_type
128    }
129
130    fn get_account(&self) -> Option<AccountAny> {
131        self.base.get_account()
132    }
133
134    fn generate_account_state(
135        &self,
136        balances: Vec<AccountBalance>,
137        margins: Vec<MarginBalance>,
138        reported: bool,
139        ts_event: UnixNanos,
140    ) -> anyhow::Result<()> {
141        self.base
142            .generate_account_state(balances, margins, reported, ts_event)
143    }
144
145    fn start(&mut self) -> anyhow::Result<()> {
146        self.is_connected = true;
147        log::info!("Backtest execution client started");
148        Ok(())
149    }
150
151    fn stop(&mut self) -> anyhow::Result<()> {
152        self.is_connected = false;
153        log::info!("Backtest execution client stopped");
154        Ok(())
155    }
156
157    fn submit_order(&self, cmd: &SubmitOrder) -> anyhow::Result<()> {
158        self.base.generate_order_submitted(
159            cmd.strategy_id,
160            cmd.instrument_id,
161            cmd.client_order_id,
162            self.clock.borrow().timestamp_ns(),
163        );
164
165        self.exchange
166            .borrow_mut()
167            .send(TradingCommand::SubmitOrder(cmd.clone())); // TODO: Remove this clone
168        Ok(())
169    }
170
171    fn submit_order_list(&self, cmd: &SubmitOrderList) -> anyhow::Result<()> {
172        for order in &cmd.order_list.orders {
173            self.base.generate_order_submitted(
174                cmd.strategy_id,
175                order.instrument_id(),
176                order.client_order_id(),
177                self.clock.borrow().timestamp_ns(),
178            );
179        }
180
181        self.exchange
182            .borrow_mut()
183            .send(TradingCommand::SubmitOrderList(cmd.clone()));
184        Ok(())
185    }
186
187    fn modify_order(&self, cmd: &ModifyOrder) -> anyhow::Result<()> {
188        self.exchange
189            .borrow_mut()
190            .send(TradingCommand::ModifyOrder(cmd.clone()));
191        Ok(())
192    }
193
194    fn cancel_order(&self, cmd: &CancelOrder) -> anyhow::Result<()> {
195        self.exchange
196            .borrow_mut()
197            .send(TradingCommand::CancelOrder(cmd.clone()));
198        Ok(())
199    }
200
201    fn cancel_all_orders(&self, cmd: &CancelAllOrders) -> anyhow::Result<()> {
202        self.exchange
203            .borrow_mut()
204            .send(TradingCommand::CancelAllOrders(cmd.clone()));
205        Ok(())
206    }
207
208    fn batch_cancel_orders(&self, cmd: &BatchCancelOrders) -> anyhow::Result<()> {
209        self.exchange
210            .borrow_mut()
211            .send(TradingCommand::BatchCancelOrders(cmd.clone()));
212        Ok(())
213    }
214
215    fn query_order(&self, cmd: &QueryOrder) -> anyhow::Result<()> {
216        self.exchange
217            .borrow_mut()
218            .send(TradingCommand::QueryOrder(cmd.clone()));
219        Ok(())
220    }
221}