Compare commits

...

33 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
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
38 changed files with 375 additions and 265 deletions
+3 -3
View File
@@ -30,14 +30,14 @@ jobs:
name: Cross-library benchmark report
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.11"
@@ -64,7 +64,7 @@ jobs:
| tee benchmark.txt
- name: Upload report
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: cross-library-bench
path: bindings/python/benchmark.txt
+45 -28
View File
@@ -19,7 +19,7 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
@@ -48,14 +48,22 @@ jobs:
run: cargo build -p wickra --benches --verbose
- name: Compile examples
run: |
cargo build -p wickra --example backtest
cargo build -p wickra-data --example live_binance --features live-binance
# All runnable examples now live in the dedicated wickra-examples crate
# (examples/rust/src/bin/*.rs) which enables the live-binance feature
# on its wickra-data dep, so a single --bins build covers backtest,
# live_binance, fetch_btcusdt, multi_timeframe, parallel_assets and
# streaming.
run: cargo build -p wickra-examples --bins
# Verify the crates still build and test on their declared minimum supported
# Rust version. The workspace pins rust-version = "1.75"; bindings/node needs
# 1.77 because napi-build emits `cargo::` directives. Without this job an
# accidental use of a newer API would only surface for downstream users.
# Rust version. The workspace pins rust-version = "1.86" — that floor is
# set by criterion 0.8.2 (the bench dev-dep), which itself rolled past the
# clap_lex 1.1.0 / edition2024 / Rust 1.85 floor and now needs 1.86; the
# earlier rayon-core 1.13.0 (1.80) and clap_lex (1.85) requirements are
# subsumed. bindings/node pins rust-version = "1.88" because napi-build
# 2.3.2 requires it (and that subsumes the older 1.77 floor needed for
# `cargo::` directives). Without this job an accidental use of a newer
# API would only surface for downstream users.
msrv:
name: ${{ matrix.name }}
runs-on: ubuntu-latest
@@ -63,14 +71,14 @@ jobs:
fail-fast: false
matrix:
include:
- name: MSRV workspace (Rust 1.75)
toolchain: "1.75"
- name: MSRV workspace (Rust 1.86)
toolchain: "1.86"
packages: "-p wickra-core -p wickra -p wickra-data"
- name: MSRV node binding (Rust 1.77)
toolchain: "1.77"
- name: MSRV node binding (Rust 1.88)
toolchain: "1.88"
packages: "-p wickra-node"
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Rust ${{ matrix.toolchain }}
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
@@ -91,7 +99,7 @@ jobs:
name: Coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
@@ -102,7 +110,7 @@ jobs:
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@e0eafa9a0d485c37f97c0f7beb930a58a2facbac # v2.79.4
uses: taiki-e/install-action@6c1f7cf125e42770ff087ea443901b487cc5471a # v2.79.5
with:
tool: cargo-llvm-cov
@@ -114,7 +122,7 @@ jobs:
--lcov --output-path lcov.info
- name: Upload to Codecov
uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5.5.4
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
with:
files: lcov.info
fail_ci_if_error: false
@@ -127,7 +135,7 @@ jobs:
name: Supply-chain (cargo-deny)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: cargo-deny
uses: EmbarkStudios/cargo-deny-action@a531616d8ce3b9177443e48a1159bc945a099823 # v2.0.19
@@ -143,7 +151,7 @@ jobs:
name: Fuzz (smoke)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install nightly Rust
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
@@ -156,26 +164,35 @@ jobs:
workspaces: fuzz
- name: Install cargo-fuzz
run: cargo install cargo-fuzz --locked
# Use the prebuilt binary from taiki-e/install-action instead of
# `cargo install cargo-fuzz --locked`. The latter resolves the
# version graph from cargo-fuzz's own Cargo.lock, which pins to
# rustix 0.36.5 — a version that still uses internal `rustc_*`
# attributes the modern nightly compiler rejects, so the install
# never gets off the ground. The prebuilt binary avoids the entire
# transitive-dep compile.
uses: taiki-e/install-action@6c1f7cf125e42770ff087ea443901b487cc5471a # v2.79.5
with:
tool: cargo-fuzz
- name: Fuzz csv_reader (30 s)
run: cargo +nightly fuzz run csv_reader -- -max_total_time=30
run: cargo +nightly fuzz run --target x86_64-unknown-linux-gnu csv_reader -- -max_total_time=30
working-directory: fuzz
- name: Fuzz binance_envelope (30 s)
run: cargo +nightly fuzz run binance_envelope -- -max_total_time=30
run: cargo +nightly fuzz run --target x86_64-unknown-linux-gnu binance_envelope -- -max_total_time=30
working-directory: fuzz
- name: Fuzz indicator_update (30 s)
run: cargo +nightly fuzz run indicator_update -- -max_total_time=30
run: cargo +nightly fuzz run --target x86_64-unknown-linux-gnu indicator_update -- -max_total_time=30
working-directory: fuzz
- name: Fuzz indicator_update_candle (30 s)
run: cargo +nightly fuzz run indicator_update_candle -- -max_total_time=30
run: cargo +nightly fuzz run --target x86_64-unknown-linux-gnu indicator_update_candle -- -max_total_time=30
working-directory: fuzz
- name: Fuzz tick_aggregator (30 s)
run: cargo +nightly fuzz run tick_aggregator -- -max_total_time=30
run: cargo +nightly fuzz run --target x86_64-unknown-linux-gnu tick_aggregator -- -max_total_time=30
working-directory: fuzz
python:
@@ -187,7 +204,7 @@ jobs:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.9", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
@@ -196,7 +213,7 @@ jobs:
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ matrix.python-version }}
@@ -222,7 +239,7 @@ jobs:
name: WASM build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Rust toolchain (with wasm target)
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
@@ -256,7 +273,7 @@ jobs:
os: [ubuntu-latest, macos-latest, windows-latest]
node-version: ["18", "20"]
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
@@ -265,7 +282,7 @@ jobs:
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
- name: Set up Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ matrix.node-version }}
+30 -22
View File
@@ -23,7 +23,7 @@ jobs:
# Settings -> Environments.
environment: release
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
@@ -74,7 +74,7 @@ jobs:
cargo package -p wickra
- name: Upload .crate files
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: crate-files
path: target/package/*.crate
@@ -102,8 +102,8 @@ jobs:
- { os: windows-11-arm, target: aarch64, manylinux: auto }
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.11"
- uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
@@ -112,7 +112,7 @@ jobs:
target: ${{ matrix.target }}
args: --release --strip --out dist
manylinux: ${{ matrix.manylinux }}
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
# Include manylinux in the name so the glibc and musl x86_64/aarch64
# builds do not collide on the same artifact name.
@@ -123,13 +123,13 @@ jobs:
name: Build Python sdist
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
with:
working-directory: bindings/python
command: sdist
args: --out dist
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: wheels-sdist
path: bindings/python/dist/*
@@ -140,7 +140,7 @@ jobs:
runs-on: ubuntu-latest
environment: release
steps:
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: dist
pattern: wheels-*
@@ -168,12 +168,20 @@ jobs:
- { host: macos-latest, target: x86_64-apple-darwin }
- { host: macos-latest, target: aarch64-apple-darwin }
- { host: windows-latest, target: x86_64-pc-windows-msvc }
- { host: windows-11-arm, target: aarch64-pc-windows-msvc }
# NOTE: aarch64-pc-windows-msvc is temporarily skipped for 0.2.1.
# The wickra-win32-arm64-msvc npm subpackage name is blocked by the
# npm spam-detection filter for new accounts (same situation that
# affected wickra-win32-x64-msvc through 0.1.4 until npm Support
# unblocked it). A support ticket is open; once the new arm64 name
# is unblocked this matrix entry will be restored alongside the
# corresponding optionalDependencies / napi.triples / npm/<target>
# entries in a follow-up release.
# - { host: windows-11-arm, target: aarch64-pc-windows-msvc }
runs-on: ${{ matrix.host }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "20"
@@ -192,7 +200,7 @@ jobs:
run: npx napi build --platform --release --target ${{ matrix.target }}
- name: Upload artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: bindings-${{ matrix.target }}
path: bindings/node/wickra.*.node
@@ -204,9 +212,9 @@ jobs:
runs-on: ubuntu-latest
environment: release
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "20"
registry-url: "https://registry.npmjs.org"
@@ -216,7 +224,7 @@ jobs:
run: npm install
- name: Download all platform binaries
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: bindings/node/artifacts
pattern: bindings-*
@@ -323,7 +331,7 @@ jobs:
done
- name: Upload Node tarballs
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: node-tarballs
path: |
@@ -338,9 +346,9 @@ jobs:
runs-on: ubuntu-latest
environment: release
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "20"
registry-url: "https://registry.npmjs.org"
@@ -373,7 +381,7 @@ jobs:
run: npm pack
- name: Upload WASM tarball
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: wasm-tarball
path: bindings/wasm/pkg/wickra-wasm-*.tgz
@@ -397,7 +405,7 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
@@ -418,7 +426,7 @@ jobs:
echo "::notice::attaching assets to release $tag"
- name: Download all build artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: artifacts
@@ -439,7 +447,7 @@ jobs:
echo "asset-count=$(ls release-assets/ | wc -l)"
- name: Create / update GitHub Release with assets
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
tag_name: ${{ steps.tag.outputs.tag }}
name: Wickra ${{ steps.tag.outputs.tag }}
+82 -1
View File
@@ -7,6 +7,86 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.2.1] - 2026-05-23
### Changed
- **MSRV bumped.** Workspace minimum supported Rust version is now **1.86**
(was 1.75) and the Node binding (`wickra-node`) is now **1.88** (was 1.77).
The bumps are driven by transitive-dependency floors that were lifted in
recent updates: `criterion 0.8.2` (the bench dev-dep) requires Rust 1.86,
and `napi-build >= 2.3.2` requires Rust 1.88. Pinning those deps to the
older versions would have frozen us out of future security fixes from
those upstreams, so lifting the MSRV is the cleaner path for a young 0.x
library. Downstream consumers on older Rust toolchains can stay on
Wickra 0.2.0.
- Bumped the bench dev-dep `criterion` from 0.5 to 0.8 and migrated
`bindings/wickra/benches/indicators.rs` from the deprecated
`criterion::black_box` re-export to the stable `std::hint::black_box`.
- Bumped `tokio-tungstenite` from 0.24 to 0.29. `WebSocketConfig` became
`#[non_exhaustive]` upstream, so the struct-literal construction in
`crates/wickra-data/src/live/binance.rs` is rewritten to the
builder-style `WebSocketConfig::default().max_message_size(..).max_frame_size(..)`.
Same caps, same semantics, same default carry-over.
- Bumped every committed CI/release GitHub Action to its latest pinned
SHA: `actions/checkout` 4 → 6, `actions/setup-node` 4 → 6,
`actions/setup-python` 5 → 6, `actions/upload-artifact` 4 → 7,
`actions/download-artifact` 4 → 8, `softprops/action-gh-release` 2 → 3,
`codecov/codecov-action` 5 → 6, `taiki-e/install-action` patch.
### Fixed
- `tick_aggregator` gap-fill no longer allocates an unbounded number of
placeholder candles. The new `MAX_GAP_FILL_CANDLES = 1_000_000` cap
surfaces an adversarial timestamp jump (e.g. a clock-glitch tick years
in the future) as `Error::Malformed` instead of an OOM panic. Found by
the new `tick_aggregator` fuzz target.
- `HistoricalVolatility::geometric_series_yields_zero` now uses an `1e-6`
tolerance instead of `1e-9`. The mathematical result 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 widened tolerance stays four decimal places below any
realistic annualised volatility value while absorbing the drift across
every supported platform.
- Replaced every `(high + low) / 2.0` test-helper and three real call
sites (`Ohlcv::median_price`, `Donchian.middle`, `EaseOfMovement.mid`,
`SuperTrend.hl2`) with `f64::midpoint(high, low)`. The change satisfies
clippy 1.95's new `manual_midpoint` lint without affecting values
(`f64::midpoint` matches the naive average to better than 1 ULP for the
inputs used here).
- Replaced `i.is_multiple_of(2)` (unstable on Rust 1.85) with `i % 2 == 0`
in the SMA / Bollinger long-stream-drift tests so the workspace MSRV
job builds cleanly on Rust 1.86.
- The `Compile examples` CI step now invokes
`cargo build -p wickra-examples --bins` instead of the now-deleted
`cargo build -p wickra --example backtest` / `-p wickra-data --example
live_binance` (the Z5 reorganisation moved every runnable example into
the dedicated `wickra-examples` crate, but the CI step had not been
updated).
- The `Fuzz (smoke)` CI job installs `cargo-fuzz` from a prebuilt binary
via `taiki-e/install-action` instead of `cargo install cargo-fuzz`.
The source install resolved against `rustix 0.36.5`, which uses
internal `#[rustc_*]` attributes the current nightly compiler rejects.
- The fuzz targets now build with an explicit
`--target x86_64-unknown-linux-gnu`; cargo-fuzz was defaulting to
`x86_64-unknown-linux-musl`, which is not installed on the standard
GitHub-hosted Ubuntu runner.
### Removed
- **`wickra-win32-arm64-msvc` is temporarily omitted from this release.**
The npm spam-detection filter blocks the first publish of this brand-new
package name (same situation that affected `wickra-win32-x64-msvc`
through 0.1.4 until npm Support unblocked it). A support ticket is open;
once the new name is unblocked the
`aarch64-pc-windows-msvc` triple will be restored in
`bindings/node/package.json` (`napi.triples.additional` +
`optionalDependencies`), in the `release.yml` `node-build` matrix, and
as a fresh `bindings/node/npm/win32-arm64-msvc/` template. Until then,
`npm install wickra@0.2.1` on Windows ARM64 will surface the loader's
standard `Cannot find module 'wickra-win32-arm64-msvc'` error; every
other platform (Linux x64 / Linux ARM64 / macOS x64 / macOS ARM64 /
Windows x64) ships normally. The PyPI wheel for Windows ARM64 is
unaffected and still published.
## [0.2.0] - 2026-05-23
### Fixed
@@ -249,7 +329,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
optional Binance live feed.
- Bindings for Python, Node.js, and WebAssembly.
[Unreleased]: https://github.com/kingchenc/wickra/compare/v0.2.0...HEAD
[Unreleased]: https://github.com/kingchenc/wickra/compare/v0.2.1...HEAD
[0.2.1]: https://github.com/kingchenc/wickra/compare/v0.2.0...v0.2.1
[0.2.0]: https://github.com/kingchenc/wickra/compare/v0.1.4...v0.2.0
[0.1.4]: https://github.com/kingchenc/wickra/compare/v0.1.3...v0.1.4
[0.1.3]: https://github.com/kingchenc/wickra/compare/v0.1.2...v0.1.3
Generated
+73 -125
View File
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "aho-corasick"
@@ -11,6 +11,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "alloca"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4"
dependencies = [
"cc",
]
[[package]]
name = "anes"
version = "0.1.6"
@@ -91,12 +100,6 @@ version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.11.1"
@@ -223,25 +226,24 @@ dependencies = [
[[package]]
name = "criterion"
version = "0.5.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3"
dependencies = [
"alloca",
"anes",
"cast",
"ciborium",
"clap",
"criterion-plot",
"is-terminal",
"itertools",
"num-traits",
"once_cell",
"oorandom",
"page_size",
"plotters",
"rayon",
"regex",
"serde",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
@@ -249,9 +251,9 @@ dependencies = [
[[package]]
name = "criterion-plot"
version = "0.5.0"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea"
dependencies = [
"cast",
"itertools",
@@ -479,17 +481,6 @@ dependencies = [
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "getrandom"
version = "0.3.4"
@@ -547,12 +538,6 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]]
name = "http"
version = "1.4.0"
@@ -690,22 +675,11 @@ dependencies = [
"serde_core",
]
[[package]]
name = "is-terminal"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
dependencies = [
"hermit-abi",
"libc",
"windows-sys",
]
[[package]]
name = "itertools"
version = "0.10.5"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
@@ -1008,6 +982,16 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "page_size"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "percent-encoding"
version = "2.3.2"
@@ -1116,8 +1100,8 @@ dependencies = [
"bit-vec",
"bitflags",
"num-traits",
"rand 0.9.4",
"rand_chacha 0.9.0",
"rand",
"rand_chacha",
"rand_xorshift",
"regex-syntax",
"rusty-fork",
@@ -1210,35 +1194,14 @@ version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rand"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
dependencies = [
"libc",
"rand_chacha 0.3.1",
"rand_core 0.6.4",
]
[[package]]
name = "rand"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.5",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core 0.6.4",
"rand_chacha",
"rand_core",
]
[[package]]
@@ -1248,16 +1211,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.5",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.17",
"rand_core",
]
[[package]]
@@ -1275,7 +1229,7 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
dependencies = [
"rand_core 0.9.5",
"rand_core",
]
[[package]]
@@ -1466,9 +1420,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.149"
version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
@@ -1563,33 +1517,13 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl 1.0.69",
]
[[package]]
name = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl 2.0.18",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
"thiserror-impl",
]
[[package]]
@@ -1661,9 +1595,9 @@ dependencies = [
[[package]]
name = "tokio-tungstenite"
version = "0.24.0"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9"
checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c"
dependencies = [
"futures-util",
"log",
@@ -1675,21 +1609,19 @@ dependencies = [
[[package]]
name = "tungstenite"
version = "0.24.0"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a"
checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8"
dependencies = [
"byteorder",
"bytes",
"data-encoding",
"http",
"httparse",
"log",
"native-tls",
"rand 0.8.6",
"rand",
"sha1",
"thiserror 1.0.69",
"utf-8",
"thiserror",
]
[[package]]
@@ -1734,12 +1666,6 @@ dependencies = [
"serde",
]
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "utf8_iter"
version = "1.0.4"
@@ -1941,7 +1867,7 @@ dependencies = [
[[package]]
name = "wickra"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"approx",
"criterion",
@@ -1952,17 +1878,17 @@ dependencies = [
[[package]]
name = "wickra-core"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"approx",
"proptest",
"rayon",
"thiserror 2.0.18",
"thiserror",
]
[[package]]
name = "wickra-data"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"approx",
"csv",
@@ -1970,7 +1896,7 @@ dependencies = [
"serde",
"serde_json",
"tempfile",
"thiserror 2.0.18",
"thiserror",
"tokio",
"tokio-tungstenite",
"url",
@@ -1989,7 +1915,7 @@ dependencies = [
[[package]]
name = "wickra-node"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"napi",
"napi-build",
@@ -1999,7 +1925,7 @@ dependencies = [
[[package]]
name = "wickra-python"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"numpy",
"pyo3",
@@ -2008,7 +1934,7 @@ dependencies = [
[[package]]
name = "wickra-wasm"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"console_error_panic_hook",
"js-sys",
@@ -2019,6 +1945,22 @@ dependencies = [
"wickra-core",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.11"
@@ -2028,6 +1970,12 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-link"
version = "0.2.1"
+4 -4
View File
@@ -12,10 +12,10 @@ members = [
exclude = ["fuzz"]
[workspace.package]
version = "0.2.0"
version = "0.2.1"
authors = ["kingchenc <kingchencp@gmail.com>"]
edition = "2021"
rust-version = "1.75"
rust-version = "1.86"
license = "PolyForm-Noncommercial-1.0.0"
repository = "https://github.com/kingchenc/wickra"
homepage = "https://github.com/kingchenc/wickra"
@@ -24,7 +24,7 @@ keywords = ["finance", "trading", "indicators", "technical-analysis", "ta"]
categories = ["finance", "mathematics", "science"]
[workspace.dependencies]
wickra-core = { path = "crates/wickra-core", version = "0.2.0" }
wickra-core = { path = "crates/wickra-core", version = "0.2.1" }
thiserror = "2"
rayon = "1.10"
@@ -32,7 +32,7 @@ rayon = "1.10"
# Testing
proptest = "1.5"
approx = "0.5"
criterion = { version = "0.5", features = ["html_reports"] }
criterion = { version = "0.8", features = ["html_reports"] }
# Python binding
pyo3 = { version = "0.28", features = ["extension-module", "abi3-py39"] }
+5 -3
View File
@@ -4,9 +4,11 @@ description = "Node.js bindings for the Wickra streaming-first technical indicat
version.workspace = true
authors.workspace = true
edition.workspace = true
# napi-build emits `cargo::` directives that require Rust >= 1.77; the rest of
# the workspace stays at 1.75 because the core crate has no such dependency.
rust-version = "1.77"
# napi-build 2.3.2 requires Rust >= 1.88 (newer than the workspace 1.80
# minimum, which itself was lifted to satisfy rayon-core 1.13.0). napi-build
# also emits `cargo::` directives that require >= 1.77 — that older floor is
# subsumed by the 1.88 requirement now.
rust-version = "1.88"
license.workspace = true
repository.workspace = true
homepage.workspace = true
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "wickra-darwin-arm64",
"version": "0.2.0",
"version": "0.2.1",
"description": "Native binding for wickra (macOS Apple Silicon). Installed automatically as an optional dependency of wickra on matching platforms.",
"main": "wickra.darwin-arm64.node",
"files": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "wickra-darwin-x64",
"version": "0.2.0",
"version": "0.2.1",
"description": "Native binding for wickra (macOS Intel). Installed automatically as an optional dependency of wickra on matching platforms.",
"main": "wickra.darwin-x64.node",
"files": [
@@ -1,6 +1,6 @@
{
"name": "wickra-linux-arm64-gnu",
"version": "0.2.0",
"version": "0.2.1",
"description": "Native binding for wickra (linux arm64 GNU). Installed automatically as an optional dependency of wickra on matching platforms.",
"main": "wickra.linux-arm64-gnu.node",
"files": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "wickra-linux-x64-gnu",
"version": "0.2.0",
"version": "0.2.1",
"description": "Native binding for wickra (linux x64 GNU). Installed automatically as an optional dependency of wickra on matching platforms.",
"main": "wickra.linux-x64-gnu.node",
"files": [
@@ -1,24 +0,0 @@
{
"name": "wickra-win32-arm64-msvc",
"version": "0.2.0",
"description": "Native binding for wickra (Windows arm64 MSVC). Installed automatically as an optional dependency of wickra on matching platforms.",
"main": "wickra.win32-arm64-msvc.node",
"files": [
"wickra.win32-arm64-msvc.node"
],
"license": "PolyForm-Noncommercial-1.0.0",
"engines": {
"node": ">= 18"
},
"os": [
"win32"
],
"cpu": [
"arm64"
],
"repository": {
"type": "git",
"url": "https://github.com/kingchenc/wickra"
},
"homepage": "https://github.com/kingchenc/wickra"
}
@@ -1,6 +1,6 @@
{
"name": "wickra-win32-x64-msvc",
"version": "0.2.0",
"version": "0.2.1",
"description": "Native binding for wickra (Windows x64 MSVC). Installed automatically as an optional dependency of wickra on matching platforms.",
"main": "wickra.win32-x64-msvc.node",
"files": [
+7 -9
View File
@@ -1,6 +1,6 @@
{
"name": "wickra",
"version": "0.2.0",
"version": "0.2.1",
"description": "Streaming-first technical indicators: incremental, fast, install-free. Node bindings powered by Rust.",
"author": "kingchenc <kingchencp@gmail.com>",
"main": "index.js",
@@ -38,8 +38,7 @@
"aarch64-unknown-linux-gnu",
"x86_64-apple-darwin",
"aarch64-apple-darwin",
"x86_64-pc-windows-msvc",
"aarch64-pc-windows-msvc"
"x86_64-pc-windows-msvc"
]
}
},
@@ -47,12 +46,11 @@
"node": ">= 18"
},
"optionalDependencies": {
"wickra-linux-x64-gnu": "0.2.0",
"wickra-linux-arm64-gnu": "0.2.0",
"wickra-darwin-x64": "0.2.0",
"wickra-darwin-arm64": "0.2.0",
"wickra-win32-x64-msvc": "0.2.0",
"wickra-win32-arm64-msvc": "0.2.0"
"wickra-linux-x64-gnu": "0.2.1",
"wickra-linux-arm64-gnu": "0.2.1",
"wickra-darwin-x64": "0.2.1",
"wickra-darwin-arm64": "0.2.1",
"wickra-win32-x64-msvc": "0.2.1"
},
"scripts": {
"build": "napi build --platform --release",
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "maturin"
[project]
name = "wickra"
version = "0.2.0"
version = "0.2.1"
description = "Streaming-first technical indicators: incremental, fast, install-free."
readme = "README.md"
license = { text = "PolyForm-Noncommercial-1.0.0" }
@@ -108,7 +108,7 @@ mod tests {
use approx::assert_relative_eq;
fn c(high: f64, low: f64, close: f64, ts: i64) -> Candle {
Candle::new((high + low) / 2.0, high, low, close, 1.0, ts).unwrap()
Candle::new(f64::midpoint(high, low), high, low, close, 1.0, ts).unwrap()
}
#[test]
@@ -139,7 +139,7 @@ mod tests {
use approx::assert_relative_eq;
fn c(high: f64, low: f64, close: f64, ts: i64) -> Candle {
Candle::new((high + low) / 2.0, high, low, close, 1.0, ts).unwrap()
Candle::new(f64::midpoint(high, low), high, low, close, 1.0, ts).unwrap()
}
#[test]
@@ -294,7 +294,7 @@ mod tests {
let n_updates = 16 * period * 5;
let mut last = None;
for i in 0..n_updates {
let v = if i.is_multiple_of(2) { 1e6 } else { 1.0 };
let v = if i % 2 == 0 { 1e6 } else { 1.0 };
last = bb.update(v);
if window.len() == period {
window.pop_front();
@@ -108,7 +108,7 @@ mod tests {
use approx::assert_relative_eq;
fn c(high: f64, low: f64, close: f64, ts: i64) -> Candle {
Candle::new((high + low) / 2.0, high, low, close, 1.0, ts).unwrap()
Candle::new(f64::midpoint(high, low), high, low, close, 1.0, ts).unwrap()
}
#[test]
@@ -173,7 +173,7 @@ mod tests {
use approx::assert_relative_eq;
fn c(high: f64, low: f64, close: f64, ts: i64) -> Candle {
Candle::new((high + low) / 2.0, high, low, close, 1.0, ts).unwrap()
Candle::new(f64::midpoint(high, low), high, low, close, 1.0, ts).unwrap()
}
#[test]
@@ -137,7 +137,7 @@ mod tests {
use approx::assert_relative_eq;
fn c(high: f64, low: f64, close: f64, ts: i64) -> Candle {
Candle::new((high + low) / 2.0, high, low, close, 1.0, ts).unwrap()
Candle::new(f64::midpoint(high, low), high, low, close, 1.0, ts).unwrap()
}
#[test]
@@ -134,7 +134,7 @@ mod tests {
use approx::assert_relative_eq;
fn c(high: f64, low: f64, close: f64, ts: i64) -> Candle {
Candle::new((high + low) / 2.0, high, low, close, 1.0, ts).unwrap()
Candle::new(f64::midpoint(high, low), high, low, close, 1.0, ts).unwrap()
}
#[test]
@@ -83,7 +83,7 @@ impl Indicator for Donchian {
.fold(f64::INFINITY, f64::min);
Some(DonchianOutput {
upper,
middle: (upper + lower) / 2.0,
middle: f64::midpoint(upper, lower),
lower,
})
}
@@ -95,7 +95,7 @@ impl Indicator for EaseOfMovement {
type Output = f64;
fn update(&mut self, candle: Candle) -> Option<f64> {
let mid = (candle.high + candle.low) / 2.0;
let mid = f64::midpoint(candle.high, candle.low);
let Some(prev_mid) = self.prev_mid else {
// The first candle only establishes the previous midpoint.
self.prev_mid = Some(mid);
@@ -205,11 +205,17 @@ mod tests {
#[test]
fn geometric_series_yields_zero() {
// A constant growth factor gives a constant log return -> zero stddev.
// The mathematical result is exactly zero, but `1.01_f64.powi(i)` and
// the subsequent log / std-dev cascade accumulate platform-sensitive
// floating-point drift on the order of 1e-7 (observed on x86_64 Linux
// and macOS; Windows happens to round closer to zero). The 1e-6
// tolerance stays four decimal places below any realistic volatility
// value while absorbing this drift across every supported platform.
let mut hv = HistoricalVolatility::new(10, 252).unwrap();
let prices: Vec<f64> = (0..40).map(|i| 100.0 * 1.01_f64.powi(i)).collect();
let out = hv.batch(&prices);
for v in out.iter().skip(10).flatten() {
assert_relative_eq!(*v, 0.0, epsilon = 1e-9);
assert_relative_eq!(*v, 0.0, epsilon = 1e-6);
}
}
+1 -1
View File
@@ -251,7 +251,7 @@ mod tests {
// `RECOMPUTE_EVERY * period * 5` updates → recompute fires 5+ times.
let n_updates = 16 * period * 5;
for i in 0..n_updates {
let v = if i.is_multiple_of(2) { 1e9 } else { 1.0 };
let v = if i % 2 == 0 { 1e9 } else { 1.0 };
sma.update(v);
if window.len() == period {
window.pop_front();
@@ -107,7 +107,7 @@ impl Indicator for SuperTrend {
fn update(&mut self, candle: Candle) -> Option<SuperTrendOutput> {
let atr = self.atr.update(candle)?;
let hl2 = (candle.high + candle.low) / 2.0;
let hl2 = f64::midpoint(candle.high, candle.low);
let basic_upper = hl2 + self.multiplier * atr;
let basic_lower = hl2 - self.multiplier * atr;
@@ -184,7 +184,7 @@ mod tests {
use crate::traits::BatchExt;
fn c(high: f64, low: f64, close: f64, ts: i64) -> Candle {
Candle::new((high + low) / 2.0, high, low, close, 1.0, ts).unwrap()
Candle::new(f64::midpoint(high, low), high, low, close, 1.0, ts).unwrap()
}
#[test]
@@ -82,7 +82,7 @@ mod tests {
use approx::assert_relative_eq;
fn c(high: f64, low: f64, close: f64, ts: i64) -> Candle {
Candle::new((high + low) / 2.0, high, low, close, 1.0, ts).unwrap()
Candle::new(f64::midpoint(high, low), high, low, close, 1.0, ts).unwrap()
}
#[test]
+1 -1
View File
@@ -110,7 +110,7 @@ impl Candle {
/// The mid price `(high + low) / 2`.
#[inline]
pub fn median_price(&self) -> f64 {
(self.high + self.low) / 2.0
f64::midpoint(self.high, self.low)
}
/// The weighted close `(high + low + 2*close) / 4`.
+1 -1
View File
@@ -30,7 +30,7 @@ serde_json = "1"
# Async / live feeds are opt-in: only pulled when a `live-*` feature is requested.
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros", "net", "time", "io-util"], optional = true }
tokio-tungstenite = { version = "0.24", optional = true, features = ["native-tls"] }
tokio-tungstenite = { version = "0.29", optional = true, features = ["native-tls"] }
futures-util = { version = "0.3", optional = true }
url = { version = "2", optional = true }
+77 -5
View File
@@ -3,6 +3,15 @@
use crate::error::{Error, Result};
use wickra_core::{Candle, Tick};
/// Hard cap on the number of placeholder candles a single
/// [`TickAggregator::push`] call may emit when gap-fill is enabled. One
/// million minute-candles is roughly 1.9 years of contiguous one-minute bars
/// — orders of magnitude beyond any realistic missing-data window in
/// production while still keeping the resulting `Vec<Candle>` to well under
/// 50 MB. Any larger gap is treated as malformed input rather than allowed
/// to OOM the process.
pub const MAX_GAP_FILL_CANDLES: i64 = 1_000_000;
/// A candle bucket size measured in the same unit as the tick timestamps.
///
/// Wickra is unit-agnostic about timestamps: choose whichever makes sense for
@@ -133,7 +142,11 @@ impl Timeframe {
/// the next non-empty bar — the skipped buckets produce no candle, so the
/// output series can have time holes. Enable [`TickAggregator::with_gap_fill`]
/// to instead emit a flat placeholder candle for every skipped bucket, giving
/// downstream indicators an unbroken, evenly spaced series.
/// downstream indicators an unbroken, evenly spaced series. To bound memory
/// against an adversarial timestamp jump, gap-filling refuses to emit more
/// than [`MAX_GAP_FILL_CANDLES`] placeholders in a single step; a larger gap
/// surfaces as an `Error::Malformed` so the caller can decide how to handle
/// the discontinuity.
#[derive(Debug, Clone)]
pub struct TickAggregator {
timeframe: Timeframe,
@@ -279,19 +292,47 @@ impl TickAggregator {
/// Append a flat placeholder candle for every empty bucket strictly between
/// the just-closed bar and the next bucket that received a tick.
///
/// Returns `Error::Malformed` when the gap would exceed
/// [`MAX_GAP_FILL_CANDLES`] — an adversarial timestamp jump (a clock-glitch
/// tick years in the future) must surface as a defined error, not as an
/// out-of-memory panic from allocating millions of placeholder candles.
fn fill_between(&self, prev: Candle, next_bucket: i64, out: &mut Vec<Candle>) -> Result<()> {
let step = self.timeframe.bucket();
let mut start = prev
let start = prev
.timestamp
.checked_add(step)
.ok_or_else(|| Error::Malformed("timestamp overflow while gap-filling".to_string()))?;
while start < next_bucket {
if start >= next_bucket {
return Ok(());
}
// Compute the gap size up-front so an adversarial timestamp delta
// is refused before we allocate. `step > 0` by `Timeframe::new`'s
// invariant, so the divisor is safe. Saturating the subtraction
// makes the arithmetic infallible; an overflowed-saturated span is
// still far above the cap so the limit check below catches it.
let span = next_bucket.saturating_sub(start);
let gap_count = span / step + i64::from(span % step != 0);
if gap_count > MAX_GAP_FILL_CANDLES {
return Err(Error::Malformed(format!(
"gap-fill between bucket {} and {next_bucket} would emit {gap_count} \
flat candles at step {step}, exceeding the {MAX_GAP_FILL_CANDLES} \
cap; reject the discontinuity instead of allocating",
prev.timestamp
)));
}
out.reserve(gap_count as usize);
let mut t = start;
while t < next_bucket {
// `prev.close` is finite (it came from a validated bar), so this
// flat candle always passes `Candle::new`'s checks.
out.push(Candle::new(
prev.close, prev.close, prev.close, prev.close, 0.0, start,
prev.close, prev.close, prev.close, prev.close, 0.0, t,
)?);
start = start.checked_add(step).ok_or_else(|| {
t = t.checked_add(step).ok_or_else(|| {
Error::Malformed("timestamp overflow while gap-filling".to_string())
})?;
}
@@ -477,6 +518,37 @@ mod tests {
assert_eq!(closed[0].timestamp, 0);
}
#[test]
fn gap_fill_rejects_runaway_timestamp_jump() {
// An adversarial clock-glitch tick years in the future must surface
// as an Error::Malformed rather than allocating millions of flat
// candles and OOMing. Found by the `tick_aggregator` fuzz target.
let mut agg = TickAggregator::new(Timeframe::new(60).unwrap()).with_gap_fill(true);
agg.push(t(10.0, 0)).unwrap();
// Two-billion-second jump = ~63 years of minute bars = ~33 million
// candles, well above the 1_000_000 cap.
let err = agg.push(t(20.0, 2_000_000_000)).unwrap_err();
let msg = err.to_string();
assert!(
msg.contains("gap-fill") && msg.contains("cap"),
"expected a malformed-gap error, got: {msg}"
);
}
#[test]
fn gap_fill_at_the_cap_succeeds() {
// Exactly one million minute-buckets between the two ticks (one real
// bar + one million flat fillers + the third tick's open bar) — the
// limit is inclusive, so this must succeed.
let mut agg = TickAggregator::new(Timeframe::new(60).unwrap()).with_gap_fill(true);
agg.push(t(10.0, 0)).unwrap();
// bucket 0 closes; jump straight to bucket 60_000_060 (1_000_001 buckets
// away). fill_between emits 1_000_000 flat candles between them, then
// the new tick opens its own bucket. Output: 1 real bar + 1_000_000 fillers.
let out = agg.push(t(20.0, 60_000_060)).unwrap();
assert_eq!(out.len(), 1 + MAX_GAP_FILL_CANDLES as usize);
}
#[test]
fn gap_fill_emits_flat_candles_for_skipped_buckets() {
let mut agg = TickAggregator::new(Timeframe::new(60).unwrap()).with_gap_fill(true);
+6 -5
View File
@@ -181,11 +181,12 @@ impl BinanceKlineStream {
streams.join("/")
);
let url = url::Url::parse(&url).map_err(|e| Error::Malformed(e.to_string()))?;
let ws_config = WebSocketConfig {
max_message_size: Some(MAX_MESSAGE_SIZE),
max_frame_size: Some(MAX_FRAME_SIZE),
..WebSocketConfig::default()
};
// tokio-tungstenite 0.29's WebSocketConfig is #[non_exhaustive],
// so the only way to set fields is via the builder-style methods on
// a fresh `default()` value.
let ws_config = WebSocketConfig::default()
.max_message_size(Some(MAX_MESSAGE_SIZE))
.max_frame_size(Some(MAX_FRAME_SIZE));
let (socket, _) =
tokio_tungstenite::connect_async_with_config(url.as_str(), Some(ws_config), false)
.await?;
+2 -1
View File
@@ -16,7 +16,8 @@
//! cargo run -p wickra-examples --bin fetch_btcusdt
//! ```
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use std::hint::black_box;
use wickra::{
Atr, BatchExt, BollingerBands, Candle, Ema, Indicator, MacdIndicator, Obv, Rsi, Sma,
Stochastic, Wma,
+7 -7
View File
@@ -26,12 +26,12 @@ open an issue on GitHub to discuss a separate license.
| Registry | Package | Version |
|-----------|----------------|---------|
| crates.io | `wickra` | 0.2.0 |
| crates.io | `wickra-core` | 0.2.0 |
| crates.io | `wickra-data` | 0.2.0 |
| PyPI | `wickra` | 0.2.0 |
| npm | `wickra` | 0.2.0 |
| npm | `wickra-wasm` | 0.2.0 |
| crates.io | `wickra` | 0.2.1 |
| crates.io | `wickra-core` | 0.2.1 |
| crates.io | `wickra-data` | 0.2.1 |
| PyPI | `wickra` | 0.2.1 |
| npm | `wickra` | 0.2.1 |
| npm | `wickra-wasm` | 0.2.1 |
Release notes and tagged builds:
<https://github.com/kingchenc/wickra/releases>.
@@ -47,7 +47,7 @@ Release notes and tagged builds:
- [Quickstart: Node](Quickstart-Node.md) — `npm install wickra`, basic
`SMA` and `MACD` calls, and the install surface. Windows x64 was
previously blocked by an npm spam filter on `wickra-win32-x64-msvc`;
that was resolved with npm Support, and 0.2.0 is the first release in
that was resolved with npm Support, and 0.2.1 is the first release in
which `npm install wickra` works end-to-end on Windows.
- [Quickstart: WASM](Quickstart-WASM.md) — `npm install wickra-wasm`,
building with `wasm-pack`, and running indicators client-side in a
+3 -3
View File
@@ -10,12 +10,12 @@ slow JS reimplementation.
npm install wickra
```
> **Windows install (0.2.0+).** Earlier patch releases were blocked on
> **Windows install (0.2.1+).** Earlier patch releases were blocked on
> Windows x64 because the platform-specific sub-package
> `wickra-win32-x64-msvc` was held back by npm's automated spam filter, so
> `require('wickra')` threw `Error: Cannot find module
> 'wickra-win32-x64-msvc'` after a successful `npm install`. npm Support
> released the name on 2026-05-22; 0.2.0 is the first version in which
> released the name on 2026-05-22; 0.2.1 is the first version in which
> Windows x64 installs cleanly end-to-end (version numbers `0.1.1``0.1.4`
> of that sub-package remain burned and cannot be republished — see the
> npm registry page for `wickra-win32-x64-msvc`). Linux x64, Linux arm64
@@ -135,7 +135,7 @@ The complete TypeScript definitions live at
| `batch(...)` | Single-output: flat `Array<number>` with `NaN` warmup.<br>Multi-output: flat interleaved `Array<number>`. |
| `reset()` | Returns to a freshly-constructed state. |
| `isReady()` | `true` once the first value has been emitted. |
| `warmupPeriod()` | Present on every indicator class (single- and multi-output, scalar- and candle-input) since `0.2.0`. |
| `warmupPeriod()` | Present on every indicator class (single- and multi-output, scalar- and candle-input) since `0.2.1`. |
A complete reference run lives in `bindings/node/__tests__/smoke.test.js`:
+1 -1
View File
@@ -15,7 +15,7 @@ build. The `wickra` crate is a thin façade that re-exports everything from
`wickra-core`; you can also depend on `wickra-core` directly if you want to
skip the façade.
The published crate is at version `0.2.0` on
The published crate is at version `0.2.1` on
[crates.io](https://crates.io/crates/wickra).
## The `Indicator` trait in 30 seconds
+1 -1
View File
@@ -111,7 +111,7 @@ console.log(last);
their fields per row (e.g. MACD: `[macd0, signal0, hist0, macd1, ...]`). The
exact layout is documented in the generated `pkg/wickra_wasm.d.ts`.
> Since `wickra-wasm@0.2.0`, every candle-input indicator (ATR, ADX,
> Since `wickra-wasm@0.2.1`, every candle-input indicator (ATR, ADX,
> WilliamsR, CCI, MFI, PSAR, Keltner, Donchian, VWAP, RollingVWAP,
> AwesomeOscillator, Aroon, Stochastic, OBV, and the rest of the
> volume / volatility / trailing-stop / price-statistics families) exposes
+1 -1
View File
@@ -160,7 +160,7 @@ const sma = new wickra.SMA(20);
console.log(sma.warmupPeriod()); // -> 20
```
(Since `wickra@0.2.0`, `warmupPeriod()` is exposed on every Node and
(Since `wickra@0.2.1`, `warmupPeriod()` is exposed on every Node and
WASM class — single- and multi-output — alongside `update()`, `reset()`
and `isReady()`. Consult `bindings/node/index.d.ts` for the
authoritative TypeScript surface.)