kestrel_chartkit/indicator/
rvi.rs1use super::smoothing::Sma;
2use super::{Indicator, IndicatorAlert, IndicatorOutput};
3use crate::model::Bar;
4use std::collections::HashMap;
5
6#[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}