use super::*;
use chrono::{TimeZone, Utc};
use rust_decimal_macros::dec;
fn make_candle_ohlc(
open: Decimal,
high: Decimal,
low: Decimal,
close: Decimal,
idx: i64,
) -> Candle {
let ts = Utc
.with_ymd_and_hms(2024, 1, 1, idx as u32, 0, 0)
.single()
.expect("valid datetime");
Candle::new(open, high, low, close, dec!(1000), ts).expect("valid candle")
}
#[test]
fn true_range_basic() {
let candle = make_candle_ohlc(dec!(100), dec!(110), dec!(95), dec!(105), 0);
let tr = true_range(&candle, dec!(100));
assert_eq!(tr, dec!(15));
let candle = make_candle_ohlc(dec!(120), dec!(125), dec!(118), dec!(122), 0);
let tr = true_range(&candle, dec!(100));
assert_eq!(tr, dec!(25));
let candle = make_candle_ohlc(dec!(80), dec!(85), dec!(75), dec!(82), 0);
let tr = true_range(&candle, dec!(100));
assert_eq!(tr, dec!(25)); }
#[test]
fn atr_measures_volatility() {
let high_vol: Vec<Candle> = (0..10)
.map(|i| {
let base = Decimal::from(100);
make_candle_ohlc(base, base + dec!(20), base - dec!(20), base, i)
})
.collect();
let low_vol: Vec<Candle> = (0..10)
.map(|i| {
let base = Decimal::from(100);
make_candle_ohlc(base, base + dec!(2), base - dec!(2), base, i)
})
.collect();
let atr = Atr::new(5).expect("valid ATR period");
let high_vol_atr = atr.compute(&high_vol).expect("sufficient high-vol data");
let low_vol_atr = atr.compute(&low_vol).expect("sufficient low-vol data");
let high_last = high_vol_atr.last().expect("non-empty high-vol series").1;
let low_last = low_vol_atr.last().expect("non-empty low-vol series").1;
assert!(
high_last > low_last * dec!(5),
"High vol ATR {} should be much larger than low vol ATR {}",
high_last,
low_last
);
}
#[test]
fn atr_always_positive() {
let candles: Vec<Candle> = (0..20)
.map(|i| {
let base = Decimal::from(100 + i % 10);
make_candle_ohlc(base, base + dec!(5), base - dec!(5), base + dec!(2), i)
})
.collect();
let atr = Atr::new(14).expect("valid ATR period");
let series = atr.compute(&candles).expect("sufficient data for ATR");
for (_, value) in series.values() {
assert!(*value > Decimal::ZERO, "ATR {} should be positive", value);
}
}
#[test]
fn atr_insufficient_data() {
let candles: Vec<Candle> = (0..5)
.map(|i| make_candle_ohlc(dec!(100), dec!(105), dec!(95), dec!(102), i))
.collect();
let atr = Atr::new(14).expect("valid ATR period");
let result = atr.compute(&candles);
assert!(matches!(
result,
Err(IndicatorError::InsufficientData {
required: 15,
actual: 5
})
));
}
#[test]
fn atr_period_zero() {
let result = Atr::new(0);
assert!(matches!(
result,
Err(IndicatorError::InvalidParameter { .. })
));
}
#[test]
fn atr_name() {
let atr = Atr::new(14).expect("valid ATR period");
assert_eq!(atr.name(), "ATR(14)");
}
#[test]
fn atr_warmup_period() {
let atr = Atr::new(14).expect("valid ATR period");
assert_eq!(atr.warmup_period(), 15);
}