use crate::indicators::pattern_swing::{SwingTracker, SWING_THRESHOLD};
use crate::ohlcv::Candle;
use crate::traits::Indicator;
const RATIOS: [f64; 7] = [0.0, 0.236, 0.382, 0.5, 0.618, 0.786, 1.0];
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FibRetracementOutput {
pub level_0: f64,
pub level_236: f64,
pub level_382: f64,
pub level_500: f64,
pub level_618: f64,
pub level_786: f64,
pub level_1000: f64,
}
#[derive(Debug, Clone)]
pub struct FibRetracement {
swing: SwingTracker,
}
impl FibRetracement {
#[must_use]
pub const fn new() -> Self {
Self {
swing: SwingTracker::new(SWING_THRESHOLD, 2),
}
}
fn level(start: f64, end: f64, r: f64) -> f64 {
end + r * (start - end)
}
fn levels(&self) -> Option<FibRetracementOutput> {
let pivots = self.swing.pivots();
let [start, end] = [pivots.first()?.price, pivots.get(1)?.price];
Some(FibRetracementOutput {
level_0: Self::level(start, end, RATIOS[0]),
level_236: Self::level(start, end, RATIOS[1]),
level_382: Self::level(start, end, RATIOS[2]),
level_500: Self::level(start, end, RATIOS[3]),
level_618: Self::level(start, end, RATIOS[4]),
level_786: Self::level(start, end, RATIOS[5]),
level_1000: Self::level(start, end, RATIOS[6]),
})
}
}
impl Default for FibRetracement {
fn default() -> Self {
Self::new()
}
}
impl Indicator for FibRetracement {
type Input = Candle;
type Output = FibRetracementOutput;
fn update(&mut self, candle: Candle) -> Option<FibRetracementOutput> {
self.swing.update(candle);
self.levels()
}
fn reset(&mut self) {
self.swing.reset();
}
fn warmup_period(&self) -> usize {
2
}
fn is_ready(&self) -> bool {
self.swing.pivots().len() >= 2
}
fn name(&self) -> &'static str {
"FibRetracement"
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::indicators::pattern_swing::candles_for_pivots;
use crate::traits::BatchExt;
use approx::assert_relative_eq;
#[test]
fn accessors_and_metadata() {
let indicator = FibRetracement::new();
assert_eq!(indicator.name(), "FibRetracement");
assert_eq!(indicator.warmup_period(), 2);
assert!(!indicator.is_ready());
assert!(!FibRetracement::default().is_ready());
}
#[test]
fn no_output_before_two_pivots() {
let mut indicator = FibRetracement::new();
let candles = candles_for_pivots(&[120.0]);
let outputs: Vec<_> = candles.into_iter().map(|c| indicator.update(c)).collect();
assert!(outputs.iter().all(Option::is_none));
assert!(!indicator.is_ready());
}
#[test]
fn retracement_levels_of_a_down_leg() {
let mut indicator = FibRetracement::new();
let mut last = None;
for candle in candles_for_pivots(&[200.0, 100.0]) {
last = indicator.update(candle);
}
let v = last.unwrap();
assert!(indicator.is_ready());
assert_relative_eq!(v.level_0, 100.0);
assert_relative_eq!(v.level_1000, 200.0);
assert_relative_eq!(v.level_618, 161.8);
assert_relative_eq!(v.level_500, 150.0);
assert_relative_eq!(v.level_382, 138.2);
assert_relative_eq!(v.level_236, 123.6);
assert_relative_eq!(v.level_786, 178.6);
}
#[test]
fn levels_refresh_on_a_new_leg() {
let mut indicator = FibRetracement::new();
let mut last = None;
for candle in candles_for_pivots(&[200.0, 100.0, 130.0, 90.0]) {
last = indicator.update(candle);
}
let v = last.unwrap();
assert_relative_eq!(v.level_0, 90.0);
assert_relative_eq!(v.level_1000, 130.0);
assert_relative_eq!(v.level_618, 90.0 + 0.618 * 40.0);
}
#[test]
fn reset_clears_state() {
let mut indicator = FibRetracement::new();
for candle in candles_for_pivots(&[200.0, 100.0]) {
let _ = indicator.update(candle);
}
assert!(indicator.is_ready());
indicator.reset();
assert!(!indicator.is_ready());
let c = Candle::new(99.5, 100.0, 99.5, 99.5, 1.0, 0).unwrap();
assert!(indicator.update(c).is_none());
}
#[test]
fn batch_equals_streaming() {
let candles = candles_for_pivots(&[200.0, 100.0, 150.0]);
let mut a = FibRetracement::new();
let mut b = FibRetracement::new();
assert_eq!(
a.batch(&candles),
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
);
}
}