Skip to main content

nautilus_sandbox/
config.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//! Configuration for sandbox execution client.
17
18use ahash::AHashMap;
19use nautilus_execution::matching_engine::config::OrderMatchingEngineConfig;
20use nautilus_model::{
21    enums::{AccountType, BookType, OmsType},
22    identifiers::{AccountId, InstrumentId, TraderId, Venue},
23    types::{Currency, Money},
24};
25use rust_decimal::Decimal;
26use serde::{Deserialize, Serialize};
27
28/// Configuration for `SandboxExecutionClient` instances.
29#[derive(Debug, Clone, Serialize, Deserialize)]
30#[cfg_attr(
31    feature = "python",
32    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.sandbox")
33)]
34pub struct SandboxExecutionClientConfig {
35    /// The trader ID for this client.
36    pub trader_id: TraderId,
37    /// The account ID for this client.
38    pub account_id: AccountId,
39    /// The venue for this sandbox execution client.
40    pub venue: Venue,
41    /// The starting balances for this sandbox venue.
42    pub starting_balances: Vec<Money>,
43    /// The base currency for this venue (None for multi-currency).
44    pub base_currency: Option<Currency>,
45    /// The order management system type used by the exchange.
46    pub oms_type: OmsType,
47    /// The account type for the client.
48    pub account_type: AccountType,
49    /// The account default leverage (for margin accounts).
50    pub default_leverage: Decimal,
51    /// Per-instrument leverage overrides.
52    pub leverages: AHashMap<InstrumentId, Decimal>,
53    /// The order book type for the matching engine.
54    pub book_type: BookType,
55    /// If True, account balances won't change (frozen).
56    pub frozen_account: bool,
57    /// If bars should be processed by the matching engine (and move the market).
58    pub bar_execution: bool,
59    /// If trades should be processed by the matching engine (and move the market).
60    pub trade_execution: bool,
61    /// If stop orders are rejected on submission if trigger price is in the market.
62    pub reject_stop_orders: bool,
63    /// If orders with GTD time in force will be supported by the venue.
64    pub support_gtd_orders: bool,
65    /// If contingent orders will be supported/respected by the venue.
66    pub support_contingent_orders: bool,
67    /// If venue position IDs will be generated on order fills.
68    pub use_position_ids: bool,
69    /// If all venue generated identifiers will be random UUID4's.
70    pub use_random_ids: bool,
71    /// If the `reduce_only` execution instruction on orders will be honored.
72    pub use_reduce_only: bool,
73}
74
75impl SandboxExecutionClientConfig {
76    /// Creates a new [`SandboxExecutionClientConfig`] instance.
77    #[must_use]
78    pub fn new(
79        trader_id: TraderId,
80        account_id: AccountId,
81        venue: Venue,
82        starting_balances: Vec<Money>,
83    ) -> Self {
84        Self {
85            trader_id,
86            account_id,
87            venue,
88            starting_balances,
89            base_currency: None,
90            oms_type: OmsType::Netting,
91            account_type: AccountType::Margin,
92            default_leverage: Decimal::ONE,
93            leverages: AHashMap::new(),
94            book_type: BookType::L1_MBP,
95            frozen_account: false,
96            bar_execution: true,
97            trade_execution: false,
98            reject_stop_orders: true,
99            support_gtd_orders: true,
100            support_contingent_orders: true,
101            use_position_ids: true,
102            use_random_ids: false,
103            use_reduce_only: true,
104        }
105    }
106
107    /// Creates an [`OrderMatchingEngineConfig`] from this sandbox config.
108    #[must_use]
109    pub fn to_matching_engine_config(&self) -> OrderMatchingEngineConfig {
110        OrderMatchingEngineConfig::new(
111            self.bar_execution,
112            self.trade_execution,
113            false, // liquidity_consumption
114            self.reject_stop_orders,
115            self.support_gtd_orders,
116            self.support_contingent_orders,
117            self.use_position_ids,
118            self.use_random_ids,
119            self.use_reduce_only,
120            false, // use_market_order_acks
121        )
122    }
123
124    /// Sets the base currency.
125    #[must_use]
126    pub fn with_base_currency(mut self, currency: Currency) -> Self {
127        self.base_currency = Some(currency);
128        self
129    }
130
131    /// Sets the OMS type.
132    #[must_use]
133    pub fn with_oms_type(mut self, oms_type: OmsType) -> Self {
134        self.oms_type = oms_type;
135        self
136    }
137
138    /// Sets the account type.
139    #[must_use]
140    pub fn with_account_type(mut self, account_type: AccountType) -> Self {
141        self.account_type = account_type;
142        self
143    }
144
145    /// Sets the default leverage.
146    #[must_use]
147    pub fn with_default_leverage(mut self, leverage: Decimal) -> Self {
148        self.default_leverage = leverage;
149        self
150    }
151
152    /// Sets the book type.
153    #[must_use]
154    pub fn with_book_type(mut self, book_type: BookType) -> Self {
155        self.book_type = book_type;
156        self
157    }
158
159    /// Sets whether the account is frozen.
160    #[must_use]
161    pub fn with_frozen_account(mut self, frozen: bool) -> Self {
162        self.frozen_account = frozen;
163        self
164    }
165
166    /// Sets whether bar execution is enabled.
167    #[must_use]
168    pub fn with_bar_execution(mut self, enabled: bool) -> Self {
169        self.bar_execution = enabled;
170        self
171    }
172
173    /// Sets whether trade execution is enabled.
174    #[must_use]
175    pub fn with_trade_execution(mut self, enabled: bool) -> Self {
176        self.trade_execution = enabled;
177        self
178    }
179}
180
181impl Default for SandboxExecutionClientConfig {
182    fn default() -> Self {
183        Self {
184            trader_id: TraderId::from("SANDBOX-001"),
185            account_id: AccountId::from("SANDBOX-001"),
186            venue: Venue::new("SANDBOX"),
187            starting_balances: Vec::new(),
188            base_currency: None,
189            oms_type: OmsType::Netting,
190            account_type: AccountType::Margin,
191            default_leverage: Decimal::ONE,
192            leverages: AHashMap::new(),
193            book_type: BookType::L1_MBP,
194            frozen_account: false,
195            bar_execution: true,
196            trade_execution: false,
197            reject_stop_orders: true,
198            support_gtd_orders: true,
199            support_contingent_orders: true,
200            use_position_ids: true,
201            use_random_ids: false,
202            use_reduce_only: true,
203        }
204    }
205}
206
207#[cfg(feature = "python")]
208mod pyo3_impl {
209    use nautilus_model::{
210        enums::{AccountType, BookType, OmsType},
211        identifiers::{AccountId, TraderId, Venue},
212        types::{Currency, Money},
213    };
214    use pyo3::prelude::*;
215    use rust_decimal::Decimal;
216
217    use super::SandboxExecutionClientConfig;
218
219    #[pymethods]
220    impl SandboxExecutionClientConfig {
221        #[new]
222        #[pyo3(signature = (venue, starting_balances, trader_id=None, account_id=None, base_currency=None, oms_type=None, account_type=None, default_leverage=None, book_type=None, frozen_account=false, bar_execution=true, trade_execution=false, reject_stop_orders=true, support_gtd_orders=true, support_contingent_orders=true, use_position_ids=true, use_random_ids=false, use_reduce_only=true))]
223        #[allow(clippy::too_many_arguments)]
224        fn py_new(
225            venue: Venue,
226            starting_balances: Vec<Money>,
227            trader_id: Option<TraderId>,
228            account_id: Option<AccountId>,
229            base_currency: Option<Currency>,
230            oms_type: Option<OmsType>,
231            account_type: Option<AccountType>,
232            default_leverage: Option<Decimal>,
233            book_type: Option<BookType>,
234            frozen_account: bool,
235            bar_execution: bool,
236            trade_execution: bool,
237            reject_stop_orders: bool,
238            support_gtd_orders: bool,
239            support_contingent_orders: bool,
240            use_position_ids: bool,
241            use_random_ids: bool,
242            use_reduce_only: bool,
243        ) -> Self {
244            // Generate default IDs from venue if not provided
245            let trader_id =
246                trader_id.unwrap_or_else(|| TraderId::from(format!("{venue}-001").as_str()));
247            let account_id = account_id
248                .unwrap_or_else(|| AccountId::from(format!("{venue}-SANDBOX-001").as_str()));
249
250            Self {
251                trader_id,
252                account_id,
253                venue,
254                starting_balances,
255                base_currency,
256                oms_type: oms_type.unwrap_or(OmsType::Netting),
257                account_type: account_type.unwrap_or(AccountType::Margin),
258                default_leverage: default_leverage.unwrap_or(Decimal::ONE),
259                leverages: ahash::AHashMap::new(),
260                book_type: book_type.unwrap_or(BookType::L1_MBP),
261                frozen_account,
262                bar_execution,
263                trade_execution,
264                reject_stop_orders,
265                support_gtd_orders,
266                support_contingent_orders,
267                use_position_ids,
268                use_random_ids,
269                use_reduce_only,
270            }
271        }
272
273        #[getter]
274        fn trader_id(&self) -> TraderId {
275            self.trader_id
276        }
277
278        #[getter]
279        fn account_id(&self) -> AccountId {
280            self.account_id
281        }
282
283        #[getter]
284        fn venue(&self) -> Venue {
285            self.venue
286        }
287
288        #[getter]
289        fn starting_balances(&self) -> Vec<Money> {
290            self.starting_balances.clone()
291        }
292
293        #[getter]
294        fn base_currency(&self) -> Option<Currency> {
295            self.base_currency
296        }
297
298        #[getter]
299        fn oms_type(&self) -> OmsType {
300            self.oms_type
301        }
302
303        #[getter]
304        fn account_type(&self) -> AccountType {
305            self.account_type
306        }
307
308        #[getter]
309        fn default_leverage(&self) -> Decimal {
310            self.default_leverage
311        }
312
313        #[getter]
314        fn book_type(&self) -> BookType {
315            self.book_type
316        }
317
318        #[getter]
319        fn frozen_account(&self) -> bool {
320            self.frozen_account
321        }
322
323        #[getter]
324        fn bar_execution(&self) -> bool {
325            self.bar_execution
326        }
327
328        #[getter]
329        fn trade_execution(&self) -> bool {
330            self.trade_execution
331        }
332
333        #[getter]
334        fn reject_stop_orders(&self) -> bool {
335            self.reject_stop_orders
336        }
337
338        #[getter]
339        fn support_gtd_orders(&self) -> bool {
340            self.support_gtd_orders
341        }
342
343        #[getter]
344        fn support_contingent_orders(&self) -> bool {
345            self.support_contingent_orders
346        }
347
348        #[getter]
349        fn use_position_ids(&self) -> bool {
350            self.use_position_ids
351        }
352
353        #[getter]
354        fn use_random_ids(&self) -> bool {
355            self.use_random_ids
356        }
357
358        #[getter]
359        fn use_reduce_only(&self) -> bool {
360            self.use_reduce_only
361        }
362    }
363}