Skip to main content

nautilus_trading/examples/strategies/hurst_vpin_directional/
config.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//! Configuration for the Hurst/VPIN directional strategy.
17
18use nautilus_common::config::{ConfigError, ConfigErrorCollector, ConfigResult};
19use nautilus_model::{
20    data::BarType,
21    identifiers::{InstrumentId, StrategyId},
22    types::Quantity,
23};
24
25use crate::strategy::StrategyConfig;
26
27pub(crate) const MAX_HURST_VPIN_WINDOW: usize = 16_384;
28
29/// Configuration for the Hurst/VPIN directional strategy.
30///
31/// Combines a rescaled-range Hurst regime filter on dollar bars with a
32/// VPIN-derived informed-flow signal, and gates entry timing on the
33/// live quote stream.
34///
35/// The Hurst and VPIN rolling windows must each be in the range `[1, 16_384]`.
36#[derive(Debug, Clone, bon::Builder)]
37#[cfg_attr(
38    feature = "python",
39    pyo3::pyclass(module = "nautilus_trader.trading", from_py_object)
40)]
41#[cfg_attr(
42    feature = "python",
43    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.trading")
44)]
45pub struct HurstVpinDirectionalConfig {
46    /// Base strategy configuration.
47    #[builder(default = StrategyConfig {
48        strategy_id: Some(StrategyId::from("HURST_VPIN-001")),
49        order_id_tag: Some("001".to_string()),
50        ..Default::default()
51    })]
52    pub base: StrategyConfig,
53    /// Instrument to subscribe to and trade.
54    pub instrument_id: InstrumentId,
55    /// Dollar bar type (value aggregation sourced from trades).
56    pub bar_type: BarType,
57    /// Order quantity for each entry.
58    pub trade_size: Quantity,
59    /// Rolling window of dollar bar returns used to estimate the Hurst exponent (range `[1, 16_384]`).
60    #[builder(default = 128)]
61    pub hurst_window: usize,
62    /// Lag set used for rescaled range regression.
63    #[builder(default = vec![4, 8, 16, 32])]
64    pub hurst_lags: Vec<usize>,
65    /// Hurst threshold for entering a position (trending regime).
66    #[builder(default = 0.55)]
67    pub hurst_enter: f64,
68    /// Hurst threshold for exiting an open position (regime decay).
69    #[builder(default = 0.50)]
70    pub hurst_exit: f64,
71    /// Number of completed volume buckets averaged for VPIN (range `[1, 16_384]`).
72    #[builder(default = 50)]
73    pub vpin_window: usize,
74    /// Minimum VPIN value required to treat a bucket imbalance as informed flow.
75    #[builder(default = 0.30)]
76    pub vpin_threshold: f64,
77    /// Maximum time (seconds) a position is held before forced flatten.
78    #[builder(default = 3600)]
79    pub max_holding_secs: u64,
80}
81
82impl HurstVpinDirectionalConfig {
83    /// Validates the rolling window sizes.
84    ///
85    /// # Errors
86    ///
87    /// Returns a [`ConfigError`] if either rolling window is outside `[1, 16_384]`.
88    pub fn validate(&self) -> ConfigResult<()> {
89        let mut errors = ConfigErrorCollector::new();
90
91        for (field, value) in [
92            ("hurst_window", self.hurst_window),
93            ("vpin_window", self.vpin_window),
94        ] {
95            errors.check(
96                (1..=MAX_HURST_VPIN_WINDOW).contains(&value),
97                ConfigError::range(
98                    field,
99                    format!("must be in range [1, {MAX_HURST_VPIN_WINDOW}], was {value}"),
100                ),
101            );
102        }
103
104        errors.into_result()
105    }
106}