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