Skip to main content

indicators/
indicator_config.rs

1//! `IndicatorConfig` — the indicator-math subset of `BotSettings`.
2//!
3//! Pure configuration: no I/O, no runtime, no exchange types.
4//! This struct is what `compute_signal` and all indicator constructors need.
5//! `kucoin-futures` embeds this inside `BotSettings` and passes it through.
6//!
7//! # Usage (kucoin-futures side)
8//! ```rust,ignore
9//! use indicators::IndicatorConfig;
10//! use serde::{Deserialize, Serialize};
11//!
12//! #[derive(Serialize, Deserialize)]
13//! pub struct BotSettings {
14//!     pub symbol: String,
15//!     pub leverage: u32,
16//!     // ...runtime fields...
17//!     #[serde(flatten)]
18//!     pub indicator: IndicatorConfig,
19//! }
20//! ```
21
22use serde::{Deserialize, Serialize};
23
24/// All tunable parameters that live inside indicators and `compute_signal`.
25/// Every field maps 1-to-1 to a key in the Python `SETTINGS` dict so
26/// Optuna-tuned JSON files load with zero field renaming.
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct IndicatorConfig {
29    // ── Engine Buffer ────────────────────────────────────────────────────────
30    /// Candle buffer capacity
31    pub history_candles: usize,
32
33    // ── Layer 1-2: VWAP & EMA ────────────────────────────────────────────────
34    pub ema_len: usize,
35
36    // ── Layer 3: ML SuperTrend ───────────────────────────────────────────────
37    pub atr_len: usize,
38    pub st_factor: f64,
39    pub training_period: usize,
40    pub highvol_pct: f64,
41    pub midvol_pct: f64,
42    pub lowvol_pct: f64,
43
44    // ── Layer 4: Trend Speed ─────────────────────────────────────────────────
45    pub ts_max_length: usize,
46    pub ts_accel_mult: f64,
47    pub ts_rma_len: usize,
48    pub ts_hma_len: usize,
49    pub ts_collen: usize,
50    pub ts_lookback: usize,
51    pub ts_speed_exit_threshold: Option<f64>,
52
53    // ── Layer 5: Liquidity Profile ───────────────────────────────────────────
54    pub liq_period: usize,
55    pub liq_bins: usize,
56
57    // ── Layer 6: Confluence ──────────────────────────────────────────────────
58    pub conf_ema_fast: usize,
59    pub conf_ema_slow: usize,
60    pub conf_ema_trend: usize,
61    pub conf_rsi_len: usize,
62    pub conf_adx_len: usize,
63    pub conf_min_score: f64,
64
65    // ── Layer 7: Market Structure + Fibonacci ────────────────────────────────
66    pub struct_swing_len: usize,
67    pub struct_atr_mult: f64,
68    pub fib_zone_enabled: bool,
69
70    // ── Signal mode ──────────────────────────────────────────────────────────
71    /// `"majority"` | `"strict"` | `"any"`
72    pub signal_mode: String,
73    pub signal_confirm_bars: usize,
74
75    // ── Layer 8: CVD ─────────────────────────────────────────────────────────
76    pub cvd_slope_bars: usize,
77    pub cvd_div_lookback: usize,
78
79    // ── Layer 9: AO + Percentile gates ──────────────────────────────────────
80    pub wave_pct_l: f64,
81    pub wave_pct_s: f64,
82    pub mom_pct_min: f64,
83    pub vol_pct_window: usize,
84
85    // ── Layer 10: Hurst ──────────────────────────────────────────────────────
86    pub hurst_threshold: f64,
87    pub hurst_lookback: usize,
88
89    // ── Layer 11: Price Acceleration / Stop ──────────────────────────────────
90    pub stop_atr_mult: f64,
91
92    // ── Entry gates ──────────────────────────────────────────────────────────
93    pub min_vol_pct: f64,
94    pub min_hold_candles: usize,
95}
96
97impl Default for IndicatorConfig {
98    fn default() -> Self {
99        Self {
100            history_candles: 200,
101            ema_len: 9,
102            atr_len: 10,
103            st_factor: 3.0,
104            training_period: 100,
105            highvol_pct: 0.75,
106            midvol_pct: 0.50,
107            lowvol_pct: 0.25,
108            ts_max_length: 50,
109            ts_accel_mult: 5.0,
110            ts_rma_len: 10,
111            ts_hma_len: 5,
112            ts_collen: 100,
113            ts_lookback: 50,
114            ts_speed_exit_threshold: None,
115            liq_period: 100,
116            liq_bins: 31,
117            conf_ema_fast: 9,
118            conf_ema_slow: 21,
119            conf_ema_trend: 55,
120            conf_rsi_len: 13,
121            conf_adx_len: 14,
122            conf_min_score: 5.0,
123            struct_swing_len: 10,
124            struct_atr_mult: 0.5,
125            fib_zone_enabled: true,
126            signal_mode: "majority".into(),
127            signal_confirm_bars: 2,
128            cvd_slope_bars: 10,
129            cvd_div_lookback: 30,
130            wave_pct_l: 0.25,
131            wave_pct_s: 0.75,
132            mom_pct_min: 0.30,
133            vol_pct_window: 200,
134            hurst_threshold: 0.52,
135            hurst_lookback: 20,
136            stop_atr_mult: 1.5,
137            min_vol_pct: 0.20,
138            min_hold_candles: 2,
139        }
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    #[test]
148    fn default_signal_mode_is_majority() {
149        assert_eq!(IndicatorConfig::default().signal_mode, "majority");
150    }
151
152    #[test]
153    fn serde_round_trip() {
154        let cfg = IndicatorConfig::default();
155        let json = serde_json::to_string(&cfg).unwrap();
156        let back: IndicatorConfig = serde_json::from_str(&json).unwrap();
157        assert_eq!(back.ema_len, cfg.ema_len);
158        assert_eq!(back.signal_mode, cfg.signal_mode);
159    }
160}