025d453293b4ccca186cd83affefe01ed5bcfbb6
205 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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 |
||
|
|
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.
|
||
|
|
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. |
||
|
|
e6375746d3 |
docs: unify README across crates.io / PyPI / npm / GitHub
Three separate README files (root, bindings/node, bindings/python) had been drifting independently — each registry showed a different project page, which is exactly the consistency debt I want to avoid. Single source of truth: /README.md. The three binding READMEs are overwritten with the root README content as a baseline, and release.yml gets a one-line cp step right before every publishing call so future edits to /README.md propagate automatically: - python-wheels job: cp README.md bindings/python/README.md before PyO3/maturin-action runs the wheel build - python-sdist job: same, before the sdist build - node-publish job: cp ../../README.md README.md (working-directory bindings/node) before the main 'npm publish wickra' - wasm-publish job: cp README.md bindings/wasm/README.md before wasm-pack build (which copies the crate README into pkg/ on its own) Cargo crates (wickra, wickra-core, wickra-data) already inherit readme.workspace = true pointing at /README.md, so crates.io was already correct — no change needed there. The per-platform npm subpackages (bindings/node/npm/<target>/) keep their tiny package.json with no README; they are install-time optionalDependencies that the loader reads through, never user-facing on the registry. Effect: same README on github.com/kingchenc/wickra, crates.io/crates/wickra, pypi.org/project/wickra, and npmjs.com/package/wickra. Will be live on the registries with the next tag-push. |
||
|
|
8aa74cb638 |
release(0.2.1): bump to 0.2.1, skipping Windows ARM64 this cycle
The 0.2.0 release left wickra@npm stuck at 0.1.4 and never created a GitHub Release entry because the brand-new `wickra-win32-arm64-msvc` sub-package name was caught by npm's spam-detection filter on its first publish attempt (same situation that affected `wickra-win32-x64-msvc` through 0.1.4 until npm Support unblocked it). A support ticket is open; until it is resolved, ship 0.2.1 for the five platforms whose sub-packages are already on npm and re-add Windows ARM64 in a follow-up release. Changes for this cycle: - bindings/node/package.json: remove "wickra-win32-arm64-msvc" from optionalDependencies and "aarch64-pc-windows-msvc" from napi.triples.additional. - bindings/node/npm/win32-arm64-msvc/: removed (will be restored fresh once the npm name is unblocked). - .github/workflows/release.yml: comment out the aarch64-pc-windows-msvc entry of the node-build matrix with a TODO/restore note. - Bump every workspace and binding version to 0.2.1 (Cargo.toml, pyproject.toml, bindings/node/package.json, five npm/<target> templates, the wiki version table). Cargo.lock regenerated. - CHANGELOG: new [0.2.1] block consolidating every fix that has landed on main since 0.2.0 (HV epsilon, examples CI step, fuzz cargo-fuzz install, MSRV 1.85 -> 1.86 / 1.77 -> 1.88, criterion 0.5 -> 0.8, tokio-tungstenite 0.24 -> 0.29, tick_aggregator gap-fill cap, every GitHub Action SHA-pin bump). Compare-link added. The arm64 loader branch in bindings/node/index.js is left untouched: a Windows ARM64 user installing 0.2.1 will get the standard `Cannot find module 'wickra-win32-arm64-msvc'` error from the loader, which is accurate. PyPI's win-arm64 wheel is unaffected. Verified locally: cargo fmt/clippy/test --workspace --all-features -> 630 passed / 0 failed cargo build -p wickra-examples --bins -> clean cargo build -p wickra-node -> clean |
||
|
|
30444ce296 |
build: lift MSRV to 1.80 workspace / 1.88 node binding
The previous MSRV pins were below what current transitive dependencies
need:
- rayon-core 1.13.0 (pulled in by the optional `parallel` feature on
wickra-core via rayon) requires rustc >= 1.80; under the old 1.75
workspace MSRV the CI MSRV job broke with "package `rayon-core
v1.13.0` cannot be built because it requires rustc 1.80 or newer".
- napi-build 2.3.2 (the build-script crate that napi-derive 2.x calls
into) requires rustc >= 1.88; under the old 1.77 node-binding MSRV
the CI MSRV-node job broke with "package `napi-build v2.3.2` cannot
be built because it requires rustc 1.88 or newer".
Pinning the deps backwards would have frozen us out of upstream
security/fix releases for both crates. Lifting the MSRV is the cleaner
path for a young 0.x library — downstream consumers on older toolchains
can stay on the already-published 0.2.0.
Updated:
- Cargo.toml workspace rust-version 1.75 -> 1.80
- bindings/node/Cargo.toml rust-version 1.77 -> 1.88
- .github/workflows/ci.yml MSRV matrix names + toolchain values + comment
- CHANGELOG.md [Unreleased] documents the bump
|
||
|
|
39a252ea66 |
release(0.2.0): bump version from 0.1.5 to 0.2.0
This release carries the full post-audit work — 46 new indicators
(25 → 71), an eight-family taxonomy restructure, new bindings for
RollingVWAP, the WASM streaming-update parity, the pyo3/numpy CVE
fix, the SMA/Bollinger drift bound, the O(1) LinearRegression
refactor, the UlcerIndex deque and the PSAR is_ready/reset fixes
plus a refreshed example suite and wiki. The earlier 0.1.5 number
was never published; jumping straight to 0.2.0 is the cleaner signal
for the scope of the change.
Bumped:
- Cargo.toml workspace + wickra-core workspace-dep version
- bindings/python/pyproject.toml
- bindings/node/package.json + optionalDependencies (six platform pins)
- 6 x bindings/node/npm/<target>/package.json
- Cargo.lock regenerated
- CHANGELOG.md [0.2.0] header + compare-link
- docs/wiki/Home.md published-versions table
- docs/wiki/Quickstart-{Rust,Node,WASM}.md + Warmup-Periods.md
version-pinned narrative lines
Verified locally:
- cargo fmt/clippy/test (628 passed, 0 failed)
- cargo deny check (no suppression)
- bindings/node node --test (92/92)
- bindings/python pytest (118/118)
- import wickra reports 0.2.0 with 72 indicator classes
|
||
|
|
c4ffcc2138 |
docs(node): correct doc-comment package name to wickra
The module doc-comment in bindings/node/src/lib.rs still pointed at @wickra/wickra. The package is published as bare "wickra" — every README, Quickstart and example already uses that name. Align the inline doc with the published name. |
||
|
|
896b71fc62 |
release(0.1.5): bump versions, finalize CHANGELOG, fail loud on missing platform binaries (R20, Z2)
Versions bumped to 0.1.5 in every authoritative location:
- workspace `Cargo.toml` (`[workspace.package].version`, the
`wickra-core` path dependency pin).
- `bindings/python/pyproject.toml`.
- `bindings/node/package.json` (main + all six `optionalDependencies`
pins).
- All six per-platform `bindings/node/npm/<target>/package.json`
templates.
CHANGELOG: the accumulated `[Unreleased]` block is promoted to
`[0.1.5] - TBD` (date left for the user to set at tag time); the new
`[Unreleased]` header sits empty above it; the compare link table is
extended with `[0.1.5]: …compare/v0.1.4...v0.1.5` and the
`[Unreleased]` link is repointed to `…compare/v0.1.5...HEAD`.
Wiki refresh for 0.1.5 (R20 + Z2):
- `Home.md` version pin table updated; the Quickstart-Node hint replaces
the "spam filter holding back Windows" caveat with "0.1.5 is the
first release in which `npm install wickra` works end-to-end on
Windows" (npm Support released the name on 2026-05-22).
- `Quickstart-Node.md`'s Windows caveat is rewritten to explain the
history (`0.1.1`–`0.1.4` of `wickra-win32-x64-msvc` are burned) and
the resolution (0.1.5+ installs cleanly).
- `Quickstart-Rust.md` version mention bumped.
- `Warmup-Periods.md` note bumped + corrected: every Node and WASM
class — single- and multi-output — now exposes `warmupPeriod()` after
R3 (this branch), not only the single-output ones.
`release.yml` `publish_dir` no longer silently swallows a
second-attempt platform-package publish failure with a `::warning::`
and `return 0`. A real failure (after the existing 30s retry) now
emits an `::error::` and fails the job. The original mask is exactly
what allowed the `wickra-win32-x64-msvc@0.1.1–0.1.4` spam-filter
rejections to land four times in a row without anyone noticing (audit
finding R20). Failing loud means the next regression of this shape is
caught at the release run, not by a Windows user trying to
`require('wickra')`.
This commit does NOT push, tag, or trigger a release — the user
publishes the 0.1.5 tag themselves once the manual npm-republish
smoke test confirms `wickra-win32-x64-msvc@0.1.5` accepts publish on
the freshly-released name.
|
||
|
|
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.
|
||
|
|
efcd6216c1 |
feat(bindings): expose RollingVWAP in Python, Node and WASM (R4)
The rolling-window VWAP indicator (`wickra_core::RollingVwap`) was only available in the Rust crate, even though the README's Volume-family table already advertised "VWAP (cumulative + rolling)" as a cross- language feature. Users on Python, Node or in the browser had to fall back to the cumulative `VWAP` or re-implement the rolling variant themselves. This commit closes the gap end-to-end: - Python: `wickra.RollingVWAP(period)` — same constructor / `update` / `batch` / `reset` / `is_ready` / `warmup_period` surface as `VWAP`, plus a `period` property and a typed `__repr__`. The `__init__.py` re-exports it and `__all__` lists it; the `.pyi` stub matches. - Node: `RollingVWAP(period)` — napi class with the same lifecycle, exported from `index.js` and declared in `index.d.ts`. - WASM: `RollingVWAP(period)` — wasm-bindgen class with the same `Float64Array` I/O as `VWAP`. Tests added: - Python: `test_rolling_vwap_streaming_matches_batch` — exercises `update == batch` plus the full lifecycle on the shared OHLC fixture. - Node: `RollingVWAP` row in the `candleScalar` parity table — covered by the generic streaming-vs-batch + lifecycle harness. - WASM: dedicated `wasm-bindgen-test` mirrors the Python test. The wiki page `Indicator-Vwap.md` drops the "Rust-only" caveat and gains Python / Node / WASM examples. |
||
|
|
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.
|
||
|
|
a1c646ae7c |
examples: move the bundled BTCUSDT datasets to a top-level examples/data/
The seven BTCUSDT OHLCV datasets used to live under crates/wickra/examples/data/, which buried them inside a Rust crate even though the Node backtest example and the upcoming Rust/Node/WASM example restructure need to reach them too. Move them to the workspace-level examples/data/ so every language's examples can resolve the same path. The bench (crates/wickra/benches/indicators.rs), the example_data integration test, fetch_btcusdt.rs and the Node backtest example all take the new ../../examples/data/ path; Data-Layer.md, examples/README.md and the CHANGELOG entry are updated to match. No data file content changes. |
||
|
|
25454fa89a |
examples(node): add a live Binance trading example
Mirror examples/python/live_trading.py for the Node binding: connect to the public Binance kline WebSocket, stream close prices through RSI / MACD / Bollinger Bands, and print BUY/SELL candidate signals when all three agree. The symbol and interval are validated before being spliced into the stream URL, and non-kline frames (acks, heartbeats) are skipped. Uses the standard `ws` package, added as a devDependency so it installs with `npm install` for anyone running the examples but never reaches a consumer of the published package. |
||
|
|
2eabda5fa3 |
examples(node): add an offline backtest example
The Node binding shipped only one example (a synthetic streaming demo), while Python and Rust both have a CSV backtest. Add the Node counterpart of examples/python/backtest.py and crates/wickra/examples/backtest.rs: it reads an OHLCV CSV, streams every candle through a basket of indicators (SMA, EMA, RSI, MACD, Bollinger Bands, ATR, ADX, OBV) via the O(1) update call, and prints a per-series summary. With no argument it runs against the bundled BTCUSDT daily dataset, so it is runnable out of the box; pass a path to use any other OHLCV CSV. |
||
|
|
c6938e8473 |
docs: refresh the stale Python and Node binding READMEs
The Python binding README still advertised "63 indicators across four families" with the pre-restructure five-group taxonomy, missing the eight indicators added since. Update it to "71 indicators across eight families" with the catalogue grouped to match the main README. The Node binding README referred to the package as @wickra/wickra in its title, install command and import example; the published package is named wickra (per bindings/node/package.json). Correct all three. |
||
|
|
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. |
||
|
|
6643f7a81d |
F13b: add True Range, Chaikin Volatility, Z-Score and Linear Regression Angle
Second half of the eight indicators that fill out the new family taxonomy. - Rust core: true_range.rs (TrueRange — the raw single-bar volatility ATR averages), chaikin_volatility.rs (ChaikinVolatility — rate of change of a smoothed high-low spread), z_score.rs (ZScore — price normalised against its rolling mean and standard deviation) and linreg_angle.rs (LinRegAngle — the rolling regression slope as a degree angle). Each with a full Indicator impl, runnable doctest and reference / property / warmup / reset / batch==streaming tests. - Python / Node / WASM: classes wired through all three bindings (ZScore and LinRegAngle ride the scalar macros where possible) plus .pyi stubs and __init__.py / __all__ entries. - Wiki: four new Indicator-*.md pages. The eight-family taxonomy restructure (Overview / Home / README / folder layout) lands next in F13c. cargo fmt + clippy (core/wickra/data/wasm/node) clean; 508 core tests, 25 data tests and 74 doctests green. |
||
|
|
e452d35a27 |
F13a: add Accelerator Oscillator, Balance of Power, Choppiness Index and Vertical Horizontal Filter
First half of the eight indicators that fill out the new family taxonomy. - Rust core: accelerator_oscillator.rs (AcceleratorOscillator — AO minus a short SMA of itself), balance_of_power.rs (BalanceOfPower — per-bar (close-open)/(high-low)), choppiness_index.rs (ChoppinessIndex — summed true range over the high-low span, log-scaled) and vertical_horizontal_filter.rs (VerticalHorizontalFilter — net move over total move). Each with a full Indicator impl, runnable doctest and reference / property / warmup / reset / batch==streaming tests. - Python / Node / WASM: classes wired through all three bindings (BalanceOfPower carries an explicit open column; VHF rides the scalar macros) plus .pyi stubs and __init__.py / __all__ entries. - Wiki: four new Indicator-*.md pages. The eight-family taxonomy restructure (Overview / Home / README / folder layout) lands in F13c once F13b's four indicators are in. cargo fmt + clippy (core/wickra/data/wasm/node) clean; 481 core tests, 25 data tests and 70 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. |
||
|
|
2d0ee926c5 |
F12: add price transforms and rolling linear regression
- Rust core: typical_price.rs ((H+L+C)/3), median_price.rs ((H+L)/2), weighted_close.rs ((H+L+2C)/4) — stateless per-bar OHLC transforms — and linreg.rs (LinearRegression — endpoint of a rolling ordinary-least-squares fit) and linreg_slope.rs (LinRegSlope — slope of that fit). Each with a full Indicator impl, runnable doctest and reference / property / warmup / reset / batch==streaming tests. - Python: PyTypicalPrice / PyMedianPrice / PyWeightedClose / PyLinearRegression / PyLinRegSlope PyO3 classes + module registration + .pyi stubs. - Node: explicit TypicalPriceNode / MedianPriceNode / WeightedCloseNode / LinearRegressionNode / LinRegSlopeNode; index.d.ts and index.js updated. - WASM: explicit WasmTypicalPrice / WasmMedianPrice / WasmWeightedClose; WasmLinearRegression / WasmLinRegSlope via the scalar macro. - Wiki: a new indicators/statistics/ folder with five Indicator-*.md pages, a new "Statistics" family in Indicators-Overview.md and Home.md. cargo fmt + clippy (core/wickra/data/wasm/node) clean; 454 core tests, 25 data tests and 66 doctests green. |
||
|
|
21bbd521b3 |
F11: add SuperTrend, Chandelier Exit, Chande Kroll Stop and ATR Trailing Stop
- Rust core: super_trend.rs (SuperTrend — ATR-banded trailing stop with
flip logic; SuperTrendOutput { value, direction }), chandelier_exit.rs
(Chandelier Exit — ATR stop hung off the window's highest high / lowest
low; ChandelierExitOutput { long_stop, short_stop }),
chande_kroll_stop.rs (Chande Kroll Stop — a two-stage ATR stop;
ChandeKrollStopOutput { stop_long, stop_short }), atr_trailing_stop.rs
(ATR Trailing Stop — a single ratcheting close-based stop). Each with a
full Indicator impl, runnable doctest and reference / property / warmup
/ reset / batch==streaming tests.
- Python: PySuperTrend / PyChandelierExit / PyChandeKrollStop /
PyAtrTrailingStop PyO3 classes (struct outputs as tuples and (n, 2)
arrays) + module registration + .pyi stubs.
- Node: explicit SuperTrendNode / ChandelierExitNode / ChandeKrollStopNode
/ AtrTrailingStopNode with SuperTrendValue / ChandelierExitValue /
ChandeKrollStopValue objects; index.d.ts and index.js updated.
- WASM: WasmSuperTrend / WasmChandelierExit / WasmChandeKrollStop /
WasmAtrTrailingStop.
- Wiki: Indicator-SuperTrend/ChandelierExit/ChandeKrollStop/
AtrTrailingStop.md plus rows in the "Trailing stop" table of
Indicators-Overview.md and entries in Home.md.
- Add clippy.toml with doc-valid-idents for the proper noun "LeBeau".
cargo fmt + clippy (core/wickra/data/wasm/node) clean; 427 core tests,
25 data tests and 61 doctests green.
|
||
|
|
0b11a523a0 |
F10: add Chaikin Money Flow, Chaikin Oscillator, Force Index and Ease of Movement
- Rust core: cmf.rs (Chaikin Money Flow — summed money-flow volume over summed volume, bounded to [-1, +1]), chaikin_oscillator.rs (Chaikin Oscillator — the MACD of the ADL, EMA(ADL, fast) - EMA(ADL, slow)), force_index.rs (Elder's Force Index — EMA of price change scaled by volume), ease_of_movement.rs (Arms' Ease of Movement — SMA of distance travelled per unit of volume). Each with a full Indicator impl, runnable doctest and reference / property / warmup / reset / batch==streaming tests. - Python: PyChaikinMoneyFlow / PyChaikinOscillator / PyForceIndex / PyEaseOfMovement PyO3 classes + module registration + .pyi stubs. - Node: explicit ChaikinMoneyFlowNode / ChaikinOscillatorNode / ForceIndexNode / EaseOfMovementNode; index.d.ts and index.js updated. - WASM: WasmChaikinMoneyFlow / WasmChaikinOscillator / WasmForceIndex / WasmEaseOfMovement. - Wiki: Indicator-ChaikinMoneyFlow/ChaikinOscillator/ForceIndex/ EaseOfMovement.md plus a new "Oscillators" sub-table in Indicators-Overview.md and entries in Home.md. cargo fmt + clippy (core/wickra/data/wasm/node) clean; 402 core tests, 25 data tests and 57 doctests green. |
||
|
|
81962485af |
F9: add Accumulation/Distribution Line and Volume-Price Trend
Completes the F9 family (Cumulative volume) end to end: - Rust core: adl.rs (Accumulation/Distribution Line — cumulative range-weighted volume) and vpt.rs (Volume-Price Trend — cumulative volume scaled by percentage price change). Each with a full Indicator impl, runnable doctest and reference / cumulative-property / warmup / reset / batch==streaming tests. - Python: PyAdl / PyVolumePriceTrend PyO3 classes + module registration + .pyi stubs (no parameters, like OBV/VWAP). - Node: explicit AdlNode and VolumePriceTrendNode; index.d.ts and index.js updated. - WASM: WasmAdl and WasmVolumePriceTrend. - Wiki: Indicator-Adl.md and Indicator-VolumePriceTrend.md plus rows in Indicators-Overview.md and entries in Home.md. cargo fmt + clippy (core/wickra/data/wasm/node) clean; 373 core tests, 25 data tests and 53 doctests green. |
||
|
|
99dd144576 |
F8: add Bollinger Bandwidth and %b
Completes the F8 family (Bands & channels) end to end: - Rust core: bollinger_bandwidth.rs ((upper - lower) / middle — the squeeze gauge) and percent_b.rs ((price - lower) / (upper - lower) — price position within the bands, unclamped). Both wrap BollingerBands and carry a full Indicator impl, runnable doctest and reference / constant-series / definition-consistency / warmup / reset / batch==streaming tests. - Python: PyBollingerBandwidth / PyPercentB PyO3 classes + module registration + .pyi stubs (defaults (20, 2.0)). - Node: explicit BollingerBandwidthNode and PercentBNode; index.d.ts and index.js updated. - WASM: WasmBollingerBandwidth / WasmPercentB via the scalar macro. - Wiki: Indicator-BollingerBandwidth.md and Indicator-PercentB.md plus rows in Indicators-Overview.md and entries in Home.md. cargo fmt + clippy (core/wickra/data/wasm/node) clean; 362 core tests, 25 data tests and 51 doctests green. |
||
|
|
6c58d3827c |
F7: add NATR, StdDev, Ulcer Index and Historical Volatility
Completes the F7 family (Volatility) end to end: - Rust core: natr.rs (ATR as a percentage of close), std_dev.rs (rolling population standard deviation), ulcer_index.rs (RMS of trailing-high drawdowns — downside-only risk), historical_volatility.rs (annualised sample stddev of log returns). Each with a full Indicator impl, runnable doctest and reference / constant-series / warmup / reset / batch==streaming tests. - Python: PyNatr / PyStdDev / PyUlcerIndex / PyHistoricalVolatility PyO3 classes + module registration + .pyi stubs. - Node: StdDevNode / UlcerIndexNode via the scalar macro, explicit NatrNode and HistoricalVolatilityNode; index.d.ts and index.js updated. - WASM: WasmStdDev / WasmUlcerIndex / WasmHistoricalVolatility via the scalar macro, explicit WasmNatr. - Wiki: Indicator-Natr/StdDev/UlcerIndex/HistoricalVolatility.md plus rows in Indicators-Overview.md and entries in Home.md. cargo fmt + clippy (core/wickra/data/wasm/node) clean; 350 core tests, 25 data tests and 49 doctests green. |
||
|
|
16c0639f0c |
F6: add Aroon Oscillator, Vortex and Mass Index
Completes the F6 family (Trend strength) end to end: - Rust core: aroon_oscillator.rs (AroonUp - AroonDown, one-line trend gauge), vortex.rs (Vortex Indicator VI+/VI- with the VortexOutput struct), mass_index.rs (Dorsey's range-expansion sum of the EMA-of-range ratio). Each with a full Indicator impl, runnable doctest and reference / saturation / warmup / reset / batch==streaming tests. - Python: PyAroonOscillator / PyVortex / PyMassIndex PyO3 classes + module registration + .pyi stubs (defaults Aroon=14, Vortex=14, MassIndex=(9,25)). - Node: explicit AroonOscillatorNode, VortexNode (with VortexValue object) and MassIndexNode; index.d.ts and index.js updated. - WASM: WasmAroonOscillator, WasmVortex, WasmMassIndex. - Wiki: Indicator-AroonOscillator/Vortex/MassIndex.md plus rows in Indicators-Overview.md and entries in Home.md. cargo fmt + clippy (core/wickra/data/wasm/node) clean; 320 core tests, 25 data tests and 45 doctests green. |
||
|
|
54148cad5b |
F5: add PPO, DPO and Coppock Curve price oscillators
Completes the F5 family (Price oscillators) end to end: - Rust core: ppo.rs (Percentage Price Oscillator — MACD as a percentage of the slow EMA), dpo.rs (Detrended Price Oscillator — shifted price minus its SMA), coppock.rs (Coppock Curve — WMA of two summed ROCs). Each with a full Indicator impl, runnable doctest and reference / constant-series / warmup / reset / batch==streaming / non-finite tests. - Python: PyPpo / PyDpo / PyCoppock PyO3 classes + module registration + .pyi stubs (defaults PPO=(12,26), DPO=20, Coppock=(14,11,10)). - Node: DpoNode via the scalar macro, explicit PpoNode and CoppockNode; index.d.ts and index.js updated. - WASM: WasmDpo / WasmPpo / WasmCoppock via the scalar macro. - Wiki: Indicator-Ppo/Dpo/Coppock.md plus rows in Indicators-Overview.md and entries in Home.md. cargo fmt + clippy (core/wickra/data/wasm/node) clean; 300 core tests, 25 data tests and 42 doctests green. |
||
|
|
e24e7726ce |
F4: add StochRSI and Ultimate Oscillator
Completes the F4 family (Stochastic oscillators) end to end: - Rust core: stoch_rsi.rs (Stochastic Oscillator applied to the RSI series, bounded [0,100]) and ultimate_oscillator.rs (Larry Williams' weighted three-timeframe buying-pressure oscillator). Each with a full Indicator impl, runnable doctest and reference / saturation / bounds / warmup / reset / batch==streaming tests. - Python: PyStochRsi / PyUltimateOscillator PyO3 classes + module registration + .pyi stubs (defaults StochRSI=(14,14), UO=(7,14,28)). - Node: explicit StochRsiNode and UltimateOscillatorNode; index.d.ts and index.js updated. - WASM: WasmStochRsi via the scalar macro, explicit WasmUltimateOscillator. - Wiki: Indicator-StochRsi.md and Indicator-UltimateOscillator.md plus rows in Indicators-Overview.md and entries in Home.md. cargo fmt + clippy (core/wickra/data/wasm/node) clean; 278 core tests, 25 data tests and 39 doctests green. |
||
|
|
7728151c87 |
F3: add MOM, CMO, TSI and PMO momentum indicators
Completes the F3 family (Momentum) end to end: - Rust core: mom.rs (raw price-difference momentum), cmo.rs (Chande Momentum Oscillator — unsmoothed gain/loss sum, bounded [-100,100]), tsi.rs (True Strength Index — double-EMA-smoothed momentum ratio), pmo.rs (DecisionPoint Price Momentum Oscillator — doubly-smoothed ROC with the 2/period custom smoothing). Each with a full Indicator impl, runnable doctest and reference-value / saturation / warmup / reset / batch==streaming / non-finite tests. - Python: PyMom / PyCmo / PyTsi / PyPmo PyO3 classes + module registration + .pyi stubs (defaults MOM=10, CMO=14, TSI=(25,13), PMO=(35,20)). - Node: MomNode / CmoNode via the scalar macro, explicit TsiNode and PmoNode; index.d.ts and index.js updated. - WASM: WasmMom / WasmCmo / WasmTsi / WasmPmo via the scalar macro. - Wiki: Indicator-Mom/Cmo/Tsi/Pmo.md plus rows in Indicators-Overview.md and entries in Home.md. cargo fmt + clippy (core/wickra/data/wasm/node) clean; 262 core tests, 25 data tests and 37 doctests green. |
||
|
|
780a176072 |
F2: add ZLEMA, T3 and VWMA advanced moving averages
Completes the F2 family (Advanced MAs) end to end: - Rust core: zlema.rs (Zero-Lag EMA over the de-lagged series 2·price − price[lag]), t3.rs (Tillson's six-EMA cascade with the volume-factor polynomial), vwma.rs (volume-weighted rolling mean with a zero-volume fallback to the unweighted mean). Each with a full Indicator impl, runnable doctest and reference-value / warmup / reset / batch==streaming / non-finite tests. - Python: PyZlema / PyT3 / PyVwma PyO3 classes + module registration + .pyi stubs (T3 defaults v=0.7). - Node: ZlemaNode via the scalar macro, explicit T3Node and VwmaNode classes; index.d.ts and index.js updated. - WASM: WasmZlema / WasmT3 via the scalar macro, explicit WasmVwma. - Wiki: Indicator-Zlema.md, Indicator-T3.md, Indicator-Vwma.md plus rows in Indicators-Overview.md and entries in Home.md. cargo fmt + clippy (core/wickra/data/wasm/node) clean; 232 core tests, 25 data tests and 33 doctests green. |
||
|
|
ed7324115c |
F1: wire SMMA and TRIMA through every binding and the wiki
Completes the F1 family (Simple & Weighted MAs). The Rust core for both SMMA (Wilder's RMA) and TRIMA (triangular MA) already landed; this adds the remaining Definition-of-Done steps: - Python: PySmma / PyTrima PyO3 classes + module registration + .pyi stubs. - Node: SmmaNode / TrimaNode via the scalar-indicator macro; index.d.ts and index.js updated for the two new classes. - WASM: WasmSmma / WasmTrima via the scalar-indicator macro. - Wiki: Indicator-Smma.md and Indicator-Trima.md (full pages) plus rows in Indicators-Overview.md and entries in Home.md. cargo fmt + clippy (core/wickra/data/wasm/node) clean; 208 core tests, 25 data tests and 31 doctests green. |
||
|
|
b919c33dee |
E17: add a runnable Node example
bindings/node had no examples/ directory — the README pointed at a test file as its "Example". (bindings/wasm/examples/index.html already exists and is a complete browser demo, so only the Node side was missing.) Add bindings/node/examples/streaming.js: a deterministic synthetic price series fed tick by tick through SMA, EMA, RSI and MACD, printing a status line and flagging overbought/oversold candidates — the same O(1)-per-update streaming model a live bot would use. Verified against the built native module. |
||
|
|
2fc7a90ec0 |
B11: ship npm binaries for linux-arm64 and win32-arm64
The napi loader resolves wickra-linux-arm64-gnu and
wickra-win32-arm64-msvc, but neither was published, so require('wickra')
failed on those platforms. Adds both to napi.triples and
optionalDependencies, adds their npm/ package templates, and extends the
release node-build matrix to build them on GitHub's native ARM runners
(ubuntu-24.04-arm, windows-11-arm).
|
||
|
|
490ce28645 |
B10: document the interleaved layout of Node MACD/Bollinger batch
MacdNode.batch and BollingerNode.batch return flat interleaved arrays (3*n and 4*n) but index.d.ts only said Array<number>. Adds /// doc comments describing the layout; napi-rs now propagates them into the generated index.d.ts as JSDoc. |
||
|
|
59c435fbd5 |
chore: apply rustfmt to binding/core sources and sync Cargo.lock
Normalises whitespace in sources committed earlier in this branch before rustfmt was run over them (Node/WASM bindings, and three core indicator test modules), and records the wasm-bindgen-test dependency tree added in B6 into Cargo.lock. No functional change; cargo fmt --all --check is now clean. |
||
|
|
aeea6a89bb |
B8: add a comprehensive Node binding test suite
The Node binding had only 7 smoke cases with no streaming-vs-batch or candle-indicator coverage. Adds indicators.test.js: streaming update() matches batch() for all 25 indicators (scalar, candle-scalar and interleaved multi-output), a lifecycle check that every indicator exposes reset/isReady/warmupPeriod, and reference values (SMA(3), MFI(2)=1200/23, RSI uptrend, MACD histogram). 38 Node tests pass. |
||
|
|
ad3b068d47 |
B4: complete reset/isReady/warmupPeriod across Node indicators
KAMA and the nine candle indicators (CCI, WilliamsR, MFI, PSAR, Keltner, Donchian, VWAP, AO, Aroon) exposed none of the three lifecycle methods; Stochastic/OBV/ADX exposed only reset; MACD/Bollinger/ATR lacked warmupPeriod. Every indicator now exposes reset(), isReady() and warmupPeriod(), matching the scalar-macro surface. Verified against the rebuilt module (e.g. MACD warmupPeriod 34, Stochastic 16, KAMA 11). |
||
|
|
b4613a74c8 |
B3: guard candle batch methods against unequal-length arrays
Candle batch() methods that index parallel high/low/close/volume arrays without first checking their lengths panic on a length mismatch. Adds an equal-length guard returning a clean error to the 11 affected Node methods (Stochastic, ADX, CCI, WilliamsR, MFI, PSAR, Keltner, Donchian, VWAP, AO, Aroon), the 10 affected WASM methods, and the 8 affected Python methods (WilliamsR, ADX, MFI, PSAR, Keltner, VWAP, AO, Aroon) -- matching the guard ATR/OBV already had. Verified in Node: mismatched arrays now throw instead of crashing. |
||
|
|
a0884fa010 |
B2: make fallible Node constructors throw instead of panicking
The 14 candle and multi-parameter indicator constructors passed raw parameters through must() (an expect()), so invalid arguments such as new MACD(0,0,0) or new BollingerBands(20,-1) aborted the whole process over the napi boundary (the release profile sets panic=abort). napi-rs 2.16 does accept a #[napi(constructor)] returning napi::Result<Self> (the old must() comment was wrong), so each now returns napi::Result<Self> and throws a clean JS error via map_err. must()/clamp_period stay for the scalar macro, where period is clamped and the Result is provably Ok. Verified: every invalid constructor throws, valid ones still build. |
||
|
|
4b6f25a32c |
B1: add update() to the 12 Node candle indicators
Stochastic, OBV, ADX, CCI, WilliamsR, MFI, PSAR, Keltner, Donchian, VWAP, AwesomeOscillator and Aroon previously exposed only batch() in the Node binding, so a streaming-first library could not actually stream them from Node. Each now has an update() mirroring the AtrNode pattern, with the same input arity and output type as its batch() counterpart. Verified against the rebuilt native module. |
||
|
|
528e5c9174 |
Release 0.1.4: add GitHub Release asset attachments
Pure tooling release on top of 0.1.3. The library code is unchanged; only the release workflow grew a new github-release job that attaches every built artefact to the GitHub Release page so users have direct download links next to the source archives: - Python wheels (5 platforms) + sdist - Native Node bindings (linux-x64-gnu, darwin-x64, darwin-arm64, win32-x64-msvc) - npm-pack tarballs for the main wickra package, every per-platform subpackage, and wickra-wasm - Cargo .crate files for wickra-core, wickra-data, wickra The job runs at the end of the release pipeline and also accepts workflow_dispatch so future asset-only fixups don't require a version bump. |
||
|
|
529f302f73 |
Release 0.1.3: full pipeline working end to end
CI is now green across all 20 jobs: - Rust on Linux/macOS/Windows - Python 3.9/3.11/3.12 on Linux/macOS/Windows - Node 18/20 on Linux/macOS/Windows (Windows previously failed because ci.yml built the native module without --platform; fixed in the previous commit) - WASM build - Cross-library benchmark report This tag re-publishes 0.1.3 across crates.io / PyPI / npm so a single version covers every working binding. The release workflow's idempotent publish steps mean re-runs are safe; the new code in this version is just the build-script and loader changes that fixed CI. |
||
|
|
5c40acd288 |
ci+loader: build with --platform; ship napi-generated loader
The Windows Node 18 and Node 20 CI jobs failed because:
- ci.yml ran 'napi build --release' (no --platform), which produces a
filename without the target triple ('wickra.node').
- The committed loader at bindings/node/index.js looked for the
triple-suffixed file ('wickra.win32-x64-msvc.node') and then for the
per-platform npm subpackage as a fallback.
- 'wickra-win32-x64-msvc' isn't on npm yet (blocked by the spam filter),
so Windows had nothing to load. Linux and macOS passed only because
their optionalDependencies do exist on npm and got fetched.
Two-part fix:
- ci.yml now runs 'napi build --platform --release', matching release.yml.
--platform encodes the target triple in the filename, so the loader's
primary lookup always finds the freshly built binary on every host.
- bindings/node/index.js is now the canonical napi-rs auto-generated
loader (it gets regenerated by 'napi build'). It covers all the platforms
napi knows about (linux glibc/musl, Android, FreeBSD, ARM, etc.) rather
than just the four we publish, so unsupported platforms get a clear
'this binding isn't built for your system' error instead of a confusing
ENOENT.
Local sanity: 'npx napi build --platform --release' then 'node --test
__tests__/' succeeds end to end on Windows; the eight existing tests
pass.
|
||
|
|
de39415413 |
ci(release): drop prepublishOnly hook + ignore-scripts on main publish
The root cause of the previous failure was the package.json "prepublishOnly": "napi prepublish -t npm" script. When the workflow ran `npm publish` for the main wickra package it implicitly invoked that hook, which re-attempted to publish every platform subpackage and hit 403 "cannot publish over 0.1.2" because they were already on the registry. The error trail looked like it came from the main publish step but the failing command was actually the hook. Two-part fix: - Remove prepublishOnly entirely from bindings/node/package.json. The release workflow already does the per-platform publish itself, in a loop, idempotently. - Belt-and-braces: pass --ignore-scripts when publishing the main package so any prepublish-hook drift in the future is suppressed. |