1use 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 #[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 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}