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_model::{
20    data::BarType,
21    enums::TimeInForce,
22    identifiers::{ActorId, ClientId, InstrumentId, StrategyId},
23    types::Quantity,
24};
25use nautilus_trading::strategy::StrategyConfig;
26use pyo3::prelude::*;
27use rust_decimal::Decimal;
28
29use crate::{DataTesterConfig, ExecTesterConfig};
30
31#[pymethods]
32#[pyo3_stub_gen::derive::gen_stub_pymethods]
33impl DataTesterConfig {
34    /// Configuration for the data tester actor.
35    #[new]
36    #[allow(
37        clippy::needless_pass_by_value,
38        reason = "PyO3 #[new] requires owned params"
39    )]
40    #[expect(clippy::too_many_arguments)]
41    #[pyo3(signature = (
42        actor_id = None,
43        client_id = None,
44        instrument_ids = None,
45        bar_types = None,
46        subscribe_book_deltas = None,
47        subscribe_book_depth = None,
48        subscribe_book_at_interval = None,
49        subscribe_quotes = None,
50        subscribe_trades = None,
51        subscribe_mark_prices = None,
52        subscribe_index_prices = None,
53        subscribe_funding_rates = None,
54        subscribe_bars = None,
55        subscribe_instrument = None,
56        subscribe_instrument_status = None,
57        subscribe_instrument_close = None,
58        subscribe_option_greeks = None,
59        can_unsubscribe = None,
60        request_instruments = None,
61        request_quotes = None,
62        request_trades = None,
63        request_bars = None,
64        request_book_snapshot = None,
65        request_book_deltas = None,
66        request_funding_rates = None,
67        book_depth = None,
68        book_interval_ms = None,
69        book_levels_to_print = None,
70        manage_book = None,
71        log_data = None,
72        stats_interval_secs = None,
73        log_events = None,
74        log_commands = None,
75    ))]
76    fn py_new(
77        actor_id: Option<ActorId>,
78        client_id: Option<ClientId>,
79        instrument_ids: Option<Vec<InstrumentId>>,
80        bar_types: Option<Vec<BarType>>,
81        subscribe_book_deltas: Option<bool>,
82        subscribe_book_depth: Option<bool>,
83        subscribe_book_at_interval: Option<bool>,
84        subscribe_quotes: Option<bool>,
85        subscribe_trades: Option<bool>,
86        subscribe_mark_prices: Option<bool>,
87        subscribe_index_prices: Option<bool>,
88        subscribe_funding_rates: Option<bool>,
89        subscribe_bars: Option<bool>,
90        subscribe_instrument: Option<bool>,
91        subscribe_instrument_status: Option<bool>,
92        subscribe_instrument_close: Option<bool>,
93        subscribe_option_greeks: Option<bool>,
94        can_unsubscribe: Option<bool>,
95        request_instruments: Option<bool>,
96        request_quotes: Option<bool>,
97        request_trades: Option<bool>,
98        request_bars: Option<bool>,
99        request_book_snapshot: Option<bool>,
100        request_book_deltas: Option<bool>,
101        request_funding_rates: Option<bool>,
102        book_depth: Option<usize>,
103        book_interval_ms: Option<usize>,
104        book_levels_to_print: Option<usize>,
105        manage_book: Option<bool>,
106        log_data: Option<bool>,
107        stats_interval_secs: Option<u64>,
108        log_events: Option<bool>,
109        log_commands: Option<bool>,
110    ) -> PyResult<Self> {
111        let defaults = Self::default();
112        let config = Self {
113            base: DataActorConfig {
114                actor_id,
115                log_events: log_events.unwrap_or(defaults.base.log_events),
116                log_commands: log_commands.unwrap_or(defaults.base.log_commands),
117            },
118            instrument_ids: instrument_ids.unwrap_or(defaults.instrument_ids),
119            client_id,
120            bar_types,
121            subscribe_book_deltas: subscribe_book_deltas.unwrap_or(defaults.subscribe_book_deltas),
122            subscribe_book_depth: subscribe_book_depth.unwrap_or(defaults.subscribe_book_depth),
123            subscribe_book_at_interval: subscribe_book_at_interval
124                .unwrap_or(defaults.subscribe_book_at_interval),
125            subscribe_quotes: subscribe_quotes.unwrap_or(defaults.subscribe_quotes),
126            subscribe_trades: subscribe_trades.unwrap_or(defaults.subscribe_trades),
127            subscribe_mark_prices: subscribe_mark_prices.unwrap_or(defaults.subscribe_mark_prices),
128            subscribe_index_prices: subscribe_index_prices
129                .unwrap_or(defaults.subscribe_index_prices),
130            subscribe_funding_rates: subscribe_funding_rates
131                .unwrap_or(defaults.subscribe_funding_rates),
132            subscribe_bars: subscribe_bars.unwrap_or(defaults.subscribe_bars),
133            subscribe_instrument: subscribe_instrument.unwrap_or(defaults.subscribe_instrument),
134            subscribe_instrument_status: subscribe_instrument_status
135                .unwrap_or(defaults.subscribe_instrument_status),
136            subscribe_instrument_close: subscribe_instrument_close
137                .unwrap_or(defaults.subscribe_instrument_close),
138            subscribe_option_greeks: subscribe_option_greeks
139                .unwrap_or(defaults.subscribe_option_greeks),
140            subscribe_params: defaults.subscribe_params,
141            request_params: defaults.request_params,
142            can_unsubscribe: can_unsubscribe.unwrap_or(defaults.can_unsubscribe),
143            request_instruments: request_instruments.unwrap_or(defaults.request_instruments),
144            request_quotes: request_quotes.unwrap_or(defaults.request_quotes),
145            request_trades: request_trades.unwrap_or(defaults.request_trades),
146            request_bars: request_bars.unwrap_or(defaults.request_bars),
147            request_book_snapshot: request_book_snapshot.unwrap_or(defaults.request_book_snapshot),
148            request_book_deltas: request_book_deltas.unwrap_or(defaults.request_book_deltas),
149            request_funding_rates: request_funding_rates.unwrap_or(defaults.request_funding_rates),
150            book_type: defaults.book_type,
151            book_depth,
152            book_interval_ms: book_interval_ms.unwrap_or(defaults.book_interval_ms),
153            book_levels_to_print: book_levels_to_print.unwrap_or(defaults.book_levels_to_print),
154            manage_book: manage_book.unwrap_or(defaults.manage_book),
155            log_data: log_data.unwrap_or(defaults.log_data),
156            stats_interval_secs: stats_interval_secs.unwrap_or(defaults.stats_interval_secs),
157        };
158        config.validate().map_err(config_error_to_pyvalue_err)?;
159        Ok(config)
160    }
161
162    fn __repr__(&self) -> String {
163        format!("{self:?}")
164    }
165}
166
167#[pymethods]
168#[pyo3_stub_gen::derive::gen_stub_pymethods]
169impl ExecTesterConfig {
170    /// Configuration for the execution tester strategy.
171    #[new]
172    #[allow(
173        clippy::needless_pass_by_value,
174        reason = "PyO3 #[new] requires owned params"
175    )]
176    #[expect(clippy::too_many_arguments)]
177    #[pyo3(signature = (
178        strategy_id = None,
179        order_id_tag = None,
180        use_hyphens_in_client_order_ids = None,
181        external_order_claims = None,
182        instrument_id = None,
183        client_id = None,
184        order_qty = None,
185        subscribe_book = None,
186        subscribe_quotes = None,
187        subscribe_trades = None,
188        open_position_on_start_qty = None,
189        open_position_on_first_quote = None,
190        open_position_time_in_force = None,
191        enable_limit_buys = None,
192        enable_limit_sells = None,
193        enable_stop_buys = None,
194        enable_stop_sells = None,
195        tob_offset_ticks = None,
196        limit_time_in_force = None,
197        use_post_only = None,
198        limit_aggressive = None,
199        cancel_orders_on_stop = None,
200        close_positions_on_stop = None,
201        close_positions_time_in_force = None,
202        reduce_only_on_stop = None,
203        dry_run = None,
204        log_data = None,
205        can_unsubscribe = None,
206        clamp_to_instrument_price_range = None,
207        log_events = None,
208        log_commands = None,
209    ))]
210    fn py_new(
211        strategy_id: Option<StrategyId>,
212        order_id_tag: Option<String>,
213        use_hyphens_in_client_order_ids: Option<bool>,
214        external_order_claims: Option<Vec<InstrumentId>>,
215        instrument_id: Option<InstrumentId>,
216        client_id: Option<ClientId>,
217        order_qty: Option<Quantity>,
218        subscribe_book: Option<bool>,
219        subscribe_quotes: Option<bool>,
220        subscribe_trades: Option<bool>,
221        open_position_on_start_qty: Option<Decimal>,
222        open_position_on_first_quote: Option<bool>,
223        open_position_time_in_force: Option<TimeInForce>,
224        enable_limit_buys: Option<bool>,
225        enable_limit_sells: Option<bool>,
226        enable_stop_buys: Option<bool>,
227        enable_stop_sells: Option<bool>,
228        tob_offset_ticks: Option<u64>,
229        limit_time_in_force: Option<TimeInForce>,
230        use_post_only: Option<bool>,
231        limit_aggressive: Option<bool>,
232        cancel_orders_on_stop: Option<bool>,
233        close_positions_on_stop: Option<bool>,
234        close_positions_time_in_force: Option<TimeInForce>,
235        reduce_only_on_stop: Option<bool>,
236        dry_run: Option<bool>,
237        log_data: Option<bool>,
238        can_unsubscribe: Option<bool>,
239        clamp_to_instrument_price_range: Option<bool>,
240        log_events: Option<bool>,
241        log_commands: Option<bool>,
242    ) -> Self {
243        let defaults = Self::default();
244        Self {
245            base: StrategyConfig {
246                strategy_id,
247                order_id_tag,
248                use_hyphens_in_client_order_ids: use_hyphens_in_client_order_ids
249                    .unwrap_or(defaults.base.use_hyphens_in_client_order_ids),
250                external_order_claims,
251                log_events: log_events.unwrap_or(defaults.base.log_events),
252                log_commands: log_commands.unwrap_or(defaults.base.log_commands),
253                ..Default::default()
254            },
255            instrument_id: instrument_id.unwrap_or(defaults.instrument_id),
256            order_qty: order_qty.unwrap_or(defaults.order_qty),
257            order_display_qty: defaults.order_display_qty,
258            order_expire_time_delta_mins: defaults.order_expire_time_delta_mins,
259            order_params: defaults.order_params,
260            client_id,
261            subscribe_book: subscribe_book.unwrap_or(defaults.subscribe_book),
262            subscribe_quotes: subscribe_quotes.unwrap_or(defaults.subscribe_quotes),
263            subscribe_trades: subscribe_trades.unwrap_or(defaults.subscribe_trades),
264            book_type: defaults.book_type,
265            book_depth: defaults.book_depth,
266            book_interval_ms: defaults.book_interval_ms,
267            book_levels_to_print: defaults.book_levels_to_print,
268            open_position_on_start_qty,
269            open_position_on_first_quote: open_position_on_first_quote
270                .unwrap_or(defaults.open_position_on_first_quote),
271            open_position_time_in_force: open_position_time_in_force
272                .unwrap_or(defaults.open_position_time_in_force),
273            enable_limit_buys: enable_limit_buys.unwrap_or(defaults.enable_limit_buys),
274            enable_limit_sells: enable_limit_sells.unwrap_or(defaults.enable_limit_sells),
275            enable_stop_buys: enable_stop_buys.unwrap_or(defaults.enable_stop_buys),
276            enable_stop_sells: enable_stop_sells.unwrap_or(defaults.enable_stop_sells),
277            tob_offset_ticks: tob_offset_ticks.unwrap_or(defaults.tob_offset_ticks),
278            limit_time_in_force,
279            stop_order_type: defaults.stop_order_type,
280            stop_offset_ticks: defaults.stop_offset_ticks,
281            stop_limit_offset_ticks: defaults.stop_limit_offset_ticks,
282            stop_trigger_type: defaults.stop_trigger_type,
283            stop_time_in_force: defaults.stop_time_in_force,
284            trailing_offset: defaults.trailing_offset,
285            trailing_offset_type: defaults.trailing_offset_type,
286            enable_brackets: defaults.enable_brackets,
287            batch_submit_limit_pair: defaults.batch_submit_limit_pair,
288            bracket_entry_order_type: defaults.bracket_entry_order_type,
289            bracket_offset_ticks: defaults.bracket_offset_ticks,
290            modify_orders_to_maintain_tob_offset: defaults.modify_orders_to_maintain_tob_offset,
291            modify_stop_orders_to_maintain_offset: defaults.modify_stop_orders_to_maintain_offset,
292            cancel_replace_orders_to_maintain_tob_offset: defaults
293                .cancel_replace_orders_to_maintain_tob_offset,
294            cancel_replace_stop_orders_to_maintain_offset: defaults
295                .cancel_replace_stop_orders_to_maintain_offset,
296            use_post_only: use_post_only.unwrap_or(defaults.use_post_only),
297            limit_aggressive: limit_aggressive.unwrap_or(defaults.limit_aggressive),
298            use_quote_quantity: defaults.use_quote_quantity,
299            emulation_trigger: defaults.emulation_trigger,
300            cancel_orders_on_stop: cancel_orders_on_stop.unwrap_or(defaults.cancel_orders_on_stop),
301            close_positions_on_stop: close_positions_on_stop
302                .unwrap_or(defaults.close_positions_on_stop),
303            close_positions_time_in_force,
304            reduce_only_on_stop: reduce_only_on_stop.unwrap_or(defaults.reduce_only_on_stop),
305            use_individual_cancels_on_stop: defaults.use_individual_cancels_on_stop,
306            use_batch_cancel_on_stop: defaults.use_batch_cancel_on_stop,
307            dry_run: dry_run.unwrap_or(defaults.dry_run),
308            log_data: log_data.unwrap_or(defaults.log_data),
309            test_reject_post_only: defaults.test_reject_post_only,
310            test_reject_reduce_only: defaults.test_reject_reduce_only,
311            test_modify_rejected: defaults.test_modify_rejected,
312            can_unsubscribe: can_unsubscribe.unwrap_or(defaults.can_unsubscribe),
313            clamp_to_instrument_price_range: clamp_to_instrument_price_range
314                .unwrap_or(defaults.clamp_to_instrument_price_range),
315        }
316    }
317
318    fn __repr__(&self) -> String {
319        format!("{self:?}")
320    }
321}