Skip to main content

nautilus_execution/client/
core.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 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//! Base execution client functionality.
17
18use std::{
19    cell::RefCell,
20    rc::Rc,
21    sync::atomic::{AtomicBool, Ordering},
22};
23
24use nautilus_common::cache::Cache;
25use nautilus_model::{
26    enums::{AccountType, OmsType},
27    identifiers::{AccountId, ClientId, ClientOrderId, TraderId, Venue},
28    orders::{OrderAny, OrderList},
29    types::Currency,
30};
31
32/// Base implementation for execution clients providing identity and connection state.
33///
34/// This struct provides the foundation for all execution clients, holding
35/// client identity, connection state, and read-only cache access. Execution
36/// clients use this as a base and extend it with venue-specific implementations.
37///
38/// For event generation, use `OrderEventFactory` from `nautilus_common::factories`.
39/// For live adapters, use `ExecutionEventEmitter` which combines event generation
40/// with async dispatch. For backtest/sandbox, use `OrderEventFactory` directly
41/// and dispatch via `msgbus::send_order_event()`.
42#[derive(Debug)]
43pub struct ExecutionClientCore {
44    pub trader_id: TraderId,
45    pub client_id: ClientId,
46    pub venue: Venue,
47    pub oms_type: OmsType,
48    pub account_id: AccountId,
49    pub account_type: AccountType,
50    pub base_currency: Option<Currency>,
51    connected: AtomicBool,
52    started: AtomicBool,
53    instruments_initialized: AtomicBool,
54    cache: Rc<RefCell<Cache>>,
55}
56
57impl Clone for ExecutionClientCore {
58    fn clone(&self) -> Self {
59        Self {
60            trader_id: self.trader_id,
61            client_id: self.client_id,
62            venue: self.venue,
63            oms_type: self.oms_type,
64            account_id: self.account_id,
65            account_type: self.account_type,
66            base_currency: self.base_currency,
67            connected: AtomicBool::new(self.connected.load(Ordering::Acquire)),
68            started: AtomicBool::new(self.started.load(Ordering::Acquire)),
69            instruments_initialized: AtomicBool::new(
70                self.instruments_initialized.load(Ordering::Acquire),
71            ),
72            cache: self.cache.clone(),
73        }
74    }
75}
76
77impl ExecutionClientCore {
78    /// Creates a new [`ExecutionClientCore`] instance.
79    #[expect(clippy::too_many_arguments)]
80    #[must_use]
81    pub fn new(
82        trader_id: TraderId,
83        client_id: ClientId,
84        venue: Venue,
85        oms_type: OmsType,
86        account_id: AccountId,
87        account_type: AccountType,
88        base_currency: Option<Currency>,
89        cache: Rc<RefCell<Cache>>,
90    ) -> Self {
91        Self {
92            trader_id,
93            client_id,
94            venue,
95            oms_type,
96            account_id,
97            account_type,
98            base_currency,
99            connected: AtomicBool::new(false),
100            started: AtomicBool::new(false),
101            instruments_initialized: AtomicBool::new(false),
102            cache,
103        }
104    }
105
106    /// Returns a read-only borrow of the cache.
107    pub fn cache(&self) -> std::cell::Ref<'_, Cache> {
108        self.cache.borrow()
109    }
110
111    /// Returns a mutable borrow of the cache.
112    pub fn cache_mut(&self) -> std::cell::RefMut<'_, Cache> {
113        self.cache.borrow_mut()
114    }
115
116    /// Returns the order for the given `client_order_id` from the cache.
117    ///
118    /// # Errors
119    ///
120    /// Returns an error if the order is not found in the cache.
121    pub fn get_order(&self, client_order_id: &ClientOrderId) -> anyhow::Result<OrderAny> {
122        self.cache
123            .borrow()
124            .order(client_order_id)
125            .cloned()
126            .ok_or_else(|| anyhow::anyhow!("Order not found in cache: {client_order_id}"))
127    }
128
129    /// Returns all orders for the given order list from the cache.
130    ///
131    /// # Errors
132    ///
133    /// Returns an error if any order is not found in the cache.
134    pub fn get_orders_for_list(&self, order_list: &OrderList) -> anyhow::Result<Vec<OrderAny>> {
135        order_list
136            .client_order_ids
137            .iter()
138            .map(|id| self.get_order(id))
139            .collect()
140    }
141
142    /// Returns `true` if the client is connected.
143    #[must_use]
144    pub fn is_connected(&self) -> bool {
145        self.connected.load(Ordering::Acquire)
146    }
147
148    /// Returns `true` if the client is disconnected.
149    #[must_use]
150    pub fn is_disconnected(&self) -> bool {
151        !self.is_connected()
152    }
153
154    /// Sets the client as connected.
155    pub fn set_connected(&self) {
156        self.connected.store(true, Ordering::Release);
157    }
158
159    /// Sets the client as disconnected.
160    pub fn set_disconnected(&self) {
161        self.connected.store(false, Ordering::Release);
162    }
163
164    /// Returns `true` if the client has been started.
165    #[must_use]
166    pub fn is_started(&self) -> bool {
167        self.started.load(Ordering::Acquire)
168    }
169
170    /// Returns `true` if the client has not been started.
171    #[must_use]
172    pub fn is_stopped(&self) -> bool {
173        !self.is_started()
174    }
175
176    /// Sets the client as started.
177    pub fn set_started(&self) {
178        self.started.store(true, Ordering::Release);
179    }
180
181    /// Sets the client as stopped.
182    pub fn set_stopped(&self) {
183        self.started.store(false, Ordering::Release);
184    }
185
186    /// Returns `true` if instruments have been initialized.
187    #[must_use]
188    pub fn instruments_initialized(&self) -> bool {
189        self.instruments_initialized.load(Ordering::Acquire)
190    }
191
192    /// Sets instruments as initialized.
193    pub fn set_instruments_initialized(&self) {
194        self.instruments_initialized.store(true, Ordering::Release);
195    }
196
197    /// Sets the account identifier for the execution client.
198    pub const fn set_account_id(&mut self, account_id: AccountId) {
199        self.account_id = account_id;
200    }
201}