kestrel_chartkit/indicator/
nvi_pvi.rs1use super::{Indicator, IndicatorAlert, IndicatorOutput};
2use crate::model::Bar;
3
4#[derive(Debug, Clone)]
12pub struct NviEngine {
13 nvi: f64,
14 prev_bar: Option<Bar>,
15}
16
17impl NviEngine {
18 pub fn new() -> Self {
19 Self {
20 nvi: 1000.0,
21 prev_bar: None,
22 }
23 }
24}
25
26impl Default for NviEngine {
27 fn default() -> Self {
28 Self::new()
29 }
30}
31
32impl Indicator for NviEngine {
33 fn name(&self) -> &str {
34 "nvi"
35 }
36
37 fn warmup_period(&self) -> usize {
38 1
39 }
40
41 fn reset(&mut self) {
42 self.nvi = 1000.0;
43 self.prev_bar = None;
44 }
45
46 fn on_bar(&mut self, bar: &Bar) -> Option<IndicatorOutput> {
47 let prev = match &self.prev_bar {
48 Some(p) => p.clone(),
49 None => {
50 self.prev_bar = Some(bar.clone());
51 return Some(IndicatorOutput::new(self.nvi));
52 }
53 };
54 self.prev_bar = Some(bar.clone());
55
56 if bar.volume < prev.volume {
57 let roc = if prev.close > 0.0 {
58 (bar.close - prev.close) / prev.close
59 } else {
60 0.0
61 };
62 self.nvi += self.nvi * roc;
63 }
64
65 Some(IndicatorOutput::new(self.nvi))
66 }
67
68 fn alerts(&self) -> Vec<IndicatorAlert> {
69 Vec::new()
70 }
71}
72
73#[derive(Debug, Clone)]
81pub struct PviEngine {
82 pvi: f64,
83 prev_bar: Option<Bar>,
84}
85
86impl PviEngine {
87 pub fn new() -> Self {
88 Self {
89 pvi: 1000.0,
90 prev_bar: None,
91 }
92 }
93}
94
95impl Default for PviEngine {
96 fn default() -> Self {
97 Self::new()
98 }
99}
100
101impl Indicator for PviEngine {
102 fn name(&self) -> &str {
103 "pvi"
104 }
105
106 fn warmup_period(&self) -> usize {
107 1
108 }
109
110 fn reset(&mut self) {
111 self.pvi = 1000.0;
112 self.prev_bar = None;
113 }
114
115 fn on_bar(&mut self, bar: &Bar) -> Option<IndicatorOutput> {
116 let prev = match &self.prev_bar {
117 Some(p) => p.clone(),
118 None => {
119 self.prev_bar = Some(bar.clone());
120 return Some(IndicatorOutput::new(self.pvi));
121 }
122 };
123 self.prev_bar = Some(bar.clone());
124
125 if bar.volume > prev.volume {
126 let roc = if prev.close > 0.0 {
127 (bar.close - prev.close) / prev.close
128 } else {
129 0.0
130 };
131 self.pvi += self.pvi * roc;
132 }
133
134 Some(IndicatorOutput::new(self.pvi))
135 }
136
137 fn alerts(&self) -> Vec<IndicatorAlert> {
138 Vec::new()
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn test_nvi_pvi() {
148 let mut nvi = NviEngine::new();
149 let mut pvi = PviEngine::new();
150
151 let b1 = Bar::new(1, 100.0, 105.0, 95.0, 100.0, 1000.0);
152 let b2 = Bar::new(2, 100.0, 105.0, 95.0, 105.0, 500.0); nvi.on_bar(&b1);
155 pvi.on_bar(&b1);
156
157 let out_nvi = nvi.on_bar(&b2).unwrap();
158 let out_pvi = pvi.on_bar(&b2).unwrap();
159
160 assert_eq!(out_nvi.value, 1050.0);
161 assert_eq!(out_pvi.value, 1000.0);
162 }
163}