Skip to main content

nautilus_trading/python/
examples.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 the example strategy and actor configs.
17
18use nautilus_model::{
19    data::BarType,
20    enums::TimeInForce,
21    identifiers::{ActorId, ClientId, InstrumentId, StrategyId},
22    types::Quantity,
23};
24use pyo3::prelude::*;
25
26use crate::examples::{
27    actors::BookImbalanceActorConfig,
28    strategies::{
29        CompositeMarketMakerConfig, DeltaNeutralVolConfig, EmaCrossConfig, GridMarketMakerConfig,
30        HurstVpinDirectionalConfig,
31    },
32};
33
34#[pymethods]
35#[pyo3_stub_gen::derive::gen_stub_pymethods]
36impl CompositeMarketMakerConfig {
37    /// Configuration for the composite market making strategy.
38    #[new]
39    #[pyo3(signature = (
40        instrument_id,
41        signal_instrument_id,
42        max_position,
43        strategy_id=None,
44        order_id_tag=None,
45        trade_size=None,
46        half_spread_bps=5,
47        inventory_skew_factor=0.0,
48        signal_skew_factor=0.0,
49        signal_baseline=None,
50        requote_threshold_bps=5,
51        expire_time_secs=None,
52        on_cancel_resubmit=false,
53    ))]
54    #[expect(clippy::too_many_arguments)]
55    fn py_new(
56        instrument_id: InstrumentId,
57        signal_instrument_id: InstrumentId,
58        max_position: Quantity,
59        strategy_id: Option<StrategyId>,
60        order_id_tag: Option<String>,
61        trade_size: Option<Quantity>,
62        half_spread_bps: u32,
63        inventory_skew_factor: f64,
64        signal_skew_factor: f64,
65        signal_baseline: Option<f64>,
66        requote_threshold_bps: u32,
67        expire_time_secs: Option<u64>,
68        on_cancel_resubmit: bool,
69    ) -> Self {
70        let mut config = Self::builder()
71            .instrument_id(instrument_id)
72            .signal_instrument_id(signal_instrument_id)
73            .max_position(max_position)
74            .half_spread_bps(half_spread_bps)
75            .inventory_skew_factor(inventory_skew_factor)
76            .signal_skew_factor(signal_skew_factor)
77            .requote_threshold_bps(requote_threshold_bps)
78            .on_cancel_resubmit(on_cancel_resubmit)
79            .maybe_trade_size(trade_size)
80            .maybe_signal_baseline(signal_baseline)
81            .maybe_expire_time_secs(expire_time_secs)
82            .build();
83
84        if let Some(id) = strategy_id {
85            config.base.strategy_id = Some(id);
86        }
87
88        if let Some(tag) = order_id_tag {
89            config.base.order_id_tag = Some(tag);
90        }
91
92        config
93    }
94
95    #[getter]
96    fn instrument_id(&self) -> InstrumentId {
97        self.instrument_id
98    }
99
100    #[getter]
101    fn signal_instrument_id(&self) -> InstrumentId {
102        self.signal_instrument_id
103    }
104
105    #[getter]
106    fn max_position(&self) -> Quantity {
107        self.max_position
108    }
109
110    #[getter]
111    fn trade_size(&self) -> Option<Quantity> {
112        self.trade_size
113    }
114
115    #[getter]
116    fn half_spread_bps(&self) -> u32 {
117        self.half_spread_bps
118    }
119
120    #[getter]
121    fn inventory_skew_factor(&self) -> f64 {
122        self.inventory_skew_factor
123    }
124
125    #[getter]
126    fn signal_skew_factor(&self) -> f64 {
127        self.signal_skew_factor
128    }
129
130    #[getter]
131    fn signal_baseline(&self) -> Option<f64> {
132        self.signal_baseline
133    }
134
135    #[getter]
136    fn requote_threshold_bps(&self) -> u32 {
137        self.requote_threshold_bps
138    }
139
140    #[getter]
141    fn expire_time_secs(&self) -> Option<u64> {
142        self.expire_time_secs
143    }
144
145    #[getter]
146    fn on_cancel_resubmit(&self) -> bool {
147        self.on_cancel_resubmit
148    }
149}
150
151#[pymethods]
152#[pyo3_stub_gen::derive::gen_stub_pymethods]
153impl GridMarketMakerConfig {
154    /// Configuration for the grid market making strategy.
155    #[new]
156    #[pyo3(signature = (
157        instrument_id,
158        max_position,
159        strategy_id=None,
160        order_id_tag=None,
161        trade_size=None,
162        num_levels=3,
163        grid_step_bps=10,
164        skew_factor=0.0,
165        requote_threshold_bps=5,
166        expire_time_secs=None,
167        on_cancel_resubmit=false,
168    ))]
169    #[expect(clippy::too_many_arguments)]
170    fn py_new(
171        instrument_id: InstrumentId,
172        max_position: Quantity,
173        strategy_id: Option<StrategyId>,
174        order_id_tag: Option<String>,
175        trade_size: Option<Quantity>,
176        num_levels: usize,
177        grid_step_bps: u32,
178        skew_factor: f64,
179        requote_threshold_bps: u32,
180        expire_time_secs: Option<u64>,
181        on_cancel_resubmit: bool,
182    ) -> Self {
183        let mut config = Self::builder()
184            .instrument_id(instrument_id)
185            .max_position(max_position)
186            .num_levels(num_levels)
187            .grid_step_bps(grid_step_bps)
188            .skew_factor(skew_factor)
189            .requote_threshold_bps(requote_threshold_bps)
190            .on_cancel_resubmit(on_cancel_resubmit)
191            .maybe_trade_size(trade_size)
192            .maybe_expire_time_secs(expire_time_secs)
193            .build();
194
195        if let Some(id) = strategy_id {
196            config.base.strategy_id = Some(id);
197        }
198
199        if let Some(tag) = order_id_tag {
200            config.base.order_id_tag = Some(tag);
201        }
202
203        config
204    }
205
206    #[getter]
207    fn instrument_id(&self) -> InstrumentId {
208        self.instrument_id
209    }
210
211    #[getter]
212    fn max_position(&self) -> Quantity {
213        self.max_position
214    }
215
216    #[getter]
217    fn trade_size(&self) -> Option<Quantity> {
218        self.trade_size
219    }
220
221    #[getter]
222    fn num_levels(&self) -> usize {
223        self.num_levels
224    }
225
226    #[getter]
227    fn grid_step_bps(&self) -> u32 {
228        self.grid_step_bps
229    }
230
231    #[getter]
232    fn skew_factor(&self) -> f64 {
233        self.skew_factor
234    }
235
236    #[getter]
237    fn requote_threshold_bps(&self) -> u32 {
238        self.requote_threshold_bps
239    }
240
241    #[getter]
242    fn expire_time_secs(&self) -> Option<u64> {
243        self.expire_time_secs
244    }
245
246    #[getter]
247    fn on_cancel_resubmit(&self) -> bool {
248        self.on_cancel_resubmit
249    }
250}
251
252#[pymethods]
253#[pyo3_stub_gen::derive::gen_stub_pymethods]
254impl EmaCrossConfig {
255    /// Configuration for the dual-EMA crossover strategy.
256    #[new]
257    #[pyo3(signature = (
258        instrument_id,
259        trade_size,
260        fast_period=10,
261        slow_period=50,
262        strategy_id=None,
263        order_id_tag=None,
264    ))]
265    fn py_new(
266        instrument_id: InstrumentId,
267        trade_size: Quantity,
268        fast_period: usize,
269        slow_period: usize,
270        strategy_id: Option<StrategyId>,
271        order_id_tag: Option<String>,
272    ) -> Self {
273        let mut config = Self::builder()
274            .instrument_id(instrument_id)
275            .trade_size(trade_size)
276            .fast_period(fast_period)
277            .slow_period(slow_period)
278            .build();
279
280        if let Some(id) = strategy_id {
281            config.base.strategy_id = Some(id);
282        }
283
284        if let Some(tag) = order_id_tag {
285            config.base.order_id_tag = Some(tag);
286        }
287
288        config
289    }
290
291    #[getter]
292    fn instrument_id(&self) -> InstrumentId {
293        self.instrument_id
294    }
295
296    #[getter]
297    fn trade_size(&self) -> Quantity {
298        self.trade_size
299    }
300
301    #[getter]
302    fn fast_period(&self) -> usize {
303        self.fast_period
304    }
305
306    #[getter]
307    fn slow_period(&self) -> usize {
308        self.slow_period
309    }
310}
311
312#[pymethods]
313#[pyo3_stub_gen::derive::gen_stub_pymethods]
314impl DeltaNeutralVolConfig {
315    /// Configuration for the delta-neutral short volatility hedger.
316    ///
317    /// Tracks a short OTM call and put (strangle) and delta-hedges with the
318    /// underlying perpetual swap. Rehedges when portfolio delta exceeds a
319    /// configurable threshold or on a periodic timer.
320    #[new]
321    #[pyo3(signature = (
322        option_family,
323        hedge_instrument_id,
324        client_id,
325        strategy_id=None,
326        order_id_tag=None,
327        target_call_delta=0.20,
328        target_put_delta=-0.20,
329        contracts=1,
330        rehedge_delta_threshold=0.5,
331        rehedge_interval_secs=30,
332        expiry_filter=None,
333        enter_strangle=true,
334        entry_iv_offset=0.0,
335        entry_time_in_force=TimeInForce::Gtc,
336        entry_premium_offset_ticks=None,
337        iv_param_key="px_vol",
338    ))]
339    #[expect(clippy::too_many_arguments)]
340    fn py_new(
341        option_family: String,
342        hedge_instrument_id: InstrumentId,
343        client_id: ClientId,
344        strategy_id: Option<StrategyId>,
345        order_id_tag: Option<String>,
346        target_call_delta: f64,
347        target_put_delta: f64,
348        contracts: u64,
349        rehedge_delta_threshold: f64,
350        rehedge_interval_secs: u64,
351        expiry_filter: Option<String>,
352        enter_strangle: bool,
353        entry_iv_offset: f64,
354        entry_time_in_force: TimeInForce,
355        entry_premium_offset_ticks: Option<i32>,
356        iv_param_key: &str,
357    ) -> Self {
358        let mut config = Self::builder()
359            .option_family(option_family)
360            .hedge_instrument_id(hedge_instrument_id)
361            .client_id(client_id)
362            .target_call_delta(target_call_delta)
363            .target_put_delta(target_put_delta)
364            .contracts(contracts)
365            .rehedge_delta_threshold(rehedge_delta_threshold)
366            .rehedge_interval_secs(rehedge_interval_secs)
367            .enter_strangle(enter_strangle)
368            .entry_iv_offset(entry_iv_offset)
369            .entry_time_in_force(entry_time_in_force)
370            .iv_param_key(iv_param_key.to_string())
371            .maybe_expiry_filter(expiry_filter)
372            .maybe_entry_premium_offset_ticks(entry_premium_offset_ticks)
373            .build();
374
375        if let Some(id) = strategy_id {
376            config.base.strategy_id = Some(id);
377        }
378
379        if let Some(tag) = order_id_tag {
380            config.base.order_id_tag = Some(tag);
381        }
382
383        config
384    }
385
386    #[getter]
387    fn option_family(&self) -> &str {
388        &self.option_family
389    }
390
391    #[getter]
392    fn hedge_instrument_id(&self) -> InstrumentId {
393        self.hedge_instrument_id
394    }
395
396    #[getter]
397    fn client_id(&self) -> ClientId {
398        self.client_id
399    }
400
401    #[getter]
402    fn target_call_delta(&self) -> f64 {
403        self.target_call_delta
404    }
405
406    #[getter]
407    fn target_put_delta(&self) -> f64 {
408        self.target_put_delta
409    }
410
411    #[getter]
412    fn contracts(&self) -> u64 {
413        self.contracts
414    }
415
416    #[getter]
417    fn rehedge_delta_threshold(&self) -> f64 {
418        self.rehedge_delta_threshold
419    }
420
421    #[getter]
422    fn rehedge_interval_secs(&self) -> u64 {
423        self.rehedge_interval_secs
424    }
425
426    #[getter]
427    fn expiry_filter(&self) -> Option<&str> {
428        self.expiry_filter.as_deref()
429    }
430
431    #[getter]
432    fn enter_strangle(&self) -> bool {
433        self.enter_strangle
434    }
435
436    #[getter]
437    fn entry_iv_offset(&self) -> f64 {
438        self.entry_iv_offset
439    }
440
441    #[getter]
442    fn entry_time_in_force(&self) -> TimeInForce {
443        self.entry_time_in_force
444    }
445
446    #[getter]
447    fn entry_premium_offset_ticks(&self) -> Option<i32> {
448        self.entry_premium_offset_ticks
449    }
450}
451
452#[pymethods]
453#[pyo3_stub_gen::derive::gen_stub_pymethods]
454impl HurstVpinDirectionalConfig {
455    /// Configuration for the Hurst/VPIN directional strategy.
456    ///
457    /// Combines a rescaled-range Hurst regime filter on dollar bars with a
458    /// VPIN-derived informed-flow signal, and gates entry timing on the
459    /// live quote stream.
460    #[new]
461    #[pyo3(signature = (
462        instrument_id,
463        bar_type,
464        trade_size,
465        strategy_id=None,
466        order_id_tag=None,
467        hurst_window=128,
468        hurst_lags=None,
469        hurst_enter=0.55,
470        hurst_exit=0.50,
471        vpin_window=50,
472        vpin_threshold=0.30,
473        max_holding_secs=3600,
474    ))]
475    #[expect(clippy::too_many_arguments)]
476    fn py_new(
477        instrument_id: InstrumentId,
478        bar_type: BarType,
479        trade_size: Quantity,
480        strategy_id: Option<StrategyId>,
481        order_id_tag: Option<String>,
482        hurst_window: usize,
483        hurst_lags: Option<Vec<usize>>,
484        hurst_enter: f64,
485        hurst_exit: f64,
486        vpin_window: usize,
487        vpin_threshold: f64,
488        max_holding_secs: u64,
489    ) -> Self {
490        let mut config = Self::builder()
491            .instrument_id(instrument_id)
492            .bar_type(bar_type)
493            .trade_size(trade_size)
494            .hurst_window(hurst_window)
495            .maybe_hurst_lags(hurst_lags)
496            .hurst_enter(hurst_enter)
497            .hurst_exit(hurst_exit)
498            .vpin_window(vpin_window)
499            .vpin_threshold(vpin_threshold)
500            .max_holding_secs(max_holding_secs)
501            .build();
502
503        if let Some(id) = strategy_id {
504            config.base.strategy_id = Some(id);
505        }
506
507        if let Some(tag) = order_id_tag {
508            config.base.order_id_tag = Some(tag);
509        }
510
511        config
512    }
513
514    #[getter]
515    fn instrument_id(&self) -> InstrumentId {
516        self.instrument_id
517    }
518
519    #[getter]
520    fn bar_type(&self) -> BarType {
521        self.bar_type
522    }
523
524    #[getter]
525    fn trade_size(&self) -> Quantity {
526        self.trade_size
527    }
528
529    #[getter]
530    fn hurst_window(&self) -> usize {
531        self.hurst_window
532    }
533
534    #[getter]
535    fn hurst_lags(&self) -> Vec<usize> {
536        self.hurst_lags.clone()
537    }
538
539    #[getter]
540    fn hurst_enter(&self) -> f64 {
541        self.hurst_enter
542    }
543
544    #[getter]
545    fn hurst_exit(&self) -> f64 {
546        self.hurst_exit
547    }
548
549    #[getter]
550    fn vpin_window(&self) -> usize {
551        self.vpin_window
552    }
553
554    #[getter]
555    fn vpin_threshold(&self) -> f64 {
556        self.vpin_threshold
557    }
558
559    #[getter]
560    fn max_holding_secs(&self) -> u64 {
561        self.max_holding_secs
562    }
563}
564
565#[pymethods]
566#[pyo3_stub_gen::derive::gen_stub_pymethods]
567impl BookImbalanceActorConfig {
568    /// Configuration for the order book imbalance actor.
569    #[new]
570    #[pyo3(signature = (instrument_ids, log_interval=100, actor_id=None))]
571    fn py_new(
572        instrument_ids: Vec<InstrumentId>,
573        log_interval: u64,
574        actor_id: Option<ActorId>,
575    ) -> Self {
576        Self::builder()
577            .instrument_ids(instrument_ids)
578            .log_interval(log_interval)
579            .maybe_actor_id(actor_id)
580            .build()
581    }
582
583    #[getter]
584    fn instrument_ids(&self) -> Vec<InstrumentId> {
585        self.instrument_ids.clone()
586    }
587
588    #[getter]
589    fn log_interval(&self) -> u64 {
590        self.log_interval
591    }
592
593    #[getter]
594    fn actor_id(&self) -> Option<ActorId> {
595        self.actor_id
596    }
597}