167 Commits

Author SHA1 Message Date
kingchenc 5a573650d2 Merge pull request #18 from kingchenc/release/0.2.1
release(0.2.1): bump to 0.2.1, Windows ARM64 skipped this cycle
v0.2.1
2026-05-23 22:24:47 +02:00
kingchenc 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
2026-05-23 22:20:20 +02:00
kingchenc aea17a87af Merge pull request #16 from kingchenc/fix/criterion-0.8
deps: criterion 0.5 → 0.8 (replaces #10)
2026-05-23 21:43:28 +02:00
kingchenc 23c649aca9 Merge pull request #17 from kingchenc/fix/tokio-tungstenite-0.29
deps: tokio-tungstenite 0.24 → 0.29 (replaces #13)
2026-05-23 21:41:04 +02:00
kingchenc 57d3b785c2 Merge pull request #9 from kingchenc/dependabot/github_actions/actions/download-artifact-8.0.1
deps(actions): bump actions/download-artifact from 4.3.0 to 8.0.1
2026-05-23 21:37:55 +02:00
kingchenc d94dc43fb2 Merge pull request #5 from kingchenc/dependabot/github_actions/actions/upload-artifact-7.0.1
deps(actions): bump actions/upload-artifact from 4.6.2 to 7.0.1
2026-05-23 21:37:51 +02:00
kingchenc ed57632576 build: lift workspace MSRV to 1.86 to satisfy criterion 0.8.2
criterion 0.8.2 raised its MSRV from 1.85 to 1.86 (rustc 1.85.1 is now
explicitly rejected by its Cargo.toml `rust-version`). One more notch
on the same upward drift that already took us from 1.75 -> 1.80 (rayon)
-> 1.85 (clap_lex/edition2024).

Updated:
- Cargo.toml workspace rust-version 1.85 -> 1.86
- .github/workflows/ci.yml MSRV matrix name + toolchain
- ci.yml comment refreshed to reflect the new driving dep
2026-05-23 21:36:56 +02:00
kingchenc 64faa707a4 deps: bump tokio-tungstenite 0.24 -> 0.29 (replaces #13)
Dependabot opened #13 to bump tokio-tungstenite from 0.24 to 0.29 but
the bare-version bump fails to compile: WebSocketConfig became
#[non_exhaustive] starting with 0.27, so the existing struct-literal
construction

    let ws_config = WebSocketConfig {
        max_message_size: Some(MAX_MESSAGE_SIZE),
        max_frame_size: Some(MAX_FRAME_SIZE),
        ..WebSocketConfig::default()
    };

produces

    error[E0639]: cannot create non-exhaustive struct using struct expression

Switch to the builder-style setters that 0.29 exposes on the default
value. Semantics are unchanged; both fields still carry the
MAX_MESSAGE_SIZE / MAX_FRAME_SIZE caps from the original config and the
rest of the WebSocketConfig defaults are preserved by starting from
WebSocketConfig::default().

This supersedes #13 — same target version, plus the code change
Dependabot can't make on its own.

Verified locally:
  cargo check -p wickra-data --features live-binance  # clean
  cargo test --workspace --all-features  # 630 passed / 0 failed
  cargo clippy --workspace --all-targets --all-features -- -D warnings
2026-05-23 21:31:57 +02:00
kingchenc 1011fa2bbd deps: bump criterion 0.5 -> 0.8 and switch to std::hint::black_box
Dependabot opened #10 to bump criterion from 0.5.1 to 0.8.2 but the
straight version bump fails to build: criterion::black_box was
deprecated in 0.6 and removed/marked deny-warn in 0.8, so the existing
`use criterion::{black_box, ...}` produces

    error: use of deprecated function `criterion::black_box`: use
    `std::hint::black_box()` instead

across every bench callsite. Switch the import to std::hint::black_box
(stable since Rust 1.66, well under our MSRV of 1.85) and drop the
criterion re-export.

This supersedes #10 — same target version, plus the code change
Dependabot can't make on its own.

Verified locally:
  cargo bench -p wickra --no-run  # builds clean
  cargo clippy --workspace --all-targets --all-features -- -D warnings
  cargo test --workspace  # 630 passed / 0 failed
2026-05-23 21:30:09 +02:00
dependabot[bot] 7a501a39cf deps(actions): bump actions/download-artifact from 4.3.0 to 8.0.1
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.3.0 to 8.0.1.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/d3f86a106a0bac45b974a628896c90dbdf5c8093...3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: 8.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-23 19:26:58 +00:00
dependabot[bot] 4acb2c7d68 deps(actions): bump actions/upload-artifact from 4.6.2 to 7.0.1
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.2 to 7.0.1.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/ea165f8d65b6e75b540449e92b4886f43607fa02...043fb46d1a93c77aae656e7c1c64a875d1fc6a0a)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-23 19:26:45 +00:00
kingchenc 7ba4380693 Merge pull request #1 from kingchenc/dependabot/github_actions/actions/checkout-6.0.2
deps(actions): bump actions/checkout from 4.3.1 to 6.0.2
2026-05-23 21:21:32 +02:00
dependabot[bot] 862a130d8c deps(actions): bump actions/checkout from 4.3.1 to 6.0.2
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.3.1 to 6.0.2.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/34e114876b0b11c390a56381ad16ebd13914f8d5...de0fac2e4500dabe0009e67214ff5f5447ce83dd)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.2
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-23 19:21:27 +00:00
kingchenc c009ecc7f9 Merge pull request #8 from kingchenc/dependabot/github_actions/actions/setup-python-6.2.0
deps(actions): bump actions/setup-python from 5.6.0 to 6.2.0
2026-05-23 21:19:29 +02:00
kingchenc bf4faddf0f Merge pull request #6 from kingchenc/dependabot/github_actions/actions/setup-node-6.4.0
deps(actions): bump actions/setup-node from 4.4.0 to 6.4.0
2026-05-23 21:19:26 +02:00
kingchenc 0112693ec3 Merge pull request #4 from kingchenc/dependabot/github_actions/softprops/action-gh-release-3.0.0
deps(actions): bump softprops/action-gh-release from 2.6.2 to 3.0.0
2026-05-23 21:19:23 +02:00
kingchenc 4dd36953c3 Merge pull request #2 from kingchenc/dependabot/github_actions/codecov/codecov-action-6.0.1
deps(actions): bump codecov/codecov-action from 5.5.4 to 6.0.1
2026-05-23 21:19:20 +02:00
dependabot[bot] 9b34c84d86 deps(actions): bump actions/setup-python from 5.6.0 to 6.2.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.6.0 to 6.2.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/a26af69be951a213d495a4c3e4e4022e16d87065...a309ff8b426b58ec0e2a45f0f869d46889d02405)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: 6.2.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-23 18:54:30 +00:00
dependabot[bot] 08928c5b17 deps(actions): bump actions/setup-node from 4.4.0 to 6.4.0
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4.4.0 to 6.4.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/49933ea5288caeca8642d1e84afbd3f7d6820020...48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 6.4.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-23 18:54:20 +00:00
dependabot[bot] 86b4f73ec7 deps(actions): bump softprops/action-gh-release from 2.6.2 to 3.0.0
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.6.2 to 3.0.0.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/3bb12739c298aeb8a4eeaf626c5b8d85266b0e65...b4309332981a82ec1c5618f44dd2e27cc8bfbfda)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: 3.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-23 18:54:12 +00:00
dependabot[bot] 246d982e93 deps(actions): bump codecov/codecov-action from 5.5.4 to 6.0.1
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.5.4 to 6.0.1.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/75cd11691c0faa626561e295848008c8a7dddffe...e79a6962e0d4c0c17b229090214935d2e33f8354)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: 6.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-23 18:53:50 +00:00
kingchenc 3d2e58c531 Merge pull request #14 from kingchenc/dependabot/cargo/serde_json-1.0.150
deps(cargo): bump serde_json from 1.0.149 to 1.0.150
2026-05-23 20:52:11 +02:00
kingchenc f966841ced Merge pull request #3 from kingchenc/dependabot/github_actions/taiki-e/install-action-2.79.5
deps(actions): bump taiki-e/install-action from 2.79.4 to 2.79.5
2026-05-23 20:51:56 +02:00
dependabot[bot] 99490f8604 deps(cargo): bump serde_json from 1.0.149 to 1.0.150
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.149 to 1.0.150.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.149...v1.0.150)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-version: 1.0.150
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-23 18:49:27 +00:00
dependabot[bot] 4d8c0ce1a5 deps(actions): bump taiki-e/install-action from 2.79.4 to 2.79.5
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.79.4 to 2.79.5.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/e0eafa9a0d485c37f97c0f7beb930a58a2facbac...6c1f7cf125e42770ff087ea443901b487cc5471a)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.79.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-23 18:49:22 +00:00
kingchenc d063555cd1 Merge pull request #15 from kingchenc/fix/ci-and-deps
fix(ci): green up the matrix — HV epsilon, examples step, fuzz install, MSRV bump
2026-05-23 20:47:41 +02:00
kingchenc df179fa579 fix(aggregator): cap gap-fill at 1_000_000 candles per push (fuzz finding)
The tick_aggregator fuzz target found that TickAggregator::fill_between
allocates one placeholder Candle per skipped bucket without bounding the
gap size. An adversarial input (a clock-glitch tick years in the future)
produced an OOM on libFuzzer after malloc(~3 GB):

    SUMMARY: libFuzzer: out-of-memory (malloc(3221225472))

A real-world failure mode too: a single bad timestamp from a flaky feed
could OOM the host process even though every individual tick passed
Tick::new validation.

Fix:
- Compute the gap size up-front via saturating arithmetic, before any
  allocation, and refuse with Error::Malformed when it exceeds the new
  MAX_GAP_FILL_CANDLES = 1_000_000 cap (≈ 1.9 years of contiguous
  one-minute bars, well above any realistic missing-data window).
- Reserve the right Vec capacity in advance once we know the gap fits,
  avoiding intermediate reallocations.
- Add two regression tests: gap_fill_rejects_runaway_timestamp_jump
  (the fuzz scenario) and gap_fill_at_the_cap_succeeds (exact-cap input
  still works).

The cap is exposed as pub const so callers can pre-validate their input
without relying on the error string.
2026-05-23 20:41:03 +02:00
kingchenc d52ddeaccb fix(core): use f64::midpoint and stable %2 to satisfy newer toolchains
Two unrelated newer-toolchain breakages bundled because they hit on the
same CI run and have the same shape (newer Rust got stricter about
patterns we used):

1. clippy 1.95 added the manual_midpoint lint which fires on every
   instance of (a + b) / 2.0 with a help suggesting f64::midpoint.
   CI runs with -D warnings so it became a hard error. Twelve sites
   were affected — three real call sites in src/ohlcv.rs (median_price),
   src/indicators/donchian.rs (DonchianOutput.middle),
   src/indicators/ease_of_movement.rs (mid), and
   src/indicators/super_trend.rs (hl2); plus eight test-helper
   Candle::new constructions across accelerator_oscillator,
   atr_trailing_stop, chaikin_volatility, chandelier_exit,
   chande_kroll_stop, choppiness_index, super_trend, true_range.
   All twelve switched to f64::midpoint (stable since Rust 1.85,
   our workspace MSRV).

2. usize::is_multiple_of is still unstable (rust-lang/rust#128101) and
   only stabilizes in Rust 1.87, but the MSRV CI job uses 1.85. The
   two call sites in bollinger.rs and sma.rs (added with the R7
   periodic-reseed tests) switched back to i % 2 == 0.
2026-05-23 20:37:35 +02:00
kingchenc 3f9429dd2c build: lift workspace MSRV further to 1.85 for criterion's clap_lex
The first MSRV bump to 1.80 fixed the rayon-core floor but ran into a
second transitive-dep floor on the same CI run:

    error: failed to parse manifest at clap_lex-1.1.0/Cargo.toml
    Caused by: feature `edition2024` is required
    The package requires the Cargo feature called `edition2024`, but
    that feature is not stabilized in this version of Cargo (1.80.1).

clap_lex 1.1.0 is pulled in by criterion (dev-dep on the wickra crate)
via clap 4.6 -> clap_builder 4.6 -> clap_lex 1.1. edition2024 was
stabilized in Rust 1.85, so lift the workspace MSRV one more step. The
1.85 floor subsumes the rayon-core 1.80 requirement; bindings/node
stays at 1.88 (napi-build) which already covers everything below it.

Also fix the fuzz job: cargo-fuzz defaulted to building for
x86_64-unknown-linux-musl, which is not installed on the GitHub-hosted
ubuntu runner. Pass --target x86_64-unknown-linux-gnu explicitly on
every cargo fuzz run invocation so it builds for the actual host
target.
2026-05-23 20:30:46 +02:00
kingchenc 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
2026-05-23 20:22:35 +02:00
kingchenc 90d8a299c3 ci(fuzz): install cargo-fuzz from a prebuilt binary
The previous `cargo install cargo-fuzz --locked` resolved against
cargo-fuzz's own Cargo.lock which pins to rustix 0.36.5. That rustix
version still annotates its source with internal #[rustc_*] attributes,
and the current nightly compiler rejects those attributes from
out-of-tree crates, so cargo-fuzz never finished compiling on CI:

    error: attributes starting with `rustc` are reserved for use by the
    `rustc` compiler
      --> rustix-0.36.5/src/backend/linux_raw/io/errno.rs:28
    error: could not compile `rustix` (lib) due to 4 previous errors
    error: failed to compile `cargo-fuzz v0.13.1`

Switch the install to taiki-e/install-action, the same prebuilt-binary
provider we already use for cargo-llvm-cov. That skips the full
transitive-dependency compile and lets the fuzz-smoke job actually run.
2026-05-23 20:21:03 +02:00
kingchenc 73f8da49bd ci: build wickra-examples bins instead of removed cargo examples
The Z5 reorganisation moved every runnable example out of the per-crate
examples/ folders and into a dedicated wickra-examples crate at
examples/rust/, with the binaries living under src/bin/<name>.rs. The
old ci.yml Compile-examples step still pointed at the now-deleted
cargo example targets backtest (wickra) and live_binance (wickra-data),
which is why Rust windows-latest failed with 'no example target named
backtest in wickra package' and 'no example target named live_binance
in wickra-data package'.

Replace both calls with a single cargo build -p wickra-examples --bins.
That covers backtest, live_binance, fetch_btcusdt, multi_timeframe,
parallel_assets and streaming in one shot, and the wickra-examples
crate already enables the live-binance feature on its wickra-data dep
so no extra --features flag is needed.
2026-05-23 20:19:14 +02:00
kingchenc ae8fcd9051 test(hv): widen geometric_series_yields_zero tolerance to 1e-6
The mathematical result of HistoricalVolatility on a perfectly geometric
price series is exactly zero — but the underlying 1.01_f64.powi(i) +
log-return + std-dev cascade accumulates platform-sensitive FP drift on
the order of 1e-7 on x86_64 Linux and macOS (the Windows result happened
to round closer to zero, which is why the test passed locally and on the
Windows CI runner but failed on Linux and macOS).

Bump the tolerance from 1e-9 to 1e-6. That stays four decimal places
below any realistic annualised volatility value while comfortably
absorbing the observed cross-platform drift.

Also extend the comment to document the rationale so the next person
who reads the test does not tighten it back down.
2026-05-23 20:18:24 +02:00
kingchenc 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
v0.2.0
2026-05-23 19:58:02 +02:00
kingchenc 11c52b8e86 release(0.1.5): stamp the CHANGELOG date
Replace the TBD placeholder under ## [0.1.5] with today's date so the
release header is complete. The compare-link at v0.1.4...v0.1.5 is
already in place and points at the right pair.
2026-05-23 19:12:11 +02:00
kingchenc c6d2030103 chore(python): add canonical authors metadata to pyproject.toml
Every other manifest in the repo carries the author string
kingchenc <kingchencp@gmail.com> — workspace Cargo.toml,
bindings/node/package.json, all per-platform npm package.jsons,
release.yml. pyproject.toml had neither authors nor maintainers, so
the PyPI listing fell back to the implicit empty value. Align with
the rest of the repo.
2026-05-23 19:11:53 +02:00
kingchenc a10bda188f docs(wiki): align RollingVwap count with Home and README
Indicators-Overview.md listed RollingVwap as its own row in the Volume
table (count = 9, total = 72), but Home.md and README.md treat it as
a sub-variant of Vwap (count = 8, total = 71). Drop the standalone row
in Indicators-Overview and fold the rolling variant into the Vwap row.
Warmup-Periods.md keeps both constructors (Vwap::new() and
RollingVwap::new(n) have different warmup periods, which is the whole
purpose of that page) but adds a note explaining that the row count
exceeds the 71 canonical indicators by one.
2026-05-23 19:11:34 +02:00
kingchenc 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.
2026-05-23 19:10:18 +02:00
kingchenc f3ad0940c3 docs(wiki): correct Node warmupPeriod() coverage note (R3, R8)
The Quickstart-Node API-surface table claimed `warmupPeriod()` was
"not exposed on every multi-output class". After the B-series fixes
in `todo-detailed.md` and the R3/R8 pass on this branch, every Node
indicator class — single- and multi-output, scalar- and candle-input —
exposes `warmupPeriod()`. Updated the note accordingly.
2026-05-23 11:04:15 +02:00
kingchenc a32719d709 docs(wiki): expand WASM Quickstart for the full multi-output surface (R3)
The Quickstart-WASM page only mentioned `MACD` and `BollingerBands` as
multi-output indicators. After R3 (this branch) every candle-input
WASM class also exposes a structured `update`: `Stochastic`, `ADX`,
`Keltner`, `Donchian`, `Aroon` (plus `SuperTrend`, which has always
been there).

The "Multi-output indicators" section now lists every multi-output
shape in a table, and a short note at the end calls out the 0.1.5
parity: every candle-input indicator ships `update` / `batch` /
`reset` / `isReady` / `warmupPeriod`, so browser code doesn't need
to replay `batch` on each tick anymore.
2026-05-23 11:03:53 +02:00
kingchenc 6fd110b4ce docs(wiki): document O(1) regression update and long-stream sum reseed (R2, R7)
Three indicator pages get a short follow-up paragraph that surfaces an
internal implementation detail the audit findings made user-visible:

- `Indicator-LinearRegression.md` gains a "Complexity" section explaining
  the O(1) update (precomputed `Σx`, `Σxx`; incrementally slid `Σy`,
  `Σxy` via the closed-form sliding identity), and the existing
  "Reset" bullet mentions the additional running accumulators. The same
  story applies to `LinRegSlope` and `LinRegAngle` (the page now links
  to both rather than repeating the derivation three times).

- `Indicator-Sma.md` and `Indicator-BollingerBands.md` mention the
  periodic reseed (`16 · period` updates) that caps floating-point
  drift on long-running streams. Amortised cost is still O(1) and the
  user-facing behaviour on benign inputs is unchanged.

No behavioural claim, no API claim, no example changes — just narrative
catching up with the implementation.
2026-05-23 11:03:18 +02:00
kingchenc 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.
2026-05-23 10:58:08 +02:00
kingchenc 62fcab1c86 docs(wiki): refresh PSAR streaming exposure and HV non-positive behaviour
PSAR — the wiki page still claimed "Node streaming. Not exposed in the
Node binding." That was true for an early release but is no longer:
the Node binding has exposed `psar.update(high, low, close)` since the
B1 fix in todo-detailed.md, and the WASM binding now exposes the same
streaming surface (audit finding R3, this branch). The page lists all
three streaming + batch shapes and adds a paragraph on the new
`is_ready` convention (audit finding R6 — flips on the first non-None
SAR, not on the seed candle).

HistoricalVolatility — the "Non-positive prices" edge-case note still
described the old behaviour ("that return is treated as 0"). The new
behaviour skips the bad tick entirely (audit finding R13): the
indicator's state is left untouched, the previous valid value is
returned, and the next real tick re-anchors against the previous
*valid* price. Updated the note to describe the new behaviour and
explain why (silently treating bad ticks as "no movement" underreports
realised volatility).
2026-05-23 10:54:51 +02:00
kingchenc 6786917cee ci: move bench to scheduled workflow + add Python 3.13 (R10, R11)
R10 — `cross-library-bench` previously ran on every push and every PR
to `main`, adding 5–10 minutes of build + bench time per CI run with
no automated consumer of the artefact. It moves to a dedicated
workflow (`.github/workflows/bench.yml`) that fires nightly at 03:00
UTC and on-demand via `workflow_dispatch` (with optional `size` /
`iterations` inputs). The job in `ci.yml` is removed and a pointer
comment is left in its place so future readers find the new home.

R11 — `pyproject.toml`'s `requires-python = ">=3.9"` already allowed
Python 3.13 installs, but the classifier list stopped at 3.12 and CI
tested only 3.9 / 3.11 / 3.12. The Python CI matrix gains `"3.13"`
and the matching `Programming Language :: Python :: 3.13` classifier
is added so PyPI listings and version-search tooling reflect the
actually-tested range.
2026-05-23 10:52:20 +02:00
kingchenc 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.
2026-05-23 10:50:29 +02:00
kingchenc 183ebec7ba fix(core): skip non-positive HV prices and add Error::InvalidTick (R13, R14)
R13 — `HistoricalVolatility::update` previously substituted `0.0` for
the log-return whenever `prev <= 0` or `input <= 0`. The log-return is
undefined there, and silently treating bad ticks as "no movement"
underreports realised volatility on broken data feeds. The fix skips
non-positive prices entirely: `self.last` is returned, state is left
untouched, and the next real tick re-anchors against the previous
*valid* `prev_price`. This matches how every other indicator handles
invalid inputs (SMA / EMA / ROC / Bollinger).

A new test `skips_non_positive_prices` proves the invariant: after a
warmed-up indicator, two consecutive bad ticks (`-5.0` and `0.0`) must
return the baseline value, and a subsequent real positive tick must
produce the same output as a control indicator that simply never saw
the bad ticks.

R14 — `Tick::new` previously returned `Error::InvalidCandle` for
negative volume. A tick is not a candle; downstream tick-stream
pipelines should be able to match on a semantically-correct error. A
new `Error::InvalidTick { message }` variant is added; the existing
test is updated to assert against it. Python's `map_err` is extended
to forward the new variant as `PyValueError`; the Node and WASM
bindings format via `Error::to_string()` and pick the new variant up
automatically without source changes.
2026-05-23 10:46:52 +02:00
kingchenc 510013fc5a fix(sma, bollinger): periodic recompute to bound long-stream drift (R7, L2-Rust)
`Sma` and `BollingerBands` both maintained their running `sum` (and
`sum_sq` for Bollinger) with a single-subtract incremental update. That
is correct in exact arithmetic, but in f64 the sequence `sum -= old;
sum += new` on long streams with alternating large/small magnitudes
can accumulate catastrophic-cancellation error. Bollinger's existing
`.max(0.0)` clamp on the computed variance was a band-aid for the same
root cause — the drift had already driven the running variance below
zero.

The fix: every `16 · period` finite updates, reseed `sum` (and `sum_sq`
for Bollinger) from the live window. Amortised cost stays at O(1) —
`O(period)` work amortised over `O(period)` updates — and the reseed
strategy is named after the constant `RECOMPUTE_EVERY` so the
intention is clear at the call site.

Behaviour is unchanged on inputs that did not drift to begin with
(every existing test still passes, including `batch_equals_streaming`
and the SMA proptest). Two new stress tests
(`long_stream_drift_stays_bounded` in each module) feed a
magnitude-alternating stream for `5 · RECOMPUTE_EVERY · period`
updates and assert the reported value tracks a fresh from-scratch
computation over the live window to within tight tolerance — these
would have failed without the reseed on Bollinger's `sum_sq`.

The misleading `sma.rs` comment that claimed drift was already
bounded by recomputing the sum after each pop is rewritten to
describe the actual reseed strategy (audit finding L2-Rust).
2026-05-23 10:42:50 +02:00
kingchenc 2db546ac12 chore(coppock): fix unbalanced backticks in the new doc comment
Follow-up to b340ecd — the doc comment on
`warmup_period_matches_first_some_for_every_parameter_set` had an
unbalanced inline-code span ("`Some``") that tripped
`clippy::doc_invalid_doc_attributes` (caught by `clippy -D warnings`
but not by `cargo build` or `cargo test`). Rephrased the sentence so
every backtick is paired. No code change, no test change.
2026-05-23 10:38:46 +02:00
kingchenc b340ecd3d6 test(coppock): lock in warmup_period for every parameter set (refutes R12)
Audit finding R12 claimed `Coppock::warmup_period()` was off by one
because it returns `max(roc_long, roc_short) + wma`, while
`Roc::warmup_period() = period + 1`. After tracing the actual emission
sequence the existing formula is correct: when both ROCs reach `Some`
at 0-based index L (the slower of `roc_long_period` and
`roc_short_period`), the WMA receives its first input there and emits
its `wma_period`-th value at 0-based index `L + wma_period − 1`. The
`warmup_period()` is the 1-based count of inputs needed before the
first `Some`, i.e. `L + wma_period`. R12 was a misread by both Sonnet
audit agents and the Opus verifier — none of them traced the actual
emission timeline.

This commit:

- Expands the doc comment on `warmup_period` with the precise emission
  argument and a worked example for `Coppock::new(6, 4, 3)` (the
  existing test) so a future reader cannot mis-derive the formula.
- Adds `warmup_period_matches_first_some_for_every_parameter_set`,
  which asserts `out[warmup - 1].is_some()` for five parameter
  combinations — including the audit's smoking gun `(4, 2, 3)`. The
  audit's proposed `max + 1 + wma` formula would have predicted index
  7 (the 8th input) for that combination; the real first `Some` lands
  at index 6 (the 7th input), exactly what the current formula
  reports.

No behaviour change — the audit was wrong and the test makes the
contract regression-proof.
2026-05-23 10:38:24 +02:00
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