use crate::indicators::ht_dcphase::HtDcPhase;
use crate::traits::Indicator;
#[derive(Debug, Clone, Default)]
pub struct SineWave {
phase: HtDcPhase,
last_sine: Option<f64>,
last_lead: f64,
}
impl SineWave {
pub fn new() -> Self {
Self::default()
}
pub const fn lead(&self) -> f64 {
self.last_lead
}
pub const fn value(&self) -> Option<f64> {
self.last_sine
}
}
impl Indicator for SineWave {
type Input = f64;
type Output = f64;
fn update(&mut self, input: f64) -> Option<f64> {
if !input.is_finite() {
return None;
}
let phase = self.phase.update(input)?.to_radians();
let sine = phase.sin();
self.last_lead = (phase + 45f64.to_radians()).sin();
self.last_sine = Some(sine);
Some(sine)
}
fn reset(&mut self) {
self.phase.reset();
self.last_sine = None;
self.last_lead = 0.0;
}
#[inline]
fn warmup_period(&self) -> usize {
self.phase.warmup_period()
}
#[inline]
fn is_ready(&self) -> bool {
self.last_sine.is_some()
}
#[inline]
fn name(&self) -> &'static str {
"SineWave"
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::BatchExt;
#[test]
fn accessors_and_metadata() {
let mut sw = SineWave::new();
assert_eq!(sw.warmup_period(), 50);
assert_eq!(sw.name(), "SineWave");
assert!(!sw.is_ready());
assert!(sw.value().is_none());
let prices: Vec<f64> = (0..120)
.map(|i| 100.0 + (f64::from(i) * 0.4).sin() * 5.0)
.collect();
sw.batch(&prices);
assert!(sw.is_ready());
assert!(sw.value().is_some());
}
#[test]
fn output_bounded() {
let prices: Vec<f64> = (0..200)
.map(|i| 100.0 + (f64::from(i) * 0.3).cos() * 5.0)
.collect();
let mut sw = SineWave::new();
for v in sw.batch(&prices).into_iter().flatten() {
assert!((-1.0..=1.0).contains(&v), "sine out of bounds: {v}");
}
assert!(sw.lead() >= -1.0 && sw.lead() <= 1.0);
}
#[test]
fn batch_equals_streaming() {
let prices: Vec<f64> = (0..200)
.map(|i| 100.0 + (f64::from(i) * 0.3).sin() * 5.0)
.collect();
let mut a = SineWave::new();
let mut b = SineWave::new();
let batch = a.batch(&prices);
let streamed: Vec<_> = prices.iter().map(|p| b.update(*p)).collect();
assert_eq!(batch, streamed);
}
#[test]
fn ignores_non_finite_input() {
let mut sw = SineWave::new();
let prices: Vec<f64> = (0..120)
.map(|i| 100.0 + (f64::from(i) * 0.4).sin() * 5.0)
.collect();
sw.batch(&prices);
let before = sw.value();
assert!(before.is_some());
assert_eq!(sw.update(f64::NAN), None);
}
#[test]
fn reset_clears_state() {
let mut sw = SineWave::new();
let prices: Vec<f64> = (0..120)
.map(|i| 100.0 + (f64::from(i) * 0.4).sin() * 5.0)
.collect();
sw.batch(&prices);
assert!(sw.is_ready());
sw.reset();
assert!(!sw.is_ready());
assert!(sw.value().is_none());
}
#[test]
fn flat_input_uses_phase_fallback() {
let mut sw = SineWave::new();
let _ = sw.batch(&[0.0_f64; 120]);
assert!(sw.value().is_some());
}
use crate::traits::BatchNanExt;
use approx::assert_relative_eq;
fn sine_prices(n: u32) -> Vec<f64> {
(0..n)
.map(|i| 100.0 + (f64::from(i) * 0.4).sin() * 5.0)
.collect()
}
#[test]
fn first_value_lands_exactly_at_warmup() {
let mut sw = SineWave::new();
let out = sw.batch(&sine_prices(120));
let warmup = sw.warmup_period();
assert!(out[..warmup - 1].iter().all(Option::is_none));
assert!(out[warmup - 1].is_some());
}
#[test]
fn reset_replays_identically() {
let prices = sine_prices(150);
let fresh = SineWave::new().batch(&prices);
let mut sw = SineWave::new();
let first = sw.batch(&prices);
sw.reset();
assert_eq!(sw.lead().to_bits(), 0.0_f64.to_bits());
let second = sw.batch(&prices);
assert_eq!(first, fresh);
assert_eq!(second, fresh);
}
#[test]
fn batch_nan_paths_match_streaming_bitwise() {
let prices = sine_prices(150);
let mut out = vec![0.0; prices.len()];
SineWave::new().batch_nan_into(&prices, &mut out);
let nan = SineWave::new().batch_nan(&prices);
let fast = SineWave::new().batch_fast(&prices);
let mut stream = SineWave::new();
let expected: Vec<u64> = prices
.iter()
.map(|&p| stream.update(p).unwrap_or(f64::NAN).to_bits())
.collect();
assert!(out.iter().zip(&expected).all(|(v, e)| v.to_bits() == *e));
assert!(nan.iter().zip(&expected).all(|(v, e)| v.to_bits() == *e));
assert!(fast.iter().zip(&expected).all(|(v, e)| v.to_bits() == *e));
}
#[test]
fn sine_and_lead_are_functions_of_dc_phase() {
let prices = sine_prices(150);
let mut sw = SineWave::new();
let mut phase = HtDcPhase::new();
for &p in &prices {
let sine = sw.update(p);
let ph = phase.update(p);
assert_eq!(sine.is_some(), ph.is_some());
if let (Some(s), Some(deg)) = (sine, ph) {
let rad = deg.to_radians();
assert_eq!(s.to_bits(), rad.sin().to_bits());
assert_eq!(
sw.lead().to_bits(),
(rad + 45f64.to_radians()).sin().to_bits()
);
}
}
}
#[test]
fn zero_series_hand_computed() {
let mut sw = SineWave::new();
let _ = sw.batch(&[0.0; 400]);
assert_relative_eq!(sw.value().unwrap(), -(3.0_f64.sqrt()) / 2.0, epsilon = 1e-6);
assert_relative_eq!(
sw.lead(),
-(6.0_f64.sqrt() + 2.0_f64.sqrt()) / 4.0,
epsilon = 1e-6
);
}
}