Skip to main content

kestrel_chartkit/indicator/
rvi.rs

1use super::smoothing::Sma;
2use super::{Indicator, IndicatorAlert, IndicatorOutput};
3use crate::model::Bar;
4use std::collections::HashMap;
5
6/// Vigor index: the close-open span relative to the range, averaged.
7///
8/// `RVI = SMA(period)((close - open) / (high - low))`, the range floored at `1e-8`;
9/// `extra["signal"]` is `SMA(4)` of the RVI and `extra["rvi"]` repeats the value. This is a plain
10/// average, not the symmetrically weighted four-bar numerator and denominator of the usual
11/// Relative Vigor Index; on bars with `open == close` it is `0`.
12///
13/// First output: once the signal exists, i.e. with the `period + 3`-th bar. [`Indicator::reset`]
14/// clears both averages.
15#[derive(Debug, Clone)]
16pub struct RviEngine {
17    period: usize,
18    val_sma: Sma,
19    sig_sma: Sma,
20}
21
22impl RviEngine {
23    pub fn new(period: usize) -> Self {
24        Self {
25            period: period.max(1),
26            val_sma: Sma::new(period),
27            sig_sma: Sma::new(4),
28        }
29    }
30}
31
32impl Indicator for RviEngine {
33    fn name(&self) -> &str {
34        "rvi"
35    }
36
37    fn warmup_period(&self) -> usize {
38        self.period + 4
39    }
40
41    fn reset(&mut self) {
42        self.val_sma.reset();
43        self.sig_sma.reset();
44    }
45
46    fn on_bar(&mut self, bar: &Bar) -> Option<IndicatorOutput> {
47        let range = (bar.high - bar.low).max(1e-8);
48        let raw_val = (bar.close - bar.open) / range;
49
50        let rvi_val = self.val_sma.update(raw_val)?;
51        let sig_val = self.sig_sma.update(rvi_val)?;
52
53        let mut extra = HashMap::new();
54        extra.insert("rvi".to_string(), rvi_val);
55        extra.insert("signal".to_string(), sig_val);
56
57        Some(IndicatorOutput::with_extra(rvi_val, extra))
58    }
59
60    fn alerts(&self) -> Vec<IndicatorAlert> {
61        Vec::new()
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn test_rvi_basic() {
71        let mut rvi = RviEngine::new(10);
72        let mut out = None;
73        for i in 0..20 {
74            let b = Bar::new(i, 100.0, 105.0, 95.0, 104.0, 1000.0);
75            out = rvi.on_bar(&b);
76        }
77        assert!(out.is_some());
78    }
79}