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, QueryAccount, QueryOrder,
29        SubmitOrder, SubmitOrderList, TradingCommand,
30    },
31};
32use nautilus_core::{SharedCell, UnixNanos, WeakCell};
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: WeakCell<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_shared: SharedCell<SimulatedExchange> = SharedCell::from(exchange.clone());
82        let exchange_id = exchange_shared.borrow().id;
83        let base_client = BaseExecutionClient::new(
84            trader_id,
85            ClientId::from(exchange_id.as_str()),
86            Venue::from(exchange_id.as_str()),
87            exchange.borrow().oms_type,
88            account_id,
89            exchange.borrow().account_type,
90            exchange.borrow().base_currency,
91            clock.clone(),
92            cache,
93        );
94
95        if !frozen_account {
96            // TODO Register calculated account
97        }
98
99        Self {
100            exchange: exchange_shared.downgrade(),
101            clock,
102            base: base_client,
103            is_connected: false,
104            routing,
105            frozen_account,
106        }
107    }
108}
109
110impl ExecutionClient for BacktestExecutionClient {
111    fn is_connected(&self) -> bool {
112        self.is_connected
113    }
114
115    fn client_id(&self) -> ClientId {
116        self.base.client_id
117    }
118
119    fn account_id(&self) -> AccountId {
120        self.base.account_id
121    }
122
123    fn venue(&self) -> Venue {
124        self.base.venue
125    }
126
127    fn oms_type(&self) -> OmsType {
128        self.base.oms_type
129    }
130
131    fn get_account(&self) -> Option<AccountAny> {
132        self.base.get_account()
133    }
134
135    fn generate_account_state(
136        &self,
137        balances: Vec<AccountBalance>,
138        margins: Vec<MarginBalance>,
139        reported: bool,
140        ts_event: UnixNanos,
141    ) -> anyhow::Result<()> {
142        self.base
143            .generate_account_state(balances, margins, reported, ts_event)
144    }
145
146    fn start(&mut self) -> anyhow::Result<()> {
147        self.is_connected = true;
148        log::info!("Backtest execution client started");
149        Ok(())
150    }
151
152    fn stop(&mut self) -> anyhow::Result<()> {
153        self.is_connected = false;
154        log::info!("Backtest execution client stopped");
155        Ok(())
156    }
157
158    fn submit_order(&self, cmd: &SubmitOrder) -> anyhow::Result<()> {
159        self.base.generate_order_submitted(
160            cmd.strategy_id,
161            cmd.instrument_id,
162            cmd.client_order_id,
163            self.clock.borrow().timestamp_ns(),
164        );
165
166        if let Some(exchange) = self.exchange.upgrade() {
167            exchange
168                .borrow_mut()
169                .send(TradingCommand::SubmitOrder(cmd.clone()));
170        } else {
171            log::error!("submit_order: SimulatedExchange has been dropped");
172        }
173        Ok(())
174    }
175
176    fn submit_order_list(&self, cmd: &SubmitOrderList) -> anyhow::Result<()> {
177        for order in &cmd.order_list.orders {
178            self.base.generate_order_submitted(
179                cmd.strategy_id,
180                order.instrument_id(),
181                order.client_order_id(),
182                self.clock.borrow().timestamp_ns(),
183            );
184        }
185
186        if let Some(exchange) = self.exchange.upgrade() {
187            exchange
188                .borrow_mut()
189                .send(TradingCommand::SubmitOrderList(cmd.clone()));
190        } else {
191            log::error!("submit_order_list: SimulatedExchange has been dropped");
192        }
193        Ok(())
194    }
195
196    fn modify_order(&self, cmd: &ModifyOrder) -> anyhow::Result<()> {
197        if let Some(exchange) = self.exchange.upgrade() {
198            exchange
199                .borrow_mut()
200                .send(TradingCommand::ModifyOrder(cmd.clone()));
201        } else {
202            log::error!("modify_order: SimulatedExchange has been dropped");
203        }
204        Ok(())
205    }
206
207    fn cancel_order(&self, cmd: &CancelOrder) -> anyhow::Result<()> {
208        if let Some(exchange) = self.exchange.upgrade() {
209            exchange
210                .borrow_mut()
211                .send(TradingCommand::CancelOrder(cmd.clone()));
212        } else {
213            log::error!("cancel_order: SimulatedExchange has been dropped");
214        }
215        Ok(())
216    }
217
218    fn cancel_all_orders(&self, cmd: &CancelAllOrders) -> anyhow::Result<()> {
219        if let Some(exchange) = self.exchange.upgrade() {
220            exchange
221                .borrow_mut()
222                .send(TradingCommand::CancelAllOrders(cmd.clone()));
223        } else {
224            log::error!("cancel_all_orders: SimulatedExchange has been dropped");
225        }
226        Ok(())
227    }
228
229    fn batch_cancel_orders(&self, cmd: &BatchCancelOrders) -> anyhow::Result<()> {
230        if let Some(exchange) = self.exchange.upgrade() {
231            exchange
232                .borrow_mut()
233                .send(TradingCommand::BatchCancelOrders(cmd.clone()));
234        } else {
235            log::error!("batch_cancel_orders: SimulatedExchange has been dropped");
236        }
237        Ok(())
238    }
239
240    fn query_account(&self, cmd: &QueryAccount) -> anyhow::Result<()> {
241        if let Some(exchange) = self.exchange.upgrade() {
242            exchange
243                .borrow_mut()
244                .send(TradingCommand::QueryAccount(cmd.clone()));
245        } else {
246            log::error!("query_account: SimulatedExchange has been dropped");
247        }
248        Ok(())
249    }
250
251    fn query_order(&self, cmd: &QueryOrder) -> anyhow::Result<()> {
252        if let Some(exchange) = self.exchange.upgrade() {
253            exchange
254                .borrow_mut()
255                .send(TradingCommand::QueryOrder(cmd.clone()));
256        } else {
257            log::error!("query_order: SimulatedExchange has been dropped");
258        }
259        Ok(())
260    }
261}