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