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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
//! Typical Price.
use crate::ohlcv::Candle;
use crate::traits::Indicator;
/// Typical Price — the bar's `(high + low + close) / 3`.
///
/// A single representative price per bar that weights the close no more
/// heavily than the two extremes. It is the price series that
/// [`Cci`](crate::Cci) and [`Mfi`](crate::Mfi) are built on, and a common
/// input to feed other indicators in place of the raw close. As a stateless
/// per-bar transform it emits a value from the very first candle.
///
/// # Example
///
/// ```
/// use wickra_core::{Candle, Indicator, TypicalPrice};
///
/// let mut indicator = TypicalPrice::new();
/// let mut last = None;
/// for i in 0..80 {
/// let base = 100.0 + f64::from(i);
/// let candle =
/// Candle::new(base, base + 2.0, base - 2.0, base + 1.0, 10.0, i64::from(i)).unwrap();
/// last = indicator.update(candle);
/// }
/// assert!(last.is_some());
/// ```
#[derive(Debug, Clone, Default)]
pub struct TypicalPrice {
has_emitted: bool,
}
impl TypicalPrice {
/// Construct a new Typical Price transform.
pub const fn new() -> Self {
Self { has_emitted: false }
}
}
impl Indicator for TypicalPrice {
type Input = Candle;
type Output = f64;
#[inline]
fn update(&mut self, candle: Candle) -> Option<f64> {
self.has_emitted = true;
Some(candle.typical_price())
}
fn reset(&mut self) {
self.has_emitted = false;
}
#[inline]
fn warmup_period(&self) -> usize {
1
}
#[inline]
fn is_ready(&self) -> bool {
self.has_emitted
}
#[inline]
fn name(&self) -> &'static str {
"TypicalPrice"
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::BatchExt;
use approx::assert_relative_eq;
fn candle(open: f64, high: f64, low: f64, close: f64, ts: i64) -> Candle {
Candle::new(open, high, low, close, 1.0, ts).unwrap()
}
#[test]
fn reference_value() {
// (high + low + close) / 3 = (12 + 6 + 9) / 3 = 9.
let mut tp = TypicalPrice::new();
assert_relative_eq!(
tp.update(candle(9.0, 12.0, 6.0, 9.0, 0)).unwrap(),
9.0,
epsilon = 1e-12
);
}
/// Cover the Indicator-impl `name` body (62-64).
#[test]
fn name_metadata() {
let tp = TypicalPrice::new();
assert_eq!(tp.name(), "TypicalPrice");
}
#[test]
fn emits_from_first_candle() {
let mut tp = TypicalPrice::new();
assert_eq!(tp.warmup_period(), 1);
assert!(!tp.is_ready());
assert!(tp.update(candle(10.0, 11.0, 9.0, 10.0, 0)).is_some());
assert!(tp.is_ready());
}
#[test]
fn reset_clears_state() {
let mut tp = TypicalPrice::new();
tp.update(candle(10.0, 11.0, 9.0, 10.0, 0));
assert!(tp.is_ready());
tp.reset();
assert!(!tp.is_ready());
}
#[test]
fn batch_equals_streaming() {
let candles: Vec<Candle> = (0..40)
.map(|i| {
let base = 100.0 + i as f64;
candle(base, base + 2.0, base - 2.0, base + 1.0, i)
})
.collect();
let mut a = TypicalPrice::new();
let mut b = TypicalPrice::new();
assert_eq!(
a.batch(&candles),
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
);
}
}