Compare commits

...

94 Commits

Author SHA1 Message Date
kingchenc 41d5a7dd25 fix: build Linux Python wheels with vendored OpenSSL (manylinux + musllinux) (#327)
Fixes the Linux Python wheel build that broke the `0.9.3` release (and would
have broken `0.9.4`), and adds a CI guard so it cannot regress silently.

### Root cause
The `live-binance` data layer links `native-tls` -> `openssl-sys`, which needs
OpenSSL at build time. Neither wheel container provides it:
- **manylinux** ships no OpenSSL headers, and
- **musllinux** cross-compiles against a musl sysroot that has no OpenSSL at all,
  so installing a host package (`yum`/`apk`) cannot reach the cross target.

The 3-OS Python CI jobs build natively on the runner, which already has system
OpenSSL, so CI stayed green while the release container build failed.

### Fix
- New opt-in **`vendored-tls`** feature on `wickra-data` and the Python binding:
  enables `native-tls/vendored`, compiling OpenSSL from source and linking it
  statically. No system OpenSSL needed on either libc. No-op on macOS/Windows
  (Security.framework / SChannel — `openssl-sys` is never in the graph there).
- `release.yml` builds the Linux wheels with `--features vendored-tls` (replaces
  the manylinux-only `before-script-linux` header install, which could not fix
  the musllinux cross build).
- CI gains a **`manylinux` + `musllinux` container build-smoke** matrix job, so
  both container builds run on every PR. This PR's own CI is the proof the fix
  works before any release re-attempt.

### Notes
- No version bump: `0.9.4` published nowhere (the release run was cancelled
  before any publish job ran), so this lands on `0.9.4` and the tag is re-pointed
  at the fixed commit.
- Adds checks to `ci.yml` (the smoke job is now a 2-entry matrix).
2026-06-17 22:26:21 +02:00
kingchenc e595ea8bfe release: bump 0.9.3 -> 0.9.4 (#326)
Packaging fix for the `0.9.3` data layer.

`0.9.3` published to crates.io, Maven Central, NuGet, npm, and the Go mirror, but
the **Linux Python wheels failed to build**, so `Publish to PyPI` was skipped and
no GitHub Release was attached. Root cause: the `live-binance` data layer links
`native-tls` -> `openssl-sys`, and the `manylinux` / `musllinux` wheel-build
containers do not ship the system OpenSSL headers. The native macOS and Windows
wheels were unaffected (system TLS), which is why CI — running Python natively on
the runners, where OpenSSL is present — stayed green.

### Changes
- **`ci`**: install the OpenSSL headers inside the wheel container via
  maturin-action's `before-script-linux` (`openssl-devel` on `manylinux`,
  `openssl-dev` on `musllinux`) before maturin compiles. No library code changed.
- **`release: bump 0.9.3 -> 0.9.4`**: version-string bump across the manual
  touchpoints + `CHANGELOG` `[0.9.4]`.

`0.9.4` is functionally identical to `0.9.3`; it exists only because the already-
published registries cannot re-release `0.9.3`. PyPI publishes for the first time
starting at `0.9.4` (it skips `0.9.3`).
2026-06-17 21:35:14 +02:00
kingchenc a5fe2e71c4 release: bump 0.9.2 -> 0.9.3 (#325)
Release the native data-layer bundle.

`0.9.3` ships everything merged since `0.9.2`:

- **Data layer in all 10 languages** — `CandleReader` (CSV), `TickAggregator`, `Resampler`, live `BinanceFeed` (WebSocket) and the historical `fetch_binance_klines` (REST), each cross-language golden-pinned.
- **`name()` on every indicator** in all 10 languages (514 indicators, golden-pinned).
- **Python is now zero third-party deps** — NumPy is optional (`pip install wickra[numpy]`); `batch` returns a stdlib `array.array` / buffer-protocol `Matrix` (breaking; results numerically identical, batch throughput tradeoff noted in the CHANGELOG).
- **Binance feed: missing `3d` / `1M` intervals** fixed.

Pure version-string bump on top — `bump_version.py` touched 19 files (Cargo + Lock, pyproject, all node package.json/locks + 6 platform stubs, pom + csproj + DESCRIPTION, SECURITY, CHANGELOG `[0.9.3]` + compare URLs). `cargo fmt`/`clippy -D warnings`/`test --workspace --all-features` all green locally (4225+ tests, 0 failed).

The tag/publish is **not** part of this PR — it waits for explicit GO (irreversible publish to crates.io / PyPI / npm / NuGet / Maven / Go / r-universe).
2026-06-17 21:14:22 +02:00
kingchenc 75eefbbd08 examples: fix and harmonize the strategy backtests across all languages (#324)
The strategy_* examples were only syntax-smoked in CI, never run, which hid two
classes of problem:

1. Python strategy_macd_adx / strategy_bollinger_squeeze passed three separate
   arguments to the candle indicators ADX/ATR, whose .update() takes a single
   candle — a TypeError at runtime — and read the ADX tuple at index 0 (plus_di)
   instead of 2 (adx). Both fixed.

2. The Go / C# / R / Java strategies defaulted to synthetic data and used a
   different (annualised) one-line summary, so they printed wildly different
   numbers from the Rust/Python/Node/C/WASM suite. Rewrite them to the shared
   per-trade backtest (load the bundled BTCUSDT CSV by default, same entry/exit
   logic, same print_summary output).

All nine runnable bindings now print byte-identical backtest summaries on the
same data (MACD+ADX 246 trades / -47.19%, RSI 37 / -17.84%, Bollinger 1 / -7.82%),
verified by diffing each language's output against the Python reference. WASM
shares the same logic and bundled dataset (browser-rendered).
2026-06-17 17:56:22 +02:00
kingchenc 2e07c07a40 docs(examples): drop stale third-party install hints (#323)
live_binance.py uses the native BinanceFeed and the node examples no longer
depend on ws, so the examples README no longer tells readers to pip install
websockets or that npm install pulls ws. The whole examples set runs on
Wickra alone.
2026-06-17 17:56:17 +02:00
kingchenc ec937b8281 docs: re-measure per-binding throughput on the 9950X (#322)
Refresh the section 3 / README per-binding throughput table with a fresh
SMA(20) run on the reference machine. The Python batch figure now reflects
the stdlib array.array output path (NumPy is optional since the zero-dep
change), so batch is no longer near-core for Python and Node; the prose is
updated to match.
2026-06-17 15:30:20 +02:00
kingchenc 1f5eb90b0d docs: PyPI README reflects the zero-dependency Python binding (#321)
The package README shown on **PyPI** still advertised a NumPy quick-start. Now that Python dropped its NumPy runtime dependency (#317), this updates it:

- Tagline + install note: `pip install wickra` pulls **zero** third-party packages (not even NumPy); NumPy is an optional extra (`wickra[numpy]`).
- Quick start imports no NumPy; `batch` returns `array.array('d')` (with a note that `np.asarray` wraps it zero-copy if you use NumPy).

Ships with the data-layer release.
2026-06-17 03:40:18 +02:00
kingchenc bbda70f75b docs: lead with the zero-dependency native data layer (README) (#320)
Updates the README now that the data layer is native in all ten languages and Python no longer needs NumPy (#317): the docs lead with **zero third-party deps for data I/O, in every language**.

- **'Batteries included'** bullet now covers the full data layer — CSV reader, tick aggregator, resampler, live WebSocket feed, historical REST fetcher — as zero third-party deps in every language. Drops the stale 'indicator chaining' wording (chaining stays documented under Reference).
- **Hero Python example** no longer imports NumPy; `batch` returns `array.array('d')` (with a note that `np.asarray` wraps it zero-copy if you do use NumPy).
- **'Live data sources'** lists the native `fetch_binance_klines` and the `BinanceFeed` naming, and drops the stale reference to the third-party `websockets` package.
- Intro tagline notes 'zero third-party packages'.

Docs-only; ships with the data-layer release. The matching webpage / wickra-docs / wickra-go / org-profile updates follow with the release.
2026-06-17 03:27:19 +02:00
kingchenc 2d2d5970b4 bench: native data-layer throughput benchmark (#318)
Adds a measured benchmark for the native data layer (Task 6).

## What
- New criterion bench `crates/wickra/benches/data_layer.rs` exercising the data layer on **50 000 real BTCUSDT 1m candles** (`examples/data/btcusdt-1m.csv`):
  - **CSV parse** (`CandleReader`)
  - **Tick aggregation** → 1m candles (`TickAggregator`)
  - **Resample** 1m → 5m (`Resampler`)
- New **"4. Data layer"** section in `BENCHMARKS.md` with the measured numbers and the same "one core, FFI boundary" framing as the per-binding table.

## Measured (Rust core, Ryzen 9 9950X, median of 100 samples)
| Operation | Throughput | Per element |
|---|--:|--:|
| CSV parse | 3.0 M candles/s | 329 ns |
| Tick aggregate → 1m | 44 M ticks/s | 22.6 ns |
| Resample 1m → 5m | 234 M candles/s | 4.3 ns |

This is the same native code every binding rides through the FFI boundary characterised in §3 — the data I/O that replaces pandas / csv-parse / manual bucketing / pandas.resample, with zero third-party packages.

Run: `cargo bench -p wickra --bench data_layer`.

Verified locally: `cargo clippy -p wickra --benches --all-features -- -D warnings` clean; bench runs green.
2026-06-17 03:27:14 +02:00
kingchenc 8228be7069 python: drop the NumPy runtime dependency (zero third-party deps) (#317)
Makes the Python binding truly dependency-free (Task 5b). `pip install wickra` now pulls **zero** third-party packages, and `import wickra` never imports NumPy (verified: `"numpy" not in sys.modules` after a batch call).

## What changed
- **Inputs** accept any sequence or buffer of numbers — `array.array`, `memoryview`, a NumPy `ndarray`, or a plain `list` — via `Vec` extraction (newtypes `Buf1`/`BufI64`). `PyBuffer` is unavailable under `abi3-py39`, and `unsafe_code = forbid` rules out a zero-copy slice, so inputs are copied once (negligible vs. compute).
- **Single-output `batch(...)`** returns a stdlib `array.array('d')` (native buffer protocol → `numpy.asarray` zero-copy).
- **Multi-output `batch(...)`** returns a buffer-protocol `Matrix` preserving `.shape`, integer-row and `[i, j]` access, and `.tolist()`.
- **NumPy** moves to an optional extra (`pip install wickra[numpy]`); it is never required.
- Streaming `update(...)` is unchanged; results are numerically identical.

## Implementation
- Trait `IntoPyData` + a `matrix()`/`f64_array()` helper collapse the ~400 batch call sites; `array.array` is built via `bytemuck` (Zlib/MIT/Apache — `cargo deny check licenses` ok).
- Tests migrated to `array.array`/`Matrix` (1-D `.shape`→`len()`, `.dtype`→`.typecode`; numeric comparisons normalize through a `_to_np` helper). The non-contiguous-input test now asserts acceptance instead of rejection.

## Verification (local)
- `pytest bindings/python/tests` — **1991 passed**.
- `cargo fmt --all`, `cargo clippy --workspace --all-targets --all-features -- -D warnings`, `cargo test --workspace --all-features`, `cargo deny check licenses` — all green.

**BREAKING for Python**: batch return types change from NumPy arrays to `array.array`/`Matrix`. Documented in CHANGELOG; ships with the data-layer release bundle (no separate tag).
2026-06-17 03:19:50 +02:00
kingchenc 677ea37402 examples: migrate to the native data layer (drop ws/coder-websocket/jackson/jsonlite) (#316)
Stacked on #315 (the native Binance REST fetcher). Retarget to `main` once #315 merges.

Migrates the runnable examples off third-party data-I/O packages onto Wickra's
native data layer (`CandleReader`, `Resampler`, `BinanceFeed`, `fetch_*klines`).

## Third-party packages removed (the zero-dep selling point)
- **Node**: `ws` (live feed → BinanceFeed) — dropped from package.json + lockfile
- **Go**: `github.com/coder/websocket` — dropped from go.mod / go.sum (`go mod tidy`)
- **Java**: `jackson-databind` (live feed + REST fetch) — dropped from pom.xml
- **R**: `jsonlite` + `websocket` + `later` — dropped from the README notes

Each language's CSV loading now goes through `CandleReader`, manual resampling
through `Resampler`, the live feed through `BinanceFeed`, and (Java/R) the REST
download through the native fetcher.

## Verification
Ran the offline examples per language against the bundled data — backtest and
multi_timeframe produce identical output across Python / Node / Go / Java / R
(e.g. ATR(14) last 345.1010; 1h→5m resamples to 240 bars, →15m to 80 bars).

C# / C / WASM (stdlib-only, no third-party deps to remove) follow in this branch.

Note: the streaming `strategy_*` examples have pre-existing candle-indicator
runtime bugs (CI only syntax-smokes them); the CSV migration preserves their
shape and leaves those bugs for a separate fix.
2026-06-17 01:49:11 +02:00
kingchenc 2ae76bb90e feat(data): native Binance REST kline fetcher in 9 languages (#315)
Adds `BinanceRest::fetch_klines` to `wickra-data`: a blocking historical kline
downloader (`GET /api/v3/klines`) and the historical counterpart to the F4 live
`BinanceFeed`. It is the last native data-layer primitive needed to drop
third-party HTTP/JSON download helpers (`jackson`, `jsonlite`, `urllib`, …) from
the examples.

## What

- **Core** (`wickra-data`): `fetch_klines(symbol, interval, limit, start?, end?)`
  built on `ureq` with native-tls — sharing the exact same TLS backend
  (native-tls 0.2 / SChannel) as the existing tokio-tungstenite live feed, so the
  two pull one TLS stack, not two. Parses Binance's 12-element array rows via the
  existing serde infrastructure into validated `Candle`s. Blocking by design (a
  one-shot request needs no async runtime; the FFI boundary is synchronous
  anyway). Nine mock-HTTP-server tests cover parse / empty / limit / transport /
  JSON / invariant-violation paths.
- **C ABI**: `wickra_binance_fetch_klines(...)` (blocking drain into a caller
  buffer, `-1` on error) + regenerated cbindgen header and its vendored Go copy.
- **Bindings**: native Node `fetchBinanceKlines` / Python `fetch_binance_klines`;
  generated Go `FetchBinanceKlines` / C# `BinanceFeed.FetchKlines` / Java
  `BinanceFeed.fetchKlines` / R `fetch_binance_klines`. C / C++ call the C ABI
  directly. **WASM is excluded** (browsers use the host `fetch`).

The four C-ABI bindings are regenerated from the ScriptHelpers generators (not
hand-edited); the regen diff is exactly the new wrapper in each.

## Verification

All ten toolchains green locally: Rust (`cargo test`/`clippy`/`fmt`), Node, Python,
Go, C#, Java, R (`R CMD INSTALL` + smoke), WASM (`cargo check`, confirmed `ureq`
is not pulled). Each binding has an error-path smoke test; the parse/HTTP success
path is covered by the Rust mock-server tests.

No release in this PR — ships with the data-layer + numpy bundle later.
2026-06-17 01:48:24 +02:00
kingchenc b92ad32037 docs(crates): point wickra documentation link to docs.wickra.org (#314)
## What

Change the `documentation` field of the main `wickra` crate from the auto-generated rustdoc URL to the project's documentation site:

```toml
# crates/wickra/Cargo.toml
documentation = "https://docs.wickra.org"   # was: https://docs.rs/wickra
```

## Why

On crates.io the "Documentation" link defaulted to `docs.rs/wickra`. The richer landing page for someone arriving from crates.io is the prose docs site — it carries the Rust quickstart, the API guide, and the full indicator catalogue. The auto-built docs.rs API reference stays reachable at `docs.rs/wickra`; only the crates.io link target changes.

## Scope

- Only the user-facing `wickra` crate is changed.
- `wickra-core` and `wickra-data` keep their `docs.rs` links — those low-level crates have no dedicated guide page on the docs site, so docs.rs remains the correct target for them.
- Metadata only; no code, build, or runtime change. Takes effect on the next crates.io publish.
2026-06-17 01:06:01 +02:00
kingchenc 3a709d9a66 feat(data): native live Binance kline feed in 9 languages (+ 3d/1M intervals) (#313)
* feat(data): C-ABI Binance feed + Go binding (F4 wip)

C ABI exposes the existing async BinanceKlineStream (tokio + TLS, auto-reconnect,
mock-server-tested in wickra-data) through a blocking poll: wickra_binance_connect
/ _next(out, timeout_ms) -> {1 event, 0 timeout, -1 closed} / _close / _free over
an opaque BinanceStream that owns a current-thread runtime. WickraKlineEvent
carries OHLCV + open_time + is_closed + a 16-byte symbol buffer. `live-binance`
is now a default feature of wickra-c (the published DLL ships the feed; the wasm
build drops it via --no-default-features).

Go: NewBinanceFeed(symbols, interval, baseURL) + Next(timeout) + Close, with a
deterministic error-path smoke (the connect->event pipeline is covered by the
Rust mock-WS-server tests).

* feat(data): Binance feed C# + Java bindings (F4 wip)

C#: BinanceFeed(symbols, interval, baseUrl?) + Next(timeout) -> KlineEvent? +
Dispose; bespoke WickraKlineEvent native struct (fixed symbol buffer + byte
is_closed) since the scalar struct parser can't model it. `char` maps to `byte`
for the const char* params.

Java: BinanceFeed + KlineEvent record + BinanceInterval enum over Panama FFM;
the event is read at hand-computed offsets (symbol@0, doubles@16..48,
open_time@56, is_closed@64; 72-byte struct). Both with deterministic error-path
smokes (pipeline covered by the Rust mock-WS-server tests).

* feat(data): Binance feed R binding (F4 wip)

R: BinanceFeed(symbols, interval, base_url) + binance_next(feed, timeout_ms) ->
named list | NULL + binance_close, via bespoke .Call glue (wk_binance_*). The
glue + its registration entries are gated out of the Emscripten/wasm build
(#ifndef __EMSCRIPTEN__) since r-universe/webR has no raw sockets. NAMESPACE
exports added by hand (roxygen2 not installed locally). Deterministic error-path
smoke; pipeline covered by the Rust mock-WS-server tests.

* feat(data): native Binance feed for Node + Python; CHANGELOG (F4 complete)

Node (napi) BinanceFeed: new(symbols, interval, baseUrl?) + next(timeoutMs) ->
KlineEvent | null + close. Python (pyo3) BinanceFeed: same, with next releasing
the GIL (py.detach) while it waits. Both drive the mock-server-tested async
BinanceKlineStream on a single-thread tokio runtime (blocking poll); wickra-data
gains the live-binance feature + tokio in each binding.

Completes F4: the live Binance kline feed is now native in all 9 languages
(WASM excluded), with no third-party WebSocket client in any of them.
2026-06-16 02:01:37 +02:00
kingchenc baf4d0ff47 fix(data): add missing 3d and 1M Binance kline intervals (#312)
Binance supports 16 kline intervals (1s,1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,
12h,1d,3d,1w,1M); the live-binance `Interval` enum listed only 14, missing
three-day (`3d`) and one-month (`1M`). Add both variants in Binance order
with their wire-format strings, and extend the exhaustive as_str test.
2026-06-16 00:51:52 +02:00
kingchenc d362ae26a3 feat(data): expose CandleReader (CSV) natively in all 10 languages (#311)
Add the data-layer CSV candle reader to every binding so loading OHLCV
candles from a CSV no longer needs a per-language CSV/dataframe dependency.

- C ABI: wickra_candle_reader_new(bytes, len) / _count / _read / _free over
  an opaque CandleReader handle (parse the whole buffer up front, then drain).
- Native: Node/WASM CandleReader.read() -> Candle[], Python read() -> list[tuple].
- C-ABI languages: Go Read() []Candle, C# Candle[] Read(), Java Candle[] read(),
  R read() S3 generic (n x 6 matrix); C / C++ call the C ABI directly.
- Cross-language golden testdata/golden/data_csv*.csv pins the parsed candles
  bit-for-bit across every binding.

Verified locally across Rust (test+clippy+fmt), Node, WASM, Python, C#, Go,
Java, R, and the C/C++ cmake parity suite.
2026-06-16 00:10:58 +02:00
kingchenc cb6da4d737 feat(data-layer): Resampler (candle resampling) in all 10 languages (#310)
* feat(data-layer): Resampler (candle resampling) in all 10 languages

Second data-layer feature (F3): resample candles into a higher timeframe.

- Native (Node.js/WASM): new Resampler(timeframe) -> update(o,h,l,c,v,ts):
  Candle|null + flush(): Candle|null. Python the same -> tuple|None.
- C ABI: wickra_resampler_new/update/flush/free (update has the multi-output
  shape so the generators auto-emit it; flush is bespoke). Go Update -> (Candle,
  bool) + Flush; C# Candle? Update/Flush; Java Candle update/flush; R update()
  generic + a flush() S3 method (extends base::flush); C/C++ direct.
- Cross-language golden (testdata/golden/data_resampled.csv): the shared input
  candles resampled into 5-unit buckets, the final partial bucket via flush,
  pinned bit-for-bit across every binding.

Verified locally in all 10 (3 candles for the 5-unit smoke; 16 for the golden).
The WickraCandle output record is shared with the tick aggregator (deduped).

* test(node): exclude data-layer types from the indicator completeness contract

The Resampler exposes update(), so the completeness test flagged it as an
indicator and required batch/reset/isReady/warmupPeriod, which a data-layer type
does not have. Exclude TickAggregator and Resampler like the bar builders.
2026-06-15 22:36:16 +02:00
kingchenc 8a103ef920 feat(data-layer): TickAggregator (tick-to-candle) in all 10 languages (#309)
* feat(data-layer): TickAggregator in Node, WASM, Python + C ABI hub

First data-layer feature (F2): roll trade ticks up into fixed-timeframe OHLCV
candles, exposed natively and over the C ABI.

- wickra-data wired as a binding dependency (workspace dep; its wickra-core dep
  is default-features=false so it never forces rayon into the rayon-free WASM
  build — native bindings re-enable parallel through their own dependency).
- Node `TickAggregator(bucket, gapFill?)` -> `push(price, size, ts): Candle[]`;
  WASM the same (array of objects); Python `push(...) -> list[tuple]`.
- C ABI: `WickraCandle` struct + `wickra_tick_aggregator_new/push/free` (push
  writes candles into a caller buffer and returns the count), generated via the
  capi generator's new DATA_LAYER section; cbindgen now parses wickra-data so
  `TickAggregator` is a forward-declared opaque; header vendored to bindings/go.

Verified bit-identical across Node/WASM/Python/C/C++ (o=100 h=101 l=100 c=101
v=3 ts=0 for the shared 3-tick probe). WIP: Go/C#/Java/R generated bindings and
the cross-language golden are still pending.

* feat(data-layer): TickAggregator in Go, C#, Java, R (lossless push/drain)

Complete F2 across all 10 languages: the C-ABI tick aggregator now uses a
two-step push/drain so gap-fill candles are never lost, and the four generated
bindings expose it idiomatically.

- C ABI redesigned: opaque TickAggregator handle (inner aggregator + pending
  buffer); push consumes a tick and returns the closed-candle count, drain copies
  them into a count-sized caller buffer.
- Go: NewTickAggregator + Push(price,size,ts) []Candle; C#: TickAggregator +
  Candle[] Push(...); Java: TickAggregator + Candle[] push(...); R: TickAggregator
  constructor + push() S3 generic returning an (n x 6) numeric matrix.
- Candle output record generated per language from WickraCandle.

Verified bit-identical to the native bindings (o=100 h=101 l=100 c=101 v=3 ts=0)
in Go, C#, Java, and R at runtime; R passes R CMD check (pre-existing doc
warnings only). WIP: cross-language data-layer golden + CHANGELOG still pending.

* test(data-layer): cross-language golden for the tick aggregator + CHANGELOG

gen_golden emits a deterministic tick stream (testdata/golden/data_ticks.csv) and
the reference candle streams with and without gap filling (data_candles.csv,
data_candles_gap.csv). Every binding replays the shared ticks through its
TickAggregator and checks the candles bit-for-bit (fp tolerance) against the Rust
reference:

- Node / WASM / Python / Go / C# / Java / R: a dedicated parity test each.
- C / C++: data_layer_test.c (compiled as both, run as ctest).

The gap-fill fixture closes several candles from a single push, exercising the
lossless push/drain path. Records the feature under CHANGELOG [Unreleased].

* fix(examples): rename the CSV-loader candle to WickraBar

The example CSV helper (wickra_csv.h) defined its own struct WickraCandle, which
now collides with the public C ABI WickraCandle (the tick aggregator output) in
any example that includes both headers (backtest, multi_timeframe, the strategy
examples). The public type owns the name; rename the example loader's bar to
WickraBar. The generated golden_test.c is untouched (its only match was the
unrelated WickraCandleVolumeOutput).
2026-06-15 21:24:33 +02:00
kingchenc fd9f4c8bc6 feat(bindings): expose name() on every indicator in all 10 languages (#308)
* feat(bindings): expose name() on every indicator in Node, WASM, and Python

Surface the core Indicator::name() / BarBuilder::name() accessor through the
three native bindings so every indicator reports its canonical name at runtime,
matching the existing reset/isReady/warmupPeriod surface.

- Node (napi): name(): string on all 514 classes (regenerated index.d.ts)
- WASM (wasm-bindgen): name(): string on all 514 classes
- Python (pyo3): name() -> str on all classes

* feat(bindings): expose name() across the C ABI and C/C++/Go/C#/Java/R

Regenerate the C ABI and the four generated language bindings from the updated
ScriptHelpers generators so every indicator and bar builder reports its
canonical name at runtime, completing name() coverage across all 10 languages.

- C ABI (bindings/c): wickra_<ind>_name() -> *const c_char for all 514, cached
  in a per-function OnceLock<CString> with ind.name() as the source of truth;
  cbindgen header regenerated and vendored into bindings/go/include.
- Go: Name() string; C#: string Name(); Java: String name(); R: name() S3
  generic over the wk_<ind>_name C glue (methods.R + NAMESPACE).

The Java regeneration also restores two fixes that had drifted out of the
generator (bool* arrays via boolSegment; uint8_t ctor args cast to byte) and C#
re-emits '#nullable enable'; these are no-op vs the previous committed output
apart from the new name() accessors.

* test(golden): pin canonical name() across all 10 language bindings

Add a cross-language name() consistency check: every indicator must report the
exact core Indicator::name() (which can differ from the registered class name,
e.g. ChaikinMoneyFlow -> "CMF", Donchian -> "DonchianChannels"). The 514 core
names are committed as testdata/golden/names.json (keyed by Rust canonical) and
asserted by each binding's golden replay, which already reconstructs the whole
catalogue:

- node / wasm: assert against names.json in the existing golden test
- python: new test_golden_names.py over the shared node manifest
- go / csharp / java / c+c++ / r: the golden-test generators load names.json and
  emit a name assertion per indicator (regenerated test artifacts committed)

All 10 bindings return identical names by construction (each delegates to core),
so this pins that contract and guards against a future binding breaking the
passthrough.

* docs(changelog): record name() across all 10 bindings under Unreleased

* fix(r): restore bool* flag marshalling in the regenerated C glue

The name() regeneration had reverted the cross-section bool fix: the R glue
emitted (bool *)REAL(x) for const bool* inputs, reinterpreting 8-byte doubles as
1-byte bools so every flag read as false (PercentAboveMa, NewHighsNewLows,
HighLowIndex, BullishPercentIndex returned 0 instead of the breadth value). The
wk_bool_vec() helper is restored in the generator and the glue routes bool arrays
through it again.
2026-06-15 17:19:24 +02:00
kingchenc 59acefa1ea ci: retry-harden the Rust toolchain install across all jobs (#307) 2026-06-15 07:09:39 +02:00
kingchenc 5f0677d62d release: bump 0.9.1 -> 0.9.2 (#306) 2026-06-15 06:02:05 +02:00
kingchenc ce792cf8b8 fix: clear the last two build warnings (C# CA2255, WASM license files) (#304)
- C#: the [ModuleInitializer] that registers the DllImport resolver is the
  one legitimate library use of the attribute (a static ctor would run too
  late), so suppress CA2255 with a documented justification — the dotnet
  build is now warning-clean.
- WASM: add LICENSE-MIT + LICENSE-APACHE to bindings/wasm so wasm-pack stops
  warning about missing license files and the published npm package ships
  its license texts.

Full warnings audit: Rust, C, C++, Go, Java, Node, Python, C#, WASM all
build clean; R installs clean.
2026-06-15 05:26:06 +02:00
kingchenc 120b6ac265 docs+ci: surface 10-language golden verification (#303)
* docs+ci: surface 10-language golden verification; add WASM golden CI

- README: add C++ to the Quickstart list and state prominently that all 514
  indicators are replayed through all 10 languages and checked bit-for-bit
  against the Rust reference.
- CHANGELOG: document the cross-language golden suite, the Java and R C-ABI
  bool-marshalling fixes, the C# nullable directive and the live_binance rename.
- ci.yml: run the WASM golden suite (nodejs-target build + node --test); the
  C/C++ golden tests already run via the C-ABI job's ctest and the other
  bindings pick up their golden runners in their existing test suites.

* readme: add verified badge + prominent per-language throughput table

- Add the 'verified across 10 languages' badge to the badge row, linking to
  the FAQ that explains the cross-language golden parity.
- Surface a per-binding throughput table (the cost of each language's FFI
  boundary) so readers can pick a binding that keeps up with streaming hot
  loops — the cross-library benchmarks stay in BENCHMARKS.md.

* changelog: note the verified badge and per-binding throughput table

* docs: fix cross-language consistency (audit)

- docs/README.md: add the missing C++ quickstart link.
- README testing section: the golden parity now covers all 10 languages and
  all 514 indicators (was 'four C-ABI bindings, 7 archetype indicators').
- CHANGELOG: the live_binance rename also covers the C examples.
2026-06-15 05:07:52 +02:00
kingchenc 4f708d410d test: golden-pin the four de-duplicated indicators across all bindings (#305)
* test: golden-pin the four de-duplicated indicators across all C-ABI bindings

Extend gen_golden to emit reference fixtures for AdOscillator (ADOSC),
IntradayIntensity, AwesomeOscillatorHistogram and AverageDrawdown, and replay
them through the Go / C# / Java / R golden harnesses so their corrected
definitions stay bit-identical to the Rust core in every binding. Go suite
verified locally (gcc 13 + cgo): all 9 golden tests pass; C#/Java/R use the
same fixtures and harness pattern (CI-verified). First step of extending the
golden coverage beyond the seven archetype representatives.

* test: golden-pin the scalar-output tranche (308 indicators) against Rust

Extend gen_golden with a generated emit_scalar that writes reference fixtures
for every single-f64-output indicator (scalar / candle / pairwise input) using
valid constructor params, and add a manifest-driven generic Python golden
replay that reconstructs each by its native name and checks it bit-for-bit
against the Rust output. 308 indicators now value-tied to the Rust core in
Python (pytest: 308/308). Takes golden coverage from the 7 archetype
representatives to 308+ of the catalogue.

22 scalar indicators with non-default constructor constraints are skipped by
gen_golden for now (logged), as are non-f64-output ones; multi-output, exotic
inputs and the per-indicator arg arities of the C-ABI/Node replays follow.
Generated + verified locally with the full toolchain.

* test: golden-pin the multi-output tranche (70 indicators) in Python

Add a generated emit_multi to gen_golden (per-indicator Output-field access,
one CSV column per field) and a manifest-driven generic Python replay that
checks every field of each multi-output indicator against the Rust reference.
70 multi-output indicators now value-tied to Rust in Python; combined with the
scalar tranche, 378 indicators are golden-pinned. 8 multi with non-default
param constraints and 5 with non-f64 Output fields (Option/Vec/i64) are
deferred. pytest green.

* test(golden): add 30 constraint-tuned indicators to scalar/multi golden suite

Emit golden fixtures for 22 scalar-output and 8 multi-output indicators
whose constructors need non-default parameters (Alma, Jma, Psar, T3, Mama,
DoubleBollinger, ZigZag, ...). All 408 fixtures replay bit-for-bit through
the Python binding.

* test(golden): cover 36 missed scalar/multi indicators

Add 26 single-output (LinearRegression family, HT cycle, Candle
volatility estimators, DrawdownDuration) and 10 multi-output
(BollingerBands, MACD/MACDEXT/MACDFIX, Camarilla, VWAP bands, ...)
indicators to the golden suite. 444 fixtures replay bit-for-bit
through the Python binding.

* test(golden): cover 50 exotic-input indicators

Add deterministic synthetic feeders for the DerivativesTick (17),
CrossSection (15), Trade (8), TradeQuote (3) and OrderBook (7)
families, derived from the shared OHLCV input series in both
gen_golden and a new Python replay harness (test_golden_exotic).
All 494 fixtures replay bit-for-bit through the Python binding.

* test(golden): complete 514-indicator golden coverage

Add the final tranches: 3 mixed multi-output indicators (Ichimoku,
WilliamsFractals, LeadLagCrossCorrelation), 6 histogram profiles
(time/volume seasonality + TPO/volume price profiles), 10 alt-chart
bar builders and the footprint. Every one of the 514 distinct
indicators now has a Rust-generated g_<Canonical>.csv fixture and a
generic Python replay (scalar/multi/exotic/profile/bars), all passing
bit-for-bit.

* test(golden): add generic Node replay for all 514 indicators

A manifest-driven node:test harness reconstructs every indicator by its
native class, feeds the same synthetic stream derived from the shared
golden input, and checks output bit-for-bit against the Rust reference
fixtures (scalar/multi/exotic/profile/bars). node_manifest.json is
generated from index.d.ts plus the Python-side manifests. 514/514 pass.

* test(golden): add generated Go replay for all 514 indicators

golden_all_test.go (generated by gen_golden_test.py) reconstructs every
Go indicator, feeds the shared synthetic stream and checks output
bit-for-bit against the Rust reference fixtures. A reflection-based
comparator flattens multi-output structs, profiles and bar slices so one
path covers all archetypes. This is the first C-ABI binding verified
across the full catalogue. 514/514 pass.

* test(golden): add generated C# replay for all 514 indicators

GoldenAllTests.g.cs (generated by gen_golden_test.py) reconstructs every
C# indicator, feeds the shared synthetic stream and checks output
bit-for-bit against the Rust reference fixtures via a reflection-based
flatten covering scalar/multi/profile/bar archetypes. 514/514 pass.

Also add the '#nullable enable' directive the compiler requires to the
generated Indicators.g.cs, clearing the four CS8669 warnings on the
nullable double[] profile return types.

* fix(java): marshal C ABI bool params correctly; add 514 golden replay

The Java FFM binding marshalled the cross-section state flags (newHigh,
newLow, aboveMa, onBuySignal) as JAVA_DOUBLE arrays, but the C ABI takes
them as const bool* (one byte each), so the native side read the low byte
of each 8-byte double and saw every flag as false. Add WickraNative.
boolSegment and use it across the 15 cross-section indicators. Also pass
the MacdExt MaType arguments as byte to match the uint8_t downcall
descriptor (was int, throwing WrongMethodTypeException).

Add GoldenAllTest.java (generated by gen_golden_test.py): a reflection
runner replaying all 514 indicators against the Rust reference fixtures.
The bugs above were found by this test; 514/514 now pass.

* fix(r): marshal C ABI bool flags correctly; add 514 golden replay

The R wrapper passed the cross-section state flags as (bool *)REAL(x),
reinterpreting the 8-byte doubles as 1-byte bools so the native side read
every flag as false. Add wk_bool_vec to convert each flag vector into a
real C bool buffer and use it for all 15 cross-section update wrappers.

Add test-golden-all.R + generated golden_specs.R: a reflective runner
replaying all 514 indicators against the Rust reference fixtures. The bug
above was found by this test; verified 514/514 pass locally.

* test(golden): add WASM replay for all 514 indicators

A manifest-driven node:test harness loads the nodejs-target wasm-pack
build, reconstructs every indicator by its JS class, feeds the shared
synthetic stream and checks output bit-for-bit against the Rust
reference fixtures. wasm_manifest.json is generated from the wasm .d.ts
plus the shared manifests; a recursive flattener covers scalar, multi
(Reflect objects), profile and bar shapes. 514/514 pass locally
(wasm-pack build --target nodejs, then node --test).

* test(golden): add C and C++ replay for all 514 indicators

golden_test.c (generated by gen_golden_test.py) drives every indicator
through the C ABI (wickra.h) and checks output bit-for-bit against the
Rust reference fixtures. golden_test.cpp #includes the same source so the
identical runner is compiled and run under both gcc (C) and g++ (C++) via
the CMake targets golden_test / golden_test_cpp — proving the extern "C"
header is consumable from each language. Both 514/514 (verified via ctest).

* test(golden): gofmt the generated Go golden replay

* test(golden): make the Node fixture reader CRLF-safe and pin fixtures to LF
2026-06-15 04:48:51 +02:00
kingchenc de1112ea91 chore(examples): rename live_trading examples to live_binance (#301)
The examples stream a live Binance feed into the indicators and print signals;
they place no orders, so 'live_trading' overstated them and was inconsistent
with the C/Go/R examples already named live_binance. Rename the Python/Node/WASM
files to live_binance.* and update every reference, run command, header, and the
project-tree listings. Accurate use-case wording ('suitable for live trading
bots') and the risk disclaimers are left unchanged.
2026-06-15 03:41:19 +02:00
kingchenc 82d7479011 fix: de-duplicate four indicators by correcting their definitions (#300)
* fix(core): de-duplicate 3 indicators by correcting their definitions

Behavioral audit found these computed identically to another indicator:

- AverageDrawdown was the mean per-bar under-water fraction = PainIndex.
  Now the conventional average drawdown: mean of the maximum depths of the
  distinct drawdown episodes in the window.
- IntradayIntensity was a cumulative line = the A/D Line (Adl); its normalized
  form is the Chaikin Money Flow (Cmf). Now the raw per-bar Bostian intensity
  volume*(2c-h-l)/(h-l), distinct from both.
- AwesomeOscillatorHistogram was AO - SMA(AO, n) = AcceleratorOscillator. Now
  the AO momentum AO[t] - AO[t-lookback] (the histogram delta); the 3rd
  parameter is reinterpreted from sma_period to lookback (default 1).

Constructor signatures are unchanged, so the bindings keep their API. Core
unit tests rewritten with the new reference values; workspace tests + clippy
green. Binding value-tests and deep-dive docs are updated separately.

* fix(core): redefine AdOscillator as the A/D Oscillator (was a Wad duplicate)

AdOscillator computed the cumulative volume-free Williams A/D line, identical
to the Wad indicator. Redefine it as the Williams A/D *Oscillator*: the same
line minus its 13-bar SMA, so it oscillates around zero (mean-reverting) while
Wad stays the drifting cumulative line for divergence analysis. The canonical
name AdOscillator is now accurate; the trait name() becomes "ADOSC".

Constructor stays no-arg (internal 13-bar signal). Unit tests rewritten and
cross-checked against Wad - SMA(Wad, 13). The native bindings' "WilliamsAD"
alias is renamed to "ADOSC" separately.

* fix(bindings): rename WilliamsAD alias to ADOSC and update value tests

Follows the core de-duplication: the native bindings exposed the Williams A/D
line as 'WilliamsAD', which is now the A/D Oscillator. Rename the Python /
Node.js / WASM alias to 'ADOSC' (regenerated node index.js / index.d.ts) and
update the binding value-tests for the four redefined indicators
(AverageDrawdown episode mean, AwesomeOscillatorHistogram momentum warmup,
the Wad-line reference test now uses ta.Wad()). Python suite and node suite
both pass (pytest all green, node 584/584).

* docs: record indicator de-duplication in README and CHANGELOG

README volume family: 'Williams A/D' -> 'Williams A/D Oscillator', 'Intraday
Intensity Index' -> 'Intraday Intensity'. CHANGELOG [Unreleased] documents the
four redefinitions and the native WilliamsAD -> ADOSC rename as breaking.

* test(core): cover Default impl and drop dead match arm

Codecov flagged AdOscillator::default() (never exercised) and the unreachable
_ => panic!() arm in the AwesomeOscillatorHistogram test. Exercise Default in
the accessors test and rewrite the histogram check as an if-let, removing the
dead arm.
2026-06-15 03:41:15 +02:00
kingchenc a3950bf31b release: bump 0.9.0 -> 0.9.1 (#299) 2026-06-14 02:42:45 +02:00
kingchenc a0a7bc4e62 docs(readme): add a Requirements section with per-language supported versions (#298)
There was no single place listing the minimum supported version per language.
Add a Requirements table after Languages (Rust 1.86, Python 3.9, Node 20, WASM,
C99, C++14, .NET 8, Go 1.23, Java 22, R >= 2.10) linking to the full
Requirements page in the docs.
2026-06-14 01:09:02 +02:00
kingchenc 0c925aa9d5 feat(c-abi): expose warmup_period / is_ready across the C ABI bindings (#297)
* feat(c-abi): expose warmup_period / is_ready across the C ABI bindings

The C ABI hub exposed new/update/batch/reset/free per indicator but not the
Indicator::warmup_period / is_ready queries that the native (Python/Node/WASM)
bindings already had, so C/C#/Go/Java/R callers could not ask an indicator
whether it was warmed up without feeding it and watching for NaN.

Regenerated from the ScriptHelpers capi + language generators:
- bindings/c: wickra_<ind>_warmup_period (size_t) and wickra_<ind>_is_ready
  (bool) for every indicator (504; the 10 alt-chart bar builders are excluded
  by design). wickra.h regenerated via cbindgen (additive only).
- bindings/csharp: int WarmupPeriod() / bool IsReady() on each wrapper.
- bindings/go: WarmupPeriod() int / IsReady() bool.
- bindings/java: int warmupPeriod() / boolean isReady().
- bindings/r: C glue + registration; hand-written warmup_period() / is_ready()
  S3 generics in methods.R, plus NAMESPACE exports.

Tests: C-ABI Rust unit tests, the C examples/archetypes.c suite, and the C#,
Go, Java and R archetype suites all gain a warmup/is_ready transition check.

* build(go): sync vendored wickra.h with the C ABI header

The Go binding vendors bindings/c/include/wickra.h; refresh it with the new
warmup_period / is_ready declarations so the CI sync check passes.
2026-06-14 01:04:06 +02:00
kingchenc d9e6807ca0 fix: correct RelativeStrengthAB binding wrapper casing (#296)
The WASM and Node binding wrappers were named with the lowercase-b acronym
(WasmRelativeStrengthAb, RelativeStrengthAbNode) while the core type and every
other surface use RelativeStrengthAB. The published JS/WASM class name was
already correct via js_name/js_class, so this only aligns the internal Rust
identifiers and the auto-generated TypeScript type alias
(RelativeStrengthAbNode -> RelativeStrengthABNode). Runtime API unchanged.
2026-06-14 00:50:03 +02:00
kingchenc eb50ae4e90 deps(python): upgrade pyo3 + rust-numpy to 0.29, clear 2 advisories (#295)
rust-numpy 0.29 lifted its pyo3 ^0.28 pin, so the resolver can now select
pyo3 0.29. Bump both (kept as a pair) and drop the temporary not-affected
exceptions for RUSTSEC-2026-0176 and RUSTSEC-2026-0177 from deny.toml and
osv-scanner.toml — pyo3 0.29 fixes both advisories.

No public API change; the Python test suite passes unchanged (957 tests).
2026-06-14 00:36:52 +02:00
kingchenc 4a12f60a88 ci: test the Node binding on the 22/24 LTS (18/20 are EOL) (#294)
* ci: test the Node binding on the 22/24 LTS (18/20 are EOL)

Node 18 is EOL and 20 reaches EOL; move the binding CI matrix from [18, 20]
to [22, 24] (both LTS), bump the fixed Node setups in ci.yml/release.yml to 22,
and raise package.json engines to >= 20.

The N-API 8 binary is ABI-stable across Node versions, so no per-version build
is needed. The `npm test` script (`node --test __tests__/`) breaks on Node 22
— it resolves `__tests__` as a module — so switch to `node --test` (auto-
discovery). Verified locally on Node 22: build + 584 tests green.

* build(node): sync package-lock engines.node to >= 20

* ci(node): drop the __tests__/ path arg so node --test auto-discovers

On Node 22+, `node --test __tests__/` resolves the directory as a single
module and fails with one unrunnable subtest. `node --test` (no path)
auto-discovers every *.test.js under the package, matching the package.json
test script. Verified locally: 584 tests pass.
2026-06-14 00:23:36 +02:00
kingchenc 8003572d92 docs(core): add runnable rustdoc examples to 23 indicators (#293)
The fib_* / auto_fib / golden_pocket Fibonacci indicators, the harami_cross /
tristar / tower_top_bottom candlestick patterns and the td_* DeMark family
lacked the `/// # Example` runnable doctest that ARCHITECTURE.md requires of
every indicator. Add a minimal construct-and-feed example to each. The
swing-tracker helper `pattern_swing` is not an `Indicator` and is left out.
All 23 pass `cargo test --doc` (487 doctests green).
2026-06-14 00:09:07 +02:00
kingchenc db5bd5e028 ci: build the Java binding on JDK 25 LTS (22 is EOL) (#292)
Java 22 is a non-LTS that has reached end of life; bump the build JDK in
ci.yml and release.yml to the 25 LTS. The pom pins `maven.compiler.release`
to 22, so the emitted bytecode and the Java 22+ runtime floor (FFM API, final
since 22) are unchanged. Not 21 — FFM was only preview there.
2026-06-13 23:55:49 +02:00
kingchenc 2ef60874b9 fix(examples): satisfy clippy -D warnings in gen_golden (#291)
Replace `i as i64` with `i64::try_from(i)` (cast_possible_wrap) and rename
the OHLCV destructure to descriptive names (many_single_char_names) so
`cargo clippy --all-targets --all-features -- -D warnings` passes on the 1.95
toolchain. Dev-tool only; no library change.
2026-06-13 23:35:29 +02:00
kingchenc d59cd44043 docs: standardise language naming + binding security sections (#290)
* docs: standardise language naming and add binding security sections

Canonical binding list everywhere: Rust, Python, Node.js, WASM, C, C++, C#,
Go, Java, R. Use C# (not .NET) as the language label, WASM (not WebAssembly)
in prose, and frame the C ABI as a hub rather than a list item.

- Bump stale indicator counts (200+ -> 514) and family count (sixteen ->
  twenty-four) in the Node/Python/WASM and docs READMEs.
- Add a short Security section to all eight binding READMEs.
- Relabel benchmark rows (C -> C / C++, C# / .NET -> C#).
- Fix the 'language stecker' wording in the C#/Go/R API intros.
- Documentation only; no code or public API changes.

* release.yml: extend install snippets and expose version output

Add the missing registry installs to the release body (dotnet, go, Gradle/
Maven Central, r-universe) alongside cargo/pip/npm, and expose a v-stripped
'version' output from the tag step for the Gradle coordinate. Also fix the
C-ABI language order in the assets note (C# before Go).

* release.yml: correct the release body (10 languages, all registries)

Reframe the tagline to '10 languages' (native Rust/Python/Node.js/WASM + a C
ABI hub for C, C++, C#, Go, Java, R) instead of '4 language registries', note
that C#/Java/Go/R publish to NuGet/Maven/Go/r-universe via their own jobs, and
tidy the Node.js label and the C-ABI hub list.
2026-06-13 23:34:24 +02:00
kingchenc ee5ee6980e release: bump 0.8.9 -> 0.9.0 (#289) 2026-06-13 03:21:33 +02:00
dependabot[bot] 60b4705d7e deps(maven): bump org.codehaus.mojo:exec-maven-plugin from 3.2.0 to 3.6.3 in /examples/java (#284) 2026-06-13 01:01:55 +02:00
dependabot[bot] 08b2e5abd3 deps(maven): bump org.apache.maven.plugins:maven-compiler-plugin from 3.13.0 to 3.15.0 in /examples/java (#283) 2026-06-13 01:01:49 +02:00
dependabot[bot] 495d9edcc3 deps(maven): bump com.fasterxml.jackson.core:jackson-databind from 2.17.1 to 2.22.0 in /examples/java (#282) 2026-06-13 01:01:44 +02:00
dependabot[bot] c32b802461 deps(maven): bump org.codehaus.mojo:exec-maven-plugin from 3.2.0 to 3.6.3 in /bindings/java/benchmarks (#281) 2026-06-13 01:01:38 +02:00
dependabot[bot] 7e3be0d1ac deps(maven): bump org.apache.maven.plugins:maven-compiler-plugin from 3.13.0 to 3.15.0 in /bindings/java/benchmarks (#280) 2026-06-13 01:01:32 +02:00
dependabot[bot] 1a90de89ea deps(maven): bump org.apache.maven.plugins:maven-javadoc-plugin from 3.7.0 to 3.12.0 in /bindings/java (#279) 2026-06-13 01:01:27 +02:00
dependabot[bot] bb901fbd25 deps(maven): bump org.apache.maven.plugins:maven-jar-plugin from 3.4.1 to 3.5.0 in /bindings/java (#278) 2026-06-13 01:01:21 +02:00
dependabot[bot] 692473452f deps(maven): bump org.apache.maven.plugins:maven-gpg-plugin from 3.2.4 to 3.2.8 in /bindings/java (#277) 2026-06-13 01:01:15 +02:00
dependabot[bot] 2a5b012068 deps(maven): bump org.junit.jupiter:junit-jupiter from 5.10.2 to 6.1.0 in /bindings/java (#276) 2026-06-13 01:01:09 +02:00
dependabot[bot] 513e1111d2 deps(maven): bump org.apache.maven.plugins:maven-surefire-plugin from 3.2.5 to 3.5.6 in /bindings/java (#275) 2026-06-13 01:01:03 +02:00
dependabot[bot] 4b0bdef7c6 deps(maven): bump org.apache.maven.plugins:maven-compiler-plugin from 3.13.0 to 3.15.0 in /bindings/java (#274) 2026-06-13 01:00:57 +02:00
dependabot[bot] 3e4b6c7e22 deps(maven): bump org.apache.maven.plugins:maven-source-plugin from 3.3.1 to 3.4.0 in /bindings/java (#273) 2026-06-13 01:00:51 +02:00
kingchenc dd6a4affb2 ci(dependabot): group updates per ecosystem into a single PR (#288)
All nine Dependabot ecosystems now batch their updates into one grouped PR each
(`groups: { <name>: { patterns: ["*"] } }`) instead of one PR per dependency.

A routine refresh previously fanned out into a dozen-plus PRs (the Maven batch
alone opened 12), each running the full nine-language CI matrix (~55 checks) and
clogging the runner queue ahead of release and feature runs. Grouping collapses
that to one PR per ecosystem.

Trade-off: a single broken update blocks its whole group until excluded — fine
for routine maintenance bumps. Security updates are unaffected (they are not
grouped and continue to arrive individually).
2026-06-12 23:39:26 +02:00
kingchenc e5e094d370 chore(supply-chain): suppress jackson false positive in the Maven sub-scans (#287)
Follow-up to #272. The root `osv-scanner.toml` cleared the OpenSSF Scorecard
Vulnerabilities check from score 5 to 9 (the two pyo3 advisories, which live in
`Cargo.lock` at the root). One finding remained — `GHSA-72hv-8253-57qq`
(jackson-core 3.x async-parser DoS) — because OSV-Scanner resolves its config
relative to each manifest, and the repo-root config does not cover the Maven
sub-directory scans.

This adds the same not-affected suppression next to the two Maven manifests
(`bindings/java`, `examples/java`).

`tools.jackson.core:jackson-core` 3.x is not a dependency of this project: full
Maven resolution (publishing-plugin tree + project trees) resolves only jackson
`2.16.1` / `2.17.1`; tools.jackson 3.x appears nowhere. OSV-Scanner's own
resolver flags it as a false positive.
2026-06-12 23:31:21 +02:00
kingchenc 97940046d1 ci(dependabot): track NuGet (C#) and the Node/Go examples (#286)
Closes the remaining Dependabot coverage gaps after the Maven ecosystem was
added. Audited every binding for external dependency manifests:

| Language | External deps | Coverage |
|----------|---------------|----------|
| Rust (core/wasm) | yes | already `cargo` |
| Python | yes | already `pip` |
| Node (binding) | yes | already `npm` |
| Java | yes | already `maven` |
| **C# / .NET** | **yes** (xunit, Microsoft.NET.Test.Sdk, BenchmarkDotNet) | **added `nuget`** |
| **examples/node** | **yes** (`ws`) | **added `npm`** |
| **examples/go** | **yes** (`coder/websocket`) | **added `gomod`** |
| C (C ABI) | no | nothing to track |
| Go (binding) | no (empty go.mod) | nothing to track |
| R | only `Depends: R` | Dependabot has no R/CRAN ecosystem |

The published `Wickra.csproj` itself has no external NuGet packages (thin C-ABI
wrapper), so only the test and benchmark projects are tracked.
2026-06-12 23:23:08 +02:00
kingchenc f7f0bfbc48 release: bump 0.8.8 -> 0.8.9 (#285)
Maintenance release — supply-chain and CI housekeeping only. No library code or
public API changes.

### Security
- Triaged the pyo3 advisories RUSTSEC-2026-0176 / RUSTSEC-2026-0177 as not
  affecting Wickra (vulnerable APIs unreachable from the binding; fix blocked
  upstream by rust-numpy pinning pyo3 `^0.28`). Recorded in `deny.toml` and
  `osv-scanner.toml`.

### Changed
- Java binding: `central-publishing-maven-plugin` 0.5.0 → 0.10.0.
- CI GitHub Actions bumped to latest (checkout, setup-go, setup-java,
  codeql-action, taiki-e/install-action).
- Added a Maven ecosystem to Dependabot.

Version bumped across all manifests/lockfiles via `bump_version.py`; Cargo.lock
refreshed. CHANGELOG `[0.8.9]` filled. Tag/publish to follow on explicit GO.
2026-06-12 23:21:39 +02:00
kingchenc 8ccfd638b2 chore(supply-chain): osv-scanner suppressions, bump publishing plugin, track Maven in Dependabot (#272)
Follow-up to the Dependabot action-bump merges and the cargo-deny ignore (#271).
Three low-risk supply-chain housekeeping changes — config/docs only, no library
code, no runtime change.

## 1. `osv-scanner.toml` (new)

The OpenSSF Scorecard *Vulnerabilities* check runs OSV-Scanner over the repo and
was flagging five advisory IDs (`score is 5`). These reduce to three findings,
all assessed as not affecting Wickra:

| Advisory | Assessment |
|----------|------------|
| RUSTSEC-2026-0176 / GHSA-36hh-v3qg-5jq4 (pyo3) | Vulnerable API unused; fix is pyo3 0.29 but rust-numpy 0.28 pins pyo3 `^0.28` → upstream-blocked. Already in `deny.toml`. |
| RUSTSEC-2026-0177 / GHSA-chgr-c6px-7xpp (pyo3) | Same — `PyCFunction::new_closure` not called. Already in `deny.toml`. |
| GHSA-72hv-8253-57qq (jackson-core 3.x) | **Not a dependency of this project.** No manifest, Maven plugin, or the GitHub dependency-graph SBOM references `tools.jackson` 3.x; the only jackson present is `com.fasterxml.jackson.core:jackson-databind` 2.17.1. |

`osv-scanner.toml` records these as ignored-with-reason at the OSV layer,
mirroring `deny.toml` and the SECURITY.md VEX section. The Scorecard finding
also flip-flopped (fixed → reappeared) across unrelated release-bump commits,
confirming it is not a stable real exposure.

## 2. Bump `central-publishing-maven-plugin` 0.5.0 → 0.10.0

The Java binding pinned a publishing plugin five versions behind. Validated
locally with the JDK 22 toolchain (`mvn -Prelease validate`): the extension
loads, the existing `publishingServerId`/`autoPublish` config is compatible, and
all 14 binding tests pass. The actual `mvn deploy` upload path is only exercised
at release time (needs the Central token + GPG key), so it will be confirmed at
the next release.

## 3. Add a Maven ecosystem to Dependabot

The Java binding had no Dependabot coverage, which is why the stale 0.5.0 plugin
went unnoticed. Adds `package-ecosystem: maven` over `/bindings/java`,
`/bindings/java/benchmarks`, and `/examples/java` so plugin and dependency
updates (incl. the examples' jackson) are tracked going forward.
2026-06-12 23:15:56 +02:00
dependabot[bot] 452f270b4d deps(actions): bump github/codeql-action from 3.36.0 to 4.36.2 (#270)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.36.0 to 4.36.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/github/codeql-action/releases">github/codeql-action's releases</a>.</em></p>
<blockquote>
<h2>v4.36.2</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using exponential backoff when polling SARIF processing status. <a href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>. <a href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<h2>v4.36.1</h2>
<p>No user facing changes.</p>
<h2>v4.36.0</h2>
<ul>
<li><em>Breaking change</em>: Bump the minimum required CodeQL bundle version to 2.19.4. <a href="https://redirect.github.com/github/codeql-action/pull/3894">#3894</a></li>
<li>Add support for SHA-256 Git object IDs. <a href="https://redirect.github.com/github/codeql-action/pull/3893">#3893</a></li>
<li>Update default CodeQL bundle version to <a href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.5">2.25.5</a>. <a href="https://redirect.github.com/github/codeql-action/pull/3926">#3926</a></li>
</ul>
<h2>v4.35.5</h2>
<ul>
<li>We have improved how the JavaScript bundles for the CodeQL Action are generated to avoid duplication across bundles and reduce the size of the repository by around 70%. This should have no effect on the runtime behaviour of the CodeQL Action. <a href="https://redirect.github.com/github/codeql-action/pull/3899">#3899</a></li>
<li>For performance and accuracy reasons, <a href="https://redirect.github.com/github/roadmap/issues/1158">improved incremental analysis</a> will now only be enabled on a pull request when diff-informed analysis is also enabled for that run. If diff-informed analysis is unavailable (for example, because the PR diff ranges could not be computed), the action will fall back to a full analysis. <a href="https://redirect.github.com/github/codeql-action/pull/3791">#3791</a></li>
<li>If multiple inputs are provided for the GitHub-internal <code>analysis-kinds</code> input, only <code>code-scanning</code> will be enabled. The <code>analysis-kinds</code> input is experimental, for GitHub-internal use only, and may change without notice at any time. <a href="https://redirect.github.com/github/codeql-action/pull/3892">#3892</a></li>
<li>Added an experimental change which, when running a Code Scanning analysis for a PR with <a href="https://redirect.github.com/github/roadmap/issues/1158">improved incremental analysis</a> enabled, prefers CodeQL CLI versions that have a cached overlay-base database for the configured languages. This speeds up analysis for a repository when there is not yet a cached overlay-base database for the latest CLI version. We expect to roll this change out to everyone in May. <a href="https://redirect.github.com/github/codeql-action/pull/3880">#3880</a></li>
</ul>
<h2>v4.35.4</h2>
<ul>
<li>Update default CodeQL bundle version to <a href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.4">2.25.4</a>. <a href="https://redirect.github.com/github/codeql-action/pull/3881">#3881</a></li>
</ul>
<h2>v4.35.3</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for customers using CodeQL version 2.19.3 and earlier. These versions of CodeQL were discontinued on 9 April 2026 alongside GitHub Enterprise Server 3.15, and will be unsupported by the next minor release of the CodeQL Action. <a href="https://redirect.github.com/github/codeql-action/pull/3837">#3837</a></li>
<li>Configurations for private registries that use Cloudsmith or GCP OIDC are now accepted. <a href="https://redirect.github.com/github/codeql-action/pull/3850">#3850</a></li>
<li>Best-effort connection tests for private registries now use <code>GET</code> requests instead of <code>HEAD</code> for better compatibility with various registry implementations. For NuGet feeds, the test is now always performed against the service index. <a href="https://redirect.github.com/github/codeql-action/pull/3853">#3853</a></li>
<li>Fixed a bug where two diagnostics produced within the same millisecond could overwrite each other on disk, causing one of them to be lost. <a href="https://redirect.github.com/github/codeql-action/pull/3852">#3852</a></li>
<li>Update default CodeQL bundle version to <a href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.3">2.25.3</a>. <a href="https://redirect.github.com/github/codeql-action/pull/3865">#3865</a></li>
</ul>
<h2>v4.35.2</h2>
<ul>
<li>The undocumented TRAP cache cleanup feature that could be enabled using the <code>CODEQL_ACTION_CLEANUP_TRAP_CACHES</code> environment variable is deprecated and will be removed in May 2026. If you are affected by this, we recommend disabling TRAP caching by passing the <code>trap-caching: false</code> input to the <code>init</code> Action. <a href="https://redirect.github.com/github/codeql-action/pull/3795">#3795</a></li>
<li>The Git version 2.36.0 requirement for improved incremental analysis now only applies to repositories that contain submodules. <a href="https://redirect.github.com/github/codeql-action/pull/3789">#3789</a></li>
<li>Python analysis on GHES no longer extracts the standard library, relying instead on models of the standard library. This should result in significantly faster extraction and analysis times, while the effect on alerts should be minimal. <a href="https://redirect.github.com/github/codeql-action/pull/3794">#3794</a></li>
<li>Fixed a bug in the validation of OIDC configurations for private registries that was added in CodeQL Action 4.33.0 / 3.33.0. <a href="https://redirect.github.com/github/codeql-action/pull/3807">#3807</a></li>
<li>Update default CodeQL bundle version to <a href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.2">2.25.2</a>. <a href="https://redirect.github.com/github/codeql-action/pull/3823">#3823</a></li>
</ul>
<h2>v4.35.1</h2>
<ul>
<li>Fix incorrect minimum required Git version for <a href="https://redirect.github.com/github/roadmap/issues/1158">improved incremental analysis</a>: it should have been 2.36.0, not 2.11.0. <a href="https://redirect.github.com/github/codeql-action/pull/3781">#3781</a></li>
</ul>
<h2>v4.35.0</h2>
<ul>
<li>Reduced the minimum Git version required for <a href="https://redirect.github.com/github/roadmap/issues/1158">improved incremental analysis</a> from 2.38.0 to 2.11.0. <a href="https://redirect.github.com/github/codeql-action/pull/3767">#3767</a></li>
<li>Update default CodeQL bundle version to <a href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.1">2.25.1</a>. <a href="https://redirect.github.com/github/codeql-action/pull/3773">#3773</a></li>
</ul>
<h2>v4.34.1</h2>
<ul>
<li>Downgrade default CodeQL bundle version to <a href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.24.3">2.24.3</a> due to issues with a small percentage of Actions and JavaScript analyses. <a href="https://redirect.github.com/github/codeql-action/pull/3762">#3762</a></li>
</ul>
<h2>v4.34.0</h2>
<ul>
<li>Added an experimental change which disables TRAP caching when <a href="https://redirect.github.com/github/roadmap/issues/1158">improved incremental analysis</a> is enabled, since improved incremental analysis supersedes TRAP caching. This will improve performance and reduce Actions cache usage. We expect to roll this change out to everyone in March. <a href="https://redirect.github.com/github/codeql-action/pull/3569">#3569</a></li>
<li>We are rolling out improved incremental analysis to C/C++ analyses that use build mode <code>none</code>. We expect this rollout to be complete by the end of April 2026. <a href="https://redirect.github.com/github/codeql-action/pull/3584">#3584</a></li>
<li>Update default CodeQL bundle version to <a href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.0">2.25.0</a>. <a href="https://redirect.github.com/github/codeql-action/pull/3585">#3585</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action's changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a href="https://github.com/github/codeql-action/releases">releases page</a> for the relevant changes to the CodeQL CLI and language packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using exponential backoff when polling SARIF processing status. <a href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>. <a href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<h2>4.36.1 - 02 Jun 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.0 - 22 May 2026</h2>
<ul>
<li><em>Breaking change</em>: Bump the minimum required CodeQL bundle version to 2.19.4. <a href="https://redirect.github.com/github/codeql-action/pull/3894">#3894</a></li>
<li>Add support for SHA-256 Git object IDs. <a href="https://redirect.github.com/github/codeql-action/pull/3893">#3893</a></li>
<li>Update default CodeQL bundle version to <a href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.5">2.25.5</a>. <a href="https://redirect.github.com/github/codeql-action/pull/3926">#3926</a></li>
</ul>
<h2>4.35.5 - 15 May 2026</h2>
<ul>
<li>We have improved how the JavaScript bundles for the CodeQL Action are generated to avoid duplication across bundles and reduce the size of the repository by around 70%. This should have no effect on the runtime behaviour of the CodeQL Action. <a href="https://redirect.github.com/github/codeql-action/pull/3899">#3899</a></li>
<li>For performance and accuracy reasons, <a href="https://redirect.github.com/github/roadmap/issues/1158">improved incremental analysis</a> will now only be enabled on a pull request when diff-informed analysis is also enabled for that run. If diff-informed analysis is unavailable (for example, because the PR diff ranges could not be computed), the action will fall back to a full analysis. <a href="https://redirect.github.com/github/codeql-action/pull/3791">#3791</a></li>
<li>If multiple inputs are provided for the GitHub-internal <code>analysis-kinds</code> input, only <code>code-scanning</code> will be enabled. The <code>analysis-kinds</code> input is experimental, for GitHub-internal use only, and may change without notice at any time. <a href="https://redirect.github.com/github/codeql-action/pull/3892">#3892</a></li>
<li>Added an experimental change which, when running a Code Scanning analysis for a PR with <a href="https://redirect.github.com/github/roadmap/issues/1158">improved incremental analysis</a> enabled, prefers CodeQL CLI versions that have a cached overlay-base database for the configured languages. This speeds up analysis for a repository when there is not yet a cached overlay-base database for the latest CLI version. We expect to roll this change out to everyone in May. <a href="https://redirect.github.com/github/codeql-action/pull/3880">#3880</a></li>
</ul>
<h2>4.35.4 - 07 May 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.4">2.25.4</a>. <a href="https://redirect.github.com/github/codeql-action/pull/3881">#3881</a></li>
</ul>
<h2>4.35.3 - 01 May 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for customers using CodeQL version 2.19.3 and earlier. These versions of CodeQL were discontinued on 9 April 2026 alongside GitHub Enterprise Server 3.15, and will be unsupported by the next minor release of the CodeQL Action. <a href="https://redirect.github.com/github/codeql-action/pull/3837">#3837</a></li>
<li>Configurations for private registries that use Cloudsmith or GCP OIDC are now accepted. <a href="https://redirect.github.com/github/codeql-action/pull/3850">#3850</a></li>
<li>Best-effort connection tests for private registries now use <code>GET</code> requests instead of <code>HEAD</code> for better compatibility with various registry implementations. For NuGet feeds, the test is now always performed against the service index. <a href="https://redirect.github.com/github/codeql-action/pull/3853">#3853</a></li>
<li>Fixed a bug where two diagnostics produced within the same millisecond could overwrite each other on disk, causing one of them to be lost. <a href="https://redirect.github.com/github/codeql-action/pull/3852">#3852</a></li>
<li>Update default CodeQL bundle version to <a href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.3">2.25.3</a>. <a href="https://redirect.github.com/github/codeql-action/pull/3865">#3865</a></li>
</ul>
<h2>4.35.2 - 15 Apr 2026</h2>
<ul>
<li>The undocumented TRAP cache cleanup feature that could be enabled using the <code>CODEQL_ACTION_CLEANUP_TRAP_CACHES</code> environment variable is deprecated and will be removed in May 2026. If you are affected by this, we recommend disabling TRAP caching by passing the <code>trap-caching: false</code> input to the <code>init</code> Action. <a href="https://redirect.github.com/github/codeql-action/pull/3795">#3795</a></li>
<li>The Git version 2.36.0 requirement for improved incremental analysis now only applies to repositories that contain submodules. <a href="https://redirect.github.com/github/codeql-action/pull/3789">#3789</a></li>
<li>Python analysis on GHES no longer extracts the standard library, relying instead on models of the standard library. This should result in significantly faster extraction and analysis times, while the effect on alerts should be minimal. <a href="https://redirect.github.com/github/codeql-action/pull/3794">#3794</a></li>
<li>Fixed a bug in the validation of OIDC configurations for private registries that was added in CodeQL Action 4.33.0 / 3.33.0. <a href="https://redirect.github.com/github/codeql-action/pull/3807">#3807</a></li>
<li>Update default CodeQL bundle version to <a href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.2">2.25.2</a>. <a href="https://redirect.github.com/github/codeql-action/pull/3823">#3823</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/github/codeql-action/commit/8aad20d150bbac5944a9f9d289da16a4b0d87c1e"><code>8aad20d</code></a> Merge pull request <a href="https://redirect.github.com/github/codeql-action/issues/3949">#3949</a> from github/update-v4.36.2-dcb947ce1</li>
<li><a href="https://github.com/github/codeql-action/commit/f521b08cd8f468ab193ea950a589cb2e9c869c6a"><code>f521b08</code></a> Add additional changelog notes</li>
<li><a href="https://github.com/github/codeql-action/commit/8aeff0ffb7b78582ee0d0e6eebb8140684400d08"><code>8aeff0f</code></a> Update changelog for v4.36.2</li>
<li><a href="https://github.com/github/codeql-action/commit/dcb947ce15976d40ea82935510b2db4872ec124c"><code>dcb947c</code></a> Merge pull request <a href="https://redirect.github.com/github/codeql-action/issues/3948">#3948</a> from github/update-bundle/codeql-bundle-v2.25.6</li>
<li><a href="https://github.com/github/codeql-action/commit/c251bcefa178f7780f62f150002acffe3d07fde9"><code>c251bce</code></a> Add changelog note</li>
<li><a href="https://github.com/github/codeql-action/commit/62953c18b35f59e28351d2f1e806925aef8b1e3c"><code>62953c1</code></a> Update default bundle to codeql-bundle-v2.25.6</li>
<li><a href="https://github.com/github/codeql-action/commit/423b570baf1976cd7a3daeba5d6e9f9b76432f37"><code>423b570</code></a> Merge pull request <a href="https://redirect.github.com/github/codeql-action/issues/3946">#3946</a> from github/dependabot/npm_and_yarn/npm-minor-5d507a...</li>
<li><a href="https://github.com/github/codeql-action/commit/c35d1b164463ee62a100735382aaaa525c5d3496"><code>c35d1b1</code></a> Merge pull request <a href="https://redirect.github.com/github/codeql-action/issues/3947">#3947</a> from github/dependabot/github_actions/dot-github/wor...</li>
<li><a href="https://github.com/github/codeql-action/commit/cb1a588b02755b176e7b9d033ed4b69312f0e1bd"><code>cb1a588</code></a> Merge pull request <a href="https://redirect.github.com/github/codeql-action/issues/3937">#3937</a> from github/robertbrignull/waitForProcessing_backoff</li>
<li><a href="https://github.com/github/codeql-action/commit/ba47406412c54532b5b4fcfbaf877c9e2382b206"><code>ba47406</code></a> Merge pull request <a href="https://redirect.github.com/github/codeql-action/issues/3943">#3943</a> from github/henrymercer/cache-cli-version-info</li>
<li>Additional commits viewable in <a href="https://github.com/github/codeql-action/compare/03e4368ac7daa2bd82b3e85262f3bf87ee112f57...8aad20d150bbac5944a9f9d289da16a4b0d87c1e">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action&package-manager=github_actions&previous-version=3.36.0&new-version=4.36.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2026-06-12 22:49:45 +02:00
dependabot[bot] baffebd3da deps(actions): bump actions/checkout from 6.0.2 to 6.0.3 (#269)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.2 to 6.0.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/actions/checkout/releases">actions/checkout's releases</a>.</em></p>
<blockquote>
<h2>v6.0.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Update changelog by <a href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2357">actions/checkout#2357</a></li>
<li>fix: expand merge commit SHA regex and add SHA-256 test cases by <a href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li>
<li>Fix checkout init for SHA-256 repositories by <a href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2439">actions/checkout#2439</a></li>
<li>Update changelog for v6.0.3 by <a href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2446">actions/checkout#2446</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/yaananth"><code>@​yaananth</code></a> made their first contribution in <a href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/checkout/compare/v6...v6.0.3">https://github.com/actions/checkout/compare/v6...v6.0.3</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/actions/checkout/blob/main/CHANGELOG.md">actions/checkout's changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>v6.0.3</h2>
<ul>
<li>Fix checkout init for SHA-256 repositories by <a href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2439">actions/checkout#2439</a></li>
<li>fix: expand merge commit SHA regex and add SHA-256 test cases by <a href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li>
</ul>
<h2>v6.0.2</h2>
<ul>
<li>Fix tag handling: preserve annotations and explicit fetch-tags by <a href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2356">actions/checkout#2356</a></li>
</ul>
<h2>v6.0.1</h2>
<ul>
<li>Add worktree support for persist-credentials includeIf by <a href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2327">actions/checkout#2327</a></li>
</ul>
<h2>v6.0.0</h2>
<ul>
<li>Persist creds to a separate file by <a href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2286">actions/checkout#2286</a></li>
<li>Update README to include Node.js 24 support details and requirements by <a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2248">actions/checkout#2248</a></li>
</ul>
<h2>v5.0.1</h2>
<ul>
<li>Port v6 cleanup to v5 by <a href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2301">actions/checkout#2301</a></li>
</ul>
<h2>v5.0.0</h2>
<ul>
<li>Update actions checkout to use node 24 by <a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2226">actions/checkout#2226</a></li>
</ul>
<h2>v4.3.1</h2>
<ul>
<li>Port v6 cleanup to v4 by <a href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2305">actions/checkout#2305</a></li>
</ul>
<h2>v4.3.0</h2>
<ul>
<li>docs: update README.md by <a href="https://github.com/motss"><code>@​motss</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1971">actions/checkout#1971</a></li>
<li>Add internal repos for checking out multiple repositories by <a href="https://github.com/mouismail"><code>@​mouismail</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1977">actions/checkout#1977</a></li>
<li>Documentation update - add recommended permissions to Readme by <a href="https://github.com/benwells"><code>@​benwells</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2043">actions/checkout#2043</a></li>
<li>Adjust positioning of user email note and permissions heading by <a href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2044">actions/checkout#2044</a></li>
<li>Update README.md by <a href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2194">actions/checkout#2194</a></li>
<li>Update CODEOWNERS for actions by <a href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2224">actions/checkout#2224</a></li>
<li>Update package dependencies by <a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/2236">actions/checkout#2236</a></li>
</ul>
<h2>v4.2.2</h2>
<ul>
<li><code>url-helper.ts</code> now leverages well-known environment variables by <a href="https://github.com/jww3"><code>@​jww3</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1941">actions/checkout#1941</a></li>
<li>Expand unit test coverage for <code>isGhes</code> by <a href="https://github.com/jww3"><code>@​jww3</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1946">actions/checkout#1946</a></li>
</ul>
<h2>v4.2.1</h2>
<ul>
<li>Check out other refs/* by commit if provided, fall back to ref by <a href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1924">actions/checkout#1924</a></li>
</ul>
<h2>v4.2.0</h2>
<ul>
<li>Add Ref and Commit outputs by <a href="https://github.com/lucacome"><code>@​lucacome</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1180">actions/checkout#1180</a></li>
<li>Dependency updates by <a href="https://github.com/dependabot"><code>@​dependabot</code></a>- <a href="https://redirect.github.com/actions/checkout/pull/1777">actions/checkout#1777</a>, <a href="https://redirect.github.com/actions/checkout/pull/1872">actions/checkout#1872</a></li>
</ul>
<h2>v4.1.7</h2>
<ul>
<li>Bump the minor-npm-dependencies group across 1 directory with 4 updates by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1739">actions/checkout#1739</a></li>
<li>Bump actions/checkout from 3 to 4 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1697">actions/checkout#1697</a></li>
<li>Check out other refs/* by commit by <a href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a href="https://redirect.github.com/actions/checkout/pull/1774">actions/checkout#1774</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/actions/checkout/commit/df4cb1c069e1874edd31b4311f1884172cec0e10"><code>df4cb1c</code></a> Update changelog for v6.0.3 (<a href="https://redirect.github.com/actions/checkout/issues/2446">#2446</a>)</li>
<li><a href="https://github.com/actions/checkout/commit/1cce3390c2bfda521930d01229c073c7ff920824"><code>1cce339</code></a> Fix checkout init for SHA-256 repositories (<a href="https://redirect.github.com/actions/checkout/issues/2439">#2439</a>)</li>
<li><a href="https://github.com/actions/checkout/commit/900f2210b1d28bbbd0bd22d17926b9e224e8f231"><code>900f221</code></a> fix: expand merge commit SHA regex and add SHA-256 test cases (<a href="https://redirect.github.com/actions/checkout/issues/2414">#2414</a>)</li>
<li><a href="https://github.com/actions/checkout/commit/0c366fd6a839edf440554fa01a7085ccba70ac98"><code>0c366fd</code></a> Update changelog (<a href="https://redirect.github.com/actions/checkout/issues/2357">#2357</a>)</li>
<li>See full diff in <a href="https://github.com/actions/checkout/compare/de0fac2e4500dabe0009e67214ff5f5447ce83dd...df4cb1c069e1874edd31b4311f1884172cec0e10">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=6.0.2&new-version=6.0.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2026-06-12 22:36:18 +02:00
dependabot[bot] e631e55195 deps(actions): bump actions/setup-java from 4.8.0 to 5.2.0 (#268)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4.8.0 to 5.2.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/actions/setup-java/releases">actions/setup-java's releases</a>.</em></p>
<blockquote>
<h2>v5.2.0</h2>
<h2>What's Changed</h2>
<h3>Enhancement</h3>
<ul>
<li>Retry on HTTP 522 Connection timed out by <a href="https://github.com/findepi"><code>@​findepi</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/964">actions/setup-java#964</a></li>
</ul>
<h3>Documentation Changes</h3>
<ul>
<li>Update gradle caching by <a href="https://github.com/priya-kinthali"><code>@​priya-kinthali</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/972">actions/setup-java#972</a></li>
<li>Update checkout to v6 by <a href="https://github.com/mahabaleshwars"><code>@​mahabaleshwars</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/973">actions/setup-java#973</a></li>
</ul>
<h3>Dependency Updates</h3>
<ul>
<li>Upgrade <code>@​actions/cache</code> to v5 by <a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/968">actions/setup-java#968</a></li>
<li>Upgrade actions/checkout from 5 to 6 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/961">actions/setup-java#961</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/findepi"><code>@​findepi</code></a> made their first contribution in <a href="https://redirect.github.com/actions/setup-java/pull/964">actions/setup-java#964</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/setup-java/compare/v5...v5.2.0">https://github.com/actions/setup-java/compare/v5...v5.2.0</a></p>
<h2>v5.1.0</h2>
<h2>What's Changed</h2>
<h3>New Features</h3>
<ul>
<li>Add support for <code>.sdkmanrc</code> file in <code>java-version-file</code> parameter by <a href="https://github.com/guicamest"><code>@​guicamest</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/736">actions/setup-java#736</a></li>
<li>Add support for Microsoft OpenJDK 25 builds by <a href="https://github.com/the-mod"><code>@​the-mod</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/927">actions/setup-java#927</a></li>
</ul>
<h3>Bug Fixes &amp; Improvements</h3>
<ul>
<li>Update Regex to Support All ASDF Versions for the supported distributions in tool-versions File by <a href="https://github.com/aparnajyothi-y"><code>@​aparnajyothi-y</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/767">actions/setup-java#767</a></li>
<li>Enhance error logging for network failures to include endpoint/IP details, add retry mechanism and update workflows to use macos-15-intel by <a href="https://github.com/priya-kinthali"><code>@​priya-kinthali</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/946">actions/setup-java#946</a></li>
<li>Update SapMachine URLs by <a href="https://github.com/RealCLanger"><code>@​RealCLanger</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/955">actions/setup-java#955</a></li>
<li>Add GitHub Token Support for GraalVM and Refactor Code by <a href="https://github.com/mahabaleshwars"><code>@​mahabaleshwars</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/849">actions/setup-java#849</a></li>
</ul>
<h3>Documentation changes</h3>
<ul>
<li>Update documentation to use checkout and Java v5 by <a href="https://github.com/lmvysakh"><code>@​lmvysakh</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/903">actions/setup-java#903</a></li>
<li>Clarify JAVA_HOME and PATH setup in README by <a href="https://github.com/chiranjib-swain"><code>@​chiranjib-swain</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/841">actions/setup-java#841</a></li>
</ul>
<h3>Dependency updates</h3>
<ul>
<li>Upgrade prettier from 2.8.8 to 3.6.2 and document breaking changes in v5 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/873">actions/setup-java#873</a></li>
<li>Upgrade actions/publish-action from 0.3.0 to 0.4.0  by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/actions/setup-java/pull/912">actions/setup-java#912</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/lmvysakh"><code>@​lmvysakh</code></a> made their first contribution in <a href="https://redirect.github.com/actions/setup-java/pull/903">actions/setup-java#903</a></li>
<li><a href="https://github.com/chiranjib-swain"><code>@​chiranjib-swain</code></a> made their first contribution in <a href="https://redirect.github.com/actions/setup-java/pull/841">actions/setup-java#841</a></li>
<li><a href="https://github.com/the-mod"><code>@​the-mod</code></a> made their first contribution in <a href="https://redirect.github.com/actions/setup-java/pull/927">actions/setup-java#927</a></li>
<li><a href="https://github.com/priya-kinthali"><code>@​priya-kinthali</code></a> made their first contribution in <a href="https://redirect.github.com/actions/setup-java/pull/946">actions/setup-java#946</a></li>
<li><a href="https://github.com/guicamest"><code>@​guicamest</code></a> made their first contribution in <a href="https://redirect.github.com/actions/setup-java/pull/736">actions/setup-java#736</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/setup-java/compare/v5...v5.1.0">https://github.com/actions/setup-java/compare/v5...v5.1.0</a></p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/actions/setup-java/commit/be666c2fcd27ec809703dec50e508c2fdc7f6654"><code>be666c2</code></a> Chore: Version Update and Checkout Update to v6 (<a href="https://redirect.github.com/actions/setup-java/issues/973">#973</a>)</li>
<li><a href="https://github.com/actions/setup-java/commit/f7a6fefba97e80156950e16f2a9dafc8579b7d05"><code>f7a6fef</code></a> Bump actions/checkout from 5 to 6 (<a href="https://redirect.github.com/actions/setup-java/issues/961">#961</a>)</li>
<li><a href="https://github.com/actions/setup-java/commit/d81c4e45f3ac973cc936d79104023e20054ba578"><code>d81c4e4</code></a> Upgrade <code>@​actions/cache</code> to v5 (<a href="https://redirect.github.com/actions/setup-java/issues/968">#968</a>)</li>
<li><a href="https://github.com/actions/setup-java/commit/1b1bbe1085cb6ab21b5b19b7bebc091a9430026a"><code>1b1bbe1</code></a> readme update (<a href="https://redirect.github.com/actions/setup-java/issues/972">#972</a>)</li>
<li><a href="https://github.com/actions/setup-java/commit/5d7b2146334bacf88728daaa70414a99f5164e0f"><code>5d7b214</code></a> Retry on HTTP 522 Connection timed out (<a href="https://redirect.github.com/actions/setup-java/issues/964">#964</a>)</li>
<li><a href="https://github.com/actions/setup-java/commit/f2beeb24e141e01a676f977032f5a29d81c9e27e"><code>f2beeb2</code></a> Bump actions/publish-action from 0.3.0 to 0.4.0 (<a href="https://redirect.github.com/actions/setup-java/issues/912">#912</a>)</li>
<li><a href="https://github.com/actions/setup-java/commit/4e7e684fbb6e33f88ecb2cf1e6b3797739cf499b"><code>4e7e684</code></a> feat: Add support for <code>.sdkmanrc</code> file in <code>java-version-file</code> parameter (<a href="https://redirect.github.com/actions/setup-java/issues/736">#736</a>)</li>
<li><a href="https://github.com/actions/setup-java/commit/46c56d6f92c88cf540acf95a12a4a41197499222"><code>46c56d6</code></a> Add GitHub Token Support for GraalVM and Refactor Code (<a href="https://redirect.github.com/actions/setup-java/issues/849">#849</a>)</li>
<li><a href="https://github.com/actions/setup-java/commit/66b945764b75604b3cfd644c3ada5232cf6c90c6"><code>66b9457</code></a> Update SapMachine URLs (<a href="https://redirect.github.com/actions/setup-java/issues/955">#955</a>)</li>
<li><a href="https://github.com/actions/setup-java/commit/6ba5449b7dcda52941806a19f0cf626b6420191e"><code>6ba5449</code></a> Enhance error logging for network failures to include endpoint/IP details, ad...</li>
<li>Additional commits viewable in <a href="https://github.com/actions/setup-java/compare/c1e323688fd81a25caa38c78aa6df2d33d3e20d9...be666c2fcd27ec809703dec50e508c2fdc7f6654">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/setup-java&package-manager=github_actions&previous-version=4.8.0&new-version=5.2.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2026-06-12 22:21:13 +02:00
dependabot[bot] 63949f6fc8 deps(actions): bump actions/setup-go from 5.6.0 to 6.4.0 (#267)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5.6.0 to 6.4.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/actions/setup-go/releases">actions/setup-go's releases</a>.</em></p>
<blockquote>
<h2>v6.4.0</h2>
<h2>What's Changed</h2>
<h3>Enhancement</h3>
<ul>
<li>Add go-download-base-url input for custom Go distributions by <a href="https://github.com/gdams"><code>@​gdams</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/721">actions/setup-go#721</a></li>
</ul>
<h3>Dependency update</h3>
<ul>
<li>Upgrade minimatch from 3.1.2 to 3.1.5 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/727">actions/setup-go#727</a></li>
</ul>
<h3>Documentation update</h3>
<ul>
<li>Rearrange README.md, add advanced-usage.md by <a href="https://github.com/priyagupta108"><code>@​priyagupta108</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/724">actions/setup-go#724</a></li>
<li>Fix Microsoft build of Go link by <a href="https://github.com/gdams"><code>@​gdams</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/734">actions/setup-go#734</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/gdams"><code>@​gdams</code></a> made their first contribution in <a href="https://redirect.github.com/actions/setup-go/pull/721">actions/setup-go#721</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/setup-go/compare/v6...v6.4.0">https://github.com/actions/setup-go/compare/v6...v6.4.0</a></p>
<h2>v6.3.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update default Go module caching to use go.mod by <a href="https://github.com/priyagupta108"><code>@​priyagupta108</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/705">actions/setup-go#705</a></li>
<li>Fix golang download url to go.dev by <a href="https://github.com/178inaba"><code>@​178inaba</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/469">actions/setup-go#469</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/setup-go/compare/v6...v6.3.0">https://github.com/actions/setup-go/compare/v6...v6.3.0</a></p>
<h2>v6.2.0</h2>
<h2>What's Changed</h2>
<h3>Enhancements</h3>
<ul>
<li>Example for restore-only cache in documentation  by <a href="https://github.com/aparnajyothi-y"><code>@​aparnajyothi-y</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/696">actions/setup-go#696</a></li>
<li>Update Node.js version in action.yml by <a href="https://github.com/ccoVeille"><code>@​ccoVeille</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/691">actions/setup-go#691</a></li>
<li>Documentation update of actions/checkout by <a href="https://github.com/deining"><code>@​deining</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/683">actions/setup-go#683</a></li>
</ul>
<h3>Dependency updates</h3>
<ul>
<li>Upgrade js-yaml from 3.14.1 to 3.14.2 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/682">actions/setup-go#682</a></li>
<li>Upgrade <code>@​actions/cache</code> to v5 by <a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/695">actions/setup-go#695</a></li>
<li>Upgrade actions/checkout from 5 to 6 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/686">actions/setup-go#686</a></li>
<li>Upgrade qs from 6.14.0 to 6.14.1 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/703">actions/setup-go#703</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/ccoVeille"><code>@​ccoVeille</code></a> made their first contribution in <a href="https://redirect.github.com/actions/setup-go/pull/691">actions/setup-go#691</a></li>
<li><a href="https://github.com/deining"><code>@​deining</code></a> made their first contribution in <a href="https://redirect.github.com/actions/setup-go/pull/683">actions/setup-go#683</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/setup-go/compare/v6...v6.2.0">https://github.com/actions/setup-go/compare/v6...v6.2.0</a></p>
<h2>v6.1.0</h2>
<h2>What's Changed</h2>
<h3>Enhancements</h3>
<ul>
<li>Fall back to downloading from go.dev/dl instead of storage.googleapis.com/golang by <a href="https://github.com/nicholasngai"><code>@​nicholasngai</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/665">actions/setup-go#665</a></li>
<li>Add support for .tool-versions file and update workflow by <a href="https://github.com/priya-kinthali"><code>@​priya-kinthali</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/673">actions/setup-go#673</a></li>
<li>Add comprehensive breaking changes documentation for v6 by <a href="https://github.com/mahabaleshwars"><code>@​mahabaleshwars</code></a> in <a href="https://redirect.github.com/actions/setup-go/pull/674">actions/setup-go#674</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/actions/setup-go/commit/4a3601121dd01d1626a1e23e37211e3254c1c06c"><code>4a36011</code></a> docs: fix Microsoft build of Go link (<a href="https://redirect.github.com/actions/setup-go/issues/734">#734</a>)</li>
<li><a href="https://github.com/actions/setup-go/commit/8f19afcc704763637be6b1718da0af52ca05785d"><code>8f19afc</code></a> feat: add go-download-base-url input for custom Go distributions (<a href="https://redirect.github.com/actions/setup-go/issues/721">#721</a>)</li>
<li><a href="https://github.com/actions/setup-go/commit/27fdb267c15a8835f1ead03dfa07f89be2bb741a"><code>27fdb26</code></a> Bump minimatch from 3.1.2 to 3.1.5 (<a href="https://redirect.github.com/actions/setup-go/issues/727">#727</a>)</li>
<li><a href="https://github.com/actions/setup-go/commit/def8c394e3ad351a79bc93815e4a585520fe993b"><code>def8c39</code></a> Rearrange README.md, add advanced-usage.md (<a href="https://redirect.github.com/actions/setup-go/issues/724">#724</a>)</li>
<li><a href="https://github.com/actions/setup-go/commit/4b73464bb391d4059bd26b0524d20df3927bd417"><code>4b73464</code></a> Fix golang download url to go.dev (<a href="https://redirect.github.com/actions/setup-go/issues/469">#469</a>)</li>
<li><a href="https://github.com/actions/setup-go/commit/a5f9b05d2d216f63e13859e0d847461041025775"><code>a5f9b05</code></a> Update default Go module caching to use go.mod (<a href="https://redirect.github.com/actions/setup-go/issues/705">#705</a>)</li>
<li><a href="https://github.com/actions/setup-go/commit/7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5"><code>7a3fe6c</code></a> Bump qs from 6.14.0 to 6.14.1 (<a href="https://redirect.github.com/actions/setup-go/issues/703">#703</a>)</li>
<li><a href="https://github.com/actions/setup-go/commit/b9adafd441833a027479ddd0db37eaece68d35cb"><code>b9adafd</code></a> Bump actions/checkout from 5 to 6 (<a href="https://redirect.github.com/actions/setup-go/issues/686">#686</a>)</li>
<li><a href="https://github.com/actions/setup-go/commit/d73f6bcfc2b419b74f47075f8a487b40cc4680f8"><code>d73f6bc</code></a> README.md: correct to actions/checkout@v6 (<a href="https://redirect.github.com/actions/setup-go/issues/683">#683</a>)</li>
<li><a href="https://github.com/actions/setup-go/commit/ae252ee6fb24babc50e89fc67c4aa608e69fbf8f"><code>ae252ee</code></a> Bump <code>@​actions/cache</code> to v5 (<a href="https://redirect.github.com/actions/setup-go/issues/695">#695</a>)</li>
<li>Additional commits viewable in <a href="https://github.com/actions/setup-go/compare/40f1582b2485089dde7abd97c1529aa768e1baff...4a3601121dd01d1626a1e23e37211e3254c1c06c">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/setup-go&package-manager=github_actions&previous-version=5.6.0&new-version=6.4.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2026-06-12 22:08:01 +02:00
dependabot[bot] 92210eb7b8 deps(actions): bump taiki-e/install-action from 2.79.15 to 2.81.6 (#266)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.79.15 to 2.81.6.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/taiki-e/install-action/releases">taiki-e/install-action's releases</a>.</em></p>
<blockquote>
<h2>2.81.6</h2>
<ul>
<li>
<p>Update <code>prek@latest</code> to 0.4.4.</p>
</li>
<li>
<p>Update <code>cargo-shear@latest</code> to 1.13.0.</p>
</li>
</ul>
<h2>2.81.5</h2>
<ul>
<li>
<p>Update <code>vacuum@latest</code> to 0.29.0.</p>
</li>
<li>
<p>Update <code>uv@latest</code> to 0.11.19.</p>
</li>
<li>
<p>Update <code>typos@latest</code> to 1.47.2.</p>
</li>
<li>
<p>Update <code>mise@latest</code> to 2026.6.0.</p>
</li>
</ul>
<h2>2.81.4</h2>
<ul>
<li>
<p>Update <code>vacuum@latest</code> to 0.28.4.</p>
</li>
<li>
<p>Update <code>typos@latest</code> to 1.47.1.</p>
</li>
<li>
<p>Update <code>syft@latest</code> to 1.45.0.</p>
</li>
<li>
<p>Update <code>cargo-neat@latest</code> to 0.4.0.</p>
</li>
<li>
<p>Update <code>cargo-mutants@latest</code> to 27.1.0.</p>
</li>
</ul>
<h2>2.81.3</h2>
<ul>
<li>
<p>Update <code>vacuum@latest</code> to 0.28.3.</p>
</li>
<li>
<p>Update <code>uv@latest</code> to 0.11.18.</p>
</li>
<li>
<p>Update <code>trivy@latest</code> to 0.71.0.</p>
</li>
</ul>
<h2>2.81.2</h2>
<ul>
<li>
<p>Update <code>mise@latest</code> to 2026.5.18.</p>
</li>
<li>
<p>Update <code>cargo-semver-checks@latest</code> to 0.48.0.</p>
</li>
</ul>
<h2>2.81.1</h2>
<ul>
<li>
<p>Update <code>cargo-no-dev-deps@latest</code> to 0.2.24.</p>
</li>
<li>
<p>Update <code>cargo-hack@latest</code> to 0.6.45.</p>
</li>
</ul>
<h2>2.81.0</h2>
<ul>
<li>
<p>Support <code>convco</code>. (<a href="https://redirect.github.com/taiki-e/install-action/pull/1831">#1831</a>, thanks <a href="https://github.com/graelo"><code>@​graelo</code></a>)</p>
</li>
<li>
<p>Support <code>docgarden</code> (<a href="https://redirect.github.com/taiki-e/install-action/pull/1830">#1830</a>, thanks <a href="https://github.com/jesse-black"><code>@​jesse-black</code></a>)</p>
</li>
<li>
<p>Update <code>vacuum@latest</code> to 0.28.0.</p>
</li>
<li>
<p>Update <code>cargo-binstall@latest</code> to 1.19.1.</p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md">taiki-e/install-action's changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<p>All notable changes to this project will be documented in this file.</p>
<p>This project adheres to <a href="https://semver.org">Semantic Versioning</a>.</p>
<!-- raw HTML omitted -->
<h2>[Unreleased]</h2>
<h2>[2.81.10] - 2026-06-11</h2>
<ul>
<li>
<p>Update <code>tombi@latest</code> to 1.1.3.</p>
</li>
<li>
<p>Update <code>release-plz@latest</code> to 0.3.159.</p>
</li>
<li>
<p>Update <code>cosign@latest</code> to 3.1.1.</p>
</li>
</ul>
<h2>[2.81.9] - 2026-06-10</h2>
<ul>
<li>
<p>Update <code>wasm-bindgen@latest</code> to 0.2.123.</p>
</li>
<li>
<p>Update <code>tombi@latest</code> to 1.1.2.</p>
</li>
<li>
<p>Update <code>parse-changelog@latest</code> to 0.6.17.</p>
</li>
<li>
<p>Update <code>just@latest</code> to 1.52.0.</p>
</li>
<li>
<p>Update <code>gungraun-runner@latest</code> to 0.19.2.</p>
</li>
<li>
<p>Update <code>cargo-binstall@latest</code> to 1.20.0.</p>
</li>
</ul>
<h2>[2.81.8] - 2026-06-08</h2>
<ul>
<li>
<p>Update <code>vacuum@latest</code> to 0.29.2.</p>
</li>
<li>
<p>Update <code>parse-dockerfile@latest</code> to 0.1.7.</p>
</li>
<li>
<p>Update <code>mise@latest</code> to 2026.6.1.</p>
</li>
<li>
<p>Update <code>cargo-shear@latest</code> to 1.13.1.</p>
</li>
</ul>
<h2>[2.81.7] - 2026-06-06</h2>
<ul>
<li>
<p>Update <code>wasmtime@latest</code> to 45.0.1.</p>
</li>
<li>
<p>Update <code>vacuum@latest</code> to 0.29.1.</p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/taiki-e/install-action/commit/59012be0884e296ca2da49b530610e72c49039ad"><code>59012be</code></a> Release 2.81.6</li>
<li><a href="https://github.com/taiki-e/install-action/commit/52d2b0721d1d96983ea68305ea47cf412c2cb542"><code>52d2b07</code></a> Update <code>prek@latest</code> to 0.4.4</li>
<li><a href="https://github.com/taiki-e/install-action/commit/eeedd6ad125e98661ca2977b004703515f466511"><code>eeedd6a</code></a> Update <code>cargo-shear@latest</code> to 1.13.0</li>
<li><a href="https://github.com/taiki-e/install-action/commit/f23697a9b80ca29d330c6f9063d9df132e8be880"><code>f23697a</code></a> Update cargo-audit manifest</li>
<li><a href="https://github.com/taiki-e/install-action/commit/4bc351f7f2614e48088386e2a0ad917ca3a7e4ba"><code>4bc351f</code></a> Release 2.81.5</li>
<li><a href="https://github.com/taiki-e/install-action/commit/ee19c896b554e8b0244999a8559d4eaecf29da0c"><code>ee19c89</code></a> Update <code>vacuum@latest</code> to 0.29.0</li>
<li><a href="https://github.com/taiki-e/install-action/commit/026a7c19d8cde560882d2f6a1057690e982d4d70"><code>026a7c1</code></a> Update <code>uv@latest</code> to 0.11.19</li>
<li><a href="https://github.com/taiki-e/install-action/commit/73ae273667d96854ff27b31a096a2eff0076704b"><code>73ae273</code></a> Update <code>typos@latest</code> to 1.47.2</li>
<li><a href="https://github.com/taiki-e/install-action/commit/afcf03672edd439d42ab0d5d07c5254be5b35577"><code>afcf036</code></a> Update <code>mise@latest</code> to 2026.6.0</li>
<li><a href="https://github.com/taiki-e/install-action/commit/cde8c9e634f4a17bc06b61413ac0ef75450eac46"><code>cde8c9e</code></a> Release 2.81.4</li>
<li>Additional commits viewable in <a href="https://github.com/taiki-e/install-action/compare/0fd46367812ee04360509b4169d9f659d6892bb2...59012be0884e296ca2da49b530610e72c49039ad">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=taiki-e/install-action&package-manager=github_actions&previous-version=2.79.15&new-version=2.81.6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>
2026-06-12 21:54:06 +02:00
kingchenc c3029f2548 ci(supply-chain): ignore RUSTSEC-2026-0176/0177 until rust-numpy 0.29 (#271)
## Why

`Supply-chain (cargo-deny)` started failing on every CI run from 2026-06-11
onward, including all five open Dependabot action-bump PRs (#266–#270), which
touch no Rust code. The cause is two PyO3 advisories published 2026-06-11:

| Advisory | Issue | Affected | Patched |
|----------|-------|----------|---------|
| RUSTSEC-2026-0176 | OOB read in `PyList`/`PyTuple` `nth`/`nth_back` | `>=0.24.0, <0.29.0` | `>= 0.29.0` |
| RUSTSEC-2026-0177 | Missing `Sync` bound on `PyCFunction::new_closure` | `>=0.15.0, <0.29.0` | `>= 0.29.0` |

We are on pyo3 0.28.3, so both apply.

## Why not just bump pyo3 to 0.29

The clean fix is blocked upstream: `rust-numpy` 0.28 (its latest release) hard-pins
`pyo3 ^0.28.0`, so the resolver rejects 0.29 (`failed to select a version for the
requirement pyo3 = "^0.28.0"`). rust-numpy's "Updated to PyO3 version 0.29.0" PR is
open but not yet published to crates.io.

## Why the ignore is safe

Neither vulnerable code path is reachable from our binding — verified by grep over
`bindings/python/src`: no `BoundListIterator::nth`/`nth_back` or `PyTuple`
equivalents (0176), no `PyCFunction::new_closure` (0177), zero `PyList`/`PyTuple`
references at all.

## Removal trigger

Drop both ignores once rust-numpy 0.29 lands and pyo3 is bumped to 0.29.

Verified locally: `cargo deny check` → `advisories ok, bans ok, licenses ok, sources ok`.
2026-06-12 21:38:10 +02:00
kingchenc 806ae22abe release: bump 0.8.7 -> 0.8.8 (#265)
Version bump `0.8.7` → `0.8.8`.

### Fixed
- R binding: declare `Depends: R (>= 2.10)`, clearing the `R CMD check` "package needs dependence on R (>= 2.10)" warning that the bundled, lazy-loaded `sample_ohlcv` dataset triggers on r-universe / CRAN. (#264)

Bump touches the manual release touchpoints only; docs/webpage version strings are left to `sync-about.yml` on the tag.
2026-06-11 22:44:47 +02:00
kingchenc fb9c39d4cd fix(r): declare Depends: R (>= 2.10) for the bundled dataset (#264)
The `sample_ohlcv` dataset added in #262 is lazy-loaded (`LazyData: true`), which makes `R CMD check` on r-universe / CRAN warn:

```
* checking data for ASCII and uncompressed saves ... WARNING
  Warning: package needs dependence on R (>= 2.10)
```

Lazy-loading of package data requires R ≥ 2.10, so the package must declare it. This adds `Depends: R (>= 2.10)` to `bindings/r/DESCRIPTION`.

**Verified locally** (R 4.6.0, `R CMD build` + `R CMD check`): the `checking data for ASCII and uncompressed saves` step now reports **OK**. (The repo CI only runs `R CMD INSTALL` + testthat, not full `R CMD check`, so this surfaces only on r-universe — same asymmetry as the golden-test skip.)
2026-06-11 22:41:05 +02:00
kingchenc b1653e2107 release: bump 0.8.6 -> 0.8.7 (#263)
Version bump `0.8.6` → `0.8.7`.

### Added
- R binding: a *Getting started* vignette and a synthetic `sample_ohlcv` example dataset, giving new users a runnable, self-contained walkthrough and populating the R-universe Articles and Datasets tabs. The vignette's code is exercised in CI so a broken example is caught before the published build. (#262)

Bump touches the manual release touchpoints only; docs/webpage version strings are left to `sync-about.yml` on the tag.
2026-06-11 21:47:50 +02:00
kingchenc 32e18cb3a3 feat(r): getting-started vignette + sample_ohlcv dataset (#262)
Fills the two empty r-universe tabs (**Articles**, **Datasets**) for the R package and gives R users a runnable onboarding path.

## What
- **`vignettes/getting-started.Rmd`** — Articles tab. Walks through batch vs streaming (and that they're equivalent), multi-output MACD, candle ATR, and `reset()`, all over the bundled sample series. Built strictly from the already-proven README quick-start + golden-test API (`Sma`/`Ema`/`Rsi`/`Atr`/`MacdIndicator`, `batch`/`update`/`reset`) — no new indicator logic.
- **`data/sample_ohlcv.rda`** (+ `data-raw/sample_ohlcv.R` generator) — Datasets tab. A deterministic, seeded synthetic daily OHLCV series (250 rows × `date/open/high/low/close/volume`); documented via `R/data.R` + `man/sample_ohlcv.Rd`. `LazyData: true` → available right after `library(wickra)`.
- **`DESCRIPTION`** — `Suggests: knitr, rmarkdown`, `VignetteBuilder: knitr`, `LazyData: true`.
- **`ci.yml`** — the R job now knits the vignette (executes its R chunks, no pandoc needed) so a broken example is caught **in CI** before r-universe / CRAN `R CMD check`. The main job otherwise only `R CMD INSTALL`s.

## Verified locally (R 4.6.0 + Rtools45)
- Package installs with the dev C-ABI override; dataset moves to the lazyload DB.
- Vignette **knits cleanly** — every chunk runs, `batch == streaming` holds, MACD/ATR/RSI produce sensible values.

## Notes
- No version bump — metadata/docs only; rides the next release. Merging triggers an r-universe rebuild → Articles + Datasets populate **and** (now that `support@wickra.org` is verified) the maintainer avatar resolves.
- `data-raw/` is `.Rbuildignore`d (generator, not shipped). The `.rda` is XZ-compressed (~3 KB).

Not merging — for review.
2026-06-11 21:44:56 +02:00
kingchenc dc0c3d1736 docs(readme): serve footer social badges + star-history from snapshots (#261)
Pairs with **wickra-lib/.github#32**. The README footer (GitHub stars / forks / issues) hot-linked `img.shields.io` directly, so it showed shields' transient **"unable to select next github token from pool"** error live; the star-history chart hot-linked `star-history.com` and **froze** behind GitHub's Camo image cache (the embedded `<img>` is proxied + cached, while the linked page renders fresh).

All four now point at the committed snapshots in the `.github` repo (`raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/{stars,forks,issues,star-history}.svg`), refreshed hourly by the new `refresh-social.yml` — so a broken upstream never reaches the page and the last good SVG is always served. The `<a>` links and the visual style are unchanged.

## Merge order
Merge **.github#32 first** (it creates the snapshot SVGs), then this one — otherwise the footer would briefly point at missing raw URLs.

Not merging yet — for review.
2026-06-11 19:28:04 +02:00
kingchenc ad1231cde1 release: bump 0.8.5 -> 0.8.6 (#260)
Version bump `0.8.5` → `0.8.6`.

### Changed
- Package registry metadata for better discoverability (#259):
  - R (R-universe): R-universe URL + `X-schema.org-keywords` in `DESCRIPTION`, package logo at `bindings/r/man/figures/logo.png`.
  - Python (PyPI): `Documentation` project URL.
  - C# (NuGet): package icon via `PackageIcon`.

Ships the metadata so the next PyPI/NuGet publish carries the new fields (R-universe rebuilds from main independently). Bump touches the manual release touchpoints only; docs/webpage version strings are left to `sync-about.yml` on the tag.
2026-06-11 18:17:05 +02:00
kingchenc 7ebb60b60a chore: improve package registry metadata (R-universe, PyPI, NuGet) (#259)
Closes the package-page discoverability gaps found while auditing the public registry listings. No code or version change — metadata only; takes effect on the next registry build/publish.

## R — R-universe (`bindings/r`)
- `DESCRIPTION`: add the R-universe URL to `URL:` and a CRAN-permitted `X-schema.org-keywords` field (feeds R-universe's search/ranking).
- Add a package logo at `man/figures/logo.png` (pkgdown convention → shown in the R-universe packages tab) and reference it in the R `README.md`.
- Maintainer email is **unchanged** (`support@wickra.org`).

## Python — PyPI (`bindings/python`)
- `pyproject.toml`: add a `Documentation` project URL (`https://docs.wickra.org`) so it appears in the PyPI sidebar.

## C# — NuGet (`bindings/csharp`)
- Add `icon.png` (brand mark) and `<PackageIcon>` so nuget.org shows the logo instead of the default placeholder.

## Notes
- Rust/crates.io, Node/npm, Go/pkg.go.dev and Java/Maven Central were audited and are already complete for their respective metadata models (no icon/keyword concept on some).
- The repository topic `rstats` was added (replacing the redundant `webassembly`, since `wasm` already covers it) so R is represented alongside the other language tags.
2026-06-11 18:11:54 +02:00
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
2012 changed files with 297304 additions and 4634 deletions
+24 -1
View File
@@ -11,8 +11,31 @@ bindings/c/include/wickra.h text eol=lf
*.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.
# 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
# Golden fixtures are replayed byte-for-byte by every binding's parity test. Pin
# them to LF so a Windows `core.autocrlf=true` checkout doesn't rewrite them as
# CRLF — which silently broke the Node reader (`Number('inf\r')` is NaN, and a
# blank "no-bar" row gained a stray `\r`), failing only on Windows runners. The
# tolerant readers (Python `splitlines`/`float`) hid the same hazard.
testdata/golden/** text eol=lf
+2 -2
View File
@@ -30,9 +30,9 @@ assignees: ""
## Environment
- Wickra version:
- Language / binding: <!-- Rust crate / Python / Node / WASM / C ABI / C# (.NET) / Go -->
- Language / binding: <!-- Rust crate / Python / Node.js / WASM / C / C++ / C# / Go / Java / R -->
- OS and architecture:
- Rust / Python / Node / .NET version (If relevant):
- Rust / Python / Node.js / .NET version (If relevant):
## Additional context
@@ -15,7 +15,12 @@ assignees: []
- [ ] Rust crate (`wickra`)
- [ ] Python (`pip install wickra`)
- [ ] Node.js (`npm install wickra`)
- [ ] WebAssembly
- [ ] WASM
- [ ] C ABI (`bindings/c`)
- [ ] C# (`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 / .NET version | `python --version` / `node --version` / `dotnet --version` |
| Python / Node.js / .NET version | `python --version` / `node --version` / `dotnet --version` |
## Minimal reproducer
+3 -1
View File
@@ -26,8 +26,10 @@ assignees: ""
- [ ] 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 C# 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 / C ABI / C# (.NET) / Go`
- Binding: `Rust / Python / Node.js / WASM / C / C++ / C# / 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 / C ABI / C# (.NET) / Go`
- Binding: `Rust / Python / Node.js / WASM / C / C++ / C# / 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, and the C ABI + C# + Go bindings are regenerated
- [ ] Public API changes are mirrored in the Python / Node.js / 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
+4 -2
View File
@@ -22,10 +22,12 @@ Please fill in the sections below. Delete any that don't apply.
- [ ] Rust crate (`crates/wickra`)
- [ ] Python binding (`bindings/python`)
- [ ] Node.js binding (`bindings/node`)
- [ ] WebAssembly binding (`bindings/wasm`)
- [ ] WASM binding (`bindings/wasm`)
- [ ] C ABI (`bindings/c`)
- [ ] C# / .NET binding (`bindings/csharp`)
- [ ] C# binding (`bindings/csharp`)
- [ ] Go binding (`bindings/go`)
- [ ] Java binding (`bindings/java`)
- [ ] R binding (`bindings/r`)
- [ ] Examples / docs
## Linked issues
+46
View File
@@ -0,0 +1,46 @@
name: Setup Rust toolchain (with retry)
description: >
Install a Rust toolchain via dtolnay/rust-toolchain, retrying once after a
short pause on the transient rustup CDN failure (a corrupt channel manifest /
checksum mismatch on `channel-rust-<channel>.toml`) that rustup itself does
not retry — the same hardening already applied to setup-node / setup-go.
inputs:
toolchain:
description: Rust toolchain (channel or version) to install.
required: false
default: stable
components:
description: Comma-separated list of rustup components (e.g. "rustfmt, clippy").
required: false
default: ""
targets:
description: Comma-separated list of rustup targets (e.g. "wasm32-unknown-unknown").
required: false
default: ""
runs:
using: composite
steps:
- id: first
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
continue-on-error: true
with:
toolchain: ${{ inputs.toolchain }}
components: ${{ inputs.components }}
targets: ${{ inputs.targets }}
- name: Wait before retrying the toolchain install
if: steps.first.outcome == 'failure'
shell: bash
run: |
echo "::warning::rustup toolchain install failed (likely a CDN flake / corrupt channel manifest); retrying in 30s"
sleep 30
- name: Retry the Rust toolchain install
if: steps.first.outcome == 'failure'
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
with:
toolchain: ${{ inputs.toolchain }}
components: ${{ inputs.components }}
targets: ${{ inputs.targets }}
+82
View File
@@ -10,6 +10,9 @@ updates:
default-days: 7
commit-message:
prefix: "deps(cargo)"
groups:
cargo:
patterns: ["*"]
# Node binding npm dependencies.
- package-ecosystem: npm
@@ -21,6 +24,9 @@ updates:
default-days: 7
commit-message:
prefix: "deps(npm)"
groups:
node-binding:
patterns: ["*"]
# Python binding pip dependencies.
- package-ecosystem: pip
@@ -32,6 +38,9 @@ updates:
default-days: 7
commit-message:
prefix: "deps(pip)"
groups:
python-binding:
patterns: ["*"]
# Hash-pinned CI/bench Python tooling under .github/requirements/. Each
# <name>.in is the loose source; the matching hash-locked <name>.txt is the
@@ -47,6 +56,9 @@ updates:
default-days: 7
commit-message:
prefix: "deps(ci-pip)"
groups:
ci-pip:
patterns: ["*"]
# GitHub Actions — keeps the SHA-pinned actions current (Dependabot reads
# the version comment after each pinned SHA and bumps both together).
@@ -59,3 +71,73 @@ updates:
default-days: 7
commit-message:
prefix: "deps(actions)"
groups:
github-actions:
patterns: ["*"]
# Java binding + examples (Maven). Tracks the C-ABI binding's build plugins
# (e.g. central-publishing-maven-plugin) and the examples' jackson dependency,
# which had no Dependabot coverage before — a stale publishing plugin slipped
# through unnoticed until an OSV scan flagged it.
- package-ecosystem: maven
directories:
- "/bindings/java"
- "/bindings/java/benchmarks"
- "/examples/java"
schedule:
interval: weekly
open-pull-requests-limit: 10
cooldown:
default-days: 7
commit-message:
prefix: "deps(maven)"
groups:
maven:
patterns: ["*"]
# C# binding (NuGet). The published Wickra.csproj is a thin C-ABI wrapper with
# no external packages, but the test and benchmark projects pull xunit,
# Microsoft.NET.Test.Sdk and BenchmarkDotNet.
- package-ecosystem: nuget
directories:
- "/bindings/csharp/Wickra.Tests"
- "/bindings/csharp/benchmarks"
schedule:
interval: weekly
open-pull-requests-limit: 10
cooldown:
default-days: 7
commit-message:
prefix: "deps(nuget)"
groups:
nuget:
patterns: ["*"]
# Node examples (npm) — separate from the binding's own package.json.
- package-ecosystem: npm
directory: "/examples/node"
schedule:
interval: weekly
open-pull-requests-limit: 10
cooldown:
default-days: 7
commit-message:
prefix: "deps(npm)"
groups:
node-examples:
patterns: ["*"]
# Go examples (Go modules). The binding's own go.mod has no external deps;
# the examples pull coder/websocket.
- package-ecosystem: gomod
directory: "/examples/go"
schedule:
interval: weekly
open-pull-requests-limit: 10
cooldown:
default-days: 7
commit-message:
prefix: "deps(gomod)"
groups:
go-examples:
patterns: ["*"]
+4 -4
View File
@@ -48,11 +48,11 @@ jobs:
name: Cross-library benchmark report
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
- uses: ./.github/actions/setup-rust
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
@@ -122,11 +122,11 @@ jobs:
name: Rust cross-library benchmark report
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
- uses: ./.github/actions/setup-rust
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
+292 -46
View File
@@ -34,17 +34,18 @@ 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:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
uses: ./.github/actions/setup-rust
with:
components: rustfmt, clippy
@@ -104,8 +105,9 @@ 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
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
@@ -114,7 +116,7 @@ jobs:
continue-on-error: true
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "20"
node-version: "22"
- name: Wait before Node retry
if: steps.setup_node.outcome == 'failure'
@@ -127,7 +129,7 @@ jobs:
if: steps.setup_node.outcome == 'failure'
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "20"
node-version: "22"
- name: Set up Python
id: setup_python
@@ -193,13 +195,14 @@ jobs:
clippy-bindings:
name: Clippy bindings
runs-on: ubuntu-latest
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
uses: ./.github/actions/setup-rust
with:
components: clippy
@@ -228,7 +231,7 @@ jobs:
continue-on-error: true
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "20"
node-version: "22"
- name: Wait before Node retry
if: steps.setup_node.outcome == 'failure'
@@ -241,7 +244,7 @@ jobs:
if: steps.setup_node.outcome == 'failure'
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "20"
node-version: "22"
- name: Cache cargo
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
@@ -276,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:
@@ -287,12 +291,12 @@ jobs:
toolchain: "1.88"
packages: "-p wickra-node"
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Install Rust ${{ matrix.toolchain }}
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
uses: ./.github/actions/setup-rust
with:
toolchain: ${{ matrix.toolchain }}
@@ -324,13 +328,14 @@ jobs:
coverage:
name: Coverage
runs-on: ubuntu-latest
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
uses: ./.github/actions/setup-rust
with:
components: llvm-tools-preview
@@ -340,7 +345,7 @@ jobs:
timeout-minutes: 6
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@0fd46367812ee04360509b4169d9f659d6892bb2 # v2.79.15
uses: taiki-e/install-action@59012be0884e296ca2da49b530610e72c49039ad # v2.81.6
timeout-minutes: 10 # fail fast on a stuck download instead of hanging the job
with:
tool: cargo-llvm-cov
@@ -365,8 +370,9 @@ 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
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
@@ -383,13 +389,14 @@ jobs:
fuzz-smoke:
name: Fuzz (smoke)
runs-on: ubuntu-latest
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Install nightly Rust
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
uses: ./.github/actions/setup-rust
with:
toolchain: nightly
@@ -408,7 +415,7 @@ jobs:
# attributes the modern nightly compiler rejects, so the install
# never gets off the ground. The prebuilt binary avoids the entire
# transitive-dep compile.
uses: taiki-e/install-action@0fd46367812ee04360509b4169d9f659d6892bb2 # v2.79.15
uses: taiki-e/install-action@59012be0884e296ca2da49b530610e72c49039ad # v2.81.6
timeout-minutes: 10 # fail fast on a stuck download instead of hanging the job
with:
tool: cargo-fuzz
@@ -436,18 +443,19 @@ 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:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.9", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
uses: ./.github/actions/setup-rust
- name: Cache cargo
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
@@ -517,13 +525,14 @@ jobs:
wasm:
name: WASM build
runs-on: ubuntu-latest
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Install Rust toolchain (with wasm target)
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
uses: ./.github/actions/setup-rust
with:
targets: wasm32-unknown-unknown
@@ -540,7 +549,7 @@ jobs:
# same taiki-e prebuilt-binary installer we already use for
# cargo-llvm-cov and cargo-fuzz; it tracks the latest wasm-pack
# release, which has `--features` as a top-level flag (since 0.12).
uses: taiki-e/install-action@0fd46367812ee04360509b4169d9f659d6892bb2 # v2.79.15
uses: taiki-e/install-action@59012be0884e296ca2da49b530610e72c49039ad # v2.81.6
timeout-minutes: 10 # fail fast on a stuck download instead of hanging the job
with:
tool: wasm-pack
@@ -557,21 +566,28 @@ jobs:
test -f bindings/wasm/pkg/wickra_wasm_bg.wasm
test -f bindings/wasm/pkg/wickra_wasm.d.ts
- name: Build WASM package (nodejs target) for the golden suite
run: wasm-pack build bindings/wasm --target nodejs --release --out-dir pkg
- name: Golden parity — all 514 indicators vs the Rust reference
run: node --test bindings/wasm/tests/golden.test.js
node:
name: Node ${{ matrix.node-version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node-version: ["18", "20"]
node-version: ["22", "24"]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
uses: ./.github/actions/setup-rust
- name: Cache cargo
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
@@ -618,24 +634,33 @@ 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
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
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
uses: ./.github/actions/setup-rust
- name: Cache cargo
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
@@ -643,7 +668,7 @@ jobs:
timeout-minutes: 6
- name: Install cbindgen
uses: taiki-e/install-action@0fd46367812ee04360509b4169d9f659d6892bb2 # v2.79.15
uses: taiki-e/install-action@59012be0884e296ca2da49b530610e72c49039ad # v2.81.6
timeout-minutes: 10 # fail fast on a stuck download instead of hanging the job
with:
tool: cbindgen
@@ -680,17 +705,18 @@ jobs:
csharp:
name: C# on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
uses: ./.github/actions/setup-rust
- name: Cache cargo
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
@@ -730,12 +756,13 @@ jobs:
go:
name: Go on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
@@ -745,7 +772,7 @@ jobs:
- name: Set up Go
id: setup-go
continue-on-error: true
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: "stable"
cache: false
@@ -759,13 +786,13 @@ jobs:
- name: Set up Go (retry)
if: steps.setup-go.outcome == 'failure'
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: "stable"
cache: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
uses: ./.github/actions/setup-rust
- name: Cache cargo
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
@@ -778,15 +805,36 @@ jobs:
- 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: |
mkdir -p bindings/go/lib
case "$RUNNER_OS" in
Linux) cp target/release/libwickra.so bindings/go/lib/ ;;
macOS) cp target/release/libwickra.dylib bindings/go/lib/ ;;
Windows) cp target/release/wickra.dll bindings/go/lib/ ;;
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
@@ -801,9 +849,11 @@ jobs:
- name: Vet and test the Go binding
shell: bash
# On Windows there is no rpath; the loader resolves wickra.dll via PATH.
# 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="$PWD/bindings/go/lib:$PATH"
export PATH="$WICKRA_GO_LIBDIR:$PATH"
cd bindings/go
go vet ./...
go test ./...
@@ -811,7 +861,7 @@ jobs:
- name: Build the Go examples
shell: bash
run: |
export PATH="$PWD/bindings/go/lib:$PATH"
export PATH="$WICKRA_GO_LIBDIR:$PATH"
cd examples/go
go build ./...
@@ -819,13 +869,209 @@ jobs:
- name: Run the offline Go examples
shell: bash
run: |
export PATH="$PWD/bindings/go/lib:$PATH"
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Install Rust toolchain
uses: ./.github/actions/setup-rust
- 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: Build the vignette code
shell: bash
# The getting-started vignette runs at R CMD check time on r-universe /
# CRAN (with pandoc); this job only INSTALLs, so execute the vignette's R
# chunks here (knit, no pandoc needed) to catch a broken example before it
# reaches the published build.
run: |
Rscript -e 'install.packages("knitr", repos = Sys.getenv("RSPM", unset = "https://cloud.r-project.org"))'
Rscript -e 'knitr::knit("bindings/r/vignettes/getting-started.Rmd", output = tempfile(fileext = ".md"), quiet = TRUE); cat("vignette code OK\n")'
- name: Run the offline R examples
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Install Rust toolchain
uses: ./.github/actions/setup-rust
- name: Cache cargo
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
timeout-minutes: 6
# 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}. The FFM API is final since JDK 22; we
# build on the 25 LTS (22 is EOL; the pom pins bytecode to release 22 so the
# runtime floor stays Java 22). Installed explicitly (runners ship 17/21).
- name: Build the C ABI library
run: cargo build -p wickra-c --release
- name: Set up JDK 25
id: setup-java
continue-on-error: true
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: "25"
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 25 (retry)
if: steps.setup-java.outcome == 'failure'
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: "25"
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
# Build a Python wheel inside both the manylinux and the musllinux container,
# mirroring the Linux wheel build in release.yml. The 3-OS Python jobs build
# natively on the runner, which already ships system OpenSSL, so they cannot
# catch a build-time gap that only exists inside the slim release containers —
# exactly what broke the 0.9.3 Linux wheels (the live-binance data layer links
# native-tls -> openssl-sys, and the containers provide no OpenSSL: manylinux
# lacks the headers, musllinux cross-compiles against a musl sysroot without
# OpenSSL at all). The wheels are built with the `vendored-tls` feature, which
# statically compiles OpenSSL from source. This job exercises both container
# builds on every PR, so the same class of breakage now fails CI, not release.
python-wheel-container-smoke:
name: Python wheel (${{ matrix.manylinux }} smoke)
runs-on: ubuntu-latest
timeout-minutes: 30 # backstop: vendored OpenSSL adds a from-source compile
strategy:
fail-fast: false
matrix:
manylinux: [auto, musllinux_1_2]
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Sync root README into bindings/python so the build matches release
run: cp README.md bindings/python/README.md
- uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
with:
working-directory: bindings/python
target: x86_64
# Keep in sync with release.yml: vendored OpenSSL for the Linux wheels.
args: --release --out dist --features vendored-tls
manylinux: ${{ matrix.manylinux }}
# Building OpenSSL from source needs Perl modules (IPC::Cmd,
# Time::Piece, ...) the minimal manylinux (CentOS 7) image lacks.
# perl-core pulls the full distribution; the explicit names document
# the ones OpenSSL's Configure has required. The musllinux cross image
# ships a complete Perl and has no yum, so this is a no-op there.
before-script-linux: |
if command -v yum >/dev/null 2>&1; then yum install -y perl-core perl-IPC-Cmd perl-Data-Dumper perl-Time-Piece; fi
# The cross-library benchmark has moved to a dedicated scheduled workflow
# (.github/workflows/bench.yml) — see audit finding R10. It runs nightly
# at 03:00 UTC and on-demand via `workflow_dispatch`, and is no longer on
+3 -3
View File
@@ -40,17 +40,17 @@ jobs:
build-mode: none
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@03e4368ac7daa2bd82b3e85262f3bf87ee112f57 # v3.36.0
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@03e4368ac7daa2bd82b3e85262f3bf87ee112f57 # v3.36.0
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
with:
category: "/language:${{ matrix.language }}"
+219 -27
View File
@@ -45,10 +45,10 @@ jobs:
# Settings -> Environments.
environment: release
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
- uses: ./.github/actions/setup-rust
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
timeout-minutes: 6
@@ -110,7 +110,7 @@ jobs:
# consumers can audit the published dependency tree without
# re-resolving Cargo.lock.
- name: Install cargo-cyclonedx
uses: taiki-e/install-action@0fd46367812ee04360509b4169d9f659d6892bb2 # v2.79.15
uses: taiki-e/install-action@59012be0884e296ca2da49b530610e72c49039ad # v2.81.6
timeout-minutes: 10 # fail fast on a stuck download instead of hanging the job
with:
tool: cargo-cyclonedx
@@ -157,7 +157,7 @@ jobs:
- { os: windows-11-arm, target: aarch64, manylinux: auto }
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Set up Python
@@ -184,8 +184,22 @@ jobs:
with:
working-directory: bindings/python
target: ${{ matrix.target }}
args: --release --strip --out dist
# The live-binance data layer links native-tls -> openssl-sys, which
# needs OpenSSL at build time. The manylinux containers lack the headers
# and the musllinux build cross-compiles against a musl sysroot with no
# OpenSSL at all, so build the Linux wheels with vendored OpenSSL
# (compiled from source, linked statically). No-op on the native
# macOS/Windows runners, where native-tls never pulls openssl-sys. The
# CI `python-wheel-container-smoke` job exercises both containers on PRs.
args: --release --strip --out dist --features vendored-tls
manylinux: ${{ matrix.manylinux }}
# Building OpenSSL from source needs Perl modules (IPC::Cmd,
# Time::Piece, ...) the minimal manylinux (CentOS 7) image lacks.
# perl-core pulls the full distribution; the explicit names document
# the ones OpenSSL's Configure has required. The musllinux cross image
# ships a complete Perl and has no yum, so this is a no-op there.
before-script-linux: |
if command -v yum >/dev/null 2>&1; then yum install -y perl-core perl-IPC-Cmd perl-Data-Dumper perl-Time-Piece; fi
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
# Include manylinux in the name so the glibc and musl x86_64/aarch64
@@ -197,7 +211,7 @@ jobs:
name: Build Python sdist
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Sync root README into bindings/python so it ships in the sdist
@@ -249,7 +263,7 @@ jobs:
- { host: windows-11-arm, target: aarch64-pc-windows-msvc }
runs-on: ${{ matrix.host }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
@@ -258,7 +272,7 @@ jobs:
continue-on-error: true
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "20"
node-version: "22"
- name: Wait before Node retry
if: steps.setup_node.outcome == 'failure'
@@ -271,9 +285,9 @@ jobs:
if: steps.setup_node.outcome == 'failure'
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "20"
node-version: "22"
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
- uses: ./.github/actions/setup-rust
with:
targets: ${{ matrix.target }}
@@ -310,7 +324,7 @@ jobs:
contents: read
id-token: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
@@ -319,7 +333,7 @@ jobs:
continue-on-error: true
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "20"
node-version: "22"
registry-url: "https://registry.npmjs.org"
- name: Wait before Node retry
@@ -333,7 +347,7 @@ jobs:
if: steps.setup_node.outcome == 'failure'
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "20"
node-version: "22"
registry-url: "https://registry.npmjs.org"
- name: Install Node deps
@@ -479,7 +493,7 @@ jobs:
contents: read
id-token: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
@@ -488,7 +502,7 @@ jobs:
continue-on-error: true
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "20"
node-version: "22"
registry-url: "https://registry.npmjs.org"
- name: Wait before Node retry
@@ -502,17 +516,17 @@ jobs:
if: steps.setup_node.outcome == 'failure'
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: "20"
node-version: "22"
registry-url: "https://registry.npmjs.org"
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
- uses: ./.github/actions/setup-rust
with:
targets: wasm32-unknown-unknown
- name: Install wasm-pack (latest, via prebuilt binary)
# See the matching note in ci.yml: jetli's default installs an old
# 0.10.x wasm-pack whose build subcommand rejects --features.
uses: taiki-e/install-action@0fd46367812ee04360509b4169d9f659d6892bb2 # v2.79.15
uses: taiki-e/install-action@59012be0884e296ca2da49b530610e72c49039ad # v2.81.6
timeout-minutes: 10 # fail fast on a stuck download instead of hanging the job
with:
tool: wasm-pack
@@ -588,11 +602,11 @@ jobs:
- { host: windows-11-arm, target: aarch64-pc-windows-msvc }
runs-on: ${{ matrix.host }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
- uses: ./.github/actions/setup-rust
with:
targets: ${{ matrix.target }}
@@ -638,7 +652,7 @@ jobs:
contents: read
id-token: write # request the GitHub OIDC token for trusted publishing
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
@@ -706,6 +720,166 @@ jobs:
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
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 25 + Maven Central credentials + GPG
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: "25"
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
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, c-abi-build]
@@ -716,8 +890,9 @@ jobs:
# bundle to this same release without re-resolving it.
outputs:
tag: ${{ steps.tag.outputs.tag }}
version: ${{ steps.tag.outputs.version }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
fetch-depth: 0
@@ -744,6 +919,7 @@ jobs:
exit 1
fi
echo "tag=$tag" >> "$GITHUB_OUTPUT"
echo "version=${tag#v}" >> "$GITHUB_OUTPUT"
echo "::notice::attaching assets to release $tag"
- name: Download all build artifacts
@@ -781,7 +957,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 plus a C ABI.
Wickra ${{ github.ref_name }} — streaming-first technical indicators for 10 languages: native Rust, Python, Node.js, WASM plus a C ABI hub for C, C++, C#, Go, Java, R.
### Install
@@ -790,21 +966,37 @@ jobs:
pip install wickra
npm install wickra
npm install wickra-wasm
dotnet add package Wickra
go get github.com/wickra-lib/wickra-go
```
Java (Gradle, Maven Central):
```gradle
implementation("org.wickra:wickra:${{ steps.tag.outputs.version }}")
```
R (r-universe):
```r
install.packages("wickra", repos = "https://wickra-lib.r-universe.dev")
```
### Attached assets
Pre-built artefacts for every supported platform — the same files that
were uploaded to crates.io, PyPI, and npm by this workflow run.
Pre-built artefacts for every supported platform — the same files this
workflow run published to crates.io, PyPI, and npm. C# ships to NuGet,
Java to Maven Central, Go to the wickra-go module, and R to r-universe
via their own release jobs.
- `*.whl` / `wickra-*.tar.gz` — Python wheels + sdist (5 platforms, ABI3 ≥ 3.9)
- `wickra.*.node` — native Node bindings (linux-x64-gnu, darwin-x64,
- `wickra.*.node` — native Node.js bindings (linux-x64-gnu, darwin-x64,
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
the hub for C, C++, C#, Go, Java and R
### Auto-generated changelog
+2 -2
View File
@@ -24,7 +24,7 @@ jobs:
id-token: write # OIDC token to publish results to the OpenSSF API
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
@@ -51,6 +51,6 @@ jobs:
retention-days: 5
- name: Upload SARIF to code-scanning
uses: github/codeql-action/upload-sarif@03e4368ac7daa2bd82b3e85262f3bf87ee112f57 # v3.36.0
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
with:
sarif_file: results.sarif
+34 -11
View File
@@ -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.
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
#
# 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
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, WebAssembly, C ABI, .NET, and Go 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)."
@@ -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 -1
View File
@@ -14,7 +14,7 @@ jobs:
name: metadata audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
actions: read # online audits resolve referenced actions
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
+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/
+24 -20
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,7 +20,7 @@ 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 │
@@ -31,7 +31,7 @@ or replace lives behind a separate crate boundary.
┌────────────┼────────────┬────────────────┐
│ │ │ │
┌──▼───────┐ ┌──▼───────┐ ┌──▼───────────┐ ┌──▼──────────────────┐
│ Python │ │ Node │ │ WASM │ │ C ABI (cbindgen) │
│ Python │ │ Node.js │ │ WASM │ │ C ABI (cbindgen) │
│ (PyO3) │ │ (napi-rs)│ │(wasm-bindgen)│ │ cdylib + header │
└──────────┘ └──────────┘ └──────────────┘ └─────────┬───────────┘
│ linked by
@@ -41,13 +41,14 @@ or replace lives behind a separate crate boundary.
└─────────────────────┘
```
Python, Node and WASM are *native* Rust bindings (PyO3 / napi-rs /
Python, Node.js 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) and the **Go** binding (`bindings/go`, cgo) are generated from
`wickra.h`, with Java / R planned the same way.
core. C and C++ link it directly; the **C#** 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 |
|---|---|---|---|
@@ -56,7 +57,7 @@ on NuGet) and the **Go** binding (`bindings/go`, cgo) are generated from
| `wickra-data` | `crates/wickra-data` | CSV reader, tick aggregator, resampler, live exchange feeds (feature-gated) | `tokio`, `tokio-tungstenite` (live), `serde_json` |
| `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-wasm` | `bindings/wasm` | WASM 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` |
@@ -202,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
@@ -223,7 +227,7 @@ A handful of indicators need care beyond naive accumulation:
A typical full-stack call sequence for a Python live-trading example:
```
[ Python: live_trading.py ]
[ Python: live_binance.py ]
[ binance.AsyncClient WebSocket ] ──── wickra_data live feed ───┐
@@ -326,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).
+112
View File
@@ -94,3 +94,115 @@ 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++, C#, Go, Java,
R or WASM 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 crosses once.** A `batch` call crosses the boundary a single time and the
Rust core computes the whole series internally, so batch throughput stays high for
most bindings — the exceptions are the ones that copy or box the result array on
the way out (Node's JS `Array`, Python's stdlib `array.array` now that NumPy is
optional).
- **Streaming reveals the boundary.** A per-tick `update` crosses the boundary
once per value, so streaming throughput is where the bindings differ: the raw C
ABI and P/Invoke-style calls are nearly free, while managed or interpreted
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) | 380 | 498 |
| C / C++ | 365 | 358 |
| C# | 348 | 259 |
| Python | 31 | 46 |
| Java | 38 | 173 |
| Go | 23 | 394 |
| WASM | 21 | 169 |
| Node.js | 16 | 9 |
| R | 0.1 | 279 |
Streaming spans more than three orders of magnitude — the raw C ABI (365) sits
just under the FFI-free Rust ceiling (380), while R's per-call interpreter overhead
(0.1) makes streaming ~2800× slower than its own batch. Batch stays high for the
bindings that hand back a contiguous buffer (slices, typed arrays); the two low
outliers are Node — whose napi `batch` boxes every element into a JS `Array` — and
Python, which now copies into a stdlib `array.array` since NumPy became optional.
These are machine-dependent and reflect FFI overhead, not algorithm speed.
These are throughput numbers, not competitive numbers — the "Wickra is fast"
claim lives in sections 1 and 2 (Rust core + the Python/Rust cross-library runs).
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# (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)
```
## 4. Data layer — native I/O throughput
Wickra ships its own data layer — a CSV candle reader, a tick-to-candle
aggregator, and a timeframe resampler — so loading and reshaping market data
needs **no third-party package** (`pandas`, `csv-parse`, manual bucketing,
`pandas.resample`, …) in any of the ten languages. These run on the same Rust
core as the indicators, so every binding reaches these speeds minus the FFI
boundary characterised in section 3 (a `read` / `push` / `flush` call crosses the
boundary once per batch, like `batch`, so bindings land close to the core).
Rust core, 50 000 real BTCUSDT one-minute candles
(`examples/data/btcusdt-1m.csv`), median of 100 samples, on the reference machine
(Windows 11, AMD Ryzen 9 9950X):
| Operation | Throughput | Per element |
|------------------------------------|--------------------:|------------:|
| CSV parse (`CandleReader`) | 3.0 M candles/s | 329 ns |
| Tick aggregate → 1m (`TickAggregator`) | 44 M ticks/s | 22.6 ns |
| Resample 1m → 5m (`Resampler`) | 234 M candles/s | 4.3 ns |
Reading and validating a 50 000-row CSV into typed candles takes ~16 ms;
aggregating 50 000 ticks into one-minute bars ~1.1 ms; resampling 50 000
one-minute candles to five-minute bars ~0.2 ms. CSV parsing is the floor because
it does the most per row (UTF-8 scan, field split, six `f64` parses, finiteness
checks); aggregation and resampling are pure arithmetic over already-typed
candles.
The live and historical Binance feeds (`BinanceFeed`, `fetch_binance_klines`) are
network-bound — their throughput is set by the exchange and the socket, not by
Wickra — so they are not micro-benchmarked here; the relevant Wickra cost is the
per-event parse, which is the same arithmetic measured above.
Run it with:
```bash
cargo bench -p wickra --bench data_layer
```
+411 -1
View File
@@ -7,6 +7,399 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.9.4] - 2026-06-17
Packaging fix for the `0.9.3` data layer. The library is identical to `0.9.3` on
every platform that already published; the only additions are an opt-in
`vendored-tls` build feature and the Linux Python wheels, which `0.9.3` could not
build.
### Fixed
- **Linux Python wheels (`manylinux` / `musllinux`) now build.** The `live-binance`
data layer links `native-tls` -> `openssl-sys`, which needs OpenSSL at build
time. The `manylinux` wheel containers ship no OpenSSL headers and the
`musllinux` build cross-compiles against a musl sysroot that has no OpenSSL at
all, so the wheels failed to compile. The Linux wheels are now built with a new
opt-in `vendored-tls` feature that compiles OpenSSL from source and links it
statically (no system OpenSSL required, on either libc). The native macOS and
Windows wheels were unaffected (Security.framework / SChannel). As a result
`0.9.3` shipped to crates.io, Maven Central, NuGet, and npm but not to PyPI;
PyPI publishes starting with `0.9.4`.
### Added
- **`vendored-tls` feature** on `wickra-data` (and the Python binding): builds the
`live-binance` TLS stack against a statically compiled OpenSSL. Off by default;
used by the release wheels and exercised on every PR by a `manylinux` /
`musllinux` container build-smoke CI job.
## [0.9.3] - 2026-06-17
### Changed
- **Python: zero third-party dependencies — NumPy is no longer required**
(breaking). `pip install wickra` now pulls nothing else. Batch inputs accept any
sequence or buffer of numbers (`array.array`, `memoryview`, a NumPy array, or a
plain `list`); single-output `batch(...)` now returns a stdlib `array.array('d')`
and multi-output indicators return a buffer-protocol `Matrix` (with `.shape`,
integer-row and `[i, j]` element access, `.tolist()`) instead of 1-D / 2-D NumPy
arrays. Both expose the buffer protocol, so `numpy.asarray(result)` still wraps a
1-D result zero-copy when NumPy is installed — it is now an optional extra
(`pip install wickra[numpy]`). Streaming `update(...)` is unchanged, and results
are numerically identical. Single-output `batch(...)` is slower than the previous
NumPy path — a stdlib `array.array` cannot take ownership of the Rust result, so
it is copied rather than moved — though absolute batch latency stays in the
low-millisecond range. The other nine languages were already dependency-free.
### Removed
- **Python: `numpy` runtime dependency** (see *Changed*). NumPy moves to the
optional `numpy`/`test`/`bench` extras.
### Fixed
- **Binance kline feed: add the missing `3d` and `1M` intervals.** The
`live-binance` `Interval` enum was missing three-day (`3d`) and one-month (`1M`)
candles, two of Binance's 16 supported kline intervals. Both are now selectable
and map to the correct wire-format strings.
### Added
- **Native historical Binance REST kline fetcher in 9 languages (data layer).**
`fetchBinanceKlines` (Node.js / Python `fetch_binance_klines` / Go
`FetchBinanceKlines` / C# `BinanceFeed.FetchKlines` / Java `BinanceFeed.fetchKlines`
/ R `fetch_binance_klines`; C / C++ call `wickra_binance_fetch_klines`) downloads
historical OHLCV candles straight from Binance's public REST endpoint — no
third-party HTTP/JSON client (`jackson`, `jsonlite`, `urllib`, …) needed. Pass a
symbol, interval, and limit (`1..=1000`) plus optional millisecond start/end
bounds; it blocks until the response arrives and returns the parsed candles. It
is built on `ureq` with native-tls, sharing the live feed's TLS stack, and is
covered by mock-HTTP-server tests. The historical counterpart to the live
`BinanceFeed`; WASM is excluded (browsers use the host `fetch`). Ships with the
C ABI's default `live-binance` feature.
- **Native live Binance kline feed in 9 languages (data layer).** `BinanceFeed`
streams live OHLCV candles straight from Binance's public WebSocket — no
third-party WebSocket client (`ws`, `websockets`, `gorilla/websocket`, …) in any
binding. Construct it with comma-separated symbols + an interval, then poll
`next(timeout)` for the next event (or `null`/`None` on timeout); the connection
reconnects transparently. Exposed natively (Node.js / Python — a blocking poll
that drives the tested async stream on a tokio runtime) and over the C ABI as Go
`Next()`, C# `Next()`, Java `next()`, and the R `binance_next()`; C / C++ call
`wickra_binance_connect` / `_next` / `_close` / `_free` directly. The connect →
read → reconnect pipeline is covered by the existing mock-WS-server tests. WASM
is excluded (a browser has no raw sockets; use the host `WebSocket`). The C ABI
ships the feed by default (`live-binance` feature); the wasm build drops it.
- **CSV candle reading in all 10 languages (data layer).** The `CandleReader`
parses a `timestamp,open,high,low,close,volume` CSV buffer (a leading UTF-8 BOM
and field whitespace are tolerated) into candles: construct it from a CSV string
and call `read()` for every candle in file order. Exposed natively (Node.js /
WASM `read(): Candle[]`, Python `read() -> list[tuple]`) and over the C ABI as Go
`Read() []Candle`, C# `Candle[] Read()`, Java `Candle[] read()`, and the R
`read()` S3 generic (an `n×6` matrix); C / C++ call `wickra_candle_reader_new` /
`_count` / `_read` directly. A cross-language golden
(`testdata/golden/data_csv*.csv`) pins the parsed candles identically across
every binding. This makes CSV backtest loading dependency-free in every binding.
- **Candle resampling in all 10 languages (data layer).** The `Resampler`
aggregates candles into a higher timeframe (e.g. 1m → 5m): `update(open, high,
low, close, volume, timestamp)` returns the completed higher-timeframe candle on
a bucket boundary (else `null`/`None`/`NA`), and `flush()` emits the final,
still-open candle. Exposed natively (Node.js / WASM / Python) and over the C ABI
(Go / C# / Java return `(Candle, bool)` / `Candle?` / `Candle`; R via `update()`
and a `flush()` S3 method; C / C++ directly). A cross-language golden
(`testdata/golden/data_resampled.csv`) pins the resampled stream identically.
- **Tick-to-candle aggregation in all 10 languages (data layer).** The
`TickAggregator` — roll trade ticks up into fixed-timeframe OHLCV candles, with
optional gap filling — is now exposed natively (Node.js / WASM `push(price,
size, ts): Candle[]`, Python `push(...) -> list[tuple]`) and over the C ABI as
Go `Push() []Candle`, C# `Candle[] Push()`, Java `Candle[] push()`, and the R
`push()` generic (an `n×6` matrix); C / C++ call the C ABI directly. The C ABI
uses a lossless two-step `wickra_tick_aggregator_push` / `_drain` so a single
push that gap-fills across many empty buckets never overflows a fixed buffer. A
new cross-language golden (`testdata/golden/data_*.csv`) pins the candle stream
identically across every binding. This is the first feature of a data layer that
makes the non-Rust bindings dependency-free for tick aggregation.
- **`name()` on every indicator in all 10 languages.** The canonical
`Indicator::name()` / `BarBuilder::name()` accessor is now exposed through every
binding — Node.js `name()`, WASM `name()`, Python `name()`, and the C ABI
`wickra_<ind>_name()` surfaced as Go `Name()`, C# `Name()`, Java `name()`, and
the R `name()` S3 generic (C/C++ call the C ABI directly). The returned string
is the core canonical name, which may differ from the registered class name
(e.g. `ChaikinMoneyFlow` reports `"CMF"`, `Donchian` reports
`"DonchianChannels"`). A new cross-language golden (`testdata/golden/names.json`)
pins this name for all 514 indicators identically across every binding.
## [0.9.2] - 2026-06-15
### Added
- **Cross-language golden parity for all 514 indicators across all 10 languages.**
A new `gen_golden` reference emits a deterministic OHLCV input series plus the
Rust output of every one of the 514 indicators to `testdata/golden/`. Each
binding now replays that shared input and is checked **bit-for-bit against the
Rust reference**, covering every archetype (scalar, multi-output, pairwise,
derivatives-tick, cross-section, order-book, trade, profile, alt-chart bars,
footprint):
- Python, Node.js, Java and R via reflection-driven runners.
- Go, C# and C/C++ via generated dispatch (`golden_all_test.go`,
`GoldenAllTests.g.cs`, `examples/c/golden_test.c` compiled as both C and C++).
- WASM via a `node --test` runner over the nodejs-target build.
- CI now runs the WASM golden suite; the C/C++ golden tests run as `ctest`
targets in the existing C-ABI job, and the Python/Node/Go/C#/Java/R suites pick
up their golden runners automatically.
- **README:** a "verified across 10 languages" badge (linking to the FAQ that
explains the cross-language golden parity) and a per-binding throughput table so
readers can pick a binding by its streaming FFI cost.
### Fixed
- **Java binding marshalled C ABI `bool` parameters incorrectly.** The
cross-section state flags (`newHigh`, `newLow`, `aboveMa`, `onBuySignal`) were
allocated as `JAVA_DOUBLE` arrays and passed to `const bool*` parameters, so the
native side read the low byte of each 8-byte double and saw every flag as
`false` (affecting e.g. `NewHighsNewLows`, `HighLowIndex`, `BullishPercentIndex`,
`PercentAboveMa`). They are now packed into a real `bool` buffer. `MacdExt`'s
`MaType` arguments are now passed as `byte` to match the `uint8_t` downcall.
- **R binding marshalled C ABI `bool` flags incorrectly.** `(bool *)REAL(x)`
reinterpreted the 8-byte doubles as 1-byte bools across the 15 cross-section
update wrappers, reading every flag as `false`; the flags are now converted into
a real C `bool` buffer.
- C# binding: added the `#nullable enable` directive the generated
`Indicators.g.cs` requires, clearing four `CS8669` warnings.
### Changed
- Renamed the `live_trading` examples to `live_binance` across the Python, Node.js,
WASM and C examples — they poll Binance market data, they do not place trades.
- **Breaking — de-duplicated four indicators that computed identically to another
one.** Each is now its own distinct, correctly-defined indicator (the catalogue
stays at the same count):
- `AverageDrawdown` now reports the mean of the maximum depths of the distinct
drawdown episodes in the window (previously the per-bar mean under-water
fraction, which equalled `PainIndex`).
- `IntradayIntensity` now reports the raw per-bar Bostian intensity
`volume * (2*close high low) / (high low)` (previously a cumulative line
that equalled the A/D Line `Adl`; its normalized form is `Cmf`).
- `AwesomeOscillatorHistogram` now reports the AO momentum
`AO[t] AO[tlookback]`; its third parameter is the momentum `lookback`
(default 1) instead of an SMA period (the old `AO SMA(AO, n)` equalled
`AcceleratorOscillator`).
- `AdOscillator` is now the Williams **A/D Oscillator** (`WAD SMA(WAD, 13)`),
distinct from the cumulative Williams A/D line `Wad`. Its native (Python /
Node.js / WASM) alias is renamed **`WilliamsAD``ADOSC`**.
## [0.9.1] - 2026-06-14
### Added
- C ABI hub: every indicator now exposes `wickra_<ind>_warmup_period` and
`wickra_<ind>_is_ready`, closing the gap with the native bindings (which
already had them). The C-ABI languages surface them idiomatically: C# `int
WarmupPeriod()` / `bool IsReady()`, Go `WarmupPeriod()` / `IsReady()`, Java
`int warmupPeriod()` / `boolean isReady()`, and R `warmup_period()` /
`is_ready()` generics. The alt-chart bar builders are excluded by design (a
candle can complete 0..n bars, so they have no warmup).
- Runnable rustdoc examples for 23 indicators that previously lacked one.
- A Requirements reference documenting the minimum supported version per
language — a new page in the documentation site plus README, marketing-site
and organization-profile sections.
### Changed
- Raised the minimum Node.js version to 20 — Node 18 reached end-of-life. The
prebuilt N-API addon is now tested on the active LTS lines (22 and 24).
- The Java binding now builds on the JDK 25 LTS in CI (JDK 22 reached
end-of-life); the published bytecode still targets Java 22, so the runtime
requirement is unchanged.
- Standardised programming-language naming and ordering across all docs, READMEs,
the documentation site, marketing site, organization profile and GitHub
repository descriptions. Canonical list:
`Rust, Python, Node.js, WASM, C, C++, C#, Go, Java, R`. Uses C# (not .NET) as
the language label, lists C and C++ separately, prefers `Node.js` and `WASM` in
prose, and frames the C ABI as a hub (`C ABI hub → …`) rather than a
language-list entry. Documentation only — no code or public API changes.
- Python binding: upgraded `pyo3` and `rust-numpy` from 0.28 to 0.29. No public
API changes; the full test suite passes unchanged.
### Fixed
- Corrected the internal casing of the `RelativeStrengthAB` binding wrappers,
which used `...Ab` (`WasmRelativeStrengthAb` in the WASM crate,
`RelativeStrengthAbNode` in the Node crate) while every other surface uses the
acronym `AB`. The published JS/WASM class name was already `RelativeStrengthAB`
(set via `js_name`/`js_class`), so the runtime API is unchanged; the only
visible change is the auto-generated TypeScript type alias, renamed
`RelativeStrengthAbNode``RelativeStrengthABNode` in `index.d.ts`.
### Security
- Resolved the pyo3 advisories RUSTSEC-2026-0176 (out-of-bounds read in
`PyList`/`PyTuple` `nth`/`nth_back`) and RUSTSEC-2026-0177 (missing `Sync`
bound on `PyCFunction::new_closure`) by upgrading to pyo3 0.29, which fixes
both. The upgrade was previously blocked upstream by rust-numpy 0.28 pinning
pyo3 `^0.28`; rust-numpy 0.29 lifts that pin. The not-affected exceptions are
removed from `deny.toml` and `osv-scanner.toml`.
## [0.9.0] - 2026-06-13
Maintenance release: Java build-dependency updates and CI/Dependabot
housekeeping only. No library code or public API changes.
### Changed
- Java binding: upgraded the test framework to JUnit Jupiter 6.1.0 (from
5.10.2) and bumped the Maven build plugins — `maven-compiler-plugin`
3.13.0 → 3.15.0, `maven-surefire-plugin` 3.2.5 → 3.5.6, `maven-jar-plugin`
3.4.1 → 3.5.0, `maven-source-plugin` 3.3.1 → 3.4.0, `maven-javadoc-plugin`
3.7.0 → 3.12.0, and `maven-gpg-plugin` 3.2.4 → 3.2.8.
- Java benchmarks and examples: bumped `maven-compiler-plugin` to 3.15.0 and
`exec-maven-plugin` to 3.6.3; examples bumped `jackson-databind` 2.17.1 →
2.22.0.
- Grouped Dependabot updates per ecosystem into a single pull request and
extended tracking to the NuGet (C#) binding and the Node/Go examples.
## [0.8.9] - 2026-06-12
Maintenance release: supply-chain and CI housekeeping only. No library code or
public API changes.
### Security
- Triaged the pyo3 advisories RUSTSEC-2026-0176 (out-of-bounds read in
`PyList`/`PyTuple` `nth`/`nth_back`) and RUSTSEC-2026-0177 (missing `Sync`
bound on `PyCFunction::new_closure`) as **not affecting Wickra**: neither
vulnerable API is reachable from the Python binding. Both are fixed in pyo3
0.29, but rust-numpy 0.28 pins pyo3 `^0.28`, so the upgrade is blocked
upstream; the advisories are recorded with their not-affected rationale in
`deny.toml` and `osv-scanner.toml` and will be cleared once rust-numpy 0.29
ships.
### Changed
- Java binding: bumped `central-publishing-maven-plugin` 0.5.0 → 0.10.0 (the
Maven Central publishing plugin used at release time).
- Bumped the SHA-pinned GitHub Actions used in CI (`actions/checkout`,
`actions/setup-go`, `actions/setup-java`, `github/codeql-action`,
`taiki-e/install-action`) to their latest releases.
- Added a Maven ecosystem to Dependabot so the Java binding's build plugins and
dependencies are tracked going forward.
## [0.8.8] - 2026-06-11
### Fixed
- R binding: declare `Depends: R (>= 2.10)`, clearing the `R CMD check` warning
("package needs dependence on R (>= 2.10)") that the bundled, lazy-loaded
`sample_ohlcv` dataset triggers on r-universe / CRAN.
## [0.8.7] - 2026-06-11
### Added
- R binding: a *Getting started* vignette and a synthetic `sample_ohlcv` example
dataset, giving new users a runnable, self-contained walkthrough and populating
the R-universe Articles and Datasets tabs. The vignette's code is exercised in
CI so a broken example is caught before the published build.
## [0.8.6] - 2026-06-11
### Changed
- Package registry metadata for better discoverability:
- R (R-universe): added the R-universe URL and `X-schema.org-keywords` to the
R `DESCRIPTION`, plus a package logo at `bindings/r/man/figures/logo.png`
(pkgdown convention).
- Python (PyPI): added a `Documentation` project URL.
- C# (NuGet): added a package icon via `PackageIcon`.
## [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
@@ -1443,7 +1836,24 @@ 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.7.7...HEAD
[Unreleased]: https://github.com/wickra-lib/wickra/compare/v0.9.4...HEAD
[0.9.4]: https://github.com/wickra-lib/wickra/compare/v0.9.3...v0.9.4
[0.9.3]: https://github.com/wickra-lib/wickra/compare/v0.9.2...v0.9.3
[0.9.2]: https://github.com/wickra-lib/wickra/compare/v0.9.1...v0.9.2
[0.9.1]: https://github.com/wickra-lib/wickra/compare/v0.9.0...v0.9.1
[0.9.0]: https://github.com/wickra-lib/wickra/compare/v0.8.9...v0.9.0
[0.8.9]: https://github.com/wickra-lib/wickra/compare/v0.8.8...v0.8.9
[0.8.8]: https://github.com/wickra-lib/wickra/compare/v0.8.7...v0.8.8
[0.8.7]: https://github.com/wickra-lib/wickra/compare/v0.8.6...v0.8.7
[0.8.6]: https://github.com/wickra-lib/wickra/compare/v0.8.5...v0.8.6
[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
+9 -3
View File
@@ -22,8 +22,10 @@ licensed as above, without any additional terms or conditions.
| `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/csharp` | C# 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. |
@@ -105,12 +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`. The C ABI
Python, Node.js, 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).
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
+66 -304
View File
@@ -32,12 +32,6 @@ version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
[[package]]
name = "anyhow"
version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "approx"
version = "0.5.1"
@@ -64,6 +58,12 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bit-set"
version = "0.8.0"
@@ -100,6 +100,12 @@ version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]]
name = "bytemuck"
version = "1.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
[[package]]
name = "bytes"
version = "1.11.1"
@@ -398,12 +404,6 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "foreign-types"
version = "0.3.2"
@@ -489,23 +489,10 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"libc",
"r-efi 5.3.0",
"r-efi",
"wasip2",
]
[[package]]
name = "getrandom"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
dependencies = [
"cfg-if",
"libc",
"r-efi 6.0.0",
"wasip2",
"wasip3",
]
[[package]]
name = "half"
version = "2.7.1"
@@ -517,15 +504,6 @@ dependencies = [
"zerocopy",
]
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.17.1"
@@ -636,12 +614,6 @@ dependencies = [
"zerovec",
]
[[package]]
name = "id-arena"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]]
name = "idna"
version = "1.1.0"
@@ -670,9 +642,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
"hashbrown 0.17.1",
"serde",
"serde_core",
"hashbrown",
]
[[package]]
@@ -712,12 +682,6 @@ dependencies = [
"thiserror",
]
[[package]]
name = "leb128fmt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "libc"
version = "0.2.186"
@@ -758,16 +722,6 @@ version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "matrixmultiply"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08"
dependencies = [
"autocfg",
"rawpointer",
]
[[package]]
name = "memchr"
version = "2.8.0"
@@ -869,21 +823,6 @@ dependencies = [
"tempfile",
]
[[package]]
name = "ndarray"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841"
dependencies = [
"matrixmultiply",
"num-complex",
"num-integer",
"num-traits",
"portable-atomic",
"portable-atomic-util",
"rawpointer",
]
[[package]]
name = "nu-ansi-term"
version = "0.50.3"
@@ -893,24 +832,6 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
@@ -943,22 +864,6 @@ dependencies = [
"syn",
]
[[package]]
name = "numpy"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "778da78c64ddc928ebf5ad9df5edf0789410ff3bdbf3619aed51cd789a6af1e2"
dependencies = [
"libc",
"ndarray",
"num-complex",
"num-integer",
"num-traits",
"pyo3",
"pyo3-build-config",
"rustc-hash",
]
[[package]]
name = "once_cell"
version = "1.21.4"
@@ -1002,6 +907,15 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
name = "openssl-src"
version = "300.5.4+3.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72"
dependencies = [
"cc",
]
[[package]]
name = "openssl-sys"
version = "0.9.116"
@@ -1010,6 +924,7 @@ checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4"
dependencies = [
"cc",
"libc",
"openssl-src",
"pkg-config",
"vcpkg",
]
@@ -1076,15 +991,6 @@ version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
[[package]]
name = "portable-atomic-util"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
dependencies = [
"portable-atomic",
]
[[package]]
name = "potential_utf"
version = "0.1.5"
@@ -1103,16 +1009,6 @@ dependencies = [
"zerocopy",
]
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro-crate"
version = "3.5.0"
@@ -1152,9 +1048,9 @@ dependencies = [
[[package]]
name = "pyo3"
version = "0.28.3"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12"
checksum = "cd274650b21d4bfc26a0a47587962c1edb425f69287324355cd040c3ea66071c"
dependencies = [
"libc",
"once_cell",
@@ -1166,18 +1062,18 @@ dependencies = [
[[package]]
name = "pyo3-build-config"
version = "0.28.3"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e"
checksum = "c5e2a7d2f0d013342f295c048ad19237add5154a55b1c5a254c0ec93d4109078"
dependencies = [
"target-lexicon",
]
[[package]]
name = "pyo3-ffi"
version = "0.28.3"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e"
checksum = "ca85c467da1bbc8d866eea5deff9cf29ea5f7785054a17da36e65bda9c05845b"
dependencies = [
"libc",
"pyo3-build-config",
@@ -1185,9 +1081,9 @@ dependencies = [
[[package]]
name = "pyo3-macros"
version = "0.28.3"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813"
checksum = "9ac53762fd065daa3194dd09337a38bd793a188100fd1a9304c4ab312d901771"
dependencies = [
"proc-macro2",
"pyo3-macros-backend",
@@ -1197,13 +1093,12 @@ dependencies = [
[[package]]
name = "pyo3-macros-backend"
version = "0.28.3"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb"
checksum = "4ca3a1557399783172dc5bf39cfca835157732532cba56b71d2292161e53b362"
dependencies = [
"heck",
"proc-macro2",
"pyo3-build-config",
"quote",
"syn",
]
@@ -1229,12 +1124,6 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rand"
version = "0.9.4"
@@ -1261,7 +1150,7 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
dependencies = [
"getrandom 0.3.4",
"getrandom",
]
[[package]]
@@ -1273,12 +1162,6 @@ dependencies = [
"rand_core",
]
[[package]]
name = "rawpointer"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
[[package]]
name = "rayon"
version = "1.12.0"
@@ -1328,12 +1211,6 @@ version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]]
name = "rustc-hash"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
[[package]]
name = "rustix"
version = "1.1.4"
@@ -1558,7 +1435,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
"getrandom 0.4.2",
"getrandom",
"once_cell",
"rustix",
"windows-sys",
@@ -1726,10 +1603,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
[[package]]
name = "unicode-xid"
version = "0.2.6"
name = "ureq"
version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
dependencies = [
"base64",
"log",
"native-tls",
"once_cell",
"url",
]
[[package]]
name = "url"
@@ -1792,16 +1676,7 @@ version = "1.0.3+wasi-0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
dependencies = [
"wit-bindgen 0.57.1",
]
[[package]]
name = "wasip3"
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
dependencies = [
"wit-bindgen 0.51.0",
"wit-bindgen",
]
[[package]]
@@ -1898,40 +1773,6 @@ version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f4d8ae7ad5440360e9799dfd42857d126454a88441ddf72d288ef83fa47f527"
[[package]]
name = "wasm-encoder"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
dependencies = [
"leb128fmt",
"wasmparser",
]
[[package]]
name = "wasm-metadata"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
dependencies = [
"anyhow",
"indexmap",
"wasm-encoder",
"wasmparser",
]
[[package]]
name = "wasmparser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags",
"hashbrown 0.15.5",
"indexmap",
"semver",
]
[[package]]
name = "web-sys"
version = "0.3.98"
@@ -1944,7 +1785,7 @@ dependencies = [
[[package]]
name = "wickra"
version = "0.7.7"
version = "0.9.4"
dependencies = [
"approx",
"criterion",
@@ -1955,7 +1796,7 @@ dependencies = [
[[package]]
name = "wickra-bench"
version = "0.7.7"
version = "0.9.4"
dependencies = [
"criterion",
"kand",
@@ -1967,14 +1808,16 @@ dependencies = [
[[package]]
name = "wickra-c"
version = "0.7.7"
version = "0.9.4"
dependencies = [
"tokio",
"wickra-core",
"wickra-data",
]
[[package]]
name = "wickra-core"
version = "0.7.7"
version = "0.9.4"
dependencies = [
"approx",
"proptest",
@@ -1984,24 +1827,26 @@ dependencies = [
[[package]]
name = "wickra-data"
version = "0.7.7"
version = "0.9.4"
dependencies = [
"approx",
"csv",
"futures-util",
"native-tls",
"serde",
"serde_json",
"tempfile",
"thiserror",
"tokio",
"tokio-tungstenite",
"ureq",
"url",
"wickra-core",
]
[[package]]
name = "wickra-examples"
version = "0.7.7"
version = "0.9.4"
dependencies = [
"serde_json",
"tokio",
@@ -2011,26 +1856,30 @@ dependencies = [
[[package]]
name = "wickra-node"
version = "0.7.7"
version = "0.9.4"
dependencies = [
"napi",
"napi-build",
"napi-derive",
"tokio",
"wickra-core",
"wickra-data",
]
[[package]]
name = "wickra-python"
version = "0.7.7"
version = "0.9.4"
dependencies = [
"numpy",
"bytemuck",
"pyo3",
"tokio",
"wickra-core",
"wickra-data",
]
[[package]]
name = "wickra-wasm"
version = "0.7.7"
version = "0.9.4"
dependencies = [
"console_error_panic_hook",
"js-sys",
@@ -2039,6 +1888,7 @@ dependencies = [
"wasm-bindgen",
"wasm-bindgen-test",
"wickra-core",
"wickra-data",
]
[[package]]
@@ -2096,100 +1946,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "wit-bindgen"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
dependencies = [
"wit-bindgen-rust-macro",
]
[[package]]
name = "wit-bindgen"
version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
name = "wit-bindgen-core"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
dependencies = [
"anyhow",
"heck",
"wit-parser",
]
[[package]]
name = "wit-bindgen-rust"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [
"anyhow",
"heck",
"indexmap",
"prettyplease",
"syn",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
]
[[package]]
name = "wit-bindgen-rust-macro"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
dependencies = [
"anyhow",
"prettyplease",
"proc-macro2",
"quote",
"syn",
"wit-bindgen-core",
"wit-bindgen-rust",
]
[[package]]
name = "wit-component"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags",
"indexmap",
"log",
"serde",
"serde_derive",
"serde_json",
"wasm-encoder",
"wasm-metadata",
"wasmparser",
"wit-parser",
]
[[package]]
name = "wit-parser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [
"anyhow",
"id-arena",
"indexmap",
"log",
"semver",
"serde",
"serde_derive",
"serde_json",
"unicode-xid",
"wasmparser",
]
[[package]]
name = "writeable"
version = "0.6.3"
+5 -4
View File
@@ -14,7 +14,7 @@ members = [
exclude = ["fuzz"]
[workspace.package]
version = "0.7.7"
version = "0.9.4"
authors = ["kingchenc <support@wickra.org>"]
edition = "2021"
rust-version = "1.86"
@@ -26,7 +26,8 @@ keywords = ["finance", "trading", "indicators", "technical-analysis", "ta"]
categories = ["finance", "mathematics", "science"]
[workspace.dependencies]
wickra-core = { path = "crates/wickra-core", version = "0.7.7" }
wickra-core = { path = "crates/wickra-core", version = "0.9.4" }
wickra-data = { path = "crates/wickra-data", version = "0.9.4" }
thiserror = "2"
rayon = "1.10"
@@ -37,8 +38,8 @@ approx = "0.5"
criterion = { version = "0.8", features = ["html_reports"] }
# Python binding
pyo3 = { version = "0.28", features = ["extension-module", "abi3-py39"] }
numpy = "0.28"
pyo3 = { version = "0.29", features = ["extension-module", "abi3-py39"] }
numpy = "0.29"
[workspace.lints.rust]
unsafe_code = "forbid"
+142 -37
View File
@@ -10,28 +10,32 @@
[![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)
[![Verified across 10 languages](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/verified.svg)](https://docs.wickra.org/FAQ#do-all-the-language-bindings-compute-the-same-values)
**Streaming-first technical indicators. Install with `pip install wickra` — no system dependencies.**
**Streaming-first technical indicators. Install with `pip install wickra` — no system dependencies, zero third-party packages.**
Wickra is a multi-language technical-analysis library with a Rust core and
native bindings for Python, Node.js and WebAssembly, plus a C ABI that C, C++,
C# / .NET, Go and any other C-capable language links against. Every indicator is a
native bindings for Python, Node.js and WASM, plus a C ABI that C, C++,
C#, 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
import numpy as np
import wickra as ta
import wickra as ta # zero third-party deps — not even NumPy
# Batch: classic TA-Lib-style usage
prices = np.linspace(100, 200, 1000)
prices = [100.0 + i * 0.1 for i in range(1000)]
rsi = ta.RSI(14)
values = rsi.batch(prices) # numpy array, NaN during warmup
values = rsi.batch(prices) # array.array('d'), NaN during warmup
# np.asarray(values) wraps it zero-copy if you use NumPy
# Streaming: same indicator, fed tick by tick
rsi = ta.RSI(14)
@@ -50,8 +54,11 @@ Full documentation lives at **[docs.wickra.org](https://docs.wickra.org)**:
[Node](https://docs.wickra.org/Quickstart-Node),
[WASM](https://docs.wickra.org/Quickstart-WASM),
[C](https://docs.wickra.org/Quickstart-C),
[C++](https://docs.wickra.org/Quickstart-C),
[C#](https://docs.wickra.org/Quickstart-CSharp),
[Go](https://docs.wickra.org/Quickstart-Go).
[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 514 indicators; start at the
[indicators overview](https://docs.wickra.org/Indicators-Overview).
@@ -76,14 +83,19 @@ times to get there.
breadth, Renko/Kagi/Point&Figure bars, Ehlers DSP cycles, risk/performance
metrics — every single one updating in **O(1) per tick**. TA-Lib ships ~150 and
none of them stream.
- **One Rust core, five first-class targets.** Native **Python · Node.js ·
WebAssembly · Rust** plus a **C ABI** for C / C++, C# / .NET, Go and any other C-capable language —
- **One Rust core, five first-class targets.** Native **Rust · Python · Node.js ·
WASM** plus a **C ABI** for C, C++, C#, Go, Java, R and any other C-capable language —
identical math, identical results, zero per-language reimplementation and zero
GIL bottleneck.
- **Correct by construction, not by hope.** Every `update` validates its input,
runs a real warmup, and returns an `Option` so a single bad tick can't silently
poison state. `batch == streaming` is **bit-exact, fuzzed and 100 %-line-covered
for all 514 indicators**.
- **Identical across every language — proven, not promised.** All 514 indicators
are replayed through **all 10 languages** (Rust · Python · Node.js · WASM · C ·
C++ · C# · Go · Java · R) and checked **bit-for-bit against the Rust reference**
via shared golden fixtures in CI. The math is verifiably the same everywhere —
this very check caught and fixed two real cross-language marshalling bugs.
- **Orders of magnitude faster where it counts.** In streaming Wickra is **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
@@ -92,8 +104,13 @@ times to get there.
- **Install in one line, anywhere.** `pip install wickra` / `npm install wickra`
precompiled wheels and binaries, **no C toolchain, none of TA-Lib's setup pain**.
macOS · Linux · Windows.
- **Batteries included.** Indicator chaining, a streaming OHLCV CSV reader, and a
live Binance kline feed ship in the box.
- **Batteries included — zero third-party deps, in every language.** A full native
data layer ships in the box: a CSV candle reader, a tick-to-candle aggregator, a
timeframe resampler, a live Binance WebSocket feed and a historical Binance REST
fetcher — in **all 10 languages**. Loading a CSV, rolling ticks into candles,
resampling and streaming live data needs **no foreign package** — no pandas, no
`csv-parse`, no `ws`/`websockets`, no `jackson`, no `jsonlite`, not even NumPy.
`pip install wickra` / `npm install wickra` / `go get` / … pulls **nothing else**.
- **Truly permissive.** **MIT OR Apache-2.0** — drop it straight into commercial
and closed-source work.
@@ -101,8 +118,7 @@ Every other library forces one of those compromises. Wickra doesn't:
| Library | Install | Streaming | Languages | Indicators | Active |
|------------------|-------------|-------------|-----------------------------|-----------:|--------|
| **★&nbsp;Wickra**| **clean** | **yes, O(1)** | **Rust · Python · Node · WASM** | **514** | **yes** |
| | | | **C · C# · Go** | | |
| **★&nbsp;Wickra**| **clean** | **yes, O(1)** | **Rust · Python · Node.js · WASM · C · 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 |
@@ -133,12 +149,38 @@ elsewhere for `None`-warmup, NaN-safety and bit-exact `batch == streaming`.
Full tables (Rust + Python, streaming + batch) and how to reproduce them live in
**[BENCHMARKS.md](BENCHMARKS.md)**.
### Pick your language with eyes open — per-binding throughput
Every binding calls the **same** Rust core, so this is **not** a speed claim — it
is the raw cost of crossing each language's FFI boundary (`SMA(20)`, 200 000 bars,
Ryzen 9 9950X, million updates/sec). **Batch stays high for most bindings;
streaming is where the boundary shows** — so if you stream tick-by-tick, the table
tells you which binding keeps up and which to avoid for hot loops.
| Language | streaming (Mupd/s) | batch (Mupd/s) |
|-----------------|-------------------:|---------------:|
| Rust (no FFI) | 380 | 498 |
| C / C++ | 365 | 358 |
| C# | 348 | 259 |
| Python | 31 | 46 |
| Java | 38 | 173 |
| Go | 23 | 394 |
| WASM | 21 | 169 |
| Node.js | 16 | 9 |
| R | 0.1 | 279 |
All ten share one verified implementation (see the verification badge above), so
the *numbers* differ but the *values* are bit-for-bit identical. Methodology and
the per-indicator breakdown are in [BENCHMARKS.md](BENCHMARKS.md#3-per-binding-throughput--the-cost-of-the-boundary).
## Indicators
514 streaming-first indicators across twenty-four families. Every one passes the
`batch == streaming` equivalence test, reference-value tests, and reset
semantics tests. Each has a per-indicator deep dive (formula, parameters,
warmup) at [docs.wickra.org](https://docs.wickra.org/Indicators-Overview).
semantics tests — and is replayed through **all 10 languages** and checked
bit-for-bit against the Rust reference (golden fixtures, in CI). Each has a
per-indicator deep dive (formula, parameters, warmup) at
[docs.wickra.org](https://docs.wickra.org/Indicators-Overview).
| Family | Indicators |
|--------|-----------|
@@ -149,7 +191,7 @@ warmup) at [docs.wickra.org](https://docs.wickra.org/Indicators-Overview).
| Volatility & Bands | ATR, Bollinger Bands, Keltner Channels, Donchian Channels, NATR, StdDev, Ulcer Index, Historical Volatility, Bollinger Bandwidth, %B, True Range, Chaikin Volatility, RVI (Relative Volatility Index), Parkinson Volatility, Garman-Klass Volatility, Rogers-Satchell Volatility, Yang-Zhang Volatility, Volatility Cone |
| Bands & Channels | MA Envelope, Acceleration Bands, STARC Bands, ATR Bands, Hurst Channel, LinReg Channel, Standard Error Bands, Double Bollinger Bands, TTM Squeeze, Fractal Chaos Bands, VWAP StdDev Bands, Quartile Bands, Bomar Bands, Median Channel, Projection Bands, Projection Oscillator |
| Trailing Stops | Parabolic SAR, Parabolic SAR Extended (SAREXT), SuperTrend, Chandelier Exit, Chande Kroll Stop, ATR Trailing Stop, HiLo Activator, Volty Stop, Yo-Yo Exit, Donchian Channel Stop, Percentage Trailing Stop, Step Trailing Stop, Renko Trailing Stop, Kase DevStop, Elder SafeZone, ATR Ratchet, NRTR, Time-Based Stop, Modified MA Stop |
| Volume | OBV, VWAP (cumulative + rolling), ADL, Volume-Price Trend, Chaikin Money Flow, Chaikin Oscillator, Force Index, Ease of Movement, Klinger Volume Oscillator, Volume Oscillator, NVI, PVI, Williams A/D, Anchored VWAP, Demand Index, TSV, VZO, Market Facilitation Index, Volume RSI, Williams Accumulation/Distribution, Twiggs Money Flow, Trade Volume Index, Intraday Intensity Index, Better Volume, Volume-Weighted MACD |
| Volume | OBV, VWAP (cumulative + rolling), ADL, Volume-Price Trend, Chaikin Money Flow, Chaikin Oscillator, Force Index, Ease of Movement, Klinger Volume Oscillator, Volume Oscillator, NVI, PVI, Williams A/D Oscillator, Anchored VWAP, Demand Index, TSV, VZO, Market Facilitation Index, Volume RSI, Williams Accumulation/Distribution, Twiggs Money Flow, Trade Volume Index, Intraday Intensity, Better Volume, Volume-Weighted MACD |
| Price Statistics | Typical Price, Median Price, Weighted Close, Linear Regression, Linear Regression Slope, Z-Score, Linear Regression Angle, Variance, Coefficient of Variation, Skewness, Kurtosis, Standard Error, Detrended StdDev, R², Median Absolute Deviation, Autocorrelation, Hurst Exponent, Pearson Correlation, Beta, Pairwise Beta, Pair Spread Z-Score, Lead-Lag Cross-Correlation, Cointegration, Relative Strength A-vs-B, Spearman Correlation, Mid Price, Mid Point, Average Price, Linear Regression Intercept, Time Series Forecast, Rolling Correlation, Rolling Covariance, OU Half-Life, Spread Hurst, Distance SSD, Beta-Neutral Spread, Variance Ratio, Granger Causality, Kalman Hedge Ratio, Spread Bollinger Bands, Spread AR(1) Coefficient, Jarque-Bera, Rolling Min-Max Scaler, Shannon Entropy, Sample Entropy, Kendall Tau |
| Ehlers / Cycle (DSP) | MAMA, FAMA, Fisher Transform, Inverse Fisher Transform, SuperSmoother, Hilbert Dominant Cycle, Hilbert Phasor, Hilbert DC Phase, Hilbert Trend Mode, Sine Wave, Decycler, Decycler Oscillator, Roofing Filter, Center of Gravity, Cybernetic Cycle, Adaptive Cycle, Empirical Mode Decomposition, Ehlers Stochastic, Instantaneous Trendline, Highpass Filter, Reflex, Trendflex, Correlation Trend Indicator, Adaptive RSI, Universal Oscillator, Adaptive CCI, Bandpass Filter, Even Better Sinewave, Autocorrelation Periodogram |
| Pivots & S/R | Classic Pivots, Fibonacci Pivots, Camarilla, Woodie Pivots, DeMark Pivots, Williams Fractals, ZigZag, Central Pivot Range, Murrey Math Lines, Andrews Pitchfork, Volume-Weighted Support/Resistance, Pivot Reversal |
@@ -174,7 +216,7 @@ 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; every binding
inherits it automatically (the C ABI — and the C# and Go bindings generated from
inherits it automatically (the C ABI — and the C#, Go, Java and R bindings generated from
it — regenerate from the core).
## Languages
@@ -186,8 +228,10 @@ it — regenerate from the core).
| 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` |
| C# (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.
@@ -196,6 +240,29 @@ 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`).
## Requirements
The minimum supported version per language. Prebuilt packages (Rust, Python,
Node.js, WASM, C#) need only the runtime; the C-ABI bindings that compile on
install — Go (cgo) and R (`.Call`) — also need a C compiler, and Java runs with
`--enable-native-access=ALL-UNNAMED`.
| Language | Package | Minimum supported |
|-------------|--------------------------------------|----------------------------|
| Rust | crates.io · `wickra` | 1.86 (MSRV) |
| Python | PyPI · `wickra` (abi3 wheel) | 3.9 (tested through 3.13) |
| Node.js | npm · `wickra` (N-API 8) | 20 (tested on 22 · 24 LTS) |
| WASM | npm · `wickra-wasm` | any modern JS engine |
| C | `wickra.h` + library (releases) | C99 compiler |
| C++ | `wickra.hpp` over the C ABI | C++14 compiler |
| C# | NuGet · `Wickra` | .NET 8 (`net8.0`) |
| Go | module · `wickra-lib/wickra-go` | Go 1.23 (cgo) |
| Java | Maven Central · `org.wickra:wickra` | Java 22 (FFM / Panama) |
| R | source package | R ≥ 2.10 (Rtools on Win.) |
Full per-language detail (runtime vs. build-from-source) is on the
[Requirements page](https://docs.wickra.org/Requirements) in the docs.
## Rust API
```rust
@@ -219,12 +286,17 @@ chain.update(price);
## Live data sources
`wickra-data` (separate crate, opt-in) ships:
Wickra ships a complete, **native data layer** — exposed in **all 10 languages**,
pulling **zero third-party packages** (no pandas / `csv-parse` / `ws` / `jackson`
/ `jsonlite`). In Rust it lives in the `wickra-data` crate; every binding exposes
the same building blocks:
- A streaming OHLCV **CSV reader**.
- A **tick-to-candle aggregator** with arbitrary timeframes.
- A **candle resampler** for multi-timeframe analysis (1m → 5m → 1h on the fly).
- A **Binance Spot WebSocket** kline adapter (feature `live-binance`).
- A streaming OHLCV **CSV reader** (`CandleReader`).
- A **tick-to-candle aggregator** with arbitrary timeframes (`TickAggregator`).
- A **candle resampler** for multi-timeframe analysis (1m → 5m → 1h on the fly, `Resampler`).
- A live **Binance Spot WebSocket** kline feed (`BinanceFeed`, feature `live-binance`).
- A historical **Binance REST** kline fetcher (`fetch_binance_klines`) — native
HTTP + JSON, no third-party client.
```rust
use wickra::{Indicator, Rsi};
@@ -241,8 +313,9 @@ while let Some(event) = stream.next_event().await? {
}
```
A Python live-trading example using the public `websockets` package lives at
`examples/python/live_trading.py`.
Native live-feed and historical-fetch examples — using `wickra.BinanceFeed` and
`wickra.fetch_binance_klines`, **with no third-party HTTP/WebSocket client** — live
under `examples/python/` (and the matching directory for every other language).
## Project layout
@@ -258,17 +331,21 @@ wickra/
│ ├── node/ napi-rs (publishes on npm)
│ ├── 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)
│ ├── csharp/ C# 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`)
│ ├── python/ backtest, live Binance feed, parallel assets, multi-tf
│ ├── node/ streaming, backtest, live Binance feed (load `wickra`)
│ ├── wasm/ browser demo for `wickra-wasm`
│ ├── c/ C smoke + streaming, C++ RAII wrapper
│ ├── csharp/ streaming, backtest, strategies (load `Wickra`)
── go/ streaming, backtest, strategies (cgo binding)
── go/ streaming, backtest, strategies (cgo binding)
│ ├── r/ streaming, backtest, strategies (.Call binding)
│ └── java/ streaming, backtest, strategies (FFM binding)
└── .github/workflows/ CI and release pipelines
```
@@ -302,12 +379,19 @@ 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)
# C# 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
@@ -317,7 +401,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
@@ -327,8 +414,26 @@ 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.
On top of those per-binding tests, **all 10 languages** (Rust, Python, Node.js,
WASM, C, C++, C#, Go, Java, R) replay a shared, language-neutral golden fixture
(`testdata/golden/*.csv`, generated by
`cargo run -p wickra-examples --bin gen_golden`) and assert **bit-for-bit parity
with the Rust reference for every one of the 514 indicators** across every
archetype (scalar, multi-output, pairwise, derivatives-tick, cross-section,
order-book, trade, profile, alt-chart bars, footprint). This catches FFI wiring
bugs the math-only core tests cannot see — it has already found and fixed real
cross-language marshalling bugs in the Java and R bindings.
## Contributing
@@ -385,13 +490,13 @@ The library is provided **as is**, without warranty of any kind; see
<p align="center">
<a href="https://github.com/wickra-lib/wickra/stargazers">
<img alt="GitHub stars" src="https://img.shields.io/github/stars/wickra-lib/wickra?style=for-the-badge&logo=github&logoColor=white&color=ffd866">
<img alt="GitHub stars" src="https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/stars.svg">
</a>
<a href="https://github.com/wickra-lib/wickra/network/members">
<img alt="GitHub forks" src="https://img.shields.io/github/forks/wickra-lib/wickra?style=for-the-badge&logo=github&logoColor=white&color=78dce8">
<img alt="GitHub forks" src="https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/forks.svg">
</a>
<a href="https://github.com/wickra-lib/wickra/issues">
<img alt="GitHub issues" src="https://img.shields.io/github/issues/wickra-lib/wickra?style=for-the-badge&logo=github&logoColor=white&color=ff6188">
<img alt="GitHub issues" src="https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/issues.svg">
</a>
</p>
@@ -402,6 +507,6 @@ The library is provided **as is**, without warranty of any kind; see
<p align="center">
<a href="https://star-history.com/#wickra-lib/wickra&Date">
<img alt="Wickra star history" width="640"
src="https://api.star-history.com/svg?repos=wickra-lib/wickra&type=Date&theme=dark">
src="https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/star-history.svg">
</a>
</p>
+2 -2
View File
@@ -21,8 +21,8 @@ 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 — plus
the C ABI and the C# / .NET and Go bindings generated from it — in lockstep with the
- **Bindings parity.** Keep the Python, Node.js and WASM bindings — plus
the C ABI and the C#, 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.
+3 -3
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.9.4`
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.9.4 (latest) | :white_check_mark: |
| < 0.9.4 | :x: |
## Reporting a vulnerability
+1 -1
View File
@@ -7,7 +7,7 @@ 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, WebAssembly, C, C# and Go, a per-indicator reference, warmup periods, the
Node.js, WASM, C, 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.
+3 -3
View File
@@ -3,8 +3,8 @@
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 plus a C ABI
and the .NET and Go bindings built on it),
library (a Rust core with Python, Node.js and WASM bindings plus a C ABI
and the C#, Go, Java and R bindings built on it),
not a network service or trading system; the attack surface is correspondingly
small.
@@ -33,7 +33,7 @@ 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 catches panics so none cross the boundary. 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. |
| 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. |
+27 -1
View File
@@ -42,5 +42,31 @@ 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.
#
# `live-binance` is on by default too so the published C ABI ships the native
# Binance kline feed (tokio + TLS WebSocket) — the six C-ABI languages with no
# native WebSocket (C, C++, Go, R, plus Node/Python via their own bindings) get
# it without a third-party client. The wasm build drops it via
# --no-default-features (a browser has no raw TCP/TLS sockets; WASM users use the
# host `WebSocket`), exactly like rayon.
default = ["parallel", "live-binance"]
parallel = ["wickra-core/parallel"]
live-binance = ["wickra-data/live-binance", "dep:tokio"]
[dependencies]
wickra-core = { workspace = true }
# 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 }
wickra-data = { path = "../../crates/wickra-data" }
# Only pulled in by `live-binance`: a single-thread runtime drives the async
# Binance feed behind the blocking C-ABI poll. The feed's own I/O features come
# transitively from wickra-data; this just needs the runtime + timeout.
tokio = { version = "1", features = ["rt", "net", "time"], optional = true }
+25 -4
View File
@@ -9,12 +9,12 @@
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 and any
bindings for Python, Node.js and WASM, 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
C-capable language (C, C++, C#, Go, Java, R) links against one artifact instead
of re-wrapping every indicator natively.
## Install
@@ -54,6 +54,20 @@ Multi-output indicators (MACD, Bollinger, ADX, …) take a pointer to a `#[repr(
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
@@ -63,10 +77,17 @@ the main repository and documentation site:
- **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 —
Wickra ships native bindings for Python, Node.js, WASM and Rust, plus this
C ABI hub that any C-capable language (C, C++, C#, Go, Java, R) links against —
all exposing the same indicators from the shared Rust core.
## Security
Found a security issue? **Please don't open a public issue.** Report it privately
via the affected repository's *Security* tab (*"Report a vulnerability"*) or email
**support@wickra.org** with a subject line starting `[wickra security]`. Full
policy: <https://github.com/wickra-lib/wickra/blob/main/SECURITY.md>.
## Disclaimer
Wickra is an indicator toolkit, not a trading system. The values it computes
+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;
}
+1 -1
View File
@@ -17,4 +17,4 @@ documentation = false
# discovered and emitted as forward-declared opaque structs. Their fields are
# never exposed — only `T *` handles cross the boundary.
parse_deps = true
include = ["wickra-core"]
include = ["wickra-core", "wickra-data"]
File diff suppressed because it is too large Load Diff
+24455 -5
View File
File diff suppressed because it is too large Load Diff
+25 -6
View File
@@ -1,18 +1,18 @@
# Wickra — .NET
# Wickra — 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)
[![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`
**Streaming-first technical indicators for C#. `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 and any
bindings for Python, Node.js and WASM, 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
the exact same implementation. This package is the C# binding; it consumes the
C ABI hub through `[LibraryImport]` P/Invoke and exposes all 514 streaming-first
indicators as idiomatic `IDisposable` classes.
@@ -52,6 +52,18 @@ foreach (var price in liveFeed)
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
@@ -61,10 +73,17 @@ the main repository and documentation site:
- **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 —
Wickra ships native bindings for Python, Node.js, WASM 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.
## Security
Found a security issue? **Please don't open a public issue.** Report it privately
via the affected repository's *Security* tab (*"Report a vulnerability"*) or email
**support@wickra.org** with a subject line starting `[wickra security]`. Full
policy: <https://github.com/wickra-lib/wickra/blob/main/SECURITY.md>.
## Disclaimer
Wickra is an indicator toolkit, not a trading system. The values it computes
@@ -34,6 +34,21 @@ public class ArchetypeTests
Assert.InRange(last, 1.0, 10.0);
}
[Fact]
public void Query_WarmupPeriodAndIsReady()
{
using var sma = new Sma(3);
Assert.Equal(3, sma.WarmupPeriod());
Assert.False(sma.IsReady());
sma.Update(1.0);
sma.Update(2.0);
Assert.False(sma.IsReady());
sma.Update(3.0);
Assert.True(sma.IsReady());
sma.Reset();
Assert.False(sma.IsReady());
}
[Fact]
public void Candle_Atr_IsFinitePositive()
{
@@ -0,0 +1,53 @@
using System;
using Wickra;
using Xunit;
// The live Binance feed's connect → read → reconnect pipeline is covered
// deterministically by the Rust mock-WS-server tests in wickra-data. Here we
// only assert the binding's error paths, which need no network.
public class BinanceFeedTests
{
[Fact]
public void RejectsUnknownInterval()
{
Assert.Throws<ArgumentException>(() =>
new BinanceFeed("BTCUSDT", (BinanceInterval)99));
}
[Fact]
public void RejectsEmptySymbols()
{
Assert.Throws<ArgumentException>(() =>
new BinanceFeed("", BinanceInterval.OneMinute));
}
[Fact]
public void RejectsUnreachableEndpoint()
{
Assert.Throws<ArgumentException>(() =>
new BinanceFeed("BTCUSDT", BinanceInterval.OneMinute, "ws://127.0.0.1:1"));
}
// The REST fetcher's parse/HTTP success path is covered by the Rust
// mock-HTTP-server tests; here we only assert the binding's error paths.
[Fact]
public void FetchKlinesRejectsUnknownInterval()
{
Assert.Throws<ArgumentException>(() =>
BinanceFeed.FetchKlines("BTCUSDT", (BinanceInterval)99, 1));
}
[Fact]
public void FetchKlinesRejectsZeroLimit()
{
Assert.Throws<ArgumentException>(() =>
BinanceFeed.FetchKlines("BTCUSDT", BinanceInterval.OneHour, 0));
}
[Fact]
public void FetchKlinesSurfacesUnreachableEndpoint()
{
Assert.Throws<ArgumentException>(() =>
BinanceFeed.FetchKlines("BTCUSDT", BinanceInterval.OneHour, 1, baseUrl: "http://127.0.0.1:1"));
}
}
@@ -0,0 +1,110 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using Wickra;
using Xunit;
// Cross-language data-layer parity: replay the shared golden tick stream through
// the TickAggregator and check the candles against the Rust reference, with and
// without gap filling.
public class DataLayerTests
{
private static string GoldenDir([System.Runtime.CompilerServices.CallerFilePath] string file = "") =>
Path.GetFullPath(Path.Combine(Path.GetDirectoryName(file)!, "..", "..", "..", "testdata", "golden"));
private static double[][] Read(string name)
{
var lines = File.ReadAllLines(Path.Combine(GoldenDir(), name + ".csv"));
var rows = new List<double[]>();
for (var i = 1; i < lines.Length; i++)
{
if (lines[i].Length == 0)
{
continue;
}
rows.Add(Array.ConvertAll(lines[i].Split(','), s => double.Parse(s, CultureInfo.InvariantCulture)));
}
return rows.ToArray();
}
[Fact]
public void ResamplerMatchesGolden()
{
var input = Read("input"); // open,high,low,close,volume (timestamp = row index)
using var r = new Resampler(5);
var got = new List<double[]>();
for (var i = 0; i < input.Length; i++)
{
var c = r.Update(input[i][0], input[i][1], input[i][2], input[i][3], input[i][4], i);
if (c.HasValue)
{
got.Add(new[] { c.Value.Open, c.Value.High, c.Value.Low, c.Value.Close, c.Value.Volume, (double)c.Value.Timestamp });
}
}
var f = r.Flush();
if (f.HasValue)
{
got.Add(new[] { f.Value.Open, f.Value.High, f.Value.Low, f.Value.Close, f.Value.Volume, (double)f.Value.Timestamp });
}
var want = Read("data_resampled");
Assert.Equal(want.Length, got.Count);
for (var i = 0; i < got.Count; i++)
{
for (var j = 0; j < 6; j++)
{
var tol = 1e-9 * Math.Max(1, Math.Abs(want[i][j]));
Assert.True(Math.Abs(got[i][j] - want[i][j]) <= tol, $"resample row {i} col {j}: {got[i][j]} vs {want[i][j]}");
}
}
}
[Fact]
public void CandleReaderMatchesGolden()
{
var csv = File.ReadAllText(Path.Combine(GoldenDir(), "data_csv.csv"));
using var reader = new CandleReader(csv);
var candles = reader.Read();
var want = Read("data_csv_candles");
Assert.Equal(want.Length, candles.Length);
for (var i = 0; i < candles.Length; i++)
{
var c = candles[i];
var got = new[] { c.Open, c.High, c.Low, c.Close, c.Volume, (double)c.Timestamp };
for (var j = 0; j < 6; j++)
{
var tol = 1e-9 * Math.Max(1, Math.Abs(want[i][j]));
Assert.True(Math.Abs(got[j] - want[i][j]) <= tol, $"candle reader row {i} col {j}: {got[j]} vs {want[i][j]}");
}
}
}
[Theory]
[InlineData(false, "data_candles")]
[InlineData(true, "data_candles_gap")]
public void TickAggregatorMatchesGolden(bool gapFill, string fixture)
{
var ticks = Read("data_ticks");
using var agg = new TickAggregator(1000, gapFill);
var got = new List<double[]>();
foreach (var t in ticks)
{
foreach (var c in agg.Push(t[0], t[1], (long)t[2]))
{
got.Add(new[] { c.Open, c.High, c.Low, c.Close, c.Volume, (double)c.Timestamp });
}
}
var want = Read(fixture);
Assert.Equal(want.Length, got.Count);
for (var i = 0; i < got.Count; i++)
{
for (var j = 0; j < 6; j++)
{
var tol = 1e-9 * Math.Max(1, Math.Abs(want[i][j]));
Assert.True(
Math.Abs(got[i][j] - want[i][j]) <= tol,
$"{fixture} row {i} col {j}: {got[i][j]} vs {want[i][j]}");
}
}
}
}
File diff suppressed because it is too large Load Diff
+216
View File
@@ -0,0 +1,216 @@
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");
}
}
// --- the four de-duplicated indicators ------------------------------------
[Fact]
public void Candle_AdOscillator_MatchesGolden()
{
var input = Input();
var expected = ReadCsv("ad_oscillator");
using var ad = new AdOscillator();
for (var i = 0; i < input.Length; i++)
{
var (o, h, l, c, v) = (input[i][0], input[i][1], input[i][2], input[i][3], input[i][4]);
AssertClose(ad.Update(o, h, l, c, v, i), Cell(expected[i][0]), i, "ad_oscillator");
}
}
[Fact]
public void Candle_IntradayIntensity_MatchesGolden()
{
var input = Input();
var expected = ReadCsv("intraday_intensity");
using var ii = new IntradayIntensity();
for (var i = 0; i < input.Length; i++)
{
var (o, h, l, c, v) = (input[i][0], input[i][1], input[i][2], input[i][3], input[i][4]);
AssertClose(ii.Update(o, h, l, c, v, i), Cell(expected[i][0]), i, "intraday_intensity");
}
}
[Fact]
public void Candle_AwesomeOscillatorHistogram_MatchesGolden()
{
var input = Input();
var expected = ReadCsv("awesome_oscillator_histogram");
using var aoh = new AwesomeOscillatorHistogram(5, 34, 1);
for (var i = 0; i < input.Length; i++)
{
var (o, h, l, c, v) = (input[i][0], input[i][1], input[i][2], input[i][3], input[i][4]);
AssertClose(aoh.Update(o, h, l, c, v, i), Cell(expected[i][0]), i, "awesome_oscillator_histogram");
}
}
[Fact]
public void Scalar_AverageDrawdown_MatchesGolden()
{
var input = Input();
var expected = ReadCsv("average_drawdown");
using var avg = new AverageDrawdown(20);
for (var i = 0; i < input.Length; i++)
{
// generator fed the close column as the equity-curve sample.
AssertClose(avg.Update(input[i][3]), Cell(expected[i][0]), i, "average_drawdown");
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -11,7 +11,7 @@
<!-- NuGet package metadata -->
<PackageId>Wickra</PackageId>
<Version>0.7.7</Version>
<Version>0.9.4</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>
@@ -20,6 +20,7 @@
<RepositoryType>git</RepositoryType>
<PackageTags>technical-analysis;indicators;trading;finance;streaming;ffi;native</PackageTags>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageIcon>icon.png</PackageIcon>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
@@ -42,6 +43,7 @@
<ItemGroup>
<None Include="..\README.md" Pack="true" PackagePath="\" />
<None Include="..\icon.png" Pack="true" PackagePath="\" />
</ItemGroup>
<!--
+7
View File
@@ -23,6 +23,13 @@ internal static class WickraNative
// Any exported symbol works as a fingerprint; sma_new exists in every build.
private const string SentinelSymbol = "wickra_sma_new";
// CA2255 warns against [ModuleInitializer] in libraries, but registering the
// native-library resolver before any P/Invoke runs is exactly the advanced
// scenario the attribute exists for: a static constructor would run too late
// (only on first access to this type), letting the default resolver fail first.
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Usage", "CA2255:The 'ModuleInitializer' attribute should not be used in libraries",
Justification = "The DllImport resolver must be registered before the first P/Invoke; a static constructor would run too late.")]
[ModuleInitializer]
internal static void Register()
{
@@ -0,0 +1,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.");
+304
View File
@@ -0,0 +1,304 @@
"""Generate Wickra.Tests/GoldenAllTests.g.cs: a value-parity test that replays
the shared golden input through every one of the 514 C# indicators and checks
output bit-for-bit against the Rust reference fixtures g_<Canonical>.csv.
Run from repo root: python bindings/csharp/gen_golden_test.py
"""
import glob
import json
import os
import re
ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", ".."))
G = os.path.join(ROOT, "testdata", "golden")
GEN = open(os.path.join(ROOT, "bindings", "csharp", "Wickra", "Generated", "Indicators.g.cs"), encoding="utf-8").read()
# Canonical core Indicator::name() per indicator, shared across every binding.
NAMES = json.load(open(os.path.join(G, "names.json")))
# C# constructor parameter types per class.
ctor_types = {}
cur = None
for line in GEN.splitlines():
m = re.match(r"public sealed class (\w+)", line)
if m:
cur = m.group(1)
continue
if cur:
cm = re.match(r"\s*public %s\(([^)]*)\)" % re.escape(cur), line)
if cm:
ps = cm.group(1).strip()
types = [p.strip().rsplit(" ", 1)[0].strip() for p in ps.split(",")] if ps else []
ctor_types[cur] = types
cur = None
# Unified archetype + params, keyed by canonical (== C# class name).
spec = {}
for e in json.load(open(os.path.join(G, "scalar_manifest.json"))):
arch = {"f64": "scalar_f64", "Candle": "scalar_candle", "(f64, f64)": "pairwise"}[e["input"]]
spec[e["canonical"]] = {"arch": arch, "params": e["params"]}
for e in json.load(open(os.path.join(G, "multi_manifest.json"))):
arch = {"f64": "multi_f64", "Candle": "multi_candle", "(f64, f64)": "multi_pairwise"}[e["input"]]
spec[e["canonical"]] = {"arch": arch, "params": e["params"], "n": e["n"]}
ex = json.load(open(os.path.join(G, "exotic_manifest.json")))
for e in ex["deriv"]:
spec[e["canonical"]] = {"arch": "deriv_multi" if "n" in e else "deriv", "params": e["params"], "n": e.get("n")}
for e in ex["cross"]:
spec[e["canonical"]] = {"arch": "cross", "params": e["params"]}
for e in ex["trade"]:
spec[e["canonical"]] = {"arch": "trade", "params": e["params"]}
for e in ex["trademid"]:
spec[e["canonical"]] = {"arch": "trademid", "params": e["params"]}
for e in ex["ob"]:
spec[e["canonical"]] = {"arch": "ob", "params": e["params"]}
for e in json.load(open(os.path.join(G, "profile_manifest.json"))):
spec[e["canonical"]] = {"arch": "profile_" + e["kind"], "params": e["params"], "width": e["width"]}
for e in json.load(open(os.path.join(G, "bars_manifest.json"))):
arch = "footprint" if e["canonical"] == "Footprint" else "bars_" + e["feed"]
spec[e["canonical"]] = {"arch": arch, "params": e["params"]}
canons = sorted(os.path.basename(f)[2:-4] for f in glob.glob(os.path.join(G, "g_*.csv")))
def lit(value, cstype):
if cstype == "int":
return str(int(round(value)))
if cstype == "uint":
return f"{int(round(value))}u"
if cstype == "byte":
return f"(byte){int(round(value))}"
f = float(value)
s = repr(f)
return s if ("." in s or "e" in s or "E" in s) else s + ".0"
def ctor_call(canon):
types = ctor_types.get(canon, [])
vals = spec[canon]["params"]
args = ", ".join(lit(v, t) for v, t in zip(vals, types))
return f"new Wickra.{canon}({args})"
def block(canon):
s = spec[canon]
a = s["arch"]
L = [f" [Fact]", f" public void Golden_{canon}()", " {"]
L.append(f" using var ind = {ctor_call(canon)};")
L.append(f" Assert.Equal({json.dumps(NAMES[canon])}, ind.Name());")
L.append(" var got = new List<double[]>();")
L.append(" for (var i = 0; i < Rows.Length; i++)")
L.append(" {")
L.append(" var r = Rows[i];")
if a == "scalar_f64":
L.append(" got.Add(new[] { ind.Update(r[3]) });")
elif a == "pairwise":
L.append(" got.Add(new[] { ind.Update(r[3], r[0]) });")
elif a == "scalar_candle":
L.append(" got.Add(new[] { ind.Update(r[0], r[1], r[2], r[3], r[4], i) });")
elif a == "trade":
L.append(" got.Add(new[] { ind.Update(r[3], r[4], r[3] >= r[0], i) });")
elif a == "trademid":
L.append(" got.Add(new[] { ind.Update(r[3], r[4], r[3] >= r[0], i, (r[1] + r[2]) / 2) });")
elif a == "ob":
L.append(" var (bp, bs, ap, asz) = ObLists(r);")
L.append(" got.Add(new[] { ind.Update(bp, bs, ap, asz) });")
elif a == "deriv":
L.append(" var d = DerivFields(r);")
L.append(" got.Add(new[] { ind.Update(d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], i) });")
elif a == "deriv_multi":
L.append(" var d = DerivFields(r);")
L.append(" got.Add(FlattenNullable(ind.Update(d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], i), %d));" % s["n"])
elif a == "cross":
L.append(" var (ch, vo, nh, nl, am, ob) = CrossLists(r);")
L.append(" got.Add(new[] { ind.Update(ch, vo, nh, nl, am, ob, i) });")
elif a == "multi_f64":
L.append(" got.Add(FlattenNullable(ind.Update(r[3]), %d));" % s["n"])
elif a == "multi_pairwise":
L.append(" got.Add(FlattenNullable(ind.Update(r[3], r[0]), %d));" % s["n"])
elif a == "multi_candle":
L.append(" got.Add(FlattenNullable(ind.Update(r[0], r[1], r[2], r[3], r[4], i), %d));" % s["n"])
elif a == "profile_bins":
L.append(" var bins = ind.Update(r[0], r[1], r[2], r[3], r[4], i);")
L.append(" got.Add(bins ?? NanRow(%d));" % s["width"])
elif a == "profile_pricebins":
L.append(" got.Add(FlattenNullable(ind.Update(r[0], r[1], r[2], r[3], r[4], i), %d));" % s["width"])
elif a == "bars_close":
L.append(" got.Add(FlattenBars(ind.Update(r[3], r[3], r[3], r[3], 1.0, 0)));")
elif a == "bars_candle4":
L.append(" got.Add(FlattenBars(ind.Update(r[0], r[1], r[2], r[3], 1.0, 0)));")
elif a == "bars_candle5":
L.append(" got.Add(FlattenBars(ind.Update(r[0], r[1], r[2], r[3], r[4], 0)));")
elif a == "footprint":
L.append(" got.Add(FlattenBars(ind.Update(r[3], r[4], r[3] >= r[0], i)));")
else:
raise SystemExit("arch " + a)
L.append(" }")
L.append(f' Compare("{canon}", got);')
L.append(" }")
return "\n".join(L)
HEADER = '''// <auto-generated>
// Generated by gen_golden_test.py. DO NOT EDIT.
//
// Value-parity for every one of the 514 C# indicators: the shared golden input
// is replayed through each one and checked bit-for-bit against the Rust
// reference fixtures testdata/golden/g_<Canonical>.csv. Multi-output, profile
// and bar shapes are flattened by reflection so one comparator covers all
// archetypes. Regenerate with: python bindings/csharp/gen_golden_test.py
// </auto-generated>
#nullable enable
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using Xunit;
namespace Wickra.Tests;
public class GoldenAllTests
{
private const double Tol = 1e-6;
private static readonly double[][] Rows = LoadInput();
private static string GoldenDir([System.Runtime.CompilerServices.CallerFilePath] string file = "") =>
Path.GetFullPath(Path.Combine(Path.GetDirectoryName(file)!, "..", "..", "..", "testdata", "golden"));
private static double Cell(string s) =>
s == "nan" ? double.NaN
: s == "inf" ? double.PositiveInfinity
: s == "-inf" ? double.NegativeInfinity
: double.Parse(s, CultureInfo.InvariantCulture);
private static double[][] LoadInput()
{
var lines = File.ReadAllLines(Path.Combine(GoldenDir(), "input.csv"));
return lines.Skip(1).Where(l => l.Length > 0)
.Select(l => l.Split(',').Select(x => double.Parse(x, CultureInfo.InvariantCulture)).ToArray())
.ToArray();
}
// Keep blank lines (a candle on which no bar closed) so rows stay aligned.
private static double[]?[] ReadFixture(string name)
{
var lines = File.ReadAllLines(Path.Combine(GoldenDir(), "g_" + name + ".csv"));
return lines.Skip(1).Select(l => l.Length == 0 ? Array.Empty<double>() : l.Split(',').Select(Cell).ToArray()).ToArray();
}
private static double[] NanRow(int n)
{
var r = new double[n];
for (var i = 0; i < n; i++) r[i] = double.NaN;
return r;
}
private static double[] FlattenStruct(object o)
{
var props = o.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.OrderBy(p => p.MetadataToken);
var list = new List<double>();
foreach (var p in props)
{
var v = p.GetValue(o);
switch (v)
{
case double d: list.Add(d); break;
case float f: list.Add(f); break;
case long l: list.Add(l); break;
case int n: list.Add(n); break;
case double[] arr: list.AddRange(arr); break;
}
}
return list.ToArray();
}
private static double[] FlattenNullable<T>(T? value, int width) where T : struct =>
value.HasValue ? FlattenStruct(value.Value) : NanRow(width);
private static double[] FlattenBars<T>(T[] bars)
{
var list = new List<double>();
foreach (var bar in bars) list.AddRange(FlattenStruct(bar!));
return list.ToArray();
}
private static double[] DerivFields(double[] r)
{
double o = r[0], h = r[1], l = r[2], c = r[3], v = r[4];
return new[]
{
(c - o) / c * 0.01, c, c - 0.5, c + 1.0, v * 10.0, v * 0.6, v * 0.4,
v * 0.55, v * 0.45, h - c, c - l,
};
}
private static (double[], double[], bool[], bool[], bool[], bool[]) CrossLists(double[] r)
{
double o = r[0], c = r[3], v = r[4];
var change = new double[5];
var volume = new double[5];
var nh = new bool[5];
var nl = new bool[5];
var am = new bool[5];
var ob = new bool[5];
for (var j = 0; j < 5; j++)
{
change[j] = (c - o) + j;
volume[j] = v + j * 10.0;
nh[j] = j % 2 == 0;
nl[j] = j % 3 == 0;
am[j] = j % 2 == 0;
ob[j] = j % 3 == 0;
}
return (change, volume, nh, nl, am, ob);
}
private static (double[], double[], double[], double[]) ObLists(double[] r)
{
double c = r[3], v = r[4];
var bp = new double[5];
var bs = new double[5];
var ap = new double[5];
var asz = new double[5];
for (var k = 0; k < 5; k++)
{
var kf = k + 1;
bp[k] = c - 0.1 * kf;
bs[k] = v / kf;
ap[k] = c + 0.1 * kf;
asz[k] = v * 0.9 / kf;
}
return (bp, bs, ap, asz);
}
private static void Compare(string name, List<double[]> got)
{
var exp = ReadFixture(name);
Assert.True(exp.Length == got.Count, $"{name}: {exp.Length} fixture rows vs {got.Count} computed");
for (var i = 0; i < exp.Length; i++)
{
var want = exp[i]!;
var g = got[i];
Assert.True(want.Length == g.Length, $"{name} row {i}: arity {g.Length} vs {want.Length}");
for (var k = 0; k < want.Length; k++)
{
var w = want[k];
if (double.IsNaN(w)) { Assert.True(double.IsNaN(g[k]), $"{name} row {i} col {k}: want NaN got {g[k]}"); continue; }
if (double.IsInfinity(w)) { Assert.True(double.IsInfinity(g[k]) && Math.Sign(g[k]) == Math.Sign(w), $"{name} row {i} col {k}: want {w} got {g[k]}"); continue; }
var tol = Tol * Math.Max(1.0, Math.Abs(w));
Assert.True(Math.Abs(g[k] - w) <= tol, $"{name} row {i} col {k}: got {g[k]} want {w}");
}
}
}
'''
out = [HEADER]
for canon in canons:
out.append(block(canon))
out.append("}")
open(os.path.join(ROOT, "bindings", "csharp", "Wickra.Tests", "GoldenAllTests.g.cs"), "w", encoding="utf-8").write("\n".join(out) + "\n")
print("generated GoldenAllTests.g.cs with", len(canons), "indicators")
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

+45 -13
View File
@@ -2,13 +2,13 @@
[![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 Reference](https://pkg.go.dev/badge/github.com/wickra-lib/wickra/bindings/go.svg)](https://pkg.go.dev/github.com/wickra-lib/wickra/bindings/go)
[![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 and
bindings for Python, Node.js and WASM, 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
@@ -16,24 +16,38 @@ 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/bindings/go
go get github.com/wickra-lib/wickra-go
```
The binding uses cgo, so a C compiler is required, and it links against the
prebuilt Wickra C ABI library. Build that library from the workspace and stage
it under this package's `lib/` directory:
```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
cp target/release/libwickra.so bindings/go/lib/ # Linux
cp target/release/libwickra.dylib bindings/go/lib/ # macOS
cp target/release/wickra.dll bindings/go/lib/ # Windows (also on PATH at run time)
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
```
On Linux and 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`).
## Quick start
```go
@@ -74,6 +88,17 @@ values — the equivalence is enforced by the test suite. Multi-output indicator
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
@@ -83,10 +108,17 @@ main repository and documentation site:
- **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
Wickra ships native bindings for Python, Node.js, WASM 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.
## Security
Found a security issue? **Please don't open a public issue.** Report it privately
via the affected repository's *Security* tab (*"Report a vulnerability"*) or email
**support@wickra.org** with a subject line starting `[wickra security]`. Full
policy: <https://github.com/wickra-lib/wickra/blob/main/SECURITY.md>.
## Disclaimer
Wickra is an indicator toolkit, not a trading system. The values it computes are
+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")
}
+35
View File
@@ -0,0 +1,35 @@
package wickra
import "testing"
// The live Binance feed's connect → read → reconnect pipeline is covered
// deterministically by the Rust mock-WS-server tests in wickra-data. Here we
// only assert the binding's error paths, which need no network: a bad interval,
// an empty symbol list, and an unreachable endpoint must all surface as an
// error rather than a usable handle.
func TestBinanceFeedRejectsBadParams(t *testing.T) {
if _, err := NewBinanceFeed("BTCUSDT", BinanceInterval(99), ""); err == nil {
t.Fatal("expected an error for an unknown interval code")
}
if _, err := NewBinanceFeed("", OneMinute, ""); err == nil {
t.Fatal("expected an error for an empty symbol list")
}
if _, err := NewBinanceFeed("BTCUSDT", OneMinute, "ws://127.0.0.1:1"); err == nil {
t.Fatal("expected an error connecting to an unreachable endpoint")
}
}
// The REST fetcher's parse/HTTP success path is covered by the Rust
// mock-HTTP-server tests in wickra-data; here we only assert the binding's
// error paths, which need no reachable network.
func TestFetchBinanceKlinesRejectsBadParams(t *testing.T) {
if _, err := FetchBinanceKlines("BTCUSDT", BinanceInterval(99), 1, -1, -1, ""); err == nil {
t.Fatal("expected an error for an unknown interval code")
}
if _, err := FetchBinanceKlines("BTCUSDT", OneHour, 0, -1, -1, ""); err == nil {
t.Fatal("expected an error for a zero limit")
}
if _, err := FetchBinanceKlines("BTCUSDT", OneHour, 1, -1, -1, "http://127.0.0.1:1"); err == nil {
t.Fatal("expected an error connecting to an unreachable endpoint")
}
}
+119
View File
@@ -0,0 +1,119 @@
package wickra
import (
"math"
"os"
"strconv"
"testing"
)
// Cross-language data-layer parity: replay the shared golden tick stream through
// the TickAggregator and assert the candles match the Rust reference, with and
// without gap filling. Fixtures are generated by
// `cargo run -p wickra-examples --bin gen_golden`.
func dataParseF(t *testing.T, s string) float64 {
t.Helper()
v, err := strconv.ParseFloat(s, 64)
if err != nil {
t.Fatalf("parse %q: %v", s, err)
}
return v
}
func TestResamplerGolden(t *testing.T) {
input := readGolden(t, "input") // open,high,low,close,volume (timestamp = row index)
r, err := NewResampler(5)
if err != nil {
t.Fatalf("new: %v", err)
}
var got [][6]float64
for i, row := range input {
o, h, l, c, v := dataParseF(t, row[0]), dataParseF(t, row[1]), dataParseF(t, row[2]), dataParseF(t, row[3]), dataParseF(t, row[4])
if k, ok := r.Update(o, h, l, c, v, int64(i)); ok {
got = append(got, [6]float64{k.Open, k.High, k.Low, k.Close, k.Volume, float64(k.Timestamp)})
}
}
if k, ok := r.Flush(); ok {
got = append(got, [6]float64{k.Open, k.High, k.Low, k.Close, k.Volume, float64(k.Timestamp)})
}
r.Close()
want := readGolden(t, "data_resampled")
if len(got) != len(want) {
t.Fatalf("resample: %d candles vs %d", len(got), len(want))
}
for i := range got {
for j := 0; j < 6; j++ {
w := dataParseF(t, want[i][j])
if math.Abs(got[i][j]-w) > 1e-9*math.Max(1, math.Abs(w)) {
t.Errorf("resample row %d col %d: %v vs %v", i, j, got[i][j], w)
}
}
}
}
func TestCandleReaderGolden(t *testing.T) {
csv, err := os.ReadFile("../../testdata/golden/data_csv.csv")
if err != nil {
t.Fatalf("read data_csv.csv: %v", err)
}
r, err := NewCandleReader(string(csv))
if err != nil {
t.Fatalf("new: %v", err)
}
defer r.Close()
candles := r.Read()
got := make([][6]float64, len(candles))
for i, c := range candles {
got[i] = [6]float64{c.Open, c.High, c.Low, c.Close, c.Volume, float64(c.Timestamp)}
}
want := readGolden(t, "data_csv_candles")
if len(got) != len(want) {
t.Fatalf("candle reader: %d candles vs %d", len(got), len(want))
}
for i := range got {
for j := 0; j < 6; j++ {
w := dataParseF(t, want[i][j])
if math.Abs(got[i][j]-w) > 1e-9*math.Max(1, math.Abs(w)) {
t.Errorf("candle reader row %d col %d: %v vs %v", i, j, got[i][j], w)
}
}
}
}
func TestTickAggregatorGolden(t *testing.T) {
ticks := readGolden(t, "data_ticks")
cases := []struct {
gap bool
fixture string
}{
{false, "data_candles"},
{true, "data_candles_gap"},
}
for _, c := range cases {
agg, err := NewTickAggregator(1000, c.gap)
if err != nil {
t.Fatalf("new: %v", err)
}
var got [][6]float64
for _, r := range ticks {
price, size, ts := dataParseF(t, r[0]), dataParseF(t, r[1]), int64(dataParseF(t, r[2]))
for _, k := range agg.Push(price, size, ts) {
got = append(got, [6]float64{k.Open, k.High, k.Low, k.Close, k.Volume, float64(k.Timestamp)})
}
}
agg.Close()
want := readGolden(t, c.fixture)
if len(got) != len(want) {
t.Fatalf("%s: %d candles vs %d", c.fixture, len(got), len(want))
}
for i := range got {
for j := 0; j < 6; j++ {
w := dataParseF(t, want[i][j])
if math.Abs(got[i][j]-w) > 1e-9*math.Max(1, math.Abs(w)) {
t.Errorf("%s row %d col %d: %v vs %v", c.fixture, i, j, got[i][j], w)
}
}
}
}
}
+343
View File
@@ -0,0 +1,343 @@
"""Generate golden_all_test.go: a value-parity test that replays the shared
golden input through every one of the 514 Go indicators and checks output
bit-for-bit against the Rust-generated g_<Canonical>.csv fixtures.
Run from repo root: python bindings/go/gen_golden_test.py
"""
import glob
import json
import os
import re
ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", ".."))
G = os.path.join(ROOT, "testdata", "golden")
GEN = open(os.path.join(ROOT, "bindings", "go", "indicators_gen.go"), encoding="utf-8").read()
# Canonical core Indicator::name() per indicator, shared across every binding.
NAMES = json.load(open(os.path.join(G, "names.json")))
# Go constructor parameter types, keyed by canonical (== Go type name).
ctor_types = {}
for m in re.finditer(r"func New(\w+)\(([^)]*)\)\s*\(\*\w+, error\)", GEN):
name, ps = m.group(1), m.group(2).strip()
types = []
if ps:
for p in ps.split(","):
p = p.strip()
_, _, ty = p.partition(" ")
types.append(ty.strip())
ctor_types[name] = types
# Unified archetype + params, keyed by canonical.
spec = {} # canon -> dict(arch, params, width?, n?)
scal = json.load(open(os.path.join(G, "scalar_manifest.json")))
for e in scal:
inp = e["input"]
arch = {"f64": "scalar_f64", "Candle": "scalar_candle", "(f64, f64)": "pairwise"}[inp]
spec[e["canonical"]] = {"arch": arch, "params": e["params"]}
for e in json.load(open(os.path.join(G, "multi_manifest.json"))):
inp = e["input"]
arch = {"f64": "multi_f64", "Candle": "multi_candle", "(f64, f64)": "multi_pairwise"}[inp]
spec[e["canonical"]] = {"arch": arch, "params": e["params"], "n": e["n"]}
ex = json.load(open(os.path.join(G, "exotic_manifest.json")))
for e in ex["deriv"]:
spec[e["canonical"]] = {"arch": "deriv_multi" if "n" in e else "deriv", "params": e["params"], "n": e.get("n")}
for e in ex["cross"]:
spec[e["canonical"]] = {"arch": "cross", "params": e["params"]}
for e in ex["trade"]:
spec[e["canonical"]] = {"arch": "trade", "params": e["params"]}
for e in ex["trademid"]:
spec[e["canonical"]] = {"arch": "trademid", "params": e["params"]}
for e in ex["ob"]:
spec[e["canonical"]] = {"arch": "ob", "params": e["params"]}
for e in json.load(open(os.path.join(G, "profile_manifest.json"))):
spec[e["canonical"]] = {"arch": "profile_" + e["kind"], "params": e["params"], "width": e["width"]}
for e in json.load(open(os.path.join(G, "bars_manifest.json"))):
arch = "footprint" if e["canonical"] == "Footprint" else "bars_" + e["feed"]
spec[e["canonical"]] = {"arch": arch, "params": e["params"]}
canons = sorted(os.path.basename(f)[2:-4] for f in glob.glob(os.path.join(G, "g_*.csv")))
def go_param(value, gotype):
intlike = gotype in ("int", "int32", "int64", "uint", "uintptr", "usize")
if intlike:
return str(int(round(value)))
# float64
return repr(float(value)) if "." in repr(float(value)) or "e" in repr(float(value)) else f"{float(value)}"
def ctor_call(canon):
types = ctor_types.get(canon, [])
vals = spec[canon]["params"]
args = ", ".join(go_param(v, t) for v, t in zip(vals, types))
return f"New{canon}({args})"
# Update-call expression + output handling per archetype.
def block(canon):
s = spec[canon]
a = s["arch"]
ctor = ctor_call(canon)
lines = [f'\tt.Run("{canon}", func(t *testing.T) {{']
lines.append(f"\t\tind, err := {ctor}")
lines.append('\t\tif err != nil {')
lines.append(f'\t\t\tt.Fatalf("new {canon}: %v", err)')
lines.append("\t\t}")
lines.append(f'\t\tif n := ind.Name(); n != {json.dumps(NAMES[canon])} {{')
lines.append(f'\t\t\tt.Errorf("name: got %q want %q", n, {json.dumps(NAMES[canon])})')
lines.append("\t\t}")
lines.append("\t\tgot := make([][]float64, len(rows))")
lines.append("\t\tfor i, r := range rows {")
if a == "scalar_f64":
upd = "ind.Update(r[3])"
lines.append(f"\t\t\tgot[i] = []float64{{{upd}}}")
elif a == "pairwise":
lines.append("\t\t\tgot[i] = []float64{ind.Update(r[3], r[0])}")
elif a == "scalar_candle":
lines.append("\t\t\tgot[i] = []float64{ind.Update(r[0], r[1], r[2], r[3], r[4], int64(i))}")
elif a == "trade":
lines.append("\t\t\tgot[i] = []float64{ind.Update(r[3], r[4], r[3] >= r[0], int64(i))}")
elif a == "trademid":
lines.append("\t\t\tgot[i] = []float64{ind.Update(r[3], r[4], r[3] >= r[0], int64(i), (r[1]+r[2])/2)}")
elif a == "ob":
lines.append("\t\t\tbp, bs, ap, as_ := obLists(r)")
lines.append("\t\t\tgot[i] = []float64{ind.Update(bp, bs, ap, as_)}")
elif a == "deriv":
lines.append("\t\t\td := derivFields(r)")
lines.append("\t\t\tgot[i] = []float64{ind.Update(d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], int64(i))}")
elif a == "deriv_multi":
lines.append("\t\t\td := derivFields(r)")
lines.append("\t\t\tout, ok := ind.Update(d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], int64(i))")
lines.append(f"\t\t\tgot[i] = reflectRow(out, ok, {s['n']})")
elif a == "cross":
lines.append("\t\t\tch, vo, nh, nl, am, ob_ := crossLists(r)")
lines.append("\t\t\tgot[i] = []float64{ind.Update(ch, vo, nh, nl, am, ob_, int64(i))}")
elif a in ("multi_f64",):
lines.append("\t\t\tout, ok := ind.Update(r[3])")
lines.append(f"\t\t\tgot[i] = reflectRow(out, ok, {s['n']})")
elif a == "multi_pairwise":
lines.append("\t\t\tout, ok := ind.Update(r[3], r[0])")
lines.append(f"\t\t\tgot[i] = reflectRow(out, ok, {s['n']})")
elif a == "multi_candle":
lines.append("\t\t\tout, ok := ind.Update(r[0], r[1], r[2], r[3], r[4], int64(i))")
lines.append(f"\t\t\tgot[i] = reflectRow(out, ok, {s['n']})")
elif a == "profile_bins":
lines.append("\t\t\tbins, ok := ind.Update(r[0], r[1], r[2], r[3], r[4], int64(i))")
lines.append(f"\t\t\tif ok {{ got[i] = bins }} else {{ got[i] = nanRow({s['width']}) }}")
elif a == "profile_pricebins":
lines.append("\t\t\tout, ok := ind.Update(r[0], r[1], r[2], r[3], r[4], int64(i))")
lines.append(f"\t\t\tgot[i] = reflectRow(out, ok, {s['width']})")
elif a == "bars_close":
lines.append("\t\t\tgot[i] = flattenBars(ind.Update(r[3], r[3], r[3], r[3], 1.0, 0))")
elif a == "bars_candle4":
lines.append("\t\t\tgot[i] = flattenBars(ind.Update(r[0], r[1], r[2], r[3], 1.0, 0))")
elif a == "bars_candle5":
lines.append("\t\t\tgot[i] = flattenBars(ind.Update(r[0], r[1], r[2], r[3], r[4], 0))")
elif a == "footprint":
lines.append("\t\t\tgot[i] = flattenBars(ind.Update(r[3], r[4], r[3] >= r[0], int64(i)))")
else:
raise SystemExit("unknown arch " + a)
lines.append("\t\t}")
lines.append(f'\t\tcompareGolden(t, "{canon}", got)')
lines.append("\t})")
return "\n".join(lines)
HEADER = '''// Code generated by gen_golden_test.py. DO NOT EDIT.
//
// Value-parity for every one of the 514 Go indicators: the shared golden input
// is replayed through each one and checked bit-for-bit against the Rust
// reference fixtures testdata/golden/g_<Canonical>.csv. Multi-output, profile
// and bar shapes are flattened by reflection so a single comparator covers all
// archetypes. Regenerate with: python bindings/go/gen_golden_test.py
package wickra
import (
\t"bufio"
\t"math"
\t"os"
\t"reflect"
\t"strings"
\t"testing"
)
// readGoldenRaw keeps blank lines (a candle on which no bar closed) so bar rows
// stay aligned to the input; non-bar fixtures contain no blank lines.
func readGoldenRaw(t *testing.T, name string) [][]string {
\tt.Helper()
\tf, err := os.Open("../../testdata/golden/" + name + ".csv")
\tif err != nil {
\t\tt.Fatalf("open %s: %v", name, err)
\t}
\tdefer f.Close()
\tvar rows [][]string
\tsc := bufio.NewScanner(f)
\tsc.Buffer(make([]byte, 0, 1024*1024), 1024*1024)
\tfirst := true
\tfor sc.Scan() {
\t\tline := sc.Text()
\t\tif first {
\t\t\tfirst = false
\t\t\tcontinue
\t\t}
\t\tif line == "" {
\t\t\trows = append(rows, []string{})
\t\t\tcontinue
\t\t}
\t\trows = append(rows, strings.Split(line, ","))
\t}
\treturn rows
}
func nanRow(n int) []float64 {
\tr := make([]float64, n)
\tfor i := range r {
\t\tr[i] = math.NaN()
\t}
\treturn r
}
func reflectRow(out any, ok bool, width int) []float64 {
\tif !ok {
\t\treturn nanRow(width)
\t}
\tv := reflect.ValueOf(out)
\trow := make([]float64, 0, width)
\tfor k := 0; k < v.NumField(); k++ {
\t\trow = appendField(row, v.Field(k))
\t}
\treturn row
}
func appendField(row []float64, f reflect.Value) []float64 {
\tswitch f.Kind() {
\tcase reflect.Float64, reflect.Float32:
\t\treturn append(row, f.Float())
\tcase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
\t\treturn append(row, float64(f.Int()))
\tcase reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
\t\treturn append(row, float64(f.Uint()))
\tcase reflect.Slice:
\t\tfor j := 0; j < f.Len(); j++ {
\t\t\trow = appendField(row, f.Index(j))
\t\t}
\t\treturn row
\tdefault:
\t\treturn row
\t}
}
func flattenBars(bars any) []float64 {
\tv := reflect.ValueOf(bars)
\trow := []float64{}
\tfor i := 0; i < v.Len(); i++ {
\t\tbar := v.Index(i)
\t\tfor k := 0; k < bar.NumField(); k++ {
\t\t\trow = appendField(row, bar.Field(k))
\t\t}
\t}
\treturn row
}
// Synthetic feeds derived from one OHLCV row, identical to gen_golden's Rust
// construction (DerivativesTick / CrossSection / OrderBook).
func derivFields(r []float64) [11]float64 {
\to, h, l, c, v := r[0], r[1], r[2], r[3], r[4]
\treturn [11]float64{
\t\t(c - o) / c * 0.01, // funding_rate
\t\tc, // mark_price
\t\tc - 0.5, // index_price
\t\tc + 1.0, // futures_price
\t\tv * 10.0, // open_interest
\t\tv * 0.6, // long_size
\t\tv * 0.4, // short_size
\t\tv * 0.55, // taker_buy_volume
\t\tv * 0.45, // taker_sell_volume
\t\th - c, // long_liquidation
\t\tc - l, // short_liquidation
\t}
}
func crossLists(r []float64) ([]float64, []float64, []bool, []bool, []bool, []bool) {
\to, c, v := r[0], r[3], r[4]
\tchange := make([]float64, 5)
\tvolume := make([]float64, 5)
\tnewHigh := make([]bool, 5)
\tnewLow := make([]bool, 5)
\taboveMa := make([]bool, 5)
\tonBuy := make([]bool, 5)
\tfor j := 0; j < 5; j++ {
\t\tjf := float64(j)
\t\tchange[j] = (c - o) + jf
\t\tvolume[j] = v + jf*10.0
\t\tnewHigh[j] = j%2 == 0
\t\tnewLow[j] = j%3 == 0
\t\taboveMa[j] = j%2 == 0
\t\tonBuy[j] = j%3 == 0
\t}
\treturn change, volume, newHigh, newLow, aboveMa, onBuy
}
func obLists(r []float64) ([]float64, []float64, []float64, []float64) {
\tc, v := r[3], r[4]
\tbidPx := make([]float64, 5)
\tbidSz := make([]float64, 5)
\taskPx := make([]float64, 5)
\taskSz := make([]float64, 5)
\tfor k := 0; k < 5; k++ {
\t\tkf := float64(k + 1)
\t\tbidPx[k] = c - 0.1*kf
\t\tbidSz[k] = v / kf
\t\taskPx[k] = c + 0.1*kf
\t\taskSz[k] = v * 0.9 / kf
\t}
\treturn bidPx, bidSz, askPx, askSz
}
func compareGolden(t *testing.T, name string, got [][]float64) {
\tt.Helper()
\texp := readGoldenRaw(t, "g_"+name)
\tif len(exp) != len(got) {
\t\tt.Fatalf("%s: %d fixture rows vs %d computed", name, len(exp), len(got))
\t}
\tfor i := range exp {
\t\tif len(exp[i]) != len(got[i]) {
\t\t\tt.Fatalf("%s row %d: arity %d vs %d", name, i, len(got[i]), len(exp[i]))
\t\t}
\t\tfor k := range exp[i] {
\t\t\twant := goldenCell(exp[i][k])
\t\t\tg := got[i][k]
\t\t\tif math.IsNaN(want) {
\t\t\t\tif !math.IsNaN(g) {
\t\t\t\t\tt.Fatalf("%s row %d col %d: want NaN got %v", name, i, k, g)
\t\t\t\t}
\t\t\t\tcontinue
\t\t\t}
\t\t\tif math.IsInf(want, 0) {
\t\t\t\tif !math.IsInf(g, 0) || (g > 0) != (want > 0) {
\t\t\t\t\tt.Fatalf("%s row %d col %d: want %v got %v", name, i, k, want, g)
\t\t\t\t}
\t\t\t\tcontinue
\t\t\t}
\t\t\ttol := goldenTol * math.Max(1.0, math.Abs(want))
\t\t\tif math.Abs(g-want) > tol {
\t\t\t\tt.Fatalf("%s row %d col %d: got %v want %v", name, i, k, g, want)
\t\t\t}
\t\t}
\t}
}
func TestGoldenAll(t *testing.T) {
\trows := goldenInput(t)
'''
# bars need blank-line-preserving fixture reads; reuse readGolden but it skips
# blanks. We need a raw reader for bars and input.
out = [HEADER]
for canon in canons:
out.append(block(canon))
out.append("}")
open(os.path.join(ROOT, "bindings", "go", "golden_all_test.go"), "w", encoding="utf-8").write("\n".join(out) + "\n")
print("generated golden_all_test.go with", len(canons), "indicators")
File diff suppressed because it is too large Load Diff
+250
View File
@@ -0,0 +1,250 @@
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")
}
}
// The four de-duplicated indicators: pin their corrected definitions against
// the Rust reference so the Go FFI stays bit-identical.
func TestGoldenAdOscillator(t *testing.T) {
input := goldenInput(t)
exp := readGolden(t, "ad_oscillator")
ad, err := NewAdOscillator()
if err != nil {
t.Fatal(err)
}
defer ad.Close()
for i := range input {
got := ad.Update(input[i][0], input[i][1], input[i][2], input[i][3], input[i][4], int64(i))
assertGoldenClose(t, got, goldenCell(exp[i][0]), i, "ad_oscillator")
}
}
func TestGoldenIntradayIntensity(t *testing.T) {
input := goldenInput(t)
exp := readGolden(t, "intraday_intensity")
ii, err := NewIntradayIntensity()
if err != nil {
t.Fatal(err)
}
defer ii.Close()
for i := range input {
got := ii.Update(input[i][0], input[i][1], input[i][2], input[i][3], input[i][4], int64(i))
assertGoldenClose(t, got, goldenCell(exp[i][0]), i, "intraday_intensity")
}
}
func TestGoldenAwesomeOscillatorHistogram(t *testing.T) {
input := goldenInput(t)
exp := readGolden(t, "awesome_oscillator_histogram")
aoh, err := NewAwesomeOscillatorHistogram(5, 34, 1)
if err != nil {
t.Fatal(err)
}
defer aoh.Close()
for i := range input {
got := aoh.Update(input[i][0], input[i][1], input[i][2], input[i][3], input[i][4], int64(i))
assertGoldenClose(t, got, goldenCell(exp[i][0]), i, "awesome_oscillator_histogram")
}
}
func TestGoldenAverageDrawdown(t *testing.T) {
input := goldenInput(t)
exp := readGolden(t, "average_drawdown")
avg, err := NewAverageDrawdown(20)
if err != nil {
t.Fatal(err)
}
defer avg.Close()
for i := range input {
// generator fed the close column as the equity-curve sample.
assertGoldenClose(t, avg.Update(input[i][3]), goldenCell(exp[i][0]), i, "average_drawdown")
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+16 -7
View File
@@ -4,16 +4,21 @@
// 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
// (libwickra.so/.dylib or wickra.dll) staged under ./lib — see the package
// README for how to provision it.
// 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}/../c/include
#cgo linux LDFLAGS: -L${SRCDIR}/lib -lwickra -Wl,-rpath,${SRCDIR}/lib
#cgo darwin LDFLAGS: -L${SRCDIR}/lib -lwickra -Wl,-rpath,${SRCDIR}/lib
#cgo windows LDFLAGS: -L${SRCDIR}/lib -l:wickra.dll
#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"
@@ -23,3 +28,7 @@ import "errors"
// ErrInvalidParams is returned by a New<Indicator> constructor when the native
// constructor rejects the supplied parameters (for example a zero period).
var ErrInvalidParams = errors.New("wickra: invalid indicator parameters")
// ErrFeedClosed is returned by BinanceFeed.Next once the stream has been closed
// or has errored out and exhausted its reconnect attempts.
var ErrFeedClosed = errors.New("wickra: binance feed closed")
+28
View File
@@ -24,6 +24,34 @@ func TestScalarKnownValue(t *testing.T) {
}
}
func TestWarmupPeriodAndIsReady(t *testing.T) {
s, err := NewSma(3)
if err != nil {
t.Fatalf("NewSma: %v", err)
}
defer s.Close()
if got := s.WarmupPeriod(); got != 3 {
t.Fatalf("sma(3) WarmupPeriod = %d, want 3", got)
}
if s.IsReady() {
t.Fatal("sma is ready before any update")
}
s.Update(1)
s.Update(2)
if s.IsReady() {
t.Fatal("sma is ready mid-warmup")
}
s.Update(3)
if !s.IsReady() {
t.Fatal("sma is not ready after the warmup period")
}
s.Reset()
if s.IsReady() {
t.Fatal("sma is ready after reset")
}
}
func TestScalarBatchMatchesStreaming(t *testing.T) {
input := []float64{1, 2, 3, 4, 5, 6, 7, 8}
+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/
+122
View File
@@ -0,0 +1,122 @@
# 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 WASM, 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.9.4</version>
</dependency>
```
Gradle:
```kotlin
implementation("org.wickra:wickra:0.9.4")
```
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, WASM 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.
## Security
Found a security issue? **Please don't open a public issue.** Report it privately
via the affected repository's *Security* tab (*"Report a vulnerability"*) or email
**support@wickra.org** with a subject line starting `[wickra security]`. Full
policy: <https://github.com/wickra-lib/wickra/blob/main/SECURITY.md>.
## 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.15.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.6.3</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) {}
}
+293
View File
@@ -0,0 +1,293 @@
"""Generate src/test/java/org/wickra/GoldenAllTest.java: a reflection-driven
value-parity test that replays the shared golden input through every one of the
514 Java indicators and checks output bit-for-bit against the Rust reference
fixtures g_<Canonical>.csv. The per-indicator spec is embedded so the test has
no JSON dependency; a single reflective runner covers all archetypes.
Run from repo root: python bindings/java/gen_golden_test.py
"""
import json
import os
ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", ".."))
MAN = json.load(open(os.path.join(ROOT, "testdata", "golden", "golden_manifest.json")))
def params_lit(ps):
if not ps:
return "new double[]{}"
return "new double[]{" + ", ".join(repr(float(p)) for p in ps) + "}"
specs = []
for e in MAN:
n = e.get("n", e.get("width", 0))
specs.append(f' new Spec("{e["canonical"]}", "{e["arch"]}", {params_lit(e["params"])}, {n}),')
specs_block = "\n".join(specs)
# Canonical core Indicator::name() per indicator, shared across every binding.
NAMES = json.load(open(os.path.join(ROOT, "testdata", "golden", "names.json")))
names_block = "\n".join(
f" NAMES.put({json.dumps(c)}, {json.dumps(NAMES[c])});" for c in sorted(NAMES)
)
TEMPLATE = '''// Code generated by gen_golden_test.py. DO NOT EDIT.
package org.wickra;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.RecordComponent;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
/**
* Reflection-driven value parity for the whole 514-indicator catalogue: each
* indicator is reconstructed by its class name, fed the synthetic stream derived
* from the shared golden input (identical to gen_golden's Rust construction) and
* checked bit-for-bit against testdata/golden/g_&lt;Canonical&gt;.csv. One runner
* flattens scalar, multi-output records, profiles and bar arrays by reflection.
*/
class GoldenAllTest {
private static final double TOL = 1e-6;
private record Spec(String canonical, String arch, double[] params, int width) {}
private static final Spec[] SPECS = {
%SPECS%
};
private static final java.util.Map<String, String> NAMES = new java.util.HashMap<>();
static {
%NAMES%
}
private static Path goldenDir() {
java.io.File d = new java.io.File("").getAbsoluteFile();
while (d != null) {
java.io.File g = new java.io.File(d, "testdata/golden");
if (g.isDirectory()) {
return g.toPath();
}
d = d.getParentFile();
}
throw new IllegalStateException("testdata/golden not found");
}
private static double cell(String s) {
return switch (s) {
case "nan" -> Double.NaN;
case "inf" -> Double.POSITIVE_INFINITY;
case "-inf" -> Double.NEGATIVE_INFINITY;
default -> Double.parseDouble(s);
};
}
private static double[][] input() throws IOException {
List<String> lines = Files.readAllLines(goldenDir().resolve("input.csv"));
List<double[]> rows = new ArrayList<>();
for (int i = 1; i < lines.size(); i++) {
if (lines.get(i).isEmpty()) continue;
String[] p = lines.get(i).split(",");
double[] r = new double[p.length];
for (int j = 0; j < p.length; j++) r[j] = Double.parseDouble(p[j]);
rows.add(r);
}
return rows.toArray(new double[0][]);
}
// Keep blank lines (a candle on which no bar closed) so rows stay aligned.
private static double[][] fixture(String name) throws IOException {
List<String> lines = Files.readAllLines(goldenDir().resolve("g_" + name + ".csv"));
List<double[]> rows = new ArrayList<>();
for (int i = 1; i < lines.size(); i++) {
String ln = lines.get(i);
if (ln.isEmpty()) {
rows.add(new double[0]);
continue;
}
String[] p = ln.split(",");
double[] r = new double[p.length];
for (int j = 0; j < p.length; j++) r[j] = cell(p[j]);
rows.add(r);
}
return rows.toArray(new double[0][]);
}
private static double[] nanRow(int n) {
double[] r = new double[n];
java.util.Arrays.fill(r, Double.NaN);
return r;
}
private static double[] derivFields(double[] r) {
double o = r[0], h = r[1], l = r[2], c = r[3], v = r[4];
return new double[]{
(c - o) / c * 0.01, c, c - 0.5, c + 1.0, v * 10.0, v * 0.6, v * 0.4,
v * 0.55, v * 0.45, h - c, c - l,
};
}
private static double[] crossList(double[] r, int which) {
double o = r[0], c = r[3], v = r[4];
double[] out = new double[5];
for (int j = 0; j < 5; j++) {
out[j] = switch (which) {
case 0 -> (c - o) + j;
case 1 -> v + j * 10.0;
case 2 -> j % 2 == 0 ? 1.0 : 0.0; // newHigh
case 3 -> j % 3 == 0 ? 1.0 : 0.0; // newLow
case 4 -> j % 2 == 0 ? 1.0 : 0.0; // aboveMa
default -> j % 3 == 0 ? 1.0 : 0.0; // onBuySignal
};
}
return out;
}
private static double[] obList(double[] r, int which) {
double c = r[3], v = r[4];
double[] out = new double[5];
for (int k = 0; k < 5; k++) {
double kf = k + 1;
out[k] = switch (which) {
case 0 -> c - 0.1 * kf;
case 1 -> v / kf;
case 2 -> c + 0.1 * kf;
default -> v * 0.9 / kf;
};
}
return out;
}
private static double[] flattenRecord(Object o) throws Exception {
RecordComponent[] rc = o.getClass().getRecordComponents();
List<Double> list = new ArrayList<>();
for (RecordComponent c : rc) {
Object v = c.getAccessor().invoke(o);
if (v instanceof double[] arr) {
for (double d : arr) list.add(d);
} else if (v instanceof Number num) {
list.add(num.doubleValue());
}
}
double[] out = new double[list.size()];
for (int i = 0; i < out.length; i++) out[i] = list.get(i);
return out;
}
private static double[] flattenArray(Object arr) throws Exception {
int len = Array.getLength(arr);
List<Double> list = new ArrayList<>();
for (int i = 0; i < len; i++) {
for (double d : flattenRecord(Array.get(arr, i))) list.add(d);
}
double[] out = new double[list.size()];
for (int i = 0; i < out.length; i++) out[i] = list.get(i);
return out;
}
private static Object construct(Spec s) throws Exception {
Class<?> cls = Class.forName("org.wickra." + s.canonical());
Constructor<?> ctor = cls.getConstructors()[0];
Class<?>[] pt = ctor.getParameterTypes();
Object[] args = new Object[pt.length];
for (int i = 0; i < pt.length; i++) {
double v = s.params()[i];
if (pt[i] == int.class) args[i] = (int) Math.round(v);
else if (pt[i] == long.class) args[i] = (long) Math.round(v);
else args[i] = v;
}
return ctor.newInstance(args);
}
private static Method updateMethod(Object ind) {
for (Method m : ind.getClass().getMethods()) {
if (m.getName().equals("update")) return m;
}
throw new IllegalStateException("no update on " + ind.getClass());
}
private static double[] row(Spec s, Object ind, Method upd, double[] r, int i) throws Exception {
double o = r[0], h = r[1], l = r[2], c = r[3], v = r[4];
Object res = switch (s.arch()) {
case "scalar_f64", "multi_f64" -> upd.invoke(ind, c);
case "pairwise", "multi_pairwise" -> upd.invoke(ind, c, o);
case "scalar_candle", "multi_candle", "profile_bins", "profile_pricebins" ->
upd.invoke(ind, o, h, l, c, v, (long) i);
case "trade" -> upd.invoke(ind, c, v, c >= o, (long) i);
case "trademid" -> upd.invoke(ind, c, v, c >= o, (long) i, (h + l) / 2);
case "ob" -> upd.invoke(ind, obList(r, 0), obList(r, 1), obList(r, 2), obList(r, 3));
case "cross" -> upd.invoke(ind, crossList(r, 0), crossList(r, 1), crossList(r, 2),
crossList(r, 3), crossList(r, 4), crossList(r, 5), (long) i);
case "deriv", "deriv_multi" -> {
double[] d = derivFields(r);
yield upd.invoke(ind, d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], (long) i);
}
case "bars_close" -> upd.invoke(ind, c, c, c, c, 1.0, 0L);
case "bars_candle4" -> upd.invoke(ind, o, h, l, c, 1.0, 0L);
case "bars_candle5" -> upd.invoke(ind, o, h, l, c, v, 0L);
case "footprint" -> upd.invoke(ind, c, v, c >= o, (long) i);
default -> throw new IllegalStateException("arch " + s.arch());
};
return switch (s.arch()) {
case "scalar_f64", "scalar_candle", "pairwise", "trade", "trademid", "ob", "cross", "deriv" ->
new double[]{((Number) res).doubleValue()};
case "multi_f64", "multi_candle", "multi_pairwise", "deriv_multi", "profile_pricebins" ->
res == null ? nanRow(s.width()) : flattenRecord(res);
case "profile_bins" -> res == null ? nanRow(s.width()) : (double[]) res;
default -> flattenArray(res); // bars_*, footprint
};
}
@TestFactory
List<DynamicTest> golden() throws Exception {
double[][] rows = input();
List<DynamicTest> tests = new ArrayList<>();
for (Spec s : SPECS) {
tests.add(dynamicTest(s.canonical(), () -> {
Object ind = construct(s);
String gotName = (String) ind.getClass().getMethod("name").invoke(ind);
assertTrue(gotName.equals(NAMES.get(s.canonical())),
s.canonical() + ": name() " + gotName + " != " + NAMES.get(s.canonical()));
Method upd = updateMethod(ind);
double[][] exp = fixture(s.canonical());
assertTrue(exp.length == rows.length, s.canonical() + ": row count " + exp.length + " vs " + rows.length);
for (int i = 0; i < rows.length; i++) {
double[] got = row(s, ind, upd, rows[i], i);
double[] want = exp[i];
assertTrue(got.length == want.length,
s.canonical() + " row " + i + ": arity " + got.length + " vs " + want.length);
for (int k = 0; k < want.length; k++) {
double w = want[k], g = got[k];
if (Double.isNaN(w)) {
assertTrue(Double.isNaN(g), s.canonical() + " row " + i + " col " + k + ": want NaN got " + g);
} else if (Double.isInfinite(w)) {
assertTrue(Double.isInfinite(g) && Math.signum(g) == Math.signum(w),
s.canonical() + " row " + i + " col " + k + ": want " + w + " got " + g);
} else {
double tol = TOL * Math.max(1.0, Math.abs(w));
assertTrue(Math.abs(g - w) <= tol,
s.canonical() + " row " + i + " col " + k + ": got " + g + " want " + w);
}
}
}
}));
}
return tests;
}
}
'''
out = TEMPLATE.replace("%SPECS%", specs_block).replace("%NAMES%", names_block)
dest = os.path.join(ROOT, "bindings", "java", "src", "test", "java", "org", "wickra", "GoldenAllTest.java")
open(dest, "w", encoding="utf-8").write(out)
print("generated GoldenAllTest.java with", len(MAN), "indicators")
+12
View File
@@ -0,0 +1,12 @@
# OSV-Scanner suppression for this Maven manifest. OSV-Scanner looks for an
# osv-scanner.toml next to each manifest it scans, and the repo-root config does
# not cover Maven sub-directory scans — so the jackson finding is suppressed
# here as well. See the root osv-scanner.toml and the SECURITY.md VEX section.
#
# tools.jackson.core:jackson-core 3.x is NOT a dependency of this project. Full
# Maven resolution (the publishing plugin tree and the project dependency tree)
# resolves only jackson 2.16.1 / 2.17.1; tools.jackson 3.x appears nowhere.
# OSV-Scanner's own resolver flags it as a false positive.
[[IgnoredVulns]]
id = "GHSA-72hv-8253-57qq"
reason = "tools.jackson.core:jackson-core 3.x is not a dependency; only jackson 2.x resolves. Not affected."
+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.9.4</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>6.1.0</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.15.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.6</version>
<configuration>
<argLine>${native.access.arg}</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.5.0</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.4.0</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.12.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.8</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.10.0</version>
<extensions>true</extensions>
<configuration>
<publishingServerId>central</publishingServerId>
<autoPublish>true</autoPublish>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
@@ -0,0 +1,116 @@
// 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);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_ABANDONED_BABY_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_ABANDONED_BABY_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ABANDONED_BABY_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
NativeMethods.WICKRA_ABANDONED_BABY_RESET.invokeExact(handle);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
@Override public void close() {
cleanable.clean();
}
}
@@ -0,0 +1,116 @@
// 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);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_ABCD_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_ABCD_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ABCD_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
NativeMethods.WICKRA_ABCD_RESET.invokeExact(handle);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
@Override public void close() {
cleanable.clean();
}
}
@@ -0,0 +1,102 @@
// 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 = WickraNative.boolSegment(a, newHigh);
MemorySegment newLowSeg = WickraNative.boolSegment(a, newLow);
MemorySegment aboveMaSeg = WickraNative.boolSegment(a, aboveMa);
MemorySegment onBuySignalSeg = WickraNative.boolSegment(a, onBuySignal);
return (double) NativeMethods.WICKRA_ABSOLUTE_BREADTH_INDEX_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_ABSOLUTE_BREADTH_INDEX_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_ABSOLUTE_BREADTH_INDEX_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ABSOLUTE_BREADTH_INDEX_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
NativeMethods.WICKRA_ABSOLUTE_BREADTH_INDEX_RESET.invokeExact(handle);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
@Override public void close() {
cleanable.clean();
}
}
@@ -0,0 +1,92 @@
// 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);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_ACCELERATION_BANDS_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_ACCELERATION_BANDS_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ACCELERATION_BANDS_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
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,125 @@
// 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);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_ACCELERATOR_OSCILLATOR_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_ACCELERATOR_OSCILLATOR_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ACCELERATOR_OSCILLATOR_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
NativeMethods.WICKRA_ACCELERATOR_OSCILLATOR_RESET.invokeExact(handle);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
@Override public void close() {
cleanable.clean();
}
}
@@ -0,0 +1,116 @@
// 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);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_AD_OSCILLATOR_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_AD_OSCILLATOR_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_AD_OSCILLATOR_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
NativeMethods.WICKRA_AD_OSCILLATOR_RESET.invokeExact(handle);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
@Override public void close() {
cleanable.clean();
}
}
@@ -0,0 +1,102 @@
// 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 = WickraNative.boolSegment(a, newHigh);
MemorySegment newLowSeg = WickraNative.boolSegment(a, newLow);
MemorySegment aboveMaSeg = WickraNative.boolSegment(a, aboveMa);
MemorySegment onBuySignalSeg = WickraNative.boolSegment(a, onBuySignal);
return (double) NativeMethods.WICKRA_AD_VOLUME_LINE_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_AD_VOLUME_LINE_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_AD_VOLUME_LINE_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_AD_VOLUME_LINE_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
NativeMethods.WICKRA_AD_VOLUME_LINE_RESET.invokeExact(handle);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
@Override public void close() {
cleanable.clean();
}
}
@@ -0,0 +1,119 @@
// 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);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_ADAPTIVE_CCI_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_ADAPTIVE_CCI_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ADAPTIVE_CCI_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
NativeMethods.WICKRA_ADAPTIVE_CCI_RESET.invokeExact(handle);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
@Override public void close() {
cleanable.clean();
}
}
@@ -0,0 +1,96 @@
// 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);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_ADAPTIVE_CYCLE_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_ADAPTIVE_CYCLE_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ADAPTIVE_CYCLE_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
NativeMethods.WICKRA_ADAPTIVE_CYCLE_RESET.invokeExact(handle);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
@Override public void close() {
cleanable.clean();
}
}
@@ -0,0 +1,99 @@
// 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);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_ADAPTIVE_LAGUERRE_FILTER_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_ADAPTIVE_LAGUERRE_FILTER_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ADAPTIVE_LAGUERRE_FILTER_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
NativeMethods.WICKRA_ADAPTIVE_LAGUERRE_FILTER_RESET.invokeExact(handle);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
@Override public void close() {
cleanable.clean();
}
}
@@ -0,0 +1,99 @@
// 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);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_ADAPTIVE_RSI_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_ADAPTIVE_RSI_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ADAPTIVE_RSI_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
NativeMethods.WICKRA_ADAPTIVE_RSI_RESET.invokeExact(handle);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
@Override public void close() {
cleanable.clean();
}
}
@@ -0,0 +1,116 @@
// 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);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_ADL_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_ADL_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ADL_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
NativeMethods.WICKRA_ADL_RESET.invokeExact(handle);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
@Override public void close() {
cleanable.clean();
}
}
@@ -0,0 +1,116 @@
// 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);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_ADVANCE_BLOCK_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_ADVANCE_BLOCK_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ADVANCE_BLOCK_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
NativeMethods.WICKRA_ADVANCE_BLOCK_RESET.invokeExact(handle);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
@Override public void close() {
cleanable.clean();
}
}
@@ -0,0 +1,102 @@
// 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 = WickraNative.boolSegment(a, newHigh);
MemorySegment newLowSeg = WickraNative.boolSegment(a, newLow);
MemorySegment aboveMaSeg = WickraNative.boolSegment(a, aboveMa);
MemorySegment onBuySignalSeg = WickraNative.boolSegment(a, onBuySignal);
return (double) NativeMethods.WICKRA_ADVANCE_DECLINE_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_ADVANCE_DECLINE_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_ADVANCE_DECLINE_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ADVANCE_DECLINE_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
NativeMethods.WICKRA_ADVANCE_DECLINE_RESET.invokeExact(handle);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
@Override public void close() {
cleanable.clean();
}
}
@@ -0,0 +1,102 @@
// 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 = WickraNative.boolSegment(a, newHigh);
MemorySegment newLowSeg = WickraNative.boolSegment(a, newLow);
MemorySegment aboveMaSeg = WickraNative.boolSegment(a, aboveMa);
MemorySegment onBuySignalSeg = WickraNative.boolSegment(a, onBuySignal);
return (double) NativeMethods.WICKRA_ADVANCE_DECLINE_RATIO_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_ADVANCE_DECLINE_RATIO_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_ADVANCE_DECLINE_RATIO_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ADVANCE_DECLINE_RATIO_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
NativeMethods.WICKRA_ADVANCE_DECLINE_RATIO_RESET.invokeExact(handle);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
@Override public void close() {
cleanable.clean();
}
}
@@ -0,0 +1,92 @@
// 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);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_ADX_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_ADX_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ADX_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
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,119 @@
// 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);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_ADXR_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_ADXR_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ADXR_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
NativeMethods.WICKRA_ADXR_RESET.invokeExact(handle);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
@Override public void close() {
cleanable.clean();
}
}
@@ -0,0 +1,98 @@
// 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);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_ALLIGATOR_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_ALLIGATOR_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ALLIGATOR_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
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,99 @@
// 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);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_ALMA_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_ALMA_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ALMA_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
NativeMethods.WICKRA_ALMA_RESET.invokeExact(handle);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
@Override public void close() {
cleanable.clean();
}
}
@@ -0,0 +1,103 @@
// 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);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_ALPHA_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_ALPHA_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ALPHA_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
NativeMethods.WICKRA_ALPHA_RESET.invokeExact(handle);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
@Override public void close() {
cleanable.clean();
}
}
@@ -0,0 +1,84 @@
// 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);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_AMIHUD_ILLIQUIDITY_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_AMIHUD_ILLIQUIDITY_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_AMIHUD_ILLIQUIDITY_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
NativeMethods.WICKRA_AMIHUD_ILLIQUIDITY_RESET.invokeExact(handle);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
@Override public void close() {
cleanable.clean();
}
}
@@ -0,0 +1,96 @@
// 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);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_ANCHORED_RSI_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_ANCHORED_RSI_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ANCHORED_RSI_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
NativeMethods.WICKRA_ANCHORED_RSI_RESET.invokeExact(handle);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
@Override public void close() {
cleanable.clean();
}
}
@@ -0,0 +1,116 @@
// 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);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_ANCHORED_VWAP_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_ANCHORED_VWAP_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ANCHORED_VWAP_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
NativeMethods.WICKRA_ANCHORED_VWAP_RESET.invokeExact(handle);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
@Override public void close() {
cleanable.clean();
}
}
@@ -0,0 +1,92 @@
// 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);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_ANDREWS_PITCHFORK_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_ANDREWS_PITCHFORK_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_ANDREWS_PITCHFORK_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
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,102 @@
// 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);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_APO_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_APO_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_APO_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
NativeMethods.WICKRA_APO_RESET.invokeExact(handle);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
@Override public void close() {
cleanable.clean();
}
}
@@ -0,0 +1,91 @@
// 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);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_AROON_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_AROON_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_AROON_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
NativeMethods.WICKRA_AROON_RESET.invokeExact(handle);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
@Override public void close() {
cleanable.clean();
}
}
@@ -0,0 +1,119 @@
// 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);
}
}
/** Number of updates required before update() yields a value. */
public int warmupPeriod() {
try {
long n = (long) NativeMethods.WICKRA_AROON_OSCILLATOR_WARMUP_PERIOD.invokeExact(handle);
return (int) n;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Whether the indicator has consumed enough input to emit a value. */
public boolean isReady() {
try {
byte r = (byte) NativeMethods.WICKRA_AROON_OSCILLATOR_IS_READY.invokeExact(handle);
return r != 0;
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** The indicator's canonical name. */
public String name() {
try {
MemorySegment s = (MemorySegment) NativeMethods.WICKRA_AROON_OSCILLATOR_NAME.invokeExact(handle);
return s.address() == 0 ? "" : s.reinterpret(Long.MAX_VALUE).getString(0);
} catch (Throwable t) {
throw WickraNative.rethrow(t);
}
}
/** Reset to the just-constructed state. */
public void reset() {
try {
NativeMethods.WICKRA_AROON_OSCILLATOR_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