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}