2be21df8039f002b292c06383dbf482d4fd1f6cc
48 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2be21df803 |
feat: order-book microstructure indicators (part 1 of 4) (#112)
* 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. |
||
|
|
0479191b66 |
feat: signed candlestick directional ±1 encoding (Doji signed mode) (#111)
* feat(core): add signed dragonfly/gravestone encoding to Doji Doji gains an opt-in `.signed()` mode that classifies a detected Doji by the position of its body within the bar range: dragonfly (long lower shadow) emits +1.0 (bullish), gravestone (long upper shadow) emits -1.0 (bearish), and a long-legged/standard Doji emits 0.0. The default detection-flag behaviour (+1.0/0.0) is unchanged, so existing callers are unaffected. The other 14 candlestick patterns already emit the uniform +1 bull / -1 bear / 0 none convention; document that explicitly with a "Signed +-1 encoding" section on each so the whole family is a consistent drop-in ML feature. * feat(bindings): expose Doji signed mode in python, node, wasm Hand-write the Doji binding in all three language bindings (instead of the shared candle-pattern macro) so it accepts an opt-in `signed` flag and exposes an `is_signed`/`isSigned` accessor: - Python: `Doji(signed=False)` keyword argument - Node: `new Doji(signed?)` optional constructor argument (index.d.ts/.js regenerated via napi build) - WASM: `new Doji(signed?)` optional constructor argument The default construction is unchanged, so existing callers keep the direction-less +1/0 detection flag. * test(bindings,fuzz): cover Doji signed dragonfly/gravestone encoding - python: dragonfly(+1)/gravestone(-1)/neutral(0) and default-flag cases in test_known_values - node: equivalent signed/default assertions in indicators.test.js - fuzz: drive a signed Doji alongside the default in indicator_update_candle * docs: document signed candlestick convention and Doji signed mode README gains a candlestick sign-convention note; CHANGELOG records the new opt-in Doji signed dragonfly/gravestone encoding under [Unreleased]. |
||
|
|
0b85142ad1 |
feat: cross-asset / pairwise indicators (5 new) (#109)
* 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.
|
||
|
|
2ab578bee8 |
docs: surface docs.wickra.org + keep the wiki pointer count in sync (#107)
* docs(readme): surface the documentation site (docs.wickra.org)
The README never linked the canonical docs at docs.wickra.org — visitors had
no path from the repo to the per-indicator deep dives, quickstarts, and
guides. Add a docs badge, a Documentation section mirroring the binding
READMEs and docs/README.md, and an Indicators-Overview link in the indicator
section.
* ci(sync-about): keep the wiki pointer page's indicator count in sync
The GitHub wiki was collapsed to a single Home.md that points at
docs.wickra.org but still names the indicator count ('… for all N indicators').
Add a count-sync step mirroring the docs/webpage steps — clone wickra.wiki into
its own dir, sed Home.md, commit as wickra-bot, push — with the same
continue-on-error soft-skip so a missing PAT scope never fails the run.
|
||
|
|
a046c441e5 |
docs(readme): show the Wickra banner; drop the redundant heading (#98)
Embed the brand banner at the top of the README (linked to wickra.org) and drop the now-redundant "# Wickra" heading — the banner shows the wordmark. The image is served from https://wickra.org/og-banner.webp, which the webpage build regenerates on every deploy from the current indicator count (4K WebP), so the README banner stays current with no committed binary in this repo and renders on GitHub, crates.io and docs.rs alike. |
||
|
|
5030360a0c |
ci: CodeQL SAST workflow + badge (P13.6) (#96)
* ci: add OpenSSF Scorecard workflow + badge (P13.1) * ci(release): attest build provenance for crates + Python artifacts (P13.2) * docs(readme): add GitHub release badge (P13.4) * ci: add CodeQL SAST workflow + badge (P13.6) |
||
|
|
1dd487fabc |
docs(readme): GitHub release badge (P13.4) (#94)
* ci: add OpenSSF Scorecard workflow + badge (P13.1) * ci(release): attest build provenance for crates + Python artifacts (P13.2) * docs(readme): add GitHub release badge (P13.4) |
||
|
|
cee174c0de |
ci(release): build-provenance attestations for crates + Python (P13.2) (#91)
* ci: add OpenSSF Scorecard workflow + badge (P13.1) * ci(release): attest build provenance for crates + Python artifacts (P13.2) |
||
|
|
c8e5d8a658 | ci: add OpenSSF Scorecard workflow + badge (P13.1) (#90) | ||
|
|
62ab84c472 |
chore(migration): switch org to wickra-lib and maintainer email to wickra.lib@gmail.com (#59)
Introduce repo-metadata.toml as single source of truth for repo identity (org slug, maintainer email, canonical URLs) and add sync-metadata.yml workflow with a Python audit script that fails CI if any tracked file drifts back to pre-migration values. Bulk-replace across 24 tracked files: - kingchenc/wickra -> wickra-lib/wickra (URL segment) - kingchencp@gmail.com -> wickra.lib@gmail.com (maintainer email) - @kingchenc -> @wickra-lib (CODEOWNERS mention only) Person-name credits are preserved: LICENSE copyright holder, Cargo.toml authors handle, and CHANGELOG historical @kingchenc reference all remain unchanged. Crate / PyPI / npm package names also untouched. Merge this PR only after the kingchenc/wickra -> wickra-lib/wickra org transfer has happened on the GitHub side, otherwise all badges and repository links 404 until the transfer is performed. |
||
|
|
0f2ff9c3c7 |
chore(sync-about): count public indicator types, not module files (#70)
* chore(sync-about): count public indicator types, not module files
The sync-about workflow counted `mod xxx;` lines in
crates/wickra-core/src/indicators/mod.rs to derive the indicator count
that gets propagated to README, the GitHub About description, and the
wiki. That under-reported by one because `vwap.rs` exports two public
types — `Vwap` (cumulative) and `RollingVwap` (finite window) — from
the same module. The bindings reach both, so users see 214 indicators
even though there are only 213 source files.
Fix the count by parsing the canonical `pub use indicators::{ ... }`
block in lib.rs, dropping the `FAMILIES` constant and any `*Output`
companion structs, and counting the remaining public types. Pure-shell
implementation so the workflow doesn't grow a python dependency.
Sync README to the corrected count (213 -> 214) in the same commit so
the PR validates cleanly through the workflow's PR-flow.
* docs(bindings): sync per-binding READMEs and docs/ pointer to 214 / 16
The bindings/{node,python,wasm}/README.md files (which ship to
npm / PyPI / npm again) and docs/README.md (the pointer to the
wiki) all carried the stale '71 streaming-first indicators across
eight families' header from before the family expansion. Pull the
canonical 16-family table out of the main README into all three
binding READMEs and update docs/README.md to list every family by
name, so a user landing on PyPI / npm sees the current catalogue
shape.
|
||
|
|
4e3c41ea80 |
feat(family-15): add 17 risk/performance metrics (#54)
* feat(family-15): add 17 risk/performance metrics Implements Family 15 pragmatically as standard `Indicator`s instead of a separate `wickra-metrics` crate. Input is scalar `f64` per bar — period return, equity sample, or per-trade P&L depending on the metric. Scalar `Indicator<f64>` (14): - SharpeRatio(period, risk_free) - SortinoRatio(period, mar) - CalmarRatio(period) - OmegaRatio(period, threshold) - MaxDrawdown(period) — rolling, peak-to-trough - AverageDrawdown(period) - DrawdownDuration — cumulative, bars under water (u32 output) - PainIndex(period) - ValueAtRisk(period, confidence) - ConditionalValueAtRisk(period, confidence) - ProfitFactor(period) - GainLossRatio(period) - RecoveryFactor — cumulative, net return / max drawdown - KellyCriterion(period) Two-series `Indicator<(f64, f64)>` for (asset, benchmark) returns (3): - TreynorRatio(period, risk_free) - InformationRatio(period) - Alpha(period, risk_free) — Jensen / CAPM Touchpoints: - 17 new files under `crates/wickra-core/src/indicators/`. - `mod.rs` + `lib.rs` re-exports. - Python bindings (`bindings/python/src/lib.rs`, `__init__.py`). - Node bindings (`bindings/node/src/lib.rs`, `index.js`). - WASM bindings (`bindings/wasm/src/lib.rs`). - Fuzz: scalar metrics appended to `indicator_update.rs`; new `indicator_update_pair.rs` fuzz target for `(f64, f64)` indicators. - Python tests: SCALAR + new PAIR parameter lists in `test_new_indicators.py`, reference-value cases in `test_known_values.py`. - Node tests: scalar factories + new pair-factory block in `bindings/node/__tests__/indicators.test.js`. - Benches: 5 Family-15 benches added in `crates/wickra/benches/indicators.rs`. - Docs: README family-table row + counter (71 -> 88), CHANGELOG entry under [Unreleased]. Note: Family 12 (statistik-regression, PR #51) introduces `node_pair_indicator!` and `wasm_pair_indicator!` macros for Pearson / Beta / Spearman. Family 15 needs the same pair-input pattern but Family 12 is not yet in main, so the three pair wrappers below are written by hand in this PR. When PR #51 lands, the trivial merge-conflict is resolved by keeping the macros from Family 12 and re-using them for Treynor / IR / Alpha (drop the three handwritten wrappers). cargo check --workspace --all-features: green. * fix(family-15): satisfy clippy doc_markdown / if_not_else / digit_grouping * fix(family-15): unused TreynorRatio import, duplicate pairFactories, _eq_nan inf handling * fix(family-15): node eq() handles matching infinities for ratio indicators * test(family-15): cover cold paths flagged by codecov patch |
||
|
|
55284a3042 |
feat(family-14): add 15 candlestick patterns (#53)
* feat(family-14): add 15 candlestick patterns Introduces the Candlestick Patterns family (block A of the family-14 spec) as scalar f64 indicators on Candle inputs. Each detector emits +1.0 for a bullish reading, -1.0 for a bearish reading, and 0.0 when no pattern is present. Doji is direction-less and emits +1.0 / 0.0 only. New indicators (15): - Doji - Hammer - InvertedHammer - HangingMan - ShootingStar - Engulfing - Harami - MorningEveningStar (signed: +1.0 morning star, -1.0 evening star) - ThreeSoldiersOrCrows (signed: +1.0 soldiers, -1.0 crows) - PiercingDarkCloud (signed: +1.0 piercing, -1.0 dark cloud) - Marubozu (signed: +1.0 bullish, -1.0 bearish, 5 percent shadow tolerance default) - Tweezer (signed: +1.0 bottom, -1.0 top, 10 bps relative tolerance default) - SpinningTop (direction-signed indecision) - ThreeInside (confirmed Harami) - ThreeOutside (confirmed Engulfing) MVP scope notes: - Pattern-shape check only, no trend filter applied. Caller combines with a trend indicator for actionable signals. Documented in every doc comment. - Block B (Harmonic patterns) and block C (Chart patterns) remain out-of-scope and will follow when the pattern-detection framework (pivot detector, multi-bar state machines) lands. Touched across all bindings: Python, Node, WASM. Fuzz target, Python tests (streaming-vs-batch + reference values), Node tests (streaming-vs-batch + reference values), and a representative bench subset (1-, 2- and 3-bar patterns) added. README family table + indicator counter (71 -> 86, eight -> nine families) and CHANGELOG [Unreleased] updated. * fix(family-14): unpack MULTI values with *_ to handle 3-element tuples * cov(family-14): cover Default impl cold paths and MorningEveningStar guard branches |
||
|
|
9b8e1346ed |
feat(family-16): add ValueArea + InitialBalance + OpeningRange (#52)
* 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. |
||
|
|
05fcdd9a5e |
feat(family-12): add 13 Statistik/Regression indicators (#51)
* feat(family-12): add 13 Statistik/Regression indicators Brings the Price Statistics family to 20 indicators (7 → 20) and the total catalogue to 84 (71 → 84). Every indicator ships in the Rust core plus Python, Node, and WASM bindings with full streaming ↔ batch parity, fuzz coverage, and benches. Scalar (f64 → f64): - Variance, CoefficientOfVariation: rolling population variance and its dimensionless ratio with the mean. O(1) updates. - Skewness, Kurtosis: rolling Pearson skewness and excess kurtosis, derived from running sums of x, x², x³, x⁴ via the binomial identities — also O(1) per bar. - StandardError, DetrendedStdDev: standard error of estimate (n − 2) and population StdDev (n) of OLS residuals, sharing the LinReg O(1) sliding sums. - RSquared: coefficient of determination of the rolling OLS fit; the trend-quality filter, clamped to [0, 1]. - MedianAbsoluteDeviation: robust dispersion estimator; O(period log period) per emission via two in-place sorts of a reusable scratch buffer. - Autocorrelation(period, lag): rolling lag-k Pearson autocorrelation. - HurstExponent(period, chunks): R/S-analysis trend-persistence estimator clamped to [0, 1]. Pair indicators (Input = (f64, f64)): - PearsonCorrelation: rolling cross-series Pearson, O(1). - Beta: rolling OLS slope of asset vs. benchmark (CAPM). - SpearmanCorrelation: rolling rank correlation with mid-rank tie handling; O(period log period). Touchpoints: - crates/wickra-core: 13 new indicator modules + mod.rs / lib.rs re-exports. - bindings/python: pyclasses + add_class registration + __init__.py import & __all__ updates. The pair indicators expose update(x, y) and batch(x, y) over two equally-sized numpy arrays. - bindings/node: scalar indicators via node_scalar_indicator! macro; pair indicators via new node_pair_indicator! macro; explicit structs for Autocorrelation and HurstExponent (two-arg ctors). index.js extended with the new exports. - bindings/wasm: scalar wrappers via wasm_scalar_indicator!; pair wrappers via new wasm_pair_indicator! macro. - fuzz: every scalar drove through the generic helper; pair indicators stress-tested by pairing adjacent samples of the fuzz input. - Python tests (test_new_indicators.py): added to SCALAR parametrisation, plus algebraic reference values (variance of [2,4,6] = 8/3, MAD ignoring outlier = 0, monotone non-linear Spearman = 1, two-to-one Beta = 2, etc.) and a streaming-vs-batch test for the pair indicators. - Node tests (indicators.test.js): extended the scalar factories map and added a pair-indicator section with the same algebraic reference values. - crates/wickra/benches: bench_scalar entries for all 10 single- input new indicators. - README: counter 71 → 84; Price Statistics family-table row expanded with the 13 new indicators. - CHANGELOG: Unreleased section documents the family addition. Wiki drafts (ghost-ignored, manual sync to wickra.wiki at release time): indicator-ideas/families/wiki/family-12-statistik-regression/ contains 13 deep-dive pages plus _Sidebar / Indicators-Overview / Warmup-Periods / Home fragments for the curator merge. cargo check --workspace --all-features: clean. * fix(family-12): remove unreachable defensive guards in hurst_exponent The three guards (m < 2 continue, end > buf.len() break, denom == 0.0 return) are by-construction unreachable given the constructor invariant period >= 2 * chunks: m = period / k for k in 1..=chunks always satisfies m >= 2 and end = (c+1) * m <= k * m <= period = buf.len(), and m_1 = period and m_2 = period / 2 are always distinct so the slope denominator is strictly positive. Removing them brings codecov/patch back to 100%. |
||
|
|
5aa0949bce |
feat(family-13): add Ichimoku + Heikin-Ashi (#50)
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. |
||
|
|
7a18a26daf |
feat(family-10): add 16 Ehlers / Cycle (DSP) indicators (#49)
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.
|
||
|
|
4f9ed34884 |
feat(family-11): add DeMark suite (TD Setup, Sequential, DeMarker, REI, Pressure) (#48)
* feat(family-11): add DeMark suite (TD Setup, Sequential, DeMarker, REI, Pressure)
Family 11 (DeMark) was previously empty; this PR adds five
streaming-first DeMark indicators in one batch.
- **TD Setup** (`TdSetup`): parameterised buy/sell setup counter.
Counts consecutive bars whose close is less-than (buy) or
greater-than (sell) the close `lookback` bars earlier, saturating
at `target`. Emits a signed `f64` so callers read direction from
the sign and run length from the magnitude. Classic config:
`lookback = 4`, `target = 9`.
- **TD Sequential** (`TdSequential`): the canonical Setup + Countdown
exhaustion pattern. Output struct `{ setup, countdown, direction }`
exposes both phase counts as signed numbers plus the active
countdown direction (+1 buy / -1 sell / 0 none). Countdown
activates when a setup completes and tracks the close-vs-high/low
comparison `countdown_lookback` bars back, capped at
`countdown_target`. Classic: 4/9/2/13.
- **TD DeMarker** (`TdDeMarker`): bounded [0, 1] oscillator from the
rolling average of upward high expansion (DeMax) and downward low
expansion (DeMin). Falls back to the neutral 0.5 on a flat market
(denominator zero).
- **TD REI** (`TdRei`): Range Expansion Index, bounded [-100, 100].
Per-bar numerator gated on a range-overlap condition vs the bars
5 and 6 back, normalised by a `period`-bar sum of absolute moves.
Classic period = 5. Saturates at +100 in a slow steady uptrend
and at -100 in the mirror downtrend; emits 0 on a flat market.
- **TD Pressure** (`TdPressure`): volume-weighted buying / selling
pressure normalised to [-100, 100]. Per-bar pressure is the
intra-bar close-vs-open ratio scaled by volume; the output is the
rolling mean divided by the rolling mean volume. Zero-range bars
contribute zero (avoid the undefined ratio) and a flat zero-volume
window falls back to 0.
Bindings: all five exposed in Python (`ta.TDSetup`, `ta.TDSequential`,
`ta.TDDeMarker`, `ta.TDREI`, `ta.TDPressure`), Node (`wickra.TDSetup`
etc.), and WASM. Multi-output classes (`TDSequential`) return either
a struct `{ setup, countdown, direction }` per bar (streaming) or a
flat interleaved Float64Array of length `3 * n` (batch).
Tests: 47 unit tests across the five new core files (pure-trend
saturation, flat-market neutral fallback, batch-equals-streaming,
zero-parameter rejection, reset semantics, accessors). Python
test_new_indicators.py picks up all five plus a multi-output TD
Sequential block. Node indicators.test.js picks up all five.
Reference values added to test_known_values.py.
Fuzz: candle fuzz target sweeps all five DeMark indicators with the
existing `Vec<f64>` -> `Vec<Candle>` driver.
Benches: BTCUSDT 1-minute dataset benches for each DeMark indicator
in `crates/wickra/benches/indicators.rs`.
Docs: README family table gains a "DeMark" row; indicator counter
bumped 71 -> 76. CHANGELOG entry added under [Unreleased]. Wiki
drafts (deep-dive pages + Sidebar / Overview / Warmup-Periods / Home
deltas) live under `indicator-ideas/families/wiki/family-11-demark/`
for manual merge into the wiki repo.
* feat(family-11): add 7 missing DeMark indicators
Complete the DeMark suite (family 11) with the seven indicators not
covered by the first commit: TD Combo, TD Countdown, TD Lines (TDST),
TD Range Projection, TD Differential, TD Open, and TD Risk Level.
- TdCombo: aggressive countdown variant with three strictness rules
on top of the classic close-vs-low/high lookback rule (monotone
low/high, monotone close vs prior bar).
- TdCountdown: standalone 13-bar countdown packaging only the signed
countdown count (the setup machine runs internally).
- TdLines: TDST horizontal support/resistance levels from the
highest-high / lowest-low bars of the most-recently-completed
setup, exposed as a multi-output struct.
- TdRangeProjection: DeMark X-projection of the next bar's high and
low from the current bar's OHLC via an open-vs-close-weighted
pivot (three branches: close<open, close>open, close==open).
- TdDifferential: two-bar buying-pressure vs selling-pressure
reversal pattern emitting +1/-1/0.
- TdOpen: gap-and-fade reversal pattern (open outside prior range
with subsequent recovery into it) emitting +1/-1/0.
- TdRiskLevel: protective stop levels derived from the setup
extreme bar +/- its true range.
All seven are wired through Rust core, Python, Node and WASM
bindings, registered in the candle-stream fuzz target, given
benchmark entries on the BTCUSDT 1-minute dataset, and covered by
streaming-vs-batch equivalence, reference-value, lifecycle and
input-validation tests on the Python and Node sides. README counter
moves 76 -> 83 and the CHANGELOG "family 11" entry is extended to
list all twelve indicators.
* fix(td_risk_level tests): check first emission at idx 12, not last bar
TdRiskLevel re-ratchets the sell-risk level on each subsequent setup
completion, so a strictly rising series produces 22.0 at idx 19 (latest
setup) rather than 15.0 (first setup). The test comment already named
idx 12 as the reference; switch the assertion from out[-1] to out[12]
to match the reference computation.
* test(family-11): cover buy-direction branches in TD indicators
Add downtrend tests to TdSequential, TdCombo and TdCountdown so the
buy-side countdown/combo increment branches are exercised; remove an
empty `if buy_countdown == target {}` block in TdSequential whose
behavior is already enforced by the outer strict `<` guard.
Closes codecov/patch gaps reported on PR #48 (10 missed lines across
the three files).
|
||
|
|
7e1e988596 |
feat(family-08): Pivots & Support/Resistance (7 indicators) (#47)
* feat(family-08): add Classic, Fibonacci, Camarilla, Woodie and DeMark pivots + Williams Fractals + ZigZag
Seven new indicators land the previously empty Pivots & S/R family
(family 08), each implemented in wickra-core with the full Indicator
trait surface (update / reset / warmup_period / is_ready / name),
exposed across Python (PyO3), Node (napi-rs) and WASM (wasm-bindgen)
with the standard streaming + batch APIs, and covered by Rust unit
tests, Python streaming-vs-batch + reference-value tests, Node
streaming-vs-batch tests, the candle-input fuzz target and Rust
microbenchmarks.
- ClassicPivots (7 levels): PP = (H+L+C)/3, three R/S tiers per the
floor-trader formulas.
- FibonacciPivots (7 levels): PP plus R/S spaced by 0.382 / 0.618 /
1.000 of the prior range.
- Camarilla (9 levels): Nick Stott's four-tier `C +/- (H - L) * 1.1 /
{12, 6, 4, 2}` levels.
- WoodiePivots (5 levels): close-weighted PP = (H + L + 2*C) / 4 plus
two R/S tiers.
- DemarkPivots (3 levels): conditional X sum based on the previous
bar's open-vs-close relationship.
- WilliamsFractals: five-bar swing detector emitting optional up/down
fractal prices at the centre of each window.
- ZigZag: percent-threshold swing tracker, non-repainting; emits the
just-completed extreme and direction on confirmed reversals only.
README family table updated to nine families / 78 indicators;
CHANGELOG records the family-08 addition under [Unreleased].
* fix(family-08 tests): unify MULTI dict to 3-tuple (factory, batch_call, k)
The HEAD-side family-08 test parametrised MULTI[name] as
`(factory, batch_call, output_arity)` so that pivots with arity 3/5/7/9
fit the same harness. Main's entries arrived as 2-tuples; convert them
all to the 3-tuple shape so `make, batch_call, k = MULTI[name]` unpacks
cleanly. Lifecycle test now indexes the tuple instead of destructuring.
* test(zig_zag): tighten flat-oscillation test (drop dead counter branch)
The previous version of `small_oscillations_yield_no_swings` counted
emitted swings, but the assertion proves the counter never increments
so codecov flagged `emitted += 1` as uncovered. Switch to a per-bar
`assert!(...is_none())` — same coverage of the no-swing path, no dead
branch.
|
||
|
|
f10b8c2e2d |
feat(family-09): add 7 trailing stops (HiLo, Volty, Yo-Yo, Donchian, Pct, Step, Renko) (#46)
* feat(family-09): add 7 trailing stops (HiLo, Volty, Yo-Yo, Donchian, Pct, Step, Renko)
Rounds out the Trailing Stops family from 5 to 12 indicators:
- HiLoActivator (Crabel): SMA-of-high/SMA-of-low trail with a one-bar
lag; emits the opposite-side SMA as the trailing stop.
- VoltyStop (Cynthia Kase): ATR trail anchored on the extreme close
since the trade was opened — tighter than AtrTrailingStop on
pullbacks.
- YoyoExit: long-only ATR trail with an explicit re-entry trigger at
trail + multiplier*ATR; exposes an in_trade flag.
- DonchianStop (Turtle): lowest low / highest high over the window;
multi-output {stop_long, stop_short}.
- PercentageTrailingStop: fixed-percent trail that scales across
instruments without per-asset tuning.
- StepTrailingStop: snaps to a step_size-aligned grid; mirrors
discretionary stop-by-hand workflow.
- RenkoTrailingStop: block-anchored trail; only moves on full-block
advances, ignores intra-block noise.
All seven are wired into wickra-core, the Python / Node / WASM
bindings, the indicator_update + indicator_update_candle fuzz targets,
the wickra bench harness, and the Python + Node test suites. README
counter bumps from 71 to 78; CHANGELOG entry under [Unreleased].
* fix(family-09): satisfy pedantic clippy lints
- hilo_activator: rewrite match-Some/None as if-let-else (single_match_else),
add backticks around the HiLo identifier in module/struct doc (doc_markdown).
- percentage / step / renko trailing stop tests: use f64::from(i32) instead
of `as f64` (cast_lossless).
- bench `benches()` is now >100 lines after Family 09 was wired in; allow
too_many_lines (matches the python pymodule fn).
|
||
|
|
880a0e7430 |
feat: Family 07 Volume - 6 new volume-flow indicators (#45)
* feat(kvo): add Klinger Volume Oscillator
Stephen J. Klinger's trend-aware volume-force MACD. Each bar produces a 'volume force' (vf) signed by the local trend (+1 / -1 / carry) and scaled by the ratio of the current accumulation horizon to its previous trend. KVO = EMA(vf, fast) - EMA(vf, slow), classic (34, 55).
Rust core (Kvo) with 7 unit tests (rejects zero / fast>=slow, accessors, constant series collapses to 0, warmup lands at slow+1, batch == streaming, reset clears state), plus Python (PyKvo + KVO export), Node (KvoNode), and WASM (WasmKvo) bindings. Fuzz target adds Kvo to the candle-input sweep, bench adds the candle-input KVO benchmark, README counter 71 -> 72 + family table row, CHANGELOG [Unreleased].
* feat(volume-oscillator): add Volume Oscillator (VO)
Percent difference between a fast and a slow SMA of the bar volume: 100 * (SMA(vol, fast) - SMA(vol, slow)) / SMA(vol, slow). Default (14, 28). The line stays near zero in stable conditions; positive readings show rising short-term participation, negative readings show waning interest.
Rust core (VolumeOscillator) with 8 unit tests (period validation, accessors, constant volume == 0, zero-volume window defensive branch, two reference values verified algebraically, batch == streaming, reset), plus Python (PyVolumeOscillator + VolumeOscillator export), Node (VolumeOscillatorNode), and WASM (WasmVolumeOscillator) bindings. Fuzz target adds VolumeOscillator to the candle-input sweep, bench adds the volume_oscillator benchmark, README counter 72 -> 73 + family table row, CHANGELOG [Unreleased].
* feat(nvi-pvi): add Negative & Positive Volume Index
Paul Dysart's cumulative volume-flow indices, popularised by Norman Fosback in 'Stock Market Logic'. Both run from a 1000.0 baseline and only update on a specific direction of volume change:
- NVI updates on volume-contraction bars (volume_t < volume_{t-1}), absorbing the percent close change. Tracks the 'smart money' leg per Fosback.
- PVI updates on volume-expansion bars (volume_t > volume_{t-1}). Tracks the 'crowd' leg.
Both expose with_baseline(f64) for custom starting indexes. The NVI/PVI pair is listed as a single line in indicator-ideas/families/07-volume.md and shares the same lifecycle/test/binding surface, so they ship as one commit.
Rust core (Nvi, Pvi) with 9 unit tests each (accessors, baseline seed, volume direction branches, zero-prev-close guard, custom baseline, batch == streaming, reset), plus Python (PyNvi/PyPvi + NVI/PVI exports), Node (NviNode/PviNode), and WASM (WasmNvi/WasmPvi) bindings. Fuzz target adds Nvi+Pvi to the candle-input sweep, bench adds nvi+pvi entries, README counter 73 -> 75 + family table row, CHANGELOG [Unreleased].
* feat(family-07): add Williams A/D, Anchored VWAP, Demand Index, TSV, VZO, Market Facilitation Index
Finishes the volume-flow family with the remaining (new) entries from
indicator-ideas/families/07-volume.md.
Indicators added:
- Williams A/D (`WilliamsAD`): Larry Williams' volume-less cumulative
accumulation/distribution line. Anchors each bar's contribution to
the previous close via true-high/true-low (gap-aware).
- Anchored VWAP (`AnchoredVwap`): cumulative VWAP whose accumulation
starts at a user-chosen anchor bar. Exposes `set_anchor()` (queued
to the next `update`) for click-to-anchor workflows. Reset clears
both state and pending-anchor flag.
- Demand Index (`DemandIndex`): James Sibbet's smoothed buying-vs-
selling pressure, in the streaming-friendly textbook form
`EMA(volume * close-return * (1 + range/close), period)`.
- Time Segmented Volume (`Tsv`): Don Worden's rolling window-sum of
`(close_t - close_{t-1}) * volume_t`. Default `period = 18`.
- Volume Zone Oscillator (`Vzo`): Walid Khalil's normalised volume-flow
oscillator bounded in `[-100, +100]`, defined as
`100 * EMA(signed_volume) / EMA(volume)`.
- Market Facilitation Index (`MarketFacilitationIndex`): Bill Williams'
per-bar `(high - low) / volume`. Returns `None` on zero-volume bars.
All six indicators ship with unit tests (`rejects_zero_period` where
applicable, `accessors_and_metadata`, constant-series behaviour,
batch == streaming equivalence, reset semantics, and reference-value
or saturation-extreme tests), Python / Node / WASM bindings, fuzz
coverage in `indicator_update_candle`, a `bench_candle_input` line per
indicator, README + CHANGELOG entries, and Python reference-value
tests in `test_new_indicators.py`.
The README indicator counter advances 75 -> 81.
* test(family-07): cover defensive cold paths + Default impls
- ad_oscillator: exercise `value()` after first emission.
- kvo: cover the `cm == 0.0` zero-OHLC defensive branch.
- nvi / pvi: exercise the Default impls.
|
||
|
|
6287bd48c1 |
feat: Family 06 Trend-Strength - 5 new directional/random-walk indicators (#44)
* feat(adxr): add Wilder Average Directional Movement Index Rating
ADXR is the trend-strength smoother Wilder published alongside ADX in
*New Concepts in Technical Trading Systems* (1978):
ADXR_t = (ADX_t + ADX_{t - (period - 1)}) / 2
The lookback length is the same period that feeds the underlying ADX.
Because the older ADX is period - 1 bars stale, ADXR responds more
slowly than ADX and is the canonical metric for comparing
trend-strength across instruments.
Implementation reuses the existing wickra_core::Adx engine plus a
period-length ring of past ADX values; warmup is 3 * period - 1
(41 for period = 14). Bindings: Python PyAdxr (PyArray1 batch),
Node AdxrNode (number scalar), WASM WasmAdxr. Fuzz target covers
the candle-input path. Python + Node streaming-vs-batch tests
parametrised, plus a pure-uptrend reference value (ADXR == 100
when ADX saturates at 100). Criterion bench added under crates/
wickra/benches/indicators.rs.
README family table and indicator counter updated (71 -> 72).
* feat(rwi): add Mike Poulos Random Walk Index
RWI compares actual price displacement to what a random walk would
produce over the same horizon: for each lookback i in [2, period],
RWI_High_t(i) = (high_t - low_{t-i+1}) / (ATR_i(t) * sqrt(i))
RWI_Low_t(i) = (high_{t-i+1} - low_t) / (ATR_i(t) * sqrt(i))
Per-bar output is the maximum across lookbacks for each direction;
a reading > 1 means the trend beats random-walk noise, > 2 is the
typical strong-trend threshold. Multi-output (high, low). period
must be >= 2 (the shortest meaningful lookback); period < 2 returns
InvalidPeriod. Warmup = period (e.g. 14 for the standard default).
Bindings: Python PyRwi (PyArray2 shape (n, 2)), Node RwiNode +
RwiValue struct, WASM WasmRwi (Object/Reflect for update,
Float64Array interleaved for batch). Fuzz target adds the candle
input case. Python parametric streaming-vs-batch test and pure
uptrend reference test (RWI_High dominates RWI_Low and exceeds 1).
Node parametric streaming-vs-interleaved-batch test. Criterion
bench under crates/wickra/benches/indicators.rs.
README family table and indicator counter updated (72 -> 73).
* feat(tii): add M.H. Pee Trend Intensity Index
TII is a [0, 100] oscillator that asks 'what fraction of the recent
SMA deviations are positive?'. The construction is
dev_t = close_t - SMA(close, sma_period)_t
SD_pos = sum of positive dev_t over the last dev_period bars
SD_neg = sum of |negative dev_t| over the last dev_period bars
TII = 100 * SD_pos / (SD_pos + SD_neg)
Saturates at 100 on a pure uptrend (every close above the lagging
SMA), at 0 on a pure downtrend, and returns the neutral mid-point 50
on a perfectly flat window. The output is clamped to [0, 100] as
the rolling-sum subtraction loop can accumulate a few ULP of error
on long histories. Canonical Pee parameters (sma_period=60,
dev_period=30) wired as Python defaults; warmup is
sma_period + dev_period - 1 (89 for the defaults).
Bindings: Python PyTii (PyArray1 batch), Node TiiNode (scalar
update + batch), WASM WasmTii via the two-arg wasm_scalar_indicator!
macro. Fuzz target adds the scalar path. Python parametric
streaming-vs-batch test plus pure-uptrend (TII == 100) and
flat-market (TII == 50) reference tests. Node parametric
streaming-vs-batch test. Criterion bench under crates/wickra/
benches/indicators.rs.
README family table and indicator counter updated (73 -> 74).
* feat(kst): add Pring Know Sure Thing oscillator
KST is Martin Pring's long-horizon momentum gauge: four smoothed
rate-of-change components combined with fixed weights (1, 2, 3, 4),
plus an SMA signal line.
RCMA_i = SMA(ROC(close, roc_i), sma_i) for i in 1..=4
KST = 1*RCMA_1 + 2*RCMA_2 + 3*RCMA_3 + 4*RCMA_4
Signal = SMA(KST, signal_period)
Kst::classic() exposes Pring's recommended parameter set
(roc = (10, 15, 20, 30), sma = (10, 10, 10, 15), signal = 9);
warmup = max(roc_i + sma_i) + signal_period - 1 (53 for the classic
parameters). All four parallel branches are fed unconditionally so
they warm in lock-step.
Bindings: Python PyKst (PyArray2 shape (n, 2)) with a KST.classic()
staticmethod, Node KstNode + KstValue with a KST.classic() factory,
WASM WasmKst with both new(...) and classic() constructors plus
Object/Reflect for update and Float64Array for batch. Fuzz target
adds the scalar multi-output path. Python tests gain a new
MULTI_SCALAR section parametric over scalar-input/multi-output
indicators, plus a classic-on-constant-series reference test. Node
tests gain a KST entry in the multi-output section. Criterion
benchmark added under crates/wickra/benches/indicators.rs.
README family table and indicator counter updated (74 -> 75).
* feat(wave-trend): add LazyBear Wave Trend Oscillator
Two-line mean-reverting momentum gauge built from the typical price
and three cascaded EMAs:
ap = (high + low + close) / 3
esa = EMA(ap, channel_period)
d = EMA(|ap - esa|, channel_period)
ci = (ap - esa) / (0.015 * d)
wt1 = EMA(ci, average_period)
wt2 = SMA(wt1, signal_period)
WaveTrend::classic() exposes LazyBear's defaults
(channel = 10, average = 21, signal = 4); warmup is
2 * channel_period + average_period + signal_period - 3 (42 for the
classic defaults). On a perfectly flat market the SMA-seeded EMA
introduces a single-ULP drift between ap and esa, which on a tiny d
would make the ratio explode to -1/0.015 = -66.67; a price-scaled
flat-tolerance guard (d <= 16 * EPSILON * max(|esa|, 1)) collapses
the channel index to 0 in that regime so both lines remain at zero.
Bindings: Python PyWaveTrend (PyArray2 shape (n, 2)) with a
WaveTrend.classic() staticmethod, Node WaveTrendNode + WaveTrendValue
with a WaveTrend.classic() factory, WASM WasmWaveTrend with both
new(...) and classic() constructors. Fuzz target adds the candle
multi-output path (sorted alphabetically). Python parametric
streaming-vs-batch test plus a flat-market reference test. Node
parametric streaming-vs-interleaved-batch test. Criterion bench
under crates/wickra/benches/indicators.rs.
README family table and indicator counter updated (75 -> 76).
* fix(family-06): re-add KST::classic() factory + drop dup fuzz block
Family-06 PR's tests call ta.KST.classic() / wickra.KST.classic() — main's
KST binding shipped without the static factory. Add classic() in Python
(staticmethod) and Node (napi factory); WASM already had it. Also drop the
duplicate Kst::classic().unwrap() block in fuzz/indicator_update.rs that
the merge left behind (main's API no longer returns Result).
* test(rwi): drop dead count==0 guard
The loop `for i in 2..=period` makes `count = tr_end - tr_start = i - 1`
which is always >= 1, so the `if count == 0 { continue; }` branch was
unreachable defensive code that codecov flagged on the family-06 PR.
|
||
|
|
54194a4ff8 |
feat: Family 05 Bands & Channels - 11 new price-envelope indicators (#43)
* feat(bands-channels): add Family 05 with 11 indicators
Eleven price-envelope overlays organised into a new "Bands & Channels"
family, exposed across all four bindings (Rust core, Python, Node, WASM)
plus fuzz/test/bench/docs coverage:
- MaEnvelope - SMA centerline with fixed-percent envelope (the oldest
band overlay still in regular use).
- AccelerationBands (Price Headley) - momentum-biased bands that widen
with the bar's relative range (H - L) / (H + L).
- StarcBands (Stoller Average Range Channel) - SMA(close) +/- k*ATR;
Keltner's SMA-centerline sibling.
- AtrBands - close-anchored envelope of width k*ATR; the standard
volatility-targeting stop/target band.
- HurstChannel - SMA centerline wrapped by the rolling high-low range
(Brian Millard / Hurst-cycle channel).
- LinRegChannel - rolling OLS endpoint +/- k * population stddev of the
residuals; dispersion about the trend rather than the mean.
- StandardErrorBands - regression line +/- k * OLS standard error
(denominator n - 2) for prediction-interval bands.
- DoubleBollinger (Kathy Lien) - two concentric BB envelopes
(typically +/- 1 sigma and +/- 2 sigma) for the zone-partition setup.
- TtmSqueeze (John Carter) - BB-inside-KC squeeze flag paired with a
detrended-close linear-regression momentum reading.
- FractalChaosBands - Bill Williams 5-bar fractal high/low envelope.
- VwapStdDevBands - cumulative VWAP with volume-weighted population
standard deviation bands.
Each indicator ships:
- Core impl with the full Indicator trait, classic() where applicable,
and unit tests (rejects_zero_period / multiplier, accessors, flat
market, monotonic ordering, batch == streaming, reset, plus
algebraically verifiable reference values).
- Python PyO3 binding with multi-column NumPy batch (PyArray2).
- Node napi binding with #[napi(object)] struct + interleaved flat
batch.
- WASM wasm-bindgen binding via Object/Reflect for update +
Float64Array for batch.
- Fuzz coverage in fuzz_targets/indicator_update{,_candle}.rs.
- Python streaming-vs-batch parametric test + reference test.
- Node streaming-vs-interleaved-batch test + reference test.
- Criterion microbench under crates/wickra/benches/indicators.rs.
README family table, README indicator-count line, and CHANGELOG
Unreleased entry updated: indicator total rises from 71 to 82 across
nine families. Wiki pages are updated in a separate commit in the
wickra.wiki repo.
* test(acceleration-bands): cover sum_hl==0 zero-price guard
Exercises line 104 (`0.0` branch of the `sum_hl == 0.0` guard) which
was the last patch-coverage miss on the family-05 PR. `Candle::new`
accepts a fully-zero bar so the branch is reachable in principle —
add a degenerate-candle unit test to hit it.
|
||
|
|
3ea0f12b7a |
feat: Family 04 Volatility — RVI / Parkinson / Garman-Klass / Rogers-Satchell / Yang-Zhang (#42)
* feat(rvi): add Relative Volatility Index
Donald Dorsey's RSI-shaped volatility gauge. Partitions the rolling
population standard deviation of close into "up" samples (close rose
since the previous bar) and "down" samples (close fell), Wilder-smooths
each side, and reports 100 * AvgUp / (AvgUp + AvgDown). Output bounded
on [0, 100]; saturates at 100 in pure uptrends, 0 in pure downtrends,
and falls back to 50 on a completely flat series (same undefined-RS
convention as RSI).
Single period parameter (default 10) drives both the stddev window and
the Wilder smoothing constant. First emit lands at index 2*period - 2
(2*period - 1 bars are needed: period to fill the stddev window plus
period - 1 to seed the Wilder averages, overlapping by one bar).
Touchpoints: rvi.rs + mod.rs + lib.rs re-export, PyRvi + __init__.py +
test_new_indicators SCALAR + test_known_values uptrend reference,
RviNode + index.d.ts/index.js + indicators.test.js factory +
reference, WasmRvi via scalar macro, scalar-fuzz target, bench_scalar
entry, README + CHANGELOG.
* feat(parkinson): add Parkinson Volatility
Michael Parkinson's (1980) high-low realised volatility estimator.
Under a driftless Geometric-Brownian-Motion assumption, the extreme
range of a bar carries roughly 5x the variance information of the
close-to-close estimator, so for a given statistical efficiency
Parkinson needs five times fewer samples.
Formula:
sigma^2 = (1 / (4n * ln 2)) * Sum_{i=1..n} (ln(H_i / L_i))^2
out = sqrt(sigma^2) * sqrt(trading_periods) * 100
The output is annualised to a percent in the same style as
HistoricalVolatility (pass `trading_periods = 1` for the raw per-bar
sigma * 100 figure). Two parameters: `period` (default 20) for the
rolling window, `trading_periods` (default 252) for the annualisation
factor. First emit at index `period - 1`.
Touchpoints: parkinson.rs + mod.rs + lib.rs re-export,
PyParkinsonVolatility + __init__.py + test_new_indicators CANDLE_SCALAR
+ test_known_values zero-range reference, ParkinsonVolatilityNode +
index.d.ts/index.js + indicators.test.js factory + reference,
WasmParkinsonVolatility hand-rolled, candle-fuzz target,
bench_candle_input entry, README + CHANGELOG.
* feat(garman-klass): add Garman-Klass Volatility
Garman & Klass (1980) OHLC realised-volatility estimator. Extends
Parkinson's high-low estimator with an open-to-close term, lifting
statistical efficiency from ~5x to ~7.4x relative to close-to-close
stddev under driftless Geometric Brownian Motion.
Formula (per bar):
s_t = 0.5 * (ln(H_t / L_t))^2 - (2*ln(2) - 1) * (ln(C_t / O_t))^2
out = sqrt(max(mean(s_t over `period`), 0)) * sqrt(trading_periods) * 100
The per-bar sample can be marginally negative when the bar has a small
range relative to its open-to-close move; a max(., 0) clamp on the
rolling mean absorbs that and the FP cancellation noise before the
square root.
Still biased on data with meaningful overnight drift -- use Yang-Zhang
when gaps matter. Defaults: `period = 20`, `trading_periods = 252`
(annualised percent, same convention as HistoricalVolatility).
Touchpoints: garman_klass.rs + mod.rs + lib.rs re-export,
PyGarmanKlassVolatility + __init__.py + test_new_indicators
CANDLE_SCALAR + test_known_values zero-movement reference,
GarmanKlassVolatilityNode + index.d.ts/index.js + indicators.test.js
factory + reference, WasmGarmanKlassVolatility hand-rolled,
candle-fuzz target, bench_candle_input entry, README + CHANGELOG.
* feat(rogers-satchell): add Rogers-Satchell Volatility
Rogers, Satchell & Yoon (1994) OHLC realised-volatility estimator.
Unlike Garman-Klass, the per-bar sample is exact under arbitrary
Brownian drift -- the drift component cancels algebraically.
Formula (per bar):
s_t = ln(H_t / C_t) * ln(H_t / O_t) + ln(L_t / C_t) * ln(L_t / O_t)
out = sqrt(max(mean(s_t over `period`), 0)) * sqrt(trading_periods) * 100
Each per-bar sample is also non-negative by construction: with
`Candle::new` guaranteeing H >= max(O, L, C) and L <= min(O, H, C), the
four log factors have predictable signs (ln(H/.) >= 0, ln(L/.) <= 0),
so both products contribute >= 0. The max(., 0) clamp on the rolling
mean is only there to absorb FP cancellation.
Defaults: `period = 20`, `trading_periods = 252` (annualised percent,
same convention as HistoricalVolatility / Parkinson / Garman-Klass).
Touchpoints: rogers_satchell.rs + mod.rs + lib.rs re-export,
PyRogersSatchellVolatility + __init__.py + test_new_indicators
CANDLE_SCALAR + test_known_values zero-movement reference,
RogersSatchellVolatilityNode + index.d.ts/index.js + indicators.test.js
factory + reference, WasmRogersSatchellVolatility hand-rolled,
candle-fuzz target, bench_candle_input entry, README + CHANGELOG.
* feat(yang-zhang): add Yang-Zhang Volatility
Yang & Zhang (2000) drift- and gap-robust OHLC realised-volatility
estimator. Combines three independent components into a single estimate
with minimum variance:
overnight = sample_var(ln(O_t / C_{t-1})) over n bars (close-to-open)
open_close = sample_var(ln(C_t / O_t)) over n bars
rs = mean(ln(H/C)*ln(H/O) + ln(L/C)*ln(L/O)) over n bars
sigma^2_YZ = overnight + k*open_close + (1-k)*rs
k = 0.34 / (1.34 + (n+1)/(n-1))
out = sqrt(max(sigma^2_YZ, 0)) * sqrt(trading_periods) * 100
The overnight and open-to-close variances use Bessel's correction (the
sample estimator, divisor n-1), same convention as
HistoricalVolatility. The blending factor `k` is the one that
minimises estimator variance under driftless Geometric Brownian Motion
with overnight gaps.
This is the gold-standard OHLC estimator for assets with both
close-to-open gaps and intraday drift: equities, futures, and any
market that does not trade continuously. For pure intraday data (where
O_t == C_{t-1} and the open-to-close return is constant), the
overnight and open-close terms vanish and the estimator collapses to
(1-k) * Rogers-Satchell -- this is the indicator's
intraday_data_collapses_to_rs_only unit test.
Period >= 2 (Bessel correction needs >= 2 samples). First emit at
index `period` (the (period+1)-th bar): one bar seeds prev_close, the
next `period` fill the rolling windows. Defaults: `period = 20`,
`trading_periods = 252`.
Touchpoints: yang_zhang.rs + mod.rs + lib.rs re-export,
PyYangZhangVolatility + __init__.py + test_new_indicators
CANDLE_SCALAR + test_known_values zero-movement reference,
YangZhangVolatilityNode + index.d.ts/index.js + indicators.test.js
factory + reference, WasmYangZhangVolatility hand-rolled, candle-fuzz
target, bench_candle_input entry, README + CHANGELOG.
* fix(rvi): rename to RviVolatility to avoid clash with family-02 RVI
Family 02 (PR #40) ships a separate `Rvi` struct for Relative Vigor
Index. The two indicators have nothing to do with each other beyond
sharing the acronym, so disambiguate by giving the volatility one a
longer name everywhere:
- Rust crate: `Rvi` -> `RviVolatility`
- Rust file: `rvi.rs` -> `rvi_volatility.rs`
- Python: `RVI` -> `RVIVolatility`
- Node: `RVI` -> `RVIVolatility`
- WASM: `RVI` -> `RVIVolatility`
Once the two PRs are both merged, callers get `wickra::Rvi` for Vigor
and `wickra::RviVolatility` for Volatility. The shorter `RVI` acronym
stays with the Momentum family per the existing wiki pages and the
implementation that shipped first.
Updates: rvi_volatility.rs (renamed), mod.rs, lib.rs re-export,
bindings/python/src/lib.rs + __init__.py + tests, bindings/node/src/lib.rs
+ index.d.ts + index.js + __tests__, bindings/wasm/src/lib.rs,
fuzz/fuzz_targets/indicator_update.rs, crates/wickra/benches/indicators.rs,
README family-table label, CHANGELOG entry.
* test(volatility): Rename test_rvi -> test_rvi_volatility + drop dead match arms
The Python test test_rvi_pure_uptrend_saturates_at_one_hundred was
calling ta.RVI() expecting the volatility version, but ta.RVI now
means Family 02's Relative Vigor Index (candle input). Renamed to
ta.RVIVolatility to match the binding rename done at merge time.
In all four OHLC volatility tests, the existing `match (r, a) { ...,
_ => panic!() }` arm is dead in passing runs (every aligned pair is
either (None, None) or (Some, Some)). Codecov flagged it as a patch
miss on each of parkinson / garman_klass / rogers_satchell /
yang_zhang. Refactored per CLAUDE.md cold-path guidance to
`assert_eq!(r.is_some(), a.is_some()); if let (Some, Some) ...`.
|
||
|
|
d9d3ad18aa |
feat: Family 03 MACD & Price Oscillators — APO / AO-Hist / CFO / Zero-Lag MACD / Elder Impulse / STC (#41)
* feat(apo): add Absolute Price Oscillator 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. * fix(apo): add PyApo + ApoNode + WasmApo bindings missed from |
||
|
|
1ea05fb2a1 | chore: sync indicator count to 85 [skip ci] | ||
|
|
24e723fa7d |
feat: Family 02 Momentum Oscillators — RVI / PGO / KST / SMI / Laguerre / Connors / Inertia (#40)
* 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.
|
||
|
|
a39adb9dae | chore: sync indicator count to 78 [skip ci] | ||
|
|
466faddd87 |
feat: Family 01 Moving Averages — ALMA / McGinley / FRAMA / VIDYA / JMA / Alligator / EVWMA (#39)
* feat(alma): add Arnaud Legoux Moving Average
Gaussian-weighted moving average with configurable centre (offset in
[0, 1]) and kernel width (sigma > 0). Pre-computes normalised weights
at construction so each update is a single rolling window dot product.
Reference: Arnaud Legoux and Dimitrios Kouzis-Loukas, 2009.
Touchpoints:
- crates/wickra-core: alma.rs + mod.rs + lib.rs re-export
- bindings/python: PyAlma + __init__.py + test_new_indicators +
test_known_values reference
- bindings/node: AlmaNode + index.d.ts/index.js + indicators.test.js
factory + reference value
- bindings/wasm: wasm_scalar_indicator! macro
- fuzz: indicator_update target covers ALMA(9, 0.85, 6.0)
- crates/wickra/benches: bench_scalar entry
- README + CHANGELOG: Moving Averages row + Unreleased entry
* feat(mcginley): add McGinley Dynamic moving average
John McGinley's self-adjusting moving average with the recurrence
MD + (price - MD) / (0.6 * period * (price / MD)^4). Speeds up when
price falls below the indicator and damps when price runs above the
indicator. Seeded with the simple average of the first period inputs.
Reference: McGinley, Technical Analysis of Stocks & Commodities, 1990.
Touchpoints:
- crates/wickra-core: mcginley_dynamic.rs + mod.rs + lib.rs re-export
- bindings/python: PyMcGinleyDynamic + __init__.py + test_new_indicators
+ test_known_values reference
- bindings/node: McGinleyDynamicNode (scalar macro) + index.d.ts/index.js
+ indicators.test.js factory + reference value
- bindings/wasm: wasm_scalar_indicator! macro
- fuzz: indicator_update target covers McGinleyDynamic(10)
- crates/wickra/benches: bench_scalar entry
- README + CHANGELOG: Moving Averages row + Unreleased entry
* feat(frama): add Fractal Adaptive Moving Average
Ehlers' FRAMA adapts its smoothing constant to the fractal dimension of
the recent window: tight tracking in trends, heavy smoothing in chop.
Uses the close-only variant where max/min over each window half drive
the dimension estimate. Period must be even (default 16).
Reference: Ehlers, Fractal Adaptive Moving Average, 2005.
Touchpoints:
- crates/wickra-core: frama.rs + mod.rs + lib.rs re-export
- bindings/python: PyFrama + __init__.py + test_new_indicators +
test_known_values reference (constant series + uptrend tracking)
- bindings/node: FramaNode (scalar macro) + index.d.ts/index.js +
indicators.test.js factory + reference value
- bindings/wasm: wasm_scalar_indicator! macro
- fuzz: indicator_update target covers Frama(16)
- crates/wickra/benches: bench_scalar entry
- README + CHANGELOG: Moving Averages row + Unreleased entry
* feat(vidya): add Variable Index Dynamic Average
Chande's VIDYA — an EMA whose alpha scales with |CMO(cmo_period)| / 100.
Strong directional momentum lifts the smoothing constant toward the
EMA-of-period rate; flat or choppy windows shrink it toward zero so
VIDYA coasts on its previous value. Two parameters: period (14) and
cmo_period (9). Reuses the existing wickra-core Cmo internally.
Reference: Chande, Stocks & Commodities, 1992.
Also fixes a silent gap from
|
||
|
|
e30b3c6b35 |
release: 0.2.7 (Windows ARM64 restored + CPU label fix) (#37)
* chore(docs): rename benchmark CPU from 7950X3D to 9950X
The "Reproduced on" line in the umbrella + binding READMEs and the
benchmark page on the site listed the wrong AMD CPU. The benchmarks
were actually produced on a Ryzen 9 9950X, not a 7950X3D. Same
column for absolute µs values applies — the speedup ratios in the
tables are unchanged either way because they're relative across
libraries on the same machine.
The performance-regression issue template's CPU example also
updated for consistency (it was a generic placeholder, but matching
the canonical machine makes the example concrete).
* chore(npm): restore Windows ARM64 sub-package + napi matrix entry
npm Support unblocked the `wickra-win32-arm64-msvc` package name and
transferred write access to @kingchenc (placeholder 0.0.1-security
was published from their side; we ship our first real version on
top of that). This re-enables every change
|
||
|
|
070be2eb27 |
release: 0.2.6 (docs.rs fix + README table reordering) (#36)
* fix(docs-rs): rename `doc_auto_cfg` to `doc_cfg` after Rust 1.92 merge `doc_auto_cfg` was removed in Rust 1.92.0 and folded back into `doc_cfg` (rust-lang/rust#138907). docs.rs builds with the latest nightly and sets `--cfg docsrs`, so the previous #![cfg_attr(docsrs, feature(doc_auto_cfg))] aborts compilation with E0557 on every published 0.2.x. GitHub CI never tripped this — stable rustc ignores the line because nothing sets the `docsrs` cfg there. Switch all three published library crates (`wickra`, `wickra-core`, `wickra-data`) to the merged-into `doc_cfg` gate. Same intent, same on-docs.rs output, builds again on nightly. * docs(readme): float Wickra to the top of the comparison tables Reorders the "Why Wickra exists" library-comparison table and the two benchmark headers so Wickra is the first row (with a ★ marker) instead of the last. The previous order placed Wickra at the bottom, which buries the only row a reader landing on the README is here to compare against. Same column data, same ★/winner annotations, just the row order flipped and a ★ prefix on the Wickra label. Mirrored across the umbrella README and every binding README so the crates.io / PyPI / npm landing pages stay in sync. * release: bump workspace + bindings to 0.2.6 Workspace, every binding (Python, Node, Node platform stubs), the release.yml comment and the CHANGELOG all move together to 0.2.6 so the next tagged release lines every artefact up. 0.2.6 carries two changes from the [0.2.6] CHANGELOG entry: - fix(docs-rs): swap the now-removed `doc_auto_cfg` feature gate for the merged-into `doc_cfg` so docs.rs nightly builds resume. - docs(readme): float ★ Wickra to the top of every comparison table across the umbrella + binding READMEs. wickra-win32-arm64-msvc stays excluded for this release with the same npm spam-filter rationale that held for 0.2.5. |
||
|
|
b5afc0a7e7 |
release: bump workspace + bindings to 0.2.5 (#35)
Workspace, every binding (Python, Node, Node platform stubs), and the release.yml comment are all updated together so the next tagged release on `v0.2.5` lines every artefact up. Also adds a short README "Disclaimer" section pointing out that Wickra is an indicator toolkit, not a trading system, and that production use is at the caller's own risk. The legal terms in LICENSE (PolyForm Noncommercial 1.0.0, "No Liability") already cover the warranty / as-is language — the README section just makes the trading-specific framing visible without burying it in a click-through. CHANGELOG carries the new 0.2.5 entry with the API addition (`BinanceConfig` + `connect_with_config`) and the best-effort Pong write change in `BinanceKlineStream::next_event`. wickra-win32-arm64-msvc stays excluded for this release with the same npm-spam-filter rationale that held for 0.2.1. |
||
|
|
961fc1f3dc |
chore(node, readme): SPDX license, Node 18 engines, bench hardware spec (R15, R18, R19)
R15 — `bindings/node/package.json` and all six per-platform subpackage
templates (`npm/{darwin-arm64,darwin-x64,linux-arm64-gnu,linux-x64-gnu,
win32-arm64-msvc,win32-x64-msvc}/package.json`) plus the WASM
`release.yml` enrich step switch the `license` field from the npm
convention `SEE LICENSE IN LICENSE` to the SPDX identifier
`PolyForm-Noncommercial-1.0.0`. npm's license search and downstream
tooling can now surface the actual license.
R18 — every `engines.node` field is bumped from `>= 16` to `>= 18`.
The package's test script (`node --test __tests__/`) uses the built-in
`node --test` runner that landed in Node 18; advertising support for
Node 16 / 17 was a guarantee we never verified (CI tests on Node 18 /
20 only) and would have produced a confusing error on those versions.
R19 — README's benchmark section gains an explicit hardware /
software-version block and reframes the absolute µs values as a
relative-speedup snapshot rather than a universal contract. Also tells
the reader how to reproduce locally (`pip install -e
bindings/python[bench]` + `python -m benchmarks.compare_libraries`) and
where the CI artefact lives.
Side effects:
- R16 / R17 (built wheel under `bindings/python/dist/` and
`examples/node/node_modules/`) — verified that both are already
covered by `.gitignore` (`*.whl`, `dist/`, `**/node_modules/`) and
were never tracked by git. The local directories have been cleared;
no `.gitignore` change needed and no committed file removed.
|
||
|
|
a707eb5d62 |
docs: refresh the cross-library benchmark numbers
The README and Streaming-vs-Batch benchmark tables were a stale snapshot
("5 000-bar series", numbers from an older machine). Re-run
`python -m benchmarks.compare_libraries` on the current hardware against
the same peer set (finta + talipp; TA-Lib and pandas-ta stay excluded on
Windows) and replace the tables with the fresh numbers.
The new run uses the script's current defaults: a 20 000-bar batch series
and a 5 000-bar seed + 15 000-bar live streaming workload — both more
representative of real backtests than the previous 5 000 / 2 000-bar
sizes. Wickra still wins every batch row outright (3.5× to 1 244× faster
than the nearest peer) and the streaming RSI is ~13.8× faster than
talipp's incremental implementation.
|
||
|
|
d87005577e |
examples: move the WASM browser demo into a top-level examples/wasm/
Finish the per-language `examples/<lang>/` restructure by relocating the WASM browser demo from bindings/wasm/examples/ to examples/wasm/. * `examples/wasm/index.html` is the moved file; its WASM module import becomes `../../bindings/wasm/pkg/wickra_wasm.js` so the demo still loads the wasm-pack output without copying it. * bindings/wasm/README.md, Quickstart-WASM.md, examples/README.md and the root README "Languages" + project-layout block all point at the new path. The serve command in the docs now says "serve the repository root and open examples/wasm/index.html". `bindings/wasm/examples/` is empty after the move; the now-empty directory is removed. |
||
|
|
8b9e8e30b9 |
examples: move Node examples into a top-level examples/node/
Continue the per-language `examples/<lang>/` restructure: move the three
Node example files (streaming.js, backtest.js, live_trading.js) out of
bindings/node/examples/ and into a top-level examples/node/ directory.
* `examples/node/package.json` is a `private` package that pulls the
native binding via `file:../../bindings/node` and lists `ws` as a
dev-dependency for the live-trading example. `require('..')` in each
file becomes `require('wickra')` — exactly what a downstream user would
write — and the file-header run instructions are updated to the new
two-step workflow (`npm install` in bindings/node, then in
examples/node).
* `backtest.js`'s default-CSV path becomes the much shorter
`__dirname/../data/btcusdt-1d.csv` from the new location.
* `bindings/node/package.json` drops the now-unused `ws` devDependency.
* `.gitignore` is broadened from `bindings/node/node_modules/` to
`**/node_modules/` so the new `examples/node/node_modules/` directory is
not tracked.
* The README "Languages" table, project-layout block and
`examples/README.md` Node section are updated for the new paths and run
commands.
Verified by running `node backtest.js` (3200 BTCUSDT daily bars, matching
output), `node streaming.js`, and `node --check live_trading.js` from the
new location.
|
||
|
|
747d1a5b1b |
examples: move Rust examples into a top-level examples/rust/ crate
The three Rust examples (backtest, fetch_btcusdt, live_binance) used to
live each in their own crate's examples/ dir, splitting the example set
across crates and burying it inside the source tree. Move them into a new
workspace member crate at `examples/rust/` (package `wickra-examples`,
`publish = false`) so all language examples sit under one top-level
`examples/<lang>/` tree.
* `examples/rust/Cargo.toml` declares the per-binary deps (wickra,
wickra-data with the `live-binance` feature always on, serde_json, tokio
for the macro and current-thread runtime).
* `examples/rust/src/bin/{backtest,fetch_btcusdt,live_binance}.rs` are the
three migrated binaries; their doc-comments and the fetch_btcusdt output
path are updated for the new location and run command
(`cargo run -p wickra-examples --bin <name>`).
* Workspace `Cargo.toml` lists the new member; the now-empty
`[dev-dependencies]` extras (`wickra`, `tokio` in wickra-data and
`serde_json` in wickra) that existed only for these examples are dropped.
* The `[[example]] live_binance` table is removed from wickra-data's
manifest since the file moved out.
* README "Languages" + project-layout, examples/README.md, Quickstart-Rust
and Data-Layer are pointed at the new paths and commands.
`cargo build -p wickra-examples` and `cargo run --release -p wickra-examples
--bin backtest -- examples/data/btcusdt-1d.csv` both succeed; the rest of
the workspace (core, data, wickra) builds, clippies (`--all-targets -D
warnings`) and tests (508 core + 28 data + 1 integration + 74+3+1
doctests) all stay green.
|
||
|
|
ba10898801 |
docs: add a cross-language examples index
The top-level examples/ directory held only python/, which made the examples look Python-only even though Rust, Node and WASM all ship their own. Add examples/README.md: a single index of every runnable example across Rust, Python, Node and WASM, each with its run command, plus a note on the bundled BTCUSDT datasets. Point the README "Languages" table at the Node backtest example and link the new index from both the table and the project-layout section. |
||
|
|
2b3a1b7384 |
examples: add real BTCUSDT candle datasets from Binance
Add seven OHLCV datasets under crates/wickra/examples/data/, one per timeframe (1m/5m/15m/1h/12h/1d/1month), holding real BTCUSDT spot klines fetched from the Binance REST API. The new fetch_btcusdt example regenerates them: it paginates the klines endpoint through the system curl, parses with serde_json, validates every candle via Candle::new and keeps only fully closed buckets. The indicator benchmarks now run against the 1m dataset instead of a synthetic series, and a new example_data integration test checks that every file parses and carries evenly spaced, monotonic timestamps. The monthly file is named btcusdt-1month.csv rather than btcusdt-1M.csv so it does not collide with btcusdt-1m.csv on case-insensitive filesystems (Windows, default macOS). |
||
|
|
d2f99efd78 |
F13c: restructure the indicator catalogue into eight families
The original taxonomy was four classical families plus a statistics group, with the F1-F12 expansion slotted in as sub-categories. This regroups the whole 71-indicator catalogue into eight top-level families, each with at least five members: Moving Averages (12), Momentum Oscillators (13), Trend & Directional (9), Price Oscillators (5), Volatility & Bands (12), Trailing Stops (5), Volume (9), Price Statistics (7). - Wiki: docs/wiki/indicators/ reorganised into eight family folders; all 71 indicator pages moved with `git mv`. Every internal cross-link is normalised to `../<family>/Indicator-X.md`, each page's `Family` field is set to its new family, and two pre-existing `../Indicator-Chaining.md` links (should have been `../../`) are corrected. A link check confirms every relative wiki link resolves. - Indicators-Overview.md fully rewritten around the eight families; Home.md indicator reference and the README family table follow suit. - Warmup-Periods.md gains the eight F13 indicators; CHANGELOG records the 46-indicator expansion (25 -> 71) and the eight-family taxonomy. - Tests: Node indicators.test.js and Python test_new_indicators.py cover all eight new indicators (Node 91/91, Python 117/117 green). cargo fmt + clippy (core/wickra/data/wasm/node) clean; 508 core tests, 25 data tests and 74 doctests green. |
||
|
|
2f3b5cc3be |
F-Abschluss: wire the Python package, refresh docs and extend the test suites
Finalises the F1-F12 indicator expansion (25 -> 63 indicators). - Python `wickra/__init__.py`: import and re-export all 63 indicators, grouped by family, with a matching `__all__`. The package previously exposed only the original 25 even though the compiled module and the `.pyi` stubs already carried the rest. - Docs: `Home.md` and `README.md` indicator counts and family tables updated to 63; `Indicators-Overview.md` already restructured per family in F10-F12; `Warmup-Periods.md` gains all 38 new indicators across the single- and multi-output tables (and the stale two-arg `Psar::new` example is corrected to three args); `CHANGELOG.md` `[Unreleased]` lists every new indicator by family. - Tests: `bindings/node/__tests__/indicators.test.js` covers all 63 indicators (streaming==batch plus four new reference-value checks), 80/80 green; new `bindings/python/tests/test_new_indicators.py` covers the 38 additions (streaming==batch, shapes, reference values, lifecycle), Python suite 105/105 green. - `bindings/node/index.js` regenerated by `napi build`. cargo fmt + clippy (core/wickra/data/wasm/node) clean; 454 core tests, 25 data tests, 66 doctests, 80 Node tests and 105 Python tests green; `cargo check -p wickra-wasm --tests` green. |
||
|
|
5568596e18 |
E8: add code coverage to CI
CI had no coverage measurement, and although .gitignore listed coverage artefacts nothing produced them. Add a `coverage` job that runs cargo-llvm-cov over the three pure-Rust crates (with wickra-data's live-binance feature so the Binance parser tests count), emits lcov, and uploads to Codecov. The Codecov upload uses fail_ci_if_error: false so a Codecov outage cannot break CI. Both new actions (taiki-e/install-action, codecov/codecov-action) are SHA-pinned. Add a coverage badge to the README. |
||
|
|
51f64b53c0 |
E7: correct inaccuracies in the README
- "## Indicators in 0.1.0" -> "## Indicators" (the heading drifted from the actual version; making it version-neutral stops the drift). - Project layout: there is no top-level benches/ directory — Rust benches and examples live inside their crate. The tree now shows crates/wickra/benches, the per-crate examples, and the new bindings/node and bindings/wasm examples/ directories. - Node "Example" pointed at a test file; it now points at the real bindings/node/examples/streaming.js (added in E17). - "## Test counts" hardcoded numbers (171/11/56/7) that drift on every added test. Replaced with a version-neutral "## Testing" section that describes what each suite covers, including the WASM tests. |
||
|
|
11293a3b0e |
docs(readme): top-row registry badges + contribution guide + footer star badges
- Top: CI badge, crates.io / PyPI / npm version badges, license tag. - Bottom (new section before License): a short contribution guide with starting points for new indicators, bug fixes, binding work, and style. - Footer: large "give us a star" call-to-action with GitHub stars, forks, and issues badges via shields.io. |
||
|
|
06f67c8c0e |
Move Rust examples into their crates so cargo publish picks them up
The previous `[[example]] path = "../../examples/rust/..."` entries pointed at files outside the crate root. cargo only packages files inside the crate directory, so the examples would have been silently dropped on publish. Moving them to crates/wickra/examples/ and crates/wickra-data/examples/ keeps them as part of the published package and lets cargo discover them automatically (no [[example]] override needed). README and project-layout section updated. |
||
|
|
1295b63e1f |
Wire release pipeline to crates.io, PyPI, and npm
On every v* tag push the release workflow now publishes the project to all three public registries in parallel: - crates.io: wickra-core then wickra-data then wickra, with a 45-second sleep between each so the registry index can refresh before the next publish step asserts the previous version is available. - PyPI: maturin-action builds wheels for Linux x86_64 + aarch64, macOS x86_64 + aarch64, and Windows x64, plus an sdist; all artefacts upload to PyPI via MATURIN_PYPI_TOKEN. - npm: napi-rs builds a native binary per platform (linux-x64-gnu, darwin-x64, darwin-arm64, win32-x64-msvc), publishes each as its own platform package, then publishes the main `wickra` meta-package that resolves to the right platform at install time. - WASM: wasm-pack builds the bundler-target package and publishes it as `wickra-wasm` on npm, with the auto-generated package.json enriched with the author/repo/license metadata. Package metadata was unified so all targets point at kingchenc/wickra on GitHub; the Node binding is now plain `wickra` (the @wickra/ scope was unnecessary because the bare name was free). README install table updated to match. |
||
|
|
d261df4691 |
Relicense under PolyForm Noncommercial 1.0.0
Switches the project from Apache-2.0 to PolyForm Noncommercial 1.0.0. Use, modification, redistribution, forking and contribution are all permitted; the only thing the new license withholds is commercial sale of the software or of services built primarily around it. Updates: LICENSE file, workspace Cargo license metadata, the Python pyproject classifier and SPDX field, and the Node package.json. The README now explains the practical scope in plain English. |
||
|
|
3be267cb03 |
Wickra 0.1.0: streaming-first technical indicators
A multi-language technical analysis library: 25 indicators across trend,
momentum, volatility, and volume families, every one a state machine with
O(1) per-tick updates. Batch evaluation is provided by a blanket extension
trait over the streaming primitive, so live trading bots and historical
backtests run the same code path.
What ships in this initial drop:
crates/wickra-core - 25 indicators, Indicator/BatchExt/Chain traits,
OHLCV types with validation; 171 unit tests,
property tests, Wilder/Bollinger textbook tests.
crates/wickra - top-level facade + criterion benches for every
indicator at 1K/10K/100K series sizes.
crates/wickra-data - streaming CSV reader, tick-to-candle aggregator,
multi-timeframe resampler, Binance Spot kline
WebSocket adapter behind feature live-binance;
11 unit + 1 doctest.
bindings/python - PyO3 + maturin, NumPy I/O, type stubs (.pyi),
56 pytest tests including streaming==batch
equivalence, Wilder reference values, lifecycle.
bindings/node - napi-rs native module, TypeScript .d.ts
auto-generated, 7 node --test cases.
bindings/wasm - wasm-bindgen ES module for browser/bundler/Node;
interactive HTML demo at examples/index.html.
examples/ - Python and Rust scripts: backtest, live trading,
parallel multi-asset, multi-timeframe, Binance.
benchmarks/ - cross-library comparison against TA-Lib,
pandas-ta, finta, talipp; Wickra wins every
category by 11-1030x (batch) and 17x+ streaming.
.github/workflows/ - CI matrix (Rust + Python + Node + WASM on
Linux/macOS/Windows), release pipeline for
PyPI wheels and npm.
Indicators (25):
Trend SMA EMA WMA DEMA TEMA HMA KAMA
Momentum RSI MACD Stochastic CCI ROC WilliamsR ADX MFI TRIX
AwesomeOscillator Aroon
Volatility BollingerBands ATR Keltner Donchian PSAR
Volume OBV VWAP (cumulative + rolling)
cargo clippy --workspace --all-targets -D warnings is clean. License: Apache-2.0.
|