perf(linreg): incremental O(1) OLS for LinearRegression and LinRegSlope (R2)
`LinearRegression::fit` and `LinRegSlope::update` previously iterated the
full `period`-window on every tick to recompute `Σy` and `Σxy` from
scratch — O(period) per update, in violation of the `Indicator` trait's
O(1) contract. `LinRegAngle` inherits the cost transitively because it
delegates to `LinRegSlope`.
This commit slides the OLS state in closed form. The constant terms
(`Σx`, `Σxx`, the denominator `n·Σxx − (Σx)²`) were already precomputed
in `new`. The new running state is:
- `sum_y: f64` — running sum of the values currently in the window.
- `sum_xy: f64` — running Σ(x · y) where `x` is the position of each
value inside the trailing window (`0` for the oldest, `n−1` for the
newest).
On every push, when the window is already full the front value `y₀` is
popped and the indices of every remaining value shift down by 1; the
identity
new_Σxy = old_Σxy − old_Σy + y₀
closes the slide in O(1). The new value is then pushed at position `k`
(the current length before the push), contributing `k · new_value` to
`sum_xy` and `new_value` to `sum_y`. The output is the same TA-Lib OLS
formula evaluated against the incremental accumulators.
Behaviour is unchanged: same per-tick values, same warmup, same NaN
semantics. Two new tests compare the O(1) result bar-by-bar against a
fresh O(n) refit on a noisy ramp (sliding-phase dominated), a step
function (large pop/push deltas), and constants (tests floating-point
drift) — agreement is within `1e-9`.
`LinRegAngle` benefits automatically through its `LinRegSlope` field.
This commit is contained in:
@@ -24,6 +24,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
site makes the invariant explicit.
|
||||
|
||||
### Changed
|
||||
- `LinearRegression`, `LinRegSlope` and `LinRegAngle` (via composition over
|
||||
`LinRegSlope`) now run their rolling ordinary-least-squares fit
|
||||
**incrementally** in O(1) per update (audit finding R2). Previously every
|
||||
tick refit the line from scratch in O(period). The OLS denominators (`Σx`
|
||||
and `Σxx`) depend only on `period`, so they were already precomputed; this
|
||||
release adds running `Σy` and `Σxy` accumulators and slides them in closed
|
||||
form via the identity
|
||||
`new_Σxy = old_Σxy − old_Σy + popped_y₀` (then `Σxy += (n − 1) · new_value`
|
||||
and `Σy += new_value`). New per-bar equivalence tests compare the O(1)
|
||||
output against a fresh O(n) refit on noisy ramps, step functions, and
|
||||
constants — values agree to within 1e-9.
|
||||
- Fuzz suite expanded from 2 indicators to the full catalogue (audit finding
|
||||
R9). The existing `indicator_update` target now exercises every scalar-input
|
||||
indicator (~33 classes including MACD and Bollinger Bands); a new
|
||||
|
||||
Reference in New Issue
Block a user