1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
use crate::model::Bar;
use super::{Indicator, IndicatorOutput};
/// Price Volume Trend: volume weighted by the *relative* price change, accumulated.
///
/// `PVT_t = PVT_{t-1} + volume_t * (close_t / close_{t-1} - 1)`
///
/// The distinction to its neighbours is the weighting, and it is the whole point:
/// - **OBV** adds the full volume with the sign of the change — a one-cent move counts as much as
/// a five-percent one.
/// - **Elder's Force Index** weights by the *absolute* change, so the same percentage move counts
/// more at a higher price level.
/// - PVT weights by the relative change, which makes its steps comparable across price levels
/// within one series.
///
/// Unit: volume units. A cumulative total is only meaningful relative to itself — its level says
/// nothing without the series it was accumulated over, and comparing the level between two
/// instruments compares their volume conventions, not their flows.
///
/// The running total starts at zero on the first bar that has a predecessor. There is no output
/// on the very first bar of a series: without a previous close there is no return, and a zero
/// would claim one. [`Indicator::reset`] returns the total to zero, so a series switch starts a
/// fresh accumulation rather than carrying a level across a boundary it has no meaning over.
#[derive(Debug, Clone, Default)]
pub struct PriceVolumeTrend {
prev_close: Option<f64>,
total: f64,
}
impl PriceVolumeTrend {
pub fn new() -> Self {
Self::default()
}
}
impl Indicator for PriceVolumeTrend {
fn name(&self) -> &str {
"pvt"
}
fn warmup_period(&self) -> usize {
1
}
fn on_bar(&mut self, bar: &Bar) -> Option<IndicatorOutput> {
let prev_close = match self.prev_close {
None => {
self.prev_close = Some(bar.close);
return None;
}
Some(prev) => prev,
};
self.prev_close = Some(bar.close);
if prev_close != 0.0 {
self.total += bar.volume * (bar.close / prev_close - 1.0);
}
Some(IndicatorOutput::new(self.total))
}
fn reset(&mut self) {
self.prev_close = None;
self.total = 0.0;
}
}