24e723fa7d
* feat(rvi): add Relative Vigor Index
Dorsey's RVI = SMA(close - open, period) / SMA(high - low, period) over
a rolling window of period candles. Candle input, single parameter
period (default 10). Positive on average-bullish windows, negative on
average-bearish. Holds the previous value if the entire window has
zero range (denominator undefined).
Reference: Donald Dorsey, also pandas-ta rvi.
Touchpoints: rvi.rs + mod.rs + lib.rs re-export, PyRvi + __init__.py
+ test_new_indicators CANDLE_SCALAR + test_known_values reference,
RviNode (4-column OHLC batch) + index.d.ts/index.js + indicators.test
.js factory + reference, WasmRvi + make_candle_ohlc helper, candle-fuzz
target + criterion bench, README + CHANGELOG.
* feat(pgo): add Pretty Good Oscillator
Mark Johnson's PGO = (close - SMA(close, period)) / EMA(TR, period).
Counts roughly how many ATR-equivalents the close sits from its
period-bar mean. Candle input, single parameter period (default 14).
Johnson's heuristic uses +3/-3 crossings as entry signals.
Touchpoints: pgo.rs + mod.rs + lib.rs re-export, PyPgo + __init__.py
+ test_new_indicators CANDLE_SCALAR + test_known_values flat-close
reference, PgoNode (h/l/c) + index.d.ts/index.js + indicators.test.js
factory + reference, WasmPgo, candle-fuzz target + bench, README +
CHANGELOG.
* feat(kst): add Know Sure Thing (Pring)
Pring's long-horizon momentum oscillator: weighted sum of four
SMA-smoothed ROC series with fixed weights 1, 2, 3, 4, plus an SMA
signal line. Nine parameters (four ROC periods, four SMA periods, one
signal period); classic() applies Pring's recommended defaults.
Multi-output indicator emitting KstOutput { kst, signal }.
Touchpoints: kst.rs + mod.rs + lib.rs re-export, PyKst + __init__.py
+ test_new_indicators MULTI + test_known_values flat-input reference,
KstNode + KstValue + index.d.ts/index.js + indicators.test.js multi
factory + reference, WasmKst (manual JsValue object), scalar-fuzz
target (handled outside the f64-output drive helper), README +
CHANGELOG.
* feat(smi): add Stochastic Momentum Index (Blau)
Blau's doubly-EMA-smoothed bounded oscillator: measures the close's
displacement from the centre of the recent high-low range, scaled by
the smoothed range. Candle input, three parameters (period, d_period,
d2_period) with defaults 5 / 3 / 3.
Internally feeds both the displacement-EMA stack and the range-EMA
stack on every candle so they warm up in parallel (gating either
behind the other starves the second by one input).
Touchpoints: smi.rs + mod.rs + lib.rs re-export, PySmi + __init__.py
+ test_new_indicators CANDLE_SCALAR + test_known_values flat-input
reference, SmiNode + index.d.ts/index.js + indicators.test.js factory
+ reference, WasmSmi, candle-fuzz target, README + CHANGELOG.
* feat(laguerre-rsi): add Ehlers Laguerre RSI
Four-stage Laguerre polynomial filter wrapped in an RSI-style up/down
accumulator. Single gamma in [0, 1] (default 0.5) trades lag for
smoothness. State is seeded by setting all four L_i to the first input
so a constant series stays at the neutral 50. Output clamped to
[0, 100] to absorb floating-point rounding.
Reference: Ehlers, Time Warp - Without Space Travel, 2002.
Touchpoints: laguerre_rsi.rs + mod.rs + lib.rs re-export, PyLaguerreRsi
+ __init__.py + test_new_indicators SCALAR + test_known_values neutral
reference, LaguerreRsiNode + index.d.ts/index.js + indicators.test.js
factory + reference, WasmLaguerreRsi via scalar macro, scalar-fuzz
target, README + CHANGELOG.
* feat(connors-rsi): add Connors RSI (CRSI)
Larry Connors' 3-component aggregate: RSI(close), RSI(streak), and
PercentRank of the 1-period return over the last period_rank returns.
Each component is bounded in [0, 100] so the aggregate is too.
Three parameters (period_rsi, period_streak, period_rank) with
defaults 3 / 2 / 100. Streak tracks consecutive up/down runs (resets
to 0 on unchanged close).
Touchpoints: connors_rsi.rs + mod.rs + lib.rs re-export, PyConnorsRsi
+ __init__.py + test_new_indicators SCALAR + test_known_values bounded
reference, ConnorsRsiNode + index.d.ts/index.js + indicators.test.js
factory + reference, WasmConnorsRsi via scalar macro, scalar-fuzz
target, README + CHANGELOG.
* feat(inertia): add Dorsey Inertia (RVI + LinReg)
Donald Dorsey's Inertia — a LinearRegression smoothing of the RVI
series. Endpoint of an n-bar least-squares fit of RVI is the indicator
reading. Preserves trend direction while damping the ratio. Candle
input, two parameters (rvi_period, linreg_period) with defaults 14 / 20.
Touchpoints: inertia.rs + mod.rs + lib.rs re-export, PyInertia +
__init__.py + test_new_indicators CANDLE_SCALAR + test_known_values
constant reference, InertiaNode (4-column OHLC batch) + index.d.ts /
index.js + indicators.test.js factory + reference, WasmInertia,
candle-fuzz target, README + CHANGELOG.
* test(kst): Move KST out of MULTI dict (it is scalar-input)
KST sits in the MULTI dict (candle-input, multi-output) but its
update() takes a single f64, not a candle tuple. The shared streaming
loop in test_multi_streaming_matches_batch fed the OHLCV tuple in,
which crashed with `TypeError: argument 'value': must be real number,
not tuple` on every Python matrix entry.
Split into a new MULTI_SCALAR_INPUT dict with its own test function
that feeds the close-price stream as floats. KST is currently the
only such indicator; structure is ready for future scalar-input
multi-output additions (e.g. some MACD-shaped indicators).
* test(coverage): Cover SMI zero-range and ConnorsRsi zero-prev cold paths
codecov/patch on PR 40 flagged two uncovered defensive branches:
- SMI returns self.current early when the smoothed range collapses to
zero (`r2 <= 0.0`) so the formula stays defined. Exercised by feeding
bars where high == low.
- ConnorsRsi skips the ROC ring-buffer update when the previous price
is exactly zero so the divide-by-zero in `(input - prev) / prev` is
impossible. Exercised by seeding the first bar at 0.0.
Fuzzing Wickra
cargo-fuzz harnesses for
the parsing and stateful entry points of Wickra. Fuzzing requires a nightly
Rust toolchain.
Setup
cargo install cargo-fuzz
rustup toolchain install nightly
Targets
| Target | What it exercises |
|---|---|
csv_reader |
CandleReader over arbitrary bytes — headers, cells, BOM, binary noise. |
binance_envelope |
RawWsEnvelope deserialization from arbitrary strings. |
indicator_update |
Every scalar-input indicator (SMA / EMA / WMA / RSI / DEMA / TEMA / HMA / ROC / TRIX / SMMA / TRIMA / ZLEMA / KAMA / T3 / MOM / CMO / TSI / PMO / StochRSI / DPO / PPO / Coppock / StdDev / UlcerIndex / HistoricalVolatility / LinearRegression / LinRegSlope / LinRegAngle / VHF / ZScore / MACD / Bollinger) streamed + batched over arbitrary f64 sequences (NaN, ±inf, jumps). |
indicator_update_candle |
Every candle-input indicator (ATR, NATR, TrueRange, ChaikinVolatility, Keltner, Donchian, PSAR, SuperTrend, ChandelierExit, ChandeKrollStop, ATRTrailingStop, ADX, Aroon, AroonOscillator, Vortex, MassIndex, ChoppinessIndex, CCI, WilliamsR, AwesomeOscillator, AcceleratorOscillator, UltimateOscillator, BalanceOfPower, OBV, MFI, VWAP, RollingVWAP, VWMA, ADL, VPT, CMF, ChaikinOscillator, ForceIndex, EaseOfMovement, TypicalPrice, MedianPrice, WeightedClose, Stochastic) streamed + batched over fuzz-derived OHLCV candles. |
tick_aggregator |
TickAggregator over arbitrary (price, volume, timestamp) triples. |
Run
# From the repository root:
cargo +nightly fuzz run csv_reader
cargo +nightly fuzz run binance_envelope
cargo +nightly fuzz run indicator_update
cargo +nightly fuzz run indicator_update_candle
cargo +nightly fuzz run tick_aggregator
Each run continues until a crash is found or it is interrupted. A short time-boxed smoke run is useful in CI:
cargo +nightly fuzz run csv_reader -- -max_total_time=60
The expectation for every target is that it never panics: malformed or
adversarial input must surface as an Err, never a crash.