Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 96cd9b3f02 | |||
| 98bb57c868 | |||
| be05eb26d5 | |||
| af59e6eec0 | |||
| 96ae63799d | |||
| d5f5e14dda | |||
| 7e5d394f2a | |||
| ff268500ef | |||
| 929fc17127 | |||
| 41d5a7dd25 | |||
| e595ea8bfe | |||
| a5fe2e71c4 | |||
| 75eefbbd08 | |||
| 2e07c07a40 | |||
| ec937b8281 | |||
| 1f5eb90b0d | |||
| bbda70f75b | |||
| 2d2d5970b4 | |||
| 8228be7069 | |||
| 677ea37402 | |||
| 2ae76bb90e | |||
| b92ad32037 | |||
| 3a709d9a66 | |||
| baf4d0ff47 | |||
| d362ae26a3 | |||
| cb6da4d737 | |||
| 8a103ef920 | |||
| fd9f4c8bc6 | |||
| 59acefa1ea | |||
| 5f0677d62d | |||
| ce792cf8b8 | |||
| 120b6ac265 | |||
| 4f708d410d | |||
| de1112ea91 | |||
| 82d7479011 |
@@ -32,3 +32,10 @@ bindings/r/src/Makevars.in text eol=lf
|
||||
bindings/r/src/Makevars.win text eol=lf
|
||||
bindings/r/src/wickra.c text eol=lf
|
||||
|
||||
# Golden fixtures are replayed byte-for-byte by every binding's parity test. Pin
|
||||
# them to LF so a Windows `core.autocrlf=true` checkout doesn't rewrite them as
|
||||
# CRLF — which silently broke the Node reader (`Number('inf\r')` is NaN, and a
|
||||
# blank "no-bar" row gained a stray `\r`), failing only on Windows runners. The
|
||||
# tolerant readers (Python `splitlines`/`float`) hid the same hazard.
|
||||
testdata/golden/** text eol=lf
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
name: Setup Rust toolchain (with retry)
|
||||
description: >
|
||||
Install a Rust toolchain via dtolnay/rust-toolchain, retrying once after a
|
||||
short pause on the transient rustup CDN failure (a corrupt channel manifest /
|
||||
checksum mismatch on `channel-rust-<channel>.toml`) that rustup itself does
|
||||
not retry — the same hardening already applied to setup-node / setup-go.
|
||||
|
||||
inputs:
|
||||
toolchain:
|
||||
description: Rust toolchain (channel or version) to install.
|
||||
required: false
|
||||
default: stable
|
||||
components:
|
||||
description: Comma-separated list of rustup components (e.g. "rustfmt, clippy").
|
||||
required: false
|
||||
default: ""
|
||||
targets:
|
||||
description: Comma-separated list of rustup targets (e.g. "wasm32-unknown-unknown").
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- id: first
|
||||
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
continue-on-error: true
|
||||
with:
|
||||
toolchain: ${{ inputs.toolchain }}
|
||||
components: ${{ inputs.components }}
|
||||
targets: ${{ inputs.targets }}
|
||||
|
||||
- name: Wait before retrying the toolchain install
|
||||
if: steps.first.outcome == 'failure'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::warning::rustup toolchain install failed (likely a CDN flake / corrupt channel manifest); retrying in 30s"
|
||||
sleep 30
|
||||
|
||||
- name: Retry the Rust toolchain install
|
||||
if: steps.first.outcome == 'failure'
|
||||
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
with:
|
||||
toolchain: ${{ inputs.toolchain }}
|
||||
components: ${{ inputs.components }}
|
||||
targets: ${{ inputs.targets }}
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
- uses: ./.github/actions/setup-rust
|
||||
|
||||
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
||||
@@ -126,7 +126,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
- uses: ./.github/actions/setup-rust
|
||||
|
||||
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
||||
|
||||
+161
-44
@@ -34,7 +34,7 @@ jobs:
|
||||
rust:
|
||||
name: Rust ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
||||
timeout-minutes: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
uses: ./.github/actions/setup-rust
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
|
||||
@@ -105,7 +105,7 @@ jobs:
|
||||
examples-smoke:
|
||||
name: Examples (syntax smoke)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
||||
timeout-minutes: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
@@ -195,21 +195,21 @@ jobs:
|
||||
clippy-bindings:
|
||||
name: Clippy bindings
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
||||
timeout-minutes: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
uses: ./.github/actions/setup-rust
|
||||
with:
|
||||
components: clippy
|
||||
|
||||
- name: Set up Python
|
||||
id: setup_python
|
||||
continue-on-error: true
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
@@ -222,7 +222,7 @@ jobs:
|
||||
|
||||
- name: Set up Python (retry)
|
||||
if: steps.setup_python.outcome == 'failure'
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
@@ -279,7 +279,7 @@ jobs:
|
||||
msrv:
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
||||
timeout-minutes: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -296,7 +296,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Rust ${{ matrix.toolchain }}
|
||||
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
uses: ./.github/actions/setup-rust
|
||||
with:
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
|
||||
@@ -328,14 +328,14 @@ jobs:
|
||||
coverage:
|
||||
name: Coverage
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
||||
timeout-minutes: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
uses: ./.github/actions/setup-rust
|
||||
with:
|
||||
components: llvm-tools-preview
|
||||
|
||||
@@ -345,7 +345,7 @@ jobs:
|
||||
timeout-minutes: 6
|
||||
|
||||
- name: Install cargo-llvm-cov
|
||||
uses: taiki-e/install-action@59012be0884e296ca2da49b530610e72c49039ad # v2.81.6
|
||||
uses: taiki-e/install-action@7a79fe8c3a13344501c80d99cae481c1c9085912 # v2.81.10
|
||||
timeout-minutes: 10 # fail fast on a stuck download instead of hanging the job
|
||||
with:
|
||||
tool: cargo-llvm-cov
|
||||
@@ -358,7 +358,7 @@ jobs:
|
||||
--lcov --output-path lcov.info
|
||||
|
||||
- name: Upload to Codecov
|
||||
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
with:
|
||||
files: lcov.info
|
||||
fail_ci_if_error: false
|
||||
@@ -370,7 +370,7 @@ jobs:
|
||||
supply-chain:
|
||||
name: Supply-chain (cargo-deny)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
||||
timeout-minutes: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
@@ -389,14 +389,14 @@ jobs:
|
||||
fuzz-smoke:
|
||||
name: Fuzz (smoke)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
||||
timeout-minutes: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install nightly Rust
|
||||
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
uses: ./.github/actions/setup-rust
|
||||
with:
|
||||
toolchain: nightly
|
||||
|
||||
@@ -415,7 +415,7 @@ jobs:
|
||||
# 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@59012be0884e296ca2da49b530610e72c49039ad # v2.81.6
|
||||
uses: taiki-e/install-action@7a79fe8c3a13344501c80d99cae481c1c9085912 # v2.81.10
|
||||
timeout-minutes: 10 # fail fast on a stuck download instead of hanging the job
|
||||
with:
|
||||
tool: cargo-fuzz
|
||||
@@ -443,7 +443,7 @@ jobs:
|
||||
python:
|
||||
name: Python ${{ matrix.python-version }} on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
||||
timeout-minutes: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -455,7 +455,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
uses: ./.github/actions/setup-rust
|
||||
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||
@@ -525,14 +525,14 @@ jobs:
|
||||
wasm:
|
||||
name: WASM build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
||||
timeout-minutes: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Rust toolchain (with wasm target)
|
||||
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
uses: ./.github/actions/setup-rust
|
||||
with:
|
||||
targets: wasm32-unknown-unknown
|
||||
|
||||
@@ -549,7 +549,7 @@ jobs:
|
||||
# same taiki-e prebuilt-binary installer we already use for
|
||||
# cargo-llvm-cov and cargo-fuzz; it tracks the latest wasm-pack
|
||||
# release, which has `--features` as a top-level flag (since 0.12).
|
||||
uses: taiki-e/install-action@59012be0884e296ca2da49b530610e72c49039ad # v2.81.6
|
||||
uses: taiki-e/install-action@7a79fe8c3a13344501c80d99cae481c1c9085912 # v2.81.10
|
||||
timeout-minutes: 10 # fail fast on a stuck download instead of hanging the job
|
||||
with:
|
||||
tool: wasm-pack
|
||||
@@ -566,10 +566,16 @@ jobs:
|
||||
test -f bindings/wasm/pkg/wickra_wasm_bg.wasm
|
||||
test -f bindings/wasm/pkg/wickra_wasm.d.ts
|
||||
|
||||
- name: Build WASM package (nodejs target) for the golden suite
|
||||
run: wasm-pack build bindings/wasm --target nodejs --release --out-dir pkg
|
||||
|
||||
- name: Golden parity — all 514 indicators vs the Rust reference
|
||||
run: node --test bindings/wasm/tests/golden.test.js
|
||||
|
||||
node:
|
||||
name: Node ${{ matrix.node-version }} on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
||||
timeout-minutes: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -581,7 +587,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
uses: ./.github/actions/setup-rust
|
||||
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||
@@ -616,9 +622,17 @@ jobs:
|
||||
cache: npm
|
||||
cache-dependency-path: bindings/node/package-lock.json
|
||||
|
||||
# npm's own fetch-retry (npm_config_fetch_retries) rides out per-request
|
||||
# blips; wrap the whole `npm ci` once more so a longer registry hiccup
|
||||
# retries the install instead of failing the job.
|
||||
- name: Install Node dependencies
|
||||
working-directory: bindings/node
|
||||
run: npm ci
|
||||
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
retry_wait_seconds: 20
|
||||
command: cd bindings/node && npm ci
|
||||
shell: bash
|
||||
|
||||
- name: Build native module
|
||||
working-directory: bindings/node
|
||||
@@ -643,7 +657,7 @@ jobs:
|
||||
c-abi:
|
||||
name: C ABI on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
||||
timeout-minutes: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -654,7 +668,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
uses: ./.github/actions/setup-rust
|
||||
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||
@@ -662,7 +676,7 @@ jobs:
|
||||
timeout-minutes: 6
|
||||
|
||||
- name: Install cbindgen
|
||||
uses: taiki-e/install-action@59012be0884e296ca2da49b530610e72c49039ad # v2.81.6
|
||||
uses: taiki-e/install-action@7a79fe8c3a13344501c80d99cae481c1c9085912 # v2.81.10
|
||||
timeout-minutes: 10 # fail fast on a stuck download instead of hanging the job
|
||||
with:
|
||||
tool: cbindgen
|
||||
@@ -699,7 +713,7 @@ jobs:
|
||||
csharp:
|
||||
name: C# on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
||||
timeout-minutes: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -710,13 +724,26 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
uses: ./.github/actions/setup-rust
|
||||
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
||||
timeout-minutes: 6
|
||||
|
||||
# Cache the restored NuGet packages so dotnet test/build resolve from the
|
||||
# local store instead of hitting nuget.org every run. No packages.lock.json
|
||||
# exists, so key on the project files; never block the job on a slow restore.
|
||||
- name: Cache NuGet packages
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
continue-on-error: true
|
||||
timeout-minutes: 6
|
||||
with:
|
||||
path: ~/.nuget/packages
|
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nuget-
|
||||
|
||||
# The binding links against the C ABI hub at runtime; build it first so the
|
||||
# DllImportResolver finds target/release/wickra.{dll,so,dylib}. .NET 8 SDK is
|
||||
# preinstalled on the GitHub runners, so no setup-dotnet step is needed.
|
||||
@@ -726,8 +753,17 @@ jobs:
|
||||
- name: .NET info
|
||||
run: dotnet --info
|
||||
|
||||
# dotnet test restores from nuget.org first; retry so a transient restore
|
||||
# blip retries instead of failing the job (the cached packages above make
|
||||
# the retry cheap).
|
||||
- name: Test the C# binding
|
||||
run: dotnet test bindings/csharp/Wickra.Tests/Wickra.Tests.csproj -c Release
|
||||
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
|
||||
with:
|
||||
timeout_minutes: 15
|
||||
max_attempts: 2
|
||||
retry_wait_seconds: 20
|
||||
command: dotnet test bindings/csharp/Wickra.Tests/Wickra.Tests.csproj -c Release
|
||||
shell: bash
|
||||
|
||||
- name: Build the C# examples
|
||||
shell: bash
|
||||
@@ -750,7 +786,7 @@ jobs:
|
||||
go:
|
||||
name: Go on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
||||
timeout-minutes: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -786,7 +822,7 @@ jobs:
|
||||
cache: false
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
uses: ./.github/actions/setup-rust
|
||||
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||
@@ -873,7 +909,7 @@ jobs:
|
||||
r:
|
||||
name: R on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
||||
timeout-minutes: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -887,7 +923,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
uses: ./.github/actions/setup-rust
|
||||
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||
@@ -906,10 +942,17 @@ jobs:
|
||||
r-version: "release"
|
||||
use-public-rspm: true
|
||||
|
||||
# Use the repos configured by setup-r (use-public-rspm) so Linux installs
|
||||
# binary packages — building testthat's deps from source is slow and flaky.
|
||||
- name: Install test dependency
|
||||
run: Rscript -e 'install.packages("testthat")'
|
||||
# Install the R dependencies via setup-r-dependencies: it restores a cached
|
||||
# package library (actions/cache) and pulls RSPM *binaries* instead of
|
||||
# compiling testthat / knitr and their deps from source — the slow, flaky
|
||||
# path that previously blew past the job timeout on the ubuntu runner.
|
||||
- name: Install R dependencies (cached binaries)
|
||||
uses: r-lib/actions/setup-r-dependencies@a51a8012b0aab7c32ef9d19bf54da93f3254335e # v2
|
||||
with:
|
||||
working-directory: bindings/r
|
||||
extra-packages: |
|
||||
any::testthat
|
||||
any::knitr
|
||||
|
||||
- name: Install and test the R binding
|
||||
shell: bash
|
||||
@@ -926,14 +969,39 @@ jobs:
|
||||
R CMD INSTALL bindings/r
|
||||
Rscript -e 'library(testthat); library(wickra); test_dir("bindings/r/tests/testthat", stop_on_failure = TRUE)'
|
||||
|
||||
# Full R CMD check, gated on the documentation problems r-universe surfaces
|
||||
# (undocumented exported objects, codoc mismatches) that R CMD INSTALL above
|
||||
# does not catch — exactly what shipped stale to r-universe with the 0.9.3
|
||||
# data layer. Ubuntu-only; these checks are platform-independent. Vignettes
|
||||
# are skipped here (building them needs pandoc and the vignette is exercised
|
||||
# in the next step), so the two --no-build-vignettes warnings are ignored.
|
||||
- name: R CMD check (documentation & consistency, like r-universe)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
shell: bash
|
||||
run: |
|
||||
export WICKRA_INCLUDE_DIR="${WICKRA_INCLUDE_DIR//\\//}"
|
||||
export WICKRA_LIB_DIR="${WICKRA_LIB_DIR//\\//}"
|
||||
R CMD build bindings/r --no-build-vignettes --no-manual
|
||||
R CMD check wickra_*.tar.gz --no-manual --no-vignettes --no-tests || true
|
||||
log=$(find . -maxdepth 2 -name 00check.log | head -1)
|
||||
echo "::group::00check.log"; cat "$log"; echo "::endgroup::"
|
||||
problems=$(grep -E '\.\.\. (WARNING|ERROR)' "$log" \
|
||||
| grep -vE "checking (files in .vignettes.|package vignettes)" || true)
|
||||
if [ -n "$problems" ]; then
|
||||
echo "::error::R CMD check found problems (run roxygen2::roxygenise() in bindings/r if the docs are stale):"
|
||||
echo "$problems"
|
||||
exit 1
|
||||
fi
|
||||
echo "R CMD check: documentation and consistency clean."
|
||||
|
||||
- name: Build the vignette code
|
||||
shell: bash
|
||||
# The getting-started vignette runs at R CMD check time on r-universe /
|
||||
# CRAN (with pandoc); this job only INSTALLs, so execute the vignette's R
|
||||
# chunks here (knit, no pandoc needed) to catch a broken example before it
|
||||
# reaches the published build.
|
||||
# knitr is installed by the cached setup-r-dependencies step above.
|
||||
run: |
|
||||
Rscript -e 'install.packages("knitr", repos = Sys.getenv("RSPM", unset = "https://cloud.r-project.org"))'
|
||||
Rscript -e 'knitr::knit("bindings/r/vignettes/getting-started.Rmd", output = tempfile(fileext = ".md"), quiet = TRUE); cat("vignette code OK\n")'
|
||||
|
||||
- name: Run the offline R examples
|
||||
@@ -955,7 +1023,7 @@ jobs:
|
||||
java:
|
||||
name: Java on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
||||
timeout-minutes: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -966,7 +1034,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
uses: ./.github/actions/setup-rust
|
||||
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||
@@ -1010,8 +1078,16 @@ jobs:
|
||||
|
||||
# `install` runs the archetype test suite (the real FFI boundary check) and
|
||||
# installs the binding to the local repo so the examples can resolve it.
|
||||
# Maven resolves plugins/deps from the network on a cache miss; retry so a
|
||||
# transient Central blip retries instead of failing the job.
|
||||
- name: Test and install the Java binding
|
||||
run: mvn -B -f bindings/java install
|
||||
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
|
||||
with:
|
||||
timeout_minutes: 20
|
||||
max_attempts: 2
|
||||
retry_wait_seconds: 20
|
||||
command: mvn -B -f bindings/java install
|
||||
shell: bash
|
||||
|
||||
- name: Build the Java examples
|
||||
run: mvn -B -f examples/java compile
|
||||
@@ -1025,6 +1101,47 @@ jobs:
|
||||
mvn -B -q -f examples/java exec:exec -Dexec.mainClass="org.wickra.examples.$cls"
|
||||
done
|
||||
|
||||
# Build a Python wheel inside both the manylinux and the musllinux container,
|
||||
# mirroring the Linux wheel build in release.yml. The 3-OS Python jobs build
|
||||
# natively on the runner, which already ships system OpenSSL, so they cannot
|
||||
# catch a build-time gap that only exists inside the slim release containers —
|
||||
# exactly what broke the 0.9.3 Linux wheels (the live-binance data layer links
|
||||
# native-tls -> openssl-sys, and the containers provide no OpenSSL: manylinux
|
||||
# lacks the headers, musllinux cross-compiles against a musl sysroot without
|
||||
# OpenSSL at all). The wheels are built with the `vendored-tls` feature, which
|
||||
# statically compiles OpenSSL from source. This job exercises both container
|
||||
# builds on every PR, so the same class of breakage now fails CI, not release.
|
||||
python-wheel-container-smoke:
|
||||
name: Python wheel (${{ matrix.manylinux }} smoke)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30 # backstop: vendored OpenSSL adds a from-source compile
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
manylinux: [auto, musllinux_1_2]
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Sync root README into bindings/python so the build matches release
|
||||
run: cp README.md bindings/python/README.md
|
||||
|
||||
- uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
|
||||
with:
|
||||
working-directory: bindings/python
|
||||
target: x86_64
|
||||
# Keep in sync with release.yml: vendored OpenSSL for the Linux wheels.
|
||||
args: --release --out dist --features vendored-tls
|
||||
manylinux: ${{ matrix.manylinux }}
|
||||
# Building OpenSSL from source needs Perl modules (IPC::Cmd,
|
||||
# Time::Piece, ...) the minimal manylinux (CentOS 7) image lacks.
|
||||
# perl-core pulls the full distribution; the explicit names document
|
||||
# the ones OpenSSL's Configure has required. The musllinux cross image
|
||||
# ships a complete Perl and has no yum, so this is a no-op there.
|
||||
before-script-linux: |
|
||||
if command -v yum >/dev/null 2>&1; then yum install -y perl-core perl-IPC-Cmd perl-Data-Dumper perl-Time-Piece; fi
|
||||
|
||||
# The cross-library benchmark has moved to a dedicated scheduled workflow
|
||||
# (.github/workflows/bench.yml) — see audit finding R10. It runs nightly
|
||||
# at 03:00 UTC and on-demand via `workflow_dispatch`, and is no longer on
|
||||
|
||||
@@ -36,6 +36,7 @@ jobs:
|
||||
# --------------------------------------------------------------------------
|
||||
cargo-publish:
|
||||
name: Publish to crates.io
|
||||
timeout-minutes: 45 # backstop only: release build/publish jobs compile from source (vendored OpenSSL) and must not be killed mid-build; far above the ~10 min real runtime
|
||||
runs-on: ubuntu-latest
|
||||
# The publish jobs run with long-lived registry tokens. Binding them to a
|
||||
# protected GitHub environment lets the org require a reviewer to approve
|
||||
@@ -48,7 +49,7 @@ jobs:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
- uses: ./.github/actions/setup-rust
|
||||
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
||||
timeout-minutes: 6
|
||||
@@ -110,7 +111,7 @@ jobs:
|
||||
# consumers can audit the published dependency tree without
|
||||
# re-resolving Cargo.lock.
|
||||
- name: Install cargo-cyclonedx
|
||||
uses: taiki-e/install-action@59012be0884e296ca2da49b530610e72c49039ad # v2.81.6
|
||||
uses: taiki-e/install-action@7a79fe8c3a13344501c80d99cae481c1c9085912 # v2.81.10
|
||||
timeout-minutes: 10 # fail fast on a stuck download instead of hanging the job
|
||||
with:
|
||||
tool: cargo-cyclonedx
|
||||
@@ -139,6 +140,7 @@ jobs:
|
||||
# --------------------------------------------------------------------------
|
||||
python-wheels:
|
||||
name: Build wheels (${{ matrix.target }}/${{ matrix.manylinux }} on ${{ matrix.os }})
|
||||
timeout-minutes: 45 # backstop only: release build/publish jobs compile from source (vendored OpenSSL) and must not be killed mid-build; far above the ~10 min real runtime
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -184,8 +186,22 @@ jobs:
|
||||
with:
|
||||
working-directory: bindings/python
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --strip --out dist
|
||||
# The live-binance data layer links native-tls -> openssl-sys, which
|
||||
# needs OpenSSL at build time. The manylinux containers lack the headers
|
||||
# and the musllinux build cross-compiles against a musl sysroot with no
|
||||
# OpenSSL at all, so build the Linux wheels with vendored OpenSSL
|
||||
# (compiled from source, linked statically). No-op on the native
|
||||
# macOS/Windows runners, where native-tls never pulls openssl-sys. The
|
||||
# CI `python-wheel-container-smoke` job exercises both containers on PRs.
|
||||
args: --release --strip --out dist --features vendored-tls
|
||||
manylinux: ${{ matrix.manylinux }}
|
||||
# Building OpenSSL from source needs Perl modules (IPC::Cmd,
|
||||
# Time::Piece, ...) the minimal manylinux (CentOS 7) image lacks.
|
||||
# perl-core pulls the full distribution; the explicit names document
|
||||
# the ones OpenSSL's Configure has required. The musllinux cross image
|
||||
# ships a complete Perl and has no yum, so this is a no-op there.
|
||||
before-script-linux: |
|
||||
if command -v yum >/dev/null 2>&1; then yum install -y perl-core perl-IPC-Cmd perl-Data-Dumper perl-Time-Piece; fi
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
# Include manylinux in the name so the glibc and musl x86_64/aarch64
|
||||
@@ -195,6 +211,7 @@ jobs:
|
||||
|
||||
python-sdist:
|
||||
name: Build Python sdist
|
||||
timeout-minutes: 45 # backstop only: release build/publish jobs compile from source (vendored OpenSSL) and must not be killed mid-build; far above the ~10 min real runtime
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
@@ -214,6 +231,7 @@ jobs:
|
||||
|
||||
python-publish:
|
||||
name: Publish to PyPI
|
||||
timeout-minutes: 45 # backstop only: release build/publish jobs compile from source (vendored OpenSSL) and must not be killed mid-build; far above the ~10 min real runtime
|
||||
needs: [python-wheels, python-sdist]
|
||||
runs-on: ubuntu-latest
|
||||
environment: release
|
||||
@@ -237,6 +255,7 @@ jobs:
|
||||
# --------------------------------------------------------------------------
|
||||
node-build:
|
||||
name: Node build (${{ matrix.target }})
|
||||
timeout-minutes: 45 # backstop only: release build/publish jobs compile from source (vendored OpenSSL) and must not be killed mid-build; far above the ~10 min real runtime
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -273,7 +292,7 @@ jobs:
|
||||
with:
|
||||
node-version: "22"
|
||||
|
||||
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
- uses: ./.github/actions/setup-rust
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
@@ -282,8 +301,13 @@ jobs:
|
||||
timeout-minutes: 6
|
||||
|
||||
- name: Install Node deps
|
||||
working-directory: bindings/node
|
||||
run: npm ci
|
||||
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
retry_wait_seconds: 20
|
||||
command: cd bindings/node && npm ci
|
||||
shell: bash
|
||||
|
||||
- name: Build native module
|
||||
working-directory: bindings/node
|
||||
@@ -298,6 +322,7 @@ jobs:
|
||||
|
||||
node-publish:
|
||||
name: Publish to npm
|
||||
timeout-minutes: 45 # backstop only: release build/publish jobs compile from source (vendored OpenSSL) and must not be killed mid-build; far above the ~10 min real runtime
|
||||
needs: node-build
|
||||
runs-on: ubuntu-latest
|
||||
environment: release
|
||||
@@ -337,8 +362,13 @@ jobs:
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
- name: Install Node deps
|
||||
working-directory: bindings/node
|
||||
run: npm ci
|
||||
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
retry_wait_seconds: 20
|
||||
command: cd bindings/node && npm ci
|
||||
shell: bash
|
||||
|
||||
- name: Download all platform binaries
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
@@ -470,6 +500,7 @@ jobs:
|
||||
# which requires the `id-token: write` permission set at the job level.
|
||||
wasm-publish:
|
||||
name: Publish wickra-wasm to npm
|
||||
timeout-minutes: 45 # backstop only: release build/publish jobs compile from source (vendored OpenSSL) and must not be killed mid-build; far above the ~10 min real runtime
|
||||
runs-on: ubuntu-latest
|
||||
environment: release
|
||||
# `id-token: write` lets npm publish embed a Sigstore provenance
|
||||
@@ -505,14 +536,18 @@ jobs:
|
||||
node-version: "22"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
- uses: ./.github/actions/setup-rust
|
||||
with:
|
||||
targets: wasm32-unknown-unknown
|
||||
|
||||
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
||||
timeout-minutes: 6
|
||||
|
||||
- name: Install wasm-pack (latest, via prebuilt binary)
|
||||
# See the matching note in ci.yml: jetli's default installs an old
|
||||
# 0.10.x wasm-pack whose build subcommand rejects --features.
|
||||
uses: taiki-e/install-action@59012be0884e296ca2da49b530610e72c49039ad # v2.81.6
|
||||
uses: taiki-e/install-action@7a79fe8c3a13344501c80d99cae481c1c9085912 # v2.81.10
|
||||
timeout-minutes: 10 # fail fast on a stuck download instead of hanging the job
|
||||
with:
|
||||
tool: wasm-pack
|
||||
@@ -576,6 +611,7 @@ jobs:
|
||||
# --------------------------------------------------------------------------
|
||||
c-abi-build:
|
||||
name: C ABI library (${{ matrix.target }})
|
||||
timeout-minutes: 45 # backstop only: release build/publish jobs compile from source (vendored OpenSSL) and must not be killed mid-build; far above the ~10 min real runtime
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -592,7 +628,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
- uses: ./.github/actions/setup-rust
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
@@ -631,6 +667,7 @@ jobs:
|
||||
# workflow release.yml) exchanges the GitHub OIDC token for a short-lived key.
|
||||
csharp-publish:
|
||||
name: Publish to NuGet
|
||||
timeout-minutes: 45 # backstop only: release build/publish jobs compile from source (vendored OpenSSL) and must not be killed mid-build; far above the ~10 min real runtime
|
||||
needs: c-abi-build
|
||||
runs-on: ubuntu-latest
|
||||
environment: release
|
||||
@@ -675,11 +712,18 @@ jobs:
|
||||
echo "staged ${RID[$target]}:"; ls -l "$dest"
|
||||
done
|
||||
|
||||
# dotnet pack restores from nuget.org first; retry so a transient restore
|
||||
# blip retries instead of failing the release.
|
||||
- name: Pack
|
||||
shell: bash
|
||||
run: |
|
||||
version="${GITHUB_REF_NAME#v}"
|
||||
dotnet pack bindings/csharp/Wickra/Wickra.csproj -c Release -p:Version="$version" -o nupkg
|
||||
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
|
||||
with:
|
||||
timeout_minutes: 15
|
||||
max_attempts: 2
|
||||
retry_wait_seconds: 20
|
||||
shell: bash
|
||||
command: |
|
||||
version="${GITHUB_REF_NAME#v}"
|
||||
dotnet pack bindings/csharp/Wickra/Wickra.csproj -c Release -p:Version="$version" -o nupkg
|
||||
|
||||
# Exchange the GitHub OIDC token for a short-lived (~1h) NuGet API key.
|
||||
# 'user' is the nuget.org profile name (the package owner), not an email.
|
||||
@@ -714,6 +758,7 @@ jobs:
|
||||
# match the release tag (kept in sync by the version-bump checklist).
|
||||
java-publish:
|
||||
name: Publish to Maven Central
|
||||
timeout-minutes: 45 # backstop only: release build/publish jobs compile from source (vendored OpenSSL) and must not be killed mid-build; far above the ~10 min real runtime
|
||||
needs: c-abi-build
|
||||
runs-on: ubuntu-latest
|
||||
environment: release
|
||||
@@ -785,6 +830,7 @@ jobs:
|
||||
# derived artifact, so its bot commit is intentionally unsigned.
|
||||
go-mirror:
|
||||
name: Mirror the Go module to wickra-go
|
||||
timeout-minutes: 45 # backstop only: release build/publish jobs compile from source (vendored OpenSSL) and must not be killed mid-build; far above the ~10 min real runtime
|
||||
needs: c-abi-build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -868,6 +914,7 @@ jobs:
|
||||
|
||||
github-release:
|
||||
name: Attach assets to the draft GitHub Release
|
||||
timeout-minutes: 45 # backstop only: release build/publish jobs compile from source (vendored OpenSSL) and must not be killed mid-build; far above the ~10 min real runtime
|
||||
needs: [cargo-publish, python-publish, node-publish, wasm-publish, c-abi-build]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
@@ -993,6 +1040,7 @@ jobs:
|
||||
# --------------------------------------------------------------------------
|
||||
attestations:
|
||||
name: Attest build provenance
|
||||
timeout-minutes: 45 # backstop only: release build/publish jobs compile from source (vendored OpenSSL) and must not be killed mid-build; far above the ~10 min real runtime
|
||||
needs: [cargo-publish, python-wheels, python-sdist, github-release]
|
||||
runs-on: ubuntu-latest
|
||||
# Signed SLSA build-provenance attestations for the published crates and
|
||||
@@ -1076,6 +1124,7 @@ jobs:
|
||||
# --------------------------------------------------------------------------
|
||||
publish-release:
|
||||
name: Publish the GitHub Release
|
||||
timeout-minutes: 45 # backstop only: release build/publish jobs compile from source (vendored OpenSSL) and must not be killed mid-build; far above the ~10 min real runtime
|
||||
needs: [github-release, attestations]
|
||||
if: always() && needs.github-release.result == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
publish_results: true
|
||||
|
||||
- name: Upload SARIF artifact
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Audit repo-metadata.toml drift
|
||||
|
||||
+1
-1
@@ -227,7 +227,7 @@ A handful of indicators need care beyond naive accumulation:
|
||||
A typical full-stack call sequence for a Python live-trading example:
|
||||
|
||||
```
|
||||
[ Python: live_trading.py ]
|
||||
[ Python: live_binance.py ]
|
||||
│
|
||||
▼
|
||||
[ binance.AsyncClient WebSocket ] ──── wickra_data live feed ───┐
|
||||
|
||||
+59
-19
@@ -109,9 +109,11 @@ series through three indicators chosen by call-signature archetype — `SMA(20)`
|
||||
(1-in → 1-out), `ATR(14)` (multi-in → 1-out) and `MACD(12,26,9)` (1-in →
|
||||
multi-out). Two things fall out of the numbers:
|
||||
|
||||
- **Batch converges.** A `batch` call crosses the boundary once and the Rust core
|
||||
computes the whole series internally, so batch throughput is roughly the same
|
||||
in every binding — close to the core speed.
|
||||
- **Batch crosses once.** A `batch` call crosses the boundary a single time and the
|
||||
Rust core computes the whole series internally, so batch throughput stays high for
|
||||
most bindings — the exceptions are the ones that copy or box the result array on
|
||||
the way out (Node's JS `Array`, Python's stdlib `array.array` now that NumPy is
|
||||
optional).
|
||||
- **Streaming reveals the boundary.** A per-tick `update` crosses the boundary
|
||||
once per value, so streaming throughput is where the bindings differ: the raw C
|
||||
ABI and P/Invoke-style calls are nearly free, while managed or interpreted
|
||||
@@ -126,23 +128,23 @@ AMD Ryzen 9 9950X):
|
||||
|
||||
| Target | streaming (Mupd/s) | batch (Mupd/s) |
|
||||
|----------------------|-------------------:|---------------:|
|
||||
| Rust core (no FFI) | 391 | 500 |
|
||||
| C / C++ | 383 | 330 |
|
||||
| C# | 337 | 244 |
|
||||
| Python | 33 | 488 |
|
||||
| Java | 28 | 175 |
|
||||
| Go | 24 | 400 |
|
||||
| WASM | 19 | 167 |
|
||||
| Node.js | 17 | 10 |
|
||||
| R | 0.1 | 193 |
|
||||
| Rust core (no FFI) | 380 | 498 |
|
||||
| C / C++ | 365 | 358 |
|
||||
| C# | 348 | 259 |
|
||||
| Python | 31 | 46 |
|
||||
| Java | 38 | 173 |
|
||||
| Go | 23 | 394 |
|
||||
| WASM | 21 | 169 |
|
||||
| Node.js | 16 | 9 |
|
||||
| R | 0.1 | 279 |
|
||||
|
||||
Streaming spans more than three orders of magnitude — the raw C ABI (383) is
|
||||
nearly the FFI-free Rust ceiling (391), while R's per-call interpreter overhead
|
||||
(0.1) makes streaming ~2000× slower than its own batch. Batch converges near the
|
||||
core speed for the zero-copy bindings (numpy, slices, typed arrays); the two
|
||||
outliers are Node — whose napi `batch` boxes every element into a JS `Array` —
|
||||
and R. These are machine-dependent and reflect FFI overhead, not algorithm
|
||||
speed.
|
||||
Streaming spans more than three orders of magnitude — the raw C ABI (365) sits
|
||||
just under the FFI-free Rust ceiling (380), while R's per-call interpreter overhead
|
||||
(0.1) makes streaming ~2800× slower than its own batch. Batch stays high for the
|
||||
bindings that hand back a contiguous buffer (slices, typed arrays); the two low
|
||||
outliers are Node — whose napi `batch` boxes every element into a JS `Array` — and
|
||||
Python, which now copies into a stdlib `array.array` since NumPy became optional.
|
||||
These are machine-dependent and reflect FFI overhead, not algorithm speed.
|
||||
|
||||
These are throughput numbers, not competitive numbers — the "Wickra is fast"
|
||||
claim lives in sections 1 and 2 (Rust core + the Python/Rust cross-library runs).
|
||||
@@ -166,3 +168,41 @@ mvn -q -f bindings/java install -DskipTests \
|
||||
&& mvn -q -f bindings/java/benchmarks exec:exec -Dexec.mainClass=org.wickra.benchmarks.Throughput
|
||||
Rscript bindings/r/benchmarks/throughput.R # R (.Call)
|
||||
```
|
||||
|
||||
## 4. Data layer — native I/O throughput
|
||||
|
||||
Wickra ships its own data layer — a CSV candle reader, a tick-to-candle
|
||||
aggregator, and a timeframe resampler — so loading and reshaping market data
|
||||
needs **no third-party package** (`pandas`, `csv-parse`, manual bucketing,
|
||||
`pandas.resample`, …) in any of the ten languages. These run on the same Rust
|
||||
core as the indicators, so every binding reaches these speeds minus the FFI
|
||||
boundary characterised in section 3 (a `read` / `push` / `flush` call crosses the
|
||||
boundary once per batch, like `batch`, so bindings land close to the core).
|
||||
|
||||
Rust core, 50 000 real BTCUSDT one-minute candles
|
||||
(`examples/data/btcusdt-1m.csv`), median of 100 samples, on the reference machine
|
||||
(Windows 11, AMD Ryzen 9 9950X):
|
||||
|
||||
| Operation | Throughput | Per element |
|
||||
|------------------------------------|--------------------:|------------:|
|
||||
| CSV parse (`CandleReader`) | 3.0 M candles/s | 329 ns |
|
||||
| Tick aggregate → 1m (`TickAggregator`) | 44 M ticks/s | 22.6 ns |
|
||||
| Resample 1m → 5m (`Resampler`) | 234 M candles/s | 4.3 ns |
|
||||
|
||||
Reading and validating a 50 000-row CSV into typed candles takes ~16 ms;
|
||||
aggregating 50 000 ticks into one-minute bars ~1.1 ms; resampling 50 000
|
||||
one-minute candles to five-minute bars ~0.2 ms. CSV parsing is the floor because
|
||||
it does the most per row (UTF-8 scan, field split, six `f64` parses, finiteness
|
||||
checks); aggregation and resampling are pure arithmetic over already-typed
|
||||
candles.
|
||||
|
||||
The live and historical Binance feeds (`BinanceFeed`, `fetch_binance_klines`) are
|
||||
network-bound — their throughput is set by the exchange and the socket, not by
|
||||
Wickra — so they are not micro-benchmarked here; the relevant Wickra cost is the
|
||||
per-event parse, which is the same arithmetic measured above.
|
||||
|
||||
Run it with:
|
||||
|
||||
```bash
|
||||
cargo bench -p wickra --bench data_layer
|
||||
```
|
||||
|
||||
+237
-1
@@ -7,6 +7,236 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.9.7] - 2026-06-21
|
||||
|
||||
Maintenance release. The library API and every indicator are unchanged from
|
||||
`0.9.6`; this release carries an R package metadata fix and routine dependency
|
||||
and CI tooling updates.
|
||||
|
||||
### Fixed
|
||||
- **R package: credit `kingchenc` as the package author and maintainer.** The R
|
||||
`DESCRIPTION` was the only binding still listing "Wickra contributors" as the
|
||||
sole `aut`/`cre` — an outlier introduced when the binding was added. It now
|
||||
matches the Python, Node, and Rust core metadata, the package `.Rd` is
|
||||
regenerated, and the binding `LICENSE` copyright holder is aligned with the
|
||||
root `LICENSE-MIT`.
|
||||
|
||||
### Changed
|
||||
- **Dependency and CI housekeeping.** Bump the C# test dependencies
|
||||
(`Microsoft.NET.Test.Sdk`, `xunit`, `xunit.runner.visualstudio`), the GitHub
|
||||
Actions used by CI (`setup-python`, `upload-artifact`, `codecov-action`,
|
||||
`taiki-e/install-action`), and the Java benchmark's `org.wickra:wickra`
|
||||
dependency. No runtime code changes.
|
||||
|
||||
## [0.9.6] - 2026-06-18
|
||||
|
||||
Documentation release for the R binding. The library API and every indicator
|
||||
are unchanged from `0.9.5`; only the R package's help pages change.
|
||||
|
||||
### Fixed
|
||||
- **R package: document the data-layer exports and refresh the man pages.**
|
||||
r-universe's `R CMD check` reported two warnings against the `0.9.3` data layer
|
||||
it built for the first time in `0.9.5`: twelve undocumented exported objects
|
||||
(`BinanceFeed`, `CandleReader`, `Resampler`, `TickAggregator`,
|
||||
`fetch_binance_klines` and the `name` / `is_ready` / `warmup_period` / `push` /
|
||||
`read` generics) and a codoc mismatch on `AwesomeOscillatorHistogram` (its help
|
||||
page still listed `sma_period` after the argument was renamed to `lookback`).
|
||||
The roxygen sources existed but the `man/*.Rd` had never been regenerated; they
|
||||
are now complete, and a `push()` example that constructed a `TickAggregator`
|
||||
without its required `gap_fill` argument is fixed. CI now runs `R CMD check` so
|
||||
documentation drift fails the pull request instead of surfacing on r-universe.
|
||||
|
||||
|
||||
## [0.9.5] - 2026-06-17
|
||||
|
||||
Maintenance release. The library API and every indicator are unchanged from
|
||||
`0.9.4`; the only change that ships to users is to the R package's build script.
|
||||
The rest of the release is CI / release-pipeline hardening (dependency caching,
|
||||
job timeouts, and network-install retries) that does not affect the artifacts.
|
||||
|
||||
### Fixed
|
||||
- **R package: retry the C ABI download.** `configure` / `configure.win` fetch the
|
||||
prebuilt `wickra-c-<triple>.tar.gz` from the matching GitHub release. A freshly
|
||||
cut release can briefly return 404 while its assets propagate across the CDN
|
||||
(and a transient network blip would also fail it), so the single-shot download
|
||||
is now retried with a backoff (~2 min) before giving up. Fixes
|
||||
`cannot open URL … 404 Not Found` on r-universe / source installs taken right
|
||||
after a release.
|
||||
|
||||
|
||||
## [0.9.4] - 2026-06-17
|
||||
|
||||
Packaging fix for the `0.9.3` data layer. The library is identical to `0.9.3` on
|
||||
every platform that already published; the only additions are an opt-in
|
||||
`vendored-tls` build feature and the Linux Python wheels, which `0.9.3` could not
|
||||
build.
|
||||
|
||||
### Fixed
|
||||
- **Linux Python wheels (`manylinux` / `musllinux`) now build.** The `live-binance`
|
||||
data layer links `native-tls` -> `openssl-sys`, which needs OpenSSL at build
|
||||
time. The `manylinux` wheel containers ship no OpenSSL headers and the
|
||||
`musllinux` build cross-compiles against a musl sysroot that has no OpenSSL at
|
||||
all, so the wheels failed to compile. The Linux wheels are now built with a new
|
||||
opt-in `vendored-tls` feature that compiles OpenSSL from source and links it
|
||||
statically (no system OpenSSL required, on either libc). The native macOS and
|
||||
Windows wheels were unaffected (Security.framework / SChannel). As a result
|
||||
`0.9.3` shipped to crates.io, Maven Central, NuGet, and npm but not to PyPI;
|
||||
PyPI publishes starting with `0.9.4`.
|
||||
|
||||
### Added
|
||||
- **`vendored-tls` feature** on `wickra-data` (and the Python binding): builds the
|
||||
`live-binance` TLS stack against a statically compiled OpenSSL. Off by default;
|
||||
used by the release wheels and exercised on every PR by a `manylinux` /
|
||||
`musllinux` container build-smoke CI job.
|
||||
|
||||
|
||||
## [0.9.3] - 2026-06-17
|
||||
|
||||
### Changed
|
||||
- **Python: zero third-party dependencies — NumPy is no longer required**
|
||||
(breaking). `pip install wickra` now pulls nothing else. Batch inputs accept any
|
||||
sequence or buffer of numbers (`array.array`, `memoryview`, a NumPy array, or a
|
||||
plain `list`); single-output `batch(...)` now returns a stdlib `array.array('d')`
|
||||
and multi-output indicators return a buffer-protocol `Matrix` (with `.shape`,
|
||||
integer-row and `[i, j]` element access, `.tolist()`) instead of 1-D / 2-D NumPy
|
||||
arrays. Both expose the buffer protocol, so `numpy.asarray(result)` still wraps a
|
||||
1-D result zero-copy when NumPy is installed — it is now an optional extra
|
||||
(`pip install wickra[numpy]`). Streaming `update(...)` is unchanged, and results
|
||||
are numerically identical. Single-output `batch(...)` is slower than the previous
|
||||
NumPy path — a stdlib `array.array` cannot take ownership of the Rust result, so
|
||||
it is copied rather than moved — though absolute batch latency stays in the
|
||||
low-millisecond range. The other nine languages were already dependency-free.
|
||||
|
||||
### Removed
|
||||
- **Python: `numpy` runtime dependency** (see *Changed*). NumPy moves to the
|
||||
optional `numpy`/`test`/`bench` extras.
|
||||
|
||||
### Fixed
|
||||
- **Binance kline feed: add the missing `3d` and `1M` intervals.** The
|
||||
`live-binance` `Interval` enum was missing three-day (`3d`) and one-month (`1M`)
|
||||
candles, two of Binance's 16 supported kline intervals. Both are now selectable
|
||||
and map to the correct wire-format strings.
|
||||
|
||||
### Added
|
||||
- **Native historical Binance REST kline fetcher in 9 languages (data layer).**
|
||||
`fetchBinanceKlines` (Node.js / Python `fetch_binance_klines` / Go
|
||||
`FetchBinanceKlines` / C# `BinanceFeed.FetchKlines` / Java `BinanceFeed.fetchKlines`
|
||||
/ R `fetch_binance_klines`; C / C++ call `wickra_binance_fetch_klines`) downloads
|
||||
historical OHLCV candles straight from Binance's public REST endpoint — no
|
||||
third-party HTTP/JSON client (`jackson`, `jsonlite`, `urllib`, …) needed. Pass a
|
||||
symbol, interval, and limit (`1..=1000`) plus optional millisecond start/end
|
||||
bounds; it blocks until the response arrives and returns the parsed candles. It
|
||||
is built on `ureq` with native-tls, sharing the live feed's TLS stack, and is
|
||||
covered by mock-HTTP-server tests. The historical counterpart to the live
|
||||
`BinanceFeed`; WASM is excluded (browsers use the host `fetch`). Ships with the
|
||||
C ABI's default `live-binance` feature.
|
||||
- **Native live Binance kline feed in 9 languages (data layer).** `BinanceFeed`
|
||||
streams live OHLCV candles straight from Binance's public WebSocket — no
|
||||
third-party WebSocket client (`ws`, `websockets`, `gorilla/websocket`, …) in any
|
||||
binding. Construct it with comma-separated symbols + an interval, then poll
|
||||
`next(timeout)` for the next event (or `null`/`None` on timeout); the connection
|
||||
reconnects transparently. Exposed natively (Node.js / Python — a blocking poll
|
||||
that drives the tested async stream on a tokio runtime) and over the C ABI as Go
|
||||
`Next()`, C# `Next()`, Java `next()`, and the R `binance_next()`; C / C++ call
|
||||
`wickra_binance_connect` / `_next` / `_close` / `_free` directly. The connect →
|
||||
read → reconnect pipeline is covered by the existing mock-WS-server tests. WASM
|
||||
is excluded (a browser has no raw sockets; use the host `WebSocket`). The C ABI
|
||||
ships the feed by default (`live-binance` feature); the wasm build drops it.
|
||||
- **CSV candle reading in all 10 languages (data layer).** The `CandleReader`
|
||||
parses a `timestamp,open,high,low,close,volume` CSV buffer (a leading UTF-8 BOM
|
||||
and field whitespace are tolerated) into candles: construct it from a CSV string
|
||||
and call `read()` for every candle in file order. Exposed natively (Node.js /
|
||||
WASM `read(): Candle[]`, Python `read() -> list[tuple]`) and over the C ABI as Go
|
||||
`Read() []Candle`, C# `Candle[] Read()`, Java `Candle[] read()`, and the R
|
||||
`read()` S3 generic (an `n×6` matrix); C / C++ call `wickra_candle_reader_new` /
|
||||
`_count` / `_read` directly. A cross-language golden
|
||||
(`testdata/golden/data_csv*.csv`) pins the parsed candles identically across
|
||||
every binding. This makes CSV backtest loading dependency-free in every binding.
|
||||
- **Candle resampling in all 10 languages (data layer).** The `Resampler`
|
||||
aggregates candles into a higher timeframe (e.g. 1m → 5m): `update(open, high,
|
||||
low, close, volume, timestamp)` returns the completed higher-timeframe candle on
|
||||
a bucket boundary (else `null`/`None`/`NA`), and `flush()` emits the final,
|
||||
still-open candle. Exposed natively (Node.js / WASM / Python) and over the C ABI
|
||||
(Go / C# / Java return `(Candle, bool)` / `Candle?` / `Candle`; R via `update()`
|
||||
and a `flush()` S3 method; C / C++ directly). A cross-language golden
|
||||
(`testdata/golden/data_resampled.csv`) pins the resampled stream identically.
|
||||
- **Tick-to-candle aggregation in all 10 languages (data layer).** The
|
||||
`TickAggregator` — roll trade ticks up into fixed-timeframe OHLCV candles, with
|
||||
optional gap filling — is now exposed natively (Node.js / WASM `push(price,
|
||||
size, ts): Candle[]`, Python `push(...) -> list[tuple]`) and over the C ABI as
|
||||
Go `Push() []Candle`, C# `Candle[] Push()`, Java `Candle[] push()`, and the R
|
||||
`push()` generic (an `n×6` matrix); C / C++ call the C ABI directly. The C ABI
|
||||
uses a lossless two-step `wickra_tick_aggregator_push` / `_drain` so a single
|
||||
push that gap-fills across many empty buckets never overflows a fixed buffer. A
|
||||
new cross-language golden (`testdata/golden/data_*.csv`) pins the candle stream
|
||||
identically across every binding. This is the first feature of a data layer that
|
||||
makes the non-Rust bindings dependency-free for tick aggregation.
|
||||
- **`name()` on every indicator in all 10 languages.** The canonical
|
||||
`Indicator::name()` / `BarBuilder::name()` accessor is now exposed through every
|
||||
binding — Node.js `name()`, WASM `name()`, Python `name()`, and the C ABI
|
||||
`wickra_<ind>_name()` surfaced as Go `Name()`, C# `Name()`, Java `name()`, and
|
||||
the R `name()` S3 generic (C/C++ call the C ABI directly). The returned string
|
||||
is the core canonical name, which may differ from the registered class name
|
||||
(e.g. `ChaikinMoneyFlow` reports `"CMF"`, `Donchian` reports
|
||||
`"DonchianChannels"`). A new cross-language golden (`testdata/golden/names.json`)
|
||||
pins this name for all 514 indicators identically across every binding.
|
||||
|
||||
## [0.9.2] - 2026-06-15
|
||||
|
||||
### Added
|
||||
- **Cross-language golden parity for all 514 indicators across all 10 languages.**
|
||||
A new `gen_golden` reference emits a deterministic OHLCV input series plus the
|
||||
Rust output of every one of the 514 indicators to `testdata/golden/`. Each
|
||||
binding now replays that shared input and is checked **bit-for-bit against the
|
||||
Rust reference**, covering every archetype (scalar, multi-output, pairwise,
|
||||
derivatives-tick, cross-section, order-book, trade, profile, alt-chart bars,
|
||||
footprint):
|
||||
- Python, Node.js, Java and R via reflection-driven runners.
|
||||
- Go, C# and C/C++ via generated dispatch (`golden_all_test.go`,
|
||||
`GoldenAllTests.g.cs`, `examples/c/golden_test.c` compiled as both C and C++).
|
||||
- WASM via a `node --test` runner over the nodejs-target build.
|
||||
- CI now runs the WASM golden suite; the C/C++ golden tests run as `ctest`
|
||||
targets in the existing C-ABI job, and the Python/Node/Go/C#/Java/R suites pick
|
||||
up their golden runners automatically.
|
||||
- **README:** a "verified across 10 languages" badge (linking to the FAQ that
|
||||
explains the cross-language golden parity) and a per-binding throughput table so
|
||||
readers can pick a binding by its streaming FFI cost.
|
||||
|
||||
### Fixed
|
||||
- **Java binding marshalled C ABI `bool` parameters incorrectly.** The
|
||||
cross-section state flags (`newHigh`, `newLow`, `aboveMa`, `onBuySignal`) were
|
||||
allocated as `JAVA_DOUBLE` arrays and passed to `const bool*` parameters, so the
|
||||
native side read the low byte of each 8-byte double and saw every flag as
|
||||
`false` (affecting e.g. `NewHighsNewLows`, `HighLowIndex`, `BullishPercentIndex`,
|
||||
`PercentAboveMa`). They are now packed into a real `bool` buffer. `MacdExt`'s
|
||||
`MaType` arguments are now passed as `byte` to match the `uint8_t` downcall.
|
||||
- **R binding marshalled C ABI `bool` flags incorrectly.** `(bool *)REAL(x)`
|
||||
reinterpreted the 8-byte doubles as 1-byte bools across the 15 cross-section
|
||||
update wrappers, reading every flag as `false`; the flags are now converted into
|
||||
a real C `bool` buffer.
|
||||
- C# binding: added the `#nullable enable` directive the generated
|
||||
`Indicators.g.cs` requires, clearing four `CS8669` warnings.
|
||||
|
||||
### Changed
|
||||
- Renamed the `live_trading` examples to `live_binance` across the Python, Node.js,
|
||||
WASM and C examples — they poll Binance market data, they do not place trades.
|
||||
- **Breaking — de-duplicated four indicators that computed identically to another
|
||||
one.** Each is now its own distinct, correctly-defined indicator (the catalogue
|
||||
stays at the same count):
|
||||
- `AverageDrawdown` now reports the mean of the maximum depths of the distinct
|
||||
drawdown episodes in the window (previously the per-bar mean under-water
|
||||
fraction, which equalled `PainIndex`).
|
||||
- `IntradayIntensity` now reports the raw per-bar Bostian intensity
|
||||
`volume * (2*close − high − low) / (high − low)` (previously a cumulative line
|
||||
that equalled the A/D Line `Adl`; its normalized form is `Cmf`).
|
||||
- `AwesomeOscillatorHistogram` now reports the AO momentum
|
||||
`AO[t] − AO[t−lookback]`; its third parameter is the momentum `lookback`
|
||||
(default 1) instead of an SMA period (the old `AO − SMA(AO, n)` equalled
|
||||
`AcceleratorOscillator`).
|
||||
- `AdOscillator` is now the Williams **A/D Oscillator** (`WAD − SMA(WAD, 13)`),
|
||||
distinct from the cumulative Williams A/D line `Wad`. Its native (Python /
|
||||
Node.js / WASM) alias is renamed **`WilliamsAD` → `ADOSC`**.
|
||||
|
||||
## [0.9.1] - 2026-06-14
|
||||
|
||||
### Added
|
||||
@@ -1663,7 +1893,13 @@ public API changes.
|
||||
optional Binance live feed.
|
||||
- Bindings for Python, Node.js, and WebAssembly.
|
||||
|
||||
[Unreleased]: https://github.com/wickra-lib/wickra/compare/v0.9.1...HEAD
|
||||
[Unreleased]: https://github.com/wickra-lib/wickra/compare/v0.9.7...HEAD
|
||||
[0.9.7]: https://github.com/wickra-lib/wickra/compare/v0.9.6...v0.9.7
|
||||
[0.9.6]: https://github.com/wickra-lib/wickra/compare/v0.9.5...v0.9.6
|
||||
[0.9.5]: https://github.com/wickra-lib/wickra/compare/v0.9.4...v0.9.5
|
||||
[0.9.4]: https://github.com/wickra-lib/wickra/compare/v0.9.3...v0.9.4
|
||||
[0.9.3]: https://github.com/wickra-lib/wickra/compare/v0.9.2...v0.9.3
|
||||
[0.9.2]: https://github.com/wickra-lib/wickra/compare/v0.9.1...v0.9.2
|
||||
[0.9.1]: https://github.com/wickra-lib/wickra/compare/v0.9.0...v0.9.1
|
||||
[0.9.0]: https://github.com/wickra-lib/wickra/compare/v0.8.9...v0.9.0
|
||||
[0.8.9]: https://github.com/wickra-lib/wickra/compare/v0.8.8...v0.8.9
|
||||
|
||||
Generated
+56
-293
@@ -32,12 +32,6 @@ version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
version = "0.5.1"
|
||||
@@ -64,6 +58,12 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.8.0"
|
||||
@@ -100,6 +100,12 @@ version = "3.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.11.1"
|
||||
@@ -398,12 +404,6 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
@@ -489,23 +489,10 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi 5.3.0",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi 6.0.0",
|
||||
"wasip2",
|
||||
"wasip3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.7.1"
|
||||
@@ -517,15 +504,6 @@ dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
@@ -636,12 +614,6 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "1.1.0"
|
||||
@@ -670,9 +642,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.17.1",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -712,12 +682,6 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "leb128fmt"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.186"
|
||||
@@ -758,16 +722,6 @@ version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "matrixmultiply"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"rawpointer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
@@ -869,21 +823,6 @@ dependencies = [
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndarray"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841"
|
||||
dependencies = [
|
||||
"matrixmultiply",
|
||||
"num-complex",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"rawpointer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.3"
|
||||
@@ -893,24 +832,6 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-complex"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
@@ -943,22 +864,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a5b15d63a5ff39e378daed0e1340d3a5964703ea9712eb09a0dc66fade996f4"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"ndarray",
|
||||
"num-complex",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"pyo3",
|
||||
"pyo3-build-config",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
@@ -1002,6 +907,15 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-src"
|
||||
version = "300.5.4+3.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.116"
|
||||
@@ -1010,6 +924,7 @@ checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"openssl-src",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
@@ -1076,15 +991,6 @@ version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic-util"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.5"
|
||||
@@ -1103,16 +1009,6 @@ dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.5.0"
|
||||
@@ -1228,12 +1124,6 @@ version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.4"
|
||||
@@ -1260,7 +1150,7 @@ version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1272,12 +1162,6 @@ dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rawpointer"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.12.0"
|
||||
@@ -1327,12 +1211,6 @@ version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.4"
|
||||
@@ -1557,7 +1435,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.4.2",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
@@ -1725,10 +1603,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
name = "ureq"
|
||||
version = "2.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"log",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
@@ -1791,16 +1676,7 @@ version = "1.0.3+wasi-0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
|
||||
dependencies = [
|
||||
"wit-bindgen 0.57.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip3"
|
||||
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
||||
dependencies = [
|
||||
"wit-bindgen 0.51.0",
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1897,40 +1773,6 @@ version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f4d8ae7ad5440360e9799dfd42857d126454a88441ddf72d288ef83fa47f527"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
|
||||
dependencies = [
|
||||
"leb128fmt",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-metadata"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.98"
|
||||
@@ -1943,7 +1785,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra"
|
||||
version = "0.9.1"
|
||||
version = "0.9.7"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"criterion",
|
||||
@@ -1954,7 +1796,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-bench"
|
||||
version = "0.9.1"
|
||||
version = "0.9.7"
|
||||
dependencies = [
|
||||
"criterion",
|
||||
"kand",
|
||||
@@ -1966,14 +1808,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-c"
|
||||
version = "0.9.1"
|
||||
version = "0.9.7"
|
||||
dependencies = [
|
||||
"tokio",
|
||||
"wickra-core",
|
||||
"wickra-data",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wickra-core"
|
||||
version = "0.9.1"
|
||||
version = "0.9.7"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"proptest",
|
||||
@@ -1983,24 +1827,26 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-data"
|
||||
version = "0.9.1"
|
||||
version = "0.9.7"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"csv",
|
||||
"futures-util",
|
||||
"native-tls",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"ureq",
|
||||
"url",
|
||||
"wickra-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wickra-examples"
|
||||
version = "0.9.1"
|
||||
version = "0.9.7"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
"tokio",
|
||||
@@ -2010,26 +1856,30 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-node"
|
||||
version = "0.9.1"
|
||||
version = "0.9.7"
|
||||
dependencies = [
|
||||
"napi",
|
||||
"napi-build",
|
||||
"napi-derive",
|
||||
"tokio",
|
||||
"wickra-core",
|
||||
"wickra-data",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wickra-python"
|
||||
version = "0.9.1"
|
||||
version = "0.9.7"
|
||||
dependencies = [
|
||||
"numpy",
|
||||
"bytemuck",
|
||||
"pyo3",
|
||||
"tokio",
|
||||
"wickra-core",
|
||||
"wickra-data",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wickra-wasm"
|
||||
version = "0.9.1"
|
||||
version = "0.9.7"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"js-sys",
|
||||
@@ -2038,6 +1888,7 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
"wickra-core",
|
||||
"wickra-data",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2095,100 +1946,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||
dependencies = [
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.57.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"indexmap",
|
||||
"prettyplease",
|
||||
"syn",
|
||||
"wasm-metadata",
|
||||
"wit-bindgen-core",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-macro"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-component"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"wasm-encoder",
|
||||
"wasm-metadata",
|
||||
"wasmparser",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"indexmap",
|
||||
"log",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"unicode-xid",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.6.3"
|
||||
|
||||
+3
-2
@@ -14,7 +14,7 @@ members = [
|
||||
exclude = ["fuzz"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.9.1"
|
||||
version = "0.9.7"
|
||||
authors = ["kingchenc <support@wickra.org>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.86"
|
||||
@@ -26,7 +26,8 @@ keywords = ["finance", "trading", "indicators", "technical-analysis", "ta"]
|
||||
categories = ["finance", "mathematics", "science"]
|
||||
|
||||
[workspace.dependencies]
|
||||
wickra-core = { path = "crates/wickra-core", version = "0.9.1" }
|
||||
wickra-core = { path = "crates/wickra-core", version = "0.9.7" }
|
||||
wickra-data = { path = "crates/wickra-data", version = "0.9.7" }
|
||||
|
||||
thiserror = "2"
|
||||
rayon = "1.10"
|
||||
|
||||
@@ -18,8 +18,9 @@
|
||||
[](https://www.bestpractices.dev/projects/13094)
|
||||
[](https://github.com/wickra-lib/wickra/attestations)
|
||||
[](https://docs.wickra.org)
|
||||
[](https://docs.wickra.org/FAQ#do-all-the-language-bindings-compute-the-same-values)
|
||||
|
||||
**Streaming-first technical indicators. Install with `pip install wickra` — no system dependencies.**
|
||||
**Streaming-first technical indicators. Install with `pip install wickra` — no system dependencies, zero third-party packages.**
|
||||
|
||||
Wickra is a multi-language technical-analysis library with a Rust core and
|
||||
native bindings for Python, Node.js and WASM, plus a C ABI that C, C++,
|
||||
@@ -28,13 +29,13 @@ state machine that updates in O(1) per new data point, so live trading bots and
|
||||
historical backtests share the exact same implementation.
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
import wickra as ta
|
||||
import wickra as ta # zero third-party deps — not even NumPy
|
||||
|
||||
# Batch: classic TA-Lib-style usage
|
||||
prices = np.linspace(100, 200, 1000)
|
||||
prices = [100.0 + i * 0.1 for i in range(1000)]
|
||||
rsi = ta.RSI(14)
|
||||
values = rsi.batch(prices) # numpy array, NaN during warmup
|
||||
values = rsi.batch(prices) # array.array('d'), NaN during warmup
|
||||
# np.asarray(values) wraps it zero-copy if you use NumPy
|
||||
|
||||
# Streaming: same indicator, fed tick by tick
|
||||
rsi = ta.RSI(14)
|
||||
@@ -53,6 +54,7 @@ Full documentation lives at **[docs.wickra.org](https://docs.wickra.org)**:
|
||||
[Node](https://docs.wickra.org/Quickstart-Node),
|
||||
[WASM](https://docs.wickra.org/Quickstart-WASM),
|
||||
[C](https://docs.wickra.org/Quickstart-C),
|
||||
[C++](https://docs.wickra.org/Quickstart-C),
|
||||
[C#](https://docs.wickra.org/Quickstart-CSharp),
|
||||
[Go](https://docs.wickra.org/Quickstart-Go),
|
||||
[Java](https://docs.wickra.org/Quickstart-Java),
|
||||
@@ -89,6 +91,11 @@ times to get there.
|
||||
runs a real warmup, and returns an `Option` so a single bad tick can't silently
|
||||
poison state. `batch == streaming` is **bit-exact, fuzzed and 100 %-line-covered
|
||||
for all 514 indicators**.
|
||||
- **Identical across every language — proven, not promised.** All 514 indicators
|
||||
are replayed through **all 10 languages** (Rust · Python · Node.js · WASM · C ·
|
||||
C++ · C# · Go · Java · R) and checked **bit-for-bit against the Rust reference**
|
||||
via shared golden fixtures in CI. The math is verifiably the same everywhere —
|
||||
this very check caught and fixed two real cross-language marshalling bugs.
|
||||
- **Orders of magnitude faster where it counts.** In streaming Wickra is **11–56×**
|
||||
faster than the only other incremental peer and **thousands of times** faster
|
||||
than recompute-on-every-tick libraries. On batch it wins several rows outright
|
||||
@@ -97,8 +104,13 @@ times to get there.
|
||||
- **Install in one line, anywhere.** `pip install wickra` / `npm install wickra` —
|
||||
precompiled wheels and binaries, **no C toolchain, none of TA-Lib's setup pain**.
|
||||
macOS · Linux · Windows.
|
||||
- **Batteries included.** Indicator chaining, a streaming OHLCV CSV reader, and a
|
||||
live Binance kline feed ship in the box.
|
||||
- **Batteries included — zero third-party deps, in every language.** A full native
|
||||
data layer ships in the box: a CSV candle reader, a tick-to-candle aggregator, a
|
||||
timeframe resampler, a live Binance WebSocket feed and a historical Binance REST
|
||||
fetcher — in **all 10 languages**. Loading a CSV, rolling ticks into candles,
|
||||
resampling and streaming live data needs **no foreign package** — no pandas, no
|
||||
`csv-parse`, no `ws`/`websockets`, no `jackson`, no `jsonlite`, not even NumPy.
|
||||
`pip install wickra` / `npm install wickra` / `go get` / … pulls **nothing else**.
|
||||
- **Truly permissive.** **MIT OR Apache-2.0** — drop it straight into commercial
|
||||
and closed-source work.
|
||||
|
||||
@@ -137,12 +149,38 @@ elsewhere for `None`-warmup, NaN-safety and bit-exact `batch == streaming`.
|
||||
Full tables (Rust + Python, streaming + batch) and how to reproduce them live in
|
||||
**[BENCHMARKS.md](BENCHMARKS.md)**.
|
||||
|
||||
### Pick your language with eyes open — per-binding throughput
|
||||
|
||||
Every binding calls the **same** Rust core, so this is **not** a speed claim — it
|
||||
is the raw cost of crossing each language's FFI boundary (`SMA(20)`, 200 000 bars,
|
||||
Ryzen 9 9950X, million updates/sec). **Batch stays high for most bindings;
|
||||
streaming is where the boundary shows** — so if you stream tick-by-tick, the table
|
||||
tells you which binding keeps up and which to avoid for hot loops.
|
||||
|
||||
| Language | streaming (Mupd/s) | batch (Mupd/s) |
|
||||
|-----------------|-------------------:|---------------:|
|
||||
| Rust (no FFI) | 380 | 498 |
|
||||
| C / C++ | 365 | 358 |
|
||||
| C# | 348 | 259 |
|
||||
| Python | 31 | 46 |
|
||||
| Java | 38 | 173 |
|
||||
| Go | 23 | 394 |
|
||||
| WASM | 21 | 169 |
|
||||
| Node.js | 16 | 9 |
|
||||
| R | 0.1 | 279 |
|
||||
|
||||
All ten share one verified implementation (see the verification badge above), so
|
||||
the *numbers* differ but the *values* are bit-for-bit identical. Methodology and
|
||||
the per-indicator breakdown are in [BENCHMARKS.md](BENCHMARKS.md#3-per-binding-throughput--the-cost-of-the-boundary).
|
||||
|
||||
## Indicators
|
||||
|
||||
514 streaming-first indicators across twenty-four families. Every one passes the
|
||||
`batch == streaming` equivalence test, reference-value tests, and reset
|
||||
semantics tests. Each has a per-indicator deep dive (formula, parameters,
|
||||
warmup) at [docs.wickra.org](https://docs.wickra.org/Indicators-Overview).
|
||||
semantics tests — and is replayed through **all 10 languages** and checked
|
||||
bit-for-bit against the Rust reference (golden fixtures, in CI). Each has a
|
||||
per-indicator deep dive (formula, parameters, warmup) at
|
||||
[docs.wickra.org](https://docs.wickra.org/Indicators-Overview).
|
||||
|
||||
| Family | Indicators |
|
||||
|--------|-----------|
|
||||
@@ -153,7 +191,7 @@ warmup) at [docs.wickra.org](https://docs.wickra.org/Indicators-Overview).
|
||||
| Volatility & Bands | ATR, Bollinger Bands, Keltner Channels, Donchian Channels, NATR, StdDev, Ulcer Index, Historical Volatility, Bollinger Bandwidth, %B, True Range, Chaikin Volatility, RVI (Relative Volatility Index), Parkinson Volatility, Garman-Klass Volatility, Rogers-Satchell Volatility, Yang-Zhang Volatility, Volatility Cone |
|
||||
| Bands & Channels | MA Envelope, Acceleration Bands, STARC Bands, ATR Bands, Hurst Channel, LinReg Channel, Standard Error Bands, Double Bollinger Bands, TTM Squeeze, Fractal Chaos Bands, VWAP StdDev Bands, Quartile Bands, Bomar Bands, Median Channel, Projection Bands, Projection Oscillator |
|
||||
| Trailing Stops | Parabolic SAR, Parabolic SAR Extended (SAREXT), SuperTrend, Chandelier Exit, Chande Kroll Stop, ATR Trailing Stop, HiLo Activator, Volty Stop, Yo-Yo Exit, Donchian Channel Stop, Percentage Trailing Stop, Step Trailing Stop, Renko Trailing Stop, Kase DevStop, Elder SafeZone, ATR Ratchet, NRTR, Time-Based Stop, Modified MA Stop |
|
||||
| Volume | OBV, VWAP (cumulative + rolling), ADL, Volume-Price Trend, Chaikin Money Flow, Chaikin Oscillator, Force Index, Ease of Movement, Klinger Volume Oscillator, Volume Oscillator, NVI, PVI, Williams A/D, Anchored VWAP, Demand Index, TSV, VZO, Market Facilitation Index, Volume RSI, Williams Accumulation/Distribution, Twiggs Money Flow, Trade Volume Index, Intraday Intensity Index, Better Volume, Volume-Weighted MACD |
|
||||
| Volume | OBV, VWAP (cumulative + rolling), ADL, Volume-Price Trend, Chaikin Money Flow, Chaikin Oscillator, Force Index, Ease of Movement, Klinger Volume Oscillator, Volume Oscillator, NVI, PVI, Williams A/D Oscillator, Anchored VWAP, Demand Index, TSV, VZO, Market Facilitation Index, Volume RSI, Williams Accumulation/Distribution, Twiggs Money Flow, Trade Volume Index, Intraday Intensity, Better Volume, Volume-Weighted MACD |
|
||||
| Price Statistics | Typical Price, Median Price, Weighted Close, Linear Regression, Linear Regression Slope, Z-Score, Linear Regression Angle, Variance, Coefficient of Variation, Skewness, Kurtosis, Standard Error, Detrended StdDev, R², Median Absolute Deviation, Autocorrelation, Hurst Exponent, Pearson Correlation, Beta, Pairwise Beta, Pair Spread Z-Score, Lead-Lag Cross-Correlation, Cointegration, Relative Strength A-vs-B, Spearman Correlation, Mid Price, Mid Point, Average Price, Linear Regression Intercept, Time Series Forecast, Rolling Correlation, Rolling Covariance, OU Half-Life, Spread Hurst, Distance SSD, Beta-Neutral Spread, Variance Ratio, Granger Causality, Kalman Hedge Ratio, Spread Bollinger Bands, Spread AR(1) Coefficient, Jarque-Bera, Rolling Min-Max Scaler, Shannon Entropy, Sample Entropy, Kendall Tau |
|
||||
| Ehlers / Cycle (DSP) | MAMA, FAMA, Fisher Transform, Inverse Fisher Transform, SuperSmoother, Hilbert Dominant Cycle, Hilbert Phasor, Hilbert DC Phase, Hilbert Trend Mode, Sine Wave, Decycler, Decycler Oscillator, Roofing Filter, Center of Gravity, Cybernetic Cycle, Adaptive Cycle, Empirical Mode Decomposition, Ehlers Stochastic, Instantaneous Trendline, Highpass Filter, Reflex, Trendflex, Correlation Trend Indicator, Adaptive RSI, Universal Oscillator, Adaptive CCI, Bandpass Filter, Even Better Sinewave, Autocorrelation Periodogram |
|
||||
| Pivots & S/R | Classic Pivots, Fibonacci Pivots, Camarilla, Woodie Pivots, DeMark Pivots, Williams Fractals, ZigZag, Central Pivot Range, Murrey Math Lines, Andrews Pitchfork, Volume-Weighted Support/Resistance, Pivot Reversal |
|
||||
@@ -248,12 +286,17 @@ chain.update(price);
|
||||
|
||||
## Live data sources
|
||||
|
||||
`wickra-data` (separate crate, opt-in) ships:
|
||||
Wickra ships a complete, **native data layer** — exposed in **all 10 languages**,
|
||||
pulling **zero third-party packages** (no pandas / `csv-parse` / `ws` / `jackson`
|
||||
/ `jsonlite`). In Rust it lives in the `wickra-data` crate; every binding exposes
|
||||
the same building blocks:
|
||||
|
||||
- A streaming OHLCV **CSV reader**.
|
||||
- A **tick-to-candle aggregator** with arbitrary timeframes.
|
||||
- A **candle resampler** for multi-timeframe analysis (1m → 5m → 1h on the fly).
|
||||
- A **Binance Spot WebSocket** kline adapter (feature `live-binance`).
|
||||
- A streaming OHLCV **CSV reader** (`CandleReader`).
|
||||
- A **tick-to-candle aggregator** with arbitrary timeframes (`TickAggregator`).
|
||||
- A **candle resampler** for multi-timeframe analysis (1m → 5m → 1h on the fly, `Resampler`).
|
||||
- A live **Binance Spot WebSocket** kline feed (`BinanceFeed`, feature `live-binance`).
|
||||
- A historical **Binance REST** kline fetcher (`fetch_binance_klines`) — native
|
||||
HTTP + JSON, no third-party client.
|
||||
|
||||
```rust
|
||||
use wickra::{Indicator, Rsi};
|
||||
@@ -270,8 +313,9 @@ while let Some(event) = stream.next_event().await? {
|
||||
}
|
||||
```
|
||||
|
||||
A Python live-trading example using the public `websockets` package lives at
|
||||
`examples/python/live_trading.py`.
|
||||
Native live-feed and historical-fetch examples — using `wickra.BinanceFeed` and
|
||||
`wickra.fetch_binance_klines`, **with no third-party HTTP/WebSocket client** — live
|
||||
under `examples/python/` (and the matching directory for every other language).
|
||||
|
||||
## Project layout
|
||||
|
||||
@@ -294,8 +338,8 @@ wickra/
|
||||
├── examples/ examples/README.md indexes every language
|
||||
│ ├── data/ real BTCUSDT OHLCV datasets, one per timeframe
|
||||
│ ├── rust/ Rust workspace member (`wickra-examples`)
|
||||
│ ├── python/ backtest, live trading, parallel assets, multi-tf
|
||||
│ ├── node/ streaming, backtest, live trading (load `wickra`)
|
||||
│ ├── python/ backtest, live Binance feed, parallel assets, multi-tf
|
||||
│ ├── node/ streaming, backtest, live Binance feed (load `wickra`)
|
||||
│ ├── wasm/ browser demo for `wickra-wasm`
|
||||
│ ├── c/ C smoke + streaming, C++ RAII wrapper
|
||||
│ ├── csharp/ streaming, backtest, strategies (load `Wickra`)
|
||||
@@ -381,11 +425,15 @@ Every layer is covered; run the suites with the commands in
|
||||
- `bindings/java`: JUnit cases covering one indicator per FFI archetype
|
||||
(scalar/batch, multi-output, bars, profile, array input) plus batch equivalence.
|
||||
|
||||
The four C-ABI bindings (C#, Go, Java, R) additionally replay a shared,
|
||||
language-neutral golden fixture (`testdata/golden/*.csv`, generated by
|
||||
`cargo run -p wickra-examples --bin gen_golden`) and assert exact parity with the
|
||||
Rust reference outputs across every archetype (SMA, EMA, RSI, ATR, MACD, ADX,
|
||||
Beta), catching FFI wiring bugs the math-only core tests cannot see.
|
||||
On top of those per-binding tests, **all 10 languages** (Rust, Python, Node.js,
|
||||
WASM, C, C++, C#, Go, Java, R) replay a shared, language-neutral golden fixture
|
||||
(`testdata/golden/*.csv`, generated by
|
||||
`cargo run -p wickra-examples --bin gen_golden`) and assert **bit-for-bit parity
|
||||
with the Rust reference for every one of the 514 indicators** across every
|
||||
archetype (scalar, multi-output, pairwise, derivatives-tick, cross-section,
|
||||
order-book, trade, profile, alt-chart bars, footprint). This catches FFI wiring
|
||||
bugs the math-only core tests cannot see — it has already found and fixed real
|
||||
cross-language marshalling bugs in the Java and R bindings.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
+3
-3
@@ -2,13 +2,13 @@
|
||||
|
||||
## Supported versions
|
||||
|
||||
Wickra is pre-1.0. Security fixes are applied to the latest released `0.9.1`
|
||||
Wickra is pre-1.0. Security fixes are applied to the latest released `0.9.7`
|
||||
version only; please upgrade to the newest release before reporting an issue.
|
||||
|
||||
| Version | Supported |
|
||||
| --- | --- |
|
||||
| 0.9.1 (latest) | :white_check_mark: |
|
||||
| < 0.9.1 | :x: |
|
||||
| 0.9.7 (latest) | :white_check_mark: |
|
||||
| < 0.9.7 | :x: |
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
|
||||
+14
-1
@@ -48,8 +48,16 @@ float_cmp = "allow"
|
||||
# package's wasm build (r-universe / webR) compiles this crate with
|
||||
# --no-default-features to drop rayon; wickra-core falls back to its serial
|
||||
# batch path, which is cfg-gated behind the same feature.
|
||||
default = ["parallel"]
|
||||
#
|
||||
# `live-binance` is on by default too so the published C ABI ships the native
|
||||
# Binance kline feed (tokio + TLS WebSocket) — the six C-ABI languages with no
|
||||
# native WebSocket (C, C++, Go, R, plus Node/Python via their own bindings) get
|
||||
# it without a third-party client. The wasm build drops it via
|
||||
# --no-default-features (a browser has no raw TCP/TLS sockets; WASM users use the
|
||||
# host `WebSocket`), exactly like rayon.
|
||||
default = ["parallel", "live-binance"]
|
||||
parallel = ["wickra-core/parallel"]
|
||||
live-binance = ["wickra-data/live-binance", "dep:tokio"]
|
||||
|
||||
[dependencies]
|
||||
# Direct path dep rather than `workspace = true`: a member-level
|
||||
@@ -57,3 +65,8 @@ parallel = ["wickra-core/parallel"]
|
||||
# does not set it, which would leave rayon in the wasm build. wickra-c is
|
||||
# `publish = false`, so no version pin is needed (and none to keep in sync).
|
||||
wickra-core = { path = "../../crates/wickra-core", default-features = false }
|
||||
wickra-data = { path = "../../crates/wickra-data" }
|
||||
# Only pulled in by `live-binance`: a single-thread runtime drives the async
|
||||
# Binance feed behind the blocking C-ABI poll. The feed's own I/O features come
|
||||
# transitively from wickra-data; this just needs the runtime + timeout.
|
||||
tokio = { version = "1", features = ["rt", "net", "time"], optional = true }
|
||||
|
||||
@@ -17,4 +17,4 @@ documentation = false
|
||||
# discovered and emitted as forward-declared opaque structs. Their fields are
|
||||
# never exposed — only `T *` handles cross the boundary.
|
||||
parse_deps = true
|
||||
include = ["wickra-core"]
|
||||
include = ["wickra-core", "wickra-data"]
|
||||
|
||||
+1116
-1
File diff suppressed because it is too large
Load Diff
+10745
-5
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using Wickra;
|
||||
using Xunit;
|
||||
|
||||
// The live Binance feed's connect → read → reconnect pipeline is covered
|
||||
// deterministically by the Rust mock-WS-server tests in wickra-data. Here we
|
||||
// only assert the binding's error paths, which need no network.
|
||||
public class BinanceFeedTests
|
||||
{
|
||||
[Fact]
|
||||
public void RejectsUnknownInterval()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
new BinanceFeed("BTCUSDT", (BinanceInterval)99));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RejectsEmptySymbols()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
new BinanceFeed("", BinanceInterval.OneMinute));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RejectsUnreachableEndpoint()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
new BinanceFeed("BTCUSDT", BinanceInterval.OneMinute, "ws://127.0.0.1:1"));
|
||||
}
|
||||
|
||||
// The REST fetcher's parse/HTTP success path is covered by the Rust
|
||||
// mock-HTTP-server tests; here we only assert the binding's error paths.
|
||||
[Fact]
|
||||
public void FetchKlinesRejectsUnknownInterval()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
BinanceFeed.FetchKlines("BTCUSDT", (BinanceInterval)99, 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FetchKlinesRejectsZeroLimit()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
BinanceFeed.FetchKlines("BTCUSDT", BinanceInterval.OneHour, 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FetchKlinesSurfacesUnreachableEndpoint()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
BinanceFeed.FetchKlines("BTCUSDT", BinanceInterval.OneHour, 1, baseUrl: "http://127.0.0.1:1"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using Wickra;
|
||||
using Xunit;
|
||||
|
||||
// Cross-language data-layer parity: replay the shared golden tick stream through
|
||||
// the TickAggregator and check the candles against the Rust reference, with and
|
||||
// without gap filling.
|
||||
public class DataLayerTests
|
||||
{
|
||||
private static string GoldenDir([System.Runtime.CompilerServices.CallerFilePath] string file = "") =>
|
||||
Path.GetFullPath(Path.Combine(Path.GetDirectoryName(file)!, "..", "..", "..", "testdata", "golden"));
|
||||
|
||||
private static double[][] Read(string name)
|
||||
{
|
||||
var lines = File.ReadAllLines(Path.Combine(GoldenDir(), name + ".csv"));
|
||||
var rows = new List<double[]>();
|
||||
for (var i = 1; i < lines.Length; i++)
|
||||
{
|
||||
if (lines[i].Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
rows.Add(Array.ConvertAll(lines[i].Split(','), s => double.Parse(s, CultureInfo.InvariantCulture)));
|
||||
}
|
||||
return rows.ToArray();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResamplerMatchesGolden()
|
||||
{
|
||||
var input = Read("input"); // open,high,low,close,volume (timestamp = row index)
|
||||
using var r = new Resampler(5);
|
||||
var got = new List<double[]>();
|
||||
for (var i = 0; i < input.Length; i++)
|
||||
{
|
||||
var c = r.Update(input[i][0], input[i][1], input[i][2], input[i][3], input[i][4], i);
|
||||
if (c.HasValue)
|
||||
{
|
||||
got.Add(new[] { c.Value.Open, c.Value.High, c.Value.Low, c.Value.Close, c.Value.Volume, (double)c.Value.Timestamp });
|
||||
}
|
||||
}
|
||||
var f = r.Flush();
|
||||
if (f.HasValue)
|
||||
{
|
||||
got.Add(new[] { f.Value.Open, f.Value.High, f.Value.Low, f.Value.Close, f.Value.Volume, (double)f.Value.Timestamp });
|
||||
}
|
||||
var want = Read("data_resampled");
|
||||
Assert.Equal(want.Length, got.Count);
|
||||
for (var i = 0; i < got.Count; i++)
|
||||
{
|
||||
for (var j = 0; j < 6; j++)
|
||||
{
|
||||
var tol = 1e-9 * Math.Max(1, Math.Abs(want[i][j]));
|
||||
Assert.True(Math.Abs(got[i][j] - want[i][j]) <= tol, $"resample row {i} col {j}: {got[i][j]} vs {want[i][j]}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CandleReaderMatchesGolden()
|
||||
{
|
||||
var csv = File.ReadAllText(Path.Combine(GoldenDir(), "data_csv.csv"));
|
||||
using var reader = new CandleReader(csv);
|
||||
var candles = reader.Read();
|
||||
var want = Read("data_csv_candles");
|
||||
Assert.Equal(want.Length, candles.Length);
|
||||
for (var i = 0; i < candles.Length; i++)
|
||||
{
|
||||
var c = candles[i];
|
||||
var got = new[] { c.Open, c.High, c.Low, c.Close, c.Volume, (double)c.Timestamp };
|
||||
for (var j = 0; j < 6; j++)
|
||||
{
|
||||
var tol = 1e-9 * Math.Max(1, Math.Abs(want[i][j]));
|
||||
Assert.True(Math.Abs(got[j] - want[i][j]) <= tol, $"candle reader row {i} col {j}: {got[j]} vs {want[i][j]}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false, "data_candles")]
|
||||
[InlineData(true, "data_candles_gap")]
|
||||
public void TickAggregatorMatchesGolden(bool gapFill, string fixture)
|
||||
{
|
||||
var ticks = Read("data_ticks");
|
||||
using var agg = new TickAggregator(1000, gapFill);
|
||||
var got = new List<double[]>();
|
||||
foreach (var t in ticks)
|
||||
{
|
||||
foreach (var c in agg.Push(t[0], t[1], (long)t[2]))
|
||||
{
|
||||
got.Add(new[] { c.Open, c.High, c.Low, c.Close, c.Volume, (double)c.Timestamp });
|
||||
}
|
||||
}
|
||||
var want = Read(fixture);
|
||||
Assert.Equal(want.Length, got.Count);
|
||||
for (var i = 0; i < got.Count; i++)
|
||||
{
|
||||
for (var j = 0; j < 6; j++)
|
||||
{
|
||||
var tol = 1e-9 * Math.Max(1, Math.Abs(want[i][j]));
|
||||
Assert.True(
|
||||
Math.Abs(got[i][j] - want[i][j]) <= tol,
|
||||
$"{fixture} row {i} col {j}: {got[i][j]} vs {want[i][j]}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -159,4 +159,58 @@ public class GoldenTests
|
||||
AssertClose(got.Value.Adx, Cell(e[2]), i, "adx.adx");
|
||||
}
|
||||
}
|
||||
|
||||
// --- the four de-duplicated indicators ------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Candle_AdOscillator_MatchesGolden()
|
||||
{
|
||||
var input = Input();
|
||||
var expected = ReadCsv("ad_oscillator");
|
||||
using var ad = new AdOscillator();
|
||||
for (var i = 0; i < input.Length; i++)
|
||||
{
|
||||
var (o, h, l, c, v) = (input[i][0], input[i][1], input[i][2], input[i][3], input[i][4]);
|
||||
AssertClose(ad.Update(o, h, l, c, v, i), Cell(expected[i][0]), i, "ad_oscillator");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Candle_IntradayIntensity_MatchesGolden()
|
||||
{
|
||||
var input = Input();
|
||||
var expected = ReadCsv("intraday_intensity");
|
||||
using var ii = new IntradayIntensity();
|
||||
for (var i = 0; i < input.Length; i++)
|
||||
{
|
||||
var (o, h, l, c, v) = (input[i][0], input[i][1], input[i][2], input[i][3], input[i][4]);
|
||||
AssertClose(ii.Update(o, h, l, c, v, i), Cell(expected[i][0]), i, "intraday_intensity");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Candle_AwesomeOscillatorHistogram_MatchesGolden()
|
||||
{
|
||||
var input = Input();
|
||||
var expected = ReadCsv("awesome_oscillator_histogram");
|
||||
using var aoh = new AwesomeOscillatorHistogram(5, 34, 1);
|
||||
for (var i = 0; i < input.Length; i++)
|
||||
{
|
||||
var (o, h, l, c, v) = (input[i][0], input[i][1], input[i][2], input[i][3], input[i][4]);
|
||||
AssertClose(aoh.Update(o, h, l, c, v, i), Cell(expected[i][0]), i, "awesome_oscillator_histogram");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Scalar_AverageDrawdown_MatchesGolden()
|
||||
{
|
||||
var input = Input();
|
||||
var expected = ReadCsv("average_drawdown");
|
||||
using var avg = new AverageDrawdown(20);
|
||||
for (var i = 0; i < input.Length; i++)
|
||||
{
|
||||
// generator fed the close column as the equity-curve sample.
|
||||
AssertClose(avg.Update(input[i][3]), Cell(expected[i][0]), i, "average_drawdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,7 @@
|
||||
|
||||
<!-- NuGet package metadata -->
|
||||
<PackageId>Wickra</PackageId>
|
||||
<Version>0.9.1</Version>
|
||||
<Version>0.9.7</Version>
|
||||
<Authors>kingchenc</Authors>
|
||||
<Description>High-performance streaming technical-analysis indicators (514 indicators) for .NET, backed by the native Rust core via the Wickra C ABI.</Description>
|
||||
<PackageLicenseExpression>MIT OR Apache-2.0</PackageLicenseExpression>
|
||||
|
||||
@@ -23,6 +23,13 @@ internal static class WickraNative
|
||||
// Any exported symbol works as a fingerprint; sma_new exists in every build.
|
||||
private const string SentinelSymbol = "wickra_sma_new";
|
||||
|
||||
// CA2255 warns against [ModuleInitializer] in libraries, but registering the
|
||||
// native-library resolver before any P/Invoke runs is exactly the advanced
|
||||
// scenario the attribute exists for: a static constructor would run too late
|
||||
// (only on first access to this type), letting the default resolver fail first.
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage(
|
||||
"Usage", "CA2255:The 'ModuleInitializer' attribute should not be used in libraries",
|
||||
Justification = "The DllImport resolver must be registered before the first P/Invoke; a static constructor would run too late.")]
|
||||
[ModuleInitializer]
|
||||
internal static void Register()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
"""Generate Wickra.Tests/GoldenAllTests.g.cs: a value-parity test that replays
|
||||
the shared golden input through every one of the 514 C# indicators and checks
|
||||
output bit-for-bit against the Rust reference fixtures g_<Canonical>.csv.
|
||||
|
||||
Run from repo root: python bindings/csharp/gen_golden_test.py
|
||||
"""
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
G = os.path.join(ROOT, "testdata", "golden")
|
||||
GEN = open(os.path.join(ROOT, "bindings", "csharp", "Wickra", "Generated", "Indicators.g.cs"), encoding="utf-8").read()
|
||||
|
||||
# Canonical core Indicator::name() per indicator, shared across every binding.
|
||||
NAMES = json.load(open(os.path.join(G, "names.json")))
|
||||
|
||||
# C# constructor parameter types per class.
|
||||
ctor_types = {}
|
||||
cur = None
|
||||
for line in GEN.splitlines():
|
||||
m = re.match(r"public sealed class (\w+)", line)
|
||||
if m:
|
||||
cur = m.group(1)
|
||||
continue
|
||||
if cur:
|
||||
cm = re.match(r"\s*public %s\(([^)]*)\)" % re.escape(cur), line)
|
||||
if cm:
|
||||
ps = cm.group(1).strip()
|
||||
types = [p.strip().rsplit(" ", 1)[0].strip() for p in ps.split(",")] if ps else []
|
||||
ctor_types[cur] = types
|
||||
cur = None
|
||||
|
||||
# Unified archetype + params, keyed by canonical (== C# class name).
|
||||
spec = {}
|
||||
for e in json.load(open(os.path.join(G, "scalar_manifest.json"))):
|
||||
arch = {"f64": "scalar_f64", "Candle": "scalar_candle", "(f64, f64)": "pairwise"}[e["input"]]
|
||||
spec[e["canonical"]] = {"arch": arch, "params": e["params"]}
|
||||
for e in json.load(open(os.path.join(G, "multi_manifest.json"))):
|
||||
arch = {"f64": "multi_f64", "Candle": "multi_candle", "(f64, f64)": "multi_pairwise"}[e["input"]]
|
||||
spec[e["canonical"]] = {"arch": arch, "params": e["params"], "n": e["n"]}
|
||||
ex = json.load(open(os.path.join(G, "exotic_manifest.json")))
|
||||
for e in ex["deriv"]:
|
||||
spec[e["canonical"]] = {"arch": "deriv_multi" if "n" in e else "deriv", "params": e["params"], "n": e.get("n")}
|
||||
for e in ex["cross"]:
|
||||
spec[e["canonical"]] = {"arch": "cross", "params": e["params"]}
|
||||
for e in ex["trade"]:
|
||||
spec[e["canonical"]] = {"arch": "trade", "params": e["params"]}
|
||||
for e in ex["trademid"]:
|
||||
spec[e["canonical"]] = {"arch": "trademid", "params": e["params"]}
|
||||
for e in ex["ob"]:
|
||||
spec[e["canonical"]] = {"arch": "ob", "params": e["params"]}
|
||||
for e in json.load(open(os.path.join(G, "profile_manifest.json"))):
|
||||
spec[e["canonical"]] = {"arch": "profile_" + e["kind"], "params": e["params"], "width": e["width"]}
|
||||
for e in json.load(open(os.path.join(G, "bars_manifest.json"))):
|
||||
arch = "footprint" if e["canonical"] == "Footprint" else "bars_" + e["feed"]
|
||||
spec[e["canonical"]] = {"arch": arch, "params": e["params"]}
|
||||
|
||||
canons = sorted(os.path.basename(f)[2:-4] for f in glob.glob(os.path.join(G, "g_*.csv")))
|
||||
|
||||
|
||||
def lit(value, cstype):
|
||||
if cstype == "int":
|
||||
return str(int(round(value)))
|
||||
if cstype == "uint":
|
||||
return f"{int(round(value))}u"
|
||||
if cstype == "byte":
|
||||
return f"(byte){int(round(value))}"
|
||||
f = float(value)
|
||||
s = repr(f)
|
||||
return s if ("." in s or "e" in s or "E" in s) else s + ".0"
|
||||
|
||||
|
||||
def ctor_call(canon):
|
||||
types = ctor_types.get(canon, [])
|
||||
vals = spec[canon]["params"]
|
||||
args = ", ".join(lit(v, t) for v, t in zip(vals, types))
|
||||
return f"new Wickra.{canon}({args})"
|
||||
|
||||
|
||||
def block(canon):
|
||||
s = spec[canon]
|
||||
a = s["arch"]
|
||||
L = [f" [Fact]", f" public void Golden_{canon}()", " {"]
|
||||
L.append(f" using var ind = {ctor_call(canon)};")
|
||||
L.append(f" Assert.Equal({json.dumps(NAMES[canon])}, ind.Name());")
|
||||
L.append(" var got = new List<double[]>();")
|
||||
L.append(" for (var i = 0; i < Rows.Length; i++)")
|
||||
L.append(" {")
|
||||
L.append(" var r = Rows[i];")
|
||||
if a == "scalar_f64":
|
||||
L.append(" got.Add(new[] { ind.Update(r[3]) });")
|
||||
elif a == "pairwise":
|
||||
L.append(" got.Add(new[] { ind.Update(r[3], r[0]) });")
|
||||
elif a == "scalar_candle":
|
||||
L.append(" got.Add(new[] { ind.Update(r[0], r[1], r[2], r[3], r[4], i) });")
|
||||
elif a == "trade":
|
||||
L.append(" got.Add(new[] { ind.Update(r[3], r[4], r[3] >= r[0], i) });")
|
||||
elif a == "trademid":
|
||||
L.append(" got.Add(new[] { ind.Update(r[3], r[4], r[3] >= r[0], i, (r[1] + r[2]) / 2) });")
|
||||
elif a == "ob":
|
||||
L.append(" var (bp, bs, ap, asz) = ObLists(r);")
|
||||
L.append(" got.Add(new[] { ind.Update(bp, bs, ap, asz) });")
|
||||
elif a == "deriv":
|
||||
L.append(" var d = DerivFields(r);")
|
||||
L.append(" got.Add(new[] { ind.Update(d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], i) });")
|
||||
elif a == "deriv_multi":
|
||||
L.append(" var d = DerivFields(r);")
|
||||
L.append(" got.Add(FlattenNullable(ind.Update(d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], i), %d));" % s["n"])
|
||||
elif a == "cross":
|
||||
L.append(" var (ch, vo, nh, nl, am, ob) = CrossLists(r);")
|
||||
L.append(" got.Add(new[] { ind.Update(ch, vo, nh, nl, am, ob, i) });")
|
||||
elif a == "multi_f64":
|
||||
L.append(" got.Add(FlattenNullable(ind.Update(r[3]), %d));" % s["n"])
|
||||
elif a == "multi_pairwise":
|
||||
L.append(" got.Add(FlattenNullable(ind.Update(r[3], r[0]), %d));" % s["n"])
|
||||
elif a == "multi_candle":
|
||||
L.append(" got.Add(FlattenNullable(ind.Update(r[0], r[1], r[2], r[3], r[4], i), %d));" % s["n"])
|
||||
elif a == "profile_bins":
|
||||
L.append(" var bins = ind.Update(r[0], r[1], r[2], r[3], r[4], i);")
|
||||
L.append(" got.Add(bins ?? NanRow(%d));" % s["width"])
|
||||
elif a == "profile_pricebins":
|
||||
L.append(" got.Add(FlattenNullable(ind.Update(r[0], r[1], r[2], r[3], r[4], i), %d));" % s["width"])
|
||||
elif a == "bars_close":
|
||||
L.append(" got.Add(FlattenBars(ind.Update(r[3], r[3], r[3], r[3], 1.0, 0)));")
|
||||
elif a == "bars_candle4":
|
||||
L.append(" got.Add(FlattenBars(ind.Update(r[0], r[1], r[2], r[3], 1.0, 0)));")
|
||||
elif a == "bars_candle5":
|
||||
L.append(" got.Add(FlattenBars(ind.Update(r[0], r[1], r[2], r[3], r[4], 0)));")
|
||||
elif a == "footprint":
|
||||
L.append(" got.Add(FlattenBars(ind.Update(r[3], r[4], r[3] >= r[0], i)));")
|
||||
else:
|
||||
raise SystemExit("arch " + a)
|
||||
L.append(" }")
|
||||
L.append(f' Compare("{canon}", got);')
|
||||
L.append(" }")
|
||||
return "\n".join(L)
|
||||
|
||||
|
||||
HEADER = '''// <auto-generated>
|
||||
// Generated by gen_golden_test.py. DO NOT EDIT.
|
||||
//
|
||||
// Value-parity for every one of the 514 C# indicators: the shared golden input
|
||||
// is replayed through each one and checked bit-for-bit against the Rust
|
||||
// reference fixtures testdata/golden/g_<Canonical>.csv. Multi-output, profile
|
||||
// and bar shapes are flattened by reflection so one comparator covers all
|
||||
// archetypes. Regenerate with: python bindings/csharp/gen_golden_test.py
|
||||
// </auto-generated>
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Xunit;
|
||||
|
||||
namespace Wickra.Tests;
|
||||
|
||||
public class GoldenAllTests
|
||||
{
|
||||
private const double Tol = 1e-6;
|
||||
|
||||
private static readonly double[][] Rows = LoadInput();
|
||||
|
||||
private static string GoldenDir([System.Runtime.CompilerServices.CallerFilePath] string file = "") =>
|
||||
Path.GetFullPath(Path.Combine(Path.GetDirectoryName(file)!, "..", "..", "..", "testdata", "golden"));
|
||||
|
||||
private static double Cell(string s) =>
|
||||
s == "nan" ? double.NaN
|
||||
: s == "inf" ? double.PositiveInfinity
|
||||
: s == "-inf" ? double.NegativeInfinity
|
||||
: double.Parse(s, CultureInfo.InvariantCulture);
|
||||
|
||||
private static double[][] LoadInput()
|
||||
{
|
||||
var lines = File.ReadAllLines(Path.Combine(GoldenDir(), "input.csv"));
|
||||
return lines.Skip(1).Where(l => l.Length > 0)
|
||||
.Select(l => l.Split(',').Select(x => double.Parse(x, CultureInfo.InvariantCulture)).ToArray())
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
// Keep blank lines (a candle on which no bar closed) so rows stay aligned.
|
||||
private static double[]?[] ReadFixture(string name)
|
||||
{
|
||||
var lines = File.ReadAllLines(Path.Combine(GoldenDir(), "g_" + name + ".csv"));
|
||||
return lines.Skip(1).Select(l => l.Length == 0 ? Array.Empty<double>() : l.Split(',').Select(Cell).ToArray()).ToArray();
|
||||
}
|
||||
|
||||
private static double[] NanRow(int n)
|
||||
{
|
||||
var r = new double[n];
|
||||
for (var i = 0; i < n; i++) r[i] = double.NaN;
|
||||
return r;
|
||||
}
|
||||
|
||||
private static double[] FlattenStruct(object o)
|
||||
{
|
||||
var props = o.GetType()
|
||||
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.OrderBy(p => p.MetadataToken);
|
||||
var list = new List<double>();
|
||||
foreach (var p in props)
|
||||
{
|
||||
var v = p.GetValue(o);
|
||||
switch (v)
|
||||
{
|
||||
case double d: list.Add(d); break;
|
||||
case float f: list.Add(f); break;
|
||||
case long l: list.Add(l); break;
|
||||
case int n: list.Add(n); break;
|
||||
case double[] arr: list.AddRange(arr); break;
|
||||
}
|
||||
}
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
private static double[] FlattenNullable<T>(T? value, int width) where T : struct =>
|
||||
value.HasValue ? FlattenStruct(value.Value) : NanRow(width);
|
||||
|
||||
private static double[] FlattenBars<T>(T[] bars)
|
||||
{
|
||||
var list = new List<double>();
|
||||
foreach (var bar in bars) list.AddRange(FlattenStruct(bar!));
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
private static double[] DerivFields(double[] r)
|
||||
{
|
||||
double o = r[0], h = r[1], l = r[2], c = r[3], v = r[4];
|
||||
return new[]
|
||||
{
|
||||
(c - o) / c * 0.01, c, c - 0.5, c + 1.0, v * 10.0, v * 0.6, v * 0.4,
|
||||
v * 0.55, v * 0.45, h - c, c - l,
|
||||
};
|
||||
}
|
||||
|
||||
private static (double[], double[], bool[], bool[], bool[], bool[]) CrossLists(double[] r)
|
||||
{
|
||||
double o = r[0], c = r[3], v = r[4];
|
||||
var change = new double[5];
|
||||
var volume = new double[5];
|
||||
var nh = new bool[5];
|
||||
var nl = new bool[5];
|
||||
var am = new bool[5];
|
||||
var ob = new bool[5];
|
||||
for (var j = 0; j < 5; j++)
|
||||
{
|
||||
change[j] = (c - o) + j;
|
||||
volume[j] = v + j * 10.0;
|
||||
nh[j] = j % 2 == 0;
|
||||
nl[j] = j % 3 == 0;
|
||||
am[j] = j % 2 == 0;
|
||||
ob[j] = j % 3 == 0;
|
||||
}
|
||||
return (change, volume, nh, nl, am, ob);
|
||||
}
|
||||
|
||||
private static (double[], double[], double[], double[]) ObLists(double[] r)
|
||||
{
|
||||
double c = r[3], v = r[4];
|
||||
var bp = new double[5];
|
||||
var bs = new double[5];
|
||||
var ap = new double[5];
|
||||
var asz = new double[5];
|
||||
for (var k = 0; k < 5; k++)
|
||||
{
|
||||
var kf = k + 1;
|
||||
bp[k] = c - 0.1 * kf;
|
||||
bs[k] = v / kf;
|
||||
ap[k] = c + 0.1 * kf;
|
||||
asz[k] = v * 0.9 / kf;
|
||||
}
|
||||
return (bp, bs, ap, asz);
|
||||
}
|
||||
|
||||
private static void Compare(string name, List<double[]> got)
|
||||
{
|
||||
var exp = ReadFixture(name);
|
||||
Assert.True(exp.Length == got.Count, $"{name}: {exp.Length} fixture rows vs {got.Count} computed");
|
||||
for (var i = 0; i < exp.Length; i++)
|
||||
{
|
||||
var want = exp[i]!;
|
||||
var g = got[i];
|
||||
Assert.True(want.Length == g.Length, $"{name} row {i}: arity {g.Length} vs {want.Length}");
|
||||
for (var k = 0; k < want.Length; k++)
|
||||
{
|
||||
var w = want[k];
|
||||
if (double.IsNaN(w)) { Assert.True(double.IsNaN(g[k]), $"{name} row {i} col {k}: want NaN got {g[k]}"); continue; }
|
||||
if (double.IsInfinity(w)) { Assert.True(double.IsInfinity(g[k]) && Math.Sign(g[k]) == Math.Sign(w), $"{name} row {i} col {k}: want {w} got {g[k]}"); continue; }
|
||||
var tol = Tol * Math.Max(1.0, Math.Abs(w));
|
||||
Assert.True(Math.Abs(g[k] - w) <= tol, $"{name} row {i} col {k}: got {g[k]} want {w}");
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
out = [HEADER]
|
||||
for canon in canons:
|
||||
out.append(block(canon))
|
||||
out.append("}")
|
||||
open(os.path.join(ROOT, "bindings", "csharp", "Wickra.Tests", "GoldenAllTests.g.cs"), "w", encoding="utf-8").write("\n".join(out) + "\n")
|
||||
print("generated GoldenAllTests.g.cs with", len(canons), "indicators")
|
||||
@@ -0,0 +1,35 @@
|
||||
package wickra
|
||||
|
||||
import "testing"
|
||||
|
||||
// The live Binance feed's connect → read → reconnect pipeline is covered
|
||||
// deterministically by the Rust mock-WS-server tests in wickra-data. Here we
|
||||
// only assert the binding's error paths, which need no network: a bad interval,
|
||||
// an empty symbol list, and an unreachable endpoint must all surface as an
|
||||
// error rather than a usable handle.
|
||||
func TestBinanceFeedRejectsBadParams(t *testing.T) {
|
||||
if _, err := NewBinanceFeed("BTCUSDT", BinanceInterval(99), ""); err == nil {
|
||||
t.Fatal("expected an error for an unknown interval code")
|
||||
}
|
||||
if _, err := NewBinanceFeed("", OneMinute, ""); err == nil {
|
||||
t.Fatal("expected an error for an empty symbol list")
|
||||
}
|
||||
if _, err := NewBinanceFeed("BTCUSDT", OneMinute, "ws://127.0.0.1:1"); err == nil {
|
||||
t.Fatal("expected an error connecting to an unreachable endpoint")
|
||||
}
|
||||
}
|
||||
|
||||
// The REST fetcher's parse/HTTP success path is covered by the Rust
|
||||
// mock-HTTP-server tests in wickra-data; here we only assert the binding's
|
||||
// error paths, which need no reachable network.
|
||||
func TestFetchBinanceKlinesRejectsBadParams(t *testing.T) {
|
||||
if _, err := FetchBinanceKlines("BTCUSDT", BinanceInterval(99), 1, -1, -1, ""); err == nil {
|
||||
t.Fatal("expected an error for an unknown interval code")
|
||||
}
|
||||
if _, err := FetchBinanceKlines("BTCUSDT", OneHour, 0, -1, -1, ""); err == nil {
|
||||
t.Fatal("expected an error for a zero limit")
|
||||
}
|
||||
if _, err := FetchBinanceKlines("BTCUSDT", OneHour, 1, -1, -1, "http://127.0.0.1:1"); err == nil {
|
||||
t.Fatal("expected an error connecting to an unreachable endpoint")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package wickra
|
||||
|
||||
import (
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Cross-language data-layer parity: replay the shared golden tick stream through
|
||||
// the TickAggregator and assert the candles match the Rust reference, with and
|
||||
// without gap filling. Fixtures are generated by
|
||||
// `cargo run -p wickra-examples --bin gen_golden`.
|
||||
|
||||
func dataParseF(t *testing.T, s string) float64 {
|
||||
t.Helper()
|
||||
v, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
t.Fatalf("parse %q: %v", s, err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func TestResamplerGolden(t *testing.T) {
|
||||
input := readGolden(t, "input") // open,high,low,close,volume (timestamp = row index)
|
||||
r, err := NewResampler(5)
|
||||
if err != nil {
|
||||
t.Fatalf("new: %v", err)
|
||||
}
|
||||
var got [][6]float64
|
||||
for i, row := range input {
|
||||
o, h, l, c, v := dataParseF(t, row[0]), dataParseF(t, row[1]), dataParseF(t, row[2]), dataParseF(t, row[3]), dataParseF(t, row[4])
|
||||
if k, ok := r.Update(o, h, l, c, v, int64(i)); ok {
|
||||
got = append(got, [6]float64{k.Open, k.High, k.Low, k.Close, k.Volume, float64(k.Timestamp)})
|
||||
}
|
||||
}
|
||||
if k, ok := r.Flush(); ok {
|
||||
got = append(got, [6]float64{k.Open, k.High, k.Low, k.Close, k.Volume, float64(k.Timestamp)})
|
||||
}
|
||||
r.Close()
|
||||
want := readGolden(t, "data_resampled")
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("resample: %d candles vs %d", len(got), len(want))
|
||||
}
|
||||
for i := range got {
|
||||
for j := 0; j < 6; j++ {
|
||||
w := dataParseF(t, want[i][j])
|
||||
if math.Abs(got[i][j]-w) > 1e-9*math.Max(1, math.Abs(w)) {
|
||||
t.Errorf("resample row %d col %d: %v vs %v", i, j, got[i][j], w)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCandleReaderGolden(t *testing.T) {
|
||||
csv, err := os.ReadFile("../../testdata/golden/data_csv.csv")
|
||||
if err != nil {
|
||||
t.Fatalf("read data_csv.csv: %v", err)
|
||||
}
|
||||
r, err := NewCandleReader(string(csv))
|
||||
if err != nil {
|
||||
t.Fatalf("new: %v", err)
|
||||
}
|
||||
defer r.Close()
|
||||
candles := r.Read()
|
||||
got := make([][6]float64, len(candles))
|
||||
for i, c := range candles {
|
||||
got[i] = [6]float64{c.Open, c.High, c.Low, c.Close, c.Volume, float64(c.Timestamp)}
|
||||
}
|
||||
want := readGolden(t, "data_csv_candles")
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("candle reader: %d candles vs %d", len(got), len(want))
|
||||
}
|
||||
for i := range got {
|
||||
for j := 0; j < 6; j++ {
|
||||
w := dataParseF(t, want[i][j])
|
||||
if math.Abs(got[i][j]-w) > 1e-9*math.Max(1, math.Abs(w)) {
|
||||
t.Errorf("candle reader row %d col %d: %v vs %v", i, j, got[i][j], w)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTickAggregatorGolden(t *testing.T) {
|
||||
ticks := readGolden(t, "data_ticks")
|
||||
cases := []struct {
|
||||
gap bool
|
||||
fixture string
|
||||
}{
|
||||
{false, "data_candles"},
|
||||
{true, "data_candles_gap"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
agg, err := NewTickAggregator(1000, c.gap)
|
||||
if err != nil {
|
||||
t.Fatalf("new: %v", err)
|
||||
}
|
||||
var got [][6]float64
|
||||
for _, r := range ticks {
|
||||
price, size, ts := dataParseF(t, r[0]), dataParseF(t, r[1]), int64(dataParseF(t, r[2]))
|
||||
for _, k := range agg.Push(price, size, ts) {
|
||||
got = append(got, [6]float64{k.Open, k.High, k.Low, k.Close, k.Volume, float64(k.Timestamp)})
|
||||
}
|
||||
}
|
||||
agg.Close()
|
||||
want := readGolden(t, c.fixture)
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("%s: %d candles vs %d", c.fixture, len(got), len(want))
|
||||
}
|
||||
for i := range got {
|
||||
for j := 0; j < 6; j++ {
|
||||
w := dataParseF(t, want[i][j])
|
||||
if math.Abs(got[i][j]-w) > 1e-9*math.Max(1, math.Abs(w)) {
|
||||
t.Errorf("%s row %d col %d: %v vs %v", c.fixture, i, j, got[i][j], w)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
"""Generate golden_all_test.go: a value-parity test that replays the shared
|
||||
golden input through every one of the 514 Go indicators and checks output
|
||||
bit-for-bit against the Rust-generated g_<Canonical>.csv fixtures.
|
||||
|
||||
Run from repo root: python bindings/go/gen_golden_test.py
|
||||
"""
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
G = os.path.join(ROOT, "testdata", "golden")
|
||||
GEN = open(os.path.join(ROOT, "bindings", "go", "indicators_gen.go"), encoding="utf-8").read()
|
||||
|
||||
# Canonical core Indicator::name() per indicator, shared across every binding.
|
||||
NAMES = json.load(open(os.path.join(G, "names.json")))
|
||||
|
||||
# Go constructor parameter types, keyed by canonical (== Go type name).
|
||||
ctor_types = {}
|
||||
for m in re.finditer(r"func New(\w+)\(([^)]*)\)\s*\(\*\w+, error\)", GEN):
|
||||
name, ps = m.group(1), m.group(2).strip()
|
||||
types = []
|
||||
if ps:
|
||||
for p in ps.split(","):
|
||||
p = p.strip()
|
||||
_, _, ty = p.partition(" ")
|
||||
types.append(ty.strip())
|
||||
ctor_types[name] = types
|
||||
|
||||
# Unified archetype + params, keyed by canonical.
|
||||
spec = {} # canon -> dict(arch, params, width?, n?)
|
||||
scal = json.load(open(os.path.join(G, "scalar_manifest.json")))
|
||||
for e in scal:
|
||||
inp = e["input"]
|
||||
arch = {"f64": "scalar_f64", "Candle": "scalar_candle", "(f64, f64)": "pairwise"}[inp]
|
||||
spec[e["canonical"]] = {"arch": arch, "params": e["params"]}
|
||||
for e in json.load(open(os.path.join(G, "multi_manifest.json"))):
|
||||
inp = e["input"]
|
||||
arch = {"f64": "multi_f64", "Candle": "multi_candle", "(f64, f64)": "multi_pairwise"}[inp]
|
||||
spec[e["canonical"]] = {"arch": arch, "params": e["params"], "n": e["n"]}
|
||||
ex = json.load(open(os.path.join(G, "exotic_manifest.json")))
|
||||
for e in ex["deriv"]:
|
||||
spec[e["canonical"]] = {"arch": "deriv_multi" if "n" in e else "deriv", "params": e["params"], "n": e.get("n")}
|
||||
for e in ex["cross"]:
|
||||
spec[e["canonical"]] = {"arch": "cross", "params": e["params"]}
|
||||
for e in ex["trade"]:
|
||||
spec[e["canonical"]] = {"arch": "trade", "params": e["params"]}
|
||||
for e in ex["trademid"]:
|
||||
spec[e["canonical"]] = {"arch": "trademid", "params": e["params"]}
|
||||
for e in ex["ob"]:
|
||||
spec[e["canonical"]] = {"arch": "ob", "params": e["params"]}
|
||||
for e in json.load(open(os.path.join(G, "profile_manifest.json"))):
|
||||
spec[e["canonical"]] = {"arch": "profile_" + e["kind"], "params": e["params"], "width": e["width"]}
|
||||
for e in json.load(open(os.path.join(G, "bars_manifest.json"))):
|
||||
arch = "footprint" if e["canonical"] == "Footprint" else "bars_" + e["feed"]
|
||||
spec[e["canonical"]] = {"arch": arch, "params": e["params"]}
|
||||
|
||||
canons = sorted(os.path.basename(f)[2:-4] for f in glob.glob(os.path.join(G, "g_*.csv")))
|
||||
|
||||
|
||||
def go_param(value, gotype):
|
||||
intlike = gotype in ("int", "int32", "int64", "uint", "uintptr", "usize")
|
||||
if intlike:
|
||||
return str(int(round(value)))
|
||||
# float64
|
||||
return repr(float(value)) if "." in repr(float(value)) or "e" in repr(float(value)) else f"{float(value)}"
|
||||
|
||||
|
||||
def ctor_call(canon):
|
||||
types = ctor_types.get(canon, [])
|
||||
vals = spec[canon]["params"]
|
||||
args = ", ".join(go_param(v, t) for v, t in zip(vals, types))
|
||||
return f"New{canon}({args})"
|
||||
|
||||
|
||||
# Update-call expression + output handling per archetype.
|
||||
def block(canon):
|
||||
s = spec[canon]
|
||||
a = s["arch"]
|
||||
ctor = ctor_call(canon)
|
||||
lines = [f'\tt.Run("{canon}", func(t *testing.T) {{']
|
||||
lines.append(f"\t\tind, err := {ctor}")
|
||||
lines.append('\t\tif err != nil {')
|
||||
lines.append(f'\t\t\tt.Fatalf("new {canon}: %v", err)')
|
||||
lines.append("\t\t}")
|
||||
lines.append(f'\t\tif n := ind.Name(); n != {json.dumps(NAMES[canon])} {{')
|
||||
lines.append(f'\t\t\tt.Errorf("name: got %q want %q", n, {json.dumps(NAMES[canon])})')
|
||||
lines.append("\t\t}")
|
||||
lines.append("\t\tgot := make([][]float64, len(rows))")
|
||||
lines.append("\t\tfor i, r := range rows {")
|
||||
if a == "scalar_f64":
|
||||
upd = "ind.Update(r[3])"
|
||||
lines.append(f"\t\t\tgot[i] = []float64{{{upd}}}")
|
||||
elif a == "pairwise":
|
||||
lines.append("\t\t\tgot[i] = []float64{ind.Update(r[3], r[0])}")
|
||||
elif a == "scalar_candle":
|
||||
lines.append("\t\t\tgot[i] = []float64{ind.Update(r[0], r[1], r[2], r[3], r[4], int64(i))}")
|
||||
elif a == "trade":
|
||||
lines.append("\t\t\tgot[i] = []float64{ind.Update(r[3], r[4], r[3] >= r[0], int64(i))}")
|
||||
elif a == "trademid":
|
||||
lines.append("\t\t\tgot[i] = []float64{ind.Update(r[3], r[4], r[3] >= r[0], int64(i), (r[1]+r[2])/2)}")
|
||||
elif a == "ob":
|
||||
lines.append("\t\t\tbp, bs, ap, as_ := obLists(r)")
|
||||
lines.append("\t\t\tgot[i] = []float64{ind.Update(bp, bs, ap, as_)}")
|
||||
elif a == "deriv":
|
||||
lines.append("\t\t\td := derivFields(r)")
|
||||
lines.append("\t\t\tgot[i] = []float64{ind.Update(d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], int64(i))}")
|
||||
elif a == "deriv_multi":
|
||||
lines.append("\t\t\td := derivFields(r)")
|
||||
lines.append("\t\t\tout, ok := ind.Update(d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], int64(i))")
|
||||
lines.append(f"\t\t\tgot[i] = reflectRow(out, ok, {s['n']})")
|
||||
elif a == "cross":
|
||||
lines.append("\t\t\tch, vo, nh, nl, am, ob_ := crossLists(r)")
|
||||
lines.append("\t\t\tgot[i] = []float64{ind.Update(ch, vo, nh, nl, am, ob_, int64(i))}")
|
||||
elif a in ("multi_f64",):
|
||||
lines.append("\t\t\tout, ok := ind.Update(r[3])")
|
||||
lines.append(f"\t\t\tgot[i] = reflectRow(out, ok, {s['n']})")
|
||||
elif a == "multi_pairwise":
|
||||
lines.append("\t\t\tout, ok := ind.Update(r[3], r[0])")
|
||||
lines.append(f"\t\t\tgot[i] = reflectRow(out, ok, {s['n']})")
|
||||
elif a == "multi_candle":
|
||||
lines.append("\t\t\tout, ok := ind.Update(r[0], r[1], r[2], r[3], r[4], int64(i))")
|
||||
lines.append(f"\t\t\tgot[i] = reflectRow(out, ok, {s['n']})")
|
||||
elif a == "profile_bins":
|
||||
lines.append("\t\t\tbins, ok := ind.Update(r[0], r[1], r[2], r[3], r[4], int64(i))")
|
||||
lines.append(f"\t\t\tif ok {{ got[i] = bins }} else {{ got[i] = nanRow({s['width']}) }}")
|
||||
elif a == "profile_pricebins":
|
||||
lines.append("\t\t\tout, ok := ind.Update(r[0], r[1], r[2], r[3], r[4], int64(i))")
|
||||
lines.append(f"\t\t\tgot[i] = reflectRow(out, ok, {s['width']})")
|
||||
elif a == "bars_close":
|
||||
lines.append("\t\t\tgot[i] = flattenBars(ind.Update(r[3], r[3], r[3], r[3], 1.0, 0))")
|
||||
elif a == "bars_candle4":
|
||||
lines.append("\t\t\tgot[i] = flattenBars(ind.Update(r[0], r[1], r[2], r[3], 1.0, 0))")
|
||||
elif a == "bars_candle5":
|
||||
lines.append("\t\t\tgot[i] = flattenBars(ind.Update(r[0], r[1], r[2], r[3], r[4], 0))")
|
||||
elif a == "footprint":
|
||||
lines.append("\t\t\tgot[i] = flattenBars(ind.Update(r[3], r[4], r[3] >= r[0], int64(i)))")
|
||||
else:
|
||||
raise SystemExit("unknown arch " + a)
|
||||
lines.append("\t\t}")
|
||||
lines.append(f'\t\tcompareGolden(t, "{canon}", got)')
|
||||
lines.append("\t})")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
HEADER = '''// Code generated by gen_golden_test.py. DO NOT EDIT.
|
||||
//
|
||||
// Value-parity for every one of the 514 Go indicators: the shared golden input
|
||||
// is replayed through each one and checked bit-for-bit against the Rust
|
||||
// reference fixtures testdata/golden/g_<Canonical>.csv. Multi-output, profile
|
||||
// and bar shapes are flattened by reflection so a single comparator covers all
|
||||
// archetypes. Regenerate with: python bindings/go/gen_golden_test.py
|
||||
|
||||
package wickra
|
||||
|
||||
import (
|
||||
\t"bufio"
|
||||
\t"math"
|
||||
\t"os"
|
||||
\t"reflect"
|
||||
\t"strings"
|
||||
\t"testing"
|
||||
)
|
||||
|
||||
// readGoldenRaw keeps blank lines (a candle on which no bar closed) so bar rows
|
||||
// stay aligned to the input; non-bar fixtures contain no blank lines.
|
||||
func readGoldenRaw(t *testing.T, name string) [][]string {
|
||||
\tt.Helper()
|
||||
\tf, err := os.Open("../../testdata/golden/" + name + ".csv")
|
||||
\tif err != nil {
|
||||
\t\tt.Fatalf("open %s: %v", name, err)
|
||||
\t}
|
||||
\tdefer f.Close()
|
||||
\tvar rows [][]string
|
||||
\tsc := bufio.NewScanner(f)
|
||||
\tsc.Buffer(make([]byte, 0, 1024*1024), 1024*1024)
|
||||
\tfirst := true
|
||||
\tfor sc.Scan() {
|
||||
\t\tline := sc.Text()
|
||||
\t\tif first {
|
||||
\t\t\tfirst = false
|
||||
\t\t\tcontinue
|
||||
\t\t}
|
||||
\t\tif line == "" {
|
||||
\t\t\trows = append(rows, []string{})
|
||||
\t\t\tcontinue
|
||||
\t\t}
|
||||
\t\trows = append(rows, strings.Split(line, ","))
|
||||
\t}
|
||||
\treturn rows
|
||||
}
|
||||
|
||||
func nanRow(n int) []float64 {
|
||||
\tr := make([]float64, n)
|
||||
\tfor i := range r {
|
||||
\t\tr[i] = math.NaN()
|
||||
\t}
|
||||
\treturn r
|
||||
}
|
||||
|
||||
func reflectRow(out any, ok bool, width int) []float64 {
|
||||
\tif !ok {
|
||||
\t\treturn nanRow(width)
|
||||
\t}
|
||||
\tv := reflect.ValueOf(out)
|
||||
\trow := make([]float64, 0, width)
|
||||
\tfor k := 0; k < v.NumField(); k++ {
|
||||
\t\trow = appendField(row, v.Field(k))
|
||||
\t}
|
||||
\treturn row
|
||||
}
|
||||
|
||||
func appendField(row []float64, f reflect.Value) []float64 {
|
||||
\tswitch f.Kind() {
|
||||
\tcase reflect.Float64, reflect.Float32:
|
||||
\t\treturn append(row, f.Float())
|
||||
\tcase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
\t\treturn append(row, float64(f.Int()))
|
||||
\tcase reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
\t\treturn append(row, float64(f.Uint()))
|
||||
\tcase reflect.Slice:
|
||||
\t\tfor j := 0; j < f.Len(); j++ {
|
||||
\t\t\trow = appendField(row, f.Index(j))
|
||||
\t\t}
|
||||
\t\treturn row
|
||||
\tdefault:
|
||||
\t\treturn row
|
||||
\t}
|
||||
}
|
||||
|
||||
func flattenBars(bars any) []float64 {
|
||||
\tv := reflect.ValueOf(bars)
|
||||
\trow := []float64{}
|
||||
\tfor i := 0; i < v.Len(); i++ {
|
||||
\t\tbar := v.Index(i)
|
||||
\t\tfor k := 0; k < bar.NumField(); k++ {
|
||||
\t\t\trow = appendField(row, bar.Field(k))
|
||||
\t\t}
|
||||
\t}
|
||||
\treturn row
|
||||
}
|
||||
|
||||
// Synthetic feeds derived from one OHLCV row, identical to gen_golden's Rust
|
||||
// construction (DerivativesTick / CrossSection / OrderBook).
|
||||
func derivFields(r []float64) [11]float64 {
|
||||
\to, h, l, c, v := r[0], r[1], r[2], r[3], r[4]
|
||||
\treturn [11]float64{
|
||||
\t\t(c - o) / c * 0.01, // funding_rate
|
||||
\t\tc, // mark_price
|
||||
\t\tc - 0.5, // index_price
|
||||
\t\tc + 1.0, // futures_price
|
||||
\t\tv * 10.0, // open_interest
|
||||
\t\tv * 0.6, // long_size
|
||||
\t\tv * 0.4, // short_size
|
||||
\t\tv * 0.55, // taker_buy_volume
|
||||
\t\tv * 0.45, // taker_sell_volume
|
||||
\t\th - c, // long_liquidation
|
||||
\t\tc - l, // short_liquidation
|
||||
\t}
|
||||
}
|
||||
|
||||
func crossLists(r []float64) ([]float64, []float64, []bool, []bool, []bool, []bool) {
|
||||
\to, c, v := r[0], r[3], r[4]
|
||||
\tchange := make([]float64, 5)
|
||||
\tvolume := make([]float64, 5)
|
||||
\tnewHigh := make([]bool, 5)
|
||||
\tnewLow := make([]bool, 5)
|
||||
\taboveMa := make([]bool, 5)
|
||||
\tonBuy := make([]bool, 5)
|
||||
\tfor j := 0; j < 5; j++ {
|
||||
\t\tjf := float64(j)
|
||||
\t\tchange[j] = (c - o) + jf
|
||||
\t\tvolume[j] = v + jf*10.0
|
||||
\t\tnewHigh[j] = j%2 == 0
|
||||
\t\tnewLow[j] = j%3 == 0
|
||||
\t\taboveMa[j] = j%2 == 0
|
||||
\t\tonBuy[j] = j%3 == 0
|
||||
\t}
|
||||
\treturn change, volume, newHigh, newLow, aboveMa, onBuy
|
||||
}
|
||||
|
||||
func obLists(r []float64) ([]float64, []float64, []float64, []float64) {
|
||||
\tc, v := r[3], r[4]
|
||||
\tbidPx := make([]float64, 5)
|
||||
\tbidSz := make([]float64, 5)
|
||||
\taskPx := make([]float64, 5)
|
||||
\taskSz := make([]float64, 5)
|
||||
\tfor k := 0; k < 5; k++ {
|
||||
\t\tkf := float64(k + 1)
|
||||
\t\tbidPx[k] = c - 0.1*kf
|
||||
\t\tbidSz[k] = v / kf
|
||||
\t\taskPx[k] = c + 0.1*kf
|
||||
\t\taskSz[k] = v * 0.9 / kf
|
||||
\t}
|
||||
\treturn bidPx, bidSz, askPx, askSz
|
||||
}
|
||||
|
||||
func compareGolden(t *testing.T, name string, got [][]float64) {
|
||||
\tt.Helper()
|
||||
\texp := readGoldenRaw(t, "g_"+name)
|
||||
\tif len(exp) != len(got) {
|
||||
\t\tt.Fatalf("%s: %d fixture rows vs %d computed", name, len(exp), len(got))
|
||||
\t}
|
||||
\tfor i := range exp {
|
||||
\t\tif len(exp[i]) != len(got[i]) {
|
||||
\t\t\tt.Fatalf("%s row %d: arity %d vs %d", name, i, len(got[i]), len(exp[i]))
|
||||
\t\t}
|
||||
\t\tfor k := range exp[i] {
|
||||
\t\t\twant := goldenCell(exp[i][k])
|
||||
\t\t\tg := got[i][k]
|
||||
\t\t\tif math.IsNaN(want) {
|
||||
\t\t\t\tif !math.IsNaN(g) {
|
||||
\t\t\t\t\tt.Fatalf("%s row %d col %d: want NaN got %v", name, i, k, g)
|
||||
\t\t\t\t}
|
||||
\t\t\t\tcontinue
|
||||
\t\t\t}
|
||||
\t\t\tif math.IsInf(want, 0) {
|
||||
\t\t\t\tif !math.IsInf(g, 0) || (g > 0) != (want > 0) {
|
||||
\t\t\t\t\tt.Fatalf("%s row %d col %d: want %v got %v", name, i, k, want, g)
|
||||
\t\t\t\t}
|
||||
\t\t\t\tcontinue
|
||||
\t\t\t}
|
||||
\t\t\ttol := goldenTol * math.Max(1.0, math.Abs(want))
|
||||
\t\t\tif math.Abs(g-want) > tol {
|
||||
\t\t\t\tt.Fatalf("%s row %d col %d: got %v want %v", name, i, k, g, want)
|
||||
\t\t\t}
|
||||
\t\t}
|
||||
\t}
|
||||
}
|
||||
|
||||
func TestGoldenAll(t *testing.T) {
|
||||
\trows := goldenInput(t)
|
||||
'''
|
||||
|
||||
# bars need blank-line-preserving fixture reads; reuse readGolden but it skips
|
||||
# blanks. We need a raw reader for bars and input.
|
||||
out = [HEADER]
|
||||
for canon in canons:
|
||||
out.append(block(canon))
|
||||
out.append("}")
|
||||
open(os.path.join(ROOT, "bindings", "go", "golden_all_test.go"), "w", encoding="utf-8").write("\n".join(out) + "\n")
|
||||
print("generated golden_all_test.go with", len(canons), "indicators")
|
||||
File diff suppressed because it is too large
Load Diff
@@ -189,3 +189,62 @@ func TestGoldenAdx(t *testing.T) {
|
||||
assertGoldenClose(t, out.Adx, goldenCell(exp[i][2]), i, "adx.adx")
|
||||
}
|
||||
}
|
||||
|
||||
// The four de-duplicated indicators: pin their corrected definitions against
|
||||
// the Rust reference so the Go FFI stays bit-identical.
|
||||
|
||||
func TestGoldenAdOscillator(t *testing.T) {
|
||||
input := goldenInput(t)
|
||||
exp := readGolden(t, "ad_oscillator")
|
||||
ad, err := NewAdOscillator()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ad.Close()
|
||||
for i := range input {
|
||||
got := ad.Update(input[i][0], input[i][1], input[i][2], input[i][3], input[i][4], int64(i))
|
||||
assertGoldenClose(t, got, goldenCell(exp[i][0]), i, "ad_oscillator")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoldenIntradayIntensity(t *testing.T) {
|
||||
input := goldenInput(t)
|
||||
exp := readGolden(t, "intraday_intensity")
|
||||
ii, err := NewIntradayIntensity()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ii.Close()
|
||||
for i := range input {
|
||||
got := ii.Update(input[i][0], input[i][1], input[i][2], input[i][3], input[i][4], int64(i))
|
||||
assertGoldenClose(t, got, goldenCell(exp[i][0]), i, "intraday_intensity")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoldenAwesomeOscillatorHistogram(t *testing.T) {
|
||||
input := goldenInput(t)
|
||||
exp := readGolden(t, "awesome_oscillator_histogram")
|
||||
aoh, err := NewAwesomeOscillatorHistogram(5, 34, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer aoh.Close()
|
||||
for i := range input {
|
||||
got := aoh.Update(input[i][0], input[i][1], input[i][2], input[i][3], input[i][4], int64(i))
|
||||
assertGoldenClose(t, got, goldenCell(exp[i][0]), i, "awesome_oscillator_histogram")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoldenAverageDrawdown(t *testing.T) {
|
||||
input := goldenInput(t)
|
||||
exp := readGolden(t, "average_drawdown")
|
||||
avg, err := NewAverageDrawdown(20)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer avg.Close()
|
||||
for i := range input {
|
||||
// generator fed the close column as the equity-curve sample.
|
||||
assertGoldenClose(t, avg.Update(input[i][3]), goldenCell(exp[i][0]), i, "average_drawdown")
|
||||
}
|
||||
}
|
||||
|
||||
+1116
-1
File diff suppressed because it is too large
Load Diff
+3897
-2
File diff suppressed because it is too large
Load Diff
@@ -28,3 +28,7 @@ import "errors"
|
||||
// ErrInvalidParams is returned by a New<Indicator> constructor when the native
|
||||
// constructor rejects the supplied parameters (for example a zero period).
|
||||
var ErrInvalidParams = errors.New("wickra: invalid indicator parameters")
|
||||
|
||||
// ErrFeedClosed is returned by BinanceFeed.Next once the stream has been closed
|
||||
// or has errored out and exhausted its reconnect attempts.
|
||||
var ErrFeedClosed = errors.New("wickra: binance feed closed")
|
||||
|
||||
@@ -30,14 +30,14 @@ Maven:
|
||||
<dependency>
|
||||
<groupId>org.wickra</groupId>
|
||||
<artifactId>wickra</artifactId>
|
||||
<version>0.9.1</version>
|
||||
<version>0.9.7</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Gradle:
|
||||
|
||||
```kotlin
|
||||
implementation("org.wickra:wickra:0.9.1")
|
||||
implementation("org.wickra:wickra:0.9.7")
|
||||
```
|
||||
|
||||
The native library ships prebuilt per platform (Linux, macOS, Windows — x64 and
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<dependency>
|
||||
<groupId>org.wickra</groupId>
|
||||
<artifactId>wickra</artifactId>
|
||||
<version>0.8.2</version>
|
||||
<version>0.8.8</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
"""Generate src/test/java/org/wickra/GoldenAllTest.java: a reflection-driven
|
||||
value-parity test that replays the shared golden input through every one of the
|
||||
514 Java indicators and checks output bit-for-bit against the Rust reference
|
||||
fixtures g_<Canonical>.csv. The per-indicator spec is embedded so the test has
|
||||
no JSON dependency; a single reflective runner covers all archetypes.
|
||||
|
||||
Run from repo root: python bindings/java/gen_golden_test.py
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
|
||||
ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
MAN = json.load(open(os.path.join(ROOT, "testdata", "golden", "golden_manifest.json")))
|
||||
|
||||
|
||||
def params_lit(ps):
|
||||
if not ps:
|
||||
return "new double[]{}"
|
||||
return "new double[]{" + ", ".join(repr(float(p)) for p in ps) + "}"
|
||||
|
||||
|
||||
specs = []
|
||||
for e in MAN:
|
||||
n = e.get("n", e.get("width", 0))
|
||||
specs.append(f' new Spec("{e["canonical"]}", "{e["arch"]}", {params_lit(e["params"])}, {n}),')
|
||||
specs_block = "\n".join(specs)
|
||||
|
||||
# Canonical core Indicator::name() per indicator, shared across every binding.
|
||||
NAMES = json.load(open(os.path.join(ROOT, "testdata", "golden", "names.json")))
|
||||
names_block = "\n".join(
|
||||
f" NAMES.put({json.dumps(c)}, {json.dumps(NAMES[c])});" for c in sorted(NAMES)
|
||||
)
|
||||
|
||||
TEMPLATE = '''// Code generated by gen_golden_test.py. DO NOT EDIT.
|
||||
package org.wickra;
|
||||
|
||||
import org.junit.jupiter.api.DynamicTest;
|
||||
import org.junit.jupiter.api.TestFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.RecordComponent;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
|
||||
|
||||
/**
|
||||
* Reflection-driven value parity for the whole 514-indicator catalogue: each
|
||||
* indicator is reconstructed by its class name, fed the synthetic stream derived
|
||||
* from the shared golden input (identical to gen_golden's Rust construction) and
|
||||
* checked bit-for-bit against testdata/golden/g_<Canonical>.csv. One runner
|
||||
* flattens scalar, multi-output records, profiles and bar arrays by reflection.
|
||||
*/
|
||||
class GoldenAllTest {
|
||||
private static final double TOL = 1e-6;
|
||||
|
||||
private record Spec(String canonical, String arch, double[] params, int width) {}
|
||||
|
||||
private static final Spec[] SPECS = {
|
||||
%SPECS%
|
||||
};
|
||||
|
||||
private static final java.util.Map<String, String> NAMES = new java.util.HashMap<>();
|
||||
static {
|
||||
%NAMES%
|
||||
}
|
||||
|
||||
private static Path goldenDir() {
|
||||
java.io.File d = new java.io.File("").getAbsoluteFile();
|
||||
while (d != null) {
|
||||
java.io.File g = new java.io.File(d, "testdata/golden");
|
||||
if (g.isDirectory()) {
|
||||
return g.toPath();
|
||||
}
|
||||
d = d.getParentFile();
|
||||
}
|
||||
throw new IllegalStateException("testdata/golden not found");
|
||||
}
|
||||
|
||||
private static double cell(String s) {
|
||||
return switch (s) {
|
||||
case "nan" -> Double.NaN;
|
||||
case "inf" -> Double.POSITIVE_INFINITY;
|
||||
case "-inf" -> Double.NEGATIVE_INFINITY;
|
||||
default -> Double.parseDouble(s);
|
||||
};
|
||||
}
|
||||
|
||||
private static double[][] input() throws IOException {
|
||||
List<String> lines = Files.readAllLines(goldenDir().resolve("input.csv"));
|
||||
List<double[]> rows = new ArrayList<>();
|
||||
for (int i = 1; i < lines.size(); i++) {
|
||||
if (lines.get(i).isEmpty()) continue;
|
||||
String[] p = lines.get(i).split(",");
|
||||
double[] r = new double[p.length];
|
||||
for (int j = 0; j < p.length; j++) r[j] = Double.parseDouble(p[j]);
|
||||
rows.add(r);
|
||||
}
|
||||
return rows.toArray(new double[0][]);
|
||||
}
|
||||
|
||||
// Keep blank lines (a candle on which no bar closed) so rows stay aligned.
|
||||
private static double[][] fixture(String name) throws IOException {
|
||||
List<String> lines = Files.readAllLines(goldenDir().resolve("g_" + name + ".csv"));
|
||||
List<double[]> rows = new ArrayList<>();
|
||||
for (int i = 1; i < lines.size(); i++) {
|
||||
String ln = lines.get(i);
|
||||
if (ln.isEmpty()) {
|
||||
rows.add(new double[0]);
|
||||
continue;
|
||||
}
|
||||
String[] p = ln.split(",");
|
||||
double[] r = new double[p.length];
|
||||
for (int j = 0; j < p.length; j++) r[j] = cell(p[j]);
|
||||
rows.add(r);
|
||||
}
|
||||
return rows.toArray(new double[0][]);
|
||||
}
|
||||
|
||||
private static double[] nanRow(int n) {
|
||||
double[] r = new double[n];
|
||||
java.util.Arrays.fill(r, Double.NaN);
|
||||
return r;
|
||||
}
|
||||
|
||||
private static double[] derivFields(double[] r) {
|
||||
double o = r[0], h = r[1], l = r[2], c = r[3], v = r[4];
|
||||
return new double[]{
|
||||
(c - o) / c * 0.01, c, c - 0.5, c + 1.0, v * 10.0, v * 0.6, v * 0.4,
|
||||
v * 0.55, v * 0.45, h - c, c - l,
|
||||
};
|
||||
}
|
||||
|
||||
private static double[] crossList(double[] r, int which) {
|
||||
double o = r[0], c = r[3], v = r[4];
|
||||
double[] out = new double[5];
|
||||
for (int j = 0; j < 5; j++) {
|
||||
out[j] = switch (which) {
|
||||
case 0 -> (c - o) + j;
|
||||
case 1 -> v + j * 10.0;
|
||||
case 2 -> j % 2 == 0 ? 1.0 : 0.0; // newHigh
|
||||
case 3 -> j % 3 == 0 ? 1.0 : 0.0; // newLow
|
||||
case 4 -> j % 2 == 0 ? 1.0 : 0.0; // aboveMa
|
||||
default -> j % 3 == 0 ? 1.0 : 0.0; // onBuySignal
|
||||
};
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static double[] obList(double[] r, int which) {
|
||||
double c = r[3], v = r[4];
|
||||
double[] out = new double[5];
|
||||
for (int k = 0; k < 5; k++) {
|
||||
double kf = k + 1;
|
||||
out[k] = switch (which) {
|
||||
case 0 -> c - 0.1 * kf;
|
||||
case 1 -> v / kf;
|
||||
case 2 -> c + 0.1 * kf;
|
||||
default -> v * 0.9 / kf;
|
||||
};
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static double[] flattenRecord(Object o) throws Exception {
|
||||
RecordComponent[] rc = o.getClass().getRecordComponents();
|
||||
List<Double> list = new ArrayList<>();
|
||||
for (RecordComponent c : rc) {
|
||||
Object v = c.getAccessor().invoke(o);
|
||||
if (v instanceof double[] arr) {
|
||||
for (double d : arr) list.add(d);
|
||||
} else if (v instanceof Number num) {
|
||||
list.add(num.doubleValue());
|
||||
}
|
||||
}
|
||||
double[] out = new double[list.size()];
|
||||
for (int i = 0; i < out.length; i++) out[i] = list.get(i);
|
||||
return out;
|
||||
}
|
||||
|
||||
private static double[] flattenArray(Object arr) throws Exception {
|
||||
int len = Array.getLength(arr);
|
||||
List<Double> list = new ArrayList<>();
|
||||
for (int i = 0; i < len; i++) {
|
||||
for (double d : flattenRecord(Array.get(arr, i))) list.add(d);
|
||||
}
|
||||
double[] out = new double[list.size()];
|
||||
for (int i = 0; i < out.length; i++) out[i] = list.get(i);
|
||||
return out;
|
||||
}
|
||||
|
||||
private static Object construct(Spec s) throws Exception {
|
||||
Class<?> cls = Class.forName("org.wickra." + s.canonical());
|
||||
Constructor<?> ctor = cls.getConstructors()[0];
|
||||
Class<?>[] pt = ctor.getParameterTypes();
|
||||
Object[] args = new Object[pt.length];
|
||||
for (int i = 0; i < pt.length; i++) {
|
||||
double v = s.params()[i];
|
||||
if (pt[i] == int.class) args[i] = (int) Math.round(v);
|
||||
else if (pt[i] == long.class) args[i] = (long) Math.round(v);
|
||||
else args[i] = v;
|
||||
}
|
||||
return ctor.newInstance(args);
|
||||
}
|
||||
|
||||
private static Method updateMethod(Object ind) {
|
||||
for (Method m : ind.getClass().getMethods()) {
|
||||
if (m.getName().equals("update")) return m;
|
||||
}
|
||||
throw new IllegalStateException("no update on " + ind.getClass());
|
||||
}
|
||||
|
||||
private static double[] row(Spec s, Object ind, Method upd, double[] r, int i) throws Exception {
|
||||
double o = r[0], h = r[1], l = r[2], c = r[3], v = r[4];
|
||||
Object res = switch (s.arch()) {
|
||||
case "scalar_f64", "multi_f64" -> upd.invoke(ind, c);
|
||||
case "pairwise", "multi_pairwise" -> upd.invoke(ind, c, o);
|
||||
case "scalar_candle", "multi_candle", "profile_bins", "profile_pricebins" ->
|
||||
upd.invoke(ind, o, h, l, c, v, (long) i);
|
||||
case "trade" -> upd.invoke(ind, c, v, c >= o, (long) i);
|
||||
case "trademid" -> upd.invoke(ind, c, v, c >= o, (long) i, (h + l) / 2);
|
||||
case "ob" -> upd.invoke(ind, obList(r, 0), obList(r, 1), obList(r, 2), obList(r, 3));
|
||||
case "cross" -> upd.invoke(ind, crossList(r, 0), crossList(r, 1), crossList(r, 2),
|
||||
crossList(r, 3), crossList(r, 4), crossList(r, 5), (long) i);
|
||||
case "deriv", "deriv_multi" -> {
|
||||
double[] d = derivFields(r);
|
||||
yield upd.invoke(ind, d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], (long) i);
|
||||
}
|
||||
case "bars_close" -> upd.invoke(ind, c, c, c, c, 1.0, 0L);
|
||||
case "bars_candle4" -> upd.invoke(ind, o, h, l, c, 1.0, 0L);
|
||||
case "bars_candle5" -> upd.invoke(ind, o, h, l, c, v, 0L);
|
||||
case "footprint" -> upd.invoke(ind, c, v, c >= o, (long) i);
|
||||
default -> throw new IllegalStateException("arch " + s.arch());
|
||||
};
|
||||
return switch (s.arch()) {
|
||||
case "scalar_f64", "scalar_candle", "pairwise", "trade", "trademid", "ob", "cross", "deriv" ->
|
||||
new double[]{((Number) res).doubleValue()};
|
||||
case "multi_f64", "multi_candle", "multi_pairwise", "deriv_multi", "profile_pricebins" ->
|
||||
res == null ? nanRow(s.width()) : flattenRecord(res);
|
||||
case "profile_bins" -> res == null ? nanRow(s.width()) : (double[]) res;
|
||||
default -> flattenArray(res); // bars_*, footprint
|
||||
};
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
List<DynamicTest> golden() throws Exception {
|
||||
double[][] rows = input();
|
||||
List<DynamicTest> tests = new ArrayList<>();
|
||||
for (Spec s : SPECS) {
|
||||
tests.add(dynamicTest(s.canonical(), () -> {
|
||||
Object ind = construct(s);
|
||||
String gotName = (String) ind.getClass().getMethod("name").invoke(ind);
|
||||
assertTrue(gotName.equals(NAMES.get(s.canonical())),
|
||||
s.canonical() + ": name() " + gotName + " != " + NAMES.get(s.canonical()));
|
||||
Method upd = updateMethod(ind);
|
||||
double[][] exp = fixture(s.canonical());
|
||||
assertTrue(exp.length == rows.length, s.canonical() + ": row count " + exp.length + " vs " + rows.length);
|
||||
for (int i = 0; i < rows.length; i++) {
|
||||
double[] got = row(s, ind, upd, rows[i], i);
|
||||
double[] want = exp[i];
|
||||
assertTrue(got.length == want.length,
|
||||
s.canonical() + " row " + i + ": arity " + got.length + " vs " + want.length);
|
||||
for (int k = 0; k < want.length; k++) {
|
||||
double w = want[k], g = got[k];
|
||||
if (Double.isNaN(w)) {
|
||||
assertTrue(Double.isNaN(g), s.canonical() + " row " + i + " col " + k + ": want NaN got " + g);
|
||||
} else if (Double.isInfinite(w)) {
|
||||
assertTrue(Double.isInfinite(g) && Math.signum(g) == Math.signum(w),
|
||||
s.canonical() + " row " + i + " col " + k + ": want " + w + " got " + g);
|
||||
} else {
|
||||
double tol = TOL * Math.max(1.0, Math.abs(w));
|
||||
assertTrue(Math.abs(g - w) <= tol,
|
||||
s.canonical() + " row " + i + " col " + k + ": got " + g + " want " + w);
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
return tests;
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
out = TEMPLATE.replace("%SPECS%", specs_block).replace("%NAMES%", names_block)
|
||||
dest = os.path.join(ROOT, "bindings", "java", "src", "test", "java", "org", "wickra", "GoldenAllTest.java")
|
||||
open(dest, "w", encoding="utf-8").write(out)
|
||||
print("generated GoldenAllTest.java with", len(MAN), "indicators")
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>org.wickra</groupId>
|
||||
<artifactId>wickra</artifactId>
|
||||
<version>0.9.1</version>
|
||||
<version>0.9.7</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>Wickra</name>
|
||||
|
||||
@@ -91,6 +91,16 @@ public final class AbandonedBaby implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ABANDONED_BABY_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -91,6 +91,16 @@ public final class Abcd implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ABCD_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -47,10 +47,10 @@ public final class AbsoluteBreadthIndex implements AutoCloseable {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment changeSeg = a.allocateFrom(JAVA_DOUBLE, change);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment newHighSeg = a.allocateFrom(JAVA_DOUBLE, newHigh);
|
||||
MemorySegment newLowSeg = a.allocateFrom(JAVA_DOUBLE, newLow);
|
||||
MemorySegment aboveMaSeg = a.allocateFrom(JAVA_DOUBLE, aboveMa);
|
||||
MemorySegment onBuySignalSeg = a.allocateFrom(JAVA_DOUBLE, onBuySignal);
|
||||
MemorySegment newHighSeg = WickraNative.boolSegment(a, newHigh);
|
||||
MemorySegment newLowSeg = WickraNative.boolSegment(a, newLow);
|
||||
MemorySegment aboveMaSeg = WickraNative.boolSegment(a, aboveMa);
|
||||
MemorySegment onBuySignalSeg = WickraNative.boolSegment(a, onBuySignal);
|
||||
return (double) NativeMethods.WICKRA_ABSOLUTE_BREADTH_INDEX_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
@@ -77,6 +77,16 @@ public final class AbsoluteBreadthIndex implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ABSOLUTE_BREADTH_INDEX_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -67,6 +67,16 @@ public final class AccelerationBands implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ACCELERATION_BANDS_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -100,6 +100,16 @@ public final class AcceleratorOscillator implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ACCELERATOR_OSCILLATOR_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -91,6 +91,16 @@ public final class AdOscillator implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_AD_OSCILLATOR_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -47,10 +47,10 @@ public final class AdVolumeLine implements AutoCloseable {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment changeSeg = a.allocateFrom(JAVA_DOUBLE, change);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment newHighSeg = a.allocateFrom(JAVA_DOUBLE, newHigh);
|
||||
MemorySegment newLowSeg = a.allocateFrom(JAVA_DOUBLE, newLow);
|
||||
MemorySegment aboveMaSeg = a.allocateFrom(JAVA_DOUBLE, aboveMa);
|
||||
MemorySegment onBuySignalSeg = a.allocateFrom(JAVA_DOUBLE, onBuySignal);
|
||||
MemorySegment newHighSeg = WickraNative.boolSegment(a, newHigh);
|
||||
MemorySegment newLowSeg = WickraNative.boolSegment(a, newLow);
|
||||
MemorySegment aboveMaSeg = WickraNative.boolSegment(a, aboveMa);
|
||||
MemorySegment onBuySignalSeg = WickraNative.boolSegment(a, onBuySignal);
|
||||
return (double) NativeMethods.WICKRA_AD_VOLUME_LINE_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
@@ -77,6 +77,16 @@ public final class AdVolumeLine implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_AD_VOLUME_LINE_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -94,6 +94,16 @@ public final class AdaptiveCci implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ADAPTIVE_CCI_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -71,6 +71,16 @@ public final class AdaptiveCycle implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ADAPTIVE_CYCLE_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -74,6 +74,16 @@ public final class AdaptiveLaguerreFilter implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ADAPTIVE_LAGUERRE_FILTER_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -74,6 +74,16 @@ public final class AdaptiveRsi implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ADAPTIVE_RSI_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -91,6 +91,16 @@ public final class Adl implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ADL_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -91,6 +91,16 @@ public final class AdvanceBlock implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ADVANCE_BLOCK_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -47,10 +47,10 @@ public final class AdvanceDecline implements AutoCloseable {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment changeSeg = a.allocateFrom(JAVA_DOUBLE, change);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment newHighSeg = a.allocateFrom(JAVA_DOUBLE, newHigh);
|
||||
MemorySegment newLowSeg = a.allocateFrom(JAVA_DOUBLE, newLow);
|
||||
MemorySegment aboveMaSeg = a.allocateFrom(JAVA_DOUBLE, aboveMa);
|
||||
MemorySegment onBuySignalSeg = a.allocateFrom(JAVA_DOUBLE, onBuySignal);
|
||||
MemorySegment newHighSeg = WickraNative.boolSegment(a, newHigh);
|
||||
MemorySegment newLowSeg = WickraNative.boolSegment(a, newLow);
|
||||
MemorySegment aboveMaSeg = WickraNative.boolSegment(a, aboveMa);
|
||||
MemorySegment onBuySignalSeg = WickraNative.boolSegment(a, onBuySignal);
|
||||
return (double) NativeMethods.WICKRA_ADVANCE_DECLINE_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
@@ -77,6 +77,16 @@ public final class AdvanceDecline implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ADVANCE_DECLINE_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -47,10 +47,10 @@ public final class AdvanceDeclineRatio implements AutoCloseable {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment changeSeg = a.allocateFrom(JAVA_DOUBLE, change);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment newHighSeg = a.allocateFrom(JAVA_DOUBLE, newHigh);
|
||||
MemorySegment newLowSeg = a.allocateFrom(JAVA_DOUBLE, newLow);
|
||||
MemorySegment aboveMaSeg = a.allocateFrom(JAVA_DOUBLE, aboveMa);
|
||||
MemorySegment onBuySignalSeg = a.allocateFrom(JAVA_DOUBLE, onBuySignal);
|
||||
MemorySegment newHighSeg = WickraNative.boolSegment(a, newHigh);
|
||||
MemorySegment newLowSeg = WickraNative.boolSegment(a, newLow);
|
||||
MemorySegment aboveMaSeg = WickraNative.boolSegment(a, aboveMa);
|
||||
MemorySegment onBuySignalSeg = WickraNative.boolSegment(a, onBuySignal);
|
||||
return (double) NativeMethods.WICKRA_ADVANCE_DECLINE_RATIO_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
@@ -77,6 +77,16 @@ public final class AdvanceDeclineRatio implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ADVANCE_DECLINE_RATIO_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -67,6 +67,16 @@ public final class Adx implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ADX_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -94,6 +94,16 @@ public final class Adxr implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ADXR_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -73,6 +73,16 @@ public final class Alligator implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ALLIGATOR_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -74,6 +74,16 @@ public final class Alma implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ALMA_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -78,6 +78,16 @@ public final class Alpha implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ALPHA_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -59,6 +59,16 @@ public final class AmihudIlliquidity implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_AMIHUD_ILLIQUIDITY_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -71,6 +71,16 @@ public final class AnchoredRsi implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ANCHORED_RSI_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -91,6 +91,16 @@ public final class AnchoredVwap implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ANCHORED_VWAP_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -67,6 +67,16 @@ public final class AndrewsPitchfork implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ANDREWS_PITCHFORK_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -77,6 +77,16 @@ public final class Apo implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_APO_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -66,6 +66,16 @@ public final class Aroon implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_AROON_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -94,6 +94,16 @@ public final class AroonOscillator implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_AROON_OSCILLATOR_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -94,6 +94,16 @@ public final class Atr implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ATR_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -67,6 +67,16 @@ public final class AtrBands implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ATR_BANDS_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -66,6 +66,16 @@ public final class AtrRatchet implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ATR_RATCHET_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -94,6 +94,16 @@ public final class AtrTrailingStop implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ATR_TRAILING_STOP_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -68,6 +68,16 @@ public final class AutoFib implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_AUTO_FIB_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -77,6 +77,16 @@ public final class Autocorrelation implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_AUTOCORRELATION_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -77,6 +77,16 @@ public final class AutocorrelationPeriodogram implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_AUTOCORRELATION_PERIODOGRAM_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -94,6 +94,16 @@ public final class AverageDailyRange implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_AVERAGE_DAILY_RANGE_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -74,6 +74,16 @@ public final class AverageDrawdown implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_AVERAGE_DRAWDOWN_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -91,6 +91,16 @@ public final class AvgPrice implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_AVG_PRICE_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -97,6 +97,16 @@ public final class AwesomeOscillator implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_AWESOME_OSCILLATOR_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -13,19 +13,19 @@ public final class AwesomeOscillatorHistogram implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public AwesomeOscillatorHistogram(int fast, int slow, int smaPeriod) {
|
||||
public AwesomeOscillatorHistogram(int fast, int slow, int lookback) {
|
||||
if (fast < 0) {
|
||||
throw new IllegalArgumentException("fast must be non-negative");
|
||||
}
|
||||
if (slow < 0) {
|
||||
throw new IllegalArgumentException("slow must be non-negative");
|
||||
}
|
||||
if (smaPeriod < 0) {
|
||||
throw new IllegalArgumentException("smaPeriod must be non-negative");
|
||||
if (lookback < 0) {
|
||||
throw new IllegalArgumentException("lookback must be non-negative");
|
||||
}
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_AWESOME_OSCILLATOR_HISTOGRAM_NEW.invokeExact((long) fast, (long) slow, (long) smaPeriod);
|
||||
h = (MemorySegment) NativeMethods.WICKRA_AWESOME_OSCILLATOR_HISTOGRAM_NEW.invokeExact((long) fast, (long) slow, (long) lookback);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
@@ -100,6 +100,16 @@ public final class AwesomeOscillatorHistogram implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_AWESOME_OSCILLATOR_HISTOGRAM_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -91,6 +91,16 @@ public final class BalanceOfPower implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_BALANCE_OF_POWER_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -74,6 +74,16 @@ public final class BandpassFilter implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_BANDPASS_FILTER_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -91,6 +91,16 @@ public final class Bat implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_BAT_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -91,6 +91,16 @@ public final class BeltHold implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_BELT_HOLD_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -78,6 +78,16 @@ public final class Beta implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_BETA_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -78,6 +78,16 @@ public final class BetaNeutralSpread implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_BETA_NEUTRAL_SPREAD_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -94,6 +94,16 @@ public final class BetterVolume implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_BETTER_VOLUME_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
// Generated from bindings/c/include/wickra.h. Do not edit by hand.
|
||||
package org.wickra;
|
||||
|
||||
import org.wickra.internal.NativeMethods;
|
||||
import org.wickra.internal.WickraNative;
|
||||
import java.lang.foreign.Arena;
|
||||
import java.lang.foreign.MemorySegment;
|
||||
import java.lang.ref.Cleaner;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import static java.lang.foreign.ValueLayout.*;
|
||||
|
||||
/** A live Binance kline stream over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class BinanceFeed implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
/** Connect to Binance's live kline stream for the given comma-separated
|
||||
* symbols (case-insensitive) at interval. baseUrl overrides the endpoint
|
||||
* (null = production; pass a ws:// URL to target a test server). */
|
||||
public BinanceFeed(String symbols, BinanceInterval interval, String baseUrl) {
|
||||
if (symbols == null) {
|
||||
throw new NullPointerException("symbols");
|
||||
}
|
||||
MemorySegment h;
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
byte[] sb = symbols.getBytes(StandardCharsets.UTF_8);
|
||||
MemorySegment sym = a.allocate(sb.length + 1L);
|
||||
MemorySegment.copy(sb, 0, sym, JAVA_BYTE, 0L, sb.length);
|
||||
MemorySegment url = MemorySegment.NULL;
|
||||
if (baseUrl != null) {
|
||||
byte[] ub = baseUrl.getBytes(StandardCharsets.UTF_8);
|
||||
url = a.allocate(ub.length + 1L);
|
||||
MemorySegment.copy(ub, 0, url, JAVA_BYTE, 0L, ub.length);
|
||||
}
|
||||
h = (MemorySegment) NativeMethods.WICKRA_BINANCE_CONNECT.invokeExact(
|
||||
sym, (byte) interval.ordinal(), url);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid BinanceFeed parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_BINANCE_FREE);
|
||||
}
|
||||
|
||||
/** Connect with the production endpoint. */
|
||||
public BinanceFeed(String symbols, BinanceInterval interval) {
|
||||
this(symbols, interval, null);
|
||||
}
|
||||
|
||||
/** Poll for the next kline event, waiting up to timeoutMillis. Returns the
|
||||
* event, or null on timeout (call again). Throws once the stream is closed. */
|
||||
public KlineEvent next(long timeoutMillis) {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment out = a.allocate(72L);
|
||||
int code = (int) NativeMethods.WICKRA_BINANCE_NEXT.invokeExact(handle, out, timeoutMillis);
|
||||
if (code == 0) {
|
||||
return null;
|
||||
}
|
||||
if (code != 1) {
|
||||
throw new IllegalStateException("binance feed closed");
|
||||
}
|
||||
byte[] symBytes = new byte[16];
|
||||
MemorySegment.copy(out, JAVA_BYTE, 0L, symBytes, 0, 16);
|
||||
int n = 0;
|
||||
while (n < 16 && symBytes[n] != 0) {
|
||||
n++;
|
||||
}
|
||||
return new KlineEvent(
|
||||
new String(symBytes, 0, n, StandardCharsets.UTF_8),
|
||||
out.get(JAVA_DOUBLE, 16L),
|
||||
out.get(JAVA_DOUBLE, 24L),
|
||||
out.get(JAVA_DOUBLE, 32L),
|
||||
out.get(JAVA_DOUBLE, 40L),
|
||||
out.get(JAVA_DOUBLE, 48L),
|
||||
out.get(JAVA_LONG, 56L),
|
||||
out.get(JAVA_BYTE, 64L) != 0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Fetch historical klines from Binance's REST endpoint. symbol is the
|
||||
* trading pair (case-insensitive), limit the number of candles (1..=1000).
|
||||
* startMs/endMs are inclusive Unix-millisecond bounds (negative = unset);
|
||||
* baseUrl overrides the host (null = production). Blocks until done. */
|
||||
public static Candle[] fetchKlines(String symbol, BinanceInterval interval, int limit,
|
||||
long startMs, long endMs, String baseUrl) {
|
||||
if (symbol == null) {
|
||||
throw new NullPointerException("symbol");
|
||||
}
|
||||
if (limit <= 0) {
|
||||
throw new IllegalArgumentException("limit must be in 1..=1000");
|
||||
}
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
byte[] sb = symbol.getBytes(StandardCharsets.UTF_8);
|
||||
MemorySegment sym = a.allocate(sb.length + 1L);
|
||||
MemorySegment.copy(sb, 0, sym, JAVA_BYTE, 0L, sb.length);
|
||||
MemorySegment url = MemorySegment.NULL;
|
||||
if (baseUrl != null) {
|
||||
byte[] ub = baseUrl.getBytes(StandardCharsets.UTF_8);
|
||||
url = a.allocate(ub.length + 1L);
|
||||
MemorySegment.copy(ub, 0, url, JAVA_BYTE, 0L, ub.length);
|
||||
}
|
||||
MemorySegment out = a.allocate(48L * limit);
|
||||
long n = (long) NativeMethods.WICKRA_BINANCE_FETCH_KLINES.invokeExact(
|
||||
sym, (byte) interval.ordinal(), limit, startMs, endMs, url, out, (long) limit);
|
||||
if (n < 0) {
|
||||
throw new IllegalArgumentException("invalid fetchKlines parameters or transport error");
|
||||
}
|
||||
Candle[] result = new Candle[(int) n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
long b = (long) i * 48L;
|
||||
result[i] = new Candle(
|
||||
out.get(JAVA_DOUBLE, b + 0L),
|
||||
out.get(JAVA_DOUBLE, b + 8L),
|
||||
out.get(JAVA_DOUBLE, b + 16L),
|
||||
out.get(JAVA_DOUBLE, b + 24L),
|
||||
out.get(JAVA_DOUBLE, b + 32L),
|
||||
(double) out.get(JAVA_LONG, b + 40L));
|
||||
}
|
||||
return result;
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
try {
|
||||
NativeMethods.WICKRA_BINANCE_CLOSE.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Generated from bindings/c/include/wickra.h. Do not edit by hand.
|
||||
package org.wickra;
|
||||
|
||||
/** Kline interval for the live Binance feed (ordinal = the C ABI code). */
|
||||
public enum BinanceInterval {
|
||||
ONE_SECOND,
|
||||
ONE_MINUTE,
|
||||
THREE_MINUTES,
|
||||
FIVE_MINUTES,
|
||||
FIFTEEN_MINUTES,
|
||||
THIRTY_MINUTES,
|
||||
ONE_HOUR,
|
||||
TWO_HOURS,
|
||||
FOUR_HOURS,
|
||||
SIX_HOURS,
|
||||
EIGHT_HOURS,
|
||||
TWELVE_HOURS,
|
||||
ONE_DAY,
|
||||
THREE_DAYS,
|
||||
ONE_WEEK,
|
||||
ONE_MONTH
|
||||
}
|
||||
@@ -74,6 +74,16 @@ public final class BipowerVariation implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_BIPOWER_VARIATION_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -91,6 +91,16 @@ public final class BodySizePct implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_BODY_SIZE_PCT_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -68,6 +68,16 @@ public final class BollingerBands implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_BOLLINGER_BANDS_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -74,6 +74,16 @@ public final class BollingerBandwidth implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_BOLLINGER_BANDWIDTH_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -67,6 +67,16 @@ public final class BomarBands implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_BOMAR_BANDS_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -50,10 +50,10 @@ public final class BreadthThrust implements AutoCloseable {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment changeSeg = a.allocateFrom(JAVA_DOUBLE, change);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment newHighSeg = a.allocateFrom(JAVA_DOUBLE, newHigh);
|
||||
MemorySegment newLowSeg = a.allocateFrom(JAVA_DOUBLE, newLow);
|
||||
MemorySegment aboveMaSeg = a.allocateFrom(JAVA_DOUBLE, aboveMa);
|
||||
MemorySegment onBuySignalSeg = a.allocateFrom(JAVA_DOUBLE, onBuySignal);
|
||||
MemorySegment newHighSeg = WickraNative.boolSegment(a, newHigh);
|
||||
MemorySegment newLowSeg = WickraNative.boolSegment(a, newLow);
|
||||
MemorySegment aboveMaSeg = WickraNative.boolSegment(a, aboveMa);
|
||||
MemorySegment onBuySignalSeg = WickraNative.boolSegment(a, onBuySignal);
|
||||
return (double) NativeMethods.WICKRA_BREADTH_THRUST_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
@@ -80,6 +80,16 @@ public final class BreadthThrust implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_BREADTH_THRUST_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -91,6 +91,16 @@ public final class Breakaway implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_BREAKAWAY_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -47,10 +47,10 @@ public final class BullishPercentIndex implements AutoCloseable {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment changeSeg = a.allocateFrom(JAVA_DOUBLE, change);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment newHighSeg = a.allocateFrom(JAVA_DOUBLE, newHigh);
|
||||
MemorySegment newLowSeg = a.allocateFrom(JAVA_DOUBLE, newLow);
|
||||
MemorySegment aboveMaSeg = a.allocateFrom(JAVA_DOUBLE, aboveMa);
|
||||
MemorySegment onBuySignalSeg = a.allocateFrom(JAVA_DOUBLE, onBuySignal);
|
||||
MemorySegment newHighSeg = WickraNative.boolSegment(a, newHigh);
|
||||
MemorySegment newLowSeg = WickraNative.boolSegment(a, newLow);
|
||||
MemorySegment aboveMaSeg = WickraNative.boolSegment(a, aboveMa);
|
||||
MemorySegment onBuySignalSeg = WickraNative.boolSegment(a, onBuySignal);
|
||||
return (double) NativeMethods.WICKRA_BULLISH_PERCENT_INDEX_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
@@ -77,6 +77,16 @@ public final class BullishPercentIndex implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_BULLISH_PERCENT_INDEX_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -74,6 +74,16 @@ public final class BurkeRatio implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_BURKE_RATIO_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -91,6 +91,16 @@ public final class Butterfly implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_BUTTERFLY_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -56,6 +56,16 @@ public final class CalendarSpread implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_CALENDAR_SPREAD_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
@@ -74,6 +74,16 @@ public final class CalmarRatio implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_CALMAR_RATIO_NAME.invokeExact(handle);
|
||||
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user