Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ff268500ef | |||
| 929fc17127 | |||
| 41d5a7dd25 | |||
| e595ea8bfe | |||
| a5fe2e71c4 | |||
| 75eefbbd08 | |||
| 2e07c07a40 | |||
| ec937b8281 | |||
| 1f5eb90b0d | |||
| bbda70f75b | |||
| 2d2d5970b4 | |||
| 8228be7069 | |||
| 677ea37402 | |||
| 2ae76bb90e | |||
| b92ad32037 | |||
| 3a709d9a66 | |||
| baf4d0ff47 | |||
| d362ae26a3 | |||
| cb6da4d737 | |||
| 8a103ef920 | |||
| fd9f4c8bc6 | |||
| 59acefa1ea |
@@ -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
|
||||
|
||||
+123
-37
@@ -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,14 +195,14 @@ 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -575,7 +575,7 @@ jobs:
|
||||
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:
|
||||
@@ -587,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
|
||||
@@ -622,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
|
||||
@@ -649,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:
|
||||
@@ -660,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
|
||||
@@ -705,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:
|
||||
@@ -716,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.
|
||||
@@ -732,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
|
||||
@@ -756,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:
|
||||
@@ -792,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
|
||||
@@ -879,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:
|
||||
@@ -893,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
|
||||
@@ -912,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
|
||||
@@ -938,8 +975,8 @@ jobs:
|
||||
# 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
|
||||
@@ -961,7 +998,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:
|
||||
@@ -972,7 +1009,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
|
||||
@@ -1016,8 +1053,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
|
||||
@@ -1031,6 +1076,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
|
||||
@@ -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,10 +536,14 @@ 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.
|
||||
@@ -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
|
||||
|
||||
+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
|
||||
```
|
||||
|
||||
+138
-1
@@ -7,6 +7,140 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [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
|
||||
@@ -1719,7 +1853,10 @@ public API changes.
|
||||
optional Binance live feed.
|
||||
- Bindings for Python, Node.js, and WebAssembly.
|
||||
|
||||
[Unreleased]: https://github.com/wickra-lib/wickra/compare/v0.9.2...HEAD
|
||||
[Unreleased]: https://github.com/wickra-lib/wickra/compare/v0.9.5...HEAD
|
||||
[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
|
||||
|
||||
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.2"
|
||||
version = "0.9.5"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"criterion",
|
||||
@@ -1954,7 +1796,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-bench"
|
||||
version = "0.9.2"
|
||||
version = "0.9.5"
|
||||
dependencies = [
|
||||
"criterion",
|
||||
"kand",
|
||||
@@ -1966,14 +1808,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-c"
|
||||
version = "0.9.2"
|
||||
version = "0.9.5"
|
||||
dependencies = [
|
||||
"tokio",
|
||||
"wickra-core",
|
||||
"wickra-data",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wickra-core"
|
||||
version = "0.9.2"
|
||||
version = "0.9.5"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"proptest",
|
||||
@@ -1983,24 +1827,26 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-data"
|
||||
version = "0.9.2"
|
||||
version = "0.9.5"
|
||||
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.2"
|
||||
version = "0.9.5"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
"tokio",
|
||||
@@ -2010,26 +1856,30 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-node"
|
||||
version = "0.9.2"
|
||||
version = "0.9.5"
|
||||
dependencies = [
|
||||
"napi",
|
||||
"napi-build",
|
||||
"napi-derive",
|
||||
"tokio",
|
||||
"wickra-core",
|
||||
"wickra-data",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wickra-python"
|
||||
version = "0.9.2"
|
||||
version = "0.9.5"
|
||||
dependencies = [
|
||||
"numpy",
|
||||
"bytemuck",
|
||||
"pyo3",
|
||||
"tokio",
|
||||
"wickra-core",
|
||||
"wickra-data",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wickra-wasm"
|
||||
version = "0.9.2"
|
||||
version = "0.9.5"
|
||||
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.2"
|
||||
version = "0.9.5"
|
||||
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.2" }
|
||||
wickra-core = { path = "crates/wickra-core", version = "0.9.5" }
|
||||
wickra-data = { path = "crates/wickra-data", version = "0.9.5" }
|
||||
|
||||
thiserror = "2"
|
||||
rayon = "1.10"
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
[](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++,
|
||||
@@ -29,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)
|
||||
@@ -104,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.
|
||||
|
||||
@@ -148,21 +153,21 @@ Full tables (Rust + Python, streaming + batch) and how to reproduce them live in
|
||||
|
||||
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 is near-core everywhere; 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.
|
||||
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) | 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 (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
|
||||
@@ -281,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};
|
||||
@@ -303,8 +313,9 @@ while let Some(event) = stream.next_event().await? {
|
||||
}
|
||||
```
|
||||
|
||||
A Python live Binance feed example using the public `websockets` package lives at
|
||||
`examples/python/live_binance.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
|
||||
|
||||
|
||||
+3
-3
@@ -2,13 +2,13 @@
|
||||
|
||||
## Supported versions
|
||||
|
||||
Wickra is pre-1.0. Security fixes are applied to the latest released `0.9.2`
|
||||
Wickra is pre-1.0. Security fixes are applied to the latest released `0.9.5`
|
||||
version only; please upgrade to the newest release before reporting an issue.
|
||||
|
||||
| Version | Supported |
|
||||
| --- | --- |
|
||||
| 0.9.2 (latest) | :white_check_mark: |
|
||||
| < 0.9.2 | :x: |
|
||||
| 0.9.5 (latest) | :white_check_mark: |
|
||||
| < 0.9.5 | :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
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.2</Version>
|
||||
<Version>0.9.5</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>
|
||||
|
||||
@@ -13,6 +13,9 @@ 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
|
||||
@@ -81,6 +84,7 @@ def block(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(" {")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,9 @@ 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):
|
||||
@@ -81,6 +84,9 @@ def block(canon):
|
||||
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":
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+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.2</version>
|
||||
<version>0.9.5</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Gradle:
|
||||
|
||||
```kotlin
|
||||
implementation("org.wickra:wickra:0.9.2")
|
||||
implementation("org.wickra:wickra:0.9.5")
|
||||
```
|
||||
|
||||
The native library ships prebuilt per platform (Linux, macOS, Windows — x64 and
|
||||
|
||||
@@ -25,6 +25,12 @@ for e in MAN:
|
||||
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;
|
||||
|
||||
@@ -60,6 +66,11 @@ class GoldenAllTest {
|
||||
%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) {
|
||||
@@ -244,6 +255,9 @@ class GoldenAllTest {
|
||||
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);
|
||||
@@ -273,7 +287,7 @@ class GoldenAllTest {
|
||||
}
|
||||
'''
|
||||
|
||||
out = TEMPLATE.replace("%SPECS%", specs_block)
|
||||
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.2</version>
|
||||
<version>0.9.5</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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -70,6 +70,16 @@ public final class Camarilla implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_CAMARILLA_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,5 @@
|
||||
// Generated from bindings/c/include/wickra.h. Do not edit by hand.
|
||||
package org.wickra;
|
||||
|
||||
/** Output record produced by the matching indicator. */
|
||||
public record Candle(double open, double high, double low, double close, double volume, double timestamp) {}
|
||||
@@ -0,0 +1,69 @@
|
||||
// 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 static java.lang.foreign.ValueLayout.*;
|
||||
|
||||
/** CSV candle reader over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class CandleReader implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
/** Parse a timestamp,open,high,low,close,volume CSV string (a leading
|
||||
* UTF-8 BOM and field whitespace are tolerated). */
|
||||
public CandleReader(String csv) {
|
||||
if (csv == null) {
|
||||
throw new NullPointerException("csv");
|
||||
}
|
||||
byte[] bytes = csv.getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
MemorySegment h;
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment data = a.allocate(Math.max(1L, bytes.length));
|
||||
MemorySegment.copy(bytes, 0, data, JAVA_BYTE, 0L, bytes.length);
|
||||
h = (MemorySegment) NativeMethods.WICKRA_CANDLE_READER_NEW.invokeExact(data, (long) bytes.length);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid CandleReader CSV");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_CANDLE_READER_FREE);
|
||||
}
|
||||
|
||||
/** Every candle parsed from the CSV, in file order. */
|
||||
public Candle[] read() {
|
||||
try {
|
||||
long n = (long) NativeMethods.WICKRA_CANDLE_READER_COUNT.invokeExact(handle);
|
||||
if (n <= 0) {
|
||||
return new Candle[0];
|
||||
}
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment out = a.allocate(48L * n);
|
||||
long w = (long) NativeMethods.WICKRA_CANDLE_READER_READ.invokeExact(handle, out, n);
|
||||
Candle[] result = new Candle[(int) w];
|
||||
for (int i = 0; i < w; 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() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,16 @@ public final class CandleVolume implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_CANDLE_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 {
|
||||
|
||||
@@ -94,6 +94,16 @@ public final class Cci implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_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 {
|
||||
|
||||
@@ -74,6 +74,16 @@ public final class CenterOfGravity implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_CENTER_OF_GRAVITY_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 {
|
||||
|
||||
@@ -64,6 +64,16 @@ public final class CentralPivotRange implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_CENTRAL_PIVOT_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 Cfo implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_CFO_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 ChaikinMoneyFlow implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/** The indicator's canonical name. */
|
||||
public String name() {
|
||||
try {
|
||||
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_CHAIKIN_MONEY_FLOW_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