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:
@@ -369,13 +369,13 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn flat_input_uses_phase_fallback() {
|
fn flat_input_uses_phase_fallback() {
|
||||||
// A perfectly constant series leaves every smooth/detrender slot at
|
// Zero inputs make every smooth/detrender term arithmetically exact
|
||||||
// the same value, so `i1` collapses to zero and the phase calc takes
|
// zero, so `i1 == 0.0` and the phase calculation takes the
|
||||||
// the `self.prev_phase` fallback rather than `atan(q1/i1)`. Stretch
|
// `self.prev_phase` fallback rather than `atan(q1/i1)`. A non-zero
|
||||||
// the run long enough to clear the 50-bar warmup with comfortable
|
// constant like `50.0` leaves a sub-EPSILON cancellation residue
|
||||||
// margin and confirm the indicator still emits.
|
// that flips the branch back to the `atan` path on real hardware.
|
||||||
let mut mama = Mama::classic();
|
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);
|
assert!(out.iter().flatten().count() > 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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]
|
#[test]
|
||||||
fn warmup_period_is_period_plus_one() {
|
fn warmup_period_is_period_plus_one() {
|
||||||
let rsi = Rsi::new(14).unwrap();
|
let rsi = Rsi::new(14).unwrap();
|
||||||
|
|||||||
@@ -218,11 +218,13 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn flat_input_uses_phase_fallback() {
|
fn flat_input_uses_phase_fallback() {
|
||||||
// A constant series leaves the detrender chain at zero, so the `i1`
|
// Zero inputs make every smooth/detrender term arithmetically exact
|
||||||
// arm is `i1.abs() <= EPSILON` for every bar and the phase calculation
|
// zero (no IEEE-754 cancellation residue), so `i1 == 0.0` and the
|
||||||
// takes the `self.last_phase` fallback rather than `atan(q1/i1)`.
|
// 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 mut sw = SineWave::new();
|
||||||
let _ = sw.batch(&[100.0_f64; 120]);
|
let _ = sw.batch(&[0.0_f64; 120]);
|
||||||
assert!(sw.value().is_some());
|
assert!(sw.value().is_some());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user