Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d7cb771a28 | |||
| 3e5a19c94a | |||
| 8bfe24ac1d | |||
| 395a2289f4 | |||
| 9973d1a6bf | |||
| cb216668ee | |||
| 20c0002f8e | |||
| 99497eb062 | |||
| 7e51f31f02 | |||
| 3ee3fb67ec | |||
| 3ebcb3f758 | |||
| b31a9a3624 | |||
| 6433c9b3de | |||
| 447bbc8220 | |||
| c9ef2fc037 | |||
| 49c2872ad4 | |||
| 0c4bbaf314 | |||
| 99ad2ab107 | |||
| 8e225a5872 | |||
| 87bb008aa1 | |||
| b06ce2678a | |||
| 5b7523265c | |||
| d188959aab | |||
| 167f7b3ffe | |||
| 8659b42bef | |||
| b7ef63400d | |||
| 8225e1ab91 | |||
| d0061c73b8 | |||
| 23d636fd97 | |||
| fce26cf881 | |||
| 91f6f67257 | |||
| 4caaa1db97 | |||
| 12681e4b1b | |||
| 91e05e3c26 | |||
| 9d0983b666 | |||
| 13c8250488 | |||
| e5305ffa94 | |||
| 46be7a54ea | |||
| bca61322b5 | |||
| fc6f619550 | |||
| 91aa6fffbf | |||
| 5862401958 | |||
| ff5a047078 | |||
| dc415a77fd | |||
| e385734275 | |||
| 3a46b210bb | |||
| 943825d6a0 | |||
| d16df1e224 | |||
| 34c097aee2 | |||
| acd7e8dc52 | |||
| ceaeb90a22 | |||
| 57e26fb22f | |||
| 8431b1400c | |||
| ed01604a18 | |||
| 05fe7ffa90 | |||
| e97c3389fe | |||
| 4526278fa0 | |||
| 80850c81f7 |
@@ -1,3 +1,34 @@
|
||||
# Shell scripts must keep LF line endings so they run on Linux/macOS CI and
|
||||
# local shells regardless of the committer's platform autocrlf setting.
|
||||
*.sh text eol=lf
|
||||
|
||||
# The cbindgen-generated C header is committed; pin it to LF so its CI drift
|
||||
# check (regenerate + `git diff`) never trips on a CRLF normalization.
|
||||
bindings/c/include/wickra.h text eol=lf
|
||||
|
||||
# C# sources (including the generated binding) are pinned to LF so the committed
|
||||
# files stay stable regardless of the committer's autocrlf setting.
|
||||
*.cs text eol=lf
|
||||
|
||||
# Go sources (including the generated binding) are pinned to LF so gofmt's CI
|
||||
# check never trips on a CRLF checkout on Windows. The vendored C ABI header is
|
||||
# a committed copy of bindings/c/include/wickra.h (the parent dir is outside the
|
||||
# Go module), pinned to LF so the drift check never trips on CRLF.
|
||||
*.go text eol=lf
|
||||
go.mod text eol=lf
|
||||
go.sum text eol=lf
|
||||
bindings/go/include/wickra.h text eol=lf
|
||||
|
||||
# Java sources (including the generated binding) are pinned to LF so the
|
||||
# committed files stay stable regardless of the committer's autocrlf setting.
|
||||
*.java text eol=lf
|
||||
|
||||
# R binding: `R CMD check` requires LF in sources, Makevars and shell scripts.
|
||||
*.R text eol=lf
|
||||
*.Rd text eol=lf
|
||||
bindings/r/configure text eol=lf
|
||||
bindings/r/configure.win text eol=lf
|
||||
bindings/r/src/Makevars.in text eol=lf
|
||||
bindings/r/src/Makevars.win text eol=lf
|
||||
bindings/r/src/wickra.c text eol=lf
|
||||
|
||||
|
||||
@@ -30,9 +30,9 @@ assignees: ""
|
||||
## Environment
|
||||
|
||||
- Wickra version:
|
||||
- Language / binding: <!-- Rust crate / Python / Node / WASM -->
|
||||
- Language / binding: <!-- Rust crate / Python / Node / WASM / C ABI / C# (.NET) / Go / Java / R -->
|
||||
- OS and architecture:
|
||||
- Rust / Python / Node version (If relevant):
|
||||
- Rust / Python / Node / .NET version (If relevant):
|
||||
|
||||
## Additional context
|
||||
|
||||
|
||||
@@ -16,6 +16,11 @@ assignees: []
|
||||
- [ ] Python (`pip install wickra`)
|
||||
- [ ] Node.js (`npm install wickra`)
|
||||
- [ ] WebAssembly
|
||||
- [ ] C ABI (`bindings/c`)
|
||||
- [ ] C# / .NET (`Wickra` on NuGet)
|
||||
- [ ] Go (`bindings/go`)
|
||||
- [ ] Java (`org.wickra:wickra` on Maven Central)
|
||||
- [ ] R (`bindings/r`)
|
||||
- [ ] Docs / examples only
|
||||
|
||||
## Environment
|
||||
@@ -26,7 +31,7 @@ assignees: []
|
||||
| Binding version | `e.g. python 0.4.2 / node 0.4.2` |
|
||||
| OS / arch | `e.g. Windows 11 x86_64, Linux glibc` |
|
||||
| Rust toolchain | `rustc --version` (If building from source) |
|
||||
| Python / Node version | `python --version` / `node --version` |
|
||||
| Python / Node / .NET version | `python --version` / `node --version` / `dotnet --version` |
|
||||
|
||||
## Minimal reproducer
|
||||
|
||||
|
||||
@@ -25,6 +25,11 @@ assignees: ""
|
||||
- [ ] Should be exposed in the Python binding
|
||||
- [ ] Should be exposed in the Node binding
|
||||
- [ ] Should be exposed in the WASM binding
|
||||
- [ ] Should be exposed in the C ABI
|
||||
- [ ] Should be exposed in the C# / .NET binding
|
||||
- [ ] Should be exposed in the Go binding
|
||||
- [ ] Should be exposed in the Java binding
|
||||
- [ ] Should be exposed in the R binding
|
||||
|
||||
## Additional context
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ assignees: []
|
||||
## Affected code path
|
||||
|
||||
- Indicator / API: `e.g. EMA.update`
|
||||
- Binding: `Rust / Python / Node / Wasm`
|
||||
- Binding: `Rust / Python / Node / Wasm / C ABI / C# (.NET) / Go / Java / R`
|
||||
- Hot loop or one-shot call?
|
||||
|
||||
## Versions compared
|
||||
|
||||
@@ -33,4 +33,4 @@ import wickra as ta
|
||||
## Environment (Only if relevant)
|
||||
|
||||
- Wickra version: `e.g. 0.4.2`
|
||||
- Binding: `Rust / Python / Node / Wasm`
|
||||
- Binding: `Rust / Python / Node / Wasm / C ABI / C# (.NET) / Go / Java / R`
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
- [ ] `cargo clippy --workspace --all-targets -- -D warnings` is clean.
|
||||
- [ ] `cargo test --workspace` passes.
|
||||
- [ ] New behaviour has tests; bug fixes have a regression test.
|
||||
- [ ] Public API changes are mirrored in the Python / Node / WASM bindings
|
||||
- [ ] Public API changes are mirrored in the Python / Node / WASM bindings, and the C ABI + C# + Go + Java + R bindings are regenerated
|
||||
and their type stubs (If applicable).
|
||||
- [ ] The relevant page on the [documentation site](https://docs.wickra.org)
|
||||
and the `README.md` are updated (If applicable). Docs edits go to a
|
||||
|
||||
@@ -23,6 +23,11 @@ Please fill in the sections below. Delete any that don't apply.
|
||||
- [ ] Python binding (`bindings/python`)
|
||||
- [ ] Node.js binding (`bindings/node`)
|
||||
- [ ] WebAssembly binding (`bindings/wasm`)
|
||||
- [ ] C ABI (`bindings/c`)
|
||||
- [ ] C# / .NET binding (`bindings/csharp`)
|
||||
- [ ] Go binding (`bindings/go`)
|
||||
- [ ] Java binding (`bindings/java`)
|
||||
- [ ] R binding (`bindings/r`)
|
||||
- [ ] Examples / docs
|
||||
|
||||
## Linked issues
|
||||
|
||||
+436
-2
@@ -34,6 +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)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -53,6 +54,22 @@ jobs:
|
||||
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
||||
timeout-minutes: 6
|
||||
|
||||
- name: Warm cargo registry (retry transient DNS/registry flakes)
|
||||
shell: bash
|
||||
# CARGO_NET_RETRY rides out short blips, but a longer runner DNS outage
|
||||
# outlasts cargo's rapid in-process retries: the crates.io index fetch
|
||||
# the first cargo step does ("Could not resolve host: index.crates.io")
|
||||
# then fails the whole job. Pre-fetch the dependency graph here with real
|
||||
# backoff so clippy/build/test resolve from the warmed local cache.
|
||||
run: |
|
||||
for attempt in 1 2 3 4 5; do
|
||||
if cargo fetch; then exit 0; fi
|
||||
echo "::warning::cargo fetch failed (attempt $attempt/5) — likely a registry/DNS flake; retrying in $((attempt * 20))s..."
|
||||
sleep $((attempt * 20))
|
||||
done
|
||||
echo "::error::cargo fetch still failing after 5 attempts"
|
||||
exit 1
|
||||
|
||||
- name: Format check
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
@@ -88,6 +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)
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -177,6 +195,7 @@ 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)
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -232,6 +251,19 @@ jobs:
|
||||
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
||||
timeout-minutes: 6
|
||||
|
||||
- name: Warm cargo registry (retry transient DNS/registry flakes)
|
||||
shell: bash
|
||||
# See the rust job: pre-fetch with backoff so the clippy index update
|
||||
# can't fail the job on a transient "Could not resolve host" DNS blip.
|
||||
run: |
|
||||
for attempt in 1 2 3 4 5; do
|
||||
if cargo fetch; then exit 0; fi
|
||||
echo "::warning::cargo fetch failed (attempt $attempt/5) — likely a registry/DNS flake; retrying in $((attempt * 20))s..."
|
||||
sleep $((attempt * 20))
|
||||
done
|
||||
echo "::error::cargo fetch still failing after 5 attempts"
|
||||
exit 1
|
||||
|
||||
- name: Clippy (bindings, all targets)
|
||||
run: cargo clippy -p wickra-node -p wickra-python --all-targets -- -D warnings
|
||||
|
||||
@@ -247,6 +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)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -272,6 +305,19 @@ jobs:
|
||||
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
||||
timeout-minutes: 6
|
||||
|
||||
- name: Warm cargo registry (retry transient DNS/registry flakes)
|
||||
shell: bash
|
||||
# See the rust job: pre-fetch with backoff so the first cargo step can't
|
||||
# fail the job on a transient "Could not resolve host" DNS blip.
|
||||
run: |
|
||||
for attempt in 1 2 3 4 5; do
|
||||
if cargo fetch; then exit 0; fi
|
||||
echo "::warning::cargo fetch failed (attempt $attempt/5) — likely a registry/DNS flake; retrying in $((attempt * 20))s..."
|
||||
sleep $((attempt * 20))
|
||||
done
|
||||
echo "::error::cargo fetch still failing after 5 attempts"
|
||||
exit 1
|
||||
|
||||
- name: Build on MSRV
|
||||
run: cargo build ${{ matrix.packages }} --verbose
|
||||
|
||||
@@ -282,6 +328,7 @@ 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)
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -323,6 +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)
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -341,6 +389,7 @@ 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)
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -394,6 +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)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -475,6 +525,7 @@ 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)
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
@@ -518,6 +569,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)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -576,9 +628,391 @@ jobs:
|
||||
# exist yet for win32-x64-msvc).
|
||||
run: npx napi build --platform --release
|
||||
|
||||
# The Node test process has wedged on macOS runners (the step hung for
|
||||
# 1h+ while the same tests passed in ~1.5 min elsewhere). Wrap it so a
|
||||
# hung attempt is killed after 6 min and retried once, rather than
|
||||
# running up to the job-level backstop. A normal run is under a minute.
|
||||
- name: Run Node tests
|
||||
working-directory: bindings/node
|
||||
run: node --test __tests__/
|
||||
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
|
||||
with:
|
||||
timeout_minutes: 6
|
||||
max_attempts: 2
|
||||
command: cd bindings/node && node --test __tests__/
|
||||
shell: bash
|
||||
|
||||
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)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
|
||||
- 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
|
||||
|
||||
- name: Install cbindgen
|
||||
uses: taiki-e/install-action@0fd46367812ee04360509b4169d9f659d6892bb2 # v2.79.15
|
||||
timeout-minutes: 10 # fail fast on a stuck download instead of hanging the job
|
||||
with:
|
||||
tool: cbindgen
|
||||
|
||||
- name: Build the C ABI library (cdylib + staticlib)
|
||||
run: cargo build -p wickra-c --release
|
||||
|
||||
- name: Rust unit tests
|
||||
run: cargo test -p wickra-c
|
||||
|
||||
# The generated header is platform-independent, so checking drift on one OS
|
||||
# is enough — and avoids a spurious CRLF/LF diff on the Windows runner.
|
||||
- name: Check the committed header is in sync with cbindgen
|
||||
if: runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: |
|
||||
cbindgen --config bindings/c/cbindgen.toml --crate wickra-c --output bindings/c/include/wickra.h
|
||||
if ! git diff --quiet -- bindings/c/include/wickra.h; then
|
||||
echo "::error::bindings/c/include/wickra.h is out of sync — run cbindgen and commit the result"
|
||||
git --no-pager diff -- bindings/c/include/wickra.h
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The real cross-language test: a foreign C consumer links the generated
|
||||
# header + the compiled library and runs. If this passes on all three OSes,
|
||||
# every C-capable language can link the same way.
|
||||
- name: Build and run the C smoke example (CMake + ctest)
|
||||
shell: bash
|
||||
run: |
|
||||
cmake -S examples/c -B examples/c/build
|
||||
cmake --build examples/c/build --config Release
|
||||
ctest --test-dir examples/c/build -C Release --output-on-failure
|
||||
|
||||
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)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
|
||||
- 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
|
||||
|
||||
# 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.
|
||||
- name: Build the C ABI library
|
||||
run: cargo build -p wickra-c --release
|
||||
|
||||
- name: .NET info
|
||||
run: dotnet --info
|
||||
|
||||
- name: Test the C# binding
|
||||
run: dotnet test bindings/csharp/Wickra.Tests/Wickra.Tests.csproj -c Release
|
||||
|
||||
- name: Build the C# examples
|
||||
shell: bash
|
||||
run: |
|
||||
for d in streaming backtest multi_timeframe parallel_assets \
|
||||
strategy_rsi_mean_reversion strategy_macd_adx strategy_bollinger_squeeze \
|
||||
fetch_btcusdt live_binance; do
|
||||
dotnet build "examples/csharp/$d" -c Release
|
||||
done
|
||||
|
||||
# Run only the offline examples (fetch_btcusdt / live_binance need network).
|
||||
- name: Run the offline C# examples
|
||||
shell: bash
|
||||
run: |
|
||||
for d in streaming backtest multi_timeframe parallel_assets \
|
||||
strategy_rsi_mean_reversion strategy_macd_adx strategy_bollinger_squeeze; do
|
||||
dotnet run --project "examples/csharp/$d" -c Release --no-build
|
||||
done
|
||||
|
||||
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)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# Go is not reliably on PATH on every runner image, so install it
|
||||
# explicitly. cache: false — the cargo build dominates and the modules
|
||||
# have no external Go deps worth caching.
|
||||
- name: Set up Go
|
||||
id: setup-go
|
||||
continue-on-error: true
|
||||
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
|
||||
with:
|
||||
go-version: "stable"
|
||||
cache: false
|
||||
|
||||
- name: Retry Go setup (CDN flake)
|
||||
if: steps.setup-go.outcome == 'failure'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::warning::setup-go failed (likely CDN flake), waiting 30s before retry..."
|
||||
sleep 30
|
||||
|
||||
- name: Set up Go (retry)
|
||||
if: steps.setup-go.outcome == 'failure'
|
||||
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
|
||||
with:
|
||||
go-version: "stable"
|
||||
cache: false
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
|
||||
- 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
|
||||
|
||||
# The binding links against the C ABI hub via cgo; build it first and stage
|
||||
# the platform library under bindings/go/lib so cgo's LDFLAGS find it. Go is
|
||||
# preinstalled on the GitHub runners, so no setup-go step is needed.
|
||||
- name: Build the C ABI library
|
||||
run: cargo build -p wickra-c --release
|
||||
|
||||
- name: Vendored header in sync with the C ABI
|
||||
shell: bash
|
||||
# bindings/go/include/wickra.h is a committed copy of the cbindgen header
|
||||
# (the parent ../c/include is outside the Go module, so it must be
|
||||
# vendored). Fail if it drifts from the source of truth.
|
||||
run: |
|
||||
if ! diff -u bindings/c/include/wickra.h bindings/go/include/wickra.h; then
|
||||
echo "::error::bindings/go/include/wickra.h is stale — copy bindings/c/include/wickra.h over it"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Stage the native library
|
||||
shell: bash
|
||||
# Stage into lib/<goos>_<goarch>/ to match the per-platform cgo LDFLAGS.
|
||||
# CI builds the host target, so RUNNER_OS/ARCH give the right directory
|
||||
# (note macos-latest is arm64).
|
||||
run: |
|
||||
case "$RUNNER_ARCH" in
|
||||
X64) arch=amd64 ;;
|
||||
ARM64) arch=arm64 ;;
|
||||
*) echo "::error::unsupported RUNNER_ARCH '$RUNNER_ARCH'"; exit 1 ;;
|
||||
esac
|
||||
case "$RUNNER_OS" in
|
||||
Linux) dir="linux_$arch"; lib=target/release/libwickra.so ;;
|
||||
macOS) dir="darwin_$arch"; lib=target/release/libwickra.dylib ;;
|
||||
Windows) dir="windows_$arch"; lib=target/release/wickra.dll ;;
|
||||
esac
|
||||
mkdir -p "bindings/go/lib/$dir"
|
||||
cp "$lib" "bindings/go/lib/$dir/"
|
||||
echo "WICKRA_GO_LIBDIR=$PWD/bindings/go/lib/$dir" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Go info
|
||||
run: go version
|
||||
|
||||
- name: Check gofmt
|
||||
shell: bash
|
||||
run: |
|
||||
unformatted="$(gofmt -l bindings/go examples/go)"
|
||||
if [ -n "$unformatted" ]; then
|
||||
echo "gofmt needed on:"; echo "$unformatted"; exit 1
|
||||
fi
|
||||
|
||||
- name: Vet and test the Go binding
|
||||
shell: bash
|
||||
# On Windows there is no rpath; the loader resolves wickra.dll via PATH
|
||||
# (WICKRA_GO_LIBDIR is the per-platform staged lib dir). Linux/macOS use
|
||||
# the rpath baked by the per-platform cgo LDFLAGS.
|
||||
run: |
|
||||
export PATH="$WICKRA_GO_LIBDIR:$PATH"
|
||||
cd bindings/go
|
||||
go vet ./...
|
||||
go test ./...
|
||||
|
||||
- name: Build the Go examples
|
||||
shell: bash
|
||||
run: |
|
||||
export PATH="$WICKRA_GO_LIBDIR:$PATH"
|
||||
cd examples/go
|
||||
go build ./...
|
||||
|
||||
# Run only the offline examples (fetch_btcusdt / live_binance need network).
|
||||
- name: Run the offline Go examples
|
||||
shell: bash
|
||||
run: |
|
||||
export PATH="$WICKRA_GO_LIBDIR:$PATH"
|
||||
cd examples/go
|
||||
for d in streaming backtest multi_timeframe parallel_assets \
|
||||
strategy_rsi_mean_reversion strategy_macd_adx strategy_bollinger_squeeze; do
|
||||
go run "./$d"
|
||||
done
|
||||
|
||||
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)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
env:
|
||||
WICKRA_INCLUDE_DIR: ${{ github.workspace }}/bindings/c/include
|
||||
WICKRA_LIB_DIR: ${{ github.workspace }}/target/release
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
|
||||
- name: Cache cargo
|
||||
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||
continue-on-error: true
|
||||
timeout-minutes: 6
|
||||
|
||||
# The binding compiles a thin .Call glue layer against the C ABI hub; build
|
||||
# the library first. On Windows configure.win bundles a renamed copy
|
||||
# (wickra_abi.dll) so the package's own wickra.dll does not collide with it.
|
||||
- name: Build the C ABI library
|
||||
run: cargo build -p wickra-c --release
|
||||
|
||||
- name: Set up R
|
||||
uses: r-lib/actions/setup-r@a51a8012b0aab7c32ef9d19bf54da93f3254335e # v2
|
||||
with:
|
||||
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")'
|
||||
|
||||
- name: Install and test the R binding
|
||||
shell: bash
|
||||
# github.workspace is a backslash path on Windows; configure(.win) (sh) and
|
||||
# mingw need forward slashes. WICKRA_*_DIR makes configure use the locally
|
||||
# built C ABI (dev override) instead of downloading the release asset, so
|
||||
# CI is version-independent. Deliberately do NOT set LD_LIBRARY_PATH /
|
||||
# DYLD_LIBRARY_PATH: the lib is bundled into the package and must resolve
|
||||
# via the rpath ($ORIGIN / @loader_path) baked by configure — that is the
|
||||
# self-contained install path real users (and r-universe) get.
|
||||
run: |
|
||||
export WICKRA_INCLUDE_DIR="${WICKRA_INCLUDE_DIR//\\//}"
|
||||
export WICKRA_LIB_DIR="${WICKRA_LIB_DIR//\\//}"
|
||||
R CMD INSTALL bindings/r
|
||||
Rscript -e 'library(testthat); library(wickra); test_dir("bindings/r/tests/testthat", stop_on_failure = TRUE)'
|
||||
|
||||
- name: Run the offline R examples
|
||||
shell: bash
|
||||
# No loader-path exports: the installed package is self-contained (bundled
|
||||
# lib + rpath), so the examples exercise exactly what end users run.
|
||||
run: |
|
||||
cd examples/r
|
||||
for f in streaming backtest multi_timeframe parallel_assets \
|
||||
strategy_rsi_mean_reversion strategy_macd_adx strategy_bollinger_squeeze; do
|
||||
Rscript "$f.R"
|
||||
done
|
||||
|
||||
# fetch_btcusdt / live_binance need the network (and jsonlite / websocket);
|
||||
# build-check that they parse without running them.
|
||||
- name: Parse the network R examples
|
||||
run: Rscript -e 'invisible(lapply(c("examples/r/fetch_btcusdt.R", "examples/r/live_binance.R"), parse)); cat("network R examples parse OK\n")'
|
||||
|
||||
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)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
|
||||
- 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
|
||||
|
||||
# The binding links the C ABI hub at runtime through the Java FFM API; build
|
||||
# it first so WickraNative's development fallback finds
|
||||
# target/release/wickra.{so,dylib,dll}. JDK 22+ is required for the final FFM
|
||||
# API, so it is installed explicitly (runners ship 17/21).
|
||||
- name: Build the C ABI library
|
||||
run: cargo build -p wickra-c --release
|
||||
|
||||
- name: Set up JDK 22
|
||||
id: setup-java
|
||||
continue-on-error: true
|
||||
uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "22"
|
||||
cache: maven
|
||||
|
||||
- name: Retry JDK setup (CDN flake)
|
||||
if: steps.setup-java.outcome == 'failure'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::warning::setup-java failed (likely CDN flake), waiting 30s before retry..."
|
||||
sleep 30
|
||||
|
||||
- name: Set up JDK 22 (retry)
|
||||
if: steps.setup-java.outcome == 'failure'
|
||||
uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "22"
|
||||
cache: maven
|
||||
|
||||
- name: Java info
|
||||
run: java -version
|
||||
|
||||
# `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.
|
||||
- name: Test and install the Java binding
|
||||
run: mvn -B -f bindings/java install
|
||||
|
||||
- name: Build the Java examples
|
||||
run: mvn -B -f examples/java compile
|
||||
|
||||
# Run only the offline examples (fetch_btcusdt / live_binance need network).
|
||||
- name: Run the offline Java examples
|
||||
shell: bash
|
||||
run: |
|
||||
for cls in Streaming Backtest MultiTimeframe ParallelAssets \
|
||||
StrategyRsiMeanReversion StrategyMacdAdx StrategyBollingerSqueeze; do
|
||||
mvn -B -q -f examples/java exec:exec -Dexec.mainClass="org.wickra.examples.$cls"
|
||||
done
|
||||
|
||||
# The cross-library benchmark has moved to a dedicated scheduled workflow
|
||||
# (.github/workflows/bench.yml) — see audit finding R10. It runs nightly
|
||||
|
||||
@@ -569,9 +569,306 @@ jobs:
|
||||
# the old "publish, then upload provenance" order would have the provenance
|
||||
# upload rejected once immutability is enabled.
|
||||
# --------------------------------------------------------------------------
|
||||
# --------------------------------------------------------------------------
|
||||
# C ABI native libraries (bindings/c) — built per target on a native runner
|
||||
# (no cross toolchain needed) and attached to the GitHub Release as the
|
||||
# distribution channel. There is no package registry for the C ABI.
|
||||
# --------------------------------------------------------------------------
|
||||
c-abi-build:
|
||||
name: C ABI library (${{ matrix.target }})
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { host: ubuntu-latest, target: x86_64-unknown-linux-gnu }
|
||||
- { host: ubuntu-24.04-arm, target: aarch64-unknown-linux-gnu }
|
||||
- { host: macos-latest, target: x86_64-apple-darwin }
|
||||
- { host: macos-latest, target: aarch64-apple-darwin }
|
||||
- { host: windows-latest, target: x86_64-pc-windows-msvc }
|
||||
- { host: windows-11-arm, target: aarch64-pc-windows-msvc }
|
||||
runs-on: ${{ matrix.host }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
|
||||
- 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: Build the C ABI library (cdylib + staticlib)
|
||||
run: cargo build -p wickra-c --release --target ${{ matrix.target }}
|
||||
|
||||
- name: Package header + libraries
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
dir="wickra-c-${{ matrix.target }}"
|
||||
mkdir -p "$dir/include" "$dir/lib"
|
||||
cp bindings/c/include/wickra.h bindings/c/include/wickra.hpp "$dir/include/"
|
||||
for f in libwickra.so libwickra.a libwickra.dylib wickra.dll wickra.dll.lib wickra.lib; do
|
||||
src="target/${{ matrix.target }}/release/$f"
|
||||
[ -f "$src" ] && cp "$src" "$dir/lib/"
|
||||
done
|
||||
tar -czf "$dir.tar.gz" "$dir"
|
||||
echo "packaged $dir:"; ls -lR "$dir"
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: c-abi-${{ matrix.target }}
|
||||
path: wickra-c-${{ matrix.target }}.tar.gz
|
||||
if-no-files-found: error
|
||||
|
||||
# Pack and publish the .NET binding to NuGet. Independent of the GitHub-release
|
||||
# job so a C# hiccup never blocks the C/C++ asset release. Authentication uses
|
||||
# NuGet Trusted Publishing (OIDC) — no long-lived API key. The 'wickra-release'
|
||||
# trusted-publishing policy on nuget.org (owner KingchenC, repo wickra-lib/wickra,
|
||||
# workflow release.yml) exchanges the GitHub OIDC token for a short-lived key.
|
||||
csharp-publish:
|
||||
name: Publish to NuGet
|
||||
needs: c-abi-build
|
||||
runs-on: ubuntu-latest
|
||||
environment: release
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write # request the GitHub OIDC token for trusted publishing
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download the C ABI native libraries
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: c-abi-*
|
||||
path: c-abi-artifacts
|
||||
|
||||
- name: Stage native libraries into runtimes/<rid>/native
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
declare -A RID=(
|
||||
[x86_64-unknown-linux-gnu]=linux-x64
|
||||
[aarch64-unknown-linux-gnu]=linux-arm64
|
||||
[x86_64-apple-darwin]=osx-x64
|
||||
[aarch64-apple-darwin]=osx-arm64
|
||||
[x86_64-pc-windows-msvc]=win-x64
|
||||
[aarch64-pc-windows-msvc]=win-arm64
|
||||
)
|
||||
base=bindings/csharp/Wickra/runtimes
|
||||
for target in "${!RID[@]}"; do
|
||||
archive=$(find c-abi-artifacts -name "wickra-c-$target.tar.gz" | head -1)
|
||||
if [ -z "$archive" ]; then
|
||||
echo "::error::missing native artifact for $target"; exit 1
|
||||
fi
|
||||
tmp=$(mktemp -d); tar -xzf "$archive" -C "$tmp"
|
||||
dest="$base/${RID[$target]}/native"; mkdir -p "$dest"
|
||||
for f in libwickra.so libwickra.dylib wickra.dll; do
|
||||
src=$(find "$tmp" -name "$f" | head -1)
|
||||
[ -n "$src" ] && cp "$src" "$dest/"
|
||||
done
|
||||
echo "staged ${RID[$target]}:"; ls -l "$dest"
|
||||
done
|
||||
|
||||
- name: Pack
|
||||
shell: bash
|
||||
run: |
|
||||
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.
|
||||
- name: NuGet login (OIDC -> temporary API key)
|
||||
uses: NuGet/login@8d196754b4036150537f80ac539e15c2f1028841 # v1.2.0
|
||||
id: nuget_login
|
||||
with:
|
||||
user: KingchenC
|
||||
|
||||
# Pass the temporary key through the environment (not string-interpolated
|
||||
# into the script) so it cannot be parsed as shell — avoids template injection.
|
||||
- name: Push to NuGet
|
||||
shell: bash
|
||||
env:
|
||||
NUGET_API_KEY: ${{ steps.nuget_login.outputs.NUGET_API_KEY }}
|
||||
run: |
|
||||
dotnet nuget push "nupkg/*.nupkg" --api-key "$NUGET_API_KEY" \
|
||||
--source https://api.nuget.org/v3/index.json --skip-duplicate
|
||||
|
||||
- name: Upload the NuGet package as a build artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: nuget-package
|
||||
path: nupkg/*.nupkg
|
||||
if-no-files-found: error
|
||||
|
||||
# Publish the Java binding to Maven Central. Independent of the GitHub-release
|
||||
# job so a Java hiccup never blocks the C/C++ asset release. The Maven Central
|
||||
# namespace (org.wickra) is verified; authentication uses the Sonatype Central
|
||||
# Portal token (CENTRAL_USERNAME / CENTRAL_PASSWORD) and artifacts are signed
|
||||
# with the GPG key (GPG_PRIVATE_KEY / GPG_PASSPHRASE). The pom version must
|
||||
# match the release tag (kept in sync by the version-bump checklist).
|
||||
java-publish:
|
||||
name: Publish to Maven Central
|
||||
needs: c-abi-build
|
||||
runs-on: ubuntu-latest
|
||||
environment: release
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download the C ABI native libraries
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: c-abi-*
|
||||
path: c-abi-artifacts
|
||||
|
||||
- name: Stage native libraries into resources/native/<os>-<arch>
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
declare -A PLAT=(
|
||||
[x86_64-unknown-linux-gnu]=linux-x64
|
||||
[aarch64-unknown-linux-gnu]=linux-arm64
|
||||
[x86_64-apple-darwin]=osx-x64
|
||||
[aarch64-apple-darwin]=osx-arm64
|
||||
[x86_64-pc-windows-msvc]=win-x64
|
||||
[aarch64-pc-windows-msvc]=win-arm64
|
||||
)
|
||||
base=bindings/java/src/main/resources/native
|
||||
for target in "${!PLAT[@]}"; do
|
||||
archive=$(find c-abi-artifacts -name "wickra-c-$target.tar.gz" | head -1)
|
||||
if [ -z "$archive" ]; then
|
||||
echo "::error::missing native artifact for $target"; exit 1
|
||||
fi
|
||||
tmp=$(mktemp -d); tar -xzf "$archive" -C "$tmp"
|
||||
dest="$base/${PLAT[$target]}"; mkdir -p "$dest"
|
||||
for f in libwickra.so libwickra.dylib wickra.dll; do
|
||||
src=$(find "$tmp" -name "$f" | head -1)
|
||||
[ -n "$src" ] && cp "$src" "$dest/"
|
||||
done
|
||||
echo "staged ${PLAT[$target]}:"; ls -l "$dest"
|
||||
done
|
||||
|
||||
# setup-java writes a settings.xml with the 'central' server credentials
|
||||
# (mapped from the env vars below) and imports the GPG signing key.
|
||||
- name: Set up JDK 22 + Maven Central credentials + GPG
|
||||
uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "22"
|
||||
server-id: central
|
||||
server-username: CENTRAL_USERNAME
|
||||
server-password: CENTRAL_PASSWORD
|
||||
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
gpg-passphrase: GPG_PASSPHRASE
|
||||
|
||||
- name: Deploy to Maven Central
|
||||
env:
|
||||
CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
|
||||
CENTRAL_PASSWORD: ${{ secrets.CENTRAL_PASSWORD }}
|
||||
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
|
||||
run: mvn -B -f bindings/java -Prelease deploy -DskipTests
|
||||
|
||||
# Mirror the in-repo Go module (bindings/go) to the standalone wickra-go
|
||||
# repository so `go get github.com/wickra-lib/wickra-go` builds with no extra
|
||||
# steps. The in-repo module keeps the prebuilt libraries git-ignored; the
|
||||
# mirror commits them per platform under lib/<goos>_<goarch>/ alongside the
|
||||
# vendored header. Independent of the GitHub-release job so a Go hiccup never
|
||||
# blocks the C/C++ asset release. The push uses a fine-grained PAT
|
||||
# (WICKRA_GO_MIRROR_TOKEN, contents:write on wickra-go); the mirror is a
|
||||
# derived artifact, so its bot commit is intentionally unsigned.
|
||||
go-mirror:
|
||||
name: Mirror the Go module to wickra-go
|
||||
needs: c-abi-build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download the C ABI native libraries
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: c-abi-*
|
||||
path: c-abi-artifacts
|
||||
|
||||
- name: Assemble the wickra-go module tree
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
out=wickra-go-module
|
||||
mkdir -p "$out/include" "$out/lib"
|
||||
# Single-package Go source + vendored C ABI header.
|
||||
cp bindings/go/*.go "$out/"
|
||||
cp bindings/go/include/wickra.h "$out/include/"
|
||||
cp bindings/go/README.md "$out/"
|
||||
# Ship the dual license so pkg.go.dev detects a redistributable license.
|
||||
cp LICENSE-MIT LICENSE-APACHE "$out/"
|
||||
# Standalone module path (the in-repo go.mod points at the subdir).
|
||||
printf 'module github.com/wickra-lib/wickra-go\n\ngo 1.23\n' > "$out/go.mod"
|
||||
# Point install instructions at the standalone module path.
|
||||
sed -i 's#github.com/wickra-lib/wickra/bindings/go#github.com/wickra-lib/wickra-go#g' \
|
||||
"$out/README.md" "$out"/*.go
|
||||
# Stage each prebuilt library under lib/<goos>_<goarch>/ (committed in
|
||||
# the mirror, unlike the in-repo lib/.gitignore). "<dir> <libfile>".
|
||||
declare -A GO=(
|
||||
[x86_64-unknown-linux-gnu]="linux_amd64 libwickra.so"
|
||||
[aarch64-unknown-linux-gnu]="linux_arm64 libwickra.so"
|
||||
[x86_64-apple-darwin]="darwin_amd64 libwickra.dylib"
|
||||
[aarch64-apple-darwin]="darwin_arm64 libwickra.dylib"
|
||||
[x86_64-pc-windows-msvc]="windows_amd64 wickra.dll"
|
||||
[aarch64-pc-windows-msvc]="windows_arm64 wickra.dll"
|
||||
)
|
||||
for target in "${!GO[@]}"; do
|
||||
read -r dir libfile <<< "${GO[$target]}"
|
||||
archive=$(find c-abi-artifacts -name "wickra-c-$target.tar.gz" | head -1)
|
||||
if [ -z "$archive" ]; then
|
||||
echo "::error::missing native artifact for $target"; exit 1
|
||||
fi
|
||||
tmp=$(mktemp -d); tar -xzf "$archive" -C "$tmp"
|
||||
src=$(find "$tmp" -name "$libfile" | head -1)
|
||||
if [ -z "$src" ]; then
|
||||
echo "::error::missing $libfile for $target"; exit 1
|
||||
fi
|
||||
mkdir -p "$out/lib/$dir"; cp "$src" "$out/lib/$dir/"
|
||||
done
|
||||
echo "assembled module:"; find "$out" -type f | sort
|
||||
|
||||
- name: Push to wickra-go and tag the release
|
||||
shell: bash
|
||||
env:
|
||||
MIRROR_TOKEN: ${{ secrets.WICKRA_GO_MIRROR_TOKEN }}
|
||||
run: |
|
||||
set -e
|
||||
version="${GITHUB_REF_NAME#v}"
|
||||
remote="https://x-access-token:${MIRROR_TOKEN}@github.com/wickra-lib/wickra-go.git"
|
||||
git clone -q "$remote" mirror-repo
|
||||
cd mirror-repo
|
||||
git config user.name "wickra-bot"
|
||||
git config user.email "support@wickra.org"
|
||||
git symbolic-ref HEAD refs/heads/main
|
||||
# Replace all tracked content with the freshly assembled tree.
|
||||
find . -mindepth 1 -maxdepth 1 -not -name .git -exec rm -rf {} +
|
||||
cp -r ../wickra-go-module/. .
|
||||
git add -A
|
||||
if git diff --cached --quiet; then
|
||||
echo "wickra-go already up to date; tagging only"
|
||||
else
|
||||
git -c commit.gpgsign=false commit -q -m "Release v$version"
|
||||
fi
|
||||
git push origin HEAD:refs/heads/main
|
||||
git tag "v$version"
|
||||
git push origin "v$version"
|
||||
|
||||
github-release:
|
||||
name: Attach assets to the draft GitHub Release
|
||||
needs: [cargo-publish, python-publish, node-publish, wasm-publish]
|
||||
needs: [cargo-publish, python-publish, node-publish, wasm-publish, c-abi-build]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -644,7 +941,7 @@ jobs:
|
||||
# the provenance bundle is attached (P24, immutability-ready).
|
||||
draft: true
|
||||
body: |
|
||||
Wickra ${{ github.ref_name }} — streaming-first technical indicators across 4 language registries.
|
||||
Wickra ${{ github.ref_name }} — streaming-first technical indicators across 4 language registries plus a C ABI.
|
||||
|
||||
### Install
|
||||
|
||||
@@ -665,6 +962,9 @@ jobs:
|
||||
darwin-arm64, win32-x64-msvc)
|
||||
- `wickra-*.tgz` — npm-pack tarballs (main package + per-platform subpackages + WASM)
|
||||
- `*.crate` — cargo source crates (wickra-core, wickra-data, wickra)
|
||||
- `wickra-c-<target>.tar.gz` — C ABI: `include/wickra.h` + `wickra.hpp`
|
||||
and the cdylib/staticlib per target (linux/macos/windows × x64/arm64),
|
||||
the hub for C / C++ / Go / C# / Java / R
|
||||
|
||||
### Auto-generated changelog
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ name: Sync indicator count
|
||||
# 2. GitHub repo "About" description — synced on push to main / v* tag
|
||||
# 3. Docs site: index.md / overview.md / Indicators-Overview.md
|
||||
# (wickra-lib/wickra-docs) — synced on push to main / v* tag*
|
||||
# 4. Marketing site count (wickra-lib/webpage: index.md /
|
||||
# 4. Marketing site count (wickra-lib/webpage: index.md / about.md /
|
||||
# .vitepress/config.ts) — push to main / v* tag*
|
||||
# 5. org profile README count (wickra-lib/.github, profile/README.md)
|
||||
# — synced on push to main / v* tag*
|
||||
@@ -42,10 +42,10 @@ name: Sync indicator count
|
||||
# single source of truth for what the bindings reach.
|
||||
#
|
||||
# Design: on PRs this workflow is a READ-ONLY check. The indicator wiring
|
||||
# (ScriptHelpers/_common.py wire_readme_counter) bumps both README.md and
|
||||
# docs/README.md inside the author's code commit, so the counter is already
|
||||
# correct by the time CI runs. If it is not, the check below fails loud and
|
||||
# asks the author to re-run the wiring — it never pushes a fix-up commit.
|
||||
# bumps both README.md and docs/README.md inside the author's code commit, so
|
||||
# the counter is already correct by the time CI runs. If it is not, the check
|
||||
# below fails loud and asks the author to re-run the wiring — it never pushes a
|
||||
# fix-up commit.
|
||||
#
|
||||
# (An earlier version pushed a GITHUB_TOKEN "sync indicator count" commit to
|
||||
# the PR head. Because GITHUB_TOKEN pushes trigger no workflows, that commit
|
||||
@@ -86,10 +86,17 @@ jobs:
|
||||
# merge ref) so the counter check validates exactly what will land. On
|
||||
# push events we check out the default ref. No push is made, so a shallow
|
||||
# checkout is enough.
|
||||
#
|
||||
# Check out by head SHA, not head ref (branch name): a fast `gh pr merge
|
||||
# --squash --delete-branch` deletes the head branch the moment the PR
|
||||
# merges, often before this queued read-only check reaches its checkout.
|
||||
# Fetching the now-gone `refs/heads/<branch>` then fails the run (exit 1).
|
||||
# The head SHA stays reachable via `refs/pull/N/head` after the branch is
|
||||
# gone, so the checkout — and the run — survives an instant merge.
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }}
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
|
||||
repository: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name || github.repository }}
|
||||
|
||||
- name: Count indicators
|
||||
@@ -149,7 +156,7 @@ jobs:
|
||||
# actually live (Cloudflare Pages, P8.1); merging this PR is therefore
|
||||
# gated on the domain resolving, otherwise the About link would 404.
|
||||
homepage="https://docs.wickra.org"
|
||||
desc="Streaming-first technical indicators with a Rust core and Python, Node.js, and WebAssembly bindings. ${n} indicators, O(1) per-tick updates, no system dependencies. Drop-in TA-Lib replacement."
|
||||
desc="Streaming-first technical indicators with a Rust core and Python, Node.js, WebAssembly, C ABI, .NET, Go, Java, and R bindings. ${n} indicators, O(1) per-tick updates, no system dependencies. Drop-in TA-Lib replacement."
|
||||
# Enforce the homepage unconditionally — it is a constant, so this both
|
||||
# corrects the stale kingchenc URL and self-heals any future drift.
|
||||
# Same Administration-write permission as --description (no extra scope).
|
||||
@@ -325,20 +332,28 @@ jobs:
|
||||
exit 0
|
||||
fi
|
||||
cd docs-ver
|
||||
# Published-versions table rows (crates.io / PyPI / npm): replace only the
|
||||
# version number, leaving the trailing padding + pipe intact. The '.' in
|
||||
# the quickstart pattern matches the literal backtick around the version
|
||||
# without needing a backtick in this shell string. Historical "since
|
||||
# X.Y.Z" references contain no such anchor and are never matched.
|
||||
sed -i -E "s/^(\| (crates\.io|PyPI|npm) .*\| )[0-9]+\.[0-9]+\.[0-9]+/\1${version}/" overview.md
|
||||
# Published-versions table rows (all registries): replace only the
|
||||
# version number, leaving the trailing padding + pipe intact. The
|
||||
# registry name is matched whether plain or wrapped in a markdown link
|
||||
# (\[?...\]?). The '.' in the quickstart pattern matches the literal
|
||||
# backtick around the version without needing a backtick in this shell
|
||||
# string. Historical "since X.Y.Z" references contain no such anchor and
|
||||
# are never matched.
|
||||
sed -i -E "s/^(\| \[?(crates\.io|PyPI|npm|NuGet|Maven Central|Go|r-universe)\]?.*\| )[0-9]+\.[0-9]+\.[0-9]+/\1${version}/" overview.md
|
||||
sed -i -E "s/(published crate is at version .)[0-9]+\.[0-9]+\.[0-9]+/\1${version}/" Quickstart-Rust.md
|
||||
# Quickstart-Java is the only quickstart whose install block pins a
|
||||
# version (the Maven `<version>` tag and the `org.wickra:wickra:<v>`
|
||||
# Gradle coordinate). Java publishes on every release, so it tracks the
|
||||
# same ${version}.
|
||||
sed -i -E "s|<version>[0-9]+\.[0-9]+\.[0-9]+</version>|<version>${version}</version>|" Quickstart-Java.md
|
||||
sed -i -E "s|(org\.wickra:wickra:)[0-9]+\.[0-9]+\.[0-9]+|\1${version}|" Quickstart-Java.md
|
||||
if git diff --quiet; then
|
||||
echo "Docs version already at ${version}."
|
||||
exit 0
|
||||
fi
|
||||
git config user.name "wickra-bot"
|
||||
git config user.email "wickra-bot@users.noreply.github.com"
|
||||
git add overview.md Quickstart-Rust.md
|
||||
git add overview.md Quickstart-Rust.md Quickstart-Java.md
|
||||
git commit -m "chore: sync published version to ${version}"
|
||||
if ! git push 2>/dev/null; then
|
||||
echo "::warning::push to wickra-lib/wickra-docs failed — ABOUT_SYNC_TOKEN likely lacks write (findings P10.0a)."
|
||||
@@ -366,14 +381,14 @@ jobs:
|
||||
exit 0
|
||||
fi
|
||||
cd webpage-count
|
||||
sed -i -E "s/[0-9]+ (streaming-first )?indicators/${n} \1indicators/g" index.md .vitepress/config.ts
|
||||
sed -i -E "s/[0-9]+ (streaming-first )?indicators/${n} \1indicators/g" index.md about.md .vitepress/config.ts
|
||||
if git diff --quiet; then
|
||||
echo "Webpage indicator count unchanged."
|
||||
exit 0
|
||||
fi
|
||||
git config user.name "wickra-bot"
|
||||
git config user.email "wickra-bot@users.noreply.github.com"
|
||||
git add index.md .vitepress/config.ts
|
||||
git add index.md about.md .vitepress/config.ts
|
||||
git commit -m "chore: sync indicator count to ${n}"
|
||||
if ! git push 2>/dev/null; then
|
||||
echo "::warning::push to wickra-lib/webpage failed — ABOUT_SYNC_TOKEN likely lacks write (findings P10.0a)."
|
||||
@@ -424,6 +439,14 @@ jobs:
|
||||
sed -i -E "s/(Latest:\*\* \[.wickra(-wasm)? )[0-9]+\.[0-9]+\.[0-9]+/\1${version}/" api/*.md
|
||||
sed -i -E "s/(text: .v)[0-9]+\.[0-9]+\.[0-9]+/\1${version}/" .vitepress/config.ts
|
||||
sed -i -E "s/(.wickra-wasm.: .\^)[0-9]+\.[0-9]+\.[0-9]+/\1${version}/" package.json
|
||||
# Java is the only target whose install snippet pins a version: the
|
||||
# Maven `<version>` tag in the api/java.md dependency block and the
|
||||
# home-page install tab in index.md, plus any `org.wickra:wickra:<v>`
|
||||
# Gradle coordinate. Java publishes on every release, so it tracks the
|
||||
# same ${version} as the rest. (Other languages' api/*.md carry a
|
||||
# "Latest:" line handled above; Java uses the XML snippet instead.)
|
||||
sed -i -E "s|<version>[0-9]+\.[0-9]+\.[0-9]+</version>|<version>${version}</version>|" api/java.md index.md
|
||||
sed -i -E "s|(org\.wickra:wickra:)[0-9]+\.[0-9]+\.[0-9]+|\1${version}|" api/java.md index.md
|
||||
# Keep package-lock.json in sync with the package.json bump. The site's
|
||||
# Cloudflare build runs `npm clean-install` (npm ci), which hard-fails
|
||||
# with EUSAGE if the lockfile still pins the previous wickra-wasm —
|
||||
@@ -442,7 +465,7 @@ jobs:
|
||||
fi
|
||||
git config user.name "wickra-bot"
|
||||
git config user.email "wickra-bot@users.noreply.github.com"
|
||||
git add api/*.md .vitepress/config.ts package.json package-lock.json
|
||||
git add api/*.md index.md .vitepress/config.ts package.json package-lock.json
|
||||
git commit -m "chore: sync published version to ${version}"
|
||||
if ! git push 2>/dev/null; then
|
||||
echo "::warning::push to wickra-lib/webpage failed — ABOUT_SYNC_TOKEN likely lacks write (findings P10.0a)."
|
||||
|
||||
@@ -55,6 +55,7 @@ bindings/node/npm-debug.log*
|
||||
|
||||
# WASM build output
|
||||
bindings/wasm/pkg/
|
||||
bindings/wasm/pkg-node/
|
||||
|
||||
# Python venv
|
||||
.venv/
|
||||
|
||||
+38
-21
@@ -7,7 +7,7 @@ for the day-to-day workflow.
|
||||
|
||||
## Workspace layout
|
||||
|
||||
Wickra is a Cargo workspace of three Rust crates plus three binding crates.
|
||||
Wickra is a Cargo workspace of three Rust crates plus four binding crates.
|
||||
The split is deliberate: every concern that one user might want to disable
|
||||
or replace lives behind a separate crate boundary.
|
||||
|
||||
@@ -20,23 +20,36 @@ or replace lives behind a separate crate boundary.
|
||||
┌───────────▼──────────┐ ┌──────────▼─────────┐
|
||||
│ wickra-core │ │ wickra-data │
|
||||
│ indicator engine │ │ i/o + aggregation │
|
||||
│ • 214 indicators │ │ • CSV reader │
|
||||
│ • 514 indicators │ │ • CSV reader │
|
||||
│ • Indicator trait │ │ • Tick aggregator │
|
||||
│ • BatchExt impl │ │ • Resampler │
|
||||
│ • OHLCV / Candle │ │ • Live feeds │
|
||||
│ no I/O, no deps │ │ optional features │
|
||||
└──────────────────────┘ └────────────────────┘
|
||||
▲
|
||||
│ (every binding wraps the same core)
|
||||
│
|
||||
┌────────────┴───────────┬─────────────────────┐
|
||||
│ │ │
|
||||
┌──▼──────┐ ┌───────▼──────┐ ┌───────▼────────┐
|
||||
│ Python │ │ Node │ │ WASM │
|
||||
│ (PyO3) │ │ (napi-rs) │ │ (wasm-bindgen) │
|
||||
└─────────┘ └──────────────┘ └────────────────┘
|
||||
│ every binding wraps the same core
|
||||
┌────────────┼────────────┬────────────────┐
|
||||
│ │ │ │
|
||||
┌──▼───────┐ ┌──▼───────┐ ┌──▼───────────┐ ┌──▼──────────────────┐
|
||||
│ Python │ │ Node │ │ WASM │ │ C ABI (cbindgen) │
|
||||
│ (PyO3) │ │ (napi-rs)│ │(wasm-bindgen)│ │ cdylib + header │
|
||||
└──────────┘ └──────────┘ └──────────────┘ └─────────┬───────────┘
|
||||
│ linked by
|
||||
┌──────────▼──────────┐
|
||||
│ C · C++ · C# · Go │
|
||||
│ · Java · R │
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
||||
Python, Node and WASM are *native* Rust bindings (PyO3 / napi-rs /
|
||||
wasm-bindgen). The C ABI is the *hub* every other C-capable language links
|
||||
against: it builds to a `cdylib`/`staticlib` plus a generated `wickra.h`, and
|
||||
downstream languages link that one artifact rather than each re-wrapping the
|
||||
core. C and C++ link it directly; the **C# / .NET** binding (`bindings/csharp`,
|
||||
on NuGet), the **Go** binding (`bindings/go`, cgo), the **R** binding
|
||||
(`bindings/r`, `.Call`) and the **Java** binding (`bindings/java`, the Java FFM
|
||||
API / Panama, on Maven Central) are all generated from `wickra.h`.
|
||||
|
||||
| Crate | Path | What it owns | Public deps |
|
||||
|---|---|---|---|
|
||||
| `wickra-core` | `crates/wickra-core` | every indicator, the `Indicator` trait, `BatchExt`, `Candle`/`Tick` types, `Error` | `thiserror`, `rayon` (parallel batch) |
|
||||
@@ -45,6 +58,7 @@ or replace lives behind a separate crate boundary.
|
||||
| `wickra-python` | `bindings/python` | `_wickra` PyO3 module + Python package | `pyo3`, `numpy`, depends on `wickra-core` |
|
||||
| `wickra-node` | `bindings/node` | NAPI-RS native binding | `napi`, depends on `wickra-core` |
|
||||
| `wickra-wasm` | `bindings/wasm` | WebAssembly binding | `wasm-bindgen`, depends on `wickra-core` |
|
||||
| `wickra-c` | `bindings/c` | C ABI hub — `cdylib`/`staticlib` + generated `wickra.h` (cbindgen) | depends on `wickra-core` |
|
||||
| `wickra-examples` | `examples/rust` | runnable binary examples | depends on `wickra`, `wickra-data` |
|
||||
|
||||
The `fuzz/` directory is **excluded** from the workspace (it has its own
|
||||
@@ -189,14 +203,17 @@ typed object arrays for Node/WASM).
|
||||
|
||||
A handful of indicators need care beyond naive accumulation:
|
||||
|
||||
- **Welford's online variance** is used in `StdDev`, `Variance`, `ZScore`,
|
||||
`BollingerBands`, and several others. Standard sum-of-squares is
|
||||
catastrophically lossy for low-variance inputs; Welford's recurrence
|
||||
keeps O(eps) error.
|
||||
- **Kahan summation** is used wherever rolling sums could span > 1e6
|
||||
elements without resetting — currently only Hurst-exponent's R/S
|
||||
chunks. Most rolling sums are bounded by the window size and don't need
|
||||
it.
|
||||
- **Rolling variance is running-sum, not Welford.** The sliding-window
|
||||
variance family — `StdDev`, `Variance`, `ZScore`, `Bollinger` — keeps
|
||||
running `Σx` and `Σx²` over the window and reports `var = Σx²/n − mean²`,
|
||||
clamping to zero the tiny negative values floating-point cancellation can
|
||||
produce. `Bollinger` periodically reseeds its `Σx²` from the live window
|
||||
so error cannot accumulate over a long stream. Welford's online algorithm
|
||||
(an incremental `M2` accumulator) does **not** transfer cleanly to a
|
||||
sliding window — removing the oldest point from `M2` is numerically
|
||||
unstable — so it is used only where the statistic is *not* a fixed
|
||||
window: `IntradayVolatilityProfile` and `SeasonalZScore` accumulate
|
||||
per-bucket variance that way.
|
||||
- **Logarithm bases** matter for some indicators (Hurst, MFI). Wickra
|
||||
uses natural log everywhere unless the reference math explicitly
|
||||
requires `log10` or `log2` — and then it documents the choice in the
|
||||
@@ -313,9 +330,9 @@ re-discovering them.
|
||||
- **`FAMILIES` (from PR #60) is hand-maintained.** Adding a new
|
||||
indicator requires a separate entry in `FAMILIES`. The
|
||||
`total_count_matches_expected` test will fail if you forget.
|
||||
- **WASM does not have automated tests yet.** Smoke-validated only
|
||||
through the manual examples. Adding `wasm-bindgen-test` coverage is
|
||||
on the roadmap.
|
||||
- **WASM is covered by `wasm-bindgen-test`.** `bindings/wasm/src/lib.rs`
|
||||
carries 21 in-crate tests (run under `wasm-pack test` in CI), in
|
||||
addition to the manual browser examples.
|
||||
|
||||
For the high-level project goals see [`ROADMAP.md`](ROADMAP.md); for
|
||||
day-to-day contribution mechanics see [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
||||
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
# Benchmarks
|
||||
|
||||
Read these as **relative** speedups on identical input — absolute µs depend on
|
||||
CPU, memory clock and OS scheduler, not a universal contract. **Streaming is the
|
||||
headline**: it is where Wickra's design pays off and where the gap is measured in
|
||||
orders of magnitude, not percent. The batch numbers come second and are shown
|
||||
honestly — the leanest crates edge Wickra out on the simple recurrences, and that
|
||||
is a deliberate trade for warmup/NaN semantics, not a ceiling.
|
||||
|
||||
- **Reproduced on:** Windows 11 Pro 26200, AMD Ryzen 9 9950X, 64 GB DDR5,
|
||||
Rust 1.92 (release: `lto = "fat"`, `codegen-units = 1`), Python 3.12.
|
||||
- **Reproduce yourself:**
|
||||
- Rust core vs Rust crates: `cargo bench -p wickra-bench`
|
||||
- Python vs Python libs: `pip install -e bindings/python[bench]` then
|
||||
`python -m benchmarks.compare_libraries` (auto-detects installed peers).
|
||||
|
||||
## 1. Streaming — the structural win
|
||||
|
||||
Live trading feeds one tick at a time. Wickra updates every indicator in **O(1)**;
|
||||
batch-only libraries (TA-Lib, tulipy, finta, pandas-ta) have no incremental API
|
||||
and must recompute the whole history on every tick. Only `talipp` (Python) and
|
||||
`ta-rs` / `yata` (Rust) carry real per-tick state. This is the gap the library
|
||||
was built to expose.
|
||||
|
||||
**Python — per-tick latency** (seed 5 000 bars, then feed ticks one at a time):
|
||||
|
||||
| Indicator | **★ Wickra** | talipp | TA-Lib (recompute) |
|
||||
|------------------|------------------:|------------------|-----------------------|
|
||||
| SMA(20) | **0.089 µs ★** | 0.96 µs (11×) | 422 µs (4 700×) |
|
||||
| EMA(20) | **0.111 µs ★** | 1.19 µs (11×) | 430 µs (3 900×) |
|
||||
| RSI(14) | **0.061 µs ★** | 0.95 µs (16×) | 298 µs (4 900×) |
|
||||
| MACD(12, 26, 9) | **0.079 µs ★** | 3.30 µs (42×) | 327 µs (4 100×) |
|
||||
| Bollinger(20, 2) | **0.089 µs ★** | 4.97 µs (56×) | 296 µs (3 300×) |
|
||||
|
||||
Against the only other incremental Python peer Wickra is **11–56× faster**;
|
||||
against the recompute-on-every-tick libraries it is **2 800–19 000× faster**
|
||||
(`finta` RSI hits 19 000×). tulipy / pandas-ta land in the same recompute band
|
||||
as TA-Lib.
|
||||
|
||||
**Rust — per-tick latency** (whole 50 000-bar series, lower = faster):
|
||||
|
||||
| Indicator | **★ Wickra** | kand | ta-rs | yata |
|
||||
|------------------|------------------:|-----:|------:|-----:|
|
||||
| SMA(20) | 50 | 38 | 47 | 38 |
|
||||
| EMA(20) | 154 | 69 | 56 | 69 |
|
||||
| RSI(14) | 164 | 216 | 74 | — |
|
||||
| MACD(12, 26, 9) | 275 | 143 | 66 | — |
|
||||
| Bollinger(20, 2) | **128 ★** | 248 | 168 | — |
|
||||
| ATR(14) | 152 | 166 | 61 | — |
|
||||
|
||||
`ta-rs` hands back a bare `f64` from the first tick with no warmup and no
|
||||
validation; it leads several rows by giving those guarantees up. Against `kand`,
|
||||
Wickra wins streaming RSI, Bollinger and ATR. `yata` exposes only SMA/EMA as
|
||||
raw-value methods, so its other rows are omitted rather than faked.
|
||||
|
||||
## 2. Batch — competitive, not the headline
|
||||
|
||||
Whole series in one call. Here hand-tuned C (`tulipy`, TA-Lib) and the leanest
|
||||
Rust crate (`kand`) win the simple recurrences — Wickra trades a few µs per pass
|
||||
for the `None`-warmup, NaN-safety and bit-exact `batch == streaming` guarantees
|
||||
none of them keep. It still wins several rows outright and beats the rest of the
|
||||
field everywhere.
|
||||
|
||||
**Python** (20 000-bar pass, µs/op, lower = faster):
|
||||
|
||||
| Indicator | Wickra | TA-Lib | tulipy | pandas-ta | finta |
|
||||
|------------------|---------:|---------:|---------:|----------:|---------:|
|
||||
| SMA(20) | 22.2 | **15.6** | 15.9 | 32.7 | 290.1 |
|
||||
| EMA(20) | 30.5 | **30.4** | 30.9 | 46.7 | 198.5 |
|
||||
| RSI(14) | 52.3 | 72.0 | **34.2** | 88.8 | 812.3 |
|
||||
| MACD(12, 26, 9) | 129.8 | 111.1 | **38.4** | 286.8 | 716.7 |
|
||||
| Bollinger(20, 2) | 87.2 | 74.6 | **37.9** | 474.3 | 1255.5 |
|
||||
| ATR(14) | 74.7 | 87.3 | **35.5** | — | 3496.4 |
|
||||
|
||||
Wickra beats pandas-ta and finta on every row and TA-Lib on RSI and ATR;
|
||||
tulipy's SIMD C (and TA-Lib on SMA/EMA) lead the remaining rows.
|
||||
|
||||
**Rust** (50 000-bar pass, µs, lower = faster). Only Wickra and `kand` expose a
|
||||
batch API; `ta-rs` and `yata` are streaming-only:
|
||||
|
||||
| Indicator | **★ Wickra** | kand |
|
||||
|------------------|------------------:|-------:|
|
||||
| SMA(20) | 53 | **41** |
|
||||
| EMA(20) | 111 | **71** |
|
||||
| RSI(14) | **221 ★** | 259 |
|
||||
| MACD(12, 26, 9) | 533 | **327** |
|
||||
| Bollinger(20, 2) | **404 ★** | 460 |
|
||||
| ATR(14) | **122 ★** | 169 |
|
||||
|
||||
Run the suite yourself:
|
||||
|
||||
```bash
|
||||
cargo bench -p wickra-bench # Rust core vs kand / ta-rs / yata
|
||||
pip install -e bindings/python[bench] # Python peers
|
||||
python -m benchmarks.compare_libraries
|
||||
```
|
||||
|
||||
## 3. Per-binding throughput — the cost of the boundary
|
||||
|
||||
The sections above compare Wickra against other libraries, which only exists for
|
||||
Python and Rust (there is no comparable streaming TA library for C, C#, Go, Java,
|
||||
R or WebAssembly to benchmark against). Every binding calls the **same** Rust
|
||||
core, so these per-binding benchmarks are **not** a speed claim and **not** a
|
||||
cross-library ratio — they document the raw cost of crossing each language's FFI
|
||||
boundary, in million updates per second (Mupd/s).
|
||||
|
||||
Each binding ships a small `throughput` benchmark that feeds a synthetic OHLCV
|
||||
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.
|
||||
- **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
|
||||
per-call marshalling (cgo, FFM, the R/WASM boundary) costs more per tick.
|
||||
|
||||
The Rust core ships the same benchmark with **no** FFI boundary
|
||||
(`examples/rust/.../throughput.rs`) — it is the ceiling each binding is measured
|
||||
against and the value the batch paths converge towards.
|
||||
|
||||
`SMA(20)`, 200 000 bars, median of 3 runs, on the reference machine (Windows 11,
|
||||
AMD Ryzen 9 9950X):
|
||||
|
||||
| Target | streaming (Mupd/s) | batch (Mupd/s) |
|
||||
|----------------------|-------------------:|---------------:|
|
||||
| Rust core (no FFI) | 391 | 500 |
|
||||
| C | 383 | 330 |
|
||||
| C# / .NET | 337 | 244 |
|
||||
| Python | 33 | 488 |
|
||||
| Java | 28 | 175 |
|
||||
| Go | 24 | 400 |
|
||||
| WebAssembly | 19 | 167 |
|
||||
| Node.js | 17 | 10 |
|
||||
| R | 0.1 | 193 |
|
||||
|
||||
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.
|
||||
|
||||
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).
|
||||
|
||||
Run any target's benchmark (build the C ABI library first where it links one):
|
||||
|
||||
```bash
|
||||
cargo run -p wickra-examples --release --bin throughput # Rust core baseline (no FFI)
|
||||
|
||||
node bindings/node/benchmarks/throughput.js # native napi-rs
|
||||
( cd bindings/python && python -m benchmarks.throughput ) # native PyO3
|
||||
( cd bindings/wasm && wasm-pack build --target nodejs --out-dir pkg-node --release ) \
|
||||
&& node bindings/wasm/benchmarks/throughput.mjs # wasm boundary
|
||||
|
||||
cargo build -p wickra-c --release # the C ABI hub
|
||||
cmake -S bindings/c/benchmarks -B build/cbench && cmake --build build/cbench \
|
||||
&& ./build/cbench/throughput # raw C ABI
|
||||
dotnet run -c Release --project bindings/csharp/benchmarks # C# / .NET (P/Invoke)
|
||||
( cd bindings/go/benchmarks && go run . ) # Go (cgo)
|
||||
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)
|
||||
```
|
||||
+240
-1
@@ -7,6 +7,224 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.8.5] - 2026-06-11
|
||||
### Fixed
|
||||
- The R binding's golden-fixture parity test now skips gracefully when the shared
|
||||
`testdata/golden` fixtures are not bundled with the package — standalone
|
||||
r-universe / CRAN builds package only `bindings/r`, so the repo-root fixtures
|
||||
are unreachable there. The parity stays enforced by the repository CI, where
|
||||
the fixtures are present.
|
||||
|
||||
## [0.8.4] - 2026-06-11
|
||||
### Fixed
|
||||
- A single non-finite (NaN/inf) tick no longer poisons indicator state.
|
||||
The 16 pairwise running-sum/buffer indicators fixed first (`Beta`,
|
||||
`BetaNeutralSpread`, `Cointegration`, `HasbrouckInformationShare`,
|
||||
`PearsonCorrelation`, `RollingCorrelation`, `RollingCovariance`,
|
||||
`DistanceSsd`, `GrangerCausality`, `KendallTau`, `LeadLagCrossCorrelation`,
|
||||
`OuHalfLife`, `SpearmanCorrelation`, `SpreadAr1Coefficient`, `SpreadHurst`,
|
||||
`VarianceRatio`) were joined by 38 more scalar/pairwise indicators the new
|
||||
property harness surfaced (the linear-regression family, rolling quantiles
|
||||
and IQR, `Variance`/`StdDev`-derived stats, `Kurtosis`/`Skewness`, the
|
||||
trailing stops, `KalmanHedgeRatio`, `SpreadBollingerBands`, and more). Every
|
||||
`f64` / `(f64, f64)` indicator now rejects non-finite input and returns
|
||||
`None`, matching the streaming-robustness guarantee — and the harness enforces
|
||||
it going forward.
|
||||
|
||||
### Added
|
||||
- Catalogue-wide property-based invariant harness
|
||||
(`crates/wickra-core/tests/invariants.rs`) asserting `batch == streaming`,
|
||||
`reset == fresh`, and non-finite-input rejection for every indicator and
|
||||
bar-builder.
|
||||
|
||||
### Changed
|
||||
- CI: every job now has a runtime cap and the historically flaky Node test step
|
||||
auto-retries, so a wedged runner fails fast instead of hanging for hours.
|
||||
- Documentation accuracy fixes in `SECURITY.md`, `ARCHITECTURE.md`, and
|
||||
`THREAT_MODEL.md` (supported version, indicator count, WASM test coverage,
|
||||
numerical-stability notes, and the C-ABI panic strategy).
|
||||
|
||||
## [0.8.3] - 2026-06-10
|
||||
### Added
|
||||
- **Per-binding throughput benchmarks** — every target now ships a `throughput`
|
||||
benchmark mirroring the Node `throughput.js`: streaming and batch
|
||||
updates-per-second for `SMA(20)`, `ATR(14)` and `MACD(12,26,9)` over a
|
||||
synthetic OHLCV series. New for Python (`bindings/python/benchmarks/`), C
|
||||
(`bindings/c/benchmarks/`), C# (`bindings/csharp/benchmarks/`), Go
|
||||
(`bindings/go/benchmarks/`), Java (`bindings/java/benchmarks/`), R
|
||||
(`bindings/r/benchmarks/`), WebAssembly (`bindings/wasm/benchmarks/`) and the
|
||||
Rust core baseline (`examples/rust/.../throughput.rs`, no FFI). They measure
|
||||
each binding's FFI overhead — the same Rust core runs underneath all of them —
|
||||
and are documented in [BENCHMARKS.md](BENCHMARKS.md) §3, not a cross-library
|
||||
speed claim.
|
||||
- **C ABI archetype test** — `examples/c/archetypes.c` exercises one indicator
|
||||
per FFI archetype (scalar, multi-output, bars, profile, array input) through
|
||||
the C boundary, matching the Go/R/Java suites.
|
||||
|
||||
## [0.8.2] - 2026-06-10
|
||||
### Fixed
|
||||
- **R binding builds for WebAssembly** — `bindings/r/configure` now builds the
|
||||
C ABI from source for the `wasm32-unknown-emscripten` target (r-universe /
|
||||
webR) using the build image's cargo + emscripten, instead of failing with
|
||||
"unsupported OS Emscripten". rayon is dropped on wasm via
|
||||
`--no-default-features`; the indicators are pure computation, so the serial
|
||||
path is functionally identical.
|
||||
|
||||
## [0.8.1] - 2026-06-10
|
||||
### Fixed
|
||||
- **`wickra-go` license** — the release-time Go module mirror now ships the dual
|
||||
`LICENSE-MIT` and `LICENSE-APACHE` files, so pkg.go.dev detects a
|
||||
redistributable license for `github.com/wickra-lib/wickra-go`. The previous
|
||||
mirror shipped no license file.
|
||||
|
||||
## [0.8.0] - 2026-06-09
|
||||
### Added
|
||||
- **Standalone `wickra-go` module** — the Go binding is now mirrored to a
|
||||
dedicated `github.com/wickra-lib/wickra-go` repository on every release, with
|
||||
the prebuilt C ABI libraries committed per platform under
|
||||
`lib/<goos>_<goarch>/` and the C ABI header vendored alongside the source, so
|
||||
`go get github.com/wickra-lib/wickra-go` builds with no extra steps. The
|
||||
in-repo `bindings/go` module is unchanged for repo-clone workflows.
|
||||
|
||||
### Changed
|
||||
- **Go binding (`bindings/go`) is self-contained** — the C ABI header is now
|
||||
vendored inside the module (`bindings/go/include/wickra.h`) instead of being
|
||||
referenced from the parent `bindings/c` directory, and the cgo link flags
|
||||
resolve the prebuilt library per `GOOS`/`GOARCH` under `lib/<goos>_<goarch>/`.
|
||||
This removes the dependency on a full repository checkout for building the
|
||||
module.
|
||||
|
||||
## [0.7.9] - 2026-06-09
|
||||
### Added
|
||||
- **Java binding (`bindings/java`)** — a Java binding reaching the C ABI hub
|
||||
through the Java Foreign Function & Memory API (Panama, `java.lang.foreign`,
|
||||
final in Java 22) rather than JNI or jextract, exposing all 514 indicators as
|
||||
idiomatic `AutoCloseable` classes. The downcall handles, per-indicator
|
||||
wrappers and output records are generated from `wickra.h`; the opaque handle is
|
||||
a `MemorySegment` freed by a `java.lang.ref.Cleaner` action. Ships a full
|
||||
example suite mirroring the C, C#, Go and R examples; published to Maven
|
||||
Central as `org.wickra:wickra`.
|
||||
|
||||
## [0.7.8] - 2026-06-09
|
||||
### Added
|
||||
- **R binding (`bindings/r`)** — an R package reaching the C ABI hub through R's
|
||||
native `.Call` interface, exposing all 514 indicators as constructors that
|
||||
return a `wickra_indicator` object with `update`/`batch`/`reset` methods. The
|
||||
C glue and R wrappers are generated from `wickra.h`; the native handle is freed
|
||||
by a registered finalizer. Ships a full example suite mirroring the C, C# and
|
||||
Go examples; distributed for r-universe / source install.
|
||||
|
||||
## [0.7.7] - 2026-06-09
|
||||
### Added
|
||||
- **Go binding (`bindings/go`)** — a cgo binding over the C ABI hub exposing all
|
||||
514 indicators as idiomatic types with `New<Indicator>` constructors and
|
||||
`Update`/`Batch`/`Reset`/`Close` methods, generated from `wickra.h`. Handles are
|
||||
freed by `Close()` with a `runtime.SetFinalizer` backstop. Ships a full example
|
||||
suite mirroring the C and C# examples; distributed as a subdirectory module
|
||||
(`go get github.com/wickra-lib/wickra/bindings/go`).
|
||||
|
||||
## [0.7.6] - 2026-06-09
|
||||
### Added
|
||||
- **C# / .NET binding (`bindings/csharp`)** — the first language stecker on the
|
||||
C ABI hub. Exposes all 514 indicators as idiomatic `IDisposable` classes via
|
||||
`[LibraryImport]` source-generated P/Invoke, generated from `wickra.h`. Ships
|
||||
on NuGet as `Wickra` with prebuilt native libraries for six target triples
|
||||
(win/linux/osx × x64/arm64), plus a full example suite mirroring the C examples.
|
||||
|
||||
## [0.7.5] - 2026-06-09
|
||||
### Added
|
||||
- **C ABI (`bindings/c`)** — a `cdylib` + `staticlib` plus a generated
|
||||
`include/wickra.h` exposing all 514 indicators and 10 bar builders over an
|
||||
opaque-handle C ABI: the hub any C-capable language (C, C++, Go, C#, Java, R)
|
||||
links against, complementing the native Python/Node/WASM bindings. Ships a
|
||||
full example suite (streaming, backtest, multi-timeframe, OpenMP parallel
|
||||
fan-out, three educational strategies, and Binance fetch/live over `curl`)
|
||||
mirroring the other bindings, plus an optional `wickra.hpp` C++ RAII wrapper.
|
||||
|
||||
## [0.7.4] - 2026-06-08
|
||||
- **Three-Line Break** — Three-line-break bars (reversal needs N-line break) (`THREE_LINE_BREAK_BARS`).
|
||||
- **Run** — Run bars (consecutive same-direction tick runs) (`RUN_BARS`).
|
||||
- **Imbalance** — Imbalance bars (tick-rule signed imbalance threshold) (`IMBALANCE_BARS`).
|
||||
- **Dollar** — Dollar bars (fixed traded value per bar, Lopez de Prado) (`DOLLAR_BARS`).
|
||||
- **Volume** — Volume bars (fixed traded volume per bar) (`VOLUME_BARS`).
|
||||
- **Tick** — Tick bars (fixed candle count per bar) (`TICK_BARS`).
|
||||
- **Range** — Range bars (fixed price-range bricks) (`RANGE_BARS`).
|
||||
|
||||
## [0.7.3] - 2026-06-08
|
||||
- **M2Measure** — M2 measure (Modigliani; Sharpe expressed in benchmark return units) (`M2Measure`).
|
||||
- **UpsidePotentialRatio** — Upside Potential Ratio (upside mean over downside deviation) (`UpsidePotentialRatio`).
|
||||
- **GainToPainRatio** — Gain-to-Pain Ratio (sum of returns over sum of losses) (`GainToPainRatio`).
|
||||
- **CommonSenseRatio** — Common Sense Ratio (tail ratio times gain-to-pain) (`CommonSenseRatio`).
|
||||
- **KRatio** — K-Ratio (Kestner; equity-curve slope over its standard error) (`KRatio`).
|
||||
- **TailRatio** — Tail Ratio (95th over absolute 5th return percentile) (`TailRatio`).
|
||||
- **MartinRatio** — Martin Ratio (Ulcer Performance Index; return over RMS drawdown) (`MartinRatio`).
|
||||
- **BurkeRatio** — Burke Ratio (return over root-sum-squared drawdowns) (`BurkeRatio`).
|
||||
- **SterlingRatio** — Sterling Ratio (mean return over average drawdown) (`SterlingRatio`).
|
||||
|
||||
## [0.7.2] - 2026-06-08
|
||||
- **Composite Profile** — multi-session composite volume profile exposing POC, VAH and VAL (`CompositeProfile`).
|
||||
- **High/Low Volume Nodes** — highest- and lowest-volume price nodes in the profile (`HighLowVolumeNodes`).
|
||||
- **Profile Shape** — profile shape classification (b/P/D normal) as a numeric code (`ProfileShape`).
|
||||
- **Single Prints** — count of single-print (low-activity) price levels in the profile (`SinglePrints`).
|
||||
- **Naked POC** — most recent untouched (naked) point of control level (`NakedPoc`).
|
||||
|
||||
## [0.7.1] - 2026-06-08
|
||||
- **Open-Interest Momentum** — rate-of-change of open interest over a rolling window (`OpenInterestMomentum`).
|
||||
- **Funding-Implied APR** — annualised funding rate (per-interval funding times intervals per year) (`FundingImpliedApr`).
|
||||
- **Perpetual Premium Index** — relative premium of the mark price over the index price (`PerpetualPremiumIndex`).
|
||||
- **OI-to-Volume Ratio** — open interest divided by taker volume (position turnover proxy) (`OiToVolumeRatio`).
|
||||
- **Estimated Leverage Ratio** — open interest divided by aggregate long+short position size (leverage proxy) (`EstimatedLeverageRatio`).
|
||||
|
||||
## [0.7.0] - 2026-06-08
|
||||
- **Hasbrouck Information Share** — variance-ratio proxy for each venue's share of price discovery (Hasbrouck information share) (`HasbrouckInformationShare`).
|
||||
- **PIN** — probability of informed trading from rolling buy/sell imbalance (EKOP single-window estimator) (`Pin`).
|
||||
- **Trade-Sign Autocorrelation** — lag-1 autocorrelation of the signed trade aggressor (order-flow persistence) (`TradeSignAutocorrelation`).
|
||||
|
||||
## [0.6.9] - 2026-06-08
|
||||
- **Tristar** — a three-doji star reversal: three consecutive dojis with the middle gapped above (bearish) or below (bullish) its neighbours (`Tristar`).
|
||||
- **Harami Cross** — a Harami whose second candle is a contained doji, a stronger reversal than a plain Harami (`HaramiCross`).
|
||||
- **Tower Top/Bottom** — a tall bar, a small pause bar, then a tall opposite bar marking a reversal (`TowerTopBottom`).
|
||||
- **Frying Pan Bottom** — a rounded (U-shaped) accumulation base over the lookback window, confirmed when price recovers above the rim (`FryPanBottom`).
|
||||
- **Dumpling Top** — a rounded (dome-shaped) distribution top over the lookback window, confirmed when price breaks below the start (`DumplingTop`).
|
||||
- **New Price Lines** — flags a run of N consecutive new closing highs (+1) or lows (-1), the eight/ten-new-price-lines exhaustion gauge (`NewPriceLines`).
|
||||
|
||||
## [0.6.8] - 2026-06-08
|
||||
- **Smoothed Heikin-Ashi** — a Heikin-Ashi candle computed from EMA-smoothed OHLC, damping noise into a cleaner trend candle (`SmoothedHeikinAshi`).
|
||||
- **Heikin-Ashi Oscillator** — the Heikin-Ashi candle body (`ha_close − ha_open`), optionally EMA-smoothed, as a zero-line oscillator (`HeikinAshiOscillator`).
|
||||
- **Three Line Break** — the trend direction of a line-break chart, reversing only when the close breaks the extreme of the last N lines (`ThreeLineBreak`).
|
||||
- **Equivolume** — a chart box whose height is the bar range and whose width is volume-relative, fusing price range with activity (`Equivolume`).
|
||||
- **CandleVolume** — a candle whose body is close-minus-open and whose width is volume-relative, a volume-weighted candle chart (`CandleVolume`).
|
||||
|
||||
## [0.6.7] - 2026-06-08
|
||||
- **TD Camouflage** — a DeMark qualifier flagging hidden intrabar strength or weakness against the prior close (`TDCamouflage`).
|
||||
- **TD Clop** — a DeMark two-bar open/close engulfing reversal where the bar opens beyond and closes back across the prior body (`TDClop`).
|
||||
- **TD Clopwin** — the inside-body cousin of TD Clop, marking a compression bar whose direction hints at the next move (`TDClopwin`).
|
||||
- **TD Propulsion** — a DeMark continuation thrust that opens on the trend side and closes beyond the prior bar's extreme (`TDPropulsion`).
|
||||
- **TD Trap** — an inside ("trap") bar followed by a close beyond its range, triggering a directional breakout signal (`TDTrap`).
|
||||
- **TD D-Wave** — a streaming Elliott-style swing-wave counter labelling the market's 1–5 impulse / A–C correction sequence (`TDDWave`).
|
||||
- **TD Moving Averages** — the DeMark ST1 (fast) and ST2 (slow) median-price trend ribbon whose crossover frames the trend (`TDMovingAverage`).
|
||||
|
||||
## [0.6.6] - 2026-06-08
|
||||
- **Pivot Reversal** — a breakout signal when price closes through the most recently confirmed swing pivot (`PIVOT_REVERSAL`).
|
||||
- **Volume-Weighted Support/Resistance** — a band whose edges are the volume-weighted average of recent highs and lows (`VOLUME_WEIGHTED_SR`).
|
||||
- **Andrews Pitchfork** — median line and two parallels projected from the last three swing pivots (`ANDREWS_PITCHFORK`).
|
||||
- **Murrey Math Lines** — T. H. Murrey's eighths grid over the recent trading range, each level acting as support/resistance (`MURREY_MATH_LINES`).
|
||||
- **Central Pivot Range** — the classic pivot flanked by two central levels gauging the day's expected character (`CENTRAL_PIVOT_RANGE`).
|
||||
- **Faster scalar batch paths** — `Ema`, `Rsi`, `BollingerBands`, `MacdIndicator` and `Atr` gained dedicated batch fast paths (used by the Python bindings) that strip per-element `Option`/validation overhead and the intermediate `Vec<Option<_>>` allocation, while staying *bit-for-bit* equal to replaying `update` (including the SMA/Bollinger drift-reseed). Python batch is ~2× faster on EMA/RSI/MACD/ATR; streaming is unchanged.
|
||||
- **Cross-library benchmark refresh** — `benchmarks/compare_libraries.py` now measures the median across timing rounds (`--rounds` / `--streaming-rounds`), adds `--skip-batch` / `--skip-streaming`, and drives every peer through the streaming arena (recompute for batch-only libraries). `wickra-bench` compares the batch fast paths against `kand`.
|
||||
|
||||
## [0.6.5] - 2026-06-07
|
||||
- **Autocorrelation Periodogram** — Ehlers autocorrelation periodogram: dominant cycle period estimate (`AUTOCORRPGRAM`).
|
||||
- **Even Better Sinewave** — Ehlers Even Better Sinewave: normalized cycle-phase oscillator (`EVENBETTERSINE`).
|
||||
- **Bandpass Filter** — Ehlers bandpass filter: isolates a frequency band around the dominant cycle (`BANDPASS`).
|
||||
- **Adaptive CCI** — Adaptive CCI: efficiency-ratio-adaptive CCI on typical price (`ADAPTIVECCI`).
|
||||
- **Universal Oscillator** — Ehlers Universal Oscillator: SuperSmoother-based normalized cycle oscillator (`UNIVERSALOSC`).
|
||||
- **Adaptive RSI** — Adaptive RSI: dominant-cycle-tuned RSI length (Ehlers) (`ADAPTIVERSI`).
|
||||
- **Correlation Trend Indicator** — Ehlers Correlation Trend Indicator: Pearson correlation of price vs time (`CTI`).
|
||||
- **Trendflex** — Ehlers Trendflex: trend-following companion to Reflex (`TRENDFLEX`).
|
||||
- **Reflex** — Ehlers Reflex: trend-cycle oscillator measuring slope-adjusted displacement (`REFLEX`).
|
||||
- **Highpass Filter** — Ehlers highpass filter: removes low-frequency trend, leaving cyclic component (`HIGHPASS`).
|
||||
|
||||
## [0.6.4] - 2026-06-07
|
||||
- **Kendall Tau** — Kendall rank correlation (tau-b) over a rolling window of paired observations (`KENDALLTAU`).
|
||||
- **Sample Entropy** — Sample entropy: regularity/complexity of a rolling series (Richman-Moorman) (`SAMPLEENT`).
|
||||
@@ -1332,7 +1550,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
optional Binance live feed.
|
||||
- Bindings for Python, Node.js, and WebAssembly.
|
||||
|
||||
[Unreleased]: https://github.com/wickra-lib/wickra/compare/v0.6.4...HEAD
|
||||
[Unreleased]: https://github.com/wickra-lib/wickra/compare/v0.8.5...HEAD
|
||||
[0.8.5]: https://github.com/wickra-lib/wickra/compare/v0.8.4...v0.8.5
|
||||
[0.8.4]: https://github.com/wickra-lib/wickra/compare/v0.8.3...v0.8.4
|
||||
[0.8.3]: https://github.com/wickra-lib/wickra/compare/v0.8.2...v0.8.3
|
||||
[0.8.2]: https://github.com/wickra-lib/wickra/compare/v0.8.1...v0.8.2
|
||||
[0.8.1]: https://github.com/wickra-lib/wickra/compare/v0.8.0...v0.8.1
|
||||
[0.8.0]: https://github.com/wickra-lib/wickra/compare/v0.7.9...v0.8.0
|
||||
[0.7.9]: https://github.com/wickra-lib/wickra/compare/v0.7.8...v0.7.9
|
||||
[0.7.8]: https://github.com/wickra-lib/wickra/compare/v0.7.7...v0.7.8
|
||||
[0.7.7]: https://github.com/wickra-lib/wickra/compare/v0.7.6...v0.7.7
|
||||
[0.7.6]: https://github.com/wickra-lib/wickra/compare/v0.7.5...v0.7.6
|
||||
[0.7.5]: https://github.com/wickra-lib/wickra/compare/v0.7.4...v0.7.5
|
||||
[0.7.4]: https://github.com/wickra-lib/wickra/compare/v0.7.3...v0.7.4
|
||||
[0.7.3]: https://github.com/wickra-lib/wickra/compare/v0.7.2...v0.7.3
|
||||
[0.7.2]: https://github.com/wickra-lib/wickra/compare/v0.7.1...v0.7.2
|
||||
[0.7.1]: https://github.com/wickra-lib/wickra/compare/v0.7.0...v0.7.1
|
||||
[0.7.0]: https://github.com/wickra-lib/wickra/compare/v0.6.9...v0.7.0
|
||||
[0.6.9]: https://github.com/wickra-lib/wickra/compare/v0.6.8...v0.6.9
|
||||
[0.6.8]: https://github.com/wickra-lib/wickra/compare/v0.6.7...v0.6.8
|
||||
[0.6.7]: https://github.com/wickra-lib/wickra/compare/v0.6.6...v0.6.7
|
||||
[0.6.6]: https://github.com/wickra-lib/wickra/compare/v0.6.5...v0.6.6
|
||||
[0.6.5]: https://github.com/wickra-lib/wickra/compare/v0.6.4...v0.6.5
|
||||
[0.6.4]: https://github.com/wickra-lib/wickra/compare/v0.6.3...v0.6.4
|
||||
[0.6.3]: https://github.com/wickra-lib/wickra/compare/v0.6.2...v0.6.3
|
||||
[0.6.2]: https://github.com/wickra-lib/wickra/compare/v0.6.1...v0.6.2
|
||||
|
||||
+15
-1
@@ -21,6 +21,11 @@ licensed as above, without any additional terms or conditions.
|
||||
| `bindings/python` | PyO3 bindings (`wickra` on PyPI). |
|
||||
| `bindings/node` | napi-rs bindings (`wickra` on npm). |
|
||||
| `bindings/wasm` | wasm-bindgen bindings (`wickra-wasm` on npm). |
|
||||
| `bindings/c` | C ABI — `cdylib` + `staticlib` + generated `include/wickra.h`. The hub for C / C++ and any C-capable language. |
|
||||
| `bindings/csharp` | .NET binding over the C ABI (`Wickra` on NuGet) — `[LibraryImport]` P/Invoke generated from `wickra.h`. |
|
||||
| `bindings/go` | Go binding over the C ABI via cgo (module tag `bindings/go/vX.Y.Z`) — wrappers generated from `wickra.h`. |
|
||||
| `bindings/r` | R binding over the C ABI via `.Call` (R package) — C glue + R wrappers generated from `wickra.h`. |
|
||||
| `bindings/java` | Java binding over the C ABI via the FFM API (Panama, Maven Central) — wrappers generated from `wickra.h`. |
|
||||
| `examples/` | Runnable examples. |
|
||||
| `docs/` | Pointer to the documentation site (docs.wickra.org); the docs live in the `wickra-lib/wickra-docs` repo. |
|
||||
|
||||
@@ -102,7 +107,16 @@ installed. Dependabot also keeps the `.github/requirements` pins current.
|
||||
- **Streaming parity.** An indicator's `batch` output must equal the sequence
|
||||
of `update` calls.
|
||||
- **Bindings.** A change to a public indicator API must be mirrored across the
|
||||
Python, Node, and WASM bindings, including their type stubs / `.d.ts`.
|
||||
Python, Node, and WASM bindings, including their type stubs / `.d.ts`. The C ABI
|
||||
(`bindings/c`) is generated from the core, so regenerate it from the core and
|
||||
commit `src/lib.rs` + `include/wickra.h`. The C# binding (`bindings/csharp`) is
|
||||
generated from `wickra.h`, so regenerate and commit its `Generated/*.g.cs` too.
|
||||
The Go binding (`bindings/go`) is likewise generated from `wickra.h`, so
|
||||
regenerate and commit `indicators_gen.go` (`gofmt`-clean). The R binding
|
||||
(`bindings/r`) is generated from `wickra.h` too, so regenerate and commit
|
||||
`src/wickra.c` + `R/indicators.R`. The Java binding (`bindings/java`) is
|
||||
generated from `wickra.h` as well, so regenerate and commit its
|
||||
`src/main/java/org/wickra/*.java`.
|
||||
- **Docs.** Update the relevant page on the
|
||||
[documentation site](https://docs.wickra.org) and the
|
||||
`README.md` when behaviour or the public API changes. The docs live in
|
||||
|
||||
Generated
+15
-8
@@ -1944,7 +1944,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra"
|
||||
version = "0.6.4"
|
||||
version = "0.8.5"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"criterion",
|
||||
@@ -1955,7 +1955,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-bench"
|
||||
version = "0.6.4"
|
||||
version = "0.8.5"
|
||||
dependencies = [
|
||||
"criterion",
|
||||
"kand",
|
||||
@@ -1965,9 +1965,16 @@ dependencies = [
|
||||
"yata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wickra-c"
|
||||
version = "0.8.5"
|
||||
dependencies = [
|
||||
"wickra-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wickra-core"
|
||||
version = "0.6.4"
|
||||
version = "0.8.5"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"proptest",
|
||||
@@ -1977,7 +1984,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-data"
|
||||
version = "0.6.4"
|
||||
version = "0.8.5"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"csv",
|
||||
@@ -1994,7 +2001,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-examples"
|
||||
version = "0.6.4"
|
||||
version = "0.8.5"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
"tokio",
|
||||
@@ -2004,7 +2011,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-node"
|
||||
version = "0.6.4"
|
||||
version = "0.8.5"
|
||||
dependencies = [
|
||||
"napi",
|
||||
"napi-build",
|
||||
@@ -2014,7 +2021,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-python"
|
||||
version = "0.6.4"
|
||||
version = "0.8.5"
|
||||
dependencies = [
|
||||
"numpy",
|
||||
"pyo3",
|
||||
@@ -2023,7 +2030,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-wasm"
|
||||
version = "0.6.4"
|
||||
version = "0.8.5"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"js-sys",
|
||||
|
||||
+3
-2
@@ -7,13 +7,14 @@ members = [
|
||||
"bindings/python",
|
||||
"bindings/wasm",
|
||||
"bindings/node",
|
||||
"bindings/c",
|
||||
"examples/rust",
|
||||
"crates/wickra-bench",
|
||||
]
|
||||
exclude = ["fuzz"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.6.4"
|
||||
version = "0.8.5"
|
||||
authors = ["kingchenc <support@wickra.org>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.86"
|
||||
@@ -25,7 +26,7 @@ keywords = ["finance", "trading", "indicators", "technical-analysis", "ta"]
|
||||
categories = ["finance", "mathematics", "science"]
|
||||
|
||||
[workspace.dependencies]
|
||||
wickra-core = { path = "crates/wickra-core", version = "0.6.4" }
|
||||
wickra-core = { path = "crates/wickra-core", version = "0.8.5" }
|
||||
|
||||
thiserror = "2"
|
||||
rayon = "1.10"
|
||||
|
||||
@@ -1,25 +1,30 @@
|
||||
<p align="center">
|
||||
<a href="https://wickra.org"><img src="https://raw.githubusercontent.com/wickra-lib/.github/main/profile/wickra-banner.webp?v=452" alt="Wickra — streaming-first technical indicators" width="100%"></a>
|
||||
<a href="https://wickra.org"><img src="https://raw.githubusercontent.com/wickra-lib/.github/main/profile/wickra-banner.webp?v=514" alt="Wickra — streaming-first technical indicators" width="100%"></a>
|
||||
</p>
|
||||
|
||||
[](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml)
|
||||
[](https://github.com/wickra-lib/wickra/actions/workflows/codeql.yml)
|
||||
[](https://codecov.io/gh/wickra-lib/wickra)
|
||||
[](https://github.com/wickra-lib/wickra/releases/latest)
|
||||
[](https://crates.io/crates/wickra)
|
||||
[](https://pypi.org/project/wickra/)
|
||||
[](https://www.npmjs.com/package/wickra)
|
||||
[](#license)
|
||||
[](https://scorecard.dev/viewer/?uri=github.com/wickra-lib/wickra)
|
||||
[](https://www.bestpractices.dev/projects/13094)
|
||||
[](https://github.com/wickra-lib/wickra/attestations)
|
||||
[](https://docs.wickra.org)
|
||||
[](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml)
|
||||
[](https://github.com/wickra-lib/wickra/actions/workflows/codeql.yml)
|
||||
[](https://codecov.io/gh/wickra-lib/wickra)
|
||||
[](https://github.com/wickra-lib/wickra/releases/latest)
|
||||
[](https://crates.io/crates/wickra)
|
||||
[](https://pypi.org/project/wickra/)
|
||||
[](https://www.npmjs.com/package/wickra)
|
||||
[](https://www.nuget.org/packages/Wickra)
|
||||
[](https://central.sonatype.com/artifact/org.wickra/wickra)
|
||||
[](https://pkg.go.dev/github.com/wickra-lib/wickra-go)
|
||||
[](https://wickra-lib.r-universe.dev)
|
||||
[](#license)
|
||||
[](https://scorecard.dev/viewer/?uri=github.com/wickra-lib/wickra)
|
||||
[](https://www.bestpractices.dev/projects/13094)
|
||||
[](https://github.com/wickra-lib/wickra/attestations)
|
||||
[](https://docs.wickra.org)
|
||||
|
||||
**Streaming-first technical indicators. Install with `pip install wickra` — no system dependencies.**
|
||||
|
||||
Wickra is a multi-language technical-analysis library with a Rust core and
|
||||
bindings for Python, Node.js, and WebAssembly. Every indicator is a state
|
||||
machine that updates in O(1) per new data point, so live trading bots and
|
||||
native bindings for Python, Node.js and WebAssembly, plus a C ABI that C, C++,
|
||||
C# / .NET, Go, Java, R and any other C-capable language links against. Every indicator is a
|
||||
state machine that updates in O(1) per new data point, so live trading bots and
|
||||
historical backtests share the exact same implementation.
|
||||
|
||||
```python
|
||||
@@ -46,9 +51,14 @@ Full documentation lives at **[docs.wickra.org](https://docs.wickra.org)**:
|
||||
- **Quickstarts** — [Rust](https://docs.wickra.org/Quickstart-Rust),
|
||||
[Python](https://docs.wickra.org/Quickstart-Python),
|
||||
[Node](https://docs.wickra.org/Quickstart-Node),
|
||||
[WASM](https://docs.wickra.org/Quickstart-WASM).
|
||||
[WASM](https://docs.wickra.org/Quickstart-WASM),
|
||||
[C](https://docs.wickra.org/Quickstart-C),
|
||||
[C#](https://docs.wickra.org/Quickstart-CSharp),
|
||||
[Go](https://docs.wickra.org/Quickstart-Go),
|
||||
[Java](https://docs.wickra.org/Quickstart-Java),
|
||||
[R](https://docs.wickra.org/Quickstart-R).
|
||||
- **Indicators** — a per-indicator deep dive (formula, parameters, warmup) for
|
||||
every one of the 452 indicators; start at the
|
||||
every one of the 514 indicators; start at the
|
||||
[indicators overview](https://docs.wickra.org/Indicators-Overview).
|
||||
- **Reference** — [warmup periods](https://docs.wickra.org/Warmup-Periods),
|
||||
[streaming vs batch](https://docs.wickra.org/Streaming-vs-Batch),
|
||||
@@ -58,19 +68,45 @@ Full documentation lives at **[docs.wickra.org](https://docs.wickra.org)**:
|
||||
[TA-Lib migration](https://docs.wickra.org/TA-Lib-Migration),
|
||||
[FAQ](https://docs.wickra.org/FAQ).
|
||||
|
||||
## Why Wickra exists
|
||||
## Why Wickra
|
||||
|
||||
Wickra started as a personal itch. The existing TA libraries never quite fit the
|
||||
projects I was building, so I decided to build one from the ground up — partly to
|
||||
learn, partly because I genuinely enjoy taking something that already exists and
|
||||
trying to do it differently (and, ideally, better). It's open source because the
|
||||
useful version of that itch is the one other people can build on too.
|
||||
Most TA libraries are fast, *or* multi-language, *or* broad. Wickra refuses to
|
||||
pick. It's the streaming-first engine built for the workload the others treat as
|
||||
an afterthought — **live, tick-by-tick data** — without giving up the breadth of
|
||||
a full batch library, and without making you reimplement your indicators four
|
||||
times to get there.
|
||||
|
||||
Plenty of TA libraries are fast. Each one forces a trade-off Wickra does not:
|
||||
- **The biggest streaming-native catalogue, period.** 514 indicators across 24
|
||||
families — candlesticks, harmonic & chart patterns, market profile, market
|
||||
breadth, Renko/Kagi/Point&Figure bars, Ehlers DSP cycles, risk/performance
|
||||
metrics — every single one updating in **O(1) per tick**. TA-Lib ships ~150 and
|
||||
none of them stream.
|
||||
- **One Rust core, five first-class targets.** Native **Python · Node.js ·
|
||||
WebAssembly · Rust** plus a **C ABI** for C / C++, C# / .NET, Go, Java, R and any other C-capable language —
|
||||
identical math, identical results, zero per-language reimplementation and zero
|
||||
GIL bottleneck.
|
||||
- **Correct by construction, not by hope.** Every `update` validates its input,
|
||||
runs a real warmup, and returns an `Option` so a single bad tick can't silently
|
||||
poison state. `batch == streaming` is **bit-exact, fuzzed and 100 %-line-covered
|
||||
for all 514 indicators**.
|
||||
- **Orders of magnitude faster where it counts.** In streaming Wickra is **11–56×**
|
||||
faster than the only other incremental peer and **thousands of times** faster
|
||||
than recompute-on-every-tick libraries. On batch it wins several rows outright
|
||||
and trades the simple recurrences (SMA, EMA, MACD) for its guarantees — and
|
||||
the losses are shown, not hidden.
|
||||
- **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.
|
||||
- **Truly permissive.** **MIT OR Apache-2.0** — drop it straight into commercial
|
||||
and closed-source work.
|
||||
|
||||
Every other library forces one of those compromises. Wickra doesn't:
|
||||
|
||||
| Library | Install | Streaming | Languages | Indicators | Active |
|
||||
|------------------|-------------|-------------|-----------------------------|-----------:|--------|
|
||||
| **★ Wickra**| **clean** | **yes, O(1)** | **Python · Node · WASM · Rust** | **423** | **yes** |
|
||||
| **★ Wickra**| **clean** | **yes, O(1)** | **Rust · Python · Node · WASM · C · C# · Go · Java · R** | **514** | **yes** |
|
||||
| kand | clean | yes | Python · WASM · Rust | ~60 | yes |
|
||||
| ta-rs | clean | yes | Rust only | ~30 | stale |
|
||||
| yata | clean | partial | Rust only | ~35 | yes |
|
||||
@@ -79,116 +115,31 @@ Plenty of TA libraries are fast. Each one forces a trade-off Wickra does not:
|
||||
| finta | clean | no | Python | ~80 | stale |
|
||||
| talipp | clean | yes | Python | ~40 | yes |
|
||||
|
||||
Wickra's edge is **breadth with reach**: 452 indicators that all update in O(1)
|
||||
per tick and ship natively to Python, Node.js, WebAssembly and Rust from a
|
||||
single engine.
|
||||
Broad, multi-language, streaming-native **and** honest about its trade-offs — at
|
||||
the same time. That's the combination no one else ships.
|
||||
|
||||
**On speed — and why Wickra isn't the fastest.** It deliberately isn't. The
|
||||
leaner Rust crates (kand, ta-rs) win several of the micro-benchmarks below, and
|
||||
those losses are shown rather than hidden. The gap is a *choice*, not a ceiling:
|
||||
every `update` validates its input, runs a real warmup before it emits a value,
|
||||
and returns an `Option` so a single bad tick can't silently poison the state.
|
||||
ta-rs, by contrast, hands back a bare `f64` from the first tick with no
|
||||
validation. If Wickra threw all of that away — raw `f64` out, no checks, no
|
||||
warmup contract — it would match or beat the leanest crate on every row. It
|
||||
keeps the guarantees instead, and still wins RSI, Bollinger and ATR against kand.
|
||||
What no other library matches is the *combination*: catalogue size, native O(1)
|
||||
streaming, NaN-safety, and four first-class language targets at once.
|
||||
## Why Wickra exists
|
||||
|
||||
Wickra started as a personal itch. The existing TA libraries never quite fit the
|
||||
projects I was building, so I decided to build one from the ground up — partly to
|
||||
learn, partly because I genuinely enjoy taking something that already exists and
|
||||
trying to do it differently (and, ideally, better). It's open source because the
|
||||
useful version of that itch is the one other people can build on too.
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Three comparisons, split by layer and mode. Read them as **relative** speedups
|
||||
on identical input — absolute µs depend on CPU, memory clock and OS scheduler,
|
||||
not a universal contract.
|
||||
Wickra updates every indicator in **O(1)** per tick. In **streaming** — the
|
||||
workload it is built for — it is **11–56× faster** than the only other incremental
|
||||
peer and **thousands of times** faster than recompute-on-every-tick libraries.
|
||||
**Batch** is competitive: it wins several rows outright and trades a few µs
|
||||
elsewhere for `None`-warmup, NaN-safety and bit-exact `batch == streaming`.
|
||||
|
||||
- **Reproduced on:** Windows 11 Pro 26200, AMD Ryzen 9 9950X, 64 GB DDR5,
|
||||
Rust 1.92 (release: `lto = "fat"`, `codegen-units = 1`), Python 3.12.
|
||||
- **Reproduce yourself:**
|
||||
- Rust core vs Rust crates: `cargo bench -p wickra-bench`
|
||||
- Python vs Python libs: `pip install -e bindings/python[bench]` then
|
||||
`python -m benchmarks.compare_libraries` (auto-detects installed peers).
|
||||
|
||||
### 1. Rust core vs the other Rust TA crates
|
||||
|
||||
Like-for-like, no language-binding overhead, over a 50 000-bar series (µs for
|
||||
the whole series, lower = faster). This is the honest engine comparison —
|
||||
Wickra wins some and loses some, and both are shown.
|
||||
|
||||
**Streaming** (one value fed per `update`):
|
||||
|
||||
| Indicator | **★ Wickra** | kand | ta-rs | yata |
|
||||
|------------------|------------------:|-----:|------:|-----:|
|
||||
| SMA(20) | 50 | 38 | 47 | 38 |
|
||||
| EMA(20) | 154 | 69 | 56 | 69 |
|
||||
| RSI(14) | 164 | 216 | 74 | — |
|
||||
| MACD(12, 26, 9) | 275 | 143 | 66 | — |
|
||||
| Bollinger(20, 2) | **128 ★** | 248 | 168 | — |
|
||||
| ATR(14) | 152 | 166 | 61 | — |
|
||||
|
||||
**Batch** (whole series at once). Only Wickra and kand expose a batch API;
|
||||
ta-rs and yata are streaming-only.
|
||||
|
||||
| Indicator | **★ Wickra** | kand |
|
||||
|------------------|------------------:|-----:|
|
||||
| SMA(20) | 82 | 42 |
|
||||
| EMA(20) | 159 | 74 |
|
||||
| RSI(14) | **253 ★** | 274 |
|
||||
| MACD(12, 26, 9) | 681 | 283 |
|
||||
| Bollinger(20, 2) | **445 ★** | 462 |
|
||||
| ATR(14) | 175 | 173 |
|
||||
|
||||
ta-rs is the per-indicator speed champion on almost every row — it returns a
|
||||
bare `f64` with no warmup state and no input validation, trading away the
|
||||
`None`-warmup and NaN-safety semantics Wickra keeps. Against kand, Wickra wins
|
||||
streaming RSI, Bollinger and ATR (and batch RSI + Bollinger); Bollinger is the
|
||||
one row where Wickra is the outright fastest of all four. The leaner crates
|
||||
still win the pure recurrences (EMA, MACD) and SMA. yata exposes only SMA/EMA as
|
||||
raw-value methods, so its other rows are omitted rather than faked.
|
||||
|
||||
### 2. Python vs the Python TA ecosystem — batch
|
||||
|
||||
Full pass over a 20 000-bar series, µs/op (lower = faster). **★** per row.
|
||||
|
||||
| Indicator | **★ Wickra** | finta | TA-Lib | tulipy |
|
||||
|------------------|------------------:|---------------------|--------|--------|
|
||||
| SMA(20) | **59.6 ★** | 354.2 (5.9× slower) | ⧗ | ⧗ |
|
||||
| EMA(20) | **88.4 ★** | 309.3 (3.5× slower) | ⧗ | ⧗ |
|
||||
| RSI(14) | **77.3 ★** | 1 283 (16.6× slower)| ⧗ | ⧗ |
|
||||
| MACD(12, 26, 9) | **116.4 ★** | 529.5 (4.6× slower) | ⧗ | ⧗ |
|
||||
| Bollinger(20, 2) | **146.0 ★** | 1 246 (8.5× slower) | ⧗ | ⧗ |
|
||||
| ATR(14) | **135.8 ★** | 3 812 (28× slower) | ⧗ | ⧗ |
|
||||
|
||||
> ⧗ = published by the CI Linux job. TA-Lib and tulipy ship C extensions that
|
||||
> don't build cleanly on every desktop, so their canonical numbers come from the
|
||||
> `cross-library-bench` workflow rather than this local table. pandas-ta needs
|
||||
> Python ≥ 3.12 and isn't in the 3.11 CI matrix. The script auto-detects
|
||||
> whichever peers are installed in your environment.
|
||||
|
||||
### 3. Python — streaming (per-tick latency)
|
||||
|
||||
Seed 5 000 bars, then feed ticks one at a time. talipp is the only Python peer
|
||||
with a true incremental API; batch-only libraries like TA-Lib must recompute the
|
||||
entire history on every tick — Wickra updates in O(1).
|
||||
|
||||
| Indicator | **★ Wickra (per tick)** | talipp (per tick) |
|
||||
|------------------|------------------------------:|-------------------------|
|
||||
| SMA(20) | **0.067 µs ★** | 0.63 µs (9.4× slower) |
|
||||
| EMA(20) | **0.051 µs ★** | 0.63 µs (12.2× slower) |
|
||||
| RSI(14) | **0.053 µs ★** | 1.00 µs (19.1× slower) |
|
||||
| MACD(12, 26, 9) | **0.071 µs ★** | 3.64 µs (51.5× slower) |
|
||||
| Bollinger(20, 2) | **0.085 µs ★** | 4.87 µs (57.2× slower) |
|
||||
|
||||
Run the suite yourself:
|
||||
|
||||
```bash
|
||||
cargo bench -p wickra-bench # Rust core vs kand / ta-rs / yata
|
||||
pip install -e bindings/python[bench] # Python peers
|
||||
python -m benchmarks.compare_libraries
|
||||
```
|
||||
Full tables (Rust + Python, streaming + batch) and how to reproduce them live in
|
||||
**[BENCHMARKS.md](BENCHMARKS.md)**.
|
||||
|
||||
## Indicators
|
||||
|
||||
452 streaming-first indicators across twenty-four families. Every one passes the
|
||||
514 streaming-first indicators across twenty-four families. Every one passes the
|
||||
`batch == streaming` equivalence test, reference-value tests, and reset
|
||||
semantics tests. Each has a per-indicator deep dive (formula, parameters,
|
||||
warmup) at [docs.wickra.org](https://docs.wickra.org/Indicators-Overview).
|
||||
@@ -204,18 +155,18 @@ warmup) at [docs.wickra.org](https://docs.wickra.org/Indicators-Overview).
|
||||
| Trailing Stops | Parabolic SAR, Parabolic SAR Extended (SAREXT), SuperTrend, Chandelier Exit, Chande Kroll Stop, ATR Trailing Stop, HiLo Activator, Volty Stop, Yo-Yo Exit, Donchian Channel Stop, Percentage Trailing Stop, Step Trailing Stop, Renko Trailing Stop, Kase DevStop, Elder SafeZone, ATR Ratchet, NRTR, Time-Based Stop, Modified MA Stop |
|
||||
| Volume | OBV, VWAP (cumulative + rolling), ADL, Volume-Price Trend, Chaikin Money Flow, Chaikin Oscillator, Force Index, Ease of Movement, Klinger Volume Oscillator, Volume Oscillator, NVI, PVI, Williams A/D, Anchored VWAP, Demand Index, TSV, VZO, Market Facilitation Index, Volume RSI, Williams Accumulation/Distribution, Twiggs Money Flow, Trade Volume Index, Intraday Intensity Index, Better Volume, Volume-Weighted MACD |
|
||||
| Price Statistics | Typical Price, Median Price, Weighted Close, Linear Regression, Linear Regression Slope, Z-Score, Linear Regression Angle, Variance, Coefficient of Variation, Skewness, Kurtosis, Standard Error, Detrended StdDev, R², Median Absolute Deviation, Autocorrelation, Hurst Exponent, Pearson Correlation, Beta, Pairwise Beta, Pair Spread Z-Score, Lead-Lag Cross-Correlation, Cointegration, Relative Strength A-vs-B, Spearman Correlation, Mid Price, Mid Point, Average Price, Linear Regression Intercept, Time Series Forecast, Rolling Correlation, Rolling Covariance, OU Half-Life, Spread Hurst, Distance SSD, Beta-Neutral Spread, Variance Ratio, Granger Causality, Kalman Hedge Ratio, Spread Bollinger Bands, Spread AR(1) Coefficient, Jarque-Bera, Rolling Min-Max Scaler, Shannon Entropy, Sample Entropy, Kendall Tau |
|
||||
| Ehlers / Cycle (DSP) | MAMA, FAMA, Fisher Transform, Inverse Fisher Transform, SuperSmoother, Hilbert Dominant Cycle, Hilbert Phasor, Hilbert DC Phase, Hilbert Trend Mode, Sine Wave, Decycler, Decycler Oscillator, Roofing Filter, Center of Gravity, Cybernetic Cycle, Adaptive Cycle, Empirical Mode Decomposition, Ehlers Stochastic, Instantaneous Trendline |
|
||||
| Pivots & S/R | Classic Pivots, Fibonacci Pivots, Camarilla, Woodie Pivots, DeMark Pivots, Williams Fractals, ZigZag |
|
||||
| DeMark | TD Setup, TD Sequential, TD DeMarker, TD REI, TD Pressure, TD Combo, TD Countdown, TD Lines, TD Range Projection, TD Differential, TD Open, TD Risk Level |
|
||||
| Ichimoku & Charts | Ichimoku Kinko Hyo (Tenkan, Kijun, Senkou A/B, Chikou), Heikin-Ashi |
|
||||
| Alt-Chart Bars | Renko (box-size bricks), Kagi (reversal-amount lines), Point & Figure (X/O columns) |
|
||||
| Candlestick Patterns | Doji, Hammer, Inverted Hammer, Hanging Man, Shooting Star, Engulfing, Harami, Morning/Evening Star, Three White Soldiers/Black Crows, Piercing Line/Dark Cloud Cover, Marubozu, Tweezer, Spinning Top, Three Inside Up/Down, Three Outside Up/Down, Two Crows, Upside Gap Two Crows, Identical Three Crows, Three Line Strike, Three Stars in the South, Abandoned Baby, Advance Block, Belt-hold, Breakaway, Counterattack, Doji Star, Dragonfly Doji, Gravestone Doji, Long-Legged Doji, Rickshaw Man, Evening Doji Star, Morning Doji Star, Gap Side-by-Side White, High-Wave, Hikkake, Modified Hikkake, Homing Pigeon, On-Neck, In-Neck, Thrusting, Separating Lines, Kicking, Kicking by Length, Ladder Bottom, Mat Hold, Matching Low, Long Line, Short Line, Rising Three Methods, Falling Three Methods, Upside Gap Three Methods, Downside Gap Three Methods, Stalled Pattern, Stick Sandwich, Takuri, Closing Marubozu, Opening Marubozu, Tasuki Gap, Unique Three River, Concealing Baby Swallow |
|
||||
| Ehlers / Cycle (DSP) | MAMA, FAMA, Fisher Transform, Inverse Fisher Transform, SuperSmoother, Hilbert Dominant Cycle, Hilbert Phasor, Hilbert DC Phase, Hilbert Trend Mode, Sine Wave, Decycler, Decycler Oscillator, Roofing Filter, Center of Gravity, Cybernetic Cycle, Adaptive Cycle, Empirical Mode Decomposition, Ehlers Stochastic, Instantaneous Trendline, Highpass Filter, Reflex, Trendflex, Correlation Trend Indicator, Adaptive RSI, Universal Oscillator, Adaptive CCI, Bandpass Filter, Even Better Sinewave, Autocorrelation Periodogram |
|
||||
| Pivots & S/R | Classic Pivots, Fibonacci Pivots, Camarilla, Woodie Pivots, DeMark Pivots, Williams Fractals, ZigZag, Central Pivot Range, Murrey Math Lines, Andrews Pitchfork, Volume-Weighted Support/Resistance, Pivot Reversal |
|
||||
| DeMark | TD Setup, TD Sequential, TD DeMarker, TD REI, TD Pressure, TD Combo, TD Countdown, TD Lines, TD Range Projection, TD Differential, TD Open, TD Risk Level, TD Camouflage, TD Clop, TD Clopwin, TD Propulsion, TD Trap, TD D-Wave, TD Moving Averages |
|
||||
| Ichimoku & Charts | Ichimoku Kinko Hyo (Tenkan, Kijun, Senkou A/B, Chikou), Heikin-Ashi, Heikin-Ashi Oscillator, Three Line Break, Smoothed Heikin-Ashi, Equivolume, CandleVolume |
|
||||
| Alt-Chart Bars | Renko (box-size bricks), Kagi (reversal-amount lines), Point & Figure (X/O columns), Range, Tick, Volume, Dollar, Imbalance, Run, Three-Line Break |
|
||||
| Candlestick Patterns | Doji, Hammer, Inverted Hammer, Hanging Man, Shooting Star, Engulfing, Harami, Morning/Evening Star, Three White Soldiers/Black Crows, Piercing Line/Dark Cloud Cover, Marubozu, Tweezer, Spinning Top, Three Inside Up/Down, Three Outside Up/Down, Two Crows, Upside Gap Two Crows, Identical Three Crows, Three Line Strike, Three Stars in the South, Abandoned Baby, Advance Block, Belt-hold, Breakaway, Counterattack, Doji Star, Dragonfly Doji, Gravestone Doji, Long-Legged Doji, Rickshaw Man, Evening Doji Star, Morning Doji Star, Gap Side-by-Side White, High-Wave, Hikkake, Modified Hikkake, Homing Pigeon, On-Neck, In-Neck, Thrusting, Separating Lines, Kicking, Kicking by Length, Ladder Bottom, Mat Hold, Matching Low, Long Line, Short Line, Rising Three Methods, Falling Three Methods, Upside Gap Three Methods, Downside Gap Three Methods, Stalled Pattern, Stick Sandwich, Takuri, Closing Marubozu, Opening Marubozu, Tasuki Gap, Unique Three River, Concealing Baby Swallow, Tristar, Harami Cross, Tower Top/Bottom, Dumpling Top, New Price Lines, Frying Pan Bottom |
|
||||
| Chart Patterns | Double Top / Bottom, Triple Top / Bottom, Head and Shoulders, Triangle (asc/desc/sym), Wedge (rising/falling), Flag / Pennant, Rectangle / Range, Cup and Handle |
|
||||
| Harmonic Patterns | AB=CD, Gartley, Butterfly, Bat, Crab, Shark, Cypher, Three Drives |
|
||||
| Fibonacci | Fibonacci Retracement, Fibonacci Extension, Fibonacci Projection, Auto-Fibonacci, Golden Pocket, Fibonacci Confluence, Fibonacci Fan, Fibonacci Arcs, Fibonacci Channel, Fibonacci Time Zones |
|
||||
| Microstructure | Order-Book Imbalance (Top-1 / Top-N / Full), Microprice, Quoted Spread, Depth Slope, Signed Volume, Cumulative Volume Delta, Trade Imbalance, Effective Spread, Realized Spread, Kyle's Lambda, Footprint, Order Flow Imbalance, VPIN, Amihud Illiquidity, Roll Measure |
|
||||
| Derivatives | Funding Rate, Funding Rate Mean, Funding Rate Z-Score, Funding Basis, Open-Interest Delta, OI / Price Divergence, OI-Weighted Price, Long/Short Ratio, Taker Buy/Sell Ratio, Liquidation Features, Term-Structure Basis, Calendar Spread |
|
||||
| Market Profile | Value Area (POC / VAH / VAL), Volume Profile (histogram), TPO Profile, Initial Balance, Opening Range |
|
||||
| Microstructure | Order-Book Imbalance (Top-1 / Top-N / Full), Microprice, Quoted Spread, Depth Slope, Signed Volume, Cumulative Volume Delta, Trade Imbalance, Effective Spread, Realized Spread, Kyle's Lambda, Footprint, Order Flow Imbalance, VPIN, Amihud Illiquidity, Roll Measure, Trade-Sign Autocorrelation, Hasbrouck Information Share |
|
||||
| Derivatives | Funding Rate, Funding Rate Mean, Funding Rate Z-Score, Funding Basis, Open-Interest Delta, OI / Price Divergence, OI-Weighted Price, Long/Short Ratio, Taker Buy/Sell Ratio, Liquidation Features, Term-Structure Basis, Calendar Spread, Estimated Leverage Ratio, OI-to-Volume Ratio, Perpetual Premium Index, Funding-Implied APR, Open-Interest Momentum |
|
||||
| Market Profile | Value Area (POC / VAH / VAL), Volume Profile (histogram), TPO Profile, Initial Balance, Opening Range, Naked POC, Single Prints, Profile Shape, High/Low Volume Nodes, Composite Profile |
|
||||
| Market Breadth | Advance/Decline Line, Advance/Decline Ratio, Advance/Decline Volume Line, McClellan Oscillator, McClellan Summation Index, TRIN / Arms Index, Breadth Thrust, New Highs - New Lows, High-Low Index, Percent Above Moving Average, Up/Down Volume Ratio, Bullish Percent Index, Cumulative Volume Index, Absolute Breadth Index, TICK Index |
|
||||
| Risk / Performance | Sharpe Ratio, Sortino Ratio, Calmar Ratio, Omega Ratio, Max Drawdown, Average Drawdown, Drawdown Duration, Pain Index, Value at Risk, Conditional Value at Risk (CVaR), Profit Factor, Gain/Loss Ratio, Recovery Factor, Kelly Criterion, Treynor Ratio, Information Ratio, Alpha (Jensen) |
|
||||
| Seasonality & Session | Session VWAP, Session High/Low, Session Range, Average Daily Range, Overnight Gap, Overnight/Intraday Return, Turn-of-Month, Seasonal Z-Score, Time-of-Day Return Profile, Day-of-Week Profile, Intraday Volatility Profile, Volume-by-Time Profile |
|
||||
@@ -226,8 +177,9 @@ as one column each. `Doji` is direction-less by default (`+1.0` / `0.0`);
|
||||
construct it in signed mode (`Doji::new().signed()`, `Doji(signed=True)`,
|
||||
`new Doji(true)`) for a dragonfly / gravestone `±1` reading.
|
||||
|
||||
Adding a new indicator means implementing one trait in Rust; all four bindings
|
||||
inherit it automatically.
|
||||
Adding a new indicator means implementing one trait in Rust; every binding
|
||||
inherits it automatically (the C ABI — and the C#, Go, Java and R bindings generated from
|
||||
it — regenerate from the core).
|
||||
|
||||
## Languages
|
||||
|
||||
@@ -237,12 +189,18 @@ inherit it automatically.
|
||||
| Node.js (napi-rs) | `npm install wickra` | `examples/node/backtest.js` |
|
||||
| Browser / WASM | `npm install wickra-wasm` | `examples/wasm/index.html` |
|
||||
| Rust | `cargo add wickra` | `examples/rust/src/bin/backtest.rs` |
|
||||
| C / C++ (C ABI) | header + library, see [`bindings/c`](bindings/c) | `examples/c/streaming.c` |
|
||||
| C# / .NET (C ABI) | `dotnet add package Wickra`, see [`bindings/csharp`](bindings/csharp) | `examples/csharp/streaming` |
|
||||
| Go (cgo, C ABI) | `go get github.com/wickra-lib/wickra/bindings/go`, see [`bindings/go`](bindings/go) | `examples/go/streaming` |
|
||||
| Java (FFM, C ABI) | Maven Central `org.wickra:wickra`, see [`bindings/java`](bindings/java) | `examples/java` (`Streaming`) |
|
||||
| R (`.Call`, C ABI) | `R CMD INSTALL bindings/r`, see [`bindings/r`](bindings/r) | `examples/r/streaming.R` |
|
||||
|
||||
Each binding ships several runnable examples (streaming, backtest, live feed);
|
||||
[`examples/README.md`](examples/README.md) is the full cross-language index.
|
||||
|
||||
The wickra-core crate is `unsafe`-forbidden, so every binding inherits a
|
||||
memory-safe implementation.
|
||||
The wickra-core crate is `unsafe`-forbidden, so the native bindings are
|
||||
memory-safe end to end. The C ABI runs the same safe core; only its thin FFI
|
||||
boundary uses `unsafe`, and the caller owns handle lifetimes (`_new` / `_free`).
|
||||
|
||||
## Rust API
|
||||
|
||||
@@ -297,20 +255,30 @@ A Python live-trading example using the public `websockets` package lives at
|
||||
```
|
||||
wickra/
|
||||
├── crates/
|
||||
│ ├── wickra-core/ core engine + all 452 indicators
|
||||
│ ├── wickra-core/ core engine + all 514 indicators
|
||||
│ ├── wickra/ top-level facade crate (publishes on crates.io) + benches/
|
||||
│ ├── wickra-data/ CSV reader, tick aggregator, live exchange feeds
|
||||
│ └── wickra-bench/ internal cross-library benchmark harness (not published)
|
||||
├── bindings/
|
||||
│ ├── python/ PyO3 + maturin (publishes on PyPI)
|
||||
│ ├── node/ napi-rs (publishes on npm)
|
||||
│ └── wasm/ wasm-bindgen (browsers, bundlers, Node)
|
||||
│ ├── wasm/ wasm-bindgen (browsers, bundlers, Node)
|
||||
│ ├── c/ C ABI (cdylib + staticlib) + generated include/wickra.h
|
||||
│ ├── csharp/ .NET binding over the C ABI (publishes on NuGet)
|
||||
│ ├── go/ Go binding over the C ABI via cgo (module tag)
|
||||
│ ├── r/ R binding over the C ABI via .Call (R package)
|
||||
│ └── java/ Java binding over the C ABI via the FFM API (Maven Central)
|
||||
├── examples/ examples/README.md indexes every language
|
||||
│ ├── data/ real BTCUSDT OHLCV datasets, one per timeframe
|
||||
│ ├── rust/ Rust workspace member (`wickra-examples`)
|
||||
│ ├── python/ backtest, live trading, parallel assets, multi-tf
|
||||
│ ├── node/ streaming, backtest, live trading (load `wickra`)
|
||||
│ └── wasm/ browser demo for `wickra-wasm`
|
||||
│ ├── wasm/ browser demo for `wickra-wasm`
|
||||
│ ├── c/ C smoke + streaming, C++ RAII wrapper
|
||||
│ ├── csharp/ streaming, backtest, strategies (load `Wickra`)
|
||||
│ ├── go/ streaming, backtest, strategies (cgo binding)
|
||||
│ ├── r/ streaming, backtest, strategies (.Call binding)
|
||||
│ └── java/ streaming, backtest, strategies (FFM binding)
|
||||
└── .github/workflows/ CI and release pipelines
|
||||
```
|
||||
|
||||
@@ -338,6 +306,25 @@ wasm-pack build bindings/wasm --target web --release --features panic-hook
|
||||
|
||||
# Node binding (requires @napi-rs/cli)
|
||||
cd bindings/node && npm install && npm run build && npm test
|
||||
|
||||
# C ABI (cdylib + staticlib + generated header)
|
||||
cargo build -p wickra-c --release
|
||||
cmake -S examples/c -B examples/c/build -DWICKRA_LIB_DIR="$PWD/target/release"
|
||||
cmake --build examples/c/build && ctest --test-dir examples/c/build --output-on-failure
|
||||
|
||||
# C# / .NET binding (requires the .NET 8 SDK; links the C ABI above)
|
||||
dotnet test bindings/csharp/Wickra.Tests/Wickra.Tests.csproj
|
||||
|
||||
# Go binding (requires a C compiler for cgo; links the C ABI above)
|
||||
cp target/release/libwickra.so bindings/go/lib/ # .dylib on macOS, wickra.dll on Windows
|
||||
cd bindings/go && go test ./...
|
||||
|
||||
# R binding (requires a C toolchain / Rtools; links the C ABI above)
|
||||
WICKRA_INCLUDE_DIR="$PWD/bindings/c/include" WICKRA_LIB_DIR="$PWD/target/release" \
|
||||
R CMD INSTALL bindings/r
|
||||
|
||||
# Java binding (requires JDK 22+ and Maven; links the C ABI above)
|
||||
mvn -f bindings/java test
|
||||
```
|
||||
|
||||
## Testing
|
||||
@@ -347,7 +334,10 @@ Every layer is covered; run the suites with the commands in
|
||||
|
||||
- `wickra-core`: unit tests per indicator — textbook reference values
|
||||
(Wilder RSI, Bollinger Bands, MACD, ATR, Stochastic), `batch == streaming`
|
||||
equivalence, `reset` semantics, NaN/Inf handling, and property tests.
|
||||
equivalence, `reset` semantics, NaN/Inf handling, and property tests. A
|
||||
catalogue-wide property harness (`tests/invariants.rs`) additionally asserts
|
||||
`batch == streaming`, `reset == fresh`, and non-finite-input rejection for
|
||||
**every** indicator and bar-builder.
|
||||
- `wickra-data`: unit tests for CSV decoding, the tick aggregator, the
|
||||
resampler, and the Binance payload parser.
|
||||
- `bindings/python`: pytest covering smoke checks, streaming/batch
|
||||
@@ -357,6 +347,22 @@ Every layer is covered; run the suites with the commands in
|
||||
values across all indicators.
|
||||
- `bindings/wasm`: `wasm-bindgen-test` cases for constructors, equivalence,
|
||||
and reference values.
|
||||
- `bindings/c`: Rust unit tests over the FFI boundary, plus C and C++ smoke
|
||||
tests and offline example `ctest`s run on the three OSes.
|
||||
- `bindings/csharp`: `dotnet test` cases covering one indicator per FFI archetype
|
||||
(scalar/batch, multi-output, bars, profile, array input) plus SMA reference values.
|
||||
- `bindings/go`: `go test` cases covering one indicator per FFI archetype
|
||||
(scalar/batch, multi-output, bars, profile, array input), reset, and lifecycle.
|
||||
- `bindings/r`: `testthat` cases covering one indicator per FFI archetype
|
||||
(scalar/batch, multi-output, bars, profile, array input), reset, and validation.
|
||||
- `bindings/java`: JUnit cases covering one indicator per FFI archetype
|
||||
(scalar/batch, multi-output, bars, profile, array input) plus batch equivalence.
|
||||
|
||||
The four C-ABI bindings (C#, Go, Java, R) additionally replay a shared,
|
||||
language-neutral golden fixture (`testdata/golden/*.csv`, generated by
|
||||
`cargo run -p wickra-examples --bin gen_golden`) and assert exact parity with the
|
||||
Rust reference outputs across every archetype (SMA, EMA, RSI, ATR, MACD, ADX,
|
||||
Beta), catching FFI wiring bugs the math-only core tests cannot see.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
+3
-2
@@ -21,8 +21,9 @@ minor releases; breaking changes are called out in the changelog.
|
||||
versioning stability for a 1.0 release.
|
||||
- **Performance.** Keep per-tick updates O(1) and maintain the benchmark suite;
|
||||
investigate further allocation and cache improvements.
|
||||
- **Bindings parity.** Keep the Python, Node.js and WebAssembly bindings in
|
||||
lockstep with the Rust core, including type stubs and platform coverage.
|
||||
- **Bindings parity.** Keep the Python, Node.js and WebAssembly bindings — plus
|
||||
the C ABI and the C# / .NET, Go, Java and R bindings generated from it — in lockstep with the
|
||||
Rust core, including type stubs and platform coverage.
|
||||
- **Documentation.** Maintain a deep-dive page per indicator on
|
||||
<https://docs.wickra.org>, plus quickstarts and cookbook material.
|
||||
- **Project health.** Maintain test coverage, static and dynamic analysis,
|
||||
|
||||
+8
-4
@@ -2,13 +2,13 @@
|
||||
|
||||
## Supported versions
|
||||
|
||||
Wickra is pre-1.0. Security fixes are applied to the latest released `0.5.x`
|
||||
Wickra is pre-1.0. Security fixes are applied to the latest released `0.8.5`
|
||||
version only; please upgrade to the newest release before reporting an issue.
|
||||
|
||||
| Version | Supported |
|
||||
| --- | --- |
|
||||
| 0.5.x (latest) | :white_check_mark: |
|
||||
| older 0.5.x | :x: |
|
||||
| 0.8.5 (latest) | :white_check_mark: |
|
||||
| < 0.8.5 | :x: |
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
@@ -58,7 +58,11 @@ artifacts, and (4) a healthy dependency supply chain.
|
||||
|
||||
- *Memory safety* — the core and all bindings are written in Rust. The crates
|
||||
forbid or minimise `unsafe`, so the compiler guarantees memory and thread
|
||||
safety for the indicator logic.
|
||||
safety for the indicator logic. The one exception is the C ABI
|
||||
([`bindings/c`](bindings/c)), whose thin FFI shim is necessarily `unsafe`
|
||||
because it dereferences caller-supplied pointers; it adds no indicator logic,
|
||||
validates every handle for NULL, and never lets a panic cross the boundary, so
|
||||
the safe core's guarantees still cover all computation.
|
||||
- *Input robustness* — every indicator validates its parameters and rejects
|
||||
non-finite inputs at construction; behaviour on edge cases (flat markets,
|
||||
warmup, reset) is pinned by unit tests, and the public update paths are
|
||||
|
||||
+2
-2
@@ -7,8 +7,8 @@ Thanks for using Wickra! Here is where to get help, depending on what you need.
|
||||
Most questions are answered in the documentation:
|
||||
|
||||
- **Docs site:** <https://docs.wickra.org> — quickstarts for Rust, Python,
|
||||
Node.js and WebAssembly, a per-indicator reference, warmup periods, the data
|
||||
layer, and an FAQ.
|
||||
Node.js, WebAssembly, C, C#, Go, Java and R, a per-indicator reference, warmup periods, the
|
||||
data layer, and an FAQ.
|
||||
- **README:** <https://github.com/wickra-lib/wickra#readme> — installation and a
|
||||
quick overview.
|
||||
- **API docs (Rust):** <https://docs.rs/wickra>.
|
||||
|
||||
+5
-2
@@ -3,8 +3,10 @@
|
||||
This document describes Wickra's attack surface and the threats considered,
|
||||
together with their mitigations. It complements the security assurance case in
|
||||
[`SECURITY.md`](SECURITY.md). Wickra is a computational technical-analysis
|
||||
library (a Rust core with Python, Node.js and WebAssembly bindings), not a
|
||||
network service or trading system; the attack surface is correspondingly small.
|
||||
library (a Rust core with Python, Node.js and WebAssembly bindings plus a C ABI
|
||||
and the .NET, Go, Java and R bindings built on it),
|
||||
not a network service or trading system; the attack surface is correspondingly
|
||||
small.
|
||||
|
||||
## Assets
|
||||
|
||||
@@ -31,6 +33,7 @@ network service or trading system; the attack surface is correspondingly small.
|
||||
| Threat | Mitigation |
|
||||
| --- | --- |
|
||||
| Memory-safety exploit (buffer overflow, UAF) via crafted input | Pure safe Rust; `unsafe` is forbidden/minimised, so the compiler precludes these classes. |
|
||||
| Misuse of the C ABI FFI boundary (invalid/dangling handle, undersized batch buffer) | The C ABI (`bindings/c`) is the sole `unsafe` surface. Its shim adds no logic, NULL-checks every handle (returning `NaN`/no-op), writes only into caller-sized buffers, and is built with `panic = "abort"` so a panic terminates the process deterministically instead of unwinding across the FFI boundary (which would be undefined behaviour). A caller passing a non-NULL but dangling pointer is undefined behaviour by C's own contract — out of scope, the same as any C library. |
|
||||
| Denial of service via malformed/degenerate input (NaN, infinities, extreme magnitudes) | Indicators reject non-finite inputs and validate parameters at construction; update paths are exercised by coverage-guided fuzzing and unit tests for edge cases. |
|
||||
| Silently incorrect results | 100% line coverage on the core crate; reference-value tests against known-good sources; streaming/batch parity tests. |
|
||||
| Integer overflow / panics | `clippy::pedantic` with `-D warnings`; debug assertions and overflow checks enabled in test/fuzz builds. |
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
[package]
|
||||
name = "wickra-c"
|
||||
description = "C ABI (cdylib + staticlib) for the Wickra streaming-first technical indicators library — the hub every C-capable language (C, C++, Go, C#, Java, R) links against."
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
readme = "README.md"
|
||||
keywords.workspace = true
|
||||
categories.workspace = true
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "wickra"
|
||||
crate-type = ["cdylib", "staticlib"]
|
||||
|
||||
# The C ABI inherently needs `unsafe` (raw pointers across the FFI boundary,
|
||||
# `#[export_name]` symbol control). The workspace forbids `unsafe_code`, so this
|
||||
# crate cannot inherit `workspace = true`; it mirrors every workspace lint and
|
||||
# only relaxes `unsafe_code` to `allow` (parity with how the proc-macro bindings
|
||||
# emit their unsafe). The Rust core stays `unsafe`-forbidden — this is the one
|
||||
# crate where the boundary lives.
|
||||
[lints.rust]
|
||||
unsafe_code = "allow"
|
||||
missing_debug_implementations = "warn"
|
||||
unreachable_pub = "warn"
|
||||
unused_must_use = "deny"
|
||||
|
||||
[lints.clippy]
|
||||
all = { level = "warn", priority = -1 }
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
module_name_repetitions = "allow"
|
||||
must_use_candidate = "allow"
|
||||
missing_errors_doc = "allow"
|
||||
missing_panics_doc = "allow"
|
||||
cast_precision_loss = "allow"
|
||||
cast_possible_truncation = "allow"
|
||||
cast_sign_loss = "allow"
|
||||
similar_names = "allow"
|
||||
float_cmp = "allow"
|
||||
|
||||
[features]
|
||||
# `parallel` (rayon-backed batch in wickra-core) is on by default for native
|
||||
# builds. The wasm32-unknown-emscripten target has no threads, so the R
|
||||
# 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"]
|
||||
parallel = ["wickra-core/parallel"]
|
||||
|
||||
[dependencies]
|
||||
# Direct path dep rather than `workspace = true`: a member-level
|
||||
# `default-features = false` is ignored when inheriting a workspace dep that
|
||||
# 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 }
|
||||
@@ -0,0 +1,94 @@
|
||||
# Wickra — C / C++
|
||||
|
||||
[](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml)
|
||||
[](https://codecov.io/gh/wickra-lib/wickra)
|
||||
[](https://github.com/wickra-lib/wickra/releases/latest)
|
||||
[](https://github.com/wickra-lib/wickra#license)
|
||||
|
||||
**Streaming-first technical indicators for C and C++. A prebuilt shared/static
|
||||
library plus a generated `wickra.h` — no system dependencies.**
|
||||
|
||||
Wickra is a multi-language technical-analysis library with a Rust core and
|
||||
bindings for Python, Node.js and WebAssembly, plus a C ABI for C/C++, C#, Go, Java, R and any
|
||||
other C-capable language. Every indicator is an O(1)
|
||||
streaming state machine, so live trading bots and historical backtests share
|
||||
the exact same implementation. This package is the **C ABI hub**: it compiles the
|
||||
core to a C-compatible shared/static library plus a generated header, so any
|
||||
C-capable language (C, C++, Go, C#, Java, R) links against one artifact instead
|
||||
of re-wrapping every indicator natively.
|
||||
|
||||
## Install
|
||||
|
||||
Grab the prebuilt header + library for your platform from the
|
||||
[GitHub releases](https://github.com/wickra-lib/wickra/releases) — each archive
|
||||
has `wickra.h`, the optional `wickra.hpp` C++ wrapper, and the shared/static
|
||||
library — or build from source:
|
||||
|
||||
```bash
|
||||
cargo build -p wickra-c --release
|
||||
# -> target/release/libwickra.{so,dylib} or wickra.dll (+ import lib) + a staticlib
|
||||
```
|
||||
|
||||
Then compile against the header and link the library
|
||||
(`cc app.c -I include -L lib -lwickra -lm -o app`).
|
||||
|
||||
## Quick start
|
||||
|
||||
```c
|
||||
#include "wickra.h"
|
||||
|
||||
struct Rsi *rsi = wickra_rsi_new(14); /* NULL on invalid params */
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
double v = wickra_rsi_update(rsi, prices[i]); /* NaN during warmup */
|
||||
if (v == v && v > 70.0) /* v == v is the NaN check */
|
||||
printf("overbought\n");
|
||||
}
|
||||
wickra_rsi_free(rsi); /* exactly once per _new */
|
||||
```
|
||||
|
||||
Every indicator is an opaque handle with the same five functions —
|
||||
`_new` / `_update` / `_batch` / `_reset` / `_free`. `update` is O(1); there is no
|
||||
RAII across the C boundary, so each `_new` needs exactly one `_free`, and every
|
||||
function is NULL-safe (a NULL handle yields `NaN` or a no-op, never a crash).
|
||||
Multi-output indicators (MACD, Bollinger, ADX, …) take a pointer to a `#[repr(C)]`
|
||||
struct and return a `bool`. The optional `wickra.hpp` wraps any handle in a
|
||||
move-only `wickra::Handle` for exception-safe C++ lifetimes.
|
||||
|
||||
## Benchmark
|
||||
|
||||
`benchmarks/throughput.c` reports streaming and batch updates-per-second for
|
||||
`SMA`, `ATR` and `MACD`. As the thinnest binding it is the floor of the
|
||||
per-binding FFI overhead — not a cross-library ratio (the same Rust core runs
|
||||
under every binding); see the repository
|
||||
[BENCHMARKS.md](https://github.com/wickra-lib/wickra/blob/main/BENCHMARKS.md) §3.
|
||||
|
||||
```bash
|
||||
cargo build -p wickra-c --release
|
||||
cmake -S benchmarks -B build && cmake --build build
|
||||
./build/throughput
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
The full indicator catalogue, guides, quickstarts, and API reference live in
|
||||
the main repository and documentation site:
|
||||
|
||||
- **Repository & full indicator list:** <https://github.com/wickra-lib/wickra>
|
||||
- **Docs** (C quickstart, cookbook, TA-Lib migration): <https://docs.wickra.org/Quickstart-C>
|
||||
- **Runnable examples:** [`examples/c/`](https://github.com/wickra-lib/wickra/tree/main/examples/c)
|
||||
|
||||
Wickra ships native bindings for Python, Node.js, WebAssembly and Rust, plus this
|
||||
C ABI hub that any C-capable language (C, C++, Go, C#, Java, R) links against —
|
||||
all exposing the same indicators from the shared Rust core.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
Wickra is an indicator toolkit, not a trading system. The values it computes
|
||||
are deterministic transforms of the input data — they are not financial advice
|
||||
and do not predict the market. Any use in a live trading context is at your own
|
||||
risk. The library is provided **as is**, without warranty of any kind.
|
||||
|
||||
## License
|
||||
|
||||
Licensed under either of [Apache-2.0](https://github.com/wickra-lib/wickra/blob/main/LICENSE-APACHE)
|
||||
or [MIT](https://github.com/wickra-lib/wickra/blob/main/LICENSE-MIT) at your option.
|
||||
@@ -0,0 +1,40 @@
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
project(wickra_c_benchmarks C)
|
||||
|
||||
# Directory holding the compiled Wickra C library (cargo output), e.g.
|
||||
# <workspace>/target/release. Override with -DWICKRA_LIB_DIR=/path/to/target/release.
|
||||
if(NOT DEFINED WICKRA_LIB_DIR)
|
||||
set(WICKRA_LIB_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../target/release")
|
||||
endif()
|
||||
set(WICKRA_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../include")
|
||||
|
||||
# Pick the right link target per platform/toolchain.
|
||||
# - MSVC links the generated import library (wickra.dll.lib).
|
||||
# - MinGW/gcc on Windows links the DLL directly.
|
||||
# - Unix links the shared object / dylib.
|
||||
if(WIN32)
|
||||
set(WICKRA_RUNTIME "${WICKRA_LIB_DIR}/wickra.dll")
|
||||
if(MSVC)
|
||||
set(WICKRA_LINK_LIB "${WICKRA_LIB_DIR}/wickra.dll.lib")
|
||||
else()
|
||||
set(WICKRA_LINK_LIB "${WICKRA_LIB_DIR}/wickra.dll")
|
||||
endif()
|
||||
elseif(APPLE)
|
||||
set(WICKRA_LINK_LIB "${WICKRA_LIB_DIR}/libwickra.dylib")
|
||||
else()
|
||||
set(WICKRA_LINK_LIB "${WICKRA_LIB_DIR}/libwickra.so")
|
||||
endif()
|
||||
|
||||
add_executable(throughput throughput.c)
|
||||
target_include_directories(throughput PRIVATE "${WICKRA_INCLUDE_DIR}")
|
||||
target_link_libraries(throughput PRIVATE "${WICKRA_LINK_LIB}")
|
||||
if(UNIX AND NOT APPLE)
|
||||
target_link_libraries(throughput PRIVATE m)
|
||||
endif()
|
||||
|
||||
# On Windows copy the DLL next to the executable so the loader finds it.
|
||||
if(WIN32)
|
||||
add_custom_command(TARGET throughput POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${WICKRA_RUNTIME}" "$<TARGET_FILE_DIR:throughput>")
|
||||
endif()
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Throughput benchmark for the Wickra C ABI.
|
||||
*
|
||||
* Measures how many indicator updates per second the C ABI sustains, both
|
||||
* per-tick (streaming `_update`) and bulk (`_batch`), over a synthetic OHLCV
|
||||
* series. It is the C counterpart of the Node throughput.js and the Rust
|
||||
* criterion benches: it benchmarks Wickra's own O(1) streaming engine through
|
||||
* the raw C boundary (there is no comparable streaming TA library to compare
|
||||
* against), so the headline number is raw throughput, not a cross-library
|
||||
* ratio. C is the thinnest binding, so these numbers are the floor of the
|
||||
* per-binding FFI overhead the higher-level bindings build on.
|
||||
*
|
||||
* Three indicators are timed, chosen by call-signature archetype rather than
|
||||
* algorithm: SMA (1-in -> 1-out), ATR (multi-in -> 1-out), and MACD
|
||||
* (1-in -> multi-out). Streaming is timed for all three; batch only for the
|
||||
* single-output SMA and ATR (the C ABI has no MACD batch entry point).
|
||||
*
|
||||
* Build the C ABI library first, then build and run the benchmark:
|
||||
*
|
||||
* cargo build -p wickra-c --release
|
||||
* cmake -S bindings/c/benchmarks -B build/cbench -DCMAKE_BUILD_TYPE=Release
|
||||
* cmake --build build/cbench
|
||||
* ./build/cbench/throughput # 200k bars (default)
|
||||
* ./build/cbench/throughput 1000000
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "wickra.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
static double now_ns(void) {
|
||||
static LARGE_INTEGER freq;
|
||||
static int init = 0;
|
||||
LARGE_INTEGER counter;
|
||||
if (!init) {
|
||||
QueryPerformanceFrequency(&freq);
|
||||
init = 1;
|
||||
}
|
||||
QueryPerformanceCounter(&counter);
|
||||
return (double)counter.QuadPart * 1e9 / (double)freq.QuadPart;
|
||||
}
|
||||
#else
|
||||
#include <time.h>
|
||||
static double now_ns(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (double)ts.tv_sec * 1e9 + (double)ts.tv_nsec;
|
||||
}
|
||||
#endif
|
||||
|
||||
static double median3(double a, double b, double c) {
|
||||
if ((a <= b && b <= c) || (c <= b && b <= a)) return b;
|
||||
if ((b <= a && a <= c) || (c <= a && a <= b)) return a;
|
||||
return c;
|
||||
}
|
||||
|
||||
/* Run `body` once as warmup, then time three repetitions and store the median
|
||||
* elapsed nanoseconds in `dst`. `body` is a brace-enclosed statement block. */
|
||||
#define MEASURE(dst, body) \
|
||||
do { \
|
||||
body; \
|
||||
double s0, s1, s2, t0; \
|
||||
t0 = now_ns(); body; s0 = now_ns() - t0; \
|
||||
t0 = now_ns(); body; s1 = now_ns() - t0; \
|
||||
t0 = now_ns(); body; s2 = now_ns() - t0; \
|
||||
(dst) = median3(s0, s1, s2); \
|
||||
} while (0)
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
size_t bars = 200000;
|
||||
if (argc > 1) {
|
||||
long n = strtol(argv[1], NULL, 10);
|
||||
if (n >= 1000) {
|
||||
bars = (size_t)n;
|
||||
}
|
||||
}
|
||||
const size_t n = bars;
|
||||
|
||||
/* Deterministic synthetic OHLCV (no RNG, so runs are comparable). */
|
||||
double *open = malloc(n * sizeof(double));
|
||||
double *high = malloc(n * sizeof(double));
|
||||
double *low = malloc(n * sizeof(double));
|
||||
double *close = malloc(n * sizeof(double));
|
||||
double *volume = malloc(n * sizeof(double));
|
||||
int64_t *timestamp = malloc(n * sizeof(int64_t));
|
||||
double *out = malloc(n * sizeof(double)); /* reused batch scratch buffer */
|
||||
if (!open || !high || !low || !close || !volume || !timestamp || !out) {
|
||||
fprintf(stderr, "allocation failed\n");
|
||||
return 1;
|
||||
}
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
double mid = 100 + sin((double)i * 0.001) * 20 + (double)i * 1e-4;
|
||||
double c = mid + sin((double)i * 0.05) * 2;
|
||||
close[i] = c;
|
||||
open[i] = mid;
|
||||
high[i] = fmax(c, mid) + 1.5;
|
||||
low[i] = fmin(c, mid) - 1.5;
|
||||
volume[i] = 1000 + (double)(i % 97) * 13;
|
||||
timestamp[i] = (int64_t)i;
|
||||
}
|
||||
|
||||
double ns;
|
||||
double sma_stream, sma_batch, atr_stream, atr_batch, macd_stream;
|
||||
|
||||
MEASURE(ns, {
|
||||
struct Sma *ind = wickra_sma_new(20);
|
||||
for (size_t i = 0; i < n; i++) wickra_sma_update(ind, close[i]);
|
||||
wickra_sma_free(ind);
|
||||
});
|
||||
sma_stream = (double)n / (ns / 1e9) / 1e6;
|
||||
|
||||
MEASURE(ns, {
|
||||
struct Sma *ind = wickra_sma_new(20);
|
||||
wickra_sma_batch(ind, close, out, n);
|
||||
wickra_sma_free(ind);
|
||||
});
|
||||
sma_batch = (double)n / (ns / 1e9) / 1e6;
|
||||
|
||||
MEASURE(ns, {
|
||||
struct Atr *ind = wickra_atr_new(14);
|
||||
for (size_t i = 0; i < n; i++)
|
||||
wickra_atr_update(ind, open[i], high[i], low[i], close[i], volume[i], timestamp[i]);
|
||||
wickra_atr_free(ind);
|
||||
});
|
||||
atr_stream = (double)n / (ns / 1e9) / 1e6;
|
||||
|
||||
MEASURE(ns, {
|
||||
struct Atr *ind = wickra_atr_new(14);
|
||||
wickra_atr_batch(ind, open, high, low, close, volume, timestamp, out, n);
|
||||
wickra_atr_free(ind);
|
||||
});
|
||||
atr_batch = (double)n / (ns / 1e9) / 1e6;
|
||||
|
||||
MEASURE(ns, {
|
||||
struct MacdIndicator *ind = wickra_macd_indicator_new(12, 26, 9);
|
||||
struct WickraMacdOutput value;
|
||||
for (size_t i = 0; i < n; i++) wickra_macd_indicator_update(ind, close[i], &value);
|
||||
wickra_macd_indicator_free(ind);
|
||||
});
|
||||
macd_stream = (double)n / (ns / 1e9) / 1e6;
|
||||
|
||||
printf("Wickra C throughput - %zu bars (median of 3 runs)\n\n", n);
|
||||
printf("%-22s%20s%18s\n", "Indicator", "streaming (Mupd/s)", "batch (Mupd/s)");
|
||||
printf("------------------------------------------------------------\n");
|
||||
printf("%-22s%20.1f%18.1f\n", "SMA(20)", sma_stream, sma_batch);
|
||||
printf("%-22s%20.1f%18.1f\n", "ATR(14)", atr_stream, atr_batch);
|
||||
printf("%-22s%20.1f%18s\n", "MACD(12,26,9)", macd_stream, "-");
|
||||
|
||||
printf("\nMupd/s = million indicator updates per second. Streaming is the per-tick\n"
|
||||
"`_update` path (one C call per value); batch is the bulk array path (one\n"
|
||||
"C call). Higher is better. Numbers are machine-dependent - use them for\n"
|
||||
"relative comparison, not as a speed claim.\n");
|
||||
|
||||
free(open);
|
||||
free(high);
|
||||
free(low);
|
||||
free(close);
|
||||
free(volume);
|
||||
free(timestamp);
|
||||
free(out);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
language = "C"
|
||||
header = "/* Wickra C ABI — generated by cbindgen. Do not edit by hand. */"
|
||||
include_guard = "WICKRA_H"
|
||||
pragma_once = true
|
||||
# Wrap the declarations in `extern "C"` under __cplusplus so the header is usable
|
||||
# from C++ (the optional wickra.hpp RAII layer and any C++ consumer).
|
||||
cpp_compat = true
|
||||
tab_width = 4
|
||||
# Off: cbindgen copies the Rust struct/fn doc comments verbatim, and some core
|
||||
# indicator docs contain markdown (e.g. `**1/8**/**7/8**`) whose `*/` would close
|
||||
# the C block comment early and break the header. Usage docs live in the crate
|
||||
# README and examples; the header is a pure declaration contract.
|
||||
documentation = false
|
||||
|
||||
[parse]
|
||||
# Parse wickra-core too so the opaque indicator handle types (Sma, Ema, …) are
|
||||
# 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"]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,66 @@
|
||||
// Optional C++ convenience layer over the Wickra C ABI (`wickra.h`).
|
||||
//
|
||||
// The C ABI hands out raw handles that must be released exactly once with the
|
||||
// matching `wickra_<ind>_free`. `wickra::Handle` wraps that in a move-only RAII
|
||||
// owner so the free happens automatically at scope exit:
|
||||
//
|
||||
// #include "wickra.hpp"
|
||||
//
|
||||
// wickra::Handle<Sma, wickra_sma_free> sma(wickra_sma_new(14));
|
||||
// if (sma) {
|
||||
// double v = wickra_sma_update(sma.get(), 42.0); // NaN during warmup
|
||||
// }
|
||||
// // sma is freed here
|
||||
//
|
||||
// This is header-only and adds no runtime cost beyond the C calls themselves.
|
||||
|
||||
#ifndef WICKRA_HPP
|
||||
#define WICKRA_HPP
|
||||
|
||||
#include "wickra.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace wickra {
|
||||
|
||||
/// Move-only RAII owner of a Wickra handle. `T` is the opaque indicator type and
|
||||
/// `Free` its `wickra_<ind>_free` function.
|
||||
template <typename T, void (*Free)(T *)>
|
||||
class Handle {
|
||||
public:
|
||||
explicit Handle(T *ptr) noexcept : ptr_(ptr) {}
|
||||
|
||||
~Handle() {
|
||||
if (ptr_ != nullptr) {
|
||||
Free(ptr_);
|
||||
}
|
||||
}
|
||||
|
||||
Handle(const Handle &) = delete;
|
||||
Handle &operator=(const Handle &) = delete;
|
||||
|
||||
Handle(Handle &&other) noexcept : ptr_(std::exchange(other.ptr_, nullptr)) {}
|
||||
|
||||
Handle &operator=(Handle &&other) noexcept {
|
||||
if (this != &other) {
|
||||
if (ptr_ != nullptr) {
|
||||
Free(ptr_);
|
||||
}
|
||||
ptr_ = std::exchange(other.ptr_, nullptr);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// The raw handle, for passing to the `wickra_<ind>_*` functions.
|
||||
T *get() const noexcept { return ptr_; }
|
||||
|
||||
/// True if the handle is non-null (construction succeeded).
|
||||
explicit operator bool() const noexcept { return ptr_ != nullptr; }
|
||||
|
||||
private:
|
||||
T *ptr_;
|
||||
};
|
||||
|
||||
} // namespace wickra
|
||||
|
||||
#endif // WICKRA_HPP
|
||||
+44290
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
# .NET build output
|
||||
bin/
|
||||
obj/
|
||||
*.user
|
||||
|
||||
# NuGet packaging output
|
||||
*.nupkg
|
||||
*.snupkg
|
||||
|
||||
# Native libraries staged for packaging (produced by the release pipeline from
|
||||
# the wickra-c-<triple>.tar.gz assets; never committed to source).
|
||||
Wickra/runtimes/
|
||||
@@ -0,0 +1,90 @@
|
||||
# Wickra — .NET
|
||||
|
||||
[](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml)
|
||||
[](https://codecov.io/gh/wickra-lib/wickra)
|
||||
[](https://www.nuget.org/packages/Wickra)
|
||||
[](https://github.com/wickra-lib/wickra#license)
|
||||
|
||||
**Streaming-first technical indicators for .NET. `dotnet add package Wickra` —
|
||||
prebuilt native library, no system dependencies.**
|
||||
|
||||
Wickra is a multi-language technical-analysis library with a Rust core and
|
||||
bindings for Python, Node.js and WebAssembly, plus a C ABI for C/C++, C#, Go, Java, R and any
|
||||
other C-capable language. Every indicator is an O(1)
|
||||
streaming state machine, so live trading bots and historical backtests share
|
||||
the exact same implementation. This package is the .NET binding; it consumes the
|
||||
C ABI hub through `[LibraryImport]` P/Invoke and exposes all 514 streaming-first
|
||||
indicators as idiomatic `IDisposable` classes.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
dotnet add package Wickra
|
||||
```
|
||||
|
||||
The native library ships prebuilt per platform (Linux, macOS, Windows — x64 and
|
||||
arm64) under `runtimes/<rid>/native/`, selected automatically. There is nothing
|
||||
to compile. Targets .NET 8 and later.
|
||||
|
||||
## Quick start
|
||||
|
||||
```csharp
|
||||
using Wickra;
|
||||
|
||||
// Batch: run an indicator over a whole series (NaN at warmup positions).
|
||||
var prices = Enumerable.Range(0, 1000).Select(i => 100.0 + i * 0.1).ToArray();
|
||||
using var sma = new Sma(20);
|
||||
double[] values = sma.Batch(prices);
|
||||
|
||||
// Streaming: the same indicator, fed tick by tick in O(1).
|
||||
using var rsi = new Rsi(14);
|
||||
foreach (var price in liveFeed)
|
||||
{
|
||||
var value = rsi.Update(price); // NaN during warmup, no recomputation
|
||||
if (double.IsFinite(value) && value > 70)
|
||||
{
|
||||
Console.WriteLine("overbought");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Batch(prices)` and feeding the same prices through `Update()` produce identical
|
||||
values — the equivalence is enforced by the test suite. Multi-output indicators
|
||||
(MACD, Bollinger, ADX, …) return a nullable `record struct`, `null` while warming up.
|
||||
|
||||
## Benchmark
|
||||
|
||||
`benchmarks/` reports streaming and batch updates-per-second for `SMA`, `ATR`
|
||||
and `MACD`. It measures this binding's FFI overhead, not a cross-library ratio
|
||||
(the same Rust core runs under every binding) — see the repository
|
||||
[BENCHMARKS.md](https://github.com/wickra-lib/wickra/blob/main/BENCHMARKS.md) §3.
|
||||
|
||||
```bash
|
||||
cargo build -p wickra-c --release
|
||||
dotnet run -c Release --project benchmarks
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
The full indicator catalogue, guides, quickstarts, and API reference live in
|
||||
the main repository and documentation site:
|
||||
|
||||
- **Repository & full indicator list:** <https://github.com/wickra-lib/wickra>
|
||||
- **Docs** (quickstarts, cookbook, TA-Lib migration): <https://docs.wickra.org>
|
||||
- **Runnable examples:** [`examples/csharp/`](https://github.com/wickra-lib/wickra/tree/main/examples/csharp)
|
||||
|
||||
Wickra ships native bindings for Python, Node.js, WebAssembly and Rust, plus a
|
||||
C ABI hub that any C-capable language (C, C++, Go, C#, Java, R) links against —
|
||||
all exposing the same indicators from the shared, `unsafe`-forbidden Rust core.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
Wickra is an indicator toolkit, not a trading system. The values it computes
|
||||
are deterministic transforms of the input data — they are not financial advice
|
||||
and do not predict the market. Any use in a live trading context is at your own
|
||||
risk. The library is provided **as is**, without warranty of any kind.
|
||||
|
||||
## License
|
||||
|
||||
Licensed under either of [Apache-2.0](https://github.com/wickra-lib/wickra/blob/main/LICENSE-APACHE)
|
||||
or [MIT](https://github.com/wickra-lib/wickra/blob/main/LICENSE-MIT) at your option.
|
||||
@@ -0,0 +1,144 @@
|
||||
using Wickra;
|
||||
using Xunit;
|
||||
|
||||
namespace Wickra.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// One representative per FFI archetype, exercising every marshalling path the
|
||||
/// generator produces (scalar, candle, pairwise, multi-output, bars, profile,
|
||||
/// values-profile, array-input). Garbage marshalling surfaces as NaN, wild
|
||||
/// values, or crashes — so finite/sane assertions are the real check.
|
||||
/// </summary>
|
||||
public class ArchetypeTests
|
||||
{
|
||||
private static (double open, double high, double low, double close, double volume, long ts) Candle(int i)
|
||||
{
|
||||
var close = 100.0 + 10.0 * Math.Sin(i * 0.3);
|
||||
var open = 100.0 + 10.0 * Math.Sin((i - 1) * 0.3);
|
||||
var high = Math.Max(open, close) + 1.0;
|
||||
var low = Math.Min(open, close) - 1.0;
|
||||
return (open, high, low, close, 1_000.0, i * 60_000L);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Scalar_Ema_IsFiniteAfterWarmup()
|
||||
{
|
||||
using var ema = new Ema(3);
|
||||
double last = double.NaN;
|
||||
for (var i = 1; i <= 10; i++)
|
||||
{
|
||||
last = ema.Update(i);
|
||||
}
|
||||
|
||||
Assert.True(double.IsFinite(last));
|
||||
Assert.InRange(last, 1.0, 10.0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Candle_Atr_IsFinitePositive()
|
||||
{
|
||||
using var atr = new Atr(3);
|
||||
double last = double.NaN;
|
||||
for (var i = 0; i < 20; i++)
|
||||
{
|
||||
var (o, h, l, c, v, ts) = Candle(i);
|
||||
last = atr.Update(o, h, l, c, v, ts);
|
||||
}
|
||||
|
||||
Assert.True(double.IsFinite(last));
|
||||
Assert.True(last > 0.0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Pairwise_Beta_IsFinite()
|
||||
{
|
||||
using var beta = new Beta(5);
|
||||
double last = double.NaN;
|
||||
for (var i = 0; i < 30; i++)
|
||||
{
|
||||
var market = 100.0 + 10.0 * Math.Sin(i * 0.5);
|
||||
var asset = 50.0 + 6.0 * Math.Sin(i * 0.5 + 0.2);
|
||||
last = beta.Update(market, asset);
|
||||
}
|
||||
|
||||
Assert.True(double.IsFinite(last));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultiOutput_Adx_ReturnsFiniteStruct()
|
||||
{
|
||||
using var adx = new Adx(5);
|
||||
AdxOutput? result = null;
|
||||
for (var i = 0; i < 60; i++)
|
||||
{
|
||||
var (o, h, l, c, v, ts) = Candle(i);
|
||||
result = adx.Update(o, h, l, c, v, ts);
|
||||
}
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.True(double.IsFinite(result!.Value.Adx));
|
||||
Assert.True(double.IsFinite(result.Value.PlusDi));
|
||||
Assert.True(double.IsFinite(result.Value.MinusDi));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Bars_DollarBars_EmitsBars()
|
||||
{
|
||||
using var bars = new DollarBars(5_000.0);
|
||||
var total = 0;
|
||||
for (var i = 0; i < 200; i++)
|
||||
{
|
||||
var (o, h, l, c, v, ts) = Candle(i);
|
||||
total += bars.Update(o, h, l, c, v, ts).Length;
|
||||
}
|
||||
|
||||
Assert.True(total > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Profile_VolumeProfile_ReturnsValues()
|
||||
{
|
||||
using var profile = new VolumeProfile(20, 8);
|
||||
VolumeProfileOutputScalars? result = null;
|
||||
for (var i = 0; i < 60; i++)
|
||||
{
|
||||
var (o, h, l, c, v, ts) = Candle(i);
|
||||
result = profile.Update(o, h, l, c, v, ts);
|
||||
}
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.NotNull(result!.Value.Values);
|
||||
Assert.True(result.Value.PriceLow <= result.Value.PriceHigh);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProfileValues_DayOfWeekProfile_NoCrash()
|
||||
{
|
||||
using var profile = new DayOfWeekProfile(0);
|
||||
double[]? result = null;
|
||||
for (var i = 0; i < 60; i++)
|
||||
{
|
||||
var close = 100.0 + 5.0 * Math.Sin(i * 0.2);
|
||||
// one day apart so the day-of-week buckets fill
|
||||
result = profile.Update(close, close + 1, close - 1, close, 1_000.0, i * 86_400_000L);
|
||||
}
|
||||
|
||||
if (result is not null)
|
||||
{
|
||||
Assert.All(result, v => Assert.True(double.IsFinite(v)));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayInput_DepthSlope_IsFinite()
|
||||
{
|
||||
using var slope = new DepthSlope();
|
||||
ReadOnlySpan<double> bidPrice = stackalloc double[] { 99.0, 98.0, 97.0 };
|
||||
ReadOnlySpan<double> bidSize = stackalloc double[] { 10.0, 20.0, 30.0 };
|
||||
ReadOnlySpan<double> askPrice = stackalloc double[] { 101.0, 102.0, 103.0 };
|
||||
ReadOnlySpan<double> askSize = stackalloc double[] { 12.0, 22.0, 32.0 };
|
||||
|
||||
var result = slope.Update(bidPrice, bidSize, askPrice, askSize);
|
||||
Assert.True(double.IsFinite(result));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Wickra;
|
||||
using Xunit;
|
||||
|
||||
namespace Wickra.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Golden-fixture parity: replay the shared <c>testdata/golden</c> input series
|
||||
/// through the C# FFI and assert every value matches the Rust reference output.
|
||||
/// Where the archetype tests only check finiteness, this pins exact values, so a
|
||||
/// wiring bug (swapped parameter, wrong multi-output field) is caught.
|
||||
/// Fixtures are generated by <c>cargo run -p wickra-examples --bin gen_golden</c>.
|
||||
/// </summary>
|
||||
public class GoldenTests
|
||||
{
|
||||
private const double Tol = 1e-6;
|
||||
|
||||
private static string GoldenDir([CallerFilePath] string file = "") =>
|
||||
Path.GetFullPath(Path.Combine(Path.GetDirectoryName(file)!, "..", "..", "..", "testdata", "golden"));
|
||||
|
||||
private static List<string[]> ReadCsv(string name)
|
||||
{
|
||||
var path = Path.Combine(GoldenDir(), name + ".csv");
|
||||
return File.ReadAllLines(path)
|
||||
.Skip(1) // header
|
||||
.Where(l => l.Length > 0)
|
||||
.Select(l => l.Split(','))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static double[][] Input()
|
||||
{
|
||||
return ReadCsv("input")
|
||||
.Select(r => r.Select(c => double.Parse(c, CultureInfo.InvariantCulture)).ToArray())
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static double Cell(string s) =>
|
||||
s == "nan" ? double.NaN : double.Parse(s, CultureInfo.InvariantCulture);
|
||||
|
||||
private static void AssertClose(double got, double want, int row, string field)
|
||||
{
|
||||
if (double.IsNaN(want))
|
||||
{
|
||||
Assert.True(double.IsNaN(got), $"row {row} {field}: expected warmup/NaN, got {got}");
|
||||
return;
|
||||
}
|
||||
var tol = Tol * Math.Max(1.0, Math.Abs(want));
|
||||
Assert.True(Math.Abs(got - want) <= tol, $"row {row} {field}: got {got}, want {want}");
|
||||
}
|
||||
|
||||
// --- scalar (close-driven) ------------------------------------------------
|
||||
|
||||
[Theory]
|
||||
[InlineData("sma")]
|
||||
[InlineData("ema")]
|
||||
[InlineData("rsi")]
|
||||
public void Scalar_MatchesGolden(string name)
|
||||
{
|
||||
var input = Input();
|
||||
var expected = ReadCsv(name);
|
||||
using var ind = (IDisposable)(name switch
|
||||
{
|
||||
"sma" => new Sma(14),
|
||||
"ema" => new Ema(14),
|
||||
"rsi" => new Rsi(14),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(name)),
|
||||
});
|
||||
for (var i = 0; i < input.Length; i++)
|
||||
{
|
||||
var close = input[i][3];
|
||||
double got = ind switch
|
||||
{
|
||||
Sma s => s.Update(close),
|
||||
Ema e => e.Update(close),
|
||||
Rsi r => r.Update(close),
|
||||
_ => double.NaN,
|
||||
};
|
||||
AssertClose(got, Cell(expected[i][0]), i, name);
|
||||
}
|
||||
}
|
||||
|
||||
// --- candle, single output ------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Candle_Atr_MatchesGolden()
|
||||
{
|
||||
var input = Input();
|
||||
var expected = ReadCsv("atr");
|
||||
using var atr = new Atr(14);
|
||||
for (var i = 0; i < input.Length; i++)
|
||||
{
|
||||
var (o, h, l, c, v) = (input[i][0], input[i][1], input[i][2], input[i][3], input[i][4]);
|
||||
AssertClose(atr.Update(o, h, l, c, v, i), Cell(expected[i][0]), i, "atr");
|
||||
}
|
||||
}
|
||||
|
||||
// --- pairwise -------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Pairwise_Beta_MatchesGolden()
|
||||
{
|
||||
var input = Input();
|
||||
var expected = ReadCsv("beta");
|
||||
using var beta = new Beta(20);
|
||||
for (var i = 0; i < input.Length; i++)
|
||||
{
|
||||
// generator fed (close, open)
|
||||
AssertClose(beta.Update(input[i][3], input[i][0]), Cell(expected[i][0]), i, "beta");
|
||||
}
|
||||
}
|
||||
|
||||
// --- scalar multi-output: MACD -------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void MultiOutput_Macd_MatchesGolden()
|
||||
{
|
||||
var input = Input();
|
||||
var expected = ReadCsv("macd");
|
||||
using var macd = new MacdIndicator(12, 26, 9);
|
||||
for (var i = 0; i < input.Length; i++)
|
||||
{
|
||||
MacdOutput? got = macd.Update(input[i][3]);
|
||||
var e = expected[i];
|
||||
if (e[0] == "nan")
|
||||
{
|
||||
Assert.Null(got);
|
||||
continue;
|
||||
}
|
||||
Assert.NotNull(got);
|
||||
AssertClose(got!.Value.Macd, Cell(e[0]), i, "macd.macd");
|
||||
AssertClose(got.Value.Signal, Cell(e[1]), i, "macd.signal");
|
||||
AssertClose(got.Value.Histogram, Cell(e[2]), i, "macd.histogram");
|
||||
}
|
||||
}
|
||||
|
||||
// --- candle multi-output: ADX --------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void MultiOutput_Adx_MatchesGolden()
|
||||
{
|
||||
var input = Input();
|
||||
var expected = ReadCsv("adx");
|
||||
using var adx = new Adx(14);
|
||||
for (var i = 0; i < input.Length; i++)
|
||||
{
|
||||
var (o, h, l, c, v) = (input[i][0], input[i][1], input[i][2], input[i][3], input[i][4]);
|
||||
AdxOutput? got = adx.Update(o, h, l, c, v, i);
|
||||
var e = expected[i];
|
||||
if (e[0] == "nan")
|
||||
{
|
||||
Assert.Null(got);
|
||||
continue;
|
||||
}
|
||||
Assert.NotNull(got);
|
||||
AssertClose(got!.Value.PlusDi, Cell(e[0]), i, "adx.plus_di");
|
||||
AssertClose(got.Value.MinusDi, Cell(e[1]), i, "adx.minus_di");
|
||||
AssertClose(got.Value.Adx, Cell(e[2]), i, "adx.adx");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using Wickra;
|
||||
using Xunit;
|
||||
|
||||
namespace Wickra.Tests;
|
||||
|
||||
public class SmaTests
|
||||
{
|
||||
[Fact]
|
||||
public void StreamingMatchesReference()
|
||||
{
|
||||
using var sma = new Sma(3);
|
||||
Assert.True(double.IsNaN(sma.Update(1)));
|
||||
Assert.True(double.IsNaN(sma.Update(2)));
|
||||
Assert.Equal(2.0, sma.Update(3), 9);
|
||||
Assert.Equal(3.0, sma.Update(4), 9);
|
||||
Assert.Equal(4.0, sma.Update(5), 9);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BatchMatchesStreaming()
|
||||
{
|
||||
using var sma = new Sma(3);
|
||||
var output = sma.Batch(new double[] { 1, 2, 3, 4, 5 });
|
||||
|
||||
Assert.True(double.IsNaN(output[0]));
|
||||
Assert.True(double.IsNaN(output[1]));
|
||||
Assert.Equal(2.0, output[2], 9);
|
||||
Assert.Equal(3.0, output[3], 9);
|
||||
Assert.Equal(4.0, output[4], 9);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResetClearsState()
|
||||
{
|
||||
using var sma = new Sma(3);
|
||||
sma.Update(1);
|
||||
sma.Update(2);
|
||||
sma.Update(3);
|
||||
sma.Reset();
|
||||
Assert.True(double.IsNaN(sma.Update(10)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ZeroPeriodThrows()
|
||||
{
|
||||
// Zero is rejected by the native constructor (returns NULL) -> ArgumentException;
|
||||
// a negative period is caught earlier by the wrapper guard.
|
||||
Assert.Throws<ArgumentException>(() => new Sma(0));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new Sma(-1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Wickra\Wickra.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,57 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<RootNamespace>Wickra</RootNamespace>
|
||||
<AssemblyName>Wickra</AssemblyName>
|
||||
|
||||
<!-- NuGet package metadata -->
|
||||
<PackageId>Wickra</PackageId>
|
||||
<Version>0.8.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>
|
||||
<PackageProjectUrl>https://github.com/wickra-lib/wickra</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/wickra-lib/wickra</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageTags>technical-analysis;indicators;trading;finance;streaming;ffi;native</PackageTags>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<!-- A managed package carrying per-RID native assets; not built per-RID itself. -->
|
||||
<IncludeBuildOutput>true</IncludeBuildOutput>
|
||||
<!-- NU5128: managed package carrying only per-RID native assets.
|
||||
CS1591: generated members are self-descriptive; hand-written API is documented. -->
|
||||
<NoWarn>$(NoWarn);NU5128;CS1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
Supported native runtime identifiers. The release pipeline builds the C ABI
|
||||
per target triple and stages the libraries under
|
||||
Wickra/runtimes/<rid>/native/ before `dotnet pack`:
|
||||
win-x64 win-arm64 linux-x64 linux-arm64 osx-x64 osx-arm64
|
||||
-->
|
||||
<PropertyGroup>
|
||||
<WickraRuntimeIdentifiers>win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</WickraRuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\README.md" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Native libraries are packed under runtimes/<rid>/native/ by the release pipeline,
|
||||
which unpacks the wickra-c-<triple>.tar.gz assets into the matching RID folders.
|
||||
For local development and tests the natives are resolved from the cargo target dir
|
||||
via WickraNative's DllImportResolver.
|
||||
-->
|
||||
<ItemGroup>
|
||||
<None Include="runtimes/**/native/*" Pack="true" PackagePath="runtimes" Condition="Exists('runtimes')" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,26 @@
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace Wickra;
|
||||
|
||||
/// <summary>
|
||||
/// Owns an opaque native indicator handle and releases it via the indicator's
|
||||
/// <c>_free</c> function. One generic handle type backs every indicator; the
|
||||
/// correct free routine is captured at construction time.
|
||||
/// </summary>
|
||||
internal sealed class WickraHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
private readonly Action<nint> _free;
|
||||
|
||||
internal WickraHandle(nint handle, Action<nint> free)
|
||||
: base(ownsHandle: true)
|
||||
{
|
||||
_free = free;
|
||||
SetHandle(handle);
|
||||
}
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
_free(handle);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Wickra;
|
||||
|
||||
/// <summary>
|
||||
/// Native library resolution for the Wickra C ABI.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When consumed as a NuGet package the native library ships under
|
||||
/// <c>runtimes/<rid>/native/</c> and the default runtime resolver finds it
|
||||
/// automatically. For local development (project reference against a cargo build)
|
||||
/// the resolver additionally walks up the directory tree to locate
|
||||
/// <c>target/release</c> or <c>target/debug</c>. Every candidate is validated to
|
||||
/// actually export the Wickra ABI before it is accepted, so an unrelated library
|
||||
/// of the same name cannot shadow the real one.
|
||||
/// </remarks>
|
||||
internal static class WickraNative
|
||||
{
|
||||
/// <summary>The library name passed to <c>[LibraryImport]</c>.</summary>
|
||||
internal const string LibraryName = "wickra";
|
||||
|
||||
// Any exported symbol works as a fingerprint; sma_new exists in every build.
|
||||
private const string SentinelSymbol = "wickra_sma_new";
|
||||
|
||||
[ModuleInitializer]
|
||||
internal static void Register()
|
||||
{
|
||||
NativeLibrary.SetDllImportResolver(typeof(WickraNative).Assembly, Resolve);
|
||||
}
|
||||
|
||||
private static nint Resolve(string libraryName, System.Reflection.Assembly assembly, DllImportSearchPath? searchPath)
|
||||
{
|
||||
if (libraryName != LibraryName)
|
||||
{
|
||||
return nint.Zero;
|
||||
}
|
||||
|
||||
// 1. Default resolution (NuGet runtimes/ layout, app-local copies). Accept
|
||||
// only if it is genuinely the Wickra ABI; otherwise discard and fall through.
|
||||
if (NativeLibrary.TryLoad(libraryName, assembly, searchPath, out var handle))
|
||||
{
|
||||
if (Exports(handle))
|
||||
{
|
||||
return handle;
|
||||
}
|
||||
|
||||
NativeLibrary.Free(handle);
|
||||
}
|
||||
|
||||
// 2. Development fallback: locate the cargo build output.
|
||||
var fileName = NativeFileName();
|
||||
var dir = AppContext.BaseDirectory;
|
||||
for (var i = 0; i < 16 && dir is not null; i++)
|
||||
{
|
||||
foreach (var profile in new[] { "release", "debug" })
|
||||
{
|
||||
var candidate = Path.Combine(dir, "target", profile, fileName);
|
||||
if (File.Exists(candidate) && NativeLibrary.TryLoad(candidate, out var devHandle))
|
||||
{
|
||||
if (Exports(devHandle))
|
||||
{
|
||||
return devHandle;
|
||||
}
|
||||
|
||||
NativeLibrary.Free(devHandle);
|
||||
}
|
||||
}
|
||||
|
||||
dir = Path.GetDirectoryName(dir.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
|
||||
}
|
||||
|
||||
return nint.Zero;
|
||||
}
|
||||
|
||||
private static bool Exports(nint handle) => NativeLibrary.TryGetExport(handle, SentinelSymbol, out _);
|
||||
|
||||
private static string NativeFileName()
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
return "wickra.dll";
|
||||
}
|
||||
|
||||
return OperatingSystem.IsMacOS() ? "libwickra.dylib" : "libwickra.so";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<!-- Standalone throughput benchmark for the Wickra C# binding.
|
||||
See Program.cs for run instructions. Requires the C ABI library; the
|
||||
binding's dev DllImportResolver falls back to target/release. -->
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<IsPackable>false</IsPackable>
|
||||
<AssemblyName>Wickra.Benchmarks</AssemblyName>
|
||||
<RootNamespace>Wickra.Benchmarks</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Wickra\Wickra.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,101 @@
|
||||
// Throughput benchmark for the Wickra C# binding.
|
||||
//
|
||||
// Measures how many indicator updates per second the binding sustains, both
|
||||
// per-tick (streaming Update) and bulk (Batch), over a synthetic OHLCV series.
|
||||
// It is the C# counterpart of the Node throughput.js and the Rust criterion
|
||||
// benches: it benchmarks Wickra's own O(1) streaming engine across the
|
||||
// managed<->C-ABI boundary (there is no comparable streaming TA library on
|
||||
// NuGet to compare against), so the headline number is raw per-binding
|
||||
// throughput / FFI overhead, not a cross-library ratio.
|
||||
//
|
||||
// Three indicators are timed, chosen by FFI call-signature archetype rather
|
||||
// than algorithm: SMA (1-in -> 1-out), ATR (multi-in -> 1-out), and MACD
|
||||
// (1-in -> multi-out). Streaming is timed for all three; batch only for the
|
||||
// single-output SMA and ATR (multi-output batch is not exposed uniformly).
|
||||
//
|
||||
// cargo build -p wickra-c --release
|
||||
// dotnet run -c Release --project bindings/csharp/benchmarks # 200k bars
|
||||
// dotnet run -c Release --project bindings/csharp/benchmarks -- --bars 1000000
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using Wickra;
|
||||
|
||||
// Deterministic, locale-independent number formatting for the report.
|
||||
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
|
||||
|
||||
int bars = 200_000;
|
||||
for (int i = 0; i < args.Length - 1; i++)
|
||||
{
|
||||
if (args[i] == "--bars" && int.TryParse(args[i + 1], out var n) && n >= 1000)
|
||||
{
|
||||
bars = n;
|
||||
}
|
||||
}
|
||||
|
||||
// Deterministic synthetic OHLCV (no RNG, so runs are comparable).
|
||||
var open = new double[bars];
|
||||
var high = new double[bars];
|
||||
var low = new double[bars];
|
||||
var close = new double[bars];
|
||||
var volume = new double[bars];
|
||||
var timestamp = new long[bars];
|
||||
for (int i = 0; i < bars; i++)
|
||||
{
|
||||
double mid = 100 + Math.Sin(i * 0.001) * 20 + i * 1e-4;
|
||||
double c = mid + Math.Sin(i * 0.05) * 2;
|
||||
close[i] = c;
|
||||
open[i] = mid;
|
||||
high[i] = Math.Max(c, mid) + 1.5;
|
||||
low[i] = Math.Min(c, mid) - 1.5;
|
||||
volume[i] = 1000 + (i % 97) * 13;
|
||||
timestamp[i] = i;
|
||||
}
|
||||
|
||||
double Mups(double ns) => bars / (ns / 1e9) / 1e6;
|
||||
|
||||
// Median elapsed-ns over a few repetitions, after one warmup pass.
|
||||
double TimeNs(Action fn, int reps = 3)
|
||||
{
|
||||
fn(); // warmup (JIT + cache)
|
||||
var samples = new double[reps];
|
||||
for (int r = 0; r < reps; r++)
|
||||
{
|
||||
long t0 = Stopwatch.GetTimestamp();
|
||||
fn();
|
||||
samples[r] = (Stopwatch.GetTimestamp() - t0) * (1e9 / Stopwatch.Frequency);
|
||||
}
|
||||
Array.Sort(samples);
|
||||
return samples[reps / 2];
|
||||
}
|
||||
|
||||
// SMA (scalar 1-in/1-out), ATR (multi-in/1-out), MACD (1-in/multi-out).
|
||||
var indicators = new (string Name, Action Stream, Action? Batch)[]
|
||||
{
|
||||
("SMA(20)",
|
||||
() => { using var ind = new Sma(20); for (int i = 0; i < bars; i++) ind.Update(close[i]); },
|
||||
() => { using var ind = new Sma(20); ind.Batch(close); }),
|
||||
("ATR(14)",
|
||||
() => { using var ind = new Atr(14); for (int i = 0; i < bars; i++) ind.Update(open[i], high[i], low[i], close[i], volume[i], timestamp[i]); },
|
||||
() => { using var ind = new Atr(14); ind.Batch(open, high, low, close, volume, timestamp); }),
|
||||
("MACD(12,26,9)",
|
||||
() => { using var ind = new MacdIndicator(12, 26, 9); for (int i = 0; i < bars; i++) ind.Update(close[i]); },
|
||||
null), // multi-output: streaming only
|
||||
};
|
||||
|
||||
Console.WriteLine($"Wickra C# throughput - {bars:N0} bars (median of 3 runs)\n");
|
||||
Console.WriteLine($"{"Indicator",-22}{"streaming (Mupd/s)",20}{"batch (Mupd/s)",18}");
|
||||
Console.WriteLine(new string('-', 60));
|
||||
|
||||
foreach (var (name, stream, batch) in indicators)
|
||||
{
|
||||
string streamMups = Mups(TimeNs(stream)).ToString("F1");
|
||||
string batchMups = batch is null ? "-" : Mups(TimeNs(batch)).ToString("F1");
|
||||
Console.WriteLine($"{name,-22}{streamMups,20}{batchMups,18}");
|
||||
}
|
||||
|
||||
Console.WriteLine(
|
||||
"\nMupd/s = million indicator updates per second. Streaming is the per-tick\n" +
|
||||
"Update path crossing the managed<->C-ABI boundary once per value; batch is\n" +
|
||||
"the bulk array path (one boundary crossing). Higher is better. Numbers are\n" +
|
||||
"machine-dependent - use them for relative comparison, not as a speed claim.");
|
||||
@@ -0,0 +1,125 @@
|
||||
# Wickra — Go
|
||||
|
||||
[](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml)
|
||||
[](https://codecov.io/gh/wickra-lib/wickra)
|
||||
[](https://pkg.go.dev/github.com/wickra-lib/wickra/bindings/go)
|
||||
[](https://github.com/wickra-lib/wickra#license)
|
||||
|
||||
**Streaming-first technical indicators for Go, over the Wickra C ABI hub via cgo.**
|
||||
|
||||
Wickra is a multi-language technical-analysis library with a Rust core and
|
||||
bindings for Python, Node.js and WebAssembly, plus a C ABI for C/C++, C#, Go, Java, R and
|
||||
any other C-capable language. Every indicator is an O(1) streaming state machine,
|
||||
so live trading bots and historical backtests share the exact same
|
||||
implementation. This package is the Go binding; it consumes the C ABI hub through
|
||||
cgo and exposes all 514 streaming-first indicators as idiomatic types.
|
||||
|
||||
## Install
|
||||
|
||||
Use the published **`wickra-go`** module, which bundles the prebuilt C ABI
|
||||
library for every platform, so `go get` + `go build` works with no extra steps
|
||||
(a C compiler is still required, as the binding uses cgo):
|
||||
|
||||
```bash
|
||||
go get github.com/wickra-lib/wickra-go
|
||||
```
|
||||
|
||||
```go
|
||||
import wickra "github.com/wickra-lib/wickra-go"
|
||||
```
|
||||
|
||||
`wickra-go` is generated from this directory by the release pipeline: it mirrors
|
||||
the Go sources, the vendored C ABI header (`include/wickra.h`) and the prebuilt
|
||||
libraries under `lib/<goos>_<goarch>/`. On Linux/macOS the library path is baked
|
||||
in via rpath; on Windows the DLL must be discoverable at run time (next to the
|
||||
executable or on `PATH`).
|
||||
|
||||
### Building from this repository (contributors)
|
||||
|
||||
This `bindings/go` directory is the development source. To build it directly,
|
||||
compile the C ABI and stage the library into the per-platform directory cgo
|
||||
links against:
|
||||
|
||||
```bash
|
||||
cargo build -p wickra-c --release
|
||||
mkdir -p bindings/go/lib/linux_amd64 # match your GOOS_GOARCH
|
||||
cp target/release/libwickra.so bindings/go/lib/linux_amd64/ # Linux
|
||||
cp target/release/libwickra.dylib bindings/go/lib/darwin_arm64/ # macOS (arm64)
|
||||
cp target/release/wickra.dll bindings/go/lib/windows_amd64/ # Windows
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
wickra "github.com/wickra-lib/wickra/bindings/go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Batch: run an indicator over a whole series (NaN at warmup positions).
|
||||
prices := make([]float64, 1000)
|
||||
for i := range prices {
|
||||
prices[i] = 100.0 + float64(i)*0.1
|
||||
}
|
||||
sma, _ := wickra.NewSma(20)
|
||||
defer sma.Close()
|
||||
values := sma.Batch(prices)
|
||||
|
||||
// Streaming: the same indicator, fed tick by tick in O(1).
|
||||
rsi, _ := wickra.NewRsi(14)
|
||||
defer rsi.Close()
|
||||
for _, price := range prices {
|
||||
value := rsi.Update(price) // NaN during warmup, no recomputation
|
||||
if value > 70 {
|
||||
fmt.Println("overbought")
|
||||
}
|
||||
}
|
||||
_ = values
|
||||
}
|
||||
```
|
||||
|
||||
`Batch(prices)` and feeding the same prices through `Update()` produce identical
|
||||
values — the equivalence is enforced by the test suite. Multi-output indicators
|
||||
(MACD, Bollinger, ADX, …) return `(Output, bool)`, with `false` while warming up.
|
||||
Every indicator owns a native handle freed by `Close()`; a finalizer is wired as
|
||||
a backstop, but call `Close()` (e.g. with `defer`) to release memory promptly.
|
||||
|
||||
## Benchmark
|
||||
|
||||
`benchmarks/throughput.go` reports streaming and batch updates-per-second for
|
||||
`SMA`, `ATR` and `MACD`. It measures this binding's FFI overhead, not a
|
||||
cross-library ratio (the same Rust core runs under every binding) — see the
|
||||
repository [BENCHMARKS.md](https://github.com/wickra-lib/wickra/blob/main/BENCHMARKS.md) §3.
|
||||
|
||||
```bash
|
||||
cd benchmarks && go run .
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
The full indicator catalogue, guides, quickstarts, and API reference live in the
|
||||
main repository and documentation site:
|
||||
|
||||
- **Repository & full indicator list:** <https://github.com/wickra-lib/wickra>
|
||||
- **Docs** (quickstarts, cookbook, TA-Lib migration): <https://docs.wickra.org>
|
||||
- **Runnable examples:** [`examples/go/`](https://github.com/wickra-lib/wickra/tree/main/examples/go)
|
||||
|
||||
Wickra ships native bindings for Python, Node.js, WebAssembly and Rust, plus a
|
||||
C ABI hub that any C-capable language (C, C++, C#, Go, Java, R) links against —
|
||||
all exposing the same indicators from the shared, `unsafe`-forbidden Rust core.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
Wickra is an indicator toolkit, not a trading system. The values it computes are
|
||||
deterministic transforms of the input data — they are not financial advice and
|
||||
do not predict the market. Any use in a live trading context is at your own risk.
|
||||
The library is provided **as is**, without warranty of any kind.
|
||||
|
||||
## License
|
||||
|
||||
Licensed under either of [Apache-2.0](https://github.com/wickra-lib/wickra/blob/main/LICENSE-APACHE)
|
||||
or [MIT](https://github.com/wickra-lib/wickra/blob/main/LICENSE-MIT) at your option.
|
||||
@@ -0,0 +1,145 @@
|
||||
// Throughput benchmark for the Wickra Go bindings.
|
||||
//
|
||||
// Measures how many indicator updates per second the cgo binding sustains,
|
||||
// both per-tick (streaming Update) and bulk (Batch), over a synthetic OHLCV
|
||||
// series. It is the Go counterpart of the Node throughput.js and the Rust
|
||||
// criterion benches: it benchmarks Wickra's own O(1) streaming engine across
|
||||
// the Go<->C-ABI boundary (there is no comparable streaming TA library to
|
||||
// compare against), so the headline number is raw per-binding throughput /
|
||||
// FFI overhead, not a cross-library ratio.
|
||||
//
|
||||
// Three indicators are timed, chosen by FFI call-signature archetype rather
|
||||
// than algorithm: SMA (1-in -> 1-out), ATR (multi-in -> 1-out), and MACD
|
||||
// (1-in -> multi-out). Streaming is timed for all three; batch only for the
|
||||
// single-output SMA and ATR (multi-output batch is not exposed uniformly).
|
||||
//
|
||||
// Provision the C ABI library first (see bindings/go/README.md), then run:
|
||||
//
|
||||
// cd bindings/go/benchmarks
|
||||
// go run . # 200k bars (default)
|
||||
// go run . -bars 1000000
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
wickra "github.com/wickra-lib/wickra/bindings/go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
bars := flag.Int("bars", 200_000, "number of synthetic bars to feed")
|
||||
flag.Parse()
|
||||
n := *bars
|
||||
if n < 1000 {
|
||||
fmt.Println("-bars must be >= 1000")
|
||||
return
|
||||
}
|
||||
|
||||
// Deterministic synthetic OHLCV (no RNG, so runs are comparable).
|
||||
open := make([]float64, n)
|
||||
high := make([]float64, n)
|
||||
low := make([]float64, n)
|
||||
closeP := make([]float64, n)
|
||||
volume := make([]float64, n)
|
||||
timestamp := make([]int64, n)
|
||||
for i := 0; i < n; i++ {
|
||||
mid := 100 + math.Sin(float64(i)*0.001)*20 + float64(i)*1e-4
|
||||
c := mid + math.Sin(float64(i)*0.05)*2
|
||||
closeP[i] = c
|
||||
open[i] = mid
|
||||
high[i] = math.Max(c, mid) + 1.5
|
||||
low[i] = math.Min(c, mid) - 1.5
|
||||
volume[i] = 1000 + float64(i%97)*13
|
||||
timestamp[i] = int64(i)
|
||||
}
|
||||
|
||||
mups := func(d time.Duration) float64 {
|
||||
return float64(n) / d.Seconds() / 1e6
|
||||
}
|
||||
|
||||
// Median elapsed over a few repetitions, after one warmup pass.
|
||||
timeFn := func(fn func()) time.Duration {
|
||||
fn() // warmup
|
||||
const reps = 3
|
||||
samples := make([]time.Duration, reps)
|
||||
for r := 0; r < reps; r++ {
|
||||
t0 := time.Now()
|
||||
fn()
|
||||
samples[r] = time.Since(t0)
|
||||
}
|
||||
sort.Slice(samples, func(a, b int) bool { return samples[a] < samples[b] })
|
||||
return samples[reps/2]
|
||||
}
|
||||
|
||||
type indicator struct {
|
||||
name string
|
||||
stream func()
|
||||
batch func() // nil -> streaming only
|
||||
}
|
||||
|
||||
indicators := []indicator{
|
||||
{
|
||||
name: "SMA(20)",
|
||||
stream: func() {
|
||||
ind, _ := wickra.NewSma(20)
|
||||
for i := 0; i < n; i++ {
|
||||
ind.Update(closeP[i])
|
||||
}
|
||||
ind.Close()
|
||||
},
|
||||
batch: func() {
|
||||
ind, _ := wickra.NewSma(20)
|
||||
ind.Batch(closeP)
|
||||
ind.Close()
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ATR(14)",
|
||||
stream: func() {
|
||||
ind, _ := wickra.NewAtr(14)
|
||||
for i := 0; i < n; i++ {
|
||||
ind.Update(open[i], high[i], low[i], closeP[i], volume[i], timestamp[i])
|
||||
}
|
||||
ind.Close()
|
||||
},
|
||||
batch: func() {
|
||||
ind, _ := wickra.NewAtr(14)
|
||||
ind.Batch(open, high, low, closeP, volume, timestamp)
|
||||
ind.Close()
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "MACD(12,26,9)",
|
||||
stream: func() {
|
||||
ind, _ := wickra.NewMacdIndicator(12, 26, 9)
|
||||
for i := 0; i < n; i++ {
|
||||
ind.Update(closeP[i])
|
||||
}
|
||||
ind.Close()
|
||||
},
|
||||
batch: nil, // multi-output: streaming only
|
||||
},
|
||||
}
|
||||
|
||||
fmt.Printf("Wickra Go throughput - %d bars (median of 3 runs)\n\n", n)
|
||||
fmt.Printf("%-22s%20s%18s\n", "Indicator", "streaming (Mupd/s)", "batch (Mupd/s)")
|
||||
fmt.Println("------------------------------------------------------------")
|
||||
|
||||
for _, ind := range indicators {
|
||||
streamMups := fmt.Sprintf("%.1f", mups(timeFn(ind.stream)))
|
||||
batchMups := "-"
|
||||
if ind.batch != nil {
|
||||
batchMups = fmt.Sprintf("%.1f", mups(timeFn(ind.batch)))
|
||||
}
|
||||
fmt.Printf("%-22s%20s%18s\n", ind.name, streamMups, batchMups)
|
||||
}
|
||||
|
||||
fmt.Print("\nMupd/s = million indicator updates per second. Streaming is the per-tick\n",
|
||||
"Update path crossing the Go<->C-ABI boundary once per value; batch is the\n",
|
||||
"bulk slice path (one boundary crossing). Higher is better. Numbers are\n",
|
||||
"machine-dependent - use them for relative comparison, not as a speed claim.\n")
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
module github.com/wickra-lib/wickra/bindings/go
|
||||
|
||||
go 1.23
|
||||
@@ -0,0 +1,191 @@
|
||||
package wickra
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Golden-fixture parity: replay the shared testdata/golden input series through
|
||||
// the Go FFI and assert every value matches the Rust reference output. Where the
|
||||
// archetype test only checks finiteness, this pins exact values, catching wiring
|
||||
// bugs (swapped params, wrong multi-output field). Fixtures are generated by
|
||||
// `cargo run -p wickra-examples --bin gen_golden`.
|
||||
|
||||
const goldenTol = 1e-6
|
||||
|
||||
func readGolden(t *testing.T, name string) [][]string {
|
||||
t.Helper()
|
||||
f, err := os.Open("../../testdata/golden/" + name + ".csv")
|
||||
if err != nil {
|
||||
t.Fatalf("open %s: %v", name, err)
|
||||
}
|
||||
defer f.Close()
|
||||
var rows [][]string
|
||||
sc := bufio.NewScanner(f)
|
||||
first := true
|
||||
for sc.Scan() {
|
||||
line := sc.Text()
|
||||
if first {
|
||||
first = false
|
||||
continue
|
||||
}
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
rows = append(rows, strings.Split(line, ","))
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func goldenCell(s string) float64 {
|
||||
if s == "nan" {
|
||||
return math.NaN()
|
||||
}
|
||||
v, _ := strconv.ParseFloat(s, 64)
|
||||
return v
|
||||
}
|
||||
|
||||
func goldenInput(t *testing.T) [][]float64 {
|
||||
rows := readGolden(t, "input")
|
||||
out := make([][]float64, len(rows))
|
||||
for i, r := range rows {
|
||||
vals := make([]float64, len(r))
|
||||
for j, c := range r {
|
||||
vals[j] = goldenCell(c)
|
||||
}
|
||||
out[i] = vals
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func assertGoldenClose(t *testing.T, got, want float64, row int, field string) {
|
||||
t.Helper()
|
||||
if math.IsNaN(want) {
|
||||
if !math.IsNaN(got) {
|
||||
t.Errorf("row %d %s: expected warmup/NaN, got %v", row, field, got)
|
||||
}
|
||||
return
|
||||
}
|
||||
tol := goldenTol * math.Max(1.0, math.Abs(want))
|
||||
if math.Abs(got-want) > tol {
|
||||
t.Errorf("row %d %s: got %v want %v", row, field, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoldenScalar(t *testing.T) {
|
||||
input := goldenInput(t)
|
||||
sma, err := NewSma(14)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer sma.Close()
|
||||
ema, err := NewEma(14)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ema.Close()
|
||||
rsi, err := NewRsi(14)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer rsi.Close()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
upd func(close float64) float64
|
||||
}{
|
||||
{"sma", sma.Update},
|
||||
{"ema", ema.Update},
|
||||
{"rsi", rsi.Update},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
exp := readGolden(t, tc.name)
|
||||
for i := range input {
|
||||
assertGoldenClose(t, tc.upd(input[i][3]), goldenCell(exp[i][0]), i, tc.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoldenAtr(t *testing.T) {
|
||||
input := goldenInput(t)
|
||||
exp := readGolden(t, "atr")
|
||||
atr, err := NewAtr(14)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer atr.Close()
|
||||
for i := range input {
|
||||
got := atr.Update(input[i][0], input[i][1], input[i][2], input[i][3], input[i][4], int64(i))
|
||||
assertGoldenClose(t, got, goldenCell(exp[i][0]), i, "atr")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoldenBeta(t *testing.T) {
|
||||
input := goldenInput(t)
|
||||
exp := readGolden(t, "beta")
|
||||
beta, err := NewBeta(20)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer beta.Close()
|
||||
for i := range input {
|
||||
// generator fed (close, open)
|
||||
assertGoldenClose(t, beta.Update(input[i][3], input[i][0]), goldenCell(exp[i][0]), i, "beta")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoldenMacd(t *testing.T) {
|
||||
input := goldenInput(t)
|
||||
exp := readGolden(t, "macd")
|
||||
macd, err := NewMacdIndicator(12, 26, 9)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer macd.Close()
|
||||
for i := range input {
|
||||
out, ok := macd.Update(input[i][3])
|
||||
if exp[i][0] == "nan" {
|
||||
if ok {
|
||||
t.Errorf("row %d macd: expected warmup, got %+v", i, out)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !ok {
|
||||
t.Errorf("row %d macd: expected value, got warmup", i)
|
||||
continue
|
||||
}
|
||||
assertGoldenClose(t, out.Macd, goldenCell(exp[i][0]), i, "macd.macd")
|
||||
assertGoldenClose(t, out.Signal, goldenCell(exp[i][1]), i, "macd.signal")
|
||||
assertGoldenClose(t, out.Histogram, goldenCell(exp[i][2]), i, "macd.histogram")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoldenAdx(t *testing.T) {
|
||||
input := goldenInput(t)
|
||||
exp := readGolden(t, "adx")
|
||||
adx, err := NewAdx(14)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer adx.Close()
|
||||
for i := range input {
|
||||
out, ok := adx.Update(input[i][0], input[i][1], input[i][2], input[i][3], input[i][4], int64(i))
|
||||
if exp[i][0] == "nan" {
|
||||
if ok {
|
||||
t.Errorf("row %d adx: expected warmup, got %+v", i, out)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !ok {
|
||||
t.Errorf("row %d adx: expected value, got warmup", i)
|
||||
continue
|
||||
}
|
||||
assertGoldenClose(t, out.PlusDi, goldenCell(exp[i][0]), i, "adx.plus_di")
|
||||
assertGoldenClose(t, out.MinusDi, goldenCell(exp[i][1]), i, "adx.minus_di")
|
||||
assertGoldenClose(t, out.Adx, goldenCell(exp[i][2]), i, "adx.adx")
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
# Prebuilt Wickra C ABI libraries are provisioned locally / in CI, not committed.
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
*.a
|
||||
*.lib
|
||||
*.exp
|
||||
@@ -0,0 +1,30 @@
|
||||
// Package wickra provides idiomatic Go bindings for the Wickra
|
||||
// technical-analysis library over its C ABI hub.
|
||||
//
|
||||
// Each indicator is an opaque-handle type with a New<Indicator> constructor and
|
||||
// Update/Batch/Reset/Close methods. Handles are freed by Close and, as a
|
||||
// backstop, by a finalizer; call Close explicitly to release native memory
|
||||
// promptly. The binding links against the prebuilt Wickra C ABI library, staged
|
||||
// per platform under ./lib/<goos>_<goarch>/, with the C ABI header vendored
|
||||
// under ./include. For distribution the libraries are committed alongside the
|
||||
// source in the wickra-go module, so `go get` + `go build` works with no extra
|
||||
// steps — see the package README.
|
||||
package wickra
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -I${SRCDIR}/include
|
||||
#cgo linux,amd64 LDFLAGS: -L${SRCDIR}/lib/linux_amd64 -lwickra -Wl,-rpath,${SRCDIR}/lib/linux_amd64
|
||||
#cgo linux,arm64 LDFLAGS: -L${SRCDIR}/lib/linux_arm64 -lwickra -Wl,-rpath,${SRCDIR}/lib/linux_arm64
|
||||
#cgo darwin,amd64 LDFLAGS: -L${SRCDIR}/lib/darwin_amd64 -lwickra -Wl,-rpath,${SRCDIR}/lib/darwin_amd64
|
||||
#cgo darwin,arm64 LDFLAGS: -L${SRCDIR}/lib/darwin_arm64 -lwickra -Wl,-rpath,${SRCDIR}/lib/darwin_arm64
|
||||
#cgo windows,amd64 LDFLAGS: -L${SRCDIR}/lib/windows_amd64 -l:wickra.dll
|
||||
#cgo windows,arm64 LDFLAGS: -L${SRCDIR}/lib/windows_arm64 -l:wickra.dll
|
||||
#include "wickra.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
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")
|
||||
@@ -0,0 +1,151 @@
|
||||
package wickra
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// One indicator per FFI archetype, exercising the full New/Update/Batch/Reset/
|
||||
// Close surface against the real native library.
|
||||
|
||||
func TestScalarKnownValue(t *testing.T) {
|
||||
s, err := NewSma(3)
|
||||
if err != nil {
|
||||
t.Fatalf("NewSma: %v", err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
var last float64
|
||||
for _, v := range []float64{1, 2, 3, 4, 5} {
|
||||
last = s.Update(v)
|
||||
}
|
||||
if math.Abs(last-4.0) > 1e-9 {
|
||||
t.Fatalf("sma(3) last = %v, want 4.0", last)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScalarBatchMatchesStreaming(t *testing.T) {
|
||||
input := []float64{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
|
||||
stream, _ := NewSma(3)
|
||||
defer stream.Close()
|
||||
want := make([]float64, len(input))
|
||||
for i, v := range input {
|
||||
want[i] = stream.Update(v)
|
||||
}
|
||||
|
||||
batchInd, _ := NewSma(3)
|
||||
defer batchInd.Close()
|
||||
got := batchInd.Batch(input)
|
||||
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("batch len = %d, want %d", len(got), len(want))
|
||||
}
|
||||
for i := range want {
|
||||
if math.IsNaN(want[i]) && math.IsNaN(got[i]) {
|
||||
continue
|
||||
}
|
||||
if math.Abs(got[i]-want[i]) > 1e-9 {
|
||||
t.Fatalf("batch[%d] = %v, streaming = %v", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiOutput(t *testing.T) {
|
||||
m, err := NewMacdIndicator(3, 6, 3)
|
||||
if err != nil {
|
||||
t.Fatalf("NewMacdIndicator: %v", err)
|
||||
}
|
||||
defer m.Close()
|
||||
|
||||
var ok bool
|
||||
var out MacdOutput
|
||||
for i := 0; i < 30; i++ {
|
||||
out, ok = m.Update(100 + float64(i))
|
||||
}
|
||||
if !ok {
|
||||
t.Fatal("macd never produced a value after warmup")
|
||||
}
|
||||
if math.IsNaN(out.Macd) {
|
||||
t.Fatal("macd value is NaN after warmup")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBars(t *testing.T) {
|
||||
rb, err := NewRangeBars(2.0)
|
||||
if err != nil {
|
||||
t.Fatalf("NewRangeBars: %v", err)
|
||||
}
|
||||
defer rb.Close()
|
||||
|
||||
total := 0
|
||||
for _, p := range []float64{100, 101, 103, 104, 99, 96, 102, 108, 95, 110} {
|
||||
bars := rb.Update(p, p, p, p, 1, 0)
|
||||
total += len(bars)
|
||||
}
|
||||
if total == 0 {
|
||||
t.Fatal("range bars produced no bars over a 15-point move")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProfile(t *testing.T) {
|
||||
vp, err := NewVolumeProfile(10, 24)
|
||||
if err != nil {
|
||||
t.Fatalf("NewVolumeProfile: %v", err)
|
||||
}
|
||||
defer vp.Close()
|
||||
|
||||
var ok bool
|
||||
var snap VolumeProfileOutputScalars
|
||||
for i := 0; i < 50; i++ {
|
||||
price := 100 + 5*math.Sin(float64(i)*0.3)
|
||||
snap, ok = vp.Update(price, price+1, price-1, price, 1000, int64(i))
|
||||
}
|
||||
if !ok {
|
||||
t.Fatal("volume profile never produced a snapshot")
|
||||
}
|
||||
if len(snap.Values) == 0 {
|
||||
t.Fatal("volume profile returned an empty values buffer")
|
||||
}
|
||||
}
|
||||
|
||||
func TestArrayInput(t *testing.T) {
|
||||
ob, err := NewOrderBookImbalanceFull()
|
||||
if err != nil {
|
||||
t.Fatalf("NewOrderBookImbalanceFull: %v", err)
|
||||
}
|
||||
defer ob.Close()
|
||||
|
||||
bidPrice := []float64{99.9, 99.8, 99.7}
|
||||
bidSize := []float64{5, 3, 2}
|
||||
askPrice := []float64{100.1, 100.2, 100.3}
|
||||
askSize := []float64{1, 1, 1}
|
||||
v := ob.Update(bidPrice, bidSize, askPrice, askSize)
|
||||
if math.IsNaN(v) {
|
||||
t.Fatal("order-book imbalance is NaN on a populated book")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResetReturnsToWarmup(t *testing.T) {
|
||||
s, _ := NewSma(3)
|
||||
defer s.Close()
|
||||
for _, v := range []float64{1, 2, 3} {
|
||||
s.Update(v)
|
||||
}
|
||||
s.Reset()
|
||||
if got := s.Update(10); !math.IsNaN(got) {
|
||||
t.Fatalf("after reset first update = %v, want NaN (warmup)", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidParams(t *testing.T) {
|
||||
if _, err := NewSma(0); err == nil {
|
||||
t.Fatal("NewSma(0) should return ErrInvalidParams")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloseIsIdempotent(t *testing.T) {
|
||||
s, _ := NewSma(3)
|
||||
s.Close()
|
||||
s.Close() // must not panic or double-free
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
# Maven build output
|
||||
target/
|
||||
|
||||
# Native libraries staged for packaging (produced by the release pipeline from
|
||||
# the wickra-c-<triple>.tar.gz assets; never committed to source).
|
||||
src/main/resources/native/
|
||||
@@ -0,0 +1,115 @@
|
||||
# Wickra — Java
|
||||
|
||||
[](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml)
|
||||
[](https://codecov.io/gh/wickra-lib/wickra)
|
||||
[](https://central.sonatype.com/artifact/org.wickra/wickra)
|
||||
[](https://github.com/wickra-lib/wickra#license)
|
||||
|
||||
**Streaming-first technical indicators for the JVM, on the Java Foreign Function
|
||||
& Memory API — prebuilt native library, no JNI, no system dependencies.**
|
||||
|
||||
Wickra is a multi-language technical-analysis library with a Rust core and
|
||||
bindings for Python, Node.js and WebAssembly, plus a C ABI for C/C++, C#, Go, Java, R
|
||||
and any other C-capable language. Every indicator is an O(1) streaming state
|
||||
machine, so live trading bots and historical backtests share the exact same
|
||||
implementation. This package is the Java binding; it consumes the C ABI hub
|
||||
through the Panama FFM API (`java.lang.foreign`) and exposes all 514
|
||||
streaming-first indicators as idiomatic `AutoCloseable` classes.
|
||||
|
||||
## Requirements
|
||||
|
||||
- **Java 22 or later** (the FFM API is final since Java 22; no preview flag).
|
||||
- The FFM API is *restricted*: pass `--enable-native-access=ALL-UNNAMED` when you
|
||||
run your application to silence the native-access warning.
|
||||
|
||||
## Install
|
||||
|
||||
Maven:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.wickra</groupId>
|
||||
<artifactId>wickra</artifactId>
|
||||
<version>0.8.5</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Gradle:
|
||||
|
||||
```kotlin
|
||||
implementation("org.wickra:wickra:0.8.5")
|
||||
```
|
||||
|
||||
The native library ships prebuilt per platform (Linux, macOS, Windows — x64 and
|
||||
arm64) inside the jar and is extracted automatically on first use. There is
|
||||
nothing to compile.
|
||||
|
||||
## Quick start
|
||||
|
||||
```java
|
||||
import org.wickra.Ema;
|
||||
import org.wickra.Rsi;
|
||||
|
||||
// Batch: run an indicator over a whole series (NaN at warmup positions).
|
||||
double[] prices = new double[1000];
|
||||
for (int i = 0; i < prices.length; i++) {
|
||||
prices[i] = 100.0 + i * 0.1;
|
||||
}
|
||||
try (Ema ema = new Ema(20)) {
|
||||
double[] values = ema.batch(prices);
|
||||
}
|
||||
|
||||
// Streaming: the same indicator, fed tick by tick in O(1).
|
||||
try (Rsi rsi = new Rsi(14)) {
|
||||
for (double price : liveFeed) {
|
||||
double value = rsi.update(price); // NaN during warmup, no recomputation
|
||||
if (Double.isFinite(value) && value > 70) {
|
||||
System.out.println("overbought");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`batch(prices)` and feeding the same prices through `update()` produce identical
|
||||
values — the equivalence is enforced by the test suite. Multi-output indicators
|
||||
(MACD, Bollinger, ADX, …) return a `record`, `null` while warming up. Each
|
||||
indicator owns a native handle freed by a `Cleaner`; `close()` releases it
|
||||
eagerly (use try-with-resources).
|
||||
|
||||
## Benchmark
|
||||
|
||||
`benchmarks/` reports streaming and batch updates-per-second for `SMA`, `ATR`
|
||||
and `MACD`. It measures this binding's FFI overhead, not a cross-library ratio
|
||||
(the same Rust core runs under every binding) — see the repository
|
||||
[BENCHMARKS.md](https://github.com/wickra-lib/wickra/blob/main/BENCHMARKS.md) §3.
|
||||
|
||||
```bash
|
||||
cargo build -p wickra-c --release
|
||||
mvn -q install -DskipTests
|
||||
mvn -q -f benchmarks exec:exec -Dexec.mainClass=org.wickra.benchmarks.Throughput
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
The full indicator catalogue, guides, quickstarts, and API reference live in
|
||||
the main repository and documentation site:
|
||||
|
||||
- **Repository & full indicator list:** <https://github.com/wickra-lib/wickra>
|
||||
- **Docs** (quickstarts, cookbook, TA-Lib migration): <https://docs.wickra.org>
|
||||
- **Runnable examples:** [`examples/java/`](https://github.com/wickra-lib/wickra/tree/main/examples/java)
|
||||
|
||||
Wickra ships native bindings for Python, Node.js, WebAssembly and Rust, plus a
|
||||
C ABI hub that any C-capable language (C, C++, Go, C#, Java, R) links against —
|
||||
all exposing the same indicators from the shared, `unsafe`-forbidden Rust core.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
Wickra is an indicator toolkit, not a trading system. The values it computes
|
||||
are deterministic transforms of the input data — they are not financial advice
|
||||
and do not predict the market. Any use in a live trading context is at your own
|
||||
risk. The library is provided **as is**, without warranty of any kind.
|
||||
|
||||
## License
|
||||
|
||||
Licensed under either of [Apache-2.0](https://github.com/wickra-lib/wickra/blob/main/LICENSE-APACHE)
|
||||
or [MIT](https://github.com/wickra-lib/wickra/blob/main/LICENSE-MIT) at your option.
|
||||
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.wickra.benchmarks</groupId>
|
||||
<artifactId>wickra-benchmarks</artifactId>
|
||||
<version>0.8.2</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>Wickra Java benchmarks</name>
|
||||
<description>Throughput benchmark for the Wickra Java binding.</description>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.release>22</maven.compiler.release>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.wickra</groupId>
|
||||
<artifactId>wickra</artifactId>
|
||||
<version>0.8.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
</plugin>
|
||||
|
||||
<!--
|
||||
Run the benchmark in a forked JVM with native access granted:
|
||||
mvn exec:exec -Dexec.mainClass=org.wickra.benchmarks.Throughput
|
||||
Requires the C ABI library and the installed binding; see Throughput.java.
|
||||
-->
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<configuration>
|
||||
<executable>${java.home}/bin/java</executable>
|
||||
<arguments>
|
||||
<argument>--enable-native-access=ALL-UNNAMED</argument>
|
||||
<argument>-classpath</argument>
|
||||
<classpath/>
|
||||
<argument>${exec.mainClass}</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,149 @@
|
||||
package org.wickra.benchmarks;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import org.wickra.Atr;
|
||||
import org.wickra.MacdIndicator;
|
||||
import org.wickra.Sma;
|
||||
|
||||
/**
|
||||
* Throughput benchmark for the Wickra Java binding.
|
||||
*
|
||||
* <p>Measures how many indicator updates per second the binding sustains, both
|
||||
* per-tick (streaming {@code update}) and bulk ({@code batch}), over a synthetic
|
||||
* OHLCV series. It is the Java counterpart of the Node {@code throughput.js} and
|
||||
* the Rust criterion benches: it benchmarks Wickra's own O(1) streaming engine
|
||||
* across the Java FFM <-> C-ABI boundary (there is no comparable streaming
|
||||
* TA library on Maven Central to compare against), so the headline number is raw
|
||||
* per-binding throughput / FFI overhead, not a cross-library ratio.
|
||||
*
|
||||
* <p>Three indicators are timed, chosen by FFI call-signature archetype rather
|
||||
* than algorithm: SMA (1-in -> 1-out), ATR (multi-in -> 1-out), and MACD
|
||||
* (1-in -> multi-out). Streaming is timed for all three; batch only for the
|
||||
* single-output SMA and ATR (multi-output batch is not exposed uniformly).
|
||||
*
|
||||
* <p>Install the binding and build the C ABI library first, then run from the
|
||||
* repo root:
|
||||
*
|
||||
* <pre>
|
||||
* cargo build -p wickra-c --release
|
||||
* mvn -q -f bindings/java install -DskipTests
|
||||
* mvn -q -f bindings/java/benchmarks exec:exec -Dexec.mainClass=org.wickra.benchmarks.Throughput
|
||||
* </pre>
|
||||
*/
|
||||
public final class Throughput {
|
||||
private Throughput() {}
|
||||
|
||||
public static void main(String[] args) {
|
||||
int bars = 200_000;
|
||||
for (int i = 0; i < args.length - 1; i++) {
|
||||
if (args[i].equals("--bars")) {
|
||||
try {
|
||||
int n = Integer.parseInt(args[i + 1]);
|
||||
if (n >= 1000) {
|
||||
bars = n;
|
||||
}
|
||||
} catch (NumberFormatException ignored) {
|
||||
// keep default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deterministic synthetic OHLCV (no RNG, so runs are comparable).
|
||||
double[] open = new double[bars];
|
||||
double[] high = new double[bars];
|
||||
double[] low = new double[bars];
|
||||
double[] close = new double[bars];
|
||||
double[] volume = new double[bars];
|
||||
double[] timestamp = new double[bars];
|
||||
for (int i = 0; i < bars; i++) {
|
||||
double mid = 100 + Math.sin(i * 0.001) * 20 + i * 1e-4;
|
||||
double c = mid + Math.sin(i * 0.05) * 2;
|
||||
close[i] = c;
|
||||
open[i] = mid;
|
||||
high[i] = Math.max(c, mid) + 1.5;
|
||||
low[i] = Math.min(c, mid) - 1.5;
|
||||
volume[i] = 1000 + (i % 97) * 13;
|
||||
timestamp[i] = i;
|
||||
}
|
||||
|
||||
final int n = bars;
|
||||
|
||||
// SMA (scalar 1-in/1-out), ATR (multi-in/1-out), MACD (1-in/multi-out).
|
||||
Indicator[] indicators = {
|
||||
new Indicator("SMA(20)",
|
||||
() -> {
|
||||
try (Sma ind = new Sma(20)) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
ind.update(close[i]);
|
||||
}
|
||||
}
|
||||
},
|
||||
() -> {
|
||||
try (Sma ind = new Sma(20)) {
|
||||
ind.batch(close);
|
||||
}
|
||||
}),
|
||||
new Indicator("ATR(14)",
|
||||
() -> {
|
||||
try (Atr ind = new Atr(14)) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
ind.update(open[i], high[i], low[i], close[i], volume[i], (long) timestamp[i]);
|
||||
}
|
||||
}
|
||||
},
|
||||
() -> {
|
||||
try (Atr ind = new Atr(14)) {
|
||||
ind.batch(open, high, low, close, volume, timestamp);
|
||||
}
|
||||
}),
|
||||
new Indicator("MACD(12,26,9)",
|
||||
() -> {
|
||||
try (MacdIndicator ind = new MacdIndicator(12, 26, 9)) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
ind.update(close[i]);
|
||||
}
|
||||
}
|
||||
},
|
||||
null), // multi-output: streaming only
|
||||
};
|
||||
|
||||
System.out.printf(Locale.ROOT, "Wickra Java throughput - %,d bars (median of 3 runs)%n%n", bars);
|
||||
System.out.printf(Locale.ROOT, "%-22s%20s%18s%n", "Indicator", "streaming (Mupd/s)", "batch (Mupd/s)");
|
||||
System.out.println("------------------------------------------------------------");
|
||||
|
||||
for (Indicator ind : indicators) {
|
||||
String streamMups = String.format(Locale.ROOT, "%.1f", mups(bars, timeNs(ind.stream)));
|
||||
String batchMups = ind.batch == null
|
||||
? "-"
|
||||
: String.format(Locale.ROOT, "%.1f", mups(bars, timeNs(ind.batch)));
|
||||
System.out.printf(Locale.ROOT, "%-22s%20s%18s%n", ind.name, streamMups, batchMups);
|
||||
}
|
||||
|
||||
System.out.println(
|
||||
"\nMupd/s = million indicator updates per second. Streaming is the per-tick\n"
|
||||
+ "update path crossing the Java FFM<->C-ABI boundary once per value; batch is\n"
|
||||
+ "the bulk array path (one boundary crossing). Higher is better. Numbers are\n"
|
||||
+ "machine-dependent - use them for relative comparison, not as a speed claim.");
|
||||
}
|
||||
|
||||
private static double mups(int bars, double ns) {
|
||||
return bars / (ns / 1e9) / 1e6;
|
||||
}
|
||||
|
||||
// Median elapsed-ns over a few repetitions, after one warmup pass.
|
||||
private static double timeNs(Runnable fn) {
|
||||
fn.run(); // warmup (JIT + cache)
|
||||
final int reps = 3;
|
||||
double[] samples = new double[reps];
|
||||
for (int r = 0; r < reps; r++) {
|
||||
long t0 = System.nanoTime();
|
||||
fn.run();
|
||||
samples[r] = System.nanoTime() - t0;
|
||||
}
|
||||
Arrays.sort(samples);
|
||||
return samples[reps / 2];
|
||||
}
|
||||
|
||||
private record Indicator(String name, Runnable stream, Runnable batch) {}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.wickra</groupId>
|
||||
<artifactId>wickra</artifactId>
|
||||
<version>0.8.5</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>Wickra</name>
|
||||
<description>High-performance streaming technical-analysis indicators (514 indicators) for the
|
||||
JVM, backed by the native Rust core through the Wickra C ABI via the Java FFM API (Panama).</description>
|
||||
<url>https://github.com/wickra-lib/wickra</url>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>MIT</name>
|
||||
<url>https://opensource.org/licenses/MIT</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
<license>
|
||||
<name>Apache-2.0</name>
|
||||
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>kingchenc</id>
|
||||
<name>kingchenc</name>
|
||||
<url>https://github.com/kingchenc</url>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<scm>
|
||||
<connection>scm:git:https://github.com/wickra-lib/wickra.git</connection>
|
||||
<developerConnection>scm:git:https://github.com/wickra-lib/wickra.git</developerConnection>
|
||||
<url>https://github.com/wickra-lib/wickra</url>
|
||||
</scm>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.release>22</maven.compiler.release>
|
||||
<junit.version>5.10.2</junit.version>
|
||||
<!-- The FFM API is restricted; grant native access to the unnamed module so
|
||||
tests and examples run without warnings. Consumers pass the same flag. -->
|
||||
<native.access.arg>--enable-native-access=ALL-UNNAMED</native.access.arg>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.2.5</version>
|
||||
<configuration>
|
||||
<argLine>${native.access.arg}</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.4.1</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<!-- Grant native access when the jar is run directly. -->
|
||||
<Enable-Native-Access>ALL-UNNAMED</Enable-Native-Access>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<!--
|
||||
Release profile (CI only, gated): attaches source + javadoc jars, GPG-signs
|
||||
every artifact, and publishes to Maven Central via the central-publishing
|
||||
plugin. The native libraries are unpacked from the wickra-c-<triple>.tar.gz
|
||||
release assets into src/main/resources/native/<os>-<arch>/ before `mvn deploy`,
|
||||
so the published jar carries every platform's library.
|
||||
-->
|
||||
<profile>
|
||||
<id>release</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.3.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals><goal>jar-no-fork</goal></goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.7.0</version>
|
||||
<configuration>
|
||||
<!-- Generated members are self-descriptive; do not fail on doclint. -->
|
||||
<doclint>none</doclint>
|
||||
<quiet>true</quiet>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals><goal>jar</goal></goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>3.2.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals><goal>sign</goal></goals>
|
||||
<configuration>
|
||||
<!-- Non-interactive signing on CI: modern GPG needs loopback
|
||||
pinentry to take the passphrase from the env without a TTY. -->
|
||||
<gpgArguments>
|
||||
<arg>--pinentry-mode</arg>
|
||||
<arg>loopback</arg>
|
||||
</gpgArguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.sonatype.central</groupId>
|
||||
<artifactId>central-publishing-maven-plugin</artifactId>
|
||||
<version>0.5.0</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<publishingServerId>central</publishingServerId>
|
||||
<autoPublish>true</autoPublish>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
@@ -0,0 +1,86 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming AbandonedBaby indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class AbandonedBaby implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public AbandonedBaby() {
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_ABANDONED_BABY_NEW.invokeExact();
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid AbandonedBaby parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_ABANDONED_BABY_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double open, double high, double low, double close, double volume, long timestamp) {
|
||||
try {
|
||||
return (double) NativeMethods.WICKRA_ABANDONED_BABY_UPDATE.invokeExact(handle, open, high, low, close, volume, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Vectorized update over a whole series; NaN at warmup positions. */
|
||||
public double[] batch(double[] open, double[] high, double[] low, double[] close, double[] volume, double[] timestamp) {
|
||||
int n = open.length;
|
||||
if (high.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (low.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (close.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (volume.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (timestamp.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment openSeg = a.allocateFrom(JAVA_DOUBLE, open);
|
||||
MemorySegment highSeg = a.allocateFrom(JAVA_DOUBLE, high);
|
||||
MemorySegment lowSeg = a.allocateFrom(JAVA_DOUBLE, low);
|
||||
MemorySegment closeSeg = a.allocateFrom(JAVA_DOUBLE, close);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment timestampSeg = a.allocateFrom(JAVA_DOUBLE, timestamp);
|
||||
MemorySegment outSeg = a.allocate(JAVA_DOUBLE.byteSize() * n);
|
||||
NativeMethods.WICKRA_ABANDONED_BABY_BATCH.invokeExact(handle, openSeg, highSeg, lowSeg, closeSeg, volumeSeg, timestampSeg, outSeg, (long) n);
|
||||
double[] out = new double[n];
|
||||
MemorySegment.copy(outSeg, JAVA_DOUBLE, 0L, out, 0, n);
|
||||
return out;
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_ABANDONED_BABY_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming Abcd indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class Abcd implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public Abcd() {
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_ABCD_NEW.invokeExact();
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid Abcd parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_ABCD_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double open, double high, double low, double close, double volume, long timestamp) {
|
||||
try {
|
||||
return (double) NativeMethods.WICKRA_ABCD_UPDATE.invokeExact(handle, open, high, low, close, volume, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Vectorized update over a whole series; NaN at warmup positions. */
|
||||
public double[] batch(double[] open, double[] high, double[] low, double[] close, double[] volume, double[] timestamp) {
|
||||
int n = open.length;
|
||||
if (high.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (low.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (close.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (volume.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (timestamp.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment openSeg = a.allocateFrom(JAVA_DOUBLE, open);
|
||||
MemorySegment highSeg = a.allocateFrom(JAVA_DOUBLE, high);
|
||||
MemorySegment lowSeg = a.allocateFrom(JAVA_DOUBLE, low);
|
||||
MemorySegment closeSeg = a.allocateFrom(JAVA_DOUBLE, close);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment timestampSeg = a.allocateFrom(JAVA_DOUBLE, timestamp);
|
||||
MemorySegment outSeg = a.allocate(JAVA_DOUBLE.byteSize() * n);
|
||||
NativeMethods.WICKRA_ABCD_BATCH.invokeExact(handle, openSeg, highSeg, lowSeg, closeSeg, volumeSeg, timestampSeg, outSeg, (long) n);
|
||||
double[] out = new double[n];
|
||||
MemorySegment.copy(outSeg, JAVA_DOUBLE, 0L, out, 0, n);
|
||||
return out;
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_ABCD_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming AbsoluteBreadthIndex indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class AbsoluteBreadthIndex implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public AbsoluteBreadthIndex() {
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_ABSOLUTE_BREADTH_INDEX_NEW.invokeExact();
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid AbsoluteBreadthIndex parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_ABSOLUTE_BREADTH_INDEX_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double[] change, double[] volume, double[] newHigh, double[] newLow, double[] aboveMa, double[] onBuySignal, long timestamp) {
|
||||
if (volume.length != change.length) {
|
||||
throw new IllegalArgumentException("input arrays in the same group must have equal length");
|
||||
}
|
||||
if (newHigh.length != change.length) {
|
||||
throw new IllegalArgumentException("input arrays in the same group must have equal length");
|
||||
}
|
||||
if (newLow.length != change.length) {
|
||||
throw new IllegalArgumentException("input arrays in the same group must have equal length");
|
||||
}
|
||||
if (aboveMa.length != change.length) {
|
||||
throw new IllegalArgumentException("input arrays in the same group must have equal length");
|
||||
}
|
||||
if (onBuySignal.length != change.length) {
|
||||
throw new IllegalArgumentException("input arrays in the same group must have equal length");
|
||||
}
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment changeSeg = a.allocateFrom(JAVA_DOUBLE, change);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment newHighSeg = a.allocateFrom(JAVA_DOUBLE, newHigh);
|
||||
MemorySegment newLowSeg = a.allocateFrom(JAVA_DOUBLE, newLow);
|
||||
MemorySegment aboveMaSeg = a.allocateFrom(JAVA_DOUBLE, aboveMa);
|
||||
MemorySegment onBuySignalSeg = a.allocateFrom(JAVA_DOUBLE, onBuySignal);
|
||||
return (double) NativeMethods.WICKRA_ABSOLUTE_BREADTH_INDEX_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_ABSOLUTE_BREADTH_INDEX_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming AccelerationBands indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class AccelerationBands implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public AccelerationBands(int period, double factor) {
|
||||
if (period < 0) {
|
||||
throw new IllegalArgumentException("period must be non-negative");
|
||||
}
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_ACCELERATION_BANDS_NEW.invokeExact((long) period, factor);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid AccelerationBands parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_ACCELERATION_BANDS_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the result, or null during warmup. */
|
||||
public AccelerationBandsOutput update(double open, double high, double low, double close, double volume, long timestamp) {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment out = a.allocate(24L);
|
||||
byte ok = (byte) NativeMethods.WICKRA_ACCELERATION_BANDS_UPDATE.invokeExact(handle, open, high, low, close, volume, timestamp, out);
|
||||
if (ok == 0) {
|
||||
return null;
|
||||
}
|
||||
return new AccelerationBandsOutput(
|
||||
out.get(JAVA_DOUBLE, 0L),
|
||||
out.get(JAVA_DOUBLE, 8L),
|
||||
out.get(JAVA_DOUBLE, 16L));
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_ACCELERATION_BANDS_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -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 AccelerationBandsOutput(double upper, double middle, double lower) {}
|
||||
@@ -0,0 +1,95 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming AcceleratorOscillator indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class AcceleratorOscillator implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public AcceleratorOscillator(int aoFast, int aoSlow, int signalPeriod) {
|
||||
if (aoFast < 0) {
|
||||
throw new IllegalArgumentException("aoFast must be non-negative");
|
||||
}
|
||||
if (aoSlow < 0) {
|
||||
throw new IllegalArgumentException("aoSlow must be non-negative");
|
||||
}
|
||||
if (signalPeriod < 0) {
|
||||
throw new IllegalArgumentException("signalPeriod must be non-negative");
|
||||
}
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_ACCELERATOR_OSCILLATOR_NEW.invokeExact((long) aoFast, (long) aoSlow, (long) signalPeriod);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid AcceleratorOscillator parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_ACCELERATOR_OSCILLATOR_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double open, double high, double low, double close, double volume, long timestamp) {
|
||||
try {
|
||||
return (double) NativeMethods.WICKRA_ACCELERATOR_OSCILLATOR_UPDATE.invokeExact(handle, open, high, low, close, volume, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Vectorized update over a whole series; NaN at warmup positions. */
|
||||
public double[] batch(double[] open, double[] high, double[] low, double[] close, double[] volume, double[] timestamp) {
|
||||
int n = open.length;
|
||||
if (high.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (low.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (close.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (volume.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (timestamp.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment openSeg = a.allocateFrom(JAVA_DOUBLE, open);
|
||||
MemorySegment highSeg = a.allocateFrom(JAVA_DOUBLE, high);
|
||||
MemorySegment lowSeg = a.allocateFrom(JAVA_DOUBLE, low);
|
||||
MemorySegment closeSeg = a.allocateFrom(JAVA_DOUBLE, close);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment timestampSeg = a.allocateFrom(JAVA_DOUBLE, timestamp);
|
||||
MemorySegment outSeg = a.allocate(JAVA_DOUBLE.byteSize() * n);
|
||||
NativeMethods.WICKRA_ACCELERATOR_OSCILLATOR_BATCH.invokeExact(handle, openSeg, highSeg, lowSeg, closeSeg, volumeSeg, timestampSeg, outSeg, (long) n);
|
||||
double[] out = new double[n];
|
||||
MemorySegment.copy(outSeg, JAVA_DOUBLE, 0L, out, 0, n);
|
||||
return out;
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_ACCELERATOR_OSCILLATOR_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming AdOscillator indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class AdOscillator implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public AdOscillator() {
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_AD_OSCILLATOR_NEW.invokeExact();
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid AdOscillator parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_AD_OSCILLATOR_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double open, double high, double low, double close, double volume, long timestamp) {
|
||||
try {
|
||||
return (double) NativeMethods.WICKRA_AD_OSCILLATOR_UPDATE.invokeExact(handle, open, high, low, close, volume, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Vectorized update over a whole series; NaN at warmup positions. */
|
||||
public double[] batch(double[] open, double[] high, double[] low, double[] close, double[] volume, double[] timestamp) {
|
||||
int n = open.length;
|
||||
if (high.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (low.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (close.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (volume.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (timestamp.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment openSeg = a.allocateFrom(JAVA_DOUBLE, open);
|
||||
MemorySegment highSeg = a.allocateFrom(JAVA_DOUBLE, high);
|
||||
MemorySegment lowSeg = a.allocateFrom(JAVA_DOUBLE, low);
|
||||
MemorySegment closeSeg = a.allocateFrom(JAVA_DOUBLE, close);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment timestampSeg = a.allocateFrom(JAVA_DOUBLE, timestamp);
|
||||
MemorySegment outSeg = a.allocate(JAVA_DOUBLE.byteSize() * n);
|
||||
NativeMethods.WICKRA_AD_OSCILLATOR_BATCH.invokeExact(handle, openSeg, highSeg, lowSeg, closeSeg, volumeSeg, timestampSeg, outSeg, (long) n);
|
||||
double[] out = new double[n];
|
||||
MemorySegment.copy(outSeg, JAVA_DOUBLE, 0L, out, 0, n);
|
||||
return out;
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_AD_OSCILLATOR_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming AdVolumeLine indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class AdVolumeLine implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public AdVolumeLine() {
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_AD_VOLUME_LINE_NEW.invokeExact();
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid AdVolumeLine parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_AD_VOLUME_LINE_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double[] change, double[] volume, double[] newHigh, double[] newLow, double[] aboveMa, double[] onBuySignal, long timestamp) {
|
||||
if (volume.length != change.length) {
|
||||
throw new IllegalArgumentException("input arrays in the same group must have equal length");
|
||||
}
|
||||
if (newHigh.length != change.length) {
|
||||
throw new IllegalArgumentException("input arrays in the same group must have equal length");
|
||||
}
|
||||
if (newLow.length != change.length) {
|
||||
throw new IllegalArgumentException("input arrays in the same group must have equal length");
|
||||
}
|
||||
if (aboveMa.length != change.length) {
|
||||
throw new IllegalArgumentException("input arrays in the same group must have equal length");
|
||||
}
|
||||
if (onBuySignal.length != change.length) {
|
||||
throw new IllegalArgumentException("input arrays in the same group must have equal length");
|
||||
}
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment changeSeg = a.allocateFrom(JAVA_DOUBLE, change);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment newHighSeg = a.allocateFrom(JAVA_DOUBLE, newHigh);
|
||||
MemorySegment newLowSeg = a.allocateFrom(JAVA_DOUBLE, newLow);
|
||||
MemorySegment aboveMaSeg = a.allocateFrom(JAVA_DOUBLE, aboveMa);
|
||||
MemorySegment onBuySignalSeg = a.allocateFrom(JAVA_DOUBLE, onBuySignal);
|
||||
return (double) NativeMethods.WICKRA_AD_VOLUME_LINE_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_AD_VOLUME_LINE_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming AdaptiveCci indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class AdaptiveCci implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public AdaptiveCci(int period) {
|
||||
if (period < 0) {
|
||||
throw new IllegalArgumentException("period must be non-negative");
|
||||
}
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_ADAPTIVE_CCI_NEW.invokeExact((long) period);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid AdaptiveCci parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_ADAPTIVE_CCI_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double open, double high, double low, double close, double volume, long timestamp) {
|
||||
try {
|
||||
return (double) NativeMethods.WICKRA_ADAPTIVE_CCI_UPDATE.invokeExact(handle, open, high, low, close, volume, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Vectorized update over a whole series; NaN at warmup positions. */
|
||||
public double[] batch(double[] open, double[] high, double[] low, double[] close, double[] volume, double[] timestamp) {
|
||||
int n = open.length;
|
||||
if (high.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (low.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (close.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (volume.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (timestamp.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment openSeg = a.allocateFrom(JAVA_DOUBLE, open);
|
||||
MemorySegment highSeg = a.allocateFrom(JAVA_DOUBLE, high);
|
||||
MemorySegment lowSeg = a.allocateFrom(JAVA_DOUBLE, low);
|
||||
MemorySegment closeSeg = a.allocateFrom(JAVA_DOUBLE, close);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment timestampSeg = a.allocateFrom(JAVA_DOUBLE, timestamp);
|
||||
MemorySegment outSeg = a.allocate(JAVA_DOUBLE.byteSize() * n);
|
||||
NativeMethods.WICKRA_ADAPTIVE_CCI_BATCH.invokeExact(handle, openSeg, highSeg, lowSeg, closeSeg, volumeSeg, timestampSeg, outSeg, (long) n);
|
||||
double[] out = new double[n];
|
||||
MemorySegment.copy(outSeg, JAVA_DOUBLE, 0L, out, 0, n);
|
||||
return out;
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_ADAPTIVE_CCI_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming AdaptiveCycle indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class AdaptiveCycle implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public AdaptiveCycle() {
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_ADAPTIVE_CYCLE_NEW.invokeExact();
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid AdaptiveCycle parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_ADAPTIVE_CYCLE_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double value) {
|
||||
try {
|
||||
return (double) NativeMethods.WICKRA_ADAPTIVE_CYCLE_UPDATE.invokeExact(handle, value);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Vectorized update over a whole series; NaN at warmup positions. */
|
||||
public double[] batch(double[] input) {
|
||||
int n = input.length;
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment inputSeg = a.allocateFrom(JAVA_DOUBLE, input);
|
||||
MemorySegment outSeg = a.allocate(JAVA_DOUBLE.byteSize() * n);
|
||||
NativeMethods.WICKRA_ADAPTIVE_CYCLE_BATCH.invokeExact(handle, inputSeg, outSeg, (long) n);
|
||||
double[] out = new double[n];
|
||||
MemorySegment.copy(outSeg, JAVA_DOUBLE, 0L, out, 0, n);
|
||||
return out;
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_ADAPTIVE_CYCLE_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -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.*;
|
||||
|
||||
/** Streaming AdaptiveLaguerreFilter indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class AdaptiveLaguerreFilter implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public AdaptiveLaguerreFilter(int period) {
|
||||
if (period < 0) {
|
||||
throw new IllegalArgumentException("period must be non-negative");
|
||||
}
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_ADAPTIVE_LAGUERRE_FILTER_NEW.invokeExact((long) period);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid AdaptiveLaguerreFilter parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_ADAPTIVE_LAGUERRE_FILTER_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double value) {
|
||||
try {
|
||||
return (double) NativeMethods.WICKRA_ADAPTIVE_LAGUERRE_FILTER_UPDATE.invokeExact(handle, value);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Vectorized update over a whole series; NaN at warmup positions. */
|
||||
public double[] batch(double[] input) {
|
||||
int n = input.length;
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment inputSeg = a.allocateFrom(JAVA_DOUBLE, input);
|
||||
MemorySegment outSeg = a.allocate(JAVA_DOUBLE.byteSize() * n);
|
||||
NativeMethods.WICKRA_ADAPTIVE_LAGUERRE_FILTER_BATCH.invokeExact(handle, inputSeg, outSeg, (long) n);
|
||||
double[] out = new double[n];
|
||||
MemorySegment.copy(outSeg, JAVA_DOUBLE, 0L, out, 0, n);
|
||||
return out;
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_ADAPTIVE_LAGUERRE_FILTER_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -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.*;
|
||||
|
||||
/** Streaming AdaptiveRsi indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class AdaptiveRsi implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public AdaptiveRsi(int period) {
|
||||
if (period < 0) {
|
||||
throw new IllegalArgumentException("period must be non-negative");
|
||||
}
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_ADAPTIVE_RSI_NEW.invokeExact((long) period);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid AdaptiveRsi parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_ADAPTIVE_RSI_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double value) {
|
||||
try {
|
||||
return (double) NativeMethods.WICKRA_ADAPTIVE_RSI_UPDATE.invokeExact(handle, value);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Vectorized update over a whole series; NaN at warmup positions. */
|
||||
public double[] batch(double[] input) {
|
||||
int n = input.length;
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment inputSeg = a.allocateFrom(JAVA_DOUBLE, input);
|
||||
MemorySegment outSeg = a.allocate(JAVA_DOUBLE.byteSize() * n);
|
||||
NativeMethods.WICKRA_ADAPTIVE_RSI_BATCH.invokeExact(handle, inputSeg, outSeg, (long) n);
|
||||
double[] out = new double[n];
|
||||
MemorySegment.copy(outSeg, JAVA_DOUBLE, 0L, out, 0, n);
|
||||
return out;
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_ADAPTIVE_RSI_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming Adl indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class Adl implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public Adl() {
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_ADL_NEW.invokeExact();
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid Adl parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_ADL_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double open, double high, double low, double close, double volume, long timestamp) {
|
||||
try {
|
||||
return (double) NativeMethods.WICKRA_ADL_UPDATE.invokeExact(handle, open, high, low, close, volume, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Vectorized update over a whole series; NaN at warmup positions. */
|
||||
public double[] batch(double[] open, double[] high, double[] low, double[] close, double[] volume, double[] timestamp) {
|
||||
int n = open.length;
|
||||
if (high.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (low.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (close.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (volume.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (timestamp.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment openSeg = a.allocateFrom(JAVA_DOUBLE, open);
|
||||
MemorySegment highSeg = a.allocateFrom(JAVA_DOUBLE, high);
|
||||
MemorySegment lowSeg = a.allocateFrom(JAVA_DOUBLE, low);
|
||||
MemorySegment closeSeg = a.allocateFrom(JAVA_DOUBLE, close);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment timestampSeg = a.allocateFrom(JAVA_DOUBLE, timestamp);
|
||||
MemorySegment outSeg = a.allocate(JAVA_DOUBLE.byteSize() * n);
|
||||
NativeMethods.WICKRA_ADL_BATCH.invokeExact(handle, openSeg, highSeg, lowSeg, closeSeg, volumeSeg, timestampSeg, outSeg, (long) n);
|
||||
double[] out = new double[n];
|
||||
MemorySegment.copy(outSeg, JAVA_DOUBLE, 0L, out, 0, n);
|
||||
return out;
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_ADL_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming AdvanceBlock indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class AdvanceBlock implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public AdvanceBlock() {
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_ADVANCE_BLOCK_NEW.invokeExact();
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid AdvanceBlock parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_ADVANCE_BLOCK_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double open, double high, double low, double close, double volume, long timestamp) {
|
||||
try {
|
||||
return (double) NativeMethods.WICKRA_ADVANCE_BLOCK_UPDATE.invokeExact(handle, open, high, low, close, volume, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Vectorized update over a whole series; NaN at warmup positions. */
|
||||
public double[] batch(double[] open, double[] high, double[] low, double[] close, double[] volume, double[] timestamp) {
|
||||
int n = open.length;
|
||||
if (high.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (low.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (close.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (volume.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (timestamp.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment openSeg = a.allocateFrom(JAVA_DOUBLE, open);
|
||||
MemorySegment highSeg = a.allocateFrom(JAVA_DOUBLE, high);
|
||||
MemorySegment lowSeg = a.allocateFrom(JAVA_DOUBLE, low);
|
||||
MemorySegment closeSeg = a.allocateFrom(JAVA_DOUBLE, close);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment timestampSeg = a.allocateFrom(JAVA_DOUBLE, timestamp);
|
||||
MemorySegment outSeg = a.allocate(JAVA_DOUBLE.byteSize() * n);
|
||||
NativeMethods.WICKRA_ADVANCE_BLOCK_BATCH.invokeExact(handle, openSeg, highSeg, lowSeg, closeSeg, volumeSeg, timestampSeg, outSeg, (long) n);
|
||||
double[] out = new double[n];
|
||||
MemorySegment.copy(outSeg, JAVA_DOUBLE, 0L, out, 0, n);
|
||||
return out;
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_ADVANCE_BLOCK_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming AdvanceDecline indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class AdvanceDecline implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public AdvanceDecline() {
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_ADVANCE_DECLINE_NEW.invokeExact();
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid AdvanceDecline parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_ADVANCE_DECLINE_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double[] change, double[] volume, double[] newHigh, double[] newLow, double[] aboveMa, double[] onBuySignal, long timestamp) {
|
||||
if (volume.length != change.length) {
|
||||
throw new IllegalArgumentException("input arrays in the same group must have equal length");
|
||||
}
|
||||
if (newHigh.length != change.length) {
|
||||
throw new IllegalArgumentException("input arrays in the same group must have equal length");
|
||||
}
|
||||
if (newLow.length != change.length) {
|
||||
throw new IllegalArgumentException("input arrays in the same group must have equal length");
|
||||
}
|
||||
if (aboveMa.length != change.length) {
|
||||
throw new IllegalArgumentException("input arrays in the same group must have equal length");
|
||||
}
|
||||
if (onBuySignal.length != change.length) {
|
||||
throw new IllegalArgumentException("input arrays in the same group must have equal length");
|
||||
}
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment changeSeg = a.allocateFrom(JAVA_DOUBLE, change);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment newHighSeg = a.allocateFrom(JAVA_DOUBLE, newHigh);
|
||||
MemorySegment newLowSeg = a.allocateFrom(JAVA_DOUBLE, newLow);
|
||||
MemorySegment aboveMaSeg = a.allocateFrom(JAVA_DOUBLE, aboveMa);
|
||||
MemorySegment onBuySignalSeg = a.allocateFrom(JAVA_DOUBLE, onBuySignal);
|
||||
return (double) NativeMethods.WICKRA_ADVANCE_DECLINE_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_ADVANCE_DECLINE_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming AdvanceDeclineRatio indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class AdvanceDeclineRatio implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public AdvanceDeclineRatio() {
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_ADVANCE_DECLINE_RATIO_NEW.invokeExact();
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid AdvanceDeclineRatio parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_ADVANCE_DECLINE_RATIO_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double[] change, double[] volume, double[] newHigh, double[] newLow, double[] aboveMa, double[] onBuySignal, long timestamp) {
|
||||
if (volume.length != change.length) {
|
||||
throw new IllegalArgumentException("input arrays in the same group must have equal length");
|
||||
}
|
||||
if (newHigh.length != change.length) {
|
||||
throw new IllegalArgumentException("input arrays in the same group must have equal length");
|
||||
}
|
||||
if (newLow.length != change.length) {
|
||||
throw new IllegalArgumentException("input arrays in the same group must have equal length");
|
||||
}
|
||||
if (aboveMa.length != change.length) {
|
||||
throw new IllegalArgumentException("input arrays in the same group must have equal length");
|
||||
}
|
||||
if (onBuySignal.length != change.length) {
|
||||
throw new IllegalArgumentException("input arrays in the same group must have equal length");
|
||||
}
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment changeSeg = a.allocateFrom(JAVA_DOUBLE, change);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment newHighSeg = a.allocateFrom(JAVA_DOUBLE, newHigh);
|
||||
MemorySegment newLowSeg = a.allocateFrom(JAVA_DOUBLE, newLow);
|
||||
MemorySegment aboveMaSeg = a.allocateFrom(JAVA_DOUBLE, aboveMa);
|
||||
MemorySegment onBuySignalSeg = a.allocateFrom(JAVA_DOUBLE, onBuySignal);
|
||||
return (double) NativeMethods.WICKRA_ADVANCE_DECLINE_RATIO_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_ADVANCE_DECLINE_RATIO_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming Adx indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class Adx implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public Adx(int period) {
|
||||
if (period < 0) {
|
||||
throw new IllegalArgumentException("period must be non-negative");
|
||||
}
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_ADX_NEW.invokeExact((long) period);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid Adx parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_ADX_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the result, or null during warmup. */
|
||||
public AdxOutput update(double open, double high, double low, double close, double volume, long timestamp) {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment out = a.allocate(24L);
|
||||
byte ok = (byte) NativeMethods.WICKRA_ADX_UPDATE.invokeExact(handle, open, high, low, close, volume, timestamp, out);
|
||||
if (ok == 0) {
|
||||
return null;
|
||||
}
|
||||
return new AdxOutput(
|
||||
out.get(JAVA_DOUBLE, 0L),
|
||||
out.get(JAVA_DOUBLE, 8L),
|
||||
out.get(JAVA_DOUBLE, 16L));
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_ADX_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -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 AdxOutput(double plusDi, double minusDi, double adx) {}
|
||||
@@ -0,0 +1,89 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming Adxr indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class Adxr implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public Adxr(int period) {
|
||||
if (period < 0) {
|
||||
throw new IllegalArgumentException("period must be non-negative");
|
||||
}
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_ADXR_NEW.invokeExact((long) period);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid Adxr parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_ADXR_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double open, double high, double low, double close, double volume, long timestamp) {
|
||||
try {
|
||||
return (double) NativeMethods.WICKRA_ADXR_UPDATE.invokeExact(handle, open, high, low, close, volume, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Vectorized update over a whole series; NaN at warmup positions. */
|
||||
public double[] batch(double[] open, double[] high, double[] low, double[] close, double[] volume, double[] timestamp) {
|
||||
int n = open.length;
|
||||
if (high.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (low.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (close.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (volume.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (timestamp.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment openSeg = a.allocateFrom(JAVA_DOUBLE, open);
|
||||
MemorySegment highSeg = a.allocateFrom(JAVA_DOUBLE, high);
|
||||
MemorySegment lowSeg = a.allocateFrom(JAVA_DOUBLE, low);
|
||||
MemorySegment closeSeg = a.allocateFrom(JAVA_DOUBLE, close);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment timestampSeg = a.allocateFrom(JAVA_DOUBLE, timestamp);
|
||||
MemorySegment outSeg = a.allocate(JAVA_DOUBLE.byteSize() * n);
|
||||
NativeMethods.WICKRA_ADXR_BATCH.invokeExact(handle, openSeg, highSeg, lowSeg, closeSeg, volumeSeg, timestampSeg, outSeg, (long) n);
|
||||
double[] out = new double[n];
|
||||
MemorySegment.copy(outSeg, JAVA_DOUBLE, 0L, out, 0, n);
|
||||
return out;
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_ADXR_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming Alligator indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class Alligator implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public Alligator(int jawPeriod, int teethPeriod, int lipsPeriod) {
|
||||
if (jawPeriod < 0) {
|
||||
throw new IllegalArgumentException("jawPeriod must be non-negative");
|
||||
}
|
||||
if (teethPeriod < 0) {
|
||||
throw new IllegalArgumentException("teethPeriod must be non-negative");
|
||||
}
|
||||
if (lipsPeriod < 0) {
|
||||
throw new IllegalArgumentException("lipsPeriod must be non-negative");
|
||||
}
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_ALLIGATOR_NEW.invokeExact((long) jawPeriod, (long) teethPeriod, (long) lipsPeriod);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid Alligator parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_ALLIGATOR_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the result, or null during warmup. */
|
||||
public AlligatorOutput update(double open, double high, double low, double close, double volume, long timestamp) {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment out = a.allocate(24L);
|
||||
byte ok = (byte) NativeMethods.WICKRA_ALLIGATOR_UPDATE.invokeExact(handle, open, high, low, close, volume, timestamp, out);
|
||||
if (ok == 0) {
|
||||
return null;
|
||||
}
|
||||
return new AlligatorOutput(
|
||||
out.get(JAVA_DOUBLE, 0L),
|
||||
out.get(JAVA_DOUBLE, 8L),
|
||||
out.get(JAVA_DOUBLE, 16L));
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_ALLIGATOR_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -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 AlligatorOutput(double jaw, double teeth, double lips) {}
|
||||
@@ -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.*;
|
||||
|
||||
/** Streaming Alma indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class Alma implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public Alma(int period, double offset, double sigma) {
|
||||
if (period < 0) {
|
||||
throw new IllegalArgumentException("period must be non-negative");
|
||||
}
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_ALMA_NEW.invokeExact((long) period, offset, sigma);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid Alma parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_ALMA_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double value) {
|
||||
try {
|
||||
return (double) NativeMethods.WICKRA_ALMA_UPDATE.invokeExact(handle, value);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Vectorized update over a whole series; NaN at warmup positions. */
|
||||
public double[] batch(double[] input) {
|
||||
int n = input.length;
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment inputSeg = a.allocateFrom(JAVA_DOUBLE, input);
|
||||
MemorySegment outSeg = a.allocate(JAVA_DOUBLE.byteSize() * n);
|
||||
NativeMethods.WICKRA_ALMA_BATCH.invokeExact(handle, inputSeg, outSeg, (long) n);
|
||||
double[] out = new double[n];
|
||||
MemorySegment.copy(outSeg, JAVA_DOUBLE, 0L, out, 0, n);
|
||||
return out;
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_ALMA_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming Alpha indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class Alpha implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public Alpha(int period, double riskFree) {
|
||||
if (period < 0) {
|
||||
throw new IllegalArgumentException("period must be non-negative");
|
||||
}
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_ALPHA_NEW.invokeExact((long) period, riskFree);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid Alpha parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_ALPHA_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double x, double y) {
|
||||
try {
|
||||
return (double) NativeMethods.WICKRA_ALPHA_UPDATE.invokeExact(handle, x, y);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Vectorized update over a whole series; NaN at warmup positions. */
|
||||
public double[] batch(double[] x, double[] y) {
|
||||
int n = x.length;
|
||||
if (y.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment xSeg = a.allocateFrom(JAVA_DOUBLE, x);
|
||||
MemorySegment ySeg = a.allocateFrom(JAVA_DOUBLE, y);
|
||||
MemorySegment outSeg = a.allocate(JAVA_DOUBLE.byteSize() * n);
|
||||
NativeMethods.WICKRA_ALPHA_BATCH.invokeExact(handle, xSeg, ySeg, outSeg, (long) n);
|
||||
double[] out = new double[n];
|
||||
MemorySegment.copy(outSeg, JAVA_DOUBLE, 0L, out, 0, n);
|
||||
return out;
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_ALPHA_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming AmihudIlliquidity indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class AmihudIlliquidity implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public AmihudIlliquidity(int period) {
|
||||
if (period < 0) {
|
||||
throw new IllegalArgumentException("period must be non-negative");
|
||||
}
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_AMIHUD_ILLIQUIDITY_NEW.invokeExact((long) period);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid AmihudIlliquidity parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_AMIHUD_ILLIQUIDITY_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double price, double size, boolean isBuy, long timestamp) {
|
||||
try {
|
||||
return (double) NativeMethods.WICKRA_AMIHUD_ILLIQUIDITY_UPDATE.invokeExact(handle, price, size, (byte) (isBuy ? 1 : 0), timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_AMIHUD_ILLIQUIDITY_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming AnchoredRsi indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class AnchoredRsi implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public AnchoredRsi() {
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_ANCHORED_RSI_NEW.invokeExact();
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid AnchoredRsi parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_ANCHORED_RSI_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double value) {
|
||||
try {
|
||||
return (double) NativeMethods.WICKRA_ANCHORED_RSI_UPDATE.invokeExact(handle, value);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Vectorized update over a whole series; NaN at warmup positions. */
|
||||
public double[] batch(double[] input) {
|
||||
int n = input.length;
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment inputSeg = a.allocateFrom(JAVA_DOUBLE, input);
|
||||
MemorySegment outSeg = a.allocate(JAVA_DOUBLE.byteSize() * n);
|
||||
NativeMethods.WICKRA_ANCHORED_RSI_BATCH.invokeExact(handle, inputSeg, outSeg, (long) n);
|
||||
double[] out = new double[n];
|
||||
MemorySegment.copy(outSeg, JAVA_DOUBLE, 0L, out, 0, n);
|
||||
return out;
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_ANCHORED_RSI_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming AnchoredVwap indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class AnchoredVwap implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public AnchoredVwap() {
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_ANCHORED_VWAP_NEW.invokeExact();
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid AnchoredVwap parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_ANCHORED_VWAP_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double open, double high, double low, double close, double volume, long timestamp) {
|
||||
try {
|
||||
return (double) NativeMethods.WICKRA_ANCHORED_VWAP_UPDATE.invokeExact(handle, open, high, low, close, volume, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Vectorized update over a whole series; NaN at warmup positions. */
|
||||
public double[] batch(double[] open, double[] high, double[] low, double[] close, double[] volume, double[] timestamp) {
|
||||
int n = open.length;
|
||||
if (high.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (low.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (close.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (volume.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (timestamp.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment openSeg = a.allocateFrom(JAVA_DOUBLE, open);
|
||||
MemorySegment highSeg = a.allocateFrom(JAVA_DOUBLE, high);
|
||||
MemorySegment lowSeg = a.allocateFrom(JAVA_DOUBLE, low);
|
||||
MemorySegment closeSeg = a.allocateFrom(JAVA_DOUBLE, close);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment timestampSeg = a.allocateFrom(JAVA_DOUBLE, timestamp);
|
||||
MemorySegment outSeg = a.allocate(JAVA_DOUBLE.byteSize() * n);
|
||||
NativeMethods.WICKRA_ANCHORED_VWAP_BATCH.invokeExact(handle, openSeg, highSeg, lowSeg, closeSeg, volumeSeg, timestampSeg, outSeg, (long) n);
|
||||
double[] out = new double[n];
|
||||
MemorySegment.copy(outSeg, JAVA_DOUBLE, 0L, out, 0, n);
|
||||
return out;
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_ANCHORED_VWAP_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming AndrewsPitchfork indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class AndrewsPitchfork implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public AndrewsPitchfork(int strength) {
|
||||
if (strength < 0) {
|
||||
throw new IllegalArgumentException("strength must be non-negative");
|
||||
}
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_ANDREWS_PITCHFORK_NEW.invokeExact((long) strength);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid AndrewsPitchfork parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_ANDREWS_PITCHFORK_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the result, or null during warmup. */
|
||||
public AndrewsPitchforkOutput update(double open, double high, double low, double close, double volume, long timestamp) {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment out = a.allocate(24L);
|
||||
byte ok = (byte) NativeMethods.WICKRA_ANDREWS_PITCHFORK_UPDATE.invokeExact(handle, open, high, low, close, volume, timestamp, out);
|
||||
if (ok == 0) {
|
||||
return null;
|
||||
}
|
||||
return new AndrewsPitchforkOutput(
|
||||
out.get(JAVA_DOUBLE, 0L),
|
||||
out.get(JAVA_DOUBLE, 8L),
|
||||
out.get(JAVA_DOUBLE, 16L));
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_ANDREWS_PITCHFORK_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -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 AndrewsPitchforkOutput(double median, double upper, double lower) {}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming Apo indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class Apo implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public Apo(int fast, int slow) {
|
||||
if (fast < 0) {
|
||||
throw new IllegalArgumentException("fast must be non-negative");
|
||||
}
|
||||
if (slow < 0) {
|
||||
throw new IllegalArgumentException("slow must be non-negative");
|
||||
}
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_APO_NEW.invokeExact((long) fast, (long) slow);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid Apo parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_APO_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double value) {
|
||||
try {
|
||||
return (double) NativeMethods.WICKRA_APO_UPDATE.invokeExact(handle, value);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Vectorized update over a whole series; NaN at warmup positions. */
|
||||
public double[] batch(double[] input) {
|
||||
int n = input.length;
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment inputSeg = a.allocateFrom(JAVA_DOUBLE, input);
|
||||
MemorySegment outSeg = a.allocate(JAVA_DOUBLE.byteSize() * n);
|
||||
NativeMethods.WICKRA_APO_BATCH.invokeExact(handle, inputSeg, outSeg, (long) n);
|
||||
double[] out = new double[n];
|
||||
MemorySegment.copy(outSeg, JAVA_DOUBLE, 0L, out, 0, n);
|
||||
return out;
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_APO_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming Aroon indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class Aroon implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public Aroon(int period) {
|
||||
if (period < 0) {
|
||||
throw new IllegalArgumentException("period must be non-negative");
|
||||
}
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_AROON_NEW.invokeExact((long) period);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid Aroon parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_AROON_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the result, or null during warmup. */
|
||||
public AroonOutput update(double open, double high, double low, double close, double volume, long timestamp) {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment out = a.allocate(16L);
|
||||
byte ok = (byte) NativeMethods.WICKRA_AROON_UPDATE.invokeExact(handle, open, high, low, close, volume, timestamp, out);
|
||||
if (ok == 0) {
|
||||
return null;
|
||||
}
|
||||
return new AroonOutput(
|
||||
out.get(JAVA_DOUBLE, 0L),
|
||||
out.get(JAVA_DOUBLE, 8L));
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_AROON_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming AroonOscillator indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class AroonOscillator implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public AroonOscillator(int period) {
|
||||
if (period < 0) {
|
||||
throw new IllegalArgumentException("period must be non-negative");
|
||||
}
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_AROON_OSCILLATOR_NEW.invokeExact((long) period);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid AroonOscillator parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_AROON_OSCILLATOR_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double open, double high, double low, double close, double volume, long timestamp) {
|
||||
try {
|
||||
return (double) NativeMethods.WICKRA_AROON_OSCILLATOR_UPDATE.invokeExact(handle, open, high, low, close, volume, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Vectorized update over a whole series; NaN at warmup positions. */
|
||||
public double[] batch(double[] open, double[] high, double[] low, double[] close, double[] volume, double[] timestamp) {
|
||||
int n = open.length;
|
||||
if (high.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (low.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (close.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (volume.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (timestamp.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment openSeg = a.allocateFrom(JAVA_DOUBLE, open);
|
||||
MemorySegment highSeg = a.allocateFrom(JAVA_DOUBLE, high);
|
||||
MemorySegment lowSeg = a.allocateFrom(JAVA_DOUBLE, low);
|
||||
MemorySegment closeSeg = a.allocateFrom(JAVA_DOUBLE, close);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment timestampSeg = a.allocateFrom(JAVA_DOUBLE, timestamp);
|
||||
MemorySegment outSeg = a.allocate(JAVA_DOUBLE.byteSize() * n);
|
||||
NativeMethods.WICKRA_AROON_OSCILLATOR_BATCH.invokeExact(handle, openSeg, highSeg, lowSeg, closeSeg, volumeSeg, timestampSeg, outSeg, (long) n);
|
||||
double[] out = new double[n];
|
||||
MemorySegment.copy(outSeg, JAVA_DOUBLE, 0L, out, 0, n);
|
||||
return out;
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_AROON_OSCILLATOR_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -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 AroonOutput(double up, double down) {}
|
||||
@@ -0,0 +1,89 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming Atr indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class Atr implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public Atr(int period) {
|
||||
if (period < 0) {
|
||||
throw new IllegalArgumentException("period must be non-negative");
|
||||
}
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_ATR_NEW.invokeExact((long) period);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid Atr parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_ATR_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double open, double high, double low, double close, double volume, long timestamp) {
|
||||
try {
|
||||
return (double) NativeMethods.WICKRA_ATR_UPDATE.invokeExact(handle, open, high, low, close, volume, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Vectorized update over a whole series; NaN at warmup positions. */
|
||||
public double[] batch(double[] open, double[] high, double[] low, double[] close, double[] volume, double[] timestamp) {
|
||||
int n = open.length;
|
||||
if (high.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (low.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (close.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (volume.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (timestamp.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment openSeg = a.allocateFrom(JAVA_DOUBLE, open);
|
||||
MemorySegment highSeg = a.allocateFrom(JAVA_DOUBLE, high);
|
||||
MemorySegment lowSeg = a.allocateFrom(JAVA_DOUBLE, low);
|
||||
MemorySegment closeSeg = a.allocateFrom(JAVA_DOUBLE, close);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment timestampSeg = a.allocateFrom(JAVA_DOUBLE, timestamp);
|
||||
MemorySegment outSeg = a.allocate(JAVA_DOUBLE.byteSize() * n);
|
||||
NativeMethods.WICKRA_ATR_BATCH.invokeExact(handle, openSeg, highSeg, lowSeg, closeSeg, volumeSeg, timestampSeg, outSeg, (long) n);
|
||||
double[] out = new double[n];
|
||||
MemorySegment.copy(outSeg, JAVA_DOUBLE, 0L, out, 0, n);
|
||||
return out;
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_ATR_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming AtrBands indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class AtrBands implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public AtrBands(int period, double multiplier) {
|
||||
if (period < 0) {
|
||||
throw new IllegalArgumentException("period must be non-negative");
|
||||
}
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_ATR_BANDS_NEW.invokeExact((long) period, multiplier);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid AtrBands parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_ATR_BANDS_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the result, or null during warmup. */
|
||||
public AtrBandsOutput update(double open, double high, double low, double close, double volume, long timestamp) {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment out = a.allocate(24L);
|
||||
byte ok = (byte) NativeMethods.WICKRA_ATR_BANDS_UPDATE.invokeExact(handle, open, high, low, close, volume, timestamp, out);
|
||||
if (ok == 0) {
|
||||
return null;
|
||||
}
|
||||
return new AtrBandsOutput(
|
||||
out.get(JAVA_DOUBLE, 0L),
|
||||
out.get(JAVA_DOUBLE, 8L),
|
||||
out.get(JAVA_DOUBLE, 16L));
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_ATR_BANDS_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -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 AtrBandsOutput(double upper, double middle, double lower) {}
|
||||
@@ -0,0 +1,61 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming AtrRatchet indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class AtrRatchet implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public AtrRatchet(int atrPeriod, double startMult, double increment) {
|
||||
if (atrPeriod < 0) {
|
||||
throw new IllegalArgumentException("atrPeriod must be non-negative");
|
||||
}
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_ATR_RATCHET_NEW.invokeExact((long) atrPeriod, startMult, increment);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid AtrRatchet parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_ATR_RATCHET_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the result, or null during warmup. */
|
||||
public AtrRatchetOutput update(double open, double high, double low, double close, double volume, long timestamp) {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment out = a.allocate(16L);
|
||||
byte ok = (byte) NativeMethods.WICKRA_ATR_RATCHET_UPDATE.invokeExact(handle, open, high, low, close, volume, timestamp, out);
|
||||
if (ok == 0) {
|
||||
return null;
|
||||
}
|
||||
return new AtrRatchetOutput(
|
||||
out.get(JAVA_DOUBLE, 0L),
|
||||
out.get(JAVA_DOUBLE, 8L));
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_ATR_RATCHET_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -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 AtrRatchetOutput(double value, double direction) {}
|
||||
@@ -0,0 +1,89 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming AtrTrailingStop indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class AtrTrailingStop implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public AtrTrailingStop(int atrPeriod, double multiplier) {
|
||||
if (atrPeriod < 0) {
|
||||
throw new IllegalArgumentException("atrPeriod must be non-negative");
|
||||
}
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_ATR_TRAILING_STOP_NEW.invokeExact((long) atrPeriod, multiplier);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid AtrTrailingStop parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_ATR_TRAILING_STOP_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double open, double high, double low, double close, double volume, long timestamp) {
|
||||
try {
|
||||
return (double) NativeMethods.WICKRA_ATR_TRAILING_STOP_UPDATE.invokeExact(handle, open, high, low, close, volume, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Vectorized update over a whole series; NaN at warmup positions. */
|
||||
public double[] batch(double[] open, double[] high, double[] low, double[] close, double[] volume, double[] timestamp) {
|
||||
int n = open.length;
|
||||
if (high.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (low.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (close.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (volume.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
if (timestamp.length != n) {
|
||||
throw new IllegalArgumentException("all input arrays must have the same length");
|
||||
}
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment openSeg = a.allocateFrom(JAVA_DOUBLE, open);
|
||||
MemorySegment highSeg = a.allocateFrom(JAVA_DOUBLE, high);
|
||||
MemorySegment lowSeg = a.allocateFrom(JAVA_DOUBLE, low);
|
||||
MemorySegment closeSeg = a.allocateFrom(JAVA_DOUBLE, close);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment timestampSeg = a.allocateFrom(JAVA_DOUBLE, timestamp);
|
||||
MemorySegment outSeg = a.allocate(JAVA_DOUBLE.byteSize() * n);
|
||||
NativeMethods.WICKRA_ATR_TRAILING_STOP_BATCH.invokeExact(handle, openSeg, highSeg, lowSeg, closeSeg, volumeSeg, timestampSeg, outSeg, (long) n);
|
||||
double[] out = new double[n];
|
||||
MemorySegment.copy(outSeg, JAVA_DOUBLE, 0L, out, 0, n);
|
||||
return out;
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_ATR_TRAILING_STOP_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming AutoFib indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class AutoFib implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public AutoFib() {
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_AUTO_FIB_NEW.invokeExact();
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid AutoFib parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_AUTO_FIB_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the result, or null during warmup. */
|
||||
public AutoFibOutput update(double open, double high, double low, double close, double volume, long timestamp) {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment out = a.allocate(56L);
|
||||
byte ok = (byte) NativeMethods.WICKRA_AUTO_FIB_UPDATE.invokeExact(handle, open, high, low, close, volume, timestamp, out);
|
||||
if (ok == 0) {
|
||||
return null;
|
||||
}
|
||||
return new AutoFibOutput(
|
||||
out.get(JAVA_DOUBLE, 0L),
|
||||
out.get(JAVA_DOUBLE, 8L),
|
||||
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));
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_AUTO_FIB_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -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 AutoFibOutput(double level0, double level236, double level382, double level500, double level618, double level786, double level1000) {}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming Autocorrelation indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class Autocorrelation implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public Autocorrelation(int period, int lag) {
|
||||
if (period < 0) {
|
||||
throw new IllegalArgumentException("period must be non-negative");
|
||||
}
|
||||
if (lag < 0) {
|
||||
throw new IllegalArgumentException("lag must be non-negative");
|
||||
}
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_AUTOCORRELATION_NEW.invokeExact((long) period, (long) lag);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid Autocorrelation parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_AUTOCORRELATION_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double value) {
|
||||
try {
|
||||
return (double) NativeMethods.WICKRA_AUTOCORRELATION_UPDATE.invokeExact(handle, value);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Vectorized update over a whole series; NaN at warmup positions. */
|
||||
public double[] batch(double[] input) {
|
||||
int n = input.length;
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment inputSeg = a.allocateFrom(JAVA_DOUBLE, input);
|
||||
MemorySegment outSeg = a.allocate(JAVA_DOUBLE.byteSize() * n);
|
||||
NativeMethods.WICKRA_AUTOCORRELATION_BATCH.invokeExact(handle, inputSeg, outSeg, (long) n);
|
||||
double[] out = new double[n];
|
||||
MemorySegment.copy(outSeg, JAVA_DOUBLE, 0L, out, 0, n);
|
||||
return out;
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_AUTOCORRELATION_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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.*;
|
||||
|
||||
/** Streaming AutocorrelationPeriodogram indicator over the Wickra C ABI. Not thread-safe; close when done. */
|
||||
public final class AutocorrelationPeriodogram implements AutoCloseable {
|
||||
private final MemorySegment handle;
|
||||
private final Cleaner.Cleanable cleanable;
|
||||
|
||||
public AutocorrelationPeriodogram(int minPeriod, int maxPeriod) {
|
||||
if (minPeriod < 0) {
|
||||
throw new IllegalArgumentException("minPeriod must be non-negative");
|
||||
}
|
||||
if (maxPeriod < 0) {
|
||||
throw new IllegalArgumentException("maxPeriod must be non-negative");
|
||||
}
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_AUTOCORRELATION_PERIODOGRAM_NEW.invokeExact((long) minPeriod, (long) maxPeriod);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
if (h.address() == 0L) {
|
||||
throw new IllegalArgumentException("invalid AutocorrelationPeriodogram parameters");
|
||||
}
|
||||
this.handle = h;
|
||||
this.cleanable = WickraNative.register(this, h, NativeMethods.WICKRA_AUTOCORRELATION_PERIODOGRAM_FREE);
|
||||
}
|
||||
|
||||
/** Push one observation; returns the indicator value (NaN during warmup). */
|
||||
public double update(double value) {
|
||||
try {
|
||||
return (double) NativeMethods.WICKRA_AUTOCORRELATION_PERIODOGRAM_UPDATE.invokeExact(handle, value);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Vectorized update over a whole series; NaN at warmup positions. */
|
||||
public double[] batch(double[] input) {
|
||||
int n = input.length;
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment inputSeg = a.allocateFrom(JAVA_DOUBLE, input);
|
||||
MemorySegment outSeg = a.allocate(JAVA_DOUBLE.byteSize() * n);
|
||||
NativeMethods.WICKRA_AUTOCORRELATION_PERIODOGRAM_BATCH.invokeExact(handle, inputSeg, outSeg, (long) n);
|
||||
double[] out = new double[n];
|
||||
MemorySegment.copy(outSeg, JAVA_DOUBLE, 0L, out, 0, n);
|
||||
return out;
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Reset to the just-constructed state. */
|
||||
public void reset() {
|
||||
try {
|
||||
NativeMethods.WICKRA_AUTOCORRELATION_PERIODOGRAM_RESET.invokeExact(handle);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void close() {
|
||||
cleanable.clean();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user