kestrel_chartkit/indicator/
vix_fix.rs1use std::collections::HashMap;
2
3use crate::indicator::smoothing::{ExtremeWindow, Sma};
4use crate::indicator::{Indicator, IndicatorAlert, IndicatorOutput};
5use crate::model::Bar;
6
7pub struct WilliamsVixFix {
10 #[allow(dead_code)]
11 pd: usize,
12 bband_len: usize,
13 mult: f64,
14
15 close_window: ExtremeWindow,
16 wvf_window: Vec<f64>,
17 sma: Sma,
18 alerts: Vec<IndicatorAlert>,
19}
20
21impl WilliamsVixFix {
22 pub fn new(pd: usize, bband_len: usize, mult: f64) -> Self {
23 Self {
24 pd,
25 bband_len,
26 mult,
27 close_window: ExtremeWindow::new(pd),
28 wvf_window: Vec::with_capacity(bband_len),
29 sma: Sma::new(bband_len),
30 alerts: Vec::new(),
31 }
32 }
33}
34
35impl Indicator for WilliamsVixFix {
36 fn name(&self) -> &str {
37 "vix_fix"
38 }
39
40 fn reset(&mut self) {
41 self.close_window.reset();
42 self.wvf_window.clear();
43 self.sma.reset();
44 self.alerts.clear();
45 }
46
47 fn on_bar(&mut self, bar: &Bar) -> Option<IndicatorOutput> {
48 let highest_close = self
49 .close_window
50 .push(bar.close)
51 .map(|(_, high)| high)
52 .unwrap_or(bar.close);
53 self.alerts.clear();
54
55 let wvf = if highest_close > 0.0 {
56 ((highest_close - bar.low) / highest_close) * 100.0
57 } else {
58 0.0
59 };
60
61 let sma_val = self.sma.update(wvf)?;
62
63 self.wvf_window.push(wvf);
64 if self.wvf_window.len() > self.bband_len {
65 self.wvf_window.remove(0);
66 }
67
68 if self.wvf_window.len() < self.bband_len {
69 return None;
70 }
71
72 let mean = sma_val;
73 let variance = self
74 .wvf_window
75 .iter()
76 .map(|v| (v - mean).powi(2))
77 .sum::<f64>()
78 / self.bband_len as f64;
79 let std_dev = variance.sqrt();
80
81 let upper_band = mean + self.mult * std_dev;
82 let is_spike = wvf >= upper_band;
83
84 if is_spike {
85 self.alerts.push(IndicatorAlert::new(
86 "panic_bottom",
87 format!(
88 "Williams VIX Fix Spike ({:.2} >= Upper Band {:.2}) - Market Bottom Zone",
89 wvf, upper_band
90 ),
91 1.0,
92 ));
93 }
94
95 Some(IndicatorOutput::new(wvf).with_secondary(upper_band))
96 }
97
98 fn alerts(&self) -> Vec<IndicatorAlert> {
99 self.alerts.clone()
100 }
101}
102
103pub fn build_vix_fix(params: &HashMap<String, f64>) -> WilliamsVixFix {
104 let pd = params.get("pd").copied().unwrap_or(22.0) as usize;
105 let bband_len = params.get("bband_len").copied().unwrap_or(20.0) as usize;
106 let mult = params.get("mult").copied().unwrap_or(2.0);
107 WilliamsVixFix::new(pd, bband_len, mult)
108}