wickra_core/indicators/
awesome_oscillator.rs1use crate::error::{Error, Result};
4use crate::indicators::sma::Sma;
5use crate::ohlcv::Candle;
6use crate::traits::Indicator;
7
8#[derive(Debug, Clone)]
26pub struct AwesomeOscillator {
27 fast: Sma,
28 slow: Sma,
29 fast_period: usize,
30 slow_period: usize,
31}
32
33impl AwesomeOscillator {
34 pub fn new(fast: usize, slow: usize) -> Result<Self> {
37 if fast == 0 || slow == 0 {
38 return Err(Error::PeriodZero);
39 }
40 if fast >= slow {
41 return Err(Error::InvalidPeriod {
42 message: "AO fast period must be strictly less than slow",
43 });
44 }
45 Ok(Self {
46 fast: Sma::new(fast)?,
47 slow: Sma::new(slow)?,
48 fast_period: fast,
49 slow_period: slow,
50 })
51 }
52
53 pub fn classic() -> Self {
55 Self::new(5, 34).expect("classic AO periods are valid")
56 }
57
58 pub const fn periods(&self) -> (usize, usize) {
60 (self.fast_period, self.slow_period)
61 }
62}
63
64impl Indicator for AwesomeOscillator {
65 type Input = Candle;
66 type Output = f64;
67
68 fn update(&mut self, candle: Candle) -> Option<f64> {
69 let median = candle.median_price();
70 let f = self.fast.update(median);
71 let s = self.slow.update(median);
72 match (f, s) {
73 (Some(a), Some(b)) => Some(a - b),
74 _ => None,
75 }
76 }
77
78 fn reset(&mut self) {
79 self.fast.reset();
80 self.slow.reset();
81 }
82
83 fn warmup_period(&self) -> usize {
84 self.slow_period
85 }
86
87 fn is_ready(&self) -> bool {
88 self.slow.is_ready()
89 }
90
91 fn name(&self) -> &'static str {
92 "AwesomeOscillator"
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use crate::traits::BatchExt;
100 use approx::assert_relative_eq;
101
102 fn c(h: f64, l: f64, cl: f64) -> Candle {
103 Candle::new(cl, h, l, cl, 1.0, 0).unwrap()
104 }
105
106 #[test]
107 fn constant_series_yields_zero() {
108 let candles: Vec<Candle> = (0..80).map(|_| c(11.0, 9.0, 10.0)).collect();
109 let mut ao = AwesomeOscillator::classic();
110 let last = ao.batch(&candles).into_iter().flatten().last().unwrap();
111 assert_relative_eq!(last, 0.0, epsilon = 1e-9);
112 }
113
114 #[test]
115 fn rejects_fast_geq_slow() {
116 assert!(AwesomeOscillator::new(34, 5).is_err());
117 assert!(AwesomeOscillator::new(5, 5).is_err());
118 assert!(AwesomeOscillator::new(0, 5).is_err());
119 }
120
121 #[test]
125 fn accessors_and_metadata() {
126 let ao = AwesomeOscillator::classic();
127 assert_eq!(ao.periods(), (5, 34));
128 assert_eq!(ao.warmup_period(), 34);
129 assert_eq!(ao.name(), "AwesomeOscillator");
130 }
131
132 #[test]
133 fn batch_equals_streaming() {
134 let candles: Vec<Candle> = (0..50)
135 .map(|i| c(f64::from(i) + 1.0, f64::from(i) - 1.0, f64::from(i)))
136 .collect();
137 let mut a = AwesomeOscillator::classic();
138 let mut b = AwesomeOscillator::classic();
139 assert_eq!(
140 a.batch(&candles),
141 candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
142 );
143 }
144
145 #[test]
146 fn reset_clears_state() {
147 let candles: Vec<Candle> = (0..50)
148 .map(|i| c(f64::from(i) + 1.0, f64::from(i) - 1.0, f64::from(i)))
149 .collect();
150 let mut ao = AwesomeOscillator::classic();
151 ao.batch(&candles);
152 assert!(ao.is_ready());
153 ao.reset();
154 assert!(!ao.is_ready());
155 assert_eq!(ao.update(candles[0]), None);
156 }
157}