use crate::ohlcv::Candle;
use crate::traits::Indicator;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DemarkPivotsOutput {
pub pp: f64,
pub r1: f64,
pub s1: f64,
}
#[derive(Debug, Clone, Default)]
pub struct DemarkPivots {
ready: bool,
}
impl DemarkPivots {
pub const fn new() -> Self {
Self { ready: false }
}
}
impl Indicator for DemarkPivots {
type Input = Candle;
type Output = DemarkPivotsOutput;
fn update(&mut self, candle: Candle) -> Option<DemarkPivotsOutput> {
let open = candle.open;
let high = candle.high;
let low = candle.low;
let close = candle.close;
let x = if close < open {
2.0 * high + low + close
} else if close > open {
high + 2.0 * low + close
} else {
high + low + 2.0 * close
};
let pp = x / 4.0;
let half = x / 2.0;
let out = DemarkPivotsOutput {
pp,
r1: half - low,
s1: half - high,
};
self.ready = true;
Some(out)
}
fn reset(&mut self) {
self.ready = false;
}
fn warmup_period(&self) -> usize {
1
}
fn is_ready(&self) -> bool {
self.ready
}
fn name(&self) -> &'static str {
"DemarkPivots"
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::BatchExt;
#[test]
fn down_bar_uses_2h_plus_l_plus_c() {
let cd = Candle::new(110.0, 120.0, 80.0, 100.0, 1.0, 0).unwrap();
let lv = DemarkPivots::new().update(cd).unwrap();
assert!((lv.pp - 105.0).abs() < 1e-12);
assert!((lv.r1 - (210.0 - 80.0)).abs() < 1e-12);
assert!((lv.s1 - (210.0 - 120.0)).abs() < 1e-12);
}
#[test]
fn up_bar_uses_h_plus_2l_plus_c() {
let cd = Candle::new(100.0, 120.0, 80.0, 110.0, 1.0, 0).unwrap();
let lv = DemarkPivots::new().update(cd).unwrap();
assert!((lv.pp - 97.5).abs() < 1e-12);
assert!((lv.r1 - (195.0 - 80.0)).abs() < 1e-12);
assert!((lv.s1 - (195.0 - 120.0)).abs() < 1e-12);
}
#[test]
fn doji_uses_h_plus_l_plus_2c() {
let cd = Candle::new(100.0, 120.0, 80.0, 100.0, 1.0, 0).unwrap();
let lv = DemarkPivots::new().update(cd).unwrap();
assert!((lv.pp - 100.0).abs() < 1e-12);
}
#[test]
fn ordering_resistance_above_pivot_above_support() {
let cd = Candle::new(100.0, 120.0, 80.0, 110.0, 1.0, 0).unwrap();
let lv = DemarkPivots::new().update(cd).unwrap();
assert!(lv.r1 >= lv.pp);
assert!(lv.pp >= lv.s1);
}
#[test]
fn constant_series_collapses_levels() {
let cd = Candle::new(50.0, 50.0, 50.0, 50.0, 1.0, 0).unwrap();
let lv = DemarkPivots::new().update(cd).unwrap();
assert_eq!(lv.pp, 50.0);
assert_eq!(lv.r1, 50.0);
assert_eq!(lv.s1, 50.0);
}
#[test]
fn warmup_and_ready() {
let mut p = DemarkPivots::new();
assert!(!p.is_ready());
assert_eq!(p.warmup_period(), 1);
let cd = Candle::new(10.0, 11.0, 9.0, 10.0, 1.0, 0).unwrap();
p.update(cd);
assert!(p.is_ready());
}
#[test]
fn reset_clears_state() {
let mut p = DemarkPivots::new();
let cd = Candle::new(10.0, 11.0, 9.0, 10.0, 1.0, 0).unwrap();
p.update(cd);
p.reset();
assert!(!p.is_ready());
}
#[test]
fn batch_equals_streaming() {
let candles: Vec<Candle> = (0..40)
.map(|i| {
let base = f64::from(i);
Candle::new(base, base + 2.0, base - 0.5, base + 1.0, 1.0, i64::from(i)).unwrap()
})
.collect();
let mut a = DemarkPivots::new();
let mut b = DemarkPivots::new();
assert_eq!(
a.batch(&candles),
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
);
}
#[test]
fn accessors_and_metadata() {
let p = DemarkPivots::new();
assert_eq!(p.warmup_period(), 1);
assert_eq!(p.name(), "DemarkPivots");
}
}