Repository navigation
Bug-fix release. Chaining one indicator into another silently produced nothing: every indicator emits warm-up NaNs, and the moving-average kernels let a single NaN into their running accumulator, which stayed poisoned for the rest of the series.
df["RSI_wma"] = ta.rsi(ta.wma(df["close"], 55), 14)
df["RSI_avg"] = ta.wma(df["RSI_wma"], 9) # was: NaN on every row
ta.crossover(df["RSI_wma"], df["RSI_avg"]).sum() # was: 0 across 1600 barsReported in marketcalls/openalgo#2029. ta.crossover / ta.crossunder were never at fault and are unchanged.
Silent failures
Three kernels returned plausible numbers rather than NaN, which is worse than the reported symptom:
stdevreturned0.0for the rest of the series after a NaN.f64::maxyields the other operand against NaN, so(NaN).max(0.0).sqrt()is0.0— Bollinger width collapsed instead of failing visibly.rsireturned a flat100over an upstream indicator's warm-up. A NaN delta is neither a gain nor a loss, soavg_lossseeded to 0 and took the "no losses" branch. This one predates the Rust core, which is why pinning back to 1.0.50 did not help.medianpanicked through the PyO3 boundary (partial_cmp().unwrap()) on any window containing a NaN — aPanicException, so it escapes a normalexcept Exception.
ta.vi and ta.ulcerindex returned all-NaN even on clean OHLCV; both now match their textbook definitions.
The NaN contract is now stated and enforced
It was previously unstated, and the Rust core and the pure-NumPy fallback had silently drifted apart — the same script gave different answers from a wheel and from a source checkout.
- Rolling-window kernels are window-local.
out[i]is NaN iffi < period-1or the window contains a NaN, and the series recovers as soon as the NaN slides out. Same rule as pandas.rolling(period). - Recursive kernels (the EMA family) skip leading NaNs, seed at the first finite value, and carry state across an interior NaN.
Affected kernels: sma, wma, ema, hma, zlema, t3, vwma, stdev, rolling_sum, rolling_variance, ema_sma, ema_wilder, ema_first_valid, true_range, atr_wilder, and the rolling extremes behind stochastic, williams_r, aroon, donchian, midprice and midpoint.
Compatibility and performance
For NaN-free input every other indicator is unchanged, bit for bit — verified across 155 output series against 2.0.4.
The branchless slide (NaN contributes 0.0 to the accumulator, a running count decides whether the window is publishable) keeps the hot kernels at or faster than 2.0.4 on 2M rows:
| kernel | 2.0.4 | 2.0.5 |
|---|---|---|
sma |
3.36 ms | 2.43 ms |
wma |
3.38 ms | 2.62 ms |
stdev |
3.53 ms | 2.96 ms |
atr |
9.30 ms | 8.87 ms |
hma |
8.13 ms | 7.73 ms |
vwma |
2.31 ms | 3.20 ms |
13 new Rust unit tests cover the contract, and benchmark/ci_smoke.py now checks the Rust core against the NumPy fallback and against pandas on every CI run.
Install
pip install --upgrade openalgoFull notes: CHANGELOG.md