Makes the Python binding truly dependency-free (Task 5b). `pip install wickra` now pulls **zero** third-party packages, and `import wickra` never imports NumPy (verified: `"numpy" not in sys.modules` after a batch call).
## What changed
- **Inputs** accept any sequence or buffer of numbers — `array.array`, `memoryview`, a NumPy `ndarray`, or a plain `list` — via `Vec` extraction (newtypes `Buf1`/`BufI64`). `PyBuffer` is unavailable under `abi3-py39`, and `unsafe_code = forbid` rules out a zero-copy slice, so inputs are copied once (negligible vs. compute).
- **Single-output `batch(...)`** returns a stdlib `array.array('d')` (native buffer protocol → `numpy.asarray` zero-copy).
- **Multi-output `batch(...)`** returns a buffer-protocol `Matrix` preserving `.shape`, integer-row and `[i, j]` access, and `.tolist()`.
- **NumPy** moves to an optional extra (`pip install wickra[numpy]`); it is never required.
- Streaming `update(...)` is unchanged; results are numerically identical.
## Implementation
- Trait `IntoPyData` + a `matrix()`/`f64_array()` helper collapse the ~400 batch call sites; `array.array` is built via `bytemuck` (Zlib/MIT/Apache — `cargo deny check licenses` ok).
- Tests migrated to `array.array`/`Matrix` (1-D `.shape`→`len()`, `.dtype`→`.typecode`; numeric comparisons normalize through a `_to_np` helper). The non-contiguous-input test now asserts acceptance instead of rejection.
## Verification (local)
- `pytest bindings/python/tests` — **1991 passed**.
- `cargo fmt --all`, `cargo clippy --workspace --all-targets --all-features -- -D warnings`, `cargo test --workspace --all-features`, `cargo deny check licenses` — all green.
**BREAKING for Python**: batch return types change from NumPy arrays to `array.array`/`Matrix`. Documented in CHANGELOG; ships with the data-layer release bundle (no separate tag).
* feat(core): add 3 trade-flow microstructure indicators
SignedVolume (per-trade size signed by aggressor), CumulativeVolumeDelta
(running signed-volume total), and TradeImbalance (rolling buy/sell volume
imbalance over a trade window). All consume the Trade type, with full unit
coverage. Extends the Microstructure family.
* feat(bindings): expose trade-flow microstructure indicators
Python, Node and WASM bindings for SignedVolume, CumulativeVolumeDelta and
TradeImbalance. Each takes a trade via update(price, size, is_buy); Python and
Node expose a batch over three parallel arrays, WASM exposes per-trade update.
Regenerates node index.d.ts/.js.
* test(bindings,fuzz,bench): cover trade-flow microstructure indicators
Python and Node: reference values, streaming-vs-batch, lifecycle/repr and input
validation (zero window, negative size, non-positive price, mismatched batch
lengths). New indicator_update_trade fuzz target. Synthetic trade-tape benches
(signed_volume cheapest, trade_imbalance windowed/expensive).
* docs: add trade-flow indicators + bump counter to 227
README Microstructure family row gains signed volume / CVD / trade imbalance and
the counter goes 224 -> 227; CHANGELOG records the trade-flow indicators.
* feat(core): add microstructure input types (OrderBook, Trade, TradeQuote)
New non-OHLCV value types for the order-book / trade-flow indicator family:
Level, OrderBook (sorted, uncrossed depth snapshot), Side, Trade (with
aggressor side), and TradeQuote (trade paired with prevailing mid). Each has a
validating constructor plus a new_unchecked hot-path constructor, with full
unit coverage. Adds InvalidOrderBook / InvalidTrade error variants.
* feat(core): add 5 order-book microstructure indicators
OrderBookImbalanceTop1/TopN/Full (signed depth imbalance), Microprice
(size-weighted fair value), and QuotedSpread (top-of-book spread in bps). All
consume the OrderBook snapshot type, emit f64, are stateless and ready after
the first snapshot, with full unit coverage. Registers a new Microstructure
family in the taxonomy.
* feat(bindings): expose order-book microstructure indicators
Python, Node, and WASM bindings for OrderBookImbalanceTop1/TopN/Full,
Microprice and QuotedSpread. Each takes a depth snapshot via four equal-length
(bid_px, bid_sz, ask_px, ask_sz) arrays. Python and Node expose a batch over a
list of snapshots; WASM exposes per-snapshot update (the streaming model that
fits a browser book feed). Regenerates node index.d.ts/.js and registers the
new InvalidOrderBook/InvalidTrade arms in the Python error mapping.
* test(bindings,fuzz): cover order-book microstructure indicators
Python: smoke, reference values, streaming-vs-batch, lifecycle/repr and input
validation (mismatched lengths, crossed book, misordered levels, zero levels)
for all five order-book indicators. Node: reference values, streaming-vs-batch,
and rejection cases. Adds an indicator_update_orderbook fuzz target driving
every order-book indicator over arbitrary (incl. degenerate) snapshots.
* bench(microstructure): synthetic order-book benchmarks
Add a bench_orderbook_input harness and synthesise a five-level book around
each candle close (no order-book dataset ships with the repo). Benches the
cheapest (top-of-book imbalance) and most-expensive (full-depth imbalance) plus
microprice, matching the curated cheapest/expensive-per-family approach.
* docs: add Microstructure family + bump indicator counter to 224
README gains the Microstructure family row (order-book imbalance, microprice,
quoted spread) and the indicator counter goes 219 -> 224 across seventeen
families; CHANGELOG records the new order-book indicators and value types.
* feat(core): add PairwiseBeta cross-asset indicator
Rolling OLS slope of one asset's log-returns on another's. Unlike Beta,
which regresses the raw inputs it is fed, PairwiseBeta differences
consecutive prices into log-returns internally -- the conventional way to
measure cross-asset beta, where a beta on price levels would be dominated
by the shared trend.
Two-series Indicator<Input = (f64, f64)>, exposed in Rust, Python, Node
and WASM, with unit/known-value/streaming tests and a pair fuzz target.
* feat(core): add PairSpreadZScore cross-asset indicator
Standardised log-spread ln(a) - beta*ln(b) of a pair, where beta is a
rolling-OLS hedge ratio and the spread is z-scored over its own look-back.
The canonical mean-reversion / statistical-arbitrage entry signal, with
independent beta_period and z_period windows.
Two-series Indicator<Input = (f64, f64)>, exposed in Rust, Python, Node
and WASM, with sign/known-value/streaming tests and a pair fuzz target.
* feat(core): add LeadLagCrossCorrelation cross-asset indicator
Reports the integer offset k in [-max_lag, max_lag] that maximises
|corr(a[t], b[t+k])|, answering which of two assets leads the other and by
how many bars. A positive lag means a leads b. Fully causal: a's window is
held centred while b's window slides across the buffered history, so every
lag is evaluated only against data already seen.
Struct output { lag, correlation }, exposed in Rust, Python, Node and WASM
with lead-detection/streaming tests and a pair fuzz driver.
* feat(core): add Cointegration (Engle-Granger + ADF) indicator
Rolling pairs-trading screen: an OLS hedge ratio of a on b, the spread
(residual) a - (alpha + beta*b), and an augmented Dickey-Fuller t-statistic
on the spread with configurable lags. A strongly negative statistic flags a
mean-reverting, tradeable spread. Includes a small Gaussian-elimination
solver for the augmented regression.
Struct output { hedge_ratio, spread, adf_stat }, exposed in Rust, Python,
Node and WASM with stationarity/hedge-ratio/streaming tests and a pair fuzz
driver.
* feat(core): add RelativeStrengthAB cross-asset indicator
Comparative relative strength of two assets: the ratio line a/b together
with its moving average and its RSI, the classic asset-vs-asset /
asset-vs-index rotation screen. Composes the existing Sma and Rsi over the
ratio; a zero denominator or non-finite price is skipped.
Struct output { ratio, ratio_ma, ratio_rsi }, exposed in Rust, Python, Node
and WASM with flat/rising-ratio/streaming tests and a pair fuzz driver.
* test(cointegration): cover ADF guard branches
The ADF helper's short-series and degrees-of-freedom guards and the
zero-dispersion (perfect AR) path are unreachable through the public
Cointegration API (period >= 2*adf_lags + 4), so exercise them with direct
unit tests on adf_no_constant. The second linear solve cannot be singular
once the coefficient solve on the same matrix has succeeded, so it now uses
expect() instead of a dead error branch.
* 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.