fix(core): skip non-positive HV prices and add Error::InvalidTick (R13, R14)

R13 — `HistoricalVolatility::update` previously substituted `0.0` for
the log-return whenever `prev <= 0` or `input <= 0`. The log-return is
undefined there, and silently treating bad ticks as "no movement"
underreports realised volatility on broken data feeds. The fix skips
non-positive prices entirely: `self.last` is returned, state is left
untouched, and the next real tick re-anchors against the previous
*valid* `prev_price`. This matches how every other indicator handles
invalid inputs (SMA / EMA / ROC / Bollinger).

A new test `skips_non_positive_prices` proves the invariant: after a
warmed-up indicator, two consecutive bad ticks (`-5.0` and `0.0`) must
return the baseline value, and a subsequent real positive tick must
produce the same output as a control indicator that simply never saw
the bad ticks.

R14 — `Tick::new` previously returned `Error::InvalidCandle` for
negative volume. A tick is not a candle; downstream tick-stream
pipelines should be able to match on a semantically-correct error. A
new `Error::InvalidTick { message }` variant is added; the existing
test is updated to assert against it. Python's `map_err` is extended
to forward the new variant as `PyValueError`; the Node and WASM
bindings format via `Error::to_string()` and pick the new variant up
automatically without source changes.
This commit is contained in:
kingchenc
2026-05-23 10:46:52 +02:00
parent 510013fc5a
commit 183ebec7ba
5 changed files with 76 additions and 12 deletions
+2 -1
View File
@@ -22,7 +22,8 @@ fn map_err(e: wc::Error) -> PyErr {
| wc::Error::InvalidPeriod { .. }
| wc::Error::NonPositiveMultiplier
| wc::Error::NonFiniteInput
| wc::Error::InvalidCandle { .. } => PyValueError::new_err(e.to_string()),
| wc::Error::InvalidCandle { .. }
| wc::Error::InvalidTick { .. } => PyValueError::new_err(e.to_string()),
}
}