1use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
15#[serde(default)]
16pub struct SignalEngineConfig {
17 pub kmeans_recompute_bars: usize,
19 pub kmeans_max_iters: usize,
21 pub hurst_recompute_bars: usize,
23}
24
25impl Default for SignalEngineConfig {
26 fn default() -> Self {
27 Self {
28 kmeans_recompute_bars: 10,
29 kmeans_max_iters: 100,
30 hurst_recompute_bars: 10,
31 }
32 }
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct IndicatorConfig {
42 pub history_candles: usize,
45
46 pub ema_len: usize,
48
49 pub atr_len: usize,
51 pub st_factor: f64,
52 pub training_period: usize,
53 pub highvol_pct: f64,
54 pub midvol_pct: f64,
55 pub lowvol_pct: f64,
56
57 pub ts_max_length: usize,
59 pub ts_accel_mult: f64,
60 pub ts_rma_len: usize,
61 pub ts_hma_len: usize,
62 pub ts_collen: usize,
63 pub ts_lookback: usize,
64 pub ts_speed_exit_threshold: Option<f64>,
65
66 pub liq_period: usize,
68 pub liq_bins: usize,
69
70 pub conf_ema_fast: usize,
72 pub conf_ema_slow: usize,
73 pub conf_ema_trend: usize,
74 pub conf_rsi_len: usize,
75 pub conf_adx_len: usize,
76 pub conf_min_score: f64,
77
78 pub struct_swing_len: usize,
80 pub struct_atr_mult: f64,
81 pub fib_zone_enabled: bool,
82
83 pub signal_mode: String,
86 pub signal_confirm_bars: usize,
87
88 pub cvd_slope_bars: usize,
90 pub cvd_div_lookback: usize,
91
92 pub wave_pct_l: f64,
94 pub wave_pct_s: f64,
95 pub mom_pct_min: f64,
96 pub vol_pct_window: usize,
97
98 pub hurst_threshold: f64,
100 pub hurst_lookback: usize,
101
102 pub stop_atr_mult: f64,
104
105 pub min_vol_pct: f64,
107 pub min_hold_candles: usize,
108
109 #[serde(default)]
111 pub engine: SignalEngineConfig,
112}
113
114impl Default for IndicatorConfig {
115 fn default() -> Self {
116 Self {
117 history_candles: 200,
118 ema_len: 9,
119 atr_len: 10,
120 st_factor: 3.0,
121 training_period: 100,
122 highvol_pct: 0.75,
123 midvol_pct: 0.50,
124 lowvol_pct: 0.25,
125 ts_max_length: 50,
126 ts_accel_mult: 5.0,
127 ts_rma_len: 10,
128 ts_hma_len: 5,
129 ts_collen: 100,
130 ts_lookback: 50,
131 ts_speed_exit_threshold: None,
132 liq_period: 100,
133 liq_bins: 31,
134 conf_ema_fast: 9,
135 conf_ema_slow: 21,
136 conf_ema_trend: 55,
137 conf_rsi_len: 13,
138 conf_adx_len: 14,
139 conf_min_score: 5.0,
140 struct_swing_len: 10,
141 struct_atr_mult: 0.5,
142 fib_zone_enabled: true,
143 signal_mode: "majority".into(),
144 signal_confirm_bars: 2,
145 cvd_slope_bars: 10,
146 cvd_div_lookback: 30,
147 wave_pct_l: 0.25,
148 wave_pct_s: 0.75,
149 mom_pct_min: 0.30,
150 vol_pct_window: 200,
151 hurst_threshold: 0.52,
152 hurst_lookback: 20,
153 stop_atr_mult: 1.5,
154 min_vol_pct: 0.20,
155 min_hold_candles: 2,
156 engine: SignalEngineConfig::default(),
157 }
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn default_signal_mode_is_majority() {
167 assert_eq!(IndicatorConfig::default().signal_mode, "majority");
168 }
169
170 #[test]
171 fn json_without_engine_block_still_loads() {
172 let mut json = serde_json::to_value(IndicatorConfig::default()).unwrap();
174 json.as_object_mut().unwrap().remove("engine");
175 let cfg: IndicatorConfig = serde_json::from_value(json).unwrap();
176 assert_eq!(cfg.engine.kmeans_recompute_bars, 10);
177 assert_eq!(cfg.engine.kmeans_max_iters, 100);
178 assert_eq!(cfg.engine.hurst_recompute_bars, 10);
179 }
180
181 #[test]
182 fn serde_round_trip() {
183 let cfg = IndicatorConfig::default();
184 let json = serde_json::to_string(&cfg).unwrap();
185 let back: IndicatorConfig = serde_json::from_str(&json).unwrap();
186 assert_eq!(back.ema_len, cfg.ema_len);
187 assert_eq!(back.signal_mode, cfg.signal_mode);
188 }
189}