use super::*;
use quant_primitives::Candle;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
fn make_candles(count: usize, step: Decimal) -> Vec<Candle> {
let ts = chrono::Utc::now();
(0..count)
.map(|i| {
let price = Decimal::from(100) + step * Decimal::from(i);
Candle::new(
price,
price + dec!(1),
price - dec!(1),
price,
dec!(1000),
ts,
)
.expect("valid candle")
})
.collect()
}
#[test]
fn log_returns_empty_input() {
assert!(compute_log_returns(&[]).is_empty());
}
#[test]
fn log_returns_single_price() {
assert!(compute_log_returns(&[dec!(100)]).is_empty());
}
#[test]
fn log_returns_basic() {
let closes = vec![dec!(100), dec!(110), dec!(121)];
let returns = compute_log_returns(&closes);
assert_eq!(returns.len(), 2);
assert_eq!(returns[0], dec!(0.1)); assert_eq!(returns[1], dec!(0.1)); }
#[test]
fn log_returns_skips_zero_denominator() {
let closes = vec![dec!(0), dec!(100), dec!(110)];
let returns = compute_log_returns(&closes);
assert_eq!(returns.len(), 1); assert_eq!(returns[0], dec!(0.1));
}
#[test]
fn rescaled_range_none_when_n_exceeds_data() {
let returns = vec![dec!(0.01); 10];
assert!(rescaled_range_for_n(&returns, 20).is_none());
}
#[test]
fn rescaled_range_none_for_zero_n() {
let returns = vec![dec!(0.01); 10];
assert!(rescaled_range_for_n(&returns, 0).is_none());
}
#[test]
fn rescaled_range_produces_positive_value() {
let returns: Vec<Decimal> = (0..32)
.map(|i| if i % 2 == 0 { dec!(0.02) } else { dec!(-0.01) })
.collect();
let rs = rescaled_range_for_n(&returns, 16);
assert!(rs.is_some());
assert!(rs.expect("rs") > Decimal::ZERO);
}
#[test]
fn rescaled_range_none_for_constant_returns() {
let returns = vec![dec!(0.05); 32];
assert!(rescaled_range_for_n(&returns, 16).is_none());
}
#[test]
fn estimate_slope_none_with_single_point() {
assert!(estimate_slope(&[16], &[dec!(2)]).is_none());
}
#[test]
fn estimate_slope_none_with_empty() {
assert!(estimate_slope(&[], &[]).is_none());
}
#[test]
fn estimate_slope_returns_value_in_0_1() {
let h = estimate_slope(&[16, 64], &[dec!(2), dec!(4)]);
assert!(h.is_some());
let val = h.expect("slope");
assert!(
val >= Decimal::ZERO && val <= Decimal::ONE,
"H={val} outside [0,1]"
);
}
#[test]
fn estimate_slope_clamps_to_unit_interval() {
let h = estimate_slope(&[16, 32], &[dec!(1), dec!(100)]);
assert!(h.is_some());
let val = h.expect("slope");
assert!(val <= Decimal::ONE, "H={val} should be clamped to 1");
}
#[test]
fn hurst_new_rejects_small_window() {
assert!(HurstExponent::new(32).is_err());
}
#[test]
fn hurst_returns_none_for_short_series() {
let hurst = HurstExponent::new(128).expect("valid");
let candles = make_candles(50, dec!(0.5));
assert!(hurst.compute_from_candles(&candles).is_none());
}
#[test]
fn hurst_in_valid_range() {
let hurst = HurstExponent::new(128).expect("valid");
let candles = make_candles(200, dec!(0.5));
let result = hurst.compute_from_candles(&candles);
assert!(result.is_some(), "200 candles > 128 window → Some");
let h = result.expect("hurst");
assert!(
h >= Decimal::ZERO && h <= Decimal::ONE,
"Hurst {h} should be in [0, 1]"
);
}