use std::collections::VecDeque;
use crate::bar_indicators::indicator_value::IndicatorValue;
use crate::bar_indicators::HistoricalVolatilityConsumer;
use crate::core::types::HistoricalVolatility;
#[derive(Clone)]
pub struct HvSpike {
period: usize,
multiplier: f64,
history: VecDeque<f64>,
last_signal: i8,
}
impl HvSpike {
pub fn new(period: usize, multiplier: f64) -> Self {
let period = period.max(2);
Self {
period,
multiplier,
history: VecDeque::with_capacity(period),
last_signal: 0,
}
}
fn compute_signal(&self, current: f64) -> i8 {
let n = self.history.len();
if n < 2 {
return 0;
}
let mean = self.history.iter().sum::<f64>() / n as f64;
if mean > 1e-12 && current > self.multiplier * mean {
1
} else {
0
}
}
}
impl Default for HvSpike {
fn default() -> Self {
Self::new(20, 2.0)
}
}
impl HistoricalVolatilityConsumer for HvSpike {
fn update_historical_volatility(&mut self, hv: &HistoricalVolatility) -> IndicatorValue {
let current = hv.volatility;
self.history.push_back(current);
while self.history.len() > self.period {
self.history.pop_front();
}
self.last_signal = self.compute_signal(current);
IndicatorValue::Signal(self.last_signal)
}
fn value(&self) -> IndicatorValue {
IndicatorValue::Signal(self.last_signal)
}
fn reset(&mut self) {
self.history.clear();
self.last_signal = 0;
}
fn is_ready(&self) -> bool {
self.history.len() >= 2
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_hv(v: f64) -> HistoricalVolatility {
HistoricalVolatility { volatility: v, timestamp: 0 }
}
#[test]
fn spike_detected_above_threshold() {
let mut ind = HvSpike::new(5, 2.0);
for _ in 0..5 {
ind.update_historical_volatility(&make_hv(0.1));
}
let v = ind.update_historical_volatility(&make_hv(0.5));
if let IndicatorValue::Signal(s) = v {
assert_eq!(s, 1, "should detect spike");
} else {
panic!("expected Signal");
}
}
#[test]
fn no_spike_below_threshold() {
let mut ind = HvSpike::new(5, 2.0);
for _ in 0..5 {
ind.update_historical_volatility(&make_hv(0.1));
}
let v = ind.update_historical_volatility(&make_hv(0.15));
if let IndicatorValue::Signal(s) = v {
assert_eq!(s, 0, "should not detect spike");
} else {
panic!("expected Signal");
}
}
#[test]
fn reset_clears() {
let mut ind = HvSpike::new(5, 2.0);
for _ in 0..5 {
ind.update_historical_volatility(&make_hv(0.1));
}
ind.reset();
assert!(!ind.is_ready());
if let IndicatorValue::Signal(s) = ind.value() {
assert_eq!(s, 0);
}
}
}