use crate::error::Result;
use crate::indicators::awesome_oscillator::AwesomeOscillator;
use crate::indicators::sma::Sma;
use crate::ohlcv::Candle;
use crate::traits::Indicator;
#[derive(Debug, Clone)]
pub struct AcceleratorOscillator {
ao: AwesomeOscillator,
signal: Sma,
ao_fast: usize,
ao_slow: usize,
signal_period: usize,
}
impl AcceleratorOscillator {
pub fn new(ao_fast: usize, ao_slow: usize, signal_period: usize) -> Result<Self> {
Ok(Self {
ao: AwesomeOscillator::new(ao_fast, ao_slow)?,
signal: Sma::new(signal_period)?,
ao_fast,
ao_slow,
signal_period,
})
}
pub fn classic() -> Self {
Self::new(5, 34, 5).expect("classic Accelerator Oscillator params are valid")
}
pub const fn params(&self) -> (usize, usize, usize) {
(self.ao_fast, self.ao_slow, self.signal_period)
}
}
impl Indicator for AcceleratorOscillator {
type Input = Candle;
type Output = f64;
fn update(&mut self, candle: Candle) -> Option<f64> {
let ao = self.ao.update(candle)?;
let signal = self.signal.update(ao)?;
Some(ao - signal)
}
fn reset(&mut self) {
self.ao.reset();
self.signal.reset();
}
fn warmup_period(&self) -> usize {
self.ao_slow + self.signal_period - 1
}
fn is_ready(&self) -> bool {
self.signal.is_ready()
}
fn name(&self) -> &'static str {
"AcceleratorOscillator"
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::BatchExt;
use approx::assert_relative_eq;
fn c(high: f64, low: f64, close: f64, ts: i64) -> Candle {
Candle::new(f64::midpoint(high, low), high, low, close, 1.0, ts).unwrap()
}
#[test]
fn constant_series_yields_zero() {
let candles: Vec<Candle> = (0..80).map(|i| c(11.0, 9.0, 10.0, i)).collect();
let mut ac = AcceleratorOscillator::classic();
for v in ac.batch(&candles).into_iter().flatten() {
assert_relative_eq!(v, 0.0, epsilon = 1e-9);
}
}
#[test]
fn matches_independent_ao_and_signal() {
let candles: Vec<Candle> = (0..90)
.map(|i| {
let m = 100.0 + (i as f64 * 0.2).sin() * 6.0;
c(m + 1.5, m - 1.5, m + 0.3, i)
})
.collect();
let mut ac = AcceleratorOscillator::classic();
let mut ao = AwesomeOscillator::classic();
let mut signal = Sma::new(5).unwrap();
for (i, candle) in candles.iter().enumerate() {
let got = ac.update(*candle);
match ao.update(*candle) {
Some(ao_val) => match signal.update(ao_val) {
Some(sig) => {
assert_relative_eq!(got.unwrap(), ao_val - sig, epsilon = 1e-9);
}
None => assert!(got.is_none(), "i={i}"),
},
None => assert!(got.is_none(), "i={i}"),
}
}
}
#[test]
fn first_emission_matches_warmup_period() {
let candles: Vec<Candle> = (0..60).map(|i| c(11.0, 9.0, 10.0, i)).collect();
let mut ac = AcceleratorOscillator::classic();
let out = ac.batch(&candles);
assert_eq!(ac.warmup_period(), 38);
for (i, v) in out.iter().enumerate().take(37) {
assert!(v.is_none(), "index {i} must be None during warmup");
}
assert!(out[37].is_some(), "first value lands at warmup_period - 1");
}
#[test]
fn rejects_invalid_params() {
assert!(AcceleratorOscillator::new(0, 34, 5).is_err());
assert!(AcceleratorOscillator::new(5, 34, 0).is_err());
assert!(AcceleratorOscillator::new(34, 5, 5).is_err());
}
#[test]
fn accessors_and_metadata() {
let ac = AcceleratorOscillator::classic();
assert_eq!(ac.params(), (5, 34, 5));
assert_eq!(ac.name(), "AcceleratorOscillator");
}
#[test]
fn reset_clears_state() {
let candles: Vec<Candle> = (0..60).map(|i| c(11.0, 9.0, 10.0, i)).collect();
let mut ac = AcceleratorOscillator::classic();
ac.batch(&candles);
assert!(ac.is_ready());
ac.reset();
assert!(!ac.is_ready());
assert_eq!(ac.update(candles[0]), None);
}
#[test]
fn batch_equals_streaming() {
let candles: Vec<Candle> = (0..90)
.map(|i| {
let m = 100.0 + (i as f64 * 0.3).sin() * 8.0;
c(m + 1.5, m - 1.5, m + 0.5, i)
})
.collect();
let mut a = AcceleratorOscillator::classic();
let mut b = AcceleratorOscillator::classic();
assert_eq!(
a.batch(&candles),
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
);
}
}