* feat(family-16): add ValueArea + InitialBalance + OpeningRange
Opens family #16 (Market Profile) with the three OHLCV-compatible scalar /
multi-output indicators:
- ValueArea(period, bin_count, value_area_pct) -> {poc, vah, val}.
Rolling bin-approximation volume profile over the last `period`
candles. Each candle's volume is spread uniformly across [low, high];
POC is the bin with highest cumulative volume; the value area expands
symmetrically from POC and always absorbs the higher-volume neighbour
next, until `value_area_pct` (default 0.70) of total volume is
enclosed. Defaults (20, 50, 0.70).
- InitialBalance(period) -> {high, low}. Tracks session-opening high
and low over the first `period` bars, then locks. Default period = 12
(one-hour IB on 5-minute bars for US equities). Callers MUST invoke
reset() at every session boundary, otherwise IB stays fixed for the
lifetime of the instance.
- OpeningRange(period) -> {high, low, breakout_distance}. Same
lock-after-N-bars semantics as IB with a shorter default period
(6 = 30 min on 5-minute bars) and a third output that tracks
close - or_mid (positive above the range mid, negative below).
Histogram-output Market Profile variants (Volume Profile, VPVR,
Composite Profile) are deferred because they need a new histogram
output API layer rather than fixed-arity scalars. Tick-data-only
variants (TPO Profile, Single Print, Order Flow Delta, Cumulative
Delta, Volume-Weighted Open) are out of scope because `wickra-data`
does not currently expose tick / L2 data.
All four bindings (Rust core, Python, Node, WASM) ship the new
indicators with parity tests; benches added; fuzz target extended.
Counter 71 -> 74 across 8 -> 9 families. cargo check --workspace
--all-features green.
* fix(family-16): cover cold paths in InitialBalance + ValueArea
InitialBalance::value() public getter had no test covering the post-update
Some(...) branch — extended accessors_and_metadata to call value() after one
update. ValueArea single-print bar path (c.high == c.low) was unreachable in
existing tests since the only single-print test used a uniform 100-price
window which exits early via the span == 0 guard; added a mixed-window test
that triggers the c.high <= c.low branch directly. The (None, None) arm of
the expansion match was by-construction unreachable (the loop condition
already requires at least one neighbour) and has been folded into an
if/else.
Two new indicators in a brand-new "Ichimoku & alternative charts"
family:
- `Ichimoku` (Ichimoku Kinko Hyo): the full five-line cloud system
(Tenkan-sen, Kijun-sen, Senkou Span A/B, Chikou Span). Classic
(9, 26, 52, 26) defaults; configurable. Forward displacement is
handled in an O(1) ring buffer so the visible Senkou A/B at bar n
are the values computed at bar n-displacement.
- `HeikinAshi`: recursive candle smoothing transform emitting a
four-field synthetic candle. Seeds ha_open from (open+close)/2 on
the first bar.
Touchpoints: core + unit tests, mod.rs/lib.rs re-exports, Python +
Node + WASM bindings (multi-output via PyArray2 / interleaved Vec<f64>
/ Object+Float64Array), Python tests across smoke/new-indicators/
input-validation, Node parity tests, fuzz target (Candle), benches,
README family table + counter (71 -> 73, 8 -> 9 families), CHANGELOG.
Note: Renko, Kagi, and Point & Figure from the family-13 ideas list
are intentionally skipped. They are bar generators (the bar boundary
is defined by price moves, not by a fixed time interval) rather than
indicators that consume a candle stream, and belong in wickra-data
as candle/tick transforms alongside the existing tick-to-candle
aggregator and resampler.
Implements Family 10 (Ehlers / Cycle) end-to-end across Rust core,
Python / Node / WASM bindings, fuzz, tests, benches and docs. This
is an entirely new family covering John Ehlers' digital-signal-
processing school of cycle analytics — a strong differentiator
versus TA-Lib and pandas-ta, which ship only fragments.
Indicators:
- MAMA (Mesa Adaptive MA) — multi-output { mama, fama }
- FAMA (Following Adaptive MA) — scalar wrapper around MAMA's slow line
- Fisher Transform — Gaussian-normalising price transform
- Inverse Fisher Transform — bounded oscillator (tanh-based)
- SuperSmoother — 2-pole Butterworth lowpass
- Roofing Filter — high-pass + SuperSmoother bandpass
- Decycler — price minus 2-pole high-pass (lag-free trend)
- Decycler Oscillator — fast / slow Decycler difference (MACD-like)
- Hilbert Dominant Cycle — phase-derived period estimator [6, 50]
- Sine Wave Indicator — sin(phase) with 45° lead companion
- Adaptive Cycle Indicator — half-period driver for adaptive oscillators
- Center of Gravity Oscillator — weighted-mass momentum
- Cybernetic Cycle Component — EasyLanguage classic
- Empirical Mode Decomposition — bandpass + envelope mean
- Ehlers Stochastic — Stochastic on Roofing Filter input, [-1, +1]
- Instantaneous Trendline — Ehlers 2-pole lag-free trend
Indicator count rises 71 -> 87 across nine families (was eight).
All sixteen pass batch == streaming equivalence, expose the standard
Indicator surface (update / batch / reset / is_ready / warmup_period
/ name), are fuzz-tested, benchmarked against the checked-in BTCUSDT
1-minute dataset and reach across all four bindings.
Wiki deep-dive drafts for every indicator + Sidebar / Overview /
Home / Warmup updates are staged under indicator-ideas/families/
wiki/family-10-ehlers-cycle/ in the main repo (ghost-ignored) for
the maintainer to publish to the wiki repo manually.
Every Python batch() did prices.as_slice().expect("contiguous"), so a
non-contiguous NumPy input (e.g. a strided view) aborted with a Rust
panic instead of a catchable exception. as_slice() failures now map to a
PyValueError pointing at np.ascontiguousarray; the scalar / MACD /
Bollinger batch methods that returned a bare array were lifted to
PyResult so the error can propagate. Adds input-validation tests
(non-contiguous arrays, unequal-length candle batches, ROC/TRIX
defaults). All 60 Python tests pass against the freshly built wheel.