Commit Graph

118 Commits

Author SHA1 Message Date
kingchenc 2aef8c8db5 perf(linreg): incremental O(1) OLS for LinearRegression and LinRegSlope (R2)
`LinearRegression::fit` and `LinRegSlope::update` previously iterated the
full `period`-window on every tick to recompute `Σy` and `Σxy` from
scratch — O(period) per update, in violation of the `Indicator` trait's
O(1) contract. `LinRegAngle` inherits the cost transitively because it
delegates to `LinRegSlope`.

This commit slides the OLS state in closed form. The constant terms
(`Σx`, `Σxx`, the denominator `n·Σxx − (Σx)²`) were already precomputed
in `new`. The new running state is:

- `sum_y: f64` — running sum of the values currently in the window.
- `sum_xy: f64` — running Σ(x · y) where `x` is the position of each
  value inside the trailing window (`0` for the oldest, `n−1` for the
  newest).

On every push, when the window is already full the front value `y₀` is
popped and the indices of every remaining value shift down by 1; the
identity

    new_Σxy = old_Σxy − old_Σy + y₀

closes the slide in O(1). The new value is then pushed at position `k`
(the current length before the push), contributing `k · new_value` to
`sum_xy` and `new_value` to `sum_y`. The output is the same TA-Lib OLS
formula evaluated against the incremental accumulators.

Behaviour is unchanged: same per-tick values, same warmup, same NaN
semantics. Two new tests compare the O(1) result bar-by-bar against a
fresh O(n) refit on a noisy ramp (sliding-phase dominated), a step
function (large pop/push deltas), and constants (tests floating-point
drift) — agreement is within `1e-9`.

`LinRegAngle` benefits automatically through its `LinRegSlope` field.
2026-05-23 10:36:45 +02:00
kingchenc b003321562 test(fuzz): cover every indicator, scalar and candle inputs (R9)
The fuzz suite previously covered only `Rsi(14)` and `Ema(20)` — 2 of
71 indicators, no OHLCV coverage at all. Audit finding R9 asked for
ATR/ADX/Stochastic/PSAR as a minimum; this commit goes further and
brings every indicator under fuzz.

- `indicator_update` (rewritten): drives every scalar-input indicator
  through one streaming pass + one batch call per iteration. Covers
  SMA, EMA, WMA, RSI, DEMA, TEMA, HMA, ROC, TRIX, SMMA, TRIMA, ZLEMA,
  KAMA, T3, MOM, CMO, TSI, PMO, StochRSI, DPO, PPO, Coppock, StdDev,
  UlcerIndex, HistoricalVolatility, LinearRegression, LinRegSlope,
  LinRegAngle, VHF, ZScore, MACD, BollingerBands. A `drive` helper
  marked `#[inline(never)]` keeps each indicator on its own panic
  backtrace frame.

- `indicator_update_candle` (new): chunks the fuzz `f64` stream into
  `[open, high, low, close, volume]` tuples, builds candles via
  `Candle::new` (skipping ones that fail OHLCV validation — that path
  is fuzz-tested separately), then drives every candle-input indicator
  through streaming + batch. Covers ATR, NATR, TrueRange,
  ChaikinVolatility, Keltner, Donchian, PSAR, SuperTrend,
  ChandelierExit, ChandeKrollStop, ATRTrailingStop, ADX, Aroon,
  AroonOscillator, Vortex, MassIndex, ChoppinessIndex, CCI, WilliamsR,
  AwesomeOscillator, AcceleratorOscillator, UltimateOscillator,
  BalanceOfPower, OBV, MFI, VWAP, RollingVWAP, VWMA, ADL, VPT, CMF,
  ChaikinOscillator, ForceIndex, EaseOfMovement, TypicalPrice,
  MedianPrice, WeightedClose, Stochastic.

- `fuzz/Cargo.toml` registers the new target; `fuzz/README.md`
  describes both expanded targets.

- A `fuzz-smoke` CI job runs each of the five targets for 30 s on
  every push and pull-request — enough to catch a regression in the
  harness without slowing CI to a crawl. Long fuzz campaigns belong
  on dedicated infrastructure with persistent corpora.
2026-05-23 10:33:05 +02:00
kingchenc 0995f8d66a fix(psar): correct is_ready convention and use NaN sentinels (R6, B-Opus-1)
`Psar::is_ready` previously returned `self.initialised`, which flips to
`true` *after* the seed candle — but the seed candle itself returns
`None`. The contract every other indicator honours is
`is_ready() == true` ↔ "the most recent update produced (or could
produce) a real value". Streaming consumers writing
`if ind.is_ready() { use(ind.update(c)?) }` would hit an unexpected
`None` on the first post-seed update.

Fix: add a `has_emitted: bool` field that flips on the first
`Some(sar)` return; `is_ready` now reads that. New test
`is_ready_only_after_first_some_value` pins the contract.

While in the same file, `reset()` is corrected to restore the compute
fields (`prev_high`, `prev_low`, `sar`, `ep`) to `f64::NAN` sentinels
instead of `0.0` (Opus bonus finding). The fields are gated by
`initialised` today, so the `0.0` sentinel never leaked into output —
but a future refactor that read them pre-init would have silently
treated `0.0` as a real price. A `debug_assert!` at the read site makes
the invariant explicit and catches a re-introduction of the bug in
debug builds.

Bit-equivalence with the previous behaviour is preserved
(`reset_allows_clean_reuse` and `batch_equals_streaming` continue to
pass unchanged).
2026-05-23 10:28:18 +02:00
kingchenc a530f1b4cb perf(ulcer-index): track trailing max with a monotone deque (R1, B-Opus-2)
`UlcerIndex::update` previously scanned the full `period`-window every
tick via `prices.iter().fold(NEG_INFINITY, f64::max)`, breaking the
`Indicator` trait's O(1) contract. For long windows (e.g. period 50+ on
a live tick stream) this turned a constant-time update into an O(period)
one, and full-history batch replays into O(n · period).

The window of raw prices is replaced with a monotonically-decreasing
deque of `(index, price)` pairs. On every push, all back entries
`<= input` are popped (they can never be the trailing max again, since
they are dominated and at least as old). On every step, the front is
popped if its index is older than `count - period + 1`. The deque's
front is therefore always the trailing max in O(1). `count: u64` is the
1-based input counter that drives expiration; on `reset()` it returns
to zero alongside the deque and the drawdown state.

Behaviour is unchanged: same per-tick values, same warmup
(`2 * period - 1`), same non-finite-input semantics. A new test
`monotone_deque_matches_naive_max_on_adversarial_inputs` compares the
deque output bar-by-bar against an independent O(n) trailing-max scan on
inputs designed to hit every code path: strictly increasing (full tail
pops), strictly decreasing (head expirations only), constants (the
`<= input` pop rule keeps a single newest entry), and a sawtooth.

The doc comment on `warmup_period()` is also corrected (B-Opus-2): the
two windows overlap by one bar, so the formula is `2 * period - 1`, not
`2 * period`.
2026-05-23 01:46:24 +02:00
kingchenc 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.
2026-05-23 01:43:00 +02:00
kingchenc 3a6b5ebae3 feat(wasm): expose streaming update/isReady/warmupPeriod for 12 candle indicators (R3, R8)
Twelve WASM classes previously exposed only `batch()` (and not even
`reset()` for ten of them): ADX, WilliamsR, CCI, MFI, PSAR, Keltner,
Donchian, VWAP, AwesomeOscillator, Aroon, Stochastic, OBV. Browser
consumers wanting per-tick updates had to replay `batch()` on every new
candle — the opposite of the library's streaming-first promise.

Each class now exposes:

- `update(...)` — per-tick streaming update with the same column inputs
  as `batch()`. Single-output indicators return `Option<f64>`. Multi-
  output indicators (ADX, Keltner, Donchian, Aroon, Stochastic) return a
  named JS object (`{ plusDi, minusDi, adx }`, `{ upper, middle, lower }`,
  `{ up, down }`, `{ k, d }`) once warm, or `null` during warmup. This
  matches the existing `SuperTrend` convention so JS code can treat all
  multi-output WASM indicators uniformly.
- `reset()`, `isReady()`, `warmupPeriod()` — bring the lifecycle API to
  full parity with Python and Node.

`WasmKama` also gains the previously missing `warmupPeriod()` (R8). A
single new `wasm-bindgen-test` exercises every newly wired class against
a deterministic 40-bar synthetic OHLCV stream, asserting that
streaming `update` matches `batch` value-by-value and that the lifecycle
contract behaves the same as the core indicator.
2026-05-23 01:34:54 +02:00
kingchenc c99cf54a1f fix(security): upgrade pyo3 and numpy to 0.28, fix RUSTSEC-2025-0020
Bumps the Python binding from pyo3 0.22 / numpy 0.22 to 0.28 / 0.28,
which resolves RUSTSEC-2025-0020 — a buffer overflow in
`PyString::from_object` that affected every published Python wheel.

Migration:

- `into_pyarray_bound(py)` → `into_pyarray(py)` (numpy 0.23 dropped the
  `_bound` transitional suffix; the method now returns `Bound<'py, _>`
  directly).
- `downcast::<PyDict>` → `cast::<PyDict>` (pyo3 renamed the method on
  `PyAnyMethods`).
- Every `#[pyclass]` declares `skip_from_py_object` to opt out of the
  now-deprecated automatic `FromPyObject` derive for `Clone` types.
  Indicators are stateful — silently extracting them by value-clone is
  never the intended FFI semantics.
- Workspace clippy gains `unused_self = "allow"` on the python crate
  only: Python's `__repr__` protocol forces `&self` even for parameter-
  less indicators where the body does not read state.
- `map_err` arms collapsed into a single `PyValueError` arm
  (clippy::match_same_arms).

`deny.toml` no longer suppresses RUSTSEC-2025-0020; `cargo deny check`
is green on advisories, bans, licenses and sources without exceptions.
2026-05-23 01:26:55 +02:00
kingchenc 2bc6cc5505 docs: round out the docs for Z7's example additions
* examples/README.md — replace the single-row WASM section with a build
  block (one-time `wasm-pack` build), a serve note, and the full
  five-row table (`index`, `backtest`, `live_trading`, `multi_timeframe`,
  `parallel_assets`).
* examples/wasm/README.md — new dedicated index for the WASM demos
  with the build and serve commands and a description of every file
  including the module worker companion.
* CHANGELOG.md `[Unreleased]` gains three bullets: the Python and Node
  `fetch_btcusdt` siblings; the four new WASM browser demos; and the
  three new wiki pages from Z6 (TA-Lib-Migration, Cookbook, FAQ).
2026-05-23 00:45:51 +02:00
kingchenc 6e3190a44a examples(wasm): add a browser parallel-assets demo via Web Workers
Close the final "parallel assets" cell of the cross-language matrix for
WASM.

* examples/wasm/parallel_assets.html — generates a synthetic
  `(assets, bars)` panel deterministically (the LCG matches the Node
  and Rust siblings so timings are directly comparable), runs the
  serial baseline on the main thread, then dispatches the same workload
  to a pool of module Workers and reports the speedup. The render is
  three cards (serial / parallel / speedup) plus a sanity-check line
  asserting per-asset agreement between the two paths.
* examples/wasm/parallel_worker.js — companion module worker that
  loads its own copy of the WebAssembly module via `init()` and
  processes whichever slice of the panel its parent dispatches.

Modern browsers ship module-worker support (`new Worker(url, {
type: "module" })`) which lets every worker do `import init, { SMA, RSI
} from "../../bindings/wasm/pkg/wickra_wasm.js"` without bundler
glue. The inline page module and the worker module both syntax-check
cleanly under `node --check`.
2026-05-23 00:45:01 +02:00
kingchenc 04c3a83fdb examples(wasm): add a browser multi-timeframe demo
Close the "multi-timeframe" cell of the cross-language matrix for WASM.

* examples/wasm/multi_timeframe.html — fetches the bundled 1-minute
  BTCUSDT CSV (or any 1-minute OHLCV CSV), rolls it up in-page to 5m,
  15m, 1h, 4h and 1d buckets, and prints RSI(14), MACD(12,26,9)
  histogram and ADX(14) per timeframe via the WebAssembly bindings.
  Same inline-bucket aggregation as the Node sibling, same indicator
  set as the Python and Rust siblings — the Rust version uses
  `wickra-data::Resampler` directly which is currently a Rust-only API.

The render is a single table (one row per timeframe) so the cross-
language outputs sit side-by-side cleanly. The inline module script
syntax-checks cleanly under `node --check`.
2026-05-23 00:43:54 +02:00
kingchenc c87a16953b examples(wasm): add a browser live-trading demo
Close the "live trading" cell of the cross-language matrix for WASM.

* examples/wasm/live_trading.html — opens a native browser `WebSocket`
  to Binance's public kline stream, feeds every close through RSI(14),
  MACD(12,26,9) and Bollinger(20, 2.0) via the WebAssembly bindings, and
  flags BUY/SELL candidates when all three indicators agree. Mirrors
  the Node and Python live-trading examples in indicator set, signal
  logic and symbol/interval validation — the symbol is checked against
  `^[A-Za-z0-9]+$` before being spliced into the stream URL, the
  interval against the public-API allow-list.

The UI shows live close / RSI / MACD-histogram / Bollinger-band cards,
plus a scrolling log of the last 200 ticks with signal rows highlighted.
Browser-native `WebSocket` means no library dependency. Build the WASM
module once (`wasm-pack build bindings/wasm --target web --release
--features panic-hook`), serve the repository root and open
`examples/wasm/live_trading.html`.
2026-05-23 00:43:05 +02:00
kingchenc 5ea36a6064 examples(wasm): add a browser backtest demo
The WASM example set had only `index.html` (the streaming canvas demo);
the "backtest" cell of the cross-language matrix was empty. Close it.

* examples/wasm/backtest.html — loads the wasm-pack `--target web`
  bundle, fetches an OHLCV CSV from the same `examples/data/` directory
  the other languages use (default: `btcusdt-1d.csv`), parses it
  in-page and streams every candle through SMA, EMA, RSI, MACD,
  Bollinger, ATR, ADX and OBV via the WebAssembly bindings. Renders a
  summary table with mean / min / max / last per series — mirrors the
  Rust, Python and Node backtest examples both in indicator set and in
  output shape.

Build the WASM module once (`wasm-pack build bindings/wasm --target web
--release --features panic-hook`), then serve the repository root and
open `examples/wasm/backtest.html`. The inline module script
syntax-checks cleanly under `node --check` on the extracted body.
2026-05-23 00:42:14 +02:00
kingchenc 303ff0a163 examples(node): add a fetch_btcusdt script using built-in fetch
Node had no sibling for the Rust and Python `fetch_btcusdt`
data-generators — adding it closes the "fetch (data-gen)" cell for the
last remaining row of the cross-language example matrix where the
pattern makes sense.

* examples/node/fetch_btcusdt.js — uses Node 18+'s built-in global
  `fetch` (no npm dependencies); same pagination logic as the Rust and
  Python siblings (paginate backwards via `endTime`, drop the
  in-progress bucket, sort and trim to the configured target). Applies
  the same OHLC validity check the Rust `Candle::new` constructor
  enforces so a malformed kline is skipped rather than written.
* JavaScript's `String(v)` already gives the shortest round-trip
  representation and strips the `.0` suffix for whole-number floats, so
  the CSV output is byte-for-byte identical to what the Rust and
  Python fetchers produce on the same Binance snapshot. Verified by
  running it and `git diff`-ing against the checked-in dataset: every
  row older than the run is unchanged; only the most recent ~24 hours
  drift because the market kept moving.

examples/README.md gains the new row.
2026-05-23 00:40:27 +02:00
kingchenc b948b0b9cf examples(python): add a stdlib-only fetch_btcusdt script
Python had no sibling for the Rust `fetch_btcusdt` data-generator —
adding it closes the "fetch (data-gen)" cell for Python and lets users
without a Rust toolchain regenerate the bundled BTCUSDT datasets.

* examples/python/fetch_btcusdt.py — uses only the standard library
  (urllib.request + json + csv); same pagination strategy as the Rust
  version (paginate backwards via `endTime`, drop the in-progress
  bucket, sort and trim to the configured target). Applies the same
  OHLC validity check the Rust `Candle::new` constructor enforces
  (finite fields, high >= low/open/close, low <= open/close,
  volume >= 0) so a malformed kline is skipped rather than written.
* Number formatting matches Rust's `f64` Display: shortest round-trip,
  no trailing `.0` for whole-number floats. Verified by running the
  script and `git diff`-ing against the checked-in dataset: every row
  older than the run is byte-identical to Rust's output; the diff only
  shows the most recent ~24 hours where Binance has produced fresh
  candles since the original snapshot.

examples/README.md gains the new row.
2026-05-23 00:38:38 +02:00
kingchenc 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.
2026-05-23 00:24:41 +02:00
kingchenc 8b4a847d24 docs(wiki): add Cookbook, TA-Lib migration table and FAQ
Three content gaps in the wiki: there was no migration story for users
porting from TA-Lib, no strategy cookbook, and no FAQ. Add all three as
self-contained pages and link them from Home.md's "Wiki contents".

* docs/wiki/TA-Lib-Migration.md — full one-to-one mapping table from
  every common talib.X(...) call to the equivalent Wickra expression,
  plus a "what Wickra has that TA-Lib does not" / "what TA-Lib has that
  Wickra does not (yet)" delta.
* docs/wiki/Cookbook.md — seven concrete strategy recipes (RSI mean
  reversion, MACD histogram crossover, Bollinger breakout, ADX-gated
  trend, multi-timeframe confirmation, SuperTrend trailing stop,
  Chain<EMA, RSI>) with Rust or Python snippets.
* docs/wiki/FAQ.md — common questions on warmup, NaN handling, thread
  safety, installation, performance and comparing Wickra to TA-Lib /
  pandas-ta / talipp / finta.

Also extend the [Unreleased] CHANGELOG entry that records the
examples/<lang>/ restructure with the wiki additions; Home.md gains
three new bullets under "Wiki contents".
2026-05-23 00:23:00 +02:00
kingchenc 43b0b26736 examples: add parallel-assets demos for Rust and Node
Python's parallel_assets.py demoed GIL-release multi-core throughput;
Rust and Node both lacked a sibling that shows their own native
parallelism. Close the gap with two real, runnable examples.

* examples/rust/src/bin/parallel_assets.rs — synthesises an (assets,
  bars) panel with a deterministic per-asset LCG, runs a serial baseline,
  then `Sma::batch_parallel` / `Rsi::batch_parallel` via rayon, asserts
  the two outputs are element-wise identical and prints the speedup.
  Toggle indicator with `--indicator sma|rsi`.
* examples/node/parallel_assets.js — same shape, but the parallel run is
  a `worker_threads` pool that re-loads the native binding in each
  worker. Each worker computes the last non-null indicator value for its
  slice; the main thread aggregates and verifies serial == parallel
  per asset.

Both examples report timings and the serial-vs-parallel sanity check
passes. Defaults (200 × 5000) keep the example fast on dev hardware;
larger `--assets`/`--bars` is where the speedup numbers move (Node's
worker spawn cost dominates the smallest sizes, which is honest and
educational).

examples/README.md gains the two new rows.
2026-05-23 00:18:46 +02:00
kingchenc 962ced0712 examples: add multi-timeframe demos for Rust and Node
Python's examples/python/multi_timeframe.py had no Rust or Node sibling.
Add both — the Rust version uses wickra-data's `Resampler` /
`resample_all` (the canonical path; no manual roll-up), the Node version
mirrors the Python one's inline aggregation because wickra-data's
resampler is currently Rust-only.

* examples/rust/src/bin/multi_timeframe.rs — reads the bundled 1m CSV via
  `CandleReader`, resamples to 5m / 15m / 1h / 4h / 1d via `resample_all`,
  prints last RSI(14), MACD(12,26,9) histogram and ADX(14) per timeframe.
* examples/node/multi_timeframe.js — same outputs from a hand-rolled
  bucket aggregator; reuses the new examples/data/ default path.
* examples/README.md gains the new rows.

Run side by side: the Rust and Node summaries are bit-identical at every
timeframe (50000 / 10000 / 3334 / 834 / 209 / 35 bars; same RSI, MACD
histogram and ADX to two decimals) — confirming both the Rust resampler
and the inline Node aggregator produce the same OHLC buckets.
2026-05-23 00:16:08 +02:00
kingchenc 5a4cf66022 examples: add streaming demos for Python and Rust
Python and Rust both lacked a standalone "streaming indicators" example
that mirrors examples/node/streaming.js — the quickstart docs cover the
pattern, but a runnable file makes the parity visible across all four
languages.

* examples/python/streaming.py — argparse-driven synthetic streaming demo
  feeding SMA(20) / EMA(20) / RSI(14) / MACD(12,26,9), tagging BUY?/SELL?
  candidates when RSI extremes and MACD-histogram direction agree.
* examples/rust/src/bin/streaming.rs — same demo as a wickra-examples
  binary, reusing the seeded LCG so its first 40 rows are bit-identical
  to the Python (and Node) sibling — a strong cross-language consistency
  signal verified by running both side by side.
* examples/README.md gains a `streaming` row in the Rust and Python tables.
2026-05-23 00:13:38 +02:00
kingchenc 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.
2026-05-23 00:11:07 +02:00
kingchenc 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.
2026-05-23 00:10:06 +02:00
kingchenc 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.
2026-05-23 00:07:07 +02:00
kingchenc 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.
2026-05-23 00:01:52 +02:00
kingchenc 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.
2026-05-22 22:47:13 +02:00
kingchenc 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.
2026-05-22 22:45:52 +02:00
kingchenc 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.
2026-05-22 22:43:21 +02:00
kingchenc 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.
2026-05-22 22:21:49 +02:00
kingchenc 91f24946a6 docs(changelog): record the example datasets and Timeframe constructors
Add the still-unreleased Z1/Z2 work to the [Unreleased] section: the seven
real-BTCUSDT example datasets plus the fetch_btcusdt example, the
Timeframe::minutes/hours/days constructors, and the switch of the indicator
benchmarks from a synthetic series to the checked-in BTCUSDT dataset.
2026-05-22 22:20:26 +02:00
kingchenc d5ff0a9df6 wickra-data: add minutes/hours/days Timeframe constructors
Timeframe gained new/millis/seconds/one_minute_ms; add minutes, hours and
days alongside them. Each builds on seconds (minutes(5) -> a 300-second
bucket), consistent with Timeframe::seconds, and guards the multiplication
with checked_mul so an oversized n yields Error::InvalidTimeframe instead
of an overflow panic. A non-positive n is rejected by Timeframe::new.

Each method carries a runnable doctest, and unit tests cover the known
bucket sizes, non-positive rejection and overflow rejection.
2026-05-22 21:49:21 +02:00
kingchenc 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).
2026-05-22 21:47:17 +02:00
kingchenc 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.
2026-05-22 21:21:56 +02:00
kingchenc 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.
2026-05-22 21:06:36 +02:00
kingchenc 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.
2026-05-22 20:57:52 +02:00
kingchenc 27f37f5347 docs(python): refresh the binding README's indicator list to 63
The Python binding README still advertised the original 25 indicators and
the four-family list. Bring it in line with the root README: 63 indicators
across the four classical families plus the statistics group.
2026-05-22 20:31:52 +02:00
kingchenc 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.
2026-05-22 20:04:13 +02:00
kingchenc 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.
2026-05-22 19:52:04 +02:00
kingchenc 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.
2026-05-22 19:42:14 +02:00
kingchenc 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.
2026-05-22 19:25:32 +02:00
kingchenc 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.
2026-05-22 18:38:21 +02:00
kingchenc 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.
2026-05-22 18:30:49 +02:00
kingchenc 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.
2026-05-22 18:26:29 +02:00
kingchenc 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.
2026-05-22 18:17:38 +02:00
kingchenc 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.
2026-05-22 18:09:10 +02:00
kingchenc 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.
2026-05-22 18:02:44 +02:00
kingchenc 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.
2026-05-22 17:53:46 +02:00
kingchenc 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.
2026-05-22 17:45:02 +02:00
kingchenc 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.
2026-05-22 17:34:38 +02:00
kingchenc abd2d80f8d F1: add SMMA and TRIMA moving averages (core)
First step of the indicator-family expansion (see the F section of
todo-detailed.md). Family F1 — Simple & Weighted MAs — gains two
members alongside the existing Sma/Ema/Wma:

- Smma — Wilder's smoothed moving average (RMA): SMA-seeded, then the
  (prev*(n-1)+x)/n recurrence. The average underlying RSI and ATR.
- Trima — triangular moving average: two stacked SMAs (n1/n2 split by
  parity) that triangular-weight the window. Genuine stacking — the
  outer SMA consumes the inner SMA's output.

Both implement the full Indicator trait with reference-value, warmup,
reset, batch==streaming and non-finite-input tests, a runnable doctest,
and are re-exported from the crate root. 208 core tests + 30 doctests
pass; clippy and fmt clean.
2026-05-22 17:10:52 +02:00
kingchenc 3e8c48eefc fix(wasm): call expect() directly instead of ok().expect() in tests
The WASM binding's test module (added in B6) used `.ok().expect(...)`
on the Result-returning constructors. clippy's ok_expect lint rejects
this under the workspace's `-D warnings`, and the CI rust job lints
wickra-wasm with --all-targets — so the branch would fail CI.

Replace all seven `.ok().expect(...)` with `.expect(...)` directly;
JsError implements Debug, so this compiles and gives a better panic
message. clippy and fmt are now clean for wickra-wasm.
2026-05-22 16:47:16 +02:00
kingchenc a4d8c40dc2 E6: extend the Python release matrix to musl and Windows arm64
python-wheels built only glibc Linux (x86_64/aarch64), macOS, and
Windows x64 — Alpine/musl users and Windows arm64 had no wheel.

Add musllinux_1_2 wheels for x86_64 and aarch64 Linux and an
aarch64 wheel on the windows-11-arm runner. The upload artifact name
now includes the manylinux value so the glibc and musl builds of the
same architecture do not collide. The Node release matrix already
covers linux-arm64 and win32-arm64 (added in B11); Node musl is left
out, as B11 documented, because it needs a cross/container setup.
2026-05-22 16:45:20 +02:00