test(bollinger): cover classic()+accessors+metadata, drop dead naive arm
Codecov flagged 16 uncovered lines in
crates/wickra-core/src/indicators/bollinger.rs (file at 91.30%):
- classic() convenience constructor (91-93) — every test passed
explicit parameters to BollingerBands::new, so the classic-defaults
path was dead
- const accessors period (96-98), multiplier (101-103) — never queried
- Indicator-impl bodies warmup_period (156-158), name (164-166) —
never queried
- `return None;` (line 177) inside the test-only `naive` helper's
`if prices.len() < period` early-return — every caller passes a
slice of length >= period (matches_naive_definition uses
`&prices[..=i]` for `i in 19..` with period=20; long_stream_drift_
stays_bounded fills the window before measuring), so the arm is dead
Add classic_and_accessors_and_metadata to cover the constructor and
the four getter bodies, and refactor naive to return BollingerOutput
directly with an `assert!(prices.len() >= period)` precondition. The
two existing callers were already using .unwrap()/.expect() on the
Option result and simplify to direct calls.
bollinger.rs is now at 184/184 lines, no behavioural change.
This commit is contained in:
@@ -172,20 +172,23 @@ mod tests {
|
|||||||
use crate::traits::BatchExt;
|
use crate::traits::BatchExt;
|
||||||
use approx::assert_relative_eq;
|
use approx::assert_relative_eq;
|
||||||
|
|
||||||
fn naive(prices: &[f64], period: usize, mult: f64) -> Option<BollingerOutput> {
|
fn naive(prices: &[f64], period: usize, mult: f64) -> BollingerOutput {
|
||||||
if prices.len() < period {
|
assert!(
|
||||||
return None;
|
prices.len() >= period,
|
||||||
}
|
"naive: need at least `period` prices (got {}, period {})",
|
||||||
|
prices.len(),
|
||||||
|
period,
|
||||||
|
);
|
||||||
let w = &prices[prices.len() - period..];
|
let w = &prices[prices.len() - period..];
|
||||||
let mean = w.iter().sum::<f64>() / period as f64;
|
let mean = w.iter().sum::<f64>() / period as f64;
|
||||||
let var = w.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / period as f64;
|
let var = w.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / period as f64;
|
||||||
let s = var.sqrt();
|
let s = var.sqrt();
|
||||||
Some(BollingerOutput {
|
BollingerOutput {
|
||||||
upper: mean + mult * s,
|
upper: mean + mult * s,
|
||||||
middle: mean,
|
middle: mean,
|
||||||
lower: mean - mult * s,
|
lower: mean - mult * s,
|
||||||
stddev: s,
|
stddev: s,
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -212,6 +215,20 @@ mod tests {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Cover the convenience constructor `BollingerBands::classic()` plus the
|
||||||
|
/// const accessors `period` / `multiplier` and the Indicator-impl
|
||||||
|
/// metadata methods `warmup_period` / `name`. Existing tests never
|
||||||
|
/// invoked `classic()` (every test passed explicit parameters to
|
||||||
|
/// `new`) and never queried any of the four getters.
|
||||||
|
#[test]
|
||||||
|
fn classic_and_accessors_and_metadata() {
|
||||||
|
let bb = BollingerBands::classic();
|
||||||
|
assert_eq!(bb.period(), 20);
|
||||||
|
assert_relative_eq!(bb.multiplier(), 2.0, epsilon = 1e-12);
|
||||||
|
assert_eq!(bb.warmup_period(), 20);
|
||||||
|
assert_eq!(bb.name(), "BollingerBands");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn warmup_returns_none() {
|
fn warmup_returns_none() {
|
||||||
let mut bb = BollingerBands::new(5, 2.0).unwrap();
|
let mut bb = BollingerBands::new(5, 2.0).unwrap();
|
||||||
@@ -241,7 +258,7 @@ mod tests {
|
|||||||
let out = bb.batch(&prices);
|
let out = bb.batch(&prices);
|
||||||
for i in 19..prices.len() {
|
for i in 19..prices.len() {
|
||||||
let got = out[i].unwrap();
|
let got = out[i].unwrap();
|
||||||
let want = naive(&prices[..=i], 20, 2.0).unwrap();
|
let want = naive(&prices[..=i], 20, 2.0);
|
||||||
assert_relative_eq!(got.middle, want.middle, epsilon = 1e-9);
|
assert_relative_eq!(got.middle, want.middle, epsilon = 1e-9);
|
||||||
assert_relative_eq!(got.stddev, want.stddev, epsilon = 1e-9);
|
assert_relative_eq!(got.stddev, want.stddev, epsilon = 1e-9);
|
||||||
assert_relative_eq!(got.upper, want.upper, epsilon = 1e-9);
|
assert_relative_eq!(got.upper, want.upper, epsilon = 1e-9);
|
||||||
@@ -301,8 +318,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
window.push_back(v);
|
window.push_back(v);
|
||||||
}
|
}
|
||||||
let scratch =
|
let scratch = naive(&window.iter().copied().collect::<Vec<_>>(), period, mult);
|
||||||
naive(&window.iter().copied().collect::<Vec<_>>(), period, mult).expect("warmed up");
|
|
||||||
let got = last.expect("warmed up");
|
let got = last.expect("warmed up");
|
||||||
assert!(
|
assert!(
|
||||||
(got.middle - scratch.middle).abs() < 1e-3,
|
(got.middle - scratch.middle).abs() < 1e-3,
|
||||||
|
|||||||
Reference in New Issue
Block a user