use super::*;
use crate::sma::Sma;
use crate::test_helpers::helpers::{make_candle, ts};
use rust_decimal_macros::dec;
#[test]
fn diff_basic() {
let candles: Vec<Candle> = (0..10)
.map(|i| make_candle(Decimal::from(100 + i * 2), ts(i)))
.collect();
let sma3 = Sma::new(3).expect("period 3 is valid for SMA");
let sma5 = Sma::new(5).expect("period 5 is valid for SMA");
let diff = Diff::new(sma3, sma5);
let series = diff
.compute(&candles)
.expect("compute Diff on valid candles");
assert_eq!(series.len(), 6);
for (_, v) in series.values() {
assert_eq!(*v, dec!(2), "Diff should be exactly 2 in linear uptrend");
}
}
#[test]
fn diff_flat_zero() {
let candles: Vec<Candle> = (0..10).map(|i| make_candle(dec!(100), ts(i))).collect();
let sma3 = Sma::new(3).expect("period 3 is valid for SMA");
let sma5 = Sma::new(5).expect("period 5 is valid for SMA");
let diff = Diff::new(sma3, sma5);
let series = diff
.compute(&candles)
.expect("compute Diff on flat candles");
for (_, v) in series.values() {
assert_eq!(*v, Decimal::ZERO);
}
}
#[test]
fn diff_name() {
let sma3 = Sma::new(3).expect("period 3 is valid for SMA");
let sma5 = Sma::new(5).expect("period 5 is valid for SMA");
let diff = Diff::new(sma3, sma5);
assert_eq!(diff.name(), "Diff(SMA(3),SMA(5))");
}
#[test]
fn diff_warmup() {
let sma3 = Sma::new(3).expect("period 3 is valid for SMA");
let sma5 = Sma::new(5).expect("period 5 is valid for SMA");
let diff = Diff::new(sma3, sma5);
assert_eq!(diff.warmup_period(), 5); }
#[test]
fn ratio_equal_indicators() {
let candles: Vec<Candle> = (0..5).map(|i| make_candle(dec!(100), ts(i))).collect();
let sma2 = Sma::new(2).expect("period 2 is valid for SMA");
let sma3 = Sma::new(3).expect("period 3 is valid for SMA");
let ratio = Ratio::new(sma2, sma3);
let series = ratio
.compute(&candles)
.expect("compute Ratio on flat candles");
for (_, v) in series.values() {
assert_eq!(*v, Decimal::ONE, "Expected 1, got {}", v);
}
}
#[derive(Debug, Clone)]
struct ZeroIndicator;
impl Indicator for ZeroIndicator {
fn name(&self) -> &str {
"Zero"
}
fn warmup_period(&self) -> usize {
1
}
fn compute(&self, candles: &[Candle]) -> Result<Series, IndicatorError> {
let values: Vec<_> = candles
.iter()
.map(|c| (c.timestamp(), Decimal::ZERO))
.collect();
Ok(Series::new(values))
}
}
#[test]
fn ratio_division_by_zero() {
let candles: Vec<Candle> = (0..5).map(|i| make_candle(dec!(100), ts(i))).collect();
let sma2 = Sma::new(2).expect("period 2 is valid for SMA");
let ratio = Ratio::new(sma2, ZeroIndicator);
let series = ratio
.compute(&candles)
.expect("compute Ratio with zero denominator");
for (_, v) in series.values() {
assert_eq!(*v, Decimal::ZERO, "Division by zero should return 0");
}
}
#[test]
fn ratio_name() {
let sma2 = Sma::new(2).expect("period 2 is valid for SMA");
let sma3 = Sma::new(3).expect("period 3 is valid for SMA");
let ratio = Ratio::new(sma2, sma3);
assert_eq!(ratio.name(), "Ratio(SMA(2),SMA(3))");
}
#[test]
fn lag_basic() {
let candles: Vec<Candle> = vec![
make_candle(dec!(100), ts(0)),
make_candle(dec!(110), ts(1)),
make_candle(dec!(120), ts(2)),
make_candle(dec!(130), ts(3)),
make_candle(dec!(140), ts(4)),
];
let sma2 = Sma::new(2).expect("period 2 is valid for SMA");
let lag = Lag::new(sma2, 1).expect("lag period 1 is valid");
let series = lag.compute(&candles).expect("compute Lag on valid candles");
let values = series.decimal_values();
assert_eq!(values.len(), 3);
assert_eq!(values[0], dec!(105)); }
#[test]
fn lag_period_zero() {
let sma2 = Sma::new(2).expect("period 2 is valid for SMA");
let result = Lag::new(sma2, 0);
assert!(matches!(
result,
Err(IndicatorError::InvalidParameter { .. })
));
}
#[test]
fn lag_name() {
let sma2 = Sma::new(2).expect("period 2 is valid for SMA");
let lag = Lag::new(sma2, 3).expect("lag period 3 is valid");
assert_eq!(lag.name(), "Lag(SMA(2),3)");
}
#[test]
fn lag_warmup() {
let sma5 = Sma::new(5).expect("period 5 is valid for SMA");
let lag = Lag::new(sma5, 3).expect("lag period 3 is valid");
assert_eq!(lag.warmup_period(), 8); }
#[test]
fn scale_basic() {
let candles: Vec<Candle> = (0..5).map(|i| make_candle(dec!(100), ts(i))).collect();
let sma2 = Sma::new(2).expect("period 2 is valid for SMA");
let scale = Scale::new(sma2, dec!(2));
let series = scale
.compute(&candles)
.expect("compute Scale on valid candles");
for (_, v) in series.values() {
assert_eq!(*v, dec!(200));
}
}
#[test]
fn scale_fractional() {
let candles: Vec<Candle> = (0..5).map(|i| make_candle(dec!(100), ts(i))).collect();
let sma2 = Sma::new(2).expect("period 2 is valid for SMA");
let scale = Scale::new(sma2, dec!(0.5));
let series = scale
.compute(&candles)
.expect("compute Scale with fractional factor");
for (_, v) in series.values() {
assert_eq!(*v, dec!(50));
}
}
#[test]
fn scale_name() {
let sma2 = Sma::new(2).expect("period 2 is valid for SMA");
let scale = Scale::new(sma2, dec!(2.5));
assert_eq!(scale.name(), "Scale(SMA(2),2.5)");
}
#[test]
fn scale_warmup() {
let sma5 = Sma::new(5).expect("period 5 is valid for SMA");
let scale = Scale::new(sma5, dec!(2));
assert_eq!(scale.warmup_period(), 5); }
#[test]
fn scale_of_diff() {
let candles: Vec<Candle> = (0..10).map(|i| make_candle(dec!(100), ts(i))).collect();
let sma2 = Sma::new(2).expect("period 2 is valid for SMA");
let sma3 = Sma::new(3).expect("period 3 is valid for SMA");
let diff = Diff::new(sma2, sma3);
let scaled = Scale::new(diff, dec!(0.5));
let series = scaled
.compute(&candles)
.expect("compute Scale(Diff) on flat candles");
for (_, v) in series.values() {
assert_eq!(*v, Decimal::ZERO);
}
}