100 * (close - LinReg(close, period)) / close. Positive when close
overshoots the linear forecast, negative when it undershoots. Holds
the previous value if the close is zero (percentage form undefined).
Single param period (default 14).
Touchpoints: cfo.rs + mod.rs + lib.rs re-export, PyCfo + __init__.py
+ test_new_indicators SCALAR + test_known_values linear reference,
CfoNode + index.d.ts/index.js + indicators.test.js factory + reference,
WasmCfo via scalar macro, scalar-fuzz target, README + CHANGELOG.
EMA(close, fast) - EMA(close, slow). Like MACD without the signal EMA.
Defaults to (fast = 12, slow = 26); fast must be strictly less than
slow.
Touchpoints: apo.rs + mod.rs + lib.rs re-export, PyApo + __init__.py
+ test_new_indicators SCALAR + test_known_values flat reference,
ApoNode + index.d.ts/index.js + indicators.test.js factory + reference,
WasmApo via scalar macro, scalar-fuzz target, README + CHANGELOG.
The fuzz suite previously covered only `Rsi(14)` and `Ema(20)` — 2 of
71 indicators, no OHLCV coverage at all. Audit finding R9 asked for
ATR/ADX/Stochastic/PSAR as a minimum; this commit goes further and
brings every indicator under fuzz.
- `indicator_update` (rewritten): drives every scalar-input indicator
through one streaming pass + one batch call per iteration. Covers
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, BollingerBands. A `drive` helper
marked `#[inline(never)]` keeps each indicator on its own panic
backtrace frame.
- `indicator_update_candle` (new): chunks the fuzz `f64` stream into
`[open, high, low, close, volume]` tuples, builds candles via
`Candle::new` (skipping ones that fail OHLCV validation — that path
is fuzz-tested separately), then drives every candle-input indicator
through streaming + batch. Covers 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.
- `fuzz/Cargo.toml` registers the new target; `fuzz/README.md`
describes both expanded targets.
- A `fuzz-smoke` CI job runs each of the five targets for 30 s on
every push and pull-request — enough to catch a regression in the
harness without slowing CI to a crawl. Long fuzz campaigns belong
on dedicated infrastructure with persistent corpora.
The repository had no fuzzing setup despite several natural targets —
the CSV parser, the Binance envelope deserializer, and the stateful
indicator/aggregator update paths.
Add a fuzz/ cargo-fuzz crate (detached from the workspace via its own
[workspace] table and the parent's exclude) with four targets:
- csv_reader — CandleReader over arbitrary bytes
- binance_envelope — RawWsEnvelope deserialization from arbitrary strings
- indicator_update — RSI/EMA streaming + batch over arbitrary f64 series
- tick_aggregator — TickAggregator over arbitrary tick triples
Each target asserts the no-panic contract: malformed input must surface
as an Err. fuzz/README.md documents running them (nightly + cargo-fuzz).