fix(psar): correct is_ready convention and use NaN sentinels (R6, B-Opus-1)
`Psar::is_ready` previously returned `self.initialised`, which flips to
`true` *after* the seed candle — but the seed candle itself returns
`None`. The contract every other indicator honours is
`is_ready() == true` ↔ "the most recent update produced (or could
produce) a real value". Streaming consumers writing
`if ind.is_ready() { use(ind.update(c)?) }` would hit an unexpected
`None` on the first post-seed update.
Fix: add a `has_emitted: bool` field that flips on the first
`Some(sar)` return; `is_ready` now reads that. New test
`is_ready_only_after_first_some_value` pins the contract.
While in the same file, `reset()` is corrected to restore the compute
fields (`prev_high`, `prev_low`, `sar`, `ep`) to `f64::NAN` sentinels
instead of `0.0` (Opus bonus finding). The fields are gated by
`initialised` today, so the `0.0` sentinel never leaked into output —
but a future refactor that read them pre-init would have silently
treated `0.0` as a real price. A `debug_assert!` at the read site makes
the invariant explicit and catches a re-introduction of the bug in
debug builds.
Bit-equivalence with the previous behaviour is preserved
(`reset_allows_clean_reuse` and `batch_equals_streaming` continue to
pass unchanged).
This commit is contained in:
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
- `Psar::is_ready` now matches the convention shared by every other indicator:
|
||||
`is_ready() == true` iff a real value has been produced (audit finding R6).
|
||||
The previous implementation returned `self.initialised`, which flipped to
|
||||
`true` after the seed candle even though the seed candle itself returns
|
||||
`None`. A streaming consumer that wrote
|
||||
`if ind.is_ready() { use(ind.update(c)?) }` would hit an unexpected `None`
|
||||
on the first post-seed update. The fix introduces a `has_emitted` gate set
|
||||
when the first `Some` value is returned.
|
||||
- `Psar::reset` now restores the compute fields (`prev_high`, `prev_low`,
|
||||
`sar`, `ep`) to `f64::NAN` sentinels instead of `0.0` (audit Opus-Bonus 1).
|
||||
The fields are gated by `initialised` today, so the `0.0` sentinel never
|
||||
leaked into output — but a future refactor that read them pre-init would
|
||||
have silently treated `0.0` as a real price. A `debug_assert!` at the read
|
||||
site makes the invariant explicit.
|
||||
|
||||
### Changed
|
||||
- `UlcerIndex::update` now tracks the trailing maximum with a monotonically-
|
||||
decreasing deque of `(index, price)` pairs instead of scanning the whole
|
||||
|
||||
Reference in New Issue
Block a user