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)]
9pub struct RviEngine {
10 period: usize,
11 val_sma: Sma,
12 sig_sma: Sma,
13}
14
15impl RviEngine {
16 pub fn new(period: usize) -> Self {
17 Self {
18 period: period.max(1),
19 val_sma: Sma::new(period),
20 sig_sma: Sma::new(4),
21 }
22 }
23}
24
25impl Indicator for RviEngine {
26 fn name(&self) -> &str {
27 "rvi"
28 }
29
30 fn warmup_period(&self) -> usize {
31 self.period + 4
32 }
33
34 fn reset(&mut self) {
35 self.val_sma.reset();
36 self.sig_sma.reset();
37 }
38
39 fn on_bar(&mut self, bar: &Bar) -> Option<IndicatorOutput> {
40 let range = (bar.high - bar.low).max(1e-8);
41 let raw_val = (bar.close - bar.open) / range;
42
43 let rvi_val = self.val_sma.update(raw_val)?;
44 let sig_val = self.sig_sma.update(rvi_val)?;
45
46 let mut extra = HashMap::new();
47 extra.insert("rvi".to_string(), rvi_val);
48 extra.insert("signal".to_string(), sig_val);
49
50 Some(IndicatorOutput::with_extra(rvi_val, extra))
51 }
52
53 fn alerts(&self) -> Vec<IndicatorAlert> {
54 Vec::new()
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61
62 #[test]
63 fn test_rvi_basic() {
64 let mut rvi = RviEngine::new(10);
65 let mut out = None;
66 for i in 0..20 {
67 let b = Bar::new(i, 100.0, 105.0, 95.0, 104.0, 1000.0);
68 out = rvi.on_bar(&b);
69 }
70 assert!(out.is_some());
71 }
72}