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