Skip to main content

nautilus_sandbox/python/
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//! Python bindings for sandbox configuration.
17
18use nautilus_common::python::config_error_to_pyvalue_err;
19use nautilus_execution::{
20    models::{fee::FeeModelAny, fill::FillModelAny, latency::LatencyModelAny},
21    python::{
22        fee::{fee_model_any_to_pyobject, pyobject_to_fee_model_any},
23        fill::{fill_model_any_to_pyobject, pyobject_to_fill_model_any},
24        latency::{latency_model_any_to_pyobject, pyobject_to_latency_model_any},
25    },
26};
27use nautilus_model::{
28    enums::{AccountType, BookType, OmsType},
29    identifiers::{AccountId, Venue},
30    python::account::margin_model::{margin_model_any_to_pyobject, pyobject_to_margin_model_any},
31    types::{Currency, Money},
32};
33use pyo3::{Py, PyAny, Python, prelude::*};
34use rust_decimal::Decimal;
35
36use crate::config::SandboxExecutionClientConfig;
37
38#[pymethods]
39#[pyo3_stub_gen::derive::gen_stub_pymethods]
40impl SandboxExecutionClientConfig {
41    /// Configuration for `SandboxExecutionClient` instances.
42    #[new]
43    #[pyo3(signature = (
44        venue,
45        starting_balances,
46        account_id = None,
47        base_currency = None,
48        oms_type = None,
49        account_type = None,
50        default_leverage = None,
51        book_type = None,
52        fill_model = None,
53        fee_model = None,
54        margin_model = None,
55        latency_model = None,
56        frozen_account = false,
57        bar_execution = true,
58        trade_execution = true,
59        reject_stop_orders = true,
60        support_gtd_orders = true,
61        support_contingent_orders = true,
62        use_position_ids = true,
63        use_random_ids = false,
64        use_reduce_only = true,
65        queue_position = false,
66        liquidity_consumption = false,
67        bar_adaptive_high_low_ordering = false,
68        use_market_order_acks = false,
69        oto_full_trigger = false,
70        price_protection_points = None,
71    ))]
72    #[expect(clippy::too_many_arguments)]
73    fn py_new(
74        venue: Venue,
75        starting_balances: Vec<Money>,
76        account_id: Option<AccountId>,
77        base_currency: Option<Currency>,
78        oms_type: Option<OmsType>,
79        account_type: Option<AccountType>,
80        default_leverage: Option<Decimal>,
81        book_type: Option<BookType>,
82        fill_model: Option<Py<PyAny>>,
83        fee_model: Option<Py<PyAny>>,
84        margin_model: Option<Py<PyAny>>,
85        latency_model: Option<Py<PyAny>>,
86        frozen_account: bool,
87        bar_execution: bool,
88        trade_execution: bool,
89        reject_stop_orders: bool,
90        support_gtd_orders: bool,
91        support_contingent_orders: bool,
92        use_position_ids: bool,
93        use_random_ids: bool,
94        use_reduce_only: bool,
95        queue_position: bool,
96        liquidity_consumption: bool,
97        bar_adaptive_high_low_ordering: bool,
98        use_market_order_acks: bool,
99        oto_full_trigger: bool,
100        price_protection_points: Option<u32>,
101    ) -> PyResult<Self> {
102        // Generate the default account ID from the venue
103        let account_id =
104            account_id.unwrap_or_else(|| AccountId::from(format!("{venue}-SANDBOX-001").as_str()));
105        let fill_model: Option<FillModelAny> = fill_model
106            .map(|obj| Python::attach(|py| pyobject_to_fill_model_any(obj.bind(py))))
107            .transpose()?;
108
109        let fee_model: Option<FeeModelAny> = fee_model
110            .map(|obj| Python::attach(|py| pyobject_to_fee_model_any(obj.bind(py))))
111            .transpose()?;
112
113        let margin_model = margin_model
114            .map(|obj| Python::attach(|py| pyobject_to_margin_model_any(obj.bind(py))))
115            .transpose()?;
116
117        let latency_model: Option<LatencyModelAny> = latency_model
118            .map(|obj| Python::attach(|py| pyobject_to_latency_model_any(obj.bind(py))))
119            .transpose()?;
120
121        let config = Self {
122            account_id,
123            venue,
124            starting_balances,
125            base_currency,
126            oms_type: oms_type.unwrap_or(OmsType::Netting),
127            account_type: account_type.unwrap_or(AccountType::Margin),
128            default_leverage: default_leverage.unwrap_or(Decimal::ONE),
129            leverages: ahash::AHashMap::new(),
130            book_type: book_type.unwrap_or(BookType::L1_MBP),
131            fill_model,
132            fee_model,
133            margin_model,
134            latency_model,
135            frozen_account,
136            bar_execution,
137            trade_execution,
138            reject_stop_orders,
139            support_gtd_orders,
140            support_contingent_orders,
141            use_position_ids,
142            use_random_ids,
143            use_reduce_only,
144            queue_position,
145            liquidity_consumption,
146            bar_adaptive_high_low_ordering,
147            use_market_order_acks,
148            oto_full_trigger,
149            price_protection_points: price_protection_points.unwrap_or(0),
150        };
151
152        config.validate().map_err(config_error_to_pyvalue_err)?;
153        Ok(config)
154    }
155
156    #[getter]
157    fn account_id(&self) -> AccountId {
158        self.account_id
159    }
160
161    #[getter]
162    fn venue(&self) -> Venue {
163        self.venue
164    }
165
166    #[getter]
167    fn starting_balances(&self) -> Vec<Money> {
168        self.starting_balances.clone()
169    }
170
171    #[getter]
172    fn base_currency(&self) -> Option<Currency> {
173        self.base_currency
174    }
175
176    #[getter]
177    fn oms_type(&self) -> OmsType {
178        self.oms_type
179    }
180
181    #[getter]
182    fn account_type(&self) -> AccountType {
183        self.account_type
184    }
185
186    #[getter]
187    fn default_leverage(&self) -> Decimal {
188        self.default_leverage
189    }
190
191    #[getter]
192    fn book_type(&self) -> BookType {
193        self.book_type
194    }
195
196    #[getter]
197    fn fill_model(&self, py: Python<'_>) -> PyResult<Option<Py<PyAny>>> {
198        self.fill_model
199            .as_ref()
200            .map(|model| fill_model_any_to_pyobject(py, model))
201            .transpose()
202    }
203
204    #[getter]
205    fn fee_model(&self, py: Python<'_>) -> PyResult<Option<Py<PyAny>>> {
206        self.fee_model
207            .as_ref()
208            .map(|model| fee_model_any_to_pyobject(py, model))
209            .transpose()
210    }
211
212    #[getter]
213    fn margin_model(&self, py: Python<'_>) -> PyResult<Option<Py<PyAny>>> {
214        self.margin_model
215            .as_ref()
216            .map(|model| margin_model_any_to_pyobject(py, model))
217            .transpose()
218    }
219
220    #[getter]
221    fn latency_model(&self, py: Python<'_>) -> PyResult<Option<Py<PyAny>>> {
222        self.latency_model
223            .as_ref()
224            .map(|model| latency_model_any_to_pyobject(py, model))
225            .transpose()
226    }
227
228    #[getter]
229    fn frozen_account(&self) -> bool {
230        self.frozen_account
231    }
232
233    #[getter]
234    fn bar_execution(&self) -> bool {
235        self.bar_execution
236    }
237
238    #[getter]
239    fn trade_execution(&self) -> bool {
240        self.trade_execution
241    }
242
243    #[getter]
244    fn reject_stop_orders(&self) -> bool {
245        self.reject_stop_orders
246    }
247
248    #[getter]
249    fn support_gtd_orders(&self) -> bool {
250        self.support_gtd_orders
251    }
252
253    #[getter]
254    fn support_contingent_orders(&self) -> bool {
255        self.support_contingent_orders
256    }
257
258    #[getter]
259    fn use_position_ids(&self) -> bool {
260        self.use_position_ids
261    }
262
263    #[getter]
264    fn use_random_ids(&self) -> bool {
265        self.use_random_ids
266    }
267
268    #[getter]
269    fn use_reduce_only(&self) -> bool {
270        self.use_reduce_only
271    }
272
273    #[getter]
274    fn queue_position(&self) -> bool {
275        self.queue_position
276    }
277
278    #[getter]
279    fn liquidity_consumption(&self) -> bool {
280        self.liquidity_consumption
281    }
282
283    #[getter]
284    fn bar_adaptive_high_low_ordering(&self) -> bool {
285        self.bar_adaptive_high_low_ordering
286    }
287
288    #[getter]
289    fn use_market_order_acks(&self) -> bool {
290        self.use_market_order_acks
291    }
292
293    #[getter]
294    fn oto_full_trigger(&self) -> bool {
295        self.oto_full_trigger
296    }
297
298    #[getter]
299    fn price_protection_points(&self) -> u32 {
300        self.price_protection_points
301    }
302}