test(cold-paths): cover 3 lines in mama/rsi/sine_wave (#58)

* test(cold-paths): cover phase fallback in mama/sine_wave and rsi naive helper saturation

* fix(rsi): drop redundant closure in test helper
This commit is contained in:
kingchenc
2026-05-26 21:08:50 +02:00
committed by GitHub
parent 4e3c41ea80
commit e85334a2e9
3 changed files with 26 additions and 10 deletions
+6 -6
View File
@@ -369,13 +369,13 @@ mod tests {
#[test]
fn flat_input_uses_phase_fallback() {
// A perfectly constant series leaves every smooth/detrender slot at
// the same value, so `i1` collapses to zero and the phase calc takes
// the `self.prev_phase` fallback rather than `atan(q1/i1)`. Stretch
// the run long enough to clear the 50-bar warmup with comfortable
// margin and confirm the indicator still emits.
// Zero inputs make every smooth/detrender term arithmetically exact
// zero, so `i1 == 0.0` and the phase calculation takes the
// `self.prev_phase` fallback rather than `atan(q1/i1)`. A non-zero
// constant like `50.0` leaves a sub-EPSILON cancellation residue
// that flips the branch back to the `atan` path on real hardware.
let mut mama = Mama::classic();
let out = mama.batch(&[50.0_f64; 200]);
let out = mama.batch(&[0.0_f64; 200]);
assert!(out.iter().flatten().count() > 100);
}
}
+14
View File
@@ -230,6 +230,20 @@ mod tests {
}
}
/// Cover the `100.0` branch (line 169) of the test-helper `rsi_naive`:
/// strictly increasing prices give `avg_loss == 0` while `avg_gain > 0`,
/// the textbook overbought saturation case. Random proptest inputs
/// virtually never satisfy `al == 0 && ag != 0`, so this needs an
/// explicit monotone series.
#[test]
fn naive_helper_monotone_up_yields_100() {
let prices: Vec<f64> = (1..=20).map(f64::from).collect();
let ks = rsi_naive(&prices, 5);
for r in ks.into_iter().skip(5) {
assert_eq!(r.expect("ready after period+1 inputs"), 100.0);
}
}
#[test]
fn warmup_period_is_period_plus_one() {
let rsi = Rsi::new(14).unwrap();
@@ -218,11 +218,13 @@ mod tests {
#[test]
fn flat_input_uses_phase_fallback() {
// A constant series leaves the detrender chain at zero, so the `i1`
// arm is `i1.abs() <= EPSILON` for every bar and the phase calculation
// takes the `self.last_phase` fallback rather than `atan(q1/i1)`.
// Zero inputs make every smooth/detrender term arithmetically exact
// zero (no IEEE-754 cancellation residue), so `i1 == 0.0` and the
// phase calculation deterministically takes the `self.last_phase`
// fallback rather than `atan(q1/i1)`. A non-zero constant like
// `100.0` leaves a sub-EPSILON residue that flips the branch back.
let mut sw = SineWave::new();
let _ = sw.batch(&[100.0_f64; 120]);
let _ = sw.batch(&[0.0_f64; 120]);
assert!(sw.value().is_some());
}
}