Skip to main content

nautilus_testkit/python/
testers.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 live tester configuration.
17
18use nautilus_common::{actor::DataActorConfig, python::config_error_to_pyvalue_err};
19use nautilus_core::from_pydict;
20use nautilus_model::{
21    data::BarType,
22    enums::{BookType, OrderType, TimeInForce, TrailingOffsetType, TriggerType},
23    identifiers::{ActorId, ClientId, InstrumentId, StrategyId},
24    types::Quantity,
25};
26use nautilus_trading::strategy::StrategyConfig;
27use pyo3::{prelude::*, types::PyDict};
28use rust_decimal::Decimal;
29
30use crate::{DataTesterConfig, ExecTesterConfig};
31
32#[pymethods]
33#[pyo3_stub_gen::derive::gen_stub_pymethods]
34impl DataTesterConfig {
35    /// Configuration for the data tester actor.
36    #[new]
37    #[allow(
38        clippy::needless_pass_by_value,
39        reason = "PyO3 #[new] requires owned params"
40    )]
41    #[expect(clippy::too_many_arguments)]
42    #[pyo3(signature = (
43        actor_id = None,
44        client_id = None,
45        instrument_ids = None,
46        bar_types = None,
47        subscribe_book_deltas = None,
48        subscribe_book_depth = None,
49        subscribe_book_at_interval = None,
50        subscribe_quotes = None,
51        subscribe_trades = None,
52        subscribe_mark_prices = None,
53        subscribe_index_prices = None,
54        subscribe_funding_rates = None,
55        subscribe_bars = None,
56        subscribe_instrument = None,
57        subscribe_instrument_status = None,
58        subscribe_instrument_close = None,
59        subscribe_option_greeks = None,
60        can_unsubscribe = None,
61        request_instruments = None,
62        request_quotes = None,
63        request_trades = None,
64        request_bars = None,
65        request_book_snapshot = None,
66        request_book_deltas = None,
67        request_funding_rates = None,
68        book_depth = None,
69        book_interval_ms = None,
70        book_levels_to_print = None,
71        manage_book = None,
72        log_data = None,
73        stats_interval_secs = None,
74        log_events = None,
75        log_commands = None,
76    ))]
77    fn py_new(
78        actor_id: Option<ActorId>,
79        client_id: Option<ClientId>,
80        instrument_ids: Option<Vec<InstrumentId>>,
81        bar_types: Option<Vec<BarType>>,
82        subscribe_book_deltas: Option<bool>,
83        subscribe_book_depth: Option<bool>,
84        subscribe_book_at_interval: Option<bool>,
85        subscribe_quotes: Option<bool>,
86        subscribe_trades: Option<bool>,
87        subscribe_mark_prices: Option<bool>,
88        subscribe_index_prices: Option<bool>,
89        subscribe_funding_rates: Option<bool>,
90        subscribe_bars: Option<bool>,
91        subscribe_instrument: Option<bool>,
92        subscribe_instrument_status: Option<bool>,
93        subscribe_instrument_close: Option<bool>,
94        subscribe_option_greeks: Option<bool>,
95        can_unsubscribe: Option<bool>,
96        request_instruments: Option<bool>,
97        request_quotes: Option<bool>,
98        request_trades: Option<bool>,
99        request_bars: Option<bool>,
100        request_book_snapshot: Option<bool>,
101        request_book_deltas: Option<bool>,
102        request_funding_rates: Option<bool>,
103        book_depth: Option<usize>,
104        book_interval_ms: Option<usize>,
105        book_levels_to_print: Option<usize>,
106        manage_book: Option<bool>,
107        log_data: Option<bool>,
108        stats_interval_secs: Option<u64>,
109        log_events: Option<bool>,
110        log_commands: Option<bool>,
111    ) -> PyResult<Self> {
112        let defaults = Self::default();
113        let config = Self {
114            base: DataActorConfig {
115                actor_id,
116                log_events: log_events.unwrap_or(defaults.base.log_events),
117                log_commands: log_commands.unwrap_or(defaults.base.log_commands),
118            },
119            instrument_ids: instrument_ids.unwrap_or(defaults.instrument_ids),
120            client_id,
121            bar_types,
122            subscribe_book_deltas: subscribe_book_deltas.unwrap_or(defaults.subscribe_book_deltas),
123            subscribe_book_depth: subscribe_book_depth.unwrap_or(defaults.subscribe_book_depth),
124            subscribe_book_at_interval: subscribe_book_at_interval
125                .unwrap_or(defaults.subscribe_book_at_interval),
126            subscribe_quotes: subscribe_quotes.unwrap_or(defaults.subscribe_quotes),
127            subscribe_trades: subscribe_trades.unwrap_or(defaults.subscribe_trades),
128            subscribe_mark_prices: subscribe_mark_prices.unwrap_or(defaults.subscribe_mark_prices),
129            subscribe_index_prices: subscribe_index_prices
130                .unwrap_or(defaults.subscribe_index_prices),
131            subscribe_funding_rates: subscribe_funding_rates
132                .unwrap_or(defaults.subscribe_funding_rates),
133            subscribe_bars: subscribe_bars.unwrap_or(defaults.subscribe_bars),
134            subscribe_instrument: subscribe_instrument.unwrap_or(defaults.subscribe_instrument),
135            subscribe_instrument_status: subscribe_instrument_status
136                .unwrap_or(defaults.subscribe_instrument_status),
137            subscribe_instrument_close: subscribe_instrument_close
138                .unwrap_or(defaults.subscribe_instrument_close),
139            subscribe_option_greeks: subscribe_option_greeks
140                .unwrap_or(defaults.subscribe_option_greeks),
141            subscribe_params: defaults.subscribe_params,
142            request_params: defaults.request_params,
143            can_unsubscribe: can_unsubscribe.unwrap_or(defaults.can_unsubscribe),
144            request_instruments: request_instruments.unwrap_or(defaults.request_instruments),
145            request_quotes: request_quotes.unwrap_or(defaults.request_quotes),
146            request_trades: request_trades.unwrap_or(defaults.request_trades),
147            request_bars: request_bars.unwrap_or(defaults.request_bars),
148            request_book_snapshot: request_book_snapshot.unwrap_or(defaults.request_book_snapshot),
149            request_book_deltas: request_book_deltas.unwrap_or(defaults.request_book_deltas),
150            request_funding_rates: request_funding_rates.unwrap_or(defaults.request_funding_rates),
151            book_type: defaults.book_type,
152            book_depth,
153            book_interval_ms: book_interval_ms.unwrap_or(defaults.book_interval_ms),
154            book_levels_to_print: book_levels_to_print.unwrap_or(defaults.book_levels_to_print),
155            manage_book: manage_book.unwrap_or(defaults.manage_book),
156            log_data: log_data.unwrap_or(defaults.log_data),
157            stats_interval_secs: stats_interval_secs.unwrap_or(defaults.stats_interval_secs),
158        };
159        config.validate().map_err(config_error_to_pyvalue_err)?;
160        Ok(config)
161    }
162
163    #[getter]
164    #[pyo3(name = "actor_id")]
165    const fn py_actor_id(&self) -> Option<ActorId> {
166        self.base.actor_id
167    }
168
169    #[getter]
170    #[pyo3(name = "client_id")]
171    const fn py_client_id(&self) -> Option<ClientId> {
172        self.client_id
173    }
174
175    #[getter]
176    #[pyo3(name = "instrument_ids")]
177    fn py_instrument_ids(&self) -> Vec<InstrumentId> {
178        self.instrument_ids.clone()
179    }
180
181    #[getter]
182    #[pyo3(name = "bar_types")]
183    fn py_bar_types(&self) -> Option<Vec<BarType>> {
184        self.bar_types.clone()
185    }
186
187    #[getter]
188    #[pyo3(name = "subscribe_book_deltas")]
189    const fn py_subscribe_book_deltas(&self) -> bool {
190        self.subscribe_book_deltas
191    }
192
193    #[getter]
194    #[pyo3(name = "subscribe_book_depth")]
195    const fn py_subscribe_book_depth(&self) -> bool {
196        self.subscribe_book_depth
197    }
198
199    #[getter]
200    #[pyo3(name = "subscribe_book_at_interval")]
201    const fn py_subscribe_book_at_interval(&self) -> bool {
202        self.subscribe_book_at_interval
203    }
204
205    #[getter]
206    #[pyo3(name = "subscribe_quotes")]
207    const fn py_subscribe_quotes(&self) -> bool {
208        self.subscribe_quotes
209    }
210
211    #[getter]
212    #[pyo3(name = "subscribe_trades")]
213    const fn py_subscribe_trades(&self) -> bool {
214        self.subscribe_trades
215    }
216
217    #[getter]
218    #[pyo3(name = "subscribe_mark_prices")]
219    const fn py_subscribe_mark_prices(&self) -> bool {
220        self.subscribe_mark_prices
221    }
222
223    #[getter]
224    #[pyo3(name = "subscribe_index_prices")]
225    const fn py_subscribe_index_prices(&self) -> bool {
226        self.subscribe_index_prices
227    }
228
229    #[getter]
230    #[pyo3(name = "subscribe_funding_rates")]
231    const fn py_subscribe_funding_rates(&self) -> bool {
232        self.subscribe_funding_rates
233    }
234
235    #[getter]
236    #[pyo3(name = "subscribe_bars")]
237    const fn py_subscribe_bars(&self) -> bool {
238        self.subscribe_bars
239    }
240
241    #[getter]
242    #[pyo3(name = "subscribe_instrument")]
243    const fn py_subscribe_instrument(&self) -> bool {
244        self.subscribe_instrument
245    }
246
247    #[getter]
248    #[pyo3(name = "subscribe_instrument_status")]
249    const fn py_subscribe_instrument_status(&self) -> bool {
250        self.subscribe_instrument_status
251    }
252
253    #[getter]
254    #[pyo3(name = "subscribe_instrument_close")]
255    const fn py_subscribe_instrument_close(&self) -> bool {
256        self.subscribe_instrument_close
257    }
258
259    #[getter]
260    #[pyo3(name = "subscribe_option_greeks")]
261    const fn py_subscribe_option_greeks(&self) -> bool {
262        self.subscribe_option_greeks
263    }
264
265    #[getter]
266    #[pyo3(name = "can_unsubscribe")]
267    const fn py_can_unsubscribe(&self) -> bool {
268        self.can_unsubscribe
269    }
270
271    #[getter]
272    #[pyo3(name = "request_instruments")]
273    const fn py_request_instruments(&self) -> bool {
274        self.request_instruments
275    }
276
277    #[getter]
278    #[pyo3(name = "request_quotes")]
279    const fn py_request_quotes(&self) -> bool {
280        self.request_quotes
281    }
282
283    #[getter]
284    #[pyo3(name = "request_trades")]
285    const fn py_request_trades(&self) -> bool {
286        self.request_trades
287    }
288
289    #[getter]
290    #[pyo3(name = "request_bars")]
291    const fn py_request_bars(&self) -> bool {
292        self.request_bars
293    }
294
295    #[getter]
296    #[pyo3(name = "request_book_snapshot")]
297    const fn py_request_book_snapshot(&self) -> bool {
298        self.request_book_snapshot
299    }
300
301    #[getter]
302    #[pyo3(name = "request_book_deltas")]
303    const fn py_request_book_deltas(&self) -> bool {
304        self.request_book_deltas
305    }
306
307    #[getter]
308    #[pyo3(name = "request_funding_rates")]
309    const fn py_request_funding_rates(&self) -> bool {
310        self.request_funding_rates
311    }
312
313    #[getter]
314    #[pyo3(name = "book_depth")]
315    const fn py_book_depth(&self) -> Option<usize> {
316        self.book_depth
317    }
318
319    #[getter]
320    #[pyo3(name = "book_interval_ms")]
321    const fn py_book_interval_ms(&self) -> usize {
322        self.book_interval_ms
323    }
324
325    #[getter]
326    #[pyo3(name = "book_levels_to_print")]
327    const fn py_book_levels_to_print(&self) -> usize {
328        self.book_levels_to_print
329    }
330
331    #[getter]
332    #[pyo3(name = "manage_book")]
333    const fn py_manage_book(&self) -> bool {
334        self.manage_book
335    }
336
337    #[getter]
338    #[pyo3(name = "log_data")]
339    const fn py_log_data(&self) -> bool {
340        self.log_data
341    }
342
343    #[getter]
344    #[pyo3(name = "stats_interval_secs")]
345    const fn py_stats_interval_secs(&self) -> u64 {
346        self.stats_interval_secs
347    }
348
349    #[getter]
350    #[pyo3(name = "log_events")]
351    const fn py_log_events(&self) -> bool {
352        self.base.log_events
353    }
354
355    #[getter]
356    #[pyo3(name = "log_commands")]
357    const fn py_log_commands(&self) -> bool {
358        self.base.log_commands
359    }
360
361    fn __repr__(&self) -> String {
362        format!("{self:?}")
363    }
364}
365
366#[pymethods]
367#[pyo3_stub_gen::derive::gen_stub_pymethods]
368impl ExecTesterConfig {
369    /// Configuration for the execution tester strategy.
370    #[new]
371    #[allow(
372        clippy::needless_pass_by_value,
373        reason = "PyO3 #[new] requires owned params"
374    )]
375    #[expect(clippy::too_many_arguments)]
376    #[pyo3(signature = (
377        strategy_id = None,
378        order_id_tag = None,
379        use_hyphens_in_client_order_ids = None,
380        use_uuid_client_order_ids = None,
381        external_order_claims = None,
382        instrument_id = None,
383        client_id = None,
384        order_qty = None,
385        order_display_qty = None,
386        order_expire_time_delta_mins = None,
387        order_params = None,
388        subscribe_book = None,
389        subscribe_quotes = None,
390        subscribe_trades = None,
391        book_type = None,
392        book_depth = None,
393        book_interval_ms = None,
394        book_levels_to_print = None,
395        open_position_on_start_qty = None,
396        open_position_on_first_quote = None,
397        open_position_time_in_force = None,
398        enable_limit_buys = None,
399        enable_limit_sells = None,
400        enable_stop_buys = None,
401        enable_stop_sells = None,
402        tob_offset_ticks = None,
403        limit_time_in_force = None,
404        stop_order_type = None,
405        stop_offset_ticks = None,
406        stop_limit_offset_ticks = None,
407        stop_trigger_type = None,
408        stop_time_in_force = None,
409        trailing_offset = None,
410        trailing_offset_type = None,
411        enable_brackets = None,
412        batch_submit_limit_pair = None,
413        bracket_entry_order_type = None,
414        bracket_offset_ticks = None,
415        modify_orders_to_maintain_tob_offset = None,
416        modify_stop_orders_to_maintain_offset = None,
417        cancel_replace_orders_to_maintain_tob_offset = None,
418        cancel_replace_stop_orders_to_maintain_offset = None,
419        trigger_limit_order_maintenance_once = None,
420        use_post_only = None,
421        limit_aggressive = None,
422        use_quote_quantity = None,
423        emulation_trigger = None,
424        use_individual_cancels_on_stop = None,
425        cancel_orders_on_stop = None,
426        close_positions_on_stop = None,
427        close_positions_qty_precision = None,
428        close_positions_time_in_force = None,
429        reduce_only_on_stop = None,
430        use_batch_cancel_on_stop = None,
431        dry_run = None,
432        log_data = None,
433        test_reject_post_only = None,
434        test_reject_reduce_only = None,
435        test_modify_rejected = None,
436        can_unsubscribe = None,
437        clamp_to_instrument_price_range = None,
438        log_events = None,
439        log_commands = None,
440    ))]
441    fn py_new(
442        py: Python<'_>,
443        strategy_id: Option<StrategyId>,
444        order_id_tag: Option<String>,
445        use_hyphens_in_client_order_ids: Option<bool>,
446        use_uuid_client_order_ids: Option<bool>,
447        external_order_claims: Option<Vec<InstrumentId>>,
448        instrument_id: Option<InstrumentId>,
449        client_id: Option<ClientId>,
450        order_qty: Option<Quantity>,
451        order_display_qty: Option<Quantity>,
452        order_expire_time_delta_mins: Option<u64>,
453        order_params: Option<Py<PyDict>>,
454        subscribe_book: Option<bool>,
455        subscribe_quotes: Option<bool>,
456        subscribe_trades: Option<bool>,
457        book_type: Option<BookType>,
458        book_depth: Option<usize>,
459        book_interval_ms: Option<usize>,
460        book_levels_to_print: Option<usize>,
461        open_position_on_start_qty: Option<Decimal>,
462        open_position_on_first_quote: Option<bool>,
463        open_position_time_in_force: Option<TimeInForce>,
464        enable_limit_buys: Option<bool>,
465        enable_limit_sells: Option<bool>,
466        enable_stop_buys: Option<bool>,
467        enable_stop_sells: Option<bool>,
468        tob_offset_ticks: Option<u64>,
469        limit_time_in_force: Option<TimeInForce>,
470        stop_order_type: Option<OrderType>,
471        stop_offset_ticks: Option<u64>,
472        stop_limit_offset_ticks: Option<u64>,
473        stop_trigger_type: Option<TriggerType>,
474        stop_time_in_force: Option<TimeInForce>,
475        trailing_offset: Option<Decimal>,
476        trailing_offset_type: Option<TrailingOffsetType>,
477        enable_brackets: Option<bool>,
478        batch_submit_limit_pair: Option<bool>,
479        bracket_entry_order_type: Option<OrderType>,
480        bracket_offset_ticks: Option<u64>,
481        modify_orders_to_maintain_tob_offset: Option<bool>,
482        modify_stop_orders_to_maintain_offset: Option<bool>,
483        cancel_replace_orders_to_maintain_tob_offset: Option<bool>,
484        cancel_replace_stop_orders_to_maintain_offset: Option<bool>,
485        trigger_limit_order_maintenance_once: Option<bool>,
486        use_post_only: Option<bool>,
487        limit_aggressive: Option<bool>,
488        use_quote_quantity: Option<bool>,
489        emulation_trigger: Option<TriggerType>,
490        use_individual_cancels_on_stop: Option<bool>,
491        cancel_orders_on_stop: Option<bool>,
492        close_positions_on_stop: Option<bool>,
493        close_positions_qty_precision: Option<u8>,
494        close_positions_time_in_force: Option<TimeInForce>,
495        reduce_only_on_stop: Option<bool>,
496        use_batch_cancel_on_stop: Option<bool>,
497        dry_run: Option<bool>,
498        log_data: Option<bool>,
499        test_reject_post_only: Option<bool>,
500        test_reject_reduce_only: Option<bool>,
501        test_modify_rejected: Option<bool>,
502        can_unsubscribe: Option<bool>,
503        clamp_to_instrument_price_range: Option<bool>,
504        log_events: Option<bool>,
505        log_commands: Option<bool>,
506    ) -> PyResult<Self> {
507        let defaults = Self::default();
508        let order_params = match order_params {
509            Some(dict) => from_pydict(py, &dict)?,
510            None => None,
511        };
512        let config = Self {
513            base: StrategyConfig {
514                strategy_id,
515                order_id_tag,
516                use_hyphens_in_client_order_ids: use_hyphens_in_client_order_ids
517                    .unwrap_or(defaults.base.use_hyphens_in_client_order_ids),
518                use_uuid_client_order_ids: use_uuid_client_order_ids
519                    .unwrap_or(defaults.base.use_uuid_client_order_ids),
520                external_order_claims,
521                log_events: log_events.unwrap_or(defaults.base.log_events),
522                log_commands: log_commands.unwrap_or(defaults.base.log_commands),
523                ..Default::default()
524            },
525            instrument_id: instrument_id.unwrap_or(defaults.instrument_id),
526            order_qty: order_qty.unwrap_or(defaults.order_qty),
527            order_display_qty,
528            order_expire_time_delta_mins,
529            order_params,
530            client_id,
531            subscribe_book: subscribe_book.unwrap_or(defaults.subscribe_book),
532            subscribe_quotes: subscribe_quotes.unwrap_or(defaults.subscribe_quotes),
533            subscribe_trades: subscribe_trades.unwrap_or(defaults.subscribe_trades),
534            book_type: book_type.unwrap_or(defaults.book_type),
535            book_depth,
536            book_interval_ms: book_interval_ms.unwrap_or(defaults.book_interval_ms),
537            book_levels_to_print: book_levels_to_print.unwrap_or(defaults.book_levels_to_print),
538            open_position_on_start_qty,
539            open_position_on_first_quote: open_position_on_first_quote
540                .unwrap_or(defaults.open_position_on_first_quote),
541            open_position_time_in_force: open_position_time_in_force
542                .unwrap_or(defaults.open_position_time_in_force),
543            enable_limit_buys: enable_limit_buys.unwrap_or(defaults.enable_limit_buys),
544            enable_limit_sells: enable_limit_sells.unwrap_or(defaults.enable_limit_sells),
545            enable_stop_buys: enable_stop_buys.unwrap_or(defaults.enable_stop_buys),
546            enable_stop_sells: enable_stop_sells.unwrap_or(defaults.enable_stop_sells),
547            tob_offset_ticks: tob_offset_ticks.unwrap_or(defaults.tob_offset_ticks),
548            limit_time_in_force,
549            stop_order_type: stop_order_type.unwrap_or(defaults.stop_order_type),
550            stop_offset_ticks: stop_offset_ticks.unwrap_or(defaults.stop_offset_ticks),
551            stop_limit_offset_ticks,
552            stop_trigger_type: stop_trigger_type.unwrap_or(defaults.stop_trigger_type),
553            stop_time_in_force,
554            trailing_offset,
555            trailing_offset_type: trailing_offset_type.unwrap_or(defaults.trailing_offset_type),
556            enable_brackets: enable_brackets.unwrap_or(defaults.enable_brackets),
557            batch_submit_limit_pair: batch_submit_limit_pair
558                .unwrap_or(defaults.batch_submit_limit_pair),
559            bracket_entry_order_type: bracket_entry_order_type
560                .unwrap_or(defaults.bracket_entry_order_type),
561            bracket_offset_ticks: bracket_offset_ticks.unwrap_or(defaults.bracket_offset_ticks),
562            modify_orders_to_maintain_tob_offset: modify_orders_to_maintain_tob_offset
563                .unwrap_or(defaults.modify_orders_to_maintain_tob_offset),
564            modify_stop_orders_to_maintain_offset: modify_stop_orders_to_maintain_offset
565                .unwrap_or(defaults.modify_stop_orders_to_maintain_offset),
566            cancel_replace_orders_to_maintain_tob_offset:
567                cancel_replace_orders_to_maintain_tob_offset
568                    .unwrap_or(defaults.cancel_replace_orders_to_maintain_tob_offset),
569            cancel_replace_stop_orders_to_maintain_offset:
570                cancel_replace_stop_orders_to_maintain_offset
571                    .unwrap_or(defaults.cancel_replace_stop_orders_to_maintain_offset),
572            trigger_limit_order_maintenance_once: trigger_limit_order_maintenance_once
573                .unwrap_or(defaults.trigger_limit_order_maintenance_once),
574            use_post_only: use_post_only.unwrap_or(defaults.use_post_only),
575            limit_aggressive: limit_aggressive.unwrap_or(defaults.limit_aggressive),
576            use_quote_quantity: use_quote_quantity.unwrap_or(defaults.use_quote_quantity),
577            emulation_trigger,
578            use_individual_cancels_on_stop: use_individual_cancels_on_stop
579                .unwrap_or(defaults.use_individual_cancels_on_stop),
580            cancel_orders_on_stop: cancel_orders_on_stop.unwrap_or(defaults.cancel_orders_on_stop),
581            close_positions_on_stop: close_positions_on_stop
582                .unwrap_or(defaults.close_positions_on_stop),
583            close_positions_qty_precision,
584            close_positions_time_in_force,
585            reduce_only_on_stop: reduce_only_on_stop.unwrap_or(defaults.reduce_only_on_stop),
586            use_batch_cancel_on_stop: use_batch_cancel_on_stop
587                .unwrap_or(defaults.use_batch_cancel_on_stop),
588            dry_run: dry_run.unwrap_or(defaults.dry_run),
589            log_data: log_data.unwrap_or(defaults.log_data),
590            test_reject_post_only: test_reject_post_only.unwrap_or(defaults.test_reject_post_only),
591            test_reject_reduce_only: test_reject_reduce_only
592                .unwrap_or(defaults.test_reject_reduce_only),
593            test_modify_rejected: test_modify_rejected.unwrap_or(defaults.test_modify_rejected),
594            can_unsubscribe: can_unsubscribe.unwrap_or(defaults.can_unsubscribe),
595            clamp_to_instrument_price_range: clamp_to_instrument_price_range
596                .unwrap_or(defaults.clamp_to_instrument_price_range),
597        };
598        config.validate().map_err(config_error_to_pyvalue_err)?;
599        Ok(config)
600    }
601
602    #[getter]
603    #[pyo3(name = "strategy_id")]
604    const fn py_strategy_id(&self) -> Option<StrategyId> {
605        self.base.strategy_id
606    }
607
608    #[getter]
609    #[pyo3(name = "order_id_tag")]
610    fn py_order_id_tag(&self) -> Option<&str> {
611        self.base.order_id_tag.as_deref()
612    }
613
614    #[getter]
615    #[pyo3(name = "use_hyphens_in_client_order_ids")]
616    const fn py_use_hyphens_in_client_order_ids(&self) -> bool {
617        self.base.use_hyphens_in_client_order_ids
618    }
619
620    #[getter]
621    #[pyo3(name = "use_uuid_client_order_ids")]
622    const fn py_use_uuid_client_order_ids(&self) -> bool {
623        self.base.use_uuid_client_order_ids
624    }
625
626    #[getter]
627    #[pyo3(name = "external_order_claims")]
628    fn py_external_order_claims(&self) -> Option<Vec<InstrumentId>> {
629        self.base.external_order_claims.clone()
630    }
631
632    #[getter]
633    #[pyo3(name = "instrument_id")]
634    const fn py_instrument_id(&self) -> InstrumentId {
635        self.instrument_id
636    }
637
638    #[getter]
639    #[pyo3(name = "client_id")]
640    const fn py_client_id(&self) -> Option<ClientId> {
641        self.client_id
642    }
643
644    #[getter]
645    #[pyo3(name = "order_qty")]
646    const fn py_order_qty(&self) -> Quantity {
647        self.order_qty
648    }
649
650    #[getter]
651    #[pyo3(name = "order_display_qty")]
652    const fn py_order_display_qty(&self) -> Option<Quantity> {
653        self.order_display_qty
654    }
655
656    #[getter]
657    #[pyo3(name = "order_expire_time_delta_mins")]
658    const fn py_order_expire_time_delta_mins(&self) -> Option<u64> {
659        self.order_expire_time_delta_mins
660    }
661
662    #[getter]
663    #[pyo3(name = "order_params")]
664    fn py_order_params(&self, py: Python<'_>) -> PyResult<Option<Py<PyDict>>> {
665        self.order_params
666            .as_ref()
667            .map(|params| params.to_pydict(py))
668            .transpose()
669    }
670
671    #[getter]
672    #[pyo3(name = "subscribe_book")]
673    const fn py_subscribe_book(&self) -> bool {
674        self.subscribe_book
675    }
676
677    #[getter]
678    #[pyo3(name = "subscribe_quotes")]
679    const fn py_subscribe_quotes(&self) -> bool {
680        self.subscribe_quotes
681    }
682
683    #[getter]
684    #[pyo3(name = "subscribe_trades")]
685    const fn py_subscribe_trades(&self) -> bool {
686        self.subscribe_trades
687    }
688
689    #[getter]
690    #[pyo3(name = "book_type")]
691    const fn py_book_type(&self) -> BookType {
692        self.book_type
693    }
694
695    #[getter]
696    #[pyo3(name = "book_depth")]
697    const fn py_book_depth(&self) -> Option<usize> {
698        self.book_depth
699    }
700
701    #[getter]
702    #[pyo3(name = "book_interval_ms")]
703    const fn py_book_interval_ms(&self) -> usize {
704        self.book_interval_ms
705    }
706
707    #[getter]
708    #[pyo3(name = "book_levels_to_print")]
709    const fn py_book_levels_to_print(&self) -> usize {
710        self.book_levels_to_print
711    }
712
713    #[getter]
714    #[pyo3(name = "open_position_on_start_qty")]
715    const fn py_open_position_on_start_qty(&self) -> Option<Decimal> {
716        self.open_position_on_start_qty
717    }
718
719    #[getter]
720    #[pyo3(name = "open_position_on_first_quote")]
721    const fn py_open_position_on_first_quote(&self) -> bool {
722        self.open_position_on_first_quote
723    }
724
725    #[getter]
726    #[pyo3(name = "open_position_time_in_force")]
727    const fn py_open_position_time_in_force(&self) -> TimeInForce {
728        self.open_position_time_in_force
729    }
730
731    #[getter]
732    #[pyo3(name = "enable_limit_buys")]
733    const fn py_enable_limit_buys(&self) -> bool {
734        self.enable_limit_buys
735    }
736
737    #[getter]
738    #[pyo3(name = "enable_limit_sells")]
739    const fn py_enable_limit_sells(&self) -> bool {
740        self.enable_limit_sells
741    }
742
743    #[getter]
744    #[pyo3(name = "enable_stop_buys")]
745    const fn py_enable_stop_buys(&self) -> bool {
746        self.enable_stop_buys
747    }
748
749    #[getter]
750    #[pyo3(name = "enable_stop_sells")]
751    const fn py_enable_stop_sells(&self) -> bool {
752        self.enable_stop_sells
753    }
754
755    #[getter]
756    #[pyo3(name = "tob_offset_ticks")]
757    const fn py_tob_offset_ticks(&self) -> u64 {
758        self.tob_offset_ticks
759    }
760
761    #[getter]
762    #[pyo3(name = "limit_time_in_force")]
763    const fn py_limit_time_in_force(&self) -> Option<TimeInForce> {
764        self.limit_time_in_force
765    }
766
767    #[getter]
768    #[pyo3(name = "stop_order_type")]
769    const fn py_stop_order_type(&self) -> OrderType {
770        self.stop_order_type
771    }
772
773    #[getter]
774    #[pyo3(name = "stop_offset_ticks")]
775    const fn py_stop_offset_ticks(&self) -> u64 {
776        self.stop_offset_ticks
777    }
778
779    #[getter]
780    #[pyo3(name = "stop_limit_offset_ticks")]
781    const fn py_stop_limit_offset_ticks(&self) -> Option<u64> {
782        self.stop_limit_offset_ticks
783    }
784
785    #[getter]
786    #[pyo3(name = "stop_trigger_type")]
787    const fn py_stop_trigger_type(&self) -> TriggerType {
788        self.stop_trigger_type
789    }
790
791    #[getter]
792    #[pyo3(name = "stop_time_in_force")]
793    const fn py_stop_time_in_force(&self) -> Option<TimeInForce> {
794        self.stop_time_in_force
795    }
796
797    #[getter]
798    #[pyo3(name = "trailing_offset")]
799    const fn py_trailing_offset(&self) -> Option<Decimal> {
800        self.trailing_offset
801    }
802
803    #[getter]
804    #[pyo3(name = "trailing_offset_type")]
805    const fn py_trailing_offset_type(&self) -> TrailingOffsetType {
806        self.trailing_offset_type
807    }
808
809    #[getter]
810    #[pyo3(name = "enable_brackets")]
811    const fn py_enable_brackets(&self) -> bool {
812        self.enable_brackets
813    }
814
815    #[getter]
816    #[pyo3(name = "batch_submit_limit_pair")]
817    const fn py_batch_submit_limit_pair(&self) -> bool {
818        self.batch_submit_limit_pair
819    }
820
821    #[getter]
822    #[pyo3(name = "bracket_entry_order_type")]
823    const fn py_bracket_entry_order_type(&self) -> OrderType {
824        self.bracket_entry_order_type
825    }
826
827    #[getter]
828    #[pyo3(name = "bracket_offset_ticks")]
829    const fn py_bracket_offset_ticks(&self) -> u64 {
830        self.bracket_offset_ticks
831    }
832
833    #[getter]
834    #[pyo3(name = "modify_orders_to_maintain_tob_offset")]
835    const fn py_modify_orders_to_maintain_tob_offset(&self) -> bool {
836        self.modify_orders_to_maintain_tob_offset
837    }
838
839    #[getter]
840    #[pyo3(name = "modify_stop_orders_to_maintain_offset")]
841    const fn py_modify_stop_orders_to_maintain_offset(&self) -> bool {
842        self.modify_stop_orders_to_maintain_offset
843    }
844
845    #[getter]
846    #[pyo3(name = "cancel_replace_orders_to_maintain_tob_offset")]
847    const fn py_cancel_replace_orders_to_maintain_tob_offset(&self) -> bool {
848        self.cancel_replace_orders_to_maintain_tob_offset
849    }
850
851    #[getter]
852    #[pyo3(name = "cancel_replace_stop_orders_to_maintain_offset")]
853    const fn py_cancel_replace_stop_orders_to_maintain_offset(&self) -> bool {
854        self.cancel_replace_stop_orders_to_maintain_offset
855    }
856
857    #[getter]
858    #[pyo3(name = "trigger_limit_order_maintenance_once")]
859    const fn py_trigger_limit_order_maintenance_once(&self) -> bool {
860        self.trigger_limit_order_maintenance_once
861    }
862
863    #[getter]
864    #[pyo3(name = "use_post_only")]
865    const fn py_use_post_only(&self) -> bool {
866        self.use_post_only
867    }
868
869    #[getter]
870    #[pyo3(name = "limit_aggressive")]
871    const fn py_limit_aggressive(&self) -> bool {
872        self.limit_aggressive
873    }
874
875    #[getter]
876    #[pyo3(name = "use_quote_quantity")]
877    const fn py_use_quote_quantity(&self) -> bool {
878        self.use_quote_quantity
879    }
880
881    #[getter]
882    #[pyo3(name = "emulation_trigger")]
883    const fn py_emulation_trigger(&self) -> Option<TriggerType> {
884        self.emulation_trigger
885    }
886
887    #[getter]
888    #[pyo3(name = "use_individual_cancels_on_stop")]
889    const fn py_use_individual_cancels_on_stop(&self) -> bool {
890        self.use_individual_cancels_on_stop
891    }
892
893    #[getter]
894    #[pyo3(name = "cancel_orders_on_stop")]
895    const fn py_cancel_orders_on_stop(&self) -> bool {
896        self.cancel_orders_on_stop
897    }
898
899    #[getter]
900    #[pyo3(name = "close_positions_on_stop")]
901    const fn py_close_positions_on_stop(&self) -> bool {
902        self.close_positions_on_stop
903    }
904
905    #[getter]
906    #[pyo3(name = "close_positions_qty_precision")]
907    const fn py_close_positions_qty_precision(&self) -> Option<u8> {
908        self.close_positions_qty_precision
909    }
910
911    #[getter]
912    #[pyo3(name = "close_positions_time_in_force")]
913    const fn py_close_positions_time_in_force(&self) -> Option<TimeInForce> {
914        self.close_positions_time_in_force
915    }
916
917    #[getter]
918    #[pyo3(name = "reduce_only_on_stop")]
919    const fn py_reduce_only_on_stop(&self) -> bool {
920        self.reduce_only_on_stop
921    }
922
923    #[getter]
924    #[pyo3(name = "use_batch_cancel_on_stop")]
925    const fn py_use_batch_cancel_on_stop(&self) -> bool {
926        self.use_batch_cancel_on_stop
927    }
928
929    #[getter]
930    #[pyo3(name = "dry_run")]
931    const fn py_dry_run(&self) -> bool {
932        self.dry_run
933    }
934
935    #[getter]
936    #[pyo3(name = "log_data")]
937    const fn py_log_data(&self) -> bool {
938        self.log_data
939    }
940
941    #[getter]
942    #[pyo3(name = "test_reject_post_only")]
943    const fn py_test_reject_post_only(&self) -> bool {
944        self.test_reject_post_only
945    }
946
947    #[getter]
948    #[pyo3(name = "test_reject_reduce_only")]
949    const fn py_test_reject_reduce_only(&self) -> bool {
950        self.test_reject_reduce_only
951    }
952
953    #[getter]
954    #[pyo3(name = "test_modify_rejected")]
955    const fn py_test_modify_rejected(&self) -> bool {
956        self.test_modify_rejected
957    }
958
959    #[getter]
960    #[pyo3(name = "can_unsubscribe")]
961    const fn py_can_unsubscribe(&self) -> bool {
962        self.can_unsubscribe
963    }
964
965    #[getter]
966    #[pyo3(name = "clamp_to_instrument_price_range")]
967    const fn py_clamp_to_instrument_price_range(&self) -> bool {
968        self.clamp_to_instrument_price_range
969    }
970
971    #[getter]
972    #[pyo3(name = "log_events")]
973    const fn py_log_events(&self) -> bool {
974        self.base.log_events
975    }
976
977    #[getter]
978    #[pyo3(name = "log_commands")]
979    const fn py_log_commands(&self) -> bool {
980        self.base.log_commands
981    }
982
983    fn __repr__(&self) -> String {
984        format!("{self:?}")
985    }
986}