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", from_py_object)
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: true,
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            false, // bar_adaptive_high_low_ordering
113            self.trade_execution,
114            false, // liquidity_consumption
115            self.reject_stop_orders,
116            self.support_gtd_orders,
117            self.support_contingent_orders,
118            self.use_position_ids,
119            self.use_random_ids,
120            self.use_reduce_only,
121            false, // use_market_order_acks
122        )
123    }
124
125    /// Sets the base currency.
126    #[must_use]
127    pub fn with_base_currency(mut self, currency: Currency) -> Self {
128        self.base_currency = Some(currency);
129        self
130    }
131
132    /// Sets the OMS type.
133    #[must_use]
134    pub fn with_oms_type(mut self, oms_type: OmsType) -> Self {
135        self.oms_type = oms_type;
136        self
137    }
138
139    /// Sets the account type.
140    #[must_use]
141    pub fn with_account_type(mut self, account_type: AccountType) -> Self {
142        self.account_type = account_type;
143        self
144    }
145
146    /// Sets the default leverage.
147    #[must_use]
148    pub fn with_default_leverage(mut self, leverage: Decimal) -> Self {
149        self.default_leverage = leverage;
150        self
151    }
152
153    /// Sets the book type.
154    #[must_use]
155    pub fn with_book_type(mut self, book_type: BookType) -> Self {
156        self.book_type = book_type;
157        self
158    }
159
160    /// Sets whether the account is frozen.
161    #[must_use]
162    pub fn with_frozen_account(mut self, frozen: bool) -> Self {
163        self.frozen_account = frozen;
164        self
165    }
166
167    /// Sets whether bar execution is enabled.
168    #[must_use]
169    pub fn with_bar_execution(mut self, enabled: bool) -> Self {
170        self.bar_execution = enabled;
171        self
172    }
173
174    /// Sets whether trade execution is enabled.
175    #[must_use]
176    pub fn with_trade_execution(mut self, enabled: bool) -> Self {
177        self.trade_execution = enabled;
178        self
179    }
180}
181
182impl Default for SandboxExecutionClientConfig {
183    fn default() -> Self {
184        Self {
185            trader_id: TraderId::from("SANDBOX-001"),
186            account_id: AccountId::from("SANDBOX-001"),
187            venue: Venue::new("SANDBOX"),
188            starting_balances: Vec::new(),
189            base_currency: None,
190            oms_type: OmsType::Netting,
191            account_type: AccountType::Margin,
192            default_leverage: Decimal::ONE,
193            leverages: AHashMap::new(),
194            book_type: BookType::L1_MBP,
195            frozen_account: false,
196            bar_execution: true,
197            trade_execution: true,
198            reject_stop_orders: true,
199            support_gtd_orders: true,
200            support_contingent_orders: true,
201            use_position_ids: true,
202            use_random_ids: false,
203            use_reduce_only: true,
204        }
205    }
206}
207
208#[cfg(feature = "python")]
209mod pyo3_impl {
210    use nautilus_model::{
211        enums::{AccountType, BookType, OmsType},
212        identifiers::{AccountId, TraderId, Venue},
213        types::{Currency, Money},
214    };
215    use pyo3::prelude::*;
216    use rust_decimal::Decimal;
217
218    use super::SandboxExecutionClientConfig;
219
220    #[pymethods]
221    impl SandboxExecutionClientConfig {
222        #[new]
223        #[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=true, reject_stop_orders=true, support_gtd_orders=true, support_contingent_orders=true, use_position_ids=true, use_random_ids=false, use_reduce_only=true))]
224        #[allow(clippy::too_many_arguments)]
225        fn py_new(
226            venue: Venue,
227            starting_balances: Vec<Money>,
228            trader_id: Option<TraderId>,
229            account_id: Option<AccountId>,
230            base_currency: Option<Currency>,
231            oms_type: Option<OmsType>,
232            account_type: Option<AccountType>,
233            default_leverage: Option<Decimal>,
234            book_type: Option<BookType>,
235            frozen_account: bool,
236            bar_execution: bool,
237            trade_execution: bool,
238            reject_stop_orders: bool,
239            support_gtd_orders: bool,
240            support_contingent_orders: bool,
241            use_position_ids: bool,
242            use_random_ids: bool,
243            use_reduce_only: bool,
244        ) -> Self {
245            // Generate default IDs from venue if not provided
246            let trader_id =
247                trader_id.unwrap_or_else(|| TraderId::from(format!("{venue}-001").as_str()));
248            let account_id = account_id
249                .unwrap_or_else(|| AccountId::from(format!("{venue}-SANDBOX-001").as_str()));
250
251            Self {
252                trader_id,
253                account_id,
254                venue,
255                starting_balances,
256                base_currency,
257                oms_type: oms_type.unwrap_or(OmsType::Netting),
258                account_type: account_type.unwrap_or(AccountType::Margin),
259                default_leverage: default_leverage.unwrap_or(Decimal::ONE),
260                leverages: ahash::AHashMap::new(),
261                book_type: book_type.unwrap_or(BookType::L1_MBP),
262                frozen_account,
263                bar_execution,
264                trade_execution,
265                reject_stop_orders,
266                support_gtd_orders,
267                support_contingent_orders,
268                use_position_ids,
269                use_random_ids,
270                use_reduce_only,
271            }
272        }
273
274        #[getter]
275        fn trader_id(&self) -> TraderId {
276            self.trader_id
277        }
278
279        #[getter]
280        fn account_id(&self) -> AccountId {
281            self.account_id
282        }
283
284        #[getter]
285        fn venue(&self) -> Venue {
286            self.venue
287        }
288
289        #[getter]
290        fn starting_balances(&self) -> Vec<Money> {
291            self.starting_balances.clone()
292        }
293
294        #[getter]
295        fn base_currency(&self) -> Option<Currency> {
296            self.base_currency
297        }
298
299        #[getter]
300        fn oms_type(&self) -> OmsType {
301            self.oms_type
302        }
303
304        #[getter]
305        fn account_type(&self) -> AccountType {
306            self.account_type
307        }
308
309        #[getter]
310        fn default_leverage(&self) -> Decimal {
311            self.default_leverage
312        }
313
314        #[getter]
315        fn book_type(&self) -> BookType {
316            self.book_type
317        }
318
319        #[getter]
320        fn frozen_account(&self) -> bool {
321            self.frozen_account
322        }
323
324        #[getter]
325        fn bar_execution(&self) -> bool {
326            self.bar_execution
327        }
328
329        #[getter]
330        fn trade_execution(&self) -> bool {
331            self.trade_execution
332        }
333
334        #[getter]
335        fn reject_stop_orders(&self) -> bool {
336            self.reject_stop_orders
337        }
338
339        #[getter]
340        fn support_gtd_orders(&self) -> bool {
341            self.support_gtd_orders
342        }
343
344        #[getter]
345        fn support_contingent_orders(&self) -> bool {
346            self.support_contingent_orders
347        }
348
349        #[getter]
350        fn use_position_ids(&self) -> bool {
351            self.use_position_ids
352        }
353
354        #[getter]
355        fn use_random_ids(&self) -> bool {
356            self.use_random_ids
357        }
358
359        #[getter]
360        fn use_reduce_only(&self) -> bool {
361            self.use_reduce_only
362        }
363    }
364}