diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..2701ced9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,34 @@ +# EditorConfig: https://editorconfig.org +# Keeps indentation and line-endings consistent across IDEs. + +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space + +# Rust + Python + most config files use 4-space indents. +[*.{rs,py,toml}] +indent_size = 4 + +# JS / TS / JSON / YAML / Markdown use 2-space indents per ecosystem conventions. +[*.{js,ts,jsx,tsx,json,yml,yaml,md}] +indent_size = 2 + +# Markdown allows trailing whitespace as a hard line break — keep it intact. +[*.md] +trim_trailing_whitespace = false + +# Makefiles must use tabs. +[Makefile] +indent_style = tab + +# Generated files are not authored by humans; leave them alone. +[bindings/node/index.{js,d.ts}] +indent_style = unset +indent_size = unset +trim_trailing_whitespace = unset +insert_final_newline = unset diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..06f5dbe6 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,6 @@ +# Funding sources surfaced on the repository "Sponsor" button. +# Each platform's value is the username/handle on that platform. +# Leave a key empty (e.g. patreon:) to skip a platform. + +github: [kingchenc] +custom: ["https://wickra.org/sponsor"] diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 00000000..aa5a7217 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,321 @@ +# Architecture + +A walkthrough of how Wickra is organised internally — written for new +contributors who want to know **where the code lives, why it's split that +way, and which invariants they must not break**. Pair it with [`CONTRIBUTING.md`](CONTRIBUTING.md) +for the day-to-day workflow. + +## Workspace layout + +Wickra is a Cargo workspace of three Rust crates plus three binding crates. +The split is deliberate: every concern that one user might want to disable +or replace lives behind a separate crate boundary. + +``` +┌────────────────────────────────────────────────────────────────────┐ +│ wickra (facade) │ +│ re-exports wickra-core::* + wickra-data::* │ +└──────────────┬──────────────────────────────────┬──────────────────┘ + │ │ + ┌───────────▼──────────┐ ┌──────────▼─────────┐ + │ wickra-core │ │ wickra-data │ + │ indicator engine │ │ i/o + aggregation │ + │ • 214 indicators │ │ • CSV reader │ + │ • Indicator trait │ │ • Tick aggregator │ + │ • BatchExt impl │ │ • Resampler │ + │ • OHLCV / Candle │ │ • Live feeds │ + │ no I/O, no deps │ │ optional features │ + └──────────────────────┘ └────────────────────┘ + ▲ + │ (every binding wraps the same core) + │ + ┌────────────┴───────────┬─────────────────────┐ + │ │ │ +┌──▼──────┐ ┌───────▼──────┐ ┌───────▼────────┐ +│ Python │ │ Node │ │ WASM │ +│ (PyO3) │ │ (napi-rs) │ │ (wasm-bindgen) │ +└─────────┘ └──────────────┘ └────────────────┘ +``` + +| Crate | Path | What it owns | Public deps | +|---|---|---|---| +| `wickra-core` | `crates/wickra-core` | every indicator, the `Indicator` trait, `BatchExt`, `Candle`/`Tick` types, `Error` | `thiserror`, `rayon` (parallel batch) | +| `wickra` | `crates/wickra` | thin facade — re-exports everything user-facing from `wickra-core` and `wickra-data` | both internal crates | +| `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-examples` | `examples/rust` | runnable binary examples | depends on `wickra`, `wickra-data` | + +The `fuzz/` directory is **excluded** from the workspace (it has its own +`Cargo.toml`) because the libfuzzer-sys harness requires a nightly +toolchain, which would otherwise infect the stable workspace lints. + +## The `Indicator` trait + +Every indicator in Wickra implements one trait, defined in +`crates/wickra-core/src/traits.rs`: + +```rust +pub trait Indicator { + type Input; + type Output; + + fn update(&mut self, input: Self::Input) -> Option; + fn reset(&mut self); + fn warmup_period(&self) -> usize; + fn is_ready(&self) -> bool; + fn name(&self) -> &'static str; +} +``` + +Four design choices that are non-negotiable: + +1. **Streaming-first.** `update` is the only computation entry point. Each + call must be O(1) amortised — no replays over history, no `clone`s of + the input window unless absolutely necessary. +2. **`Option` warmup.** A new indicator returns `None` until it has + ingested `warmup_period()` inputs. After that it returns `Some(value)` + on every call. The `None` → `Some` transition happens exactly once per + `reset()`. +3. **Reset is mandatory.** Calling `reset()` returns the indicator to the + state of a newly constructed one. Tests verify this for every indicator. +4. **No interior mutability across `update` calls.** Indicators may hold + `VecDeque` / array state, but no `Cell`/`RefCell`/`Mutex` should be + needed — `&mut self` is the only mutation channel. + +### Batch is free + +`BatchExt` is a blanket impl over `Indicator`: + +```rust +impl BatchExt for I { + fn batch<'a>(&mut self, input: &'a [I::Input]) -> Vec> + where I::Input: Copy + { + input.iter().map(|x| self.update(*x)).collect() + } + fn batch_parallel(...) // rayon-based for multi-asset processing +} +``` + +Consequence: **every indicator gets batch and parallel-batch for free** as +soon as `Indicator` is implemented. Tests verify `batch == streaming` +equivalence on every indicator — this is the `batch_equals_streaming` test +that appears in every indicator module. + +## Indicator-module convention + +Each indicator lives in its own file under +`crates/wickra-core/src/indicators/`. Naming: snake-case of the struct, +e.g. `Sma` → `sma.rs`, `MacdIndicator` → `macd.rs`. + +Layout inside an indicator file is uniform: + +```rust +//! Doc-comment with the formula and one-line summary. + +use std::collections::VecDeque; +use crate::error::{Error, Result}; +use crate::traits::Indicator; + +/// Public struct + rustdoc with mathematical definition + a runnable example. +#[derive(Debug, Clone)] +pub struct Foo { /* state fields */ } + +impl Foo { + /// Constructor with parameter validation. + pub fn new(period: usize, ...) -> Result { ... } + /// Const accessors for configured params. + pub const fn period(&self) -> usize { ... } +} + +impl Indicator for Foo { + type Input = f64; // or (f64, f64), or Candle + type Output = f64; // or FooOutput { ... } + fn update(...) -> ... { ... } + fn reset(...) { ... } + fn warmup_period(...) -> usize { ... } + fn is_ready(...) -> bool { ... } + fn name(...) -> &'static str { "Foo" } +} + +#[cfg(test)] +mod tests { + // mandatory tests (every indicator): + // - rejects_invalid_params + // - accessors_and_metadata + // - reference_value (vs TA-Lib / pandas-ta / hand-calculated) + // - ignores_non_finite_input + // - reset_clears_state + // - batch_equals_streaming + // plus indicator-specific edge cases +} +``` + +The `FAMILIES` constant in `mod.rs` (introduced in PR #60) is the +machine-readable index of which family every indicator belongs to. It is +the canonical taxonomy; README and Wiki tables should be derived from it. + +## Input types + +| Input | Used for | Examples | +|---|---|---| +| `f64` | Scalar inputs — usually a price or a return | SMA, EMA, RSI, ROC | +| `Candle` | OHLCV bar — `{open, high, low, close, volume, timestamp}` | ATR, Bollinger, Ichimoku, all candlestick patterns | +| `(f64, f64)` | Two-series indicators — `(asset, benchmark)` or `(x, y)` | PearsonCorrelation, Beta, Alpha, TreynorRatio | + +The `Candle` type lives in `wickra-core::ohlcv` and is the binding +contract across bindings — Python's `Candle` namedtuple, Node's +`Candle` object, and WASM's `Candle` JS class all map 1:1. + +## Output types + +Most indicators emit `f64`. Multi-output indicators emit a dedicated +struct in the same module, named `FooOutput`: + +```rust +pub struct BollingerOutput { + pub upper: f64, + pub middle: f64, + pub lower: f64, +} +``` + +Bindings flatten these into matrix outputs (NumPy 2-D array for Python, +typed object arrays for Node/WASM). + +## Numerical-stability notes + +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. +- **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 + rustdoc. +- **NaN / infinity guards.** Every indicator's `update` rejects + non-finite input early (returns `None` without state mutation). Tests + cover this with `ignores_non_finite_input`. + +## Cross-crate flow + +A typical full-stack call sequence for a Python live-trading example: + +``` +[ Python: live_trading.py ] + │ + ▼ +[ binance.AsyncClient WebSocket ] ──── wickra_data live feed ───┐ + │ + ┌──────────────────┘ + ▼ + [ Candle struct conversion ] + │ + ▼ + [ PyRsi.update(close) ] + │ + wraps │ + ▼ + [ wickra_core::Rsi::update(f64) ] <-- the only place math runs + │ + ▼ + [ Option -> Py ] + │ + ▼ + [ Python user code ] +``` + +The same call sequence happens identically for Node (via NAPI), +WASM (via wasm-bindgen → JS), and Rust (no FFI overhead, just direct +calls). + +## What lives where — the navigation cheat sheet + +| You want to … | Look in | +|---|---| +| add a new indicator | `crates/wickra-core/src/indicators/.rs` + add to `mod.rs` + add to `FAMILIES` + re-export in `lib.rs` | +| change the `Indicator` trait surface | `crates/wickra-core/src/traits.rs` — this affects every indicator, treat as breaking | +| add a new Candle field | `crates/wickra-core/src/ohlcv.rs` — also propagates to every binding's `Candle` mapping | +| add a new exchange / data source | `crates/wickra-data/src/live/.rs`, feature-gated under `live-` | +| expose a new binding | new crate under `bindings/` + macro-driven boilerplate in `bindings//src/lib.rs` | +| change benchmark coverage | `crates/wickra/benches/indicators.rs` | +| add a new fuzz target | `fuzz/fuzz_targets/.rs` + register in `fuzz/Cargo.toml` | +| change CI matrix | `.github/workflows/ci.yml` | +| change release pipeline | `.github/workflows/release.yml` (irreversible on `v*` tag — test on a throwaway tag first) | + +## What is **deliberately** not in this repo + +- **Backtest framework.** Wickra is an indicator library, not a backtester. + Strategy + PnL + fills logic is for the user (see `examples/` for + illustrative scripts). +- **Multi-exchange aggregation.** Binance is the demo feed; full + exchange-agnostic aggregation is `ccxt`'s job. Wickra's + `wickra-data::live` is intentionally minimal. +- **Order-book / L2 data.** Wickra works on OHLCV bars and ticks, not + full depth. Tick-data variants (cumulative delta, single print) are on + the roadmap but require new input types. +- **Charting / visualization.** Out of scope for the Rust core. The + WASM examples include a `lightweight-charts` integration as a + starting point, but no charting code lives in the published packages. +- **GPU / SIMD optimisation.** Indicators are O(1) per update — the + bottleneck is not vector throughput. SIMD would only help large-batch + workloads, which already saturate memory bandwidth via the cache- + friendly `VecDeque` window. + +## Performance characteristics + +Every indicator is amortised O(1) per `update`. The constant factor +varies: + +| Class | Indicators | Per-`update` cost (approx) | +|---|---|---| +| Simple rolling | SMA, EMA, WMA, Mom | 1-2 floating-point ops | +| Recursive smoothers | KAMA, FRAMA, VIDYA, JMA | 5-15 ops | +| Window-sort | OmegaRatio, percentile-based VaR | O(period · log period) per update | +| Multi-buffer DSP | MAMA, HilbertDominantCycle, EmpiricalModeDecomposition | 30-80 ops | +| Multi-component | MacdIndicator, TtmSqueeze, Alligator | sum of components | + +Benchmarks against real BTCUSDT 1-minute data live in +`crates/wickra/benches/indicators.rs`. Cross-library comparison vs +TA-Lib / pandas-ta / talipp / finta lives in +`bindings/python/benchmarks/compare_libraries.py`. + +## Stability commitments + +- **MSRV.** Workspace: Rust 1.86. Node binding: 1.88 (NAPI-RS pins it). +- **`Indicator` trait surface.** Breaking changes here are major-version + events. Adding a new method with a default impl is minor. +- **Indicator removal.** Once an indicator ships in a release, it stays + callable. Renames go through a deprecation period of at least one + minor version. +- **Output structs.** Adding a field to a `FooOutput` is non-breaking + because the binding contracts go through serde and accept extra keys. + +## Open questions / known sharp edges + +These are documented for contributors so you don't waste time +re-discovering them. + +- **`Rvi`** (Relative Vigor Index) and `RviVolatility` (Relative + Volatility Index) are different indicators with the same short + acronym — make sure you import the right one. +- **Fuzz coverage of pair indicators** uses `indicator_update_pair.rs`, + which is small because pair indicators are simpler — but coverage + should grow as more pair indicators land. +- **`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. + +For the high-level project goals see [`ROADMAP.md`](ROADMAP.md); for +day-to-day contribution mechanics see [`CONTRIBUTING.md`](CONTRIBUTING.md). diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 00000000..f4602e50 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,29 @@ +cff-version: 1.2.0 +title: Wickra +message: >- + If you use Wickra in academic work, please cite it using the metadata + below. +type: software +authors: + - alias: kingchenc + email: wickra.lib@gmail.com +repository-code: "https://github.com/wickra-lib/wickra" +url: "https://wickra.org" +abstract: >- + Wickra is a streaming-first technical-analysis library implemented in + Rust with bindings for Python, Node.js and WebAssembly. Each indicator + is a state machine that updates in constant time per new input, so + identical code paths serve live-trading workloads and historical + back-testing. The library covers 214 indicators across 16 families + (moving averages, momentum, volatility, volume, statistics, Ehlers + digital-signal-processing cycles, pivots, DeMark, Ichimoku, candlestick + patterns, market profile, and risk/performance metrics). +keywords: + - technical-analysis + - technical-indicators + - streaming + - algorithmic-trading + - quantitative-finance + - rust + - time-series +license: PolyForm-Noncommercial-1.0.0 diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 00000000..05204268 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,203 @@ +# Roadmap + +What Wickra is heading toward, what is explicitly out of scope, and what +contributors can expect across the next 0.x versions. Roadmap items are +**aspirations, not commitments** — order may shift based on real +user-feedback and bug-priority. + +For "what shipped already" see [`CHANGELOG.md`](CHANGELOG.md). For the +internal structure that the roadmap items will plug into, see +[`ARCHITECTURE.md`](ARCHITECTURE.md). + +## North star + +> A streaming-first technical-analysis core that is the obvious default +> for anyone writing a Rust, Python, Node or browser-based trading +> system — drop-in fast, drop-in correct, drop-in tested. + +Three measurable proxies for "obvious default": + +1. **Coverage.** Every textbook indicator from TA-Lib, pandas-ta and + talipp is in Wickra and produces matching reference values. +2. **Performance.** Streaming `update` is the fastest published number + across Rust / Python / Node / WASM for any technical indicator. +3. **Trust.** Releases are reproducible, signed, SBOM-attached, with a + public 100% test/branch coverage badge. + +## 0.3.0 — target window: Q3 2026 + +The first release after the org migration to `wickra-lib` lands and the +project portal at `wickra.org` is live. + +### Headline goals + +- **WASM has automated tests.** `wasm-bindgen-test` job in CI exercising + a representative subset (~30 indicators across all families). Today + WASM is only smoke-validated through manual examples. +- **Release-pipeline trust.** SBOM (CycloneDX) generated per release, + Sigstore cosign signatures attached to every published artifact, + npm `--provenance` flag enabled, PyPI Trusted Publishers configured. +- **Hosted documentation portal.** `wickra.org` (Cloudflare Pages, + VitePress) replaces the GitHub Wiki as the canonical doc surface. + Per-indicator deep-dives, quickstarts, FAQ, search. +- **End-to-end strategy examples.** 3 runnable examples that wire + Wickra indicators into a full mean-reversion / trend-following / + breakout strategy with PnL and equity-curve output. +- **`ARCHITECTURE.md` + `ROADMAP.md` + `CITATION.cff`** governance + baseline (this is it). + +### Stretch goals (might slip to 0.4.0) + +- **Per-binding hosted API reference.** TypeDoc for Node/WASM, + Sphinx for Python, both hosted on `wickra.org/api/*`. Rust stays on + `docs.rs`. +- **Property-tests (`proptest`) for mathematical invariants.** Bound + checks on RSI ∈ [0, 100], Bollinger ordering, batch-streaming + equivalence on random inputs. +- **Nightly long-fuzz workflow.** Each fuzz target gets ~1h overnight, + findings auto-converted to issues. + +## 0.4.0 — target window: Q4 2026 + +### Indicator catalogue expansion + +The current 214 indicators cover the textbook canon. The next wave is +the "stuff people actually ask for but skip because it's painful in +other libs": + +- **Anchored indicators.** AnchoredVwap is already in, but anchored + variants of common indicators (AnchoredATR, AnchoredVolatility) are + on the wishlist for explicit session-based analysis. +- **Multi-timeframe (MTF) chaining helpers.** A `Mtf` wrapper + that runs an indicator on a different timeframe of the same input + stream — solves the most common reason people drop down to ad-hoc + buffering code. +- **Order-flow primitives** (gated on tick-data ingestion landing in + `wickra-data`): CumulativeDelta, BidAskImbalance, VolumeAtPrice. +- **Pivot-confirmation patterns.** WilliamsFractals is already in; + ZigZag is in. Next: PivotHigh/PivotLow with configurable + left/right-bar confirmation, used as a feature for higher-level + pattern detectors. +- **Harmonic-chart patterns** (Gartley, Bat, Butterfly, Crab, Shark). + These need the pivot-detector + ratio-matcher framework first; the + candlestick-pattern family from 0.2.8 is the precedent. + +### Live-data layer + +- **Tick-data ingestion.** Extend `wickra-data` from OHLCV-only to + also accept raw ticks; the existing `Aggregator` already aggregates + ticks into bars but isn't exposed in the public live-feed API yet. +- **Generic exchange trait.** `LiveFeed { fn subscribe(...) -> impl + Stream }` so the Binance adapter is one implementor + among many. **Note**: Wickra will not aggregate exchanges itself + (use `ccxt` if you need that) — the trait exists so user code can + swap feeds without touching indicator code. + +### Performance & reliability + +- **Performance-regression tracking.** `bench.yml` outputs deployed to + a `gh-pages` branch, `github-action-benchmark` plot over time, + threshold-based alerts on regression. +- **Indicator parity test suite.** Every indicator that has a TA-Lib + / pandas-ta / talipp equivalent must pass a golden-value test + against that reference. Currently most do but the test names are + scattered — consolidate into one `parity_tests` module. + +## 0.5.0 — target window: 2027 H1 + +### Possible new bindings + +Open questions, prioritised by demand signals (≈ GitHub stars + issue +requests + community polls): + +- **Java / Kotlin** binding via UniFFI — interest from Android-side + trading-app developers and the JVM-quant community. +- **Go** binding — interest from algorithmic-trading shops running on + Linux + Go infra. +- **C / C++** header export (`cbindgen`) — drops Wickra into existing + C/C++ trading stacks (e.g. older Bloomberg / FIX-protocol shops). +- **Swift** — niche but real, iOS / macOS native trading apps. +- **.NET** — closes the Windows-native gap that NAPI-Node doesn't fill + (some shops are still on .NET Framework). + +None of these are committed — each is a 1-2-month project on its own, +and bindings without active maintenance are a liability. Priority will +be driven by which language community shows up with PRs. + +### `wickra-data` widening + +- **Historical fetch from > 1 source.** Today only Binance REST/WS. + Add Coinbase and Kraken as reference implementations — explicitly + not exchange-aggregation, just "here are two more adapters using + the trait". + +## Beyond — long-term aspirations + +- **`wickra-backtest` sub-crate** (decision pending). A minimal + event-driven backtester wrapping signal generation + position + sizing + fees + slippage. Decision factor: do users keep building + these ad-hoc from `examples/`? If yes, codifying it saves the + ecosystem time. If most users plug Wickra into existing backtesters + (vectorbt, backtrader, Lean, Hummingbot), keep Wickra focused. +- **`wickra-plot` companion** (low priority). `plotters`-backed Rust + rendering of indicator outputs. Mainly useful for static report + generation; live charting belongs in the JS/web layer. +- **Academic adoption.** Citable `CITATION.cff`; targeting at least + one peer-reviewed paper using Wickra as the reference indicator + engine. + +## What is explicitly **not** on the roadmap + +These are recurring requests that Wickra will decline so contributors +don't waste time on them: + +| Feature | Why declined | +|---|---| +| Exchange aggregation across N venues | That's `ccxt`'s job. Wickra ships *one* feed implementor (Binance) for tests and demo; users plug their preferred exchange. | +| Full backtesting framework (à la Lean, vectorbt) | Scope creep. May land as `wickra-backtest` *if* a clear minimal API surfaces from user demand, but Wickra core stays an indicator library. | +| Strategy auto-tuning / hyperparameter search | Wrong abstraction layer — belongs in user-side ML/backtester. | +| Order-management / broker integration | Even further out of scope. | +| GUI / web-based studio | Marketing-site demos are fine; full IDE is not. | +| Indicator implementations that require optional Python deps (e.g. scipy KDE) | Bindings must work on a clean install. Pure-Rust math only. | +| Proprietary indicator implementations (e.g. paywalled vendor formulas) | License conflicts + audit burden. | +| GPU / CUDA acceleration | O(1) per update means the bottleneck is API overhead, not compute. SIMD-batch is a maybe; GPU adds no value for streaming. | + +## How indicator wishlist requests are handled + +1. **Open an issue** using the `feature_request` template, naming the + indicator, citing one of: + - TA-Lib reference + - pandas-ta reference + - peer-reviewed paper + - widely-published trading book +2. Maintainer triages within ~1 week. Acceptance criteria: + - Formula has at least one written reference (no random + YouTuber-only indicators) + - Implementation can be O(1) streaming + - Test vectors are obtainable (from reference lib, hand-computed, + or paper-provided) +3. Once accepted, the indicator gets added to the next family-batch + PR. Family-batches typically ship 5-20 indicators at a time. + +## Versioning + +Wickra follows [SemVer](https://semver.org/). The promise: + +- **0.x.0 (minor):** new indicators, new bindings, new optional features, + new optional config knobs. Adding methods to `Indicator` with default + impls is minor. +- **0.x.y (patch):** bug fixes, performance improvements, doc fixes. +- **Major (1.0.0 and later):** changes to the `Indicator` trait + signature, removal of indicators, breaking changes to `Candle` / + `OHLCV` field order. + +The 1.0 milestone is reserved for when the indicator catalogue is +considered "stable enough" and the bindings API has stabilised — likely +~2027 once 2-3 more bindings have shipped and stress-tested the trait. + +## Discussion + +For roadmap discussion, open a [Discussions](https://github.com/wickra-lib/wickra/discussions) +thread tagged `roadmap`. Specific indicator wishlist items go in +[Issues](https://github.com/wickra-lib/wickra/issues) via the +`feature_request` template.