use rust_decimal_macros::dec;
use crate::detrended::DetrendedOscillator;
use crate::indicator::Indicator;
fn make_candle(close: rust_decimal::Decimal, idx: i64) -> quant_primitives::Candle {
use chrono::{DateTime, Duration, Utc};
let base: DateTime<Utc> = "2024-01-01T00:00:00Z"
.parse()
.expect("valid datetime literal");
let ts = base + Duration::days(idx);
quant_primitives::Candle::new(close, close, close, close, dec!(1000), ts).expect("valid candle")
}
fn flat_candles(price: rust_decimal::Decimal, n: usize) -> Vec<quant_primitives::Candle> {
(0..n).map(|i| make_candle(price, i as i64)).collect()
}
#[test]
fn new_rejects_invalid_hma_period() {
let err = DetrendedOscillator::new(0, 14);
assert!(err.is_err(), "period=0 should be rejected");
}
#[test]
fn new_rejects_invalid_atr_period() {
let err = DetrendedOscillator::new(21, 0);
assert!(err.is_err(), "atr_period=0 should be rejected");
}
#[test]
fn insufficient_data_returns_error() {
let osc = DetrendedOscillator::new(21, 14).expect("valid oscillator params");
let candles = flat_candles(dec!(100), 10); let result = osc.compute(&candles);
assert!(
matches!(
result,
Err(crate::error::IndicatorError::InsufficientData { .. })
),
"Expected InsufficientData, got {:?}",
result
);
}
#[test]
fn flat_series_produces_zero_oscillator() {
let osc = DetrendedOscillator::new(21, 14).expect("valid oscillator params");
let candles = flat_candles(dec!(100), 80);
let series = osc.compute(&candles).expect("Should compute on 80 candles");
assert!(!series.is_empty(), "Series should not be empty");
for (_, v) in series.values() {
assert!(
v.abs() < dec!(0.001),
"Expected ~0 on flat series, got {}",
v
);
}
}
#[test]
fn warmup_period_is_positive() {
let osc = DetrendedOscillator::new(21, 14).expect("valid oscillator params");
assert!(osc.warmup_period() > 0);
}
#[test]
fn warmup_period_at_least_hma_warmup() {
use crate::hull::HullMa;
let osc = DetrendedOscillator::new(21, 14).expect("valid oscillator params");
let hma = HullMa::new(21).expect("valid HullMA period");
assert!(osc.warmup_period() >= hma.warmup_period());
}
#[test]
fn warmup_period_at_least_atr_warmup() {
use crate::atr::Atr;
let osc = DetrendedOscillator::new(21, 14).expect("valid oscillator params");
let atr = Atr::new(14).expect("valid ATR period");
assert!(osc.warmup_period() >= atr.warmup_period());
}
#[test]
fn name_includes_both_periods() {
let osc = DetrendedOscillator::new(21, 14).expect("valid oscillator params");
assert!(osc.name().contains("21"), "Name should mention HMA period");
assert!(osc.name().contains("14"), "Name should mention ATR period");
}
#[test]
fn series_length_consistent_with_candles() {
let osc = DetrendedOscillator::new(10, 5).expect("valid oscillator params");
let candles = flat_candles(dec!(100), 80);
let series = osc
.compute(&candles)
.expect("sufficient data for oscillator");
assert!(!series.is_empty());
assert!(series.len() <= candles.len());
}