Documentation release for the R binding. The library API and every indicator are
unchanged from `0.9.5`; only the R package's help pages change.
### What's in 0.9.6
- **R package documentation** (landed in #330): the twelve undocumented data-layer
exports now have full man pages, the stale `AwesomeOscillatorHistogram` codoc is
fixed, and a broken `push()` example is corrected — clearing the two `R CMD check`
warnings r-universe reported for `0.9.5`. CI now runs `R CMD check` so doc drift
fails the PR instead of reaching r-universe.
Pure version-string bump on top — `bump_version.py` touched 19 files. `cargo fmt`
clean.
Tag/publish waits for explicit GO (irreversible publish to crates.io / PyPI / npm
/ NuGet / Maven / Go / r-universe).
Maintenance release. The library API and every indicator are unchanged from
`0.9.4`; the only change that ships to users is the R package's build script.
### What's in 0.9.5
- **R package: retry the C ABI download** (`bindings/r/configure[.win]`). A freshly
cut release can briefly 404 while assets propagate; the download is now retried
with a ~2 min backoff instead of failing with `cannot open URL … 404`. Landed in
#328; ships to r-universe / source installs with this release.
- The rest of #328 — CI/release-pipeline hardening (R/NuGet dependency caching,
job timeouts 20→30 / release 45, network-install retries, wasm-publish cache) —
is infrastructure and does not affect the published artifacts, but makes this
release's own pipeline more robust.
Pure version-string bump on top — `bump_version.py` touched 19 files (Cargo +
Lock, pyproject, node package.json/locks + 6 platform stubs, pom + csproj +
DESCRIPTION, SECURITY, CHANGELOG `[0.9.5]` + compare URLs). `cargo fmt` clean.
Tag/publish waits for explicit GO (irreversible publish to crates.io / PyPI /
npm / NuGet / Maven / Go / r-universe).
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).
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`).
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).
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).
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.
* 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.
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.
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.
* 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.
* 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).
* 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.
* 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.
* 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.
* 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.
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.
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).
* 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.
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.
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.
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.)
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.
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.
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.
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.
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.
## 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.
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.
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]`.
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.
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.
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).
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.
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.
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.
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.
Adds a Go binding (`bindings/go`) over the C ABI hub — the second language stecker after C#.
## What's here
- **`bindings/go`** — a cgo binding exposing all 514 indicators as idiomatic Go types with `New<Indicator>` constructors and `Update`/`Batch`/`Reset`/`Close` methods. The wrappers in `indicators_gen.go` are generated from `bindings/c/include/wickra.h` (same archetype taxonomy as the C# generator: scalar/batch, multi-output, bars, profile, profile-values, array-input). Opaque handles are freed by `Close()` with a `runtime.SetFinalizer` backstop; pointer arguments are caller-owned, panics never cross the boundary.
- **`examples/go`** — the full example suite mirroring C/C#: streaming, backtest, multi_timeframe, parallel_assets (goroutine fan-out), three strategies, and `fetch_btcusdt`/`live_binance`.
- **CI** — a `go` job builds the C ABI library, stages it, and runs `gofmt`/`go vet`/`go test` plus the offline examples on Linux, macOS and Windows.
- **Docs** — Go added to the README languages table, project layout, building/testing, CONTRIBUTING binding table + regenerate note, ARCHITECTURE, examples index, issue/PR templates, the About-description template, and the other binding READMEs.
## Linking / distribution
The binding links the prebuilt C ABI library via cgo (`libwickra.so`/`.dylib`/`wickra.dll` staged under `bindings/go/lib`, gitignored). The native libraries are already shipped per target triple by the existing `c-abi-build` release job; distribution is via the subdirectory module tag `bindings/go/vX.Y.Z` (gated), so `release.yml` needs no new publish job.
No Rust crate or `Cargo.toml` change — the Go module is standalone and additive.
Not for merge yet (gated, per request).
The first language stecker on the C ABI hub: a .NET binding exposing all 514
indicators as idiomatic `IDisposable` classes, generated from `wickra.h`.
## What's here
- **`bindings/csharp/`** — the `Wickra` .NET 8 package. `[LibraryImport]`
source-generated P/Invoke (`NativeMethods.g.cs`) plus idiomatic wrappers
(`Indicators.g.cs`), both generated from the committed `bindings/c/include/wickra.h`.
The binding owns no indicator maths — it only marshals types across the C ABI.
- **Marshalling, verified end-to-end against the native library.** Opaque handles
cross as `nint` kept alive per call via a `SafeHandle`; `bool` as
`[MarshalAs(U1)]` (Rust `bool` is one byte); a self-correcting
`DllImportResolver` validates the loaded library actually exports the Wickra
ABI. Tests cover one representative per FFI archetype (scalar, candle, pairwise,
multi-output, bars, profile, values-profile, order-book / array-input) plus
exact Sma reference values.
- **NuGet packaging** — `dotnet pack` produces `Wickra.<version>.nupkg`; the
release pipeline stages prebuilt native libraries under `runtimes/<rid>/native/`
for six target triples (win/linux/osx × x64/arm64).
- **`examples/csharp/`** — nine examples mirroring `examples/c/`: streaming,
backtest, multi_timeframe, parallel_assets, three strategies, and
fetch_btcusdt + live_binance.
- **CI** — a `csharp` job on the three OSes builds the C ABI, tests the binding,
and runs the offline examples. **Release** — a gated `csharp-publish` job packs
and pushes to NuGet (gated on `NUGET_API_KEY`, independent of the GitHub-release
job so a C# hiccup never blocks the C/C++ asset release).
- **Docs consistency wave** — README, CONTRIBUTING, CHANGELOG, examples/README,
the issue / PR templates, `sync-about.yml`, and `.gitattributes`.
The native Python / Node / WASM bindings and the C ABI are untouched; this is
additive. Publishing to NuGet stays gated behind the release tag and the secret.
Stacked on #222 (base `feat/c-abi-hub`), so the diff is just the additions on top of the hub foundation — no merge of #222 required.
## What this adds
**Examples — full parity with rust/python/node (`examples/c/`)**
- `streaming.c` upgraded to the multi-indicator (SMA/EMA/RSI/MACD + signals) demo
- `backtest.c`, `multi_timeframe.c` (manual time-bucket resampling), `parallel_assets.c` (serial vs OpenMP fan-out, one handle per asset)
- three educational strategies: `strategy_rsi_mean_reversion.c`, `strategy_macd_adx.c`, `strategy_bollinger_squeeze.c`
- two network examples shelling out to `curl`: `fetch_btcusdt.c`, `live_binance.c` (REST poll)
- two header-only helpers (`wickra_csv.h`, `wickra_strategy.h`) since the C ABI ships no IO layer
- CMake builds all 11; the 9 offline ones run under `ctest` on 3 OS; the network two are built-only
**Docs & metadata — surface the C ABI everywhere it was missing**
- ARCHITECTURE diagram + crate table, SECURITY + THREAT_MODEL (the C ABI as the sole `unsafe` FFI surface), the three binding package READMEs, issue/PR templates, CHANGELOG, and the GitHub About template (live About + org description updated too)
**Cleanup**
- removed all references to the private generator tooling from public files (`bindings/c/src/lib.rs` header, `CONTRIBUTING.md`, `sync-about.yml`)
Verified locally: `cargo build -p wickra-c --release`, `cmake + ctest` (9/9 pass), and `-Wall -Wextra -Wpedantic` clean on gcc 13.
## What
Introduces `wickra-c` — a `cdylib` + `staticlib` that exposes the Rust core over a **C ABI**. This is the hub every C-capable language (C, C++, Go, C#, Java, R) links against, instead of re-wiring each indicator natively. The native Python/Node/WASM bindings are untouched; this is purely additive, for ecosystems without first-class Rust tooling.
## Scope (foundation slice)
This PR deliberately validates the **whole pipeline end to end with one indicator (SMA)** before scaling to all 514, so the CI / cross-OS / header-drift mechanics are proven green first.
- Opaque `*mut T` handles; `wickra_<ind>_{new,update,batch,reset,free}`.
- NaN sentinel for warmup / NULL handles; caller-owned batch buffers; every function NULL-safe.
- cbindgen generates and commits `bindings/c/include/wickra.h` with opaque handle typedefs.
- A C smoke example (`examples/c/`) links the header + compiled library and runs (CMake + ctest).
- A `c-abi` CI job builds the library and runs the smoke test on **Linux, macOS and Windows**, plus a header drift check on Linux.
## Notes
- The per-indicator FFI blocks are plain `#[no_mangle]` functions, **not** a macro: cbindgen cannot see macro-generated functions on stable Rust (macro expansion needs nightly), so the blocks are written literally and will be generated mechanically by the ScriptHelpers `capi` wrapper in a follow-up (same model as the committed-but-generated Node `index.js`).
- `bindings/c` cannot inherit the workspace `forbid(unsafe_code)` lint (the C boundary needs raw pointers), so it mirrors every workspace lint and only relaxes `unsafe_code`. The Rust core stays `unsafe`-forbidden.
## Follow-ups (separate PRs)
- ScriptHelpers `capi` generator + wire the scalar family (~235).
- Hand-written blocks for multi-output / custom-input / bars (~279).
- Docs consistency wave (README / docs / webpage: Python·Node·WASM·Rust → +C).
- Release wiring (native-lib matrix + header/lib GH-release assets) — gated.