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