Compare commits

..

51 Commits

Author SHA1 Message Date
kingchenc d7cb771a28 release: bump 0.8.4 -> 0.8.5 (#258)
Version bump `0.8.4` → `0.8.5`.

### 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 (this was failing the r-universe build of 0.8.4). The parity stays enforced by the repository CI, where the fixtures are present. (#257)

Bump touches the manual release touchpoints only (`Cargo.toml`/`Cargo.lock`, Python/Node/Java/C#/R manifests, lockfiles, `SECURITY.md`, `CHANGELOG.md`). docs/webpage version strings are left to `sync-about.yml` on the tag.
2026-06-11 17:13:52 +02:00
kingchenc 3e5a19c94a fix(r): skip golden-fixture test in standalone package builds + document parity (#257)
## Problem
The r-universe build of `wickra` 0.8.4 fails (`R CMD check` ERROR): the golden-fixture parity test added in #255 walks up from the working directory looking for `testdata/golden` and `stop()`s when it cannot find it. Standalone package builds (r-universe / CRAN) package only `bindings/r`, so the repo-root `testdata/golden` fixtures are unreachable there — whereas the monorepo CI checks out the full repo, so the walk-up succeeds and the test passes.

Run: https://github.com/r-universe/wickra-lib/actions/runs/27354800361

## Fix
- **`bindings/r/tests/testthat/test-golden.R`** — the fixture-directory lookup now returns `NULL` instead of erroring when the fixtures are absent, and each test starts with `skip_if(is.null(golden_dir), ...)`. The repository CI (full repo present) still runs the parity checks; standalone builds skip them. The per-test `golden_input` read moved inside the (post-skip) test bodies so nothing runs at source time when the fixtures are missing.
- **`CHANGELOG.md`** — `[Unreleased]` Fixed entry.

## Docs (B6)
- **`README.md` `## Testing`** — the four C-ABI bindings (C#, Go, Java, R) were described as covering one indicator per FFI archetype; document that they additionally replay the shared golden fixture and assert exact parity with the Rust reference outputs.
2026-06-11 17:08:33 +02:00
kingchenc 8bfe24ac1d test(golden): language-neutral fixtures + per-binding parity runners (#255)
**Task 5 — golden-fixture parity for the C-ABI bindings.** Lifts the C#/Go/Java/R tests from *one indicator per archetype* toward reference-value parity, catching FFI wiring bugs (swapped params, wrong multi-output field) the math-only core tests cannot see.

## What's here
- **`examples/rust/src/bin/gen_golden.rs`** + **`testdata/golden/*.csv`** — a Rust generator computing a deterministic OHLCV series plus the core's reference outputs for a curated archetype-spanning set: scalar (`Sma`/`Ema`/`Rsi`), candle (`Atr`), scalar multi-output (`MACD`), candle multi-output (`ADX`), pairwise (`Beta`). `nan` marks warmup. Regenerate with `cargo run -p wickra-examples --bin gen_golden`.
- **Parity runners** replaying the identical fixtures through each FFI (rel-tol 1e-6), each a standard test in the binding's existing suite (no `ci.yml` change — rides `dotnet test` / `go test` / `mvn install` / `R CMD`+testthat). A walk-up search locates `testdata/golden` regardless of run dir.
  - **C#** (`bindings/csharp/.../GoldenTests.cs`) —  validated locally, 7/7 pass.
  - **Go** (`bindings/go/golden_test.go`) —  validated locally, pass.
  - **Java** (`bindings/java/.../GoldenTests.java`) — modeled on the archetype API; validated by CI (no local mvn).
  - **R** (`bindings/r/tests/testthat/test-golden.R`) — modeled on the archetype API; validated by CI (no local Rscript).

## Notes
- The curated set spans every marshalling archetype; extending the indicator list is mechanical (add to the generator + regenerate). Bars/profile archetypes can be added next.
- No new CI jobs.
2026-06-11 15:22:29 +02:00
kingchenc 395a2289f4 release: bump 0.8.3 -> 0.8.4 (#256)
Version bump `0.8.3` → `0.8.4`.

Ships the work merged via #254:

### Fixed
- A single non-finite (NaN/inf) tick no longer poisons indicator state — 38 more scalar/pairwise indicators (linear-regression family, rolling quantiles/IQR, `Variance`/`StdDev`-derived stats, `Kurtosis`/`Skewness`, trailing stops, `KalmanHedgeRatio`, `SpreadBollingerBands`, …) now reject non-finite input and return `None`, joining the 16 pairwise indicators fixed earlier.

### 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 flaky Node test step auto-retries.
- Documentation accuracy fixes in `SECURITY.md`, `ARCHITECTURE.md`, and `THREAT_MODEL.md`.

Bump touches the manual release touchpoints only (`Cargo.toml`/`Cargo.lock`, Python/Node/Java/C#/R manifests, lockfiles, `SECURITY.md`, `CHANGELOG.md`). docs/webpage version strings are left to `sync-about.yml` on the tag.
2026-06-11 14:57:15 +02:00
kingchenc 9973d1a6bf test(core): catalogue-wide invariant harness + guard non-finite input in 38 indicators (#254)
Adds `crates/wickra-core/tests/invariants.rs` — a property-based (`proptest`) harness asserting three invariants for **every** indicator and bar-builder in the catalogue (every module entry implementing `Indicator` or `BarBuilder`; the lone non-indicator helper `pattern_swing`/`SwingTracker` is excluded by design) — and fixes the non-finite bugs the harness surfaced.

## Invariants
1. **batch == streaming** — `batch()` must replay `update()` exactly.
2. **reset == fresh** — after `reset()`, re-feeding the same data matches a fresh instance.
3. **non-finite rejected without poisoning** (`f64` / `(f64, f64)` families) — a NaN/inf tick returns `None` and leaves state identical to never having seen it.

## How it works
- A single generic `check_seq<I: Indicator>` covers **all** input families — `f64`, `Candle`, `(f64, f64)`, and the exotic `CrossSection`, `Trade`, `DerivativesTick`, `OrderBook`, `TradeQuote` — via per-family `proptest` generators that produce *valid* inputs (e.g. order books are strictly monotonic and uncrossed).
- `check_bars<B: BarBuilder>` covers the bar-builder trait.
- Outputs are compared by `Debug` string so bit-identical `NaN` outputs count as equal — these properties test **determinism**, not NaN-freeness.

## The 38 non-finite fixes
The harness surfaced **38 more** scalar/pairwise indicators that let a NaN/inf tick poison their state — the same class as the 16 pairwise indicators fixed earlier (#251), but missed by the grep-based audit (`kalman_hedge_ratio` and `spread_bollinger_bands` carried `is_finite` on their **constructor** params, not the update input). All 38 now reject non-finite input via the established first-statement guard; signatures unchanged, so every binding inherits the fix. With these in, the non-finite invariant is enforced for every `f64`/`(f64,f64)` indicator going forward — the permanent regression net that would have caught #251.

Warmup-exactness was evaluated and **left out** (not universal — many multi-component/candlestick indicators emit before `warmup_period` by design).

## Verification
- `cargo test -p wickra-core`: 4225 unit + harness + 464 doc/integration, all green.
- `cargo clippy --workspace --all-targets --all-features -- -D warnings`: clean.
- Coverage runs integration tests, so the new guard branches are exercised by the harness's NaN feed.

Also documents the harness in README's Testing section and adds the pending 0.8.4 entries to CHANGELOG `[Unreleased]`.
2026-06-11 14:51:46 +02:00
kingchenc cb216668ee docs: fix accuracy drift in SECURITY, ARCHITECTURE, THREAT_MODEL (#253)
Documentation-only accuracy fixes from the codebase audit (no code changes).

## SECURITY.md
- Supported-version policy was stale at `0.5.x` while `0.8.3` is published. Bump to the exact `0.8.3` (prose + table `0.8.3 (latest)` / `< 0.8.3`). The exact `x.y.z` form lets `bump_version.py` keep it current automatically (now wired as a touchpoint).

## ARCHITECTURE.md
- `three` → **four** binding crates (the C ABI crate was added).
- workspace diagram `214` → **514** indicators (matches the `mod`-count and `lib.rs` public-type count; now wired into the indicator-wiring automation so it self-heals).
- WASM "does not have automated tests yet" → corrected: `bindings/wasm/src/lib.rs` carries **21** `wasm-bindgen-test` cases.
- **Numerical-stability notes rewritten to match the code:** the sliding-window variance family (`StdDev`, `Variance`, `ZScore`, `Bollinger`) uses running `Σx²−mean²` with clamping (and periodic reseed for `Bollinger`), **not** Welford. True Welford is used only by `IntradayVolatilityProfile` and `SeasonalZScore` (it does not transfer cleanly to a sliding window). The **Kahan-summation** bullet is removed — no Kahan summation exists in the crate.

## THREAT_MODEL.md
- The C ABI is built with `panic = "abort"` and has no `catch_unwind`. Replace the false "catches panics so none cross the boundary" claim with the honest abort strategy (terminates deterministically instead of unwinding across the FFI boundary, which would be UB).
2026-06-11 03:28:19 +02:00
kingchenc 20c0002f8e ci: cap job runtimes and auto-retry the flaky Node test step (#252)
## Problem

A Node test job wedged on a macOS runner: the **Run Node tests** step (`node --test`) hung for **over an hour** while the identical tests passed in ~1.5 min on every other runner (Node 20 macOS same run: 1m46s; Node 18 macOS on a sibling PR: 1m34s). It is a one-off stuck process — the macOS analogue of the documented `setup-node` Windows CDN flake — not a real Node 18 vs 20 difference.

No job in `ci.yml` set any `timeout-minutes`, so a hung step runs toward GitHub's **6h default** before the platform kills it.

## Changes

- **Job-level `timeout-minutes: 20` backstop on all 15 jobs.** The slowest real job is ~5 min, so 20 min is generous headroom (survives cold-cache spikes) while turning a 6h hang into a 20-min fail. The clock counts execution time only — queued/waiting time does not count against it.
- **`Run Node tests` wrapped in `nick-fields/retry` (SHA-pinned, v4.0.0):** a hung attempt is killed after 6 min and retried once (`timeout_minutes: 6`, `max_attempts: 2`), so a one-off wedge self-heals without a manual re-run. Normal run is well under a minute; worst case 2×6 min stays under the 20-min job backstop.

## Notes

- New SHA-pinned third-party action (`nick-fields/retry@ad98453…` = v4.0.0) — satisfies the SHA-pin convention; `zizmor` runs on this PR.
- `ci.yml` validated as well-formed YAML; all 15 jobs confirmed to carry a job-level timeout.
2026-06-11 03:27:05 +02:00
kingchenc 99497eb062 fix(core): reject non-finite input in 16 pairwise indicators (#251)
## Problem

16 of the 24 pairwise indicators (`type Input = (f64, f64)`) accepted non-finite input unchecked. A single NaN/Inf tick produced a NaN reading, contradicting the streaming-robustness guarantee that *a single bad tick cannot silently poison state* (README, `ARCHITECTURE.md`).

The other 8 pairwise indicators (`Alpha`, `InformationRatio`, `KalmanHedgeRatio`, `PairSpreadZScore`, `PairwiseBeta`, `RelativeStrengthAb`, `SpreadBollingerBands`, `TreynorRatio`) already guard finite input and are untouched. Scalar (169 files) and candle (`Candle::new`) inputs were already protected.

## Severity split

- **7 running-sum indicators** \(permanent corruption — NaN entered `Σ` and stayed until `reset()`\): `Beta`, `BetaNeutralSpread`, `Cointegration`, `HasbrouckInformationShare`, `PearsonCorrelation`, `RollingCorrelation`, `RollingCovariance`.
- **9 buffer-recompute indicators** \(transient — NaN cleared once evicted, but still surfaced in the reading\): `DistanceSsd`, `GrangerCausality`, `KendallTau`, `LeadLagCrossCorrelation`, `OuHalfLife`, `SpearmanCorrelation`, `SpreadAr1Coefficient`, `SpreadHurst`, `VarianceRatio`.

## Fix

Add the established finite-input guard (the same pattern `Alpha` already uses) as the first step of `update()`, before any window or sum mutation. Signature unchanged → bindings re-export unchanged, no binding regen needed; core-only.

Each indicator gains a `non_finite_input_returns_none` test that exercises the guard (NaN and Inf, covering both `||` operands) and proves a clean warmup is unaffected by the rejected ticks.

Split into two commits by severity class.

## Verification

- `cargo fmt --all` clean
- `cargo test --workspace --all-features` — 4225 core tests pass (+16 new)
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` clean
2026-06-11 03:01:24 +02:00
kingchenc 7e51f31f02 sync-about: propagate published version into Java install snippets (#250)
## Problem

Java is the only target whose install snippet pins a version (the Maven coordinate). Every other language exposes its version through an api/*.md `Latest:` line or the docs published-versions table, both of which `sync-about.yml` already rewrites on each `v*` tag. The Java `<version>` tag and the `org.wickra:wickra:<v>` Gradle coordinate were never wired in, so they drifted:

- `webpage/index.md` install tab → stuck on `0.8.0`
- `webpage/api/java.md` → stuck on `0.7.9`
- `wickra-docs/Quickstart-Java.md` (Maven `<version>` + Gradle coord) → stuck on `0.7.9`

while the published version is already `0.8.3`.

## Fix

Extend the webpage and docs version-sync steps to rewrite the Maven `<version>` tag and the Gradle coordinate to the released `${version}`, and add `index.md` / `Quickstart-Java.md` to the respective commits. Java publishes on every release, so it tracks the same version as the rest — future releases self-heal these snippets.

Patterns dry-run-verified against the live files (all three resolve to `0.8.3`). The current drift is corrected directly in companion PRs on the webpage and wickra-docs repos.
2026-06-11 01:38:54 +02:00
kingchenc 3ee3fb67ec release: bump 0.8.2 -> 0.8.3 (#249)
Version bump 0.8.2 → 0.8.3, releasing the per-binding throughput benchmark work
(merged in #246).

Bumped via `ScriptHelpers/bump_version.py` across all manual touchpoints —
`Cargo.toml`, `pyproject.toml`, the Node `package.json` + 6 platform packages +
both lockfiles, the Java `pom.xml`s + README, the C# `.csproj`, the R
`DESCRIPTION` — plus `Cargo.lock` (via `cargo build`) and the `CHANGELOG.md`
`[0.8.3]` section and compare URLs.

CHANGELOG `[0.8.3]`:
- Per-binding throughput benchmarks for all 9 targets (BENCHMARKS.md §3).
- C ABI archetype test (`examples/c/archetypes.c`).

`cargo fmt`, `cargo test --workspace --all-features` (all green) and
`cargo clippy --workspace --all-targets --all-features -D warnings` pass. The
docs/webpage version strings are bumped by `sync-about.yml` on the `v*` tag —
not touched here.
2026-06-10 03:55:39 +02:00
kingchenc 3ebcb3f758 Per-binding throughput benchmarks + test-coverage gaps (#246)
Adds a `throughput` benchmark to every target and closes two small
test-coverage documentation/QA gaps. One PR, no merge of binding code beyond
the additive benchmarks and one C test.

## 1. Per-binding throughput benchmarks (all 9 targets)

Each benchmark feeds a deterministic synthetic OHLCV series through three
indicators chosen by **FFI call-signature archetype** (not algorithm — the same
Rust core runs underneath all bindings):

- `SMA(20)` — 1-in → 1-out (baseline boundary cost)
- `ATR(14)` — multi-in → 1-out (input marshalling)
- `MACD(12,26,9)` — 1-in → multi-out (output marshalling)

Streaming is timed for all three; batch for the single-output SMA and ATR
(median of 3 runs, after a warmup pass).

New: Python (PyO3), WASM, C (CMake), C# (Stopwatch), Go, Java (FFM), R, and the
Rust core baseline (`examples/rust/.../throughput.rs`, **no FFI** — the ceiling
the bindings are measured against and the value their batch paths converge
towards). Node already had `throughput.js`.

**Not a speed claim:** there is no comparable streaming TA library for C, C#,
Go, Java, R or WASM to compare against, so these are raw per-binding throughput
numbers documenting each language's FFI overhead — see BENCHMARKS.md §3. The
"Wickra is fast" claim still lives in §1/§2 (Rust core + the Python/Rust
cross-library runs).

## 2. README `## Testing`: C# and C bullets

The section listed every layer except C# and C, even though both have suites.
Adds the two missing bullets.

## 3. C archetype ctest

`examples/c/archetypes.c` drives one indicator per FFI archetype through the
real C boundary (scalar + batch==streaming, multi-output, bars, profile, array
input) plus reset, invalid-parameter and NULL-safety — the C counterpart of the
Go/R/Java archetype suites. Runs on three OSes via the existing CMake/ctest.

## Notes

- Benchmarks are not CI-gated (manual-run scripts, like the existing
  `throughput.js`); no `ci.yml`/`release.yml` changes.
- Docs: BENCHMARKS.md §3, a `## Benchmark` section in every binding README, a
  CHANGELOG entry.
- Verified locally by running: Rust, Python, C, C#, Go, Java (real numbers); the
  C archetype ctest with `-Wall -Wextra -Wpedantic -Werror`. WASM and R are
  API-correct and syntax-checked but need their own toolchains to run.
2026-06-10 03:46:38 +02:00
kingchenc b31a9a3624 release: bump 0.8.1 -> 0.8.2 (#245)
Patch release shipping the R WebAssembly build fix (#244): `bindings/r/configure` builds the C ABI from source for `wasm32-unknown-emscripten`, so r-universe's webR build stops failing. Also carries the shared Go badge in `bindings/go/README.md`. Version bumped across all manual touchpoints via `bump_version.py` (incl. DESCRIPTION + csproj, which had drifted before).
2026-06-10 01:57:10 +02:00
kingchenc 6433c9b3de bindings/r: build the C ABI from source for the WebAssembly target (#244)
## Problem
r-universe builds every package to WebAssembly (webR) in addition to the native platforms, calling `./configure --host=wasm32-unknown-emscripten`. `bindings/r/configure` only knew Linux/macOS/Windows and exited with `unsupported OS 'Emscripten'`, so the **WASM job was the single red check** on an otherwise fully-published r-universe build (all native platforms + deploy are green; the package installs fine everywhere).

## Fix
The r-universe wasm build image ships **cargo** (`/usr/local/cargo/bin`) and **emscripten** (`EMSDK` on PATH). So instead of needing a prebuilt wasm lib (which would risk an emscripten ABI/version mismatch), `configure` now detects the Emscripten host and **builds the C ABI staticlib from source** for `wasm32-unknown-emscripten` in-place — compiled with the image's own emscripten, so the ABI always matches. The static `libwickra.a` is linked into the package object (no shared lib, no rpath).

`wickra-core`'s rayon batch needs threads (absent on wasm), so the wasm build drops it via `--no-default-features`. `wickra-c` now takes `wickra-core` as a direct path dep with `default-features = false` plus a default `parallel` feature that re-enables it for native builds (a member-level `default-features = false` is ignored when inheriting a workspace dep — that was the trap). 

## Validated locally
- `cargo build -p wickra-c` (default) → rayon present, builds.
- `cargo build -p wickra-c --no-default-features` (the wasm feature path) → rayon **gone**, builds.
- `cargo build --workspace`, clippy, fmt all clean; `configure` passes `sh -n`.

## Cannot be validated locally
No Rust/emscripten wasm toolchain here. The wasm build only runs in r-universe, and `configure` downloads the matching `v${version}` source tag — so this takes effect from the **first release that includes it** (the tagged source must contain the feature toggle). Open risks: whether the image has the `wasm32-unknown-emscripten` Rust target pre-installed (configure runs `rustup target add` best-effort) and the wasm build time. Worth one r-universe rebuild to confirm.

Not merging — review first.
2026-06-10 01:44:48 +02:00
kingchenc 447bbc8220 bindings/csharp: sync the static csproj version to 0.8.1 (#243)
The static `<Version>` in `bindings/csharp/Wickra/Wickra.csproj` had drifted to 0.7.9 — it was missed in the 0.8.0/0.8.1 bumps (wrongly assumed to be purely tag-injected). The published NuGet package was correct (the release packs with `-p:Version=`), but the static field lagged. Resync to 0.8.1; bump_version.py now covers it.
2026-06-10 01:08:37 +02:00
kingchenc c9ef2fc037 bindings/r: bump DESCRIPTION version to 0.8.1 (#242)
The R package DESCRIPTION was missed in the 0.8.0/0.8.1 version bumps (it stayed at 0.7.9), so r-universe published wickra 0.7.9 while everything else is 0.8.1. Resync it; r-universe rebuilds from the main HEAD on its next ~hourly sync.
2026-06-10 00:47:13 +02:00
kingchenc 49c2872ad4 sync-about: sync the version for every registry row (#241)
The published-versions table sync only matched crates.io/PyPI/npm rows, so the NuGet, Maven Central, Go and r-universe rows added to the docs would stay stale on each release. Extend the regex to all seven registries and match the registry name whether plain or wrapped in a markdown link.
2026-06-10 00:26:49 +02:00
kingchenc 0c4bbaf314 release: bump 0.8.0 -> 0.8.1 (#240)
Patch release shipping the wickra-go mirror license fix (#239). The release-time Go mirror now includes the dual MIT OR Apache-2.0 license files, so the next published Go module version resolves with a redistributable license on pkg.go.dev. No code changes; all other packages are republished unchanged at 0.8.1.
2026-06-10 00:22:29 +02:00
kingchenc 99ad2ab107 release.yml: ship the dual license in the wickra-go mirror (#239)
The go-mirror job copied the source, header, README and libraries into wickra-go but not the license, so pkg.go.dev reports `License: None detected` (not redistributable). Copy the root `LICENSE-MIT` and `LICENSE-APACHE` into the assembled module. Takes effect on the next release; the already-published `v0.8.0` tag is immutable on the Go proxy and keeps its current state.
2026-06-10 00:20:34 +02:00
kingchenc 8e225a5872 docs: add Maven Central, Go and r-universe badges to the README (#238)
Adds the three remaining package badges to the README badge row — Maven Central (`org.wickra:wickra`), the Go module (`wickra-go`) and the r-universe R package — mirroring the org profile badge row. They are served from the `.github` badge snapshots like the existing badges.
2026-06-10 00:12:52 +02:00
kingchenc 87bb008aa1 release: bump 0.7.9 -> 0.8.0 + Go module mirror (#237)
Bumps the workspace from 0.7.9 to 0.8.0 and adds the release-time mirror of the Go module to a standalone `wickra-go` repository.

## release.yml: `go-mirror` job
Adds a `go-mirror` job (independent of `github-release`, `needs: c-abi-build`) that on every `v*` tag:
- assembles the standalone Go module from `bindings/go` (single-package source + the vendored `include/wickra.h`),
- stages the six prebuilt C ABI libraries from the `c-abi-build` artifacts under `lib/<goos>_<goarch>/` (committed in the mirror, unlike the in-repo `lib/.gitignore`),
- rewrites `go.mod` to `module github.com/wickra-lib/wickra-go`,
- commits and tags the result in `wickra-lib/wickra-go` using the `WICKRA_GO_MIRROR_TOKEN` fine-grained PAT.

This makes `go get github.com/wickra-lib/wickra-go` build with no extra steps, closing the gap where the in-repo `bindings/go` module only built inside a full repository checkout. The mirror is a derived artifact, so its bot commit is intentionally unsigned.

## Version bump 0.7.9 -> 0.8.0
Patch never reaches double digits (`0.7.9` -> `0.8.0`). Touchpoints: `Cargo.toml` (+ `Cargo.lock` via build), `bindings/python/pyproject.toml`, `bindings/node/package.json` + 6 platform `npm/*/package.json` + both `package-lock.json` files, `bindings/java/pom.xml` + `examples/java/pom.xml` + Maven/Gradle snippets in `bindings/java/README.md`, and `CHANGELOG.md`. The C# `Wickra.csproj` (version set per tag) and `bindings/c` (inherits the workspace version) are intentionally untouched.

Tagging `v0.8.0` (which triggers the publish + the new mirror) stays gated on explicit confirmation.
2026-06-09 23:50:48 +02:00
kingchenc b06ce2678a Restructure the Go binding for self-contained distribution (#236)
## Problem
Plain `go get` + `go build` of the Go binding never worked **as a dependency**:
- cgo `CFLAGS` pointed at `${SRCDIR}/../c/include` — the parent dir is **outside** the Go module, so a proxy-fetched module has no header.
- the library under `./lib` was git-ignored and never shipped; the README told users to `cargo build` it from the workspace, which only works inside a clone, not from the read-only module cache.

cgo has no build hook and Go has no registry, so the only way a consumer's `go get`+build can find the lib is for it to be committed **inside the module the consumer pulls**. Per the design decision, that module is a separate **`wickra-go`** repo (keeps this repo free of committed binaries), populated by the release pipeline — this PR is the in-repo restructure that makes that possible.

## Changes
- **Vendor the header** at `bindings/go/include/wickra.h` (committed copy of `bindings/c/include/wickra.h`); `CFLAGS` → `-I${SRCDIR}/include`. A **CI drift check** fails if the copy goes stale.
- **Per-platform libraries**: cgo `LDFLAGS` become per `GOOS`/`GOARCH`, linking `${SRCDIR}/lib/<goos>_<goarch>/`.
- **CI** stages the host library into `lib/<goos>_<goarch>/` (`RUNNER_OS`/`RUNNER_ARCH` — note `macos-latest` is arm64) and exports `WICKRA_GO_LIBDIR` for the Windows PATH; libraries stay git-ignored here.
- **README**: install via the `wickra-go` module + a contributor build section.

## Validation
- Local **windows/amd64**: `gofmt` clean, `go vet`, `go build`, `go test` all green against the staged library + vendored header.
- Linux/macOS arches → the 3-OS `Go on …` CI job.

Follow-up (Stage 2, separate): create `wickra-lib/wickra-go` + a `release.yml` mirror job (source + 6 platform libs → `lib/<goos>_<goarch>/`, commit + tag), and point the docs at the new import path. Part of the self-contained gap (`todo-11`).
2026-06-09 23:06:16 +02:00
kingchenc 5b7523265c Make the R binding self-contained (fetch the C ABI at install time) (#235)
## Problem
The R package built **only** inside the dev/CI workspace. `src/Makevars` and `configure.win` hard-required `WICKRA_INCLUDE_DIR` / `WICKRA_LIB_DIR` pointing at a pre-built `libwickra` (`configure.win` literally `: "${WICKRA_LIB_DIR:?...}"`), and there was no Unix `configure`. So **r-universe** and any plain `install.packages` / `install_github` failed — R had no working end-user install path.

## Fix
Fetch the prebuilt `wickra-c-<triple>.tar.gz` release asset matching the package version at install time and bundle the library into the package (same outcome as the C# / Java bindings, which bundle per-platform native libs):

- **New POSIX `configure`** (the Unix hook R lacked): detect OS/arch → triple, download + `untar` via **base R** (no curl/wget system dep), stage `wickra.h` + `libwickra.{so,dylib}` into `src/`, generate `src/Makevars` from `Makevars.in` with an rpath (`$ORIGIN` Linux, `@loader_path` + `install_name_tool` macOS) so the bundled lib resolves post-install.
- **`configure.win`**: drop the hard `WICKRA_LIB_DIR` requirement; download the Windows triple when unset, then keep the existing `wickra_abi.dll` rename + `objdump`/`dlltool` import-lib dance (sourcing the dll/header from the asset).
- **`install.libs.R`** also bundles `libwickra.dylib` (macOS); the `*.so`/`*.dll` globs already covered Linux/Windows.
- `WICKRA_INCLUDE_DIR`/`WICKRA_LIB_DIR` stay as an optional **dev override**.
- **CI (3 OS)** keeps building against the locally built C ABI (version-independent — avoids the chicken-egg of downloading the in-flight version) but **no longer exports `LD_LIBRARY_PATH`/`DYLD_LIBRARY_PATH`**, so it now verifies the bundled rpath — the real self-contained path users and r-universe get.

`Makevars` is now generated from `Makevars.in`; `SystemRequirements` + ignore/attributes files updated.

## Validation
- The Windows `objdump`/`dlltool` import-lib dance was smoke-tested locally against the real v0.7.9 asset (2412 `wickra_` exports → import lib built).
- Linux/macOS rpath bundling can't be tested on this Windows host → **the 3-OS `r` CI job is the gate** (now without the loader-path mask).
- Follow-up (separate, after release): set up `wickra-lib/wickra-lib.r-universe.dev` (`packages.json` → `bindings/r`).

Closes the R half of the self-contained-distribution gap (`todo-10`).
2026-06-09 22:11:50 +02:00
kingchenc d188959aab release: bump 0.7.8 -> 0.7.9 (#234)
Version bump 0.7.8 -> 0.7.9 for the Java binding release.
2026-06-09 20:33:17 +02:00
kingchenc 167f7b3ffe Add the Java binding over the C ABI hub (Panama/FFM) (#233)
Adds a Java binding (`bindings/java`) over the C ABI hub — the fourth language stecker after C#, Go and R, reaching the hub through the Java Foreign Function & Memory API (Panama, `java.lang.foreign`, final in Java 22) rather than JNI or jextract.

## What's here
- **`bindings/java`** — a Maven module (`org.wickra:wickra`) exposing all 514 indicators as idiomatic `AutoCloseable` classes. The downcall handles (`internal/NativeMethods.java`), the per-indicator wrappers and the output records are generated from `bindings/c/include/wickra.h` (same eight-archetype taxonomy as the C#/Go/R generators: scalar/batch, multi-output, bars, profile, values-profile, array-input). The opaque handle is a `MemorySegment` freed by a registered `java.lang.ref.Cleaner` action; multi-output returns a `record` (`null` at warmup), bars a `record[]`, profiles a record with a trailing `double[]`. The hand-written `WickraNative` resolves the native library (a bundled per-platform copy, or a `target/release` fallback for local development) and validates it against a sentinel symbol. repr(C) struct offsets are computed in the generator so the FFM reads land on the exact bytes.
- **`examples/java`** — the full example suite mirroring C/C#/Go/R: streaming, backtest, multi_timeframe, parallel_assets (parallel streams), three strategies, and `FetchBtcusdt`/`LiveBinance`.
- **CI** — a `java` job builds the C ABI library, sets up JDK 22 (Temurin, with a CDN-flake retry), runs the archetype test suite and the seven offline examples on Linux, macOS and Windows.
- **Release** — a gated `java-publish` job (skipped until the `JAVA_PUBLISH_ENABLED` repository variable is set) stages the native libraries from the `wickra-c-<triple>.tar.gz` assets into the binding's resources and deploys to Maven Central with GPG signing. Independent of the GitHub-release job, like the NuGet job.
- **Docs** — Java added to the README languages table, project layout, building/testing and comparison table, CONTRIBUTING, ARCHITECTURE, the examples index, the issue/PR templates, the About-description template, and the other binding READMEs.

## Requirements
Java 22+ (the FFM API is final since Java 22). The binding requires `--enable-native-access=ALL-UNNAMED` at runtime; the test and example runners pass it automatically.

No Rust crate or `Cargo.toml` change — the Java binding is standalone and additive. The generated `*.java` are committed (like the node `index.js`/`index.d.ts`); the generator stays private.
2026-06-09 20:30:29 +02:00
kingchenc 8659b42bef release: bump 0.7.7 -> 0.7.8 (#232)
Version bump 0.7.7 -> 0.7.8 for the R binding release.
2026-06-09 19:22:11 +02:00
kingchenc b7ef63400d Add the R binding over the C ABI hub (#230)
Adds an R binding (`bindings/r`) over the C ABI hub — the third language stecker after C# and Go, reaching the hub through R's native `.Call` interface (not extendr).

## What's here
- **`bindings/r`** — an R package exposing all 514 indicators as constructors that return a `wickra_indicator` object with generic `update`/`batch`/`reset` methods. The C glue (`src/wickra.c`) and R wrappers (`R/indicators.R`) are generated from `bindings/c/include/wickra.h` (same archetype taxonomy as the C#/Go generators: scalar/batch, multi-output, bars, profile, profile-values, array-input). The opaque handle is an R external pointer freed by a registered finalizer; multi-output returns a named vector (`NA` at warmup), bars a matrix, profiles a list.
- **`examples/r`** — the full example suite mirroring C/C#/Go: streaming, backtest, multi_timeframe, parallel_assets (`mclapply`), three strategies, and `fetch_btcusdt`/`live_binance`.
- **CI** — an `r` job builds the C ABI library, installs the package, runs the `testthat` suite and the offline examples on Linux, macOS and Windows (`R CMD check` is clean: 0 warnings, 0 notes).
- **Docs** — R added to the README languages table, project layout, building/testing, CONTRIBUTING binding table + regenerate note, ARCHITECTURE, examples index, issue/PR templates, the About-description template, and the other binding READMEs.

## Linking / distribution
The package compiles a thin `.Call` glue layer against the prebuilt C ABI library (header via `WICKRA_INCLUDE_DIR`, library via `WICKRA_LIB_DIR`). On Windows the package's own `wickra.dll` would collide with the C ABI's `wickra.dll`, so `configure.win` stages a renamed copy (`wickra_abi.dll`) and builds an import library referencing it; `install.libs.R` bundles the DLL and `.onLoad` puts it on the load path. On Linux/macOS the rpath locates the shared library. No `release.yml` change — R is distributed via r-universe / source install (gated).

No Rust crate or `Cargo.toml` change — the R package is standalone and additive.
2026-06-09 19:18:40 +02:00
kingchenc 8225e1ab91 Check out the PR head by SHA in the count-sync workflow (#231)
## Problem
The **Sync indicator count** check has failed on every release-bump PR since 0.7.5 (`release/0.7.6`, `release/0.7.7`, …). It is a race, not a counter mismatch.

The checkout step used the PR head **branch name**:
\`\`\`yaml
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }}
\`\`\`
Release PRs are merged with \`gh pr merge --squash --delete-branch\`, which deletes the head branch the instant the PR merges — usually before this queued read-only check reaches its checkout. Fetching the now-gone \`refs/heads/release/X.Y.Z\` then fails with exit 1 (3 retries, then error). Push-to-main, tag and slower feature PRs stayed green because their head branch still existed when the check ran.

## Fix
Check out \`github.event.pull_request.head.sha\` instead. The head SHA stays reachable via \`refs/pull/N/head\` after the branch is deleted, so an instant merge no longer red-Xes the run. It is still the author's head commit (not the merge ref), so the counter validates exactly what will land — the existing design intent is preserved.

Because \`pull_request\` runs the workflow definition from the merge commit, the fix already applies to this PR itself.
2026-06-09 18:59:09 +02:00
kingchenc d0061c73b8 release: bump 0.7.6 -> 0.7.7 (#229)
Bumps the workspace to 0.7.7 to ship the Go binding.
2026-06-09 17:35:25 +02:00
kingchenc 23d636fd97 Add the Go binding over the C ABI hub (#228)
Adds a Go binding (`bindings/go`) over the C ABI hub — the second language stecker after C#.

## What's here
- **`bindings/go`** — a cgo binding exposing all 514 indicators as idiomatic Go types with `New<Indicator>` constructors and `Update`/`Batch`/`Reset`/`Close` methods. The wrappers in `indicators_gen.go` are generated from `bindings/c/include/wickra.h` (same archetype taxonomy as the C# generator: scalar/batch, multi-output, bars, profile, profile-values, array-input). Opaque handles are freed by `Close()` with a `runtime.SetFinalizer` backstop; pointer arguments are caller-owned, panics never cross the boundary.
- **`examples/go`** — the full example suite mirroring C/C#: streaming, backtest, multi_timeframe, parallel_assets (goroutine fan-out), three strategies, and `fetch_btcusdt`/`live_binance`.
- **CI** — a `go` job builds the C ABI library, stages it, and runs `gofmt`/`go vet`/`go test` plus the offline examples on Linux, macOS and Windows.
- **Docs** — Go added to the README languages table, project layout, building/testing, CONTRIBUTING binding table + regenerate note, ARCHITECTURE, examples index, issue/PR templates, the About-description template, and the other binding READMEs.

## Linking / distribution
The binding links the prebuilt C ABI library via cgo (`libwickra.so`/`.dylib`/`wickra.dll` staged under `bindings/go/lib`, gitignored). The native libraries are already shipped per target triple by the existing `c-abi-build` release job; distribution is via the subdirectory module tag `bindings/go/vX.Y.Z` (gated), so `release.yml` needs no new publish job.

No Rust crate or `Cargo.toml` change — the Go module is standalone and additive.

Not for merge yet (gated, per request).
2026-06-09 17:33:37 +02:00
kingchenc fce26cf881 release: bump 0.7.5 -> 0.7.6 (#227)
Bumps the workspace to 0.7.6 to ship the C# (.NET) binding to NuGet.
2026-06-09 14:34:18 +02:00
kingchenc 91f6f67257 Add C# (.NET) binding over the C ABI hub (#226)
The first language stecker on the C ABI hub: a .NET binding exposing all 514
indicators as idiomatic `IDisposable` classes, generated from `wickra.h`.

## What's here

- **`bindings/csharp/`** — the `Wickra` .NET 8 package. `[LibraryImport]`
  source-generated P/Invoke (`NativeMethods.g.cs`) plus idiomatic wrappers
  (`Indicators.g.cs`), both generated from the committed `bindings/c/include/wickra.h`.
  The binding owns no indicator maths — it only marshals types across the C ABI.
- **Marshalling, verified end-to-end against the native library.** Opaque handles
  cross as `nint` kept alive per call via a `SafeHandle`; `bool` as
  `[MarshalAs(U1)]` (Rust `bool` is one byte); a self-correcting
  `DllImportResolver` validates the loaded library actually exports the Wickra
  ABI. Tests cover one representative per FFI archetype (scalar, candle, pairwise,
  multi-output, bars, profile, values-profile, order-book / array-input) plus
  exact Sma reference values.
- **NuGet packaging** — `dotnet pack` produces `Wickra.<version>.nupkg`; the
  release pipeline stages prebuilt native libraries under `runtimes/<rid>/native/`
  for six target triples (win/linux/osx × x64/arm64).
- **`examples/csharp/`** — nine examples mirroring `examples/c/`: streaming,
  backtest, multi_timeframe, parallel_assets, three strategies, and
  fetch_btcusdt + live_binance.
- **CI** — a `csharp` job on the three OSes builds the C ABI, tests the binding,
  and runs the offline examples. **Release** — a gated `csharp-publish` job packs
  and pushes to NuGet (gated on `NUGET_API_KEY`, independent of the GitHub-release
  job so a C# hiccup never blocks the C/C++ asset release).
- **Docs consistency wave** — README, CONTRIBUTING, CHANGELOG, examples/README,
  the issue / PR templates, `sync-about.yml`, and `.gitattributes`.

The native Python / Node / WASM bindings and the C ABI are untouched; this is
additive. Publishing to NuGet stays gated behind the release tag and the secret.
2026-06-09 14:32:05 +02:00
kingchenc 4caaa1db97 release: bump 0.7.4 -> 0.7.5 (#225)
Version bump for the C ABI hub release (0.7.4 -> 0.7.5). See #222 + #224.
2026-06-09 02:26:27 +02:00
kingchenc 12681e4b1b C ABI: full example suite + docs & About coverage (#224)
Stacked on #222 (base `feat/c-abi-hub`), so the diff is just the additions on top of the hub foundation — no merge of #222 required.

## What this adds

**Examples — full parity with rust/python/node (`examples/c/`)**
- `streaming.c` upgraded to the multi-indicator (SMA/EMA/RSI/MACD + signals) demo
- `backtest.c`, `multi_timeframe.c` (manual time-bucket resampling), `parallel_assets.c` (serial vs OpenMP fan-out, one handle per asset)
- three educational strategies: `strategy_rsi_mean_reversion.c`, `strategy_macd_adx.c`, `strategy_bollinger_squeeze.c`
- two network examples shelling out to `curl`: `fetch_btcusdt.c`, `live_binance.c` (REST poll)
- two header-only helpers (`wickra_csv.h`, `wickra_strategy.h`) since the C ABI ships no IO layer
- CMake builds all 11; the 9 offline ones run under `ctest` on 3 OS; the network two are built-only

**Docs & metadata — surface the C ABI everywhere it was missing**
- ARCHITECTURE diagram + crate table, SECURITY + THREAT_MODEL (the C ABI as the sole `unsafe` FFI surface), the three binding package READMEs, issue/PR templates, CHANGELOG, and the GitHub About template (live About + org description updated too)

**Cleanup**
- removed all references to the private generator tooling from public files (`bindings/c/src/lib.rs` header, `CONTRIBUTING.md`, `sync-about.yml`)

Verified locally: `cargo build -p wickra-c --release`, `cmake + ctest` (9/9 pass), and `-Wall -Wextra -Wpedantic` clean on gcc 13.
2026-06-09 02:14:28 +02:00
kingchenc 91e05e3c26 C ABI hub crate (bindings/c) foundation (#222)
## What

Introduces `wickra-c` — a `cdylib` + `staticlib` that exposes the Rust core over a **C ABI**. This is the hub every C-capable language (C, C++, Go, C#, Java, R) links against, instead of re-wiring each indicator natively. The native Python/Node/WASM bindings are untouched; this is purely additive, for ecosystems without first-class Rust tooling.

## Scope (foundation slice)

This PR deliberately validates the **whole pipeline end to end with one indicator (SMA)** before scaling to all 514, so the CI / cross-OS / header-drift mechanics are proven green first.

- Opaque `*mut T` handles; `wickra_<ind>_{new,update,batch,reset,free}`.
- NaN sentinel for warmup / NULL handles; caller-owned batch buffers; every function NULL-safe.
- cbindgen generates and commits `bindings/c/include/wickra.h` with opaque handle typedefs.
- A C smoke example (`examples/c/`) links the header + compiled library and runs (CMake + ctest).
- A `c-abi` CI job builds the library and runs the smoke test on **Linux, macOS and Windows**, plus a header drift check on Linux.

## Notes

- The per-indicator FFI blocks are plain `#[no_mangle]` functions, **not** a macro: cbindgen cannot see macro-generated functions on stable Rust (macro expansion needs nightly), so the blocks are written literally and will be generated mechanically by the ScriptHelpers `capi` wrapper in a follow-up (same model as the committed-but-generated Node `index.js`).
- `bindings/c` cannot inherit the workspace `forbid(unsafe_code)` lint (the C boundary needs raw pointers), so it mirrors every workspace lint and only relaxes `unsafe_code`. The Rust core stays `unsafe`-forbidden.

## Follow-ups (separate PRs)

- ScriptHelpers `capi` generator + wire the scalar family (~235).
- Hand-written blocks for multi-output / custom-input / bars (~279).
- Docs consistency wave (README / docs / webpage: Python·Node·WASM·Rust → +C).
- Release wiring (native-lib matrix + header/lib GH-release assets) — gated.
2026-06-09 02:07:03 +02:00
kingchenc 9d0983b666 ci(sync-about): sync indicator count into the webpage About page (#223)
The `wickra.org/about` page carries the indicator count in the bot-syncable `<N> indicators` token, but `sync-about.yml` only rewrote `index.md` + `.vitepress/config.ts` on the webpage. Add `about.md` to the webpage count step's `sed` file list and its `git add`, so the About page's count self-heals on every push-to-main / tag like the rest of the marketing site.

No-op for the Rust build — workflow file only.
2026-06-08 21:38:42 +02:00
kingchenc 13c8250488 release: bump 0.7.3 -> 0.7.4 (#221)
Version bump **0.7.3 → 0.7.4** for the B19 Alt-Chart Bars batch (7 new bar builders, 507 → 514).

Bumps `Cargo.toml` (+ `wickra-core` dep), `Cargo.lock`, `pyproject.toml`, the Node `package.json` and its six platform packages, both `package-lock.json` files, and the CHANGELOG (`[Unreleased]` → `[0.7.4]` with compare URLs).
2026-06-08 14:33:43 +02:00
kingchenc e5305ffa94 feat: add 7 alt-chart bar builders (B19) (#220)
Adds seven information-driven bar builders to the **Alt-Chart Bars** family, the final batch of the family-deepening run. Indicator count **507 → 514**.

## Builders
All implement the `BarBuilder` trait (`update(Candle) -> Vec<Bar>`), emitting a data-dependent number of completed bars per candle.

| Builder | Driver | Bar fields |
|---------|--------|-----------|
| `RangeBars` | close | open, close, direction |
| `TickBars` | OHLCV | open, high, low, close, volume |
| `VolumeBars` | OHLCV | open, high, low, close, volume |
| `DollarBars` (Lopez de Prado) | OHLCV | + dollar |
| `ImbalanceBars` | OHLC | + imbalance, direction |
| `RunBars` | OHLC | + length, direction |
| `ThreeLineBreakBars` | close | open, close, direction |

## Touchpoints
Seven core modules (each with full unit tests), `mod.rs`/`lib.rs` (builders counted, bar element types on their own re-export lines), README family rows, Python/Node/WASM hand-written bindings for the variable-length output (Python tuples + `(k, N)` ndarray; Node `Vec<object>`; WASM array of objects), the `bar_builder_update_candle` fuzz target, dedicated Python + Node tests, the `BAR_BUILDERS` completeness exclusion, and CHANGELOG.

## Verification
- `cargo test -p wickra-core --lib` — 4207 passed
- `cargo test -p wickra-core --doc` — 464 passed
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` — clean
- `npm test` (node) — 584 passed
- `pytest` (python) — 957 passed
2026-06-08 14:32:40 +02:00
kingchenc 46be7a54ea release: bump 0.7.2 -> 0.7.3 (#219)
Version bump **0.7.2 → 0.7.3** for the B18 Risk / Performance batch (9 new indicators, 498 → 507).

Bumps `Cargo.toml` (+ `wickra-core` dep), `Cargo.lock`, `pyproject.toml`, the Node `package.json` and its six platform packages, both `package-lock.json` files, and the CHANGELOG (`[Unreleased]` → `[0.7.3]` with compare URLs).
2026-06-08 13:29:39 +02:00
kingchenc bca61322b5 feat: add 9 Risk / Performance indicators (B18) (#218)
Adds nine risk/performance metrics to the existing **Risk / Performance** family, all consuming a per-period return series (`f64` in, `f64` out). Indicator count **498 → 507**.

## Indicators

Single-param (`new(period)`, macro bindings):
- **SterlingRatio** — mean return over average drawdown of the equity curve.
- **BurkeRatio** — return over root-sum-squared drawdowns.
- **MartinRatio** — Ulcer Performance Index; return over RMS percentage drawdown.
- **TailRatio** — 95th percentile over the absolute 5th percentile return.
- **KRatio** — Kestner; equity-curve OLS slope over the standard error of that slope.
- **CommonSenseRatio** — tail ratio times gain-to-pain.
- **GainToPainRatio** — sum of returns over the sum of absolute losses.

Multi-param (hand-written Python/Node bindings, variadic WASM macro):
- **UpsidePotentialRatio** — `new(period, mar)`; upside mean over downside deviation (Sortino philosophy).
- **M2Measure** — `new(period, risk_free, benchmark_stddev)`; Modigliani M², Sharpe rescaled into benchmark return units.

## Touchpoints
Core modules + unit tests, `mod.rs`/`lib.rs` wiring, Python/Node/WASM bindings (`index.d.ts`/`index.js` regenerated), fuzz drive lines, Python `SCALAR` registry + Node factories, CHANGELOG, and the indicator counters.

## Verification
- `cargo test -p wickra-core --lib` — 4149 passed
- `cargo test -p wickra-core --doc` — 457 passed
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` — clean
- `npm test` (node) — 577 passed
- `pytest` (python) — 947 passed
2026-06-08 13:23:01 +02:00
kingchenc fc6f619550 release: bump 0.7.1 -> 0.7.2 (#217)
Version bump 0.7.1 -> 0.7.2 for the B17 Market Profile batch (498 indicators).
2026-06-08 04:18:34 +02:00
kingchenc 91aa6fffbf feat(market-profile): naked POC, single prints, profile shape, HVN/LVN, composite profile (B17) (#216)
## B17 Market Profile — five new indicators (493 → 498)

| Indicator | Output | Notes |
|-----------|--------|-------|
| `NakedPoc` | `f64` | most recent untouched point-of-control level |
| `SinglePrints` | `f64` | count of single-print price levels |
| `ProfileShape` | `f64` | b/P/D shape classification as a numeric code |
| `HighLowVolumeNodes` | struct `{hvn, lvn}` | highest/lowest volume nodes |
| `CompositeProfile` | struct `{poc, vah, val}` | multi-session composite volume profile |

### Wiring
- Core structs + full unit tests; all join the existing **Market Profile** family.
- Hand-written Python/Node/WASM bindings (f64 via candle helpers; struct via PyArray2 / `#[napi(object)]` / `Object`+`Reflect::set`).
- Fuzz drives in `indicator_update_candle.rs`; CANDLE_SCALAR + MULTI registry tests + reference tests.
- README counter + `docs/README.md` + `FAMILIES` assert bumped to 498.

### Verify (local, all green)
- `cargo test -p wickra-core --lib`: 4066 · `--doc`: 448
- clippy workspace: clean
- node: 568 · pytest: 938
2026-06-08 04:17:06 +02:00
kingchenc 5862401958 release: bump 0.7.0 -> 0.7.1 (#215)
Version bump 0.7.0 -> 0.7.1 for the B16 Derivatives batch (493 indicators).
2026-06-08 03:35:31 +02:00
kingchenc ff5a047078 feat(derivatives): leverage, OI/volume, perpetual premium, funding APR, OI momentum (B16) (#214)
## B16 Derivatives — five new indicators (488 → 493)

All consume a `DerivativesTick` and emit `f64`:

| Indicator | Reads | Formula |
|-----------|-------|---------|
| `EstimatedLeverageRatio` | open_interest, long_size, short_size | `OI / (long + short)` |
| `OiToVolumeRatio` | open_interest, taker_buy_volume, taker_sell_volume | `OI / (buy + sell)` |
| `PerpetualPremiumIndex` | mark_price, index_price | `(mark − index) / index` |
| `FundingImpliedApr` | funding_rate | `rate × intervals_per_year` |
| `OpenInterestMomentum` | open_interest | `100 · (OI_t − OI_{t−period}) / OI_{t−period}` |

### Wiring
- Core structs + full unit tests (incl. zero-denominator branches).
- Hand-written Python/Node/WASM tick bindings; two new tick helpers (`deriv_oi_long_short`, `deriv_oi_taker`).
- Fuzz drives in `indicator_update_derivatives.rs`; dedicated reference + streaming-vs-batch tests (Python + Node).
- README counter + `docs/README.md` + `FAMILIES` assert bumped to 493.

### Verify (local, all green)
- `cargo test -p wickra-core --lib`: 4028 · `--doc`: 443
- clippy workspace: clean
- node: 563 · pytest: 928
2026-06-08 03:33:59 +02:00
kingchenc dc415a77fd release: bump 0.6.9 -> 0.7.0 (#213)
Version bump 0.6.9 -> 0.7.0 for the B15 Microstructure batch (488 indicators).
2026-06-08 03:10:16 +02:00
kingchenc e385734275 feat(microstructure): trade-sign autocorrelation, PIN, Hasbrouck information share (B15) (#212)
## B15 Microstructure — three new indicators (485 → 488)

| Indicator | Input | Output | Notes |
|-----------|-------|--------|-------|
| `TradeSignAutocorrelation` | `Trade` | `f64` ∈ [-1,1] | lag-1 autocorrelation of the signed aggressor (order-flow persistence) |
| `Pin` | `Trade` | `f64` ∈ [0,1] | probability of informed trading from rolling buy/sell imbalance (EKOP single-window estimator); `name()` = `"PIN"` |
| `HasbrouckInformationShare` | `(f64, f64)` | `f64` ∈ [0,1] | variance-ratio proxy for each venue's share of price discovery |

### Wiring
- Core structs + full unit tests (every branch).
- Hand-written Python/Node/WASM bindings for the two `Trade`-input indicators (precedent `TradeImbalance`); `node_pair_indicator!` / `wasm_pair_indicator!` macro bindings + hand Python pyclass for the pairwise Hasbrouck (precedent `RollingCorrelation`).
- Fuzz drives added to `indicator_update_trade.rs` and `indicator_update_pair.rs`.
- Dedicated Python + Node streaming-vs-batch and reference tests; Hasbrouck in the `PAIR` registry.
- README counter (3 spots) + `docs/README.md` + `FAMILIES` assert bumped to 488.

### Verify (all green, local)
- `cargo test -p wickra-core --lib`: 3991 passed
- `cargo test -p wickra-core --doc`: 438 passed
- `cargo clippy --workspace --all-targets --all-features -- -D warnings`: clean
- node: 561 passed · pytest: 926 passed
2026-06-08 03:07:50 +02:00
kingchenc 3a46b210bb release: bump 0.6.8 -> 0.6.9 (#211)
Release 0.6.9 — ships the B14 Candlestick Patterns indicators (485 total). Version-string bump only.
2026-06-08 02:30:46 +02:00
kingchenc 943825d6a0 feat: add Candlestick Patterns deepening (B14, 6 indicators) (#209)
B14 of the family-deepening roadmap — six candlestick patterns (479 -> 485), all in the **Candlestick Patterns** family.

**Fixed-lookback (candle-pattern macro bindings, neutral 0.0 during warmup):**
- **Tristar** — three-doji star reversal.
- **Harami Cross** — Harami whose second candle is a contained doji.
- **Tower Top/Bottom** — tall bar, small pause, tall opposite bar.

**Windowed / parameterized (hand-bound, `candle -> f64`):**
- **Frying Pan Bottom** — rounded U-shaped accumulation base, recovery-confirmed.
- **Dumpling Top** — rounded dome-shaped distribution top, breakdown-confirmed.
- **New Price Lines** — run of N consecutive new closing highs (+1) / lows (-1).

Window/Gap (Rising-Falling) dropped (SKIP — existing gap coverage). Wiring complete across core, Python, Node, WASM, fuzz, tests, README + docs counter (485) and CHANGELOG. Verified: core 3966 + doc 435, clippy clean, node 560, python 922.
2026-06-08 02:29:24 +02:00
kingchenc d16df1e224 ci: warm the cargo registry with backoff so a DNS blip can't fail clippy (#210)
## Problem

A macOS runner on #206 failed the **Rust** job's clippy step with:

```
Updating crates.io index
error: failed to get `rayon` as a dependency ...
  download of config.json failed
  [6] Couldn't resolve host name (Could not resolve host: index.crates.io)
```

A pure transient DNS blip — unrelated to the change (a docs-only PR). `CARGO_NET_RETRY=10` only does fast in-process retries; a longer DNS outage outlasts them, so the very first cargo step's crates.io index fetch fails the whole job.

## Fix

Add a **Warm cargo registry** step right after the cache restore in the `rust`, `clippy-bindings` and `msrv` jobs. It runs `cargo fetch` in a 5-attempt loop with real backoff (20/40/60/80s sleeps) so the dependency graph is pulled once, patiently, riding out a multi-second DNS outage that cargo's rapid retries can't. The later clippy/build/test steps then resolve from the warmed local cache.

Mirrors the existing inline retry pattern already used for setup-node/setup-python CDN flakes. Can be extended to the coverage/python/wasm/node jobs if they ever hit the same blip.
2026-06-08 02:22:26 +02:00
kingchenc 34c097aee2 docs: refresh Python benchmark figures from a fresh measured run (#206)
The published Python benchmark tables (README/BENCHMARKS.md) were a stale, incoherent run. Re-measured locally with the current build (wickra 0.6.5, post batch fast-paths) via `compare_libraries.py` on the same 9950X.

- **Streaming vs talipp:** 11-56x (was 9-58x).
- **Batch:** real per-indicator numbers; MACD and ATR were notably off in the old table.
- **Prose:** Wickra beats TA-Lib on RSI and ATR (no longer MACD, which now trails 130 vs 111 us).

Rust tables unchanged. Numbers are a single coherent run; absolute us still depend on machine state (caveat already in the doc).
2026-06-08 02:13:13 +02:00
kingchenc acd7e8dc52 release: bump 0.6.7 -> 0.6.8 (#208)
Release 0.6.8 — ships the B13 Ichimoku & Charts indicators (479 total). Version-string bump only.
2026-06-08 01:50:32 +02:00
kingchenc ceaeb90a22 feat: add Ichimoku & Charts deepening (B13, 5 indicators) (#207)
B13 of the family-deepening roadmap — five alternative-chart indicators (474 -> 479), all in the **Ichimoku & Charts** family.

- **Smoothed Heikin-Ashi** (`candle -> struct {open, high, low, close}`) — a Heikin-Ashi candle computed from EMA-smoothed OHLC.
- **Heikin-Ashi Oscillator** (`candle -> f64`) — the HA body (`ha_close - ha_open`), optionally EMA-smoothed, as a zero-line oscillator.
- **Three Line Break** (`candle -> f64`) — line-break ("kakushi") chart trend direction; reverses only when the close breaks the extreme of the last N lines. Distinct from the candlestick `ThreeLineStrike`.
- **Equivolume** (`candle -> struct {height, width}`) — a box whose height is the bar range and width is volume-relative.
- **CandleVolume** (`candle -> struct {body, width}`) — a candle whose body is close-minus-open and width is volume-relative.

All bindings hand-written (3 struct-output + 2 candle-input-with-open / non-period-ctor). Wiring complete across core, Python, Node, WASM, fuzz, tests, README + docs counter (479) and CHANGELOG. Verified: core 3915 + doc 432, clippy clean, node 554, python 913.
2026-06-08 01:49:03 +02:00
1435 changed files with 235426 additions and 264 deletions
+31
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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`
+1 -1
View File
@@ -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
View File
@@ -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
+302 -2
View File
@@ -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
+40 -17
View File
@@ -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)."
+1
View File
@@ -55,6 +55,7 @@ bindings/node/npm-debug.log*
# WASM build output
bindings/wasm/pkg/
bindings/wasm/pkg-node/
# Python venv
.venv/
+38 -21
View File
@@ -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).
+90 -18
View File
@@ -26,15 +26,15 @@ was built to expose.
| Indicator | **★&nbsp;Wickra** | talipp | TA-Lib (recompute) |
|------------------|------------------:|------------------|-----------------------|
| SMA(20) | **0.063 µs ★** | 0.59 µs (9×) | 204 µs (3 300×) |
| EMA(20) | **0.060 µs ★** | 0.72 µs (12×) | 212 µs (3 500×) |
| RSI(14) | **0.065 µs ★** | 1.06 µs (16×) | 230 µs (3 600×) |
| MACD(12, 26, 9) | **0.078 µs ★** | 4.22 µs (54×) | 245 µs (3 100×) |
| Bollinger(20, 2) | **0.088 µs ★** | 5.15 µs (58×) | 229 µs (2 600×) |
| 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 **958× faster**;
against the recompute-on-every-tick libraries it is **2 60014 000× faster**
(`finta` RSI hits 14 000×). tulipy / pandas-ta land in the same recompute band
Against the only other incremental Python peer Wickra is **1156× faster**;
against the recompute-on-every-tick libraries it is **2 80019 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):
@@ -63,17 +63,17 @@ field everywhere.
**Python** (20 000-bar pass, µs/op, lower = faster):
| Indicator | Wickra | TA-Lib | tulipy | pandas-ta |
|------------------|---------:|-------:|-------:|----------:|
| SMA(20) | 22.7 | **15.4** | 15.9 | 33.7 |
| EMA(20) | 30.8 | **30.3** | 31.1 | 48.8 |
| RSI(14) | 58.9 | 72.5 | **38.5** | 94.8 |
| MACD(12, 26, 9) | 71.7 | 99.1 | **33.5** | 207.6 |
| Bollinger(20, 2) | 84.9 | 65.7 | **32.3** | 336.4 |
| ATR(14) | 52.0 | 79.4 | **31.9** | — |
| 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 TA-Lib on RSI, MACD and ATR and the whole Python field on every
row; tulipy's SIMD C stays ahead on the heavier indicators.
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:
@@ -94,3 +94,75 @@ 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)
```
+207 -1
View File
@@ -7,6 +7,194 @@ 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`).
@@ -1362,7 +1550,25 @@ 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.7...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
+15 -1
View File
@@ -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
View File
@@ -1944,7 +1944,7 @@ dependencies = [
[[package]]
name = "wickra"
version = "0.6.7"
version = "0.8.5"
dependencies = [
"approx",
"criterion",
@@ -1955,7 +1955,7 @@ dependencies = [
[[package]]
name = "wickra-bench"
version = "0.6.7"
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.7"
version = "0.8.5"
dependencies = [
"approx",
"proptest",
@@ -1977,7 +1984,7 @@ dependencies = [
[[package]]
name = "wickra-data"
version = "0.6.7"
version = "0.8.5"
dependencies = [
"approx",
"csv",
@@ -1994,7 +2001,7 @@ dependencies = [
[[package]]
name = "wickra-examples"
version = "0.6.7"
version = "0.8.5"
dependencies = [
"serde_json",
"tokio",
@@ -2004,7 +2011,7 @@ dependencies = [
[[package]]
name = "wickra-node"
version = "0.6.7"
version = "0.8.5"
dependencies = [
"napi",
"napi-build",
@@ -2014,7 +2021,7 @@ dependencies = [
[[package]]
name = "wickra-python"
version = "0.6.7"
version = "0.8.5"
dependencies = [
"numpy",
"pyo3",
@@ -2023,7 +2030,7 @@ dependencies = [
[[package]]
name = "wickra-wasm"
version = "0.6.7"
version = "0.8.5"
dependencies = [
"console_error_panic_hook",
"js-sys",
+3 -2
View File
@@ -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.7"
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.7" }
wickra-core = { path = "crates/wickra-core", version = "0.8.5" }
thiserror = "2"
rayon = "1.10"
+106 -40
View File
@@ -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=474" 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>
[![CI](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml/badge.svg)](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml)
[![CodeQL](https://github.com/wickra-lib/wickra/actions/workflows/codeql.yml/badge.svg)](https://github.com/wickra-lib/wickra/actions/workflows/codeql.yml)
[![codecov](https://codecov.io/gh/wickra-lib/wickra/branch/main/graph/badge.svg)](https://codecov.io/gh/wickra-lib/wickra)
[![GitHub release](https://img.shields.io/github/v/release/wickra-lib/wickra?logo=github&color=green)](https://github.com/wickra-lib/wickra/releases/latest)
[![crates.io](https://img.shields.io/crates/v/wickra.svg?logo=rust&color=orange)](https://crates.io/crates/wickra)
[![PyPI](https://img.shields.io/pypi/v/wickra.svg?logo=pypi&color=blue)](https://pypi.org/project/wickra/)
[![npm](https://img.shields.io/npm/v/wickra.svg?logo=npm&color=red)](https://www.npmjs.com/package/wickra)
[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT_OR_Apache--2.0-blue)](#license)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/wickra-lib/wickra/badge)](https://scorecard.dev/viewer/?uri=github.com/wickra-lib/wickra)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/13094/badge)](https://www.bestpractices.dev/projects/13094)
[![Build provenance](https://img.shields.io/badge/provenance-attested-brightgreen?logo=github)](https://github.com/wickra-lib/wickra/attestations)
[![Docs](https://img.shields.io/badge/docs-docs.wickra.org-0ea5e9?logo=readthedocs&logoColor=white)](https://docs.wickra.org)
[![CI](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/ci.svg)](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml)
[![CodeQL](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/codeql.svg)](https://github.com/wickra-lib/wickra/actions/workflows/codeql.yml)
[![codecov](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/codecov.svg)](https://codecov.io/gh/wickra-lib/wickra)
[![GitHub release](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/release.svg)](https://github.com/wickra-lib/wickra/releases/latest)
[![crates.io](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/crates.svg)](https://crates.io/crates/wickra)
[![PyPI](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/pypi.svg)](https://pypi.org/project/wickra/)
[![npm](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/npm.svg)](https://www.npmjs.com/package/wickra)
[![NuGet](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/nuget.svg)](https://www.nuget.org/packages/Wickra)
[![Maven Central](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/maven.svg)](https://central.sonatype.com/artifact/org.wickra/wickra)
[![Go module](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/go.svg)](https://pkg.go.dev/github.com/wickra-lib/wickra-go)
[![R-universe](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/r-universe.svg)](https://wickra-lib.r-universe.dev)
[![License: MIT OR Apache-2.0](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/license.svg)](#license)
[![OpenSSF Scorecard](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/scorecard.svg)](https://scorecard.dev/viewer/?uri=github.com/wickra-lib/wickra)
[![OpenSSF Best Practices](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/best-practices.svg)](https://www.bestpractices.dev/projects/13094)
[![Build provenance](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/provenance.svg)](https://github.com/wickra-lib/wickra/attestations)
[![Docs](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/docs.svg)](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 474 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),
@@ -66,19 +76,20 @@ an afterthought — **live, tick-by-tick data** — without giving up the breadt
a full batch library, and without making you reimplement your indicators four
times to get there.
- **The biggest streaming-native catalogue, period.** 474 indicators across 24
- **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, four first-class targets.** Native **Python · Node.js ·
WebAssembly · Rust** — identical math, identical results, zero per-language
reimplementation and zero GIL bottleneck.
- **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 474 indicators**.
- **Orders of magnitude faster where it counts.** In streaming Wickra is **958×**
for all 514 indicators**.
- **Orders of magnitude faster where it counts.** In streaming Wickra is **1156×**
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
@@ -95,7 +106,7 @@ Every other library forces one of those compromises. Wickra doesn't:
| Library | Install | Streaming | Languages | Indicators | Active |
|------------------|-------------|-------------|-----------------------------|-----------:|--------|
| **★&nbsp;Wickra**| **clean** | **yes, O(1)** | **Python · Node · WASM · Rust** | **474** | **yes** |
| **★&nbsp;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 |
@@ -118,7 +129,7 @@ useful version of that itch is the one other people can build on too.
## Benchmarks
Wickra updates every indicator in **O(1)** per tick. In **streaming** — the
workload it is built for — it is **958× faster** than the only other incremental
workload it is built for — it is **1156× 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`.
@@ -128,7 +139,7 @@ Full tables (Rust + Python, streaming + batch) and how to reproduce them live in
## Indicators
474 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).
@@ -147,15 +158,15 @@ warmup) at [docs.wickra.org](https://docs.wickra.org/Indicators-Overview).
| 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 |
| 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 |
| 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 |
@@ -166,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
@@ -177,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
@@ -237,20 +255,30 @@ A Python live-trading example using the public `websockets` package lives at
```
wickra/
├── crates/
│ ├── wickra-core/ core engine + all 474 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
```
@@ -278,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
@@ -287,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
@@ -297,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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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. |
+59
View File
@@ -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 }
+94
View File
@@ -0,0 +1,94 @@
# Wickra — C / C++
[![CI](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml/badge.svg)](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/wickra-lib/wickra/branch/main/graph/badge.svg)](https://codecov.io/gh/wickra-lib/wickra)
[![GitHub release](https://img.shields.io/github/v/release/wickra-lib/wickra?logo=github&color=green)](https://github.com/wickra-lib/wickra/releases/latest)
[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT_OR_Apache--2.0-blue)](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.
+40
View File
@@ -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()
+167
View File
@@ -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;
}
+20
View File
@@ -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
+66
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+12
View File
@@ -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/
+90
View File
@@ -0,0 +1,90 @@
# Wickra — .NET
[![CI](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml/badge.svg)](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/wickra-lib/wickra/branch/main/graph/badge.svg)](https://codecov.io/gh/wickra-lib/wickra)
[![NuGet](https://img.shields.io/nuget/v/Wickra.svg?logo=nuget&color=blue)](https://www.nuget.org/packages/Wickra)
[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT_OR_Apache--2.0-blue)](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));
}
}
+162
View File
@@ -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");
}
}
}
+51
View File
@@ -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
+57
View File
@@ -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>
+26
View File
@@ -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;
}
}
+87
View File
@@ -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/&lt;rid&gt;/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>
+101
View File
@@ -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.");
+125
View File
@@ -0,0 +1,125 @@
# Wickra — Go
[![CI](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml/badge.svg)](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/wickra-lib/wickra/branch/main/graph/badge.svg)](https://codecov.io/gh/wickra-lib/wickra)
[![Go module](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/go.svg)](https://pkg.go.dev/github.com/wickra-lib/wickra/bindings/go)
[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT_OR_Apache--2.0-blue)](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.
+145
View File
@@ -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")
}
+3
View File
@@ -0,0 +1,3 @@
module github.com/wickra-lib/wickra/bindings/go
go 1.23
+191
View File
@@ -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
+7
View File
@@ -0,0 +1,7 @@
# Prebuilt Wickra C ABI libraries are provisioned locally / in CI, not committed.
*.so
*.dylib
*.dll
*.a
*.lib
*.exp
+30
View File
@@ -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")
+151
View File
@@ -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
}
+6
View File
@@ -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/
+115
View File
@@ -0,0 +1,115 @@
# Wickra — Java
[![CI](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml/badge.svg)](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/wickra-lib/wickra/branch/main/graph/badge.svg)](https://codecov.io/gh/wickra-lib/wickra)
[![Maven Central](https://img.shields.io/maven-central/v/org.wickra/wickra.svg?logo=apache-maven&color=blue)](https://central.sonatype.com/artifact/org.wickra/wickra)
[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT_OR_Apache--2.0-blue)](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.
+57
View File
@@ -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 &lt;-&gt; 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 -&gt; 1-out), ATR (multi-in -&gt; 1-out), and MACD
* (1-in -&gt; 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) {}
}
+171
View File
@@ -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