Compare commits

..

47 Commits

Author SHA1 Message Date
kingchenc 8659b42bef release: bump 0.7.7 -> 0.7.8 (#232)
Version bump 0.7.7 -> 0.7.8 for the R binding release.
2026-06-09 19:22:11 +02:00
kingchenc b7ef63400d Add the R binding over the C ABI hub (#230)
Adds an R binding (`bindings/r`) over the C ABI hub — the third language stecker after C# and Go, reaching the hub through R's native `.Call` interface (not extendr).

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

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

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

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

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

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

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

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

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

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

## What's here

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

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

## What this adds

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

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

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

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

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

## Scope (foundation slice)

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

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

## Notes

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

## Follow-ups (separate PRs)

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

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

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

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

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

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

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

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

## Indicators

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Fix

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

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

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

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

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

All bindings hand-written (3 struct-output + 2 candle-input-with-open / non-period-ctor). Wiring complete across core, Python, Node, WASM, fuzz, tests, README + docs counter (479) and CHANGELOG. Verified: core 3915 + doc 432, clippy clean, node 554, python 913.
2026-06-08 01:49:03 +02:00
kingchenc 57e26fb22f release: bump 0.6.6 -> 0.6.7 (#205)
Release 0.6.7 — ships the B12 DeMark indicators (474 total).

Version-string bump only: Cargo workspace + wickra-core dep, Python pyproject, Node package.json (+6 platform packages), both package-lock files, Cargo.lock, and CHANGELOG.
2026-06-08 01:14:23 +02:00
kingchenc 8431b1400c feat: add DeMark deepening (B12, 7 indicators) (#204)
B12 of the family-deepening roadmap — seven Tom DeMark indicators (467 -> 474).

**Candle -> +1/0 qualifier patterns (candlestick macro bindings):**
- **TD Camouflage** — hidden intrabar strength/weakness against the prior close.
- **TD Clop** — two-bar open/close engulfing reversal.
- **TD Clopwin** — the inside-body cousin of TD Clop (compression bar).
- **TD Propulsion** — continuation thrust closing beyond the prior extreme.
- **TD Trap** — inside ("trap") bar followed by a range breakout.

**Hand-bound:**
- **TD D-Wave** — streaming Elliott-style 1-5 / A-C swing-wave counter (candle -> f64, `strength` param).
- **TD Moving Averages** — ST1/ST2 median-price trend ribbon (candle -> struct {st1, st2}).

All seven join the existing **DeMark** family. Patterns follow the house-style
+1/0 candle-pattern convention (neutral 0.0 during warmup). Public binding names
use the family-consistent `TD...` casing.

Wiring complete across core, Python, Node, WASM, fuzz, tests, README + docs
counter (474) and CHANGELOG. Verified: core 3874 + doc 427, clippy clean,
node 549, python 903.
2026-06-08 01:12:46 +02:00
kingchenc ed01604a18 release: bump 0.6.5 -> 0.6.6 (#203)
Release 0.6.6 — ships the B11 Pivots & S/R indicators (467 total) plus the bit-exact batch fast paths and benchmark refresh from #202.

Version-string bump only: Cargo workspace + wickra-core dep, Python pyproject, Node package.json (+6 platform packages), both package-lock files, Cargo.lock, and CHANGELOG.
2026-06-08 00:21:34 +02:00
kingchenc 05fe7ffa90 perf: bit-exact batch fast paths + streaming-first benchmark docs (#202)
## Summary
- Dedicated batch fast paths for **EMA, RSI, Bollinger, MACD and ATR** (used by the Python bindings): one allocation filled in a single pass, warmup encoded as `NaN`, no per-element `Option` or input re-validation. Each is **bit-for-bit equal** to replaying `update` — SMA/Bollinger keep the drift-reseed cadence, the EMA-family keep the seed division and `mul_add` recurrences. Adds the `BatchNanExt` extension trait.
- **Cross-library benchmark refresh**: `compare_libraries.py` reports the median across timing rounds (`--rounds` / `--streaming-rounds`), gains `--skip-batch` / `--skip-streaming`, and runs every peer through the streaming arena (recompute for batch-only libraries). `wickra-bench` drives the batch fast paths against `kand`.
- **README** benchmark section reordered streaming-first (the order-of-magnitude result), with measured TA-Lib/tulipy/pandas-ta numbers in place of the CI-only placeholders.

## Impact
- Python batch ~2× faster on EMA/RSI/MACD/ATR; streaming path unchanged.
- The `batch == streaming` equivalence stays bit-exact.

## Verification
- `cargo fmt` · `cargo clippy --workspace --all-targets --all-features -- -D warnings` (clean)
- `cargo test --workspace --all-features` — 3782 unit + 420 doc tests pass
- Python `pytest` — streaming-vs-batch, known-values, input-validation, smoke pass

## Notes
- Node/WASM bindings keep their existing batch; the fast paths are Python-only for now.
2026-06-08 00:17:58 +02:00
kingchenc e97c3389fe feat: add Pivots & S/R indicators (B11) (#201)
Adds five support/resistance and pivot indicators, growing the catalog 462 -> 467.

## Indicators
- **CentralPivotRange** (Candle -> struct) — the classic pivot `(H+L+C)/3` flanked by two central levels (TC/BC); range width gauges trending vs balanced days.
- **MurreyMathLines** (Candle -> struct) — T. H. Murrey's eighths grid over a rolling high-low frame; nine levels (0/8 .. 8/8) acting as support/resistance.
- **AndrewsPitchfork** (Candle -> struct) — median line and two parallels projected forward from the last three auto-detected swing pivots (symmetric fractal of half-width `strength`).
- **VolumeWeightedSr** (Candle -> struct) — a band whose edges are the volume-weighted average of recent highs (resistance) and lows (support); falls back to equal weighting when window volume is zero.
- **PivotReversal** (Candle -> f64) — a `+1`/`-1` breakout signal fired on the bar where price closes through the most recently confirmed swing pivot.

## Wiring
Core structs with branch-complete unit tests, Python/Node/WASM bindings, fuzz drives, reference + streaming-vs-batch tests, README + docs counter sync (FAMILIES "Pivots & S/R"), and CHANGELOG entries.

Verified locally: `cargo fmt`, `cargo test -p wickra-core` (3798 lib + 425 doc), `cargo clippy --workspace --all-targets --all-features -D warnings`, `npm run build && npm test` (542), `maturin develop` + `pytest` (891).
2026-06-08 00:13:42 +02:00
kingchenc 4526278fa0 release: bump 0.6.4 -> 0.6.5 (#200)
Version bump for the **v0.6.5** release shipping the **B10 Ehlers / Cycle** family (#199): 452 -> 462 indicators. Bumps workspace + Python/Node/WASM package versions, lockfiles and CHANGELOG. No code changes.
2026-06-07 04:34:32 +02:00
kingchenc 80850c81f7 Add B10 Ehlers / Cycle deepening (10 indicators) (#199)
Deepens the **Ehlers / Cycle (DSP)** family (B10) with ten indicators (452 -> 462):

- **HighpassFilter**, **Reflex**, **Trendflex**, **CorrelationTrendIndicator**, **AdaptiveRsi**, **UniversalOscillator** — scalar (f64) Ehlers filters/oscillators.
- **AdaptiveCci** — efficiency-ratio-adaptive CCI on typical price (Candle input).
- **BandpassFilter**, **EvenBetterSinewave**, **AutocorrelationPeriodogram** — multi-arg scalar (hand-written bindings; the wasm variadic scalar macro covers wasm).

Verified locally: 3755 core lib + 420 doc tests, clippy clean, 537 node tests, 881 pytest, counter 462.
2026-06-07 04:25:16 +02:00
kingchenc 707f29e8e4 release: bump 0.6.3 -> 0.6.4 (#198)
Version bump for the **v0.6.4** release shipping the **B9 Price Statistics** family (#197): 447 -> 452 indicators. Bumps workspace + Python/Node/WASM package versions, lockfiles and CHANGELOG. No code changes.
2026-06-07 03:20:19 +02:00
kingchenc 389200f855 Add B9 Price Statistics deepening (5 indicators) (#197)
Deepens the **Price Statistics** family (B9) with five rolling-statistics indicators (447 -> 452):

- **ShannonEntropy** — Shannon entropy of a binned rolling value distribution.
- **SampleEntropy** — Richman-Moorman sample entropy (regularity/complexity of a window).
- **KendallTau** — Kendall rank correlation (tau-b) over paired observations (pairwise; distinct from Pearson/Spearman).
- **JarqueBera** — Jarque-Bera normality test statistic over a rolling window.
- **RollingMinMaxScaler** — maps the latest value to 0..1 over a rolling window.

All scalar f64 input except KendallTau (pairwise). Multi-arg scalars (Shannon/Sample entropy) use hand-written Python/Node bindings + the variadic wasm macro; KendallTau uses the pair macros. Verified locally: 3668 core lib + 410 doc tests, clippy clean, 527 node tests, 871 pytest, counter 452.
2026-06-07 03:08:53 +02:00
kingchenc 81406e7a1b release: bump 0.6.2 -> 0.6.3 (#196)
Version bump for the **v0.6.3** release shipping the **B8 Volume** family (#195): 440 -> 447 indicators. Bumps workspace + Python/Node/WASM package versions, lockfiles and CHANGELOG. No code changes.
2026-06-07 02:39:49 +02:00
kingchenc c78b84e186 Add B8 Volume family deepening (7 indicators) (#195)
Deepens the **Volume** family (B8) with seven indicators (440 -> 447):

- **VolumeRsi** — Wilder RSI computed on signed volume flow.
- **WilliamsAd** — Williams Accumulation/Distribution cumulative line (distinct from Chaikin A/D).
- **TwiggsMoneyFlow** — true-range volume accumulation with Wilder smoothing (distinct from CMF).
- **TradeVolumeIndex** — tick-direction volume accumulation past a min-tick threshold (distinct from TSV).
- **IntradayIntensity** — volume weighted by close position within the bar range.
- **BetterVolume** — VSA volume-vs-spread effort/result classifier.
- **VolumeWeightedMacd** — MACD computed on VWMA with signal line and histogram (struct output).

("Up/Down Volume Ratio" already ships from A2.) All Candle input; the six scalar stops emit f64, VolumeWeightedMacd a {macd, signal, histogram} struct. Hand-written Python/Node/WASM bindings for the volume signature. Verified locally: 3620 core lib + 405 doc tests, clippy clean, 522 node tests, 865 pytest, counter 447.
2026-06-07 02:30:56 +02:00
kingchenc fc6f3d80c2 release: bump 0.6.1 -> 0.6.2 (#194)
Version bump for the **v0.6.2** release shipping the **B7 Trailing Stops** family (#193): 434 -> 440 indicators.

Bumps workspace + Python/Node/WASM package versions, lockfiles and CHANGELOG (cuts the `[0.6.2]` section). No code changes.
2026-06-07 01:45:23 +02:00
kingchenc 2991ba411d Add B7 Trailing Stops family (6 indicators) (#193)
Adds the **Trailing Stops** family deepening (B7), six new indicators (434 -> 440):

- **KaseDevStop** — Cynthia Kase's volatility stop on the standard deviation of the two-bar true range.
- **ElderSafeZone** — Alexander Elder's stop offset by a multiple of average market noise.
- **AtrRatchet** — Kaufman ATR ratchet that tightens its multiple by a per-bar increment.
- **Nrtr** — Nick Rypock Trailing Reverse (percentage band).
- **TimeBasedStop** — exits after a fixed number of bars (scalar fraction of elapsed life).
- **ModifiedMaStop** — moving-average based trailing stop.

("Wilder Volatility System" is intentionally skipped — it overlaps the existing VoltyStop/Psar/SarExt.)

Each takes Candle input; the five band/structure stops emit a {value, direction} struct, TimeBasedStop a scalar. Wired across core, Python/Node/WASM bindings, fuzz target and tests. Verified locally: 3560 core lib + 398 doc tests, clippy clean, 515 node tests, 852 pytest, counter 440.
2026-06-07 01:32:15 +02:00
kingchenc 83e34c6f71 release: bump 0.6.0 -> 0.6.1 (#192)
Version bump for the v0.6.1 release shipping the B6 Bands & Channels family (#191): 429 -> 434 indicators.
2026-06-07 00:13:58 +02:00
kingchenc 67feec598a feat(indicators): add B6 Bands & Channels family (429 -> 434) (#191)
Adds the **B6 Bands & Channels** batch — five band/channel indicators, taking the catalogue from 429 to 434.

| Indicator | Input → Output | Summary |
|-----------|----------------|---------|
| `ProjectionBands` | `Candle` → `{upper,middle,lower}` | Widner forward-projected high/low regression envelope |
| `ProjectionOscillator` | `Candle` → `f64` | Close position inside the projection bands, scaled 0..100 |
| `QuartileBands` | `f64` → `{upper,middle,lower}` | Rolling 25th/50th/75th-percentile (Q1/median/Q3) envelope |
| `BomarBands` | `f64` → `{upper,middle,lower}` | Adaptive percentage bands containing a target coverage fraction of recent closes |
| `MedianChannel` | `f64` → `{upper,middle,lower}` | Robust median ± multiplier·MAD envelope |

All five are distinct from existing indicators (verified against the core: `LinRegChannel`, `StandardErrorBands`, `Donchian`, `RollingQuantile`, `HurstChannel`). SKIPped from the roadmap: Price Channel (= `Donchian`) and Moving-Average Channel (≈ `MaEnvelope`/`Keltner`).

Each ships:
- Core indicator with per-branch unit tests (Codecov-strict 100%).
- python / node / wasm bindings (struct outputs are hand-written; `ProjectionOscillator` uses the generated candle→f64 path).
- Fuzz drives, python (`MULTI`/`SCALAR_MULTI`/`CANDLE_SCALAR`) + node test registries, README + CHANGELOG counter bump to 434.

Verified locally: `cargo fmt`, `clippy --workspace --all-targets --all-features -D warnings` (clean), `wickra-core` 3511 lib + 392 doc tests, node 509 tests, pytest 840.
2026-06-07 00:03:02 +02:00
kingchenc 3dfbc415c5 release: bump 0.5.9 -> 0.6.0 (#190)
Version bump for the **v0.6.0** release (ships the B5 Volatility & Bands batch, #189 — 423 -> 429 indicators).

Bumps version strings across Cargo workspace, pyproject, node package.json + 6 platform packages, both package-lock.json files, and Cargo.lock; CHANGELOG `[Unreleased]` -> `[0.6.0]`. No code changes.

Versioning note: patch never reaches two digits — `0.5.9` rolls to the next minor `0.6.0` (not 0.5.10).
2026-06-06 22:48:56 +02:00
kingchenc 6b8c6a0e7f B5 volatility & bands batch (423 -> 429) (#189)
Adds six **Volatility & Bands** indicators (Part B5 of the expansion roadmap), 423 → 429.

| Indicator | Input → Output | Summary |
|-----------|----------------|---------|
| `EwmaVolatility` | `f64` → `f64` | RiskMetrics exponentially-weighted volatility (λ decay) |
| `Garch11` | `f64` → `f64` | GARCH(1,1) conditional volatility with a long-run-variance anchor |
| `BipowerVariation` | `f64` → `f64` | jump-robust realized bipower variation (π/2 · Σ\|rₜ\|\|rₜ₋₁\|) |
| `VolatilityRatio` | `Candle` → `f64` | Schwager's true range over the EMA of prior true ranges (>2 = wide-ranging day) |
| `VolatilityCone` | `Candle` → `VolatilityConeOutput` | current realized volatility within its min/median/max envelope + percentile |
| `VolatilityOfVolatility` | `f64` → `f64` | sample stddev of a rolling realized-volatility series |

### Notes
- Two B5 roadmap items were dropped as duplicates/by-construction: `RealizedVolatility` already ships (v0.5.4); `Downside Semi-Deviation` is internal to Sortino. `Bipower Variation` confirmed distinct from `JumpIndicator` (a ±1 flag, not a variance measure).
- `VolatilityRatio` implements the widely-charted EMA-of-true-range convention (denominator excludes the current bar so the 2.0 threshold means "twice typical"), distinct from the existing pairwise `variance_ratio`.
- `Garch11` mean-reverts to `ω/(1−β)` on a flat series (does not decay to 0 like EWMA) — pinned by a dedicated test.

### Coverage / verification
- Full core + Python/Node/WASM bindings, fuzz drivers (scalar + candle), registries, CHANGELOG, README + docs counter sync.
- 100% unit-test coverage per indicator (every branch).
- Green locally: `cargo clippy --workspace --all-targets --all-features -D warnings`, core lib (3479) + doc (387), node (504), python (830).

Deep-dive docs for all six are staged for `wickra-docs` and pushed after release (gated).
2026-06-06 22:38:34 +02:00
kingchenc db186b18d3 docs(readme): star-history chart + ci(sync-about): sync docs config count (#188)
Add a dark-mode star-history chart under the README footer thank-you line (all existing badges kept), and make sync-about also patch the indicator count into wickra-docs .vitepress/config.ts.
2026-06-06 21:32:35 +02:00
kingchenc 654da5722f release: bump 0.5.8 -> 0.5.9 (#187)
Patch release: streaming/batch perf (SMA, Bollinger, RSI, EMA, ATR; outputs unchanged), cross-library benchmark harness, honest tiered README. No new indicators, no API changes.
2026-06-06 21:09:05 +02:00
kingchenc aacb9280f1 Honest tiered cross-library benchmark + streaming/batch perf (#186)
## Summary

An honest, tiered cross-library benchmark — and the optimization pass it triggered.

### Performance (wickra-core, outputs unchanged)
Profiling against the other Rust TA crates exposed real inefficiencies. Each
benchmarked indicator is now **5–79% faster** in both streaming and batch:

- **SMA, Bollinger**: flat `Box<[f64]>` ring buffers replace `VecDeque` (−69…79%).
- **RSI**: `100·ag/(ag+al)` collapses three divisions into one; Wilder smoothing
  hoists `1/period` out of the hot path (−46%).
- **ATR**: reciprocal hoisted (−42%).
- **EMA/RSI/ATR**: per-tick `Option<f64>` hot state → bare `f64` + ready flag.

Net result vs `kand`: Wickra now wins **RSI, Bollinger and ATR** (streaming), and
ties `ta-rs` on SMA — up from losing every indicator 1.5–6× before.

### Benchmark harness
New `crates/wickra-bench` (publish=false): a Criterion benchmark comparing Wickra
against `kand`, `ta-rs` and `yata` on an identical BTCUSDT candle series, in
streaming and batch modes. Peer APIs were verified against their source, not
guessed. Wired into the nightly `cross-library-bench` workflow as a separate job.

### Honest README
The benchmark section is rewritten into three layered tables (Rust core vs Rust
crates; Python vs the Python ecosystem) that **show the losses as well as the
wins**. The "only library that combines…" claim is gone; the new framing is
breadth + multi-language reach + the deliberate safety trade-off that costs raw
speed. Added an origin/why-slower rationale and a star CTA.

### Python benchmark
Added `tulipy` runners and expanded per-tick streaming coverage to SMA/EMA/RSI/
MACD/Bollinger. `bench.in`/`bench.txt` now lock `TA-Lib` + `tulipy` (hash-pinned);
`pandas-ta` stays out (it requires Python ≥ 3.12, the bench runs on 3.11).

### Notes
- TA-Lib/tulipy numbers in the README Python table are marked ⧗ — they are
  produced by the CI Linux job (C extensions don't build cleanly on every
  desktop), not measured locally.
- The matching `wickra-docs` prose update is committed separately and will be
  pushed with the release, per the docs-don't-lead-the-registries rule.

Verified locally: `cargo fmt`, `cargo test --workspace --all-features` (3413 core
+ bindings), `cargo clippy --workspace --all-targets --all-features -D warnings`,
Node build + 498 tests, and pytest all green.
2026-06-06 20:57:31 +02:00
782 changed files with 198720 additions and 549 deletions
+23
View File
@@ -1,3 +1,26 @@
# Shell scripts must keep LF line endings so they run on Linux/macOS CI and
# local shells regardless of the committer's platform autocrlf setting.
*.sh text eol=lf
# The cbindgen-generated C header is committed; pin it to LF so its CI drift
# check (regenerate + `git diff`) never trips on a CRLF normalization.
bindings/c/include/wickra.h text eol=lf
# C# sources (including the generated binding) are pinned to LF so the committed
# files stay stable regardless of the committer's autocrlf setting.
*.cs text eol=lf
# Go sources (including the generated binding) are pinned to LF so gofmt's CI
# check never trips on a CRLF checkout on Windows.
*.go text eol=lf
go.mod text eol=lf
go.sum 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.win text eol=lf
bindings/r/src/Makevars text eol=lf
bindings/r/src/Makevars.win text eol=lf
bindings/r/src/wickra.c text eol=lf
+2 -2
View File
@@ -30,9 +30,9 @@ assignees: ""
## Environment
- Wickra version:
- Language / binding: <!-- Rust crate / Python / Node / WASM -->
- Language / binding: <!-- Rust crate / Python / Node / WASM / C ABI / C# (.NET) / Go / R -->
- OS and architecture:
- Rust / Python / Node version (If relevant):
- Rust / Python / Node / .NET version (If relevant):
## Additional context
@@ -26,7 +26,7 @@ assignees: []
| Binding version | `e.g. python 0.4.2 / node 0.4.2` |
| OS / arch | `e.g. Windows 11 x86_64, Linux glibc` |
| Rust toolchain | `rustc --version` (If building from source) |
| Python / Node version | `python --version` / `node --version` |
| Python / Node / .NET version | `python --version` / `node --version` / `dotnet --version` |
## Minimal reproducer
@@ -25,6 +25,10 @@ assignees: ""
- [ ] Should be exposed in the Python binding
- [ ] Should be exposed in the Node binding
- [ ] Should be exposed in the WASM binding
- [ ] Should be exposed in the C ABI
- [ ] Should be exposed in the C# / .NET binding
- [ ] Should be exposed in the Go binding
- [ ] Should be exposed in the R binding
## Additional context
@@ -13,7 +13,7 @@ assignees: []
## Affected code path
- Indicator / API: `e.g. EMA.update`
- Binding: `Rust / Python / Node / Wasm`
- Binding: `Rust / Python / Node / Wasm / C ABI / C# (.NET) / Go / R`
- Hot loop or one-shot call?
## Versions compared
+1 -1
View File
@@ -33,4 +33,4 @@ import wickra as ta
## Environment (Only if relevant)
- Wickra version: `e.g. 0.4.2`
- Binding: `Rust / Python / Node / Wasm`
- Binding: `Rust / Python / Node / Wasm / C ABI / C# (.NET) / Go / R`
+1 -1
View File
@@ -22,7 +22,7 @@
- [ ] `cargo clippy --workspace --all-targets -- -D warnings` is clean.
- [ ] `cargo test --workspace` passes.
- [ ] New behaviour has tests; bug fixes have a regression test.
- [ ] Public API changes are mirrored in the Python / Node / WASM bindings
- [ ] Public API changes are mirrored in the Python / Node / WASM bindings, and the C ABI + C# + Go + R bindings are regenerated
and their type stubs (If applicable).
- [ ] The relevant page on the [documentation site](https://docs.wickra.org)
and the `README.md` are updated (If applicable). Docs edits go to a
@@ -23,6 +23,10 @@ Please fill in the sections below. Delete any that don't apply.
- [ ] Python binding (`bindings/python`)
- [ ] Node.js binding (`bindings/node`)
- [ ] WebAssembly binding (`bindings/wasm`)
- [ ] C ABI (`bindings/c`)
- [ ] C# / .NET binding (`bindings/csharp`)
- [ ] Go binding (`bindings/go`)
- [ ] R binding (`bindings/r`)
- [ ] Examples / docs
## Linked issues
+2
View File
@@ -5,5 +5,7 @@
maturin
numpy
pandas
TA-Lib
tulipy
talipp
finta
+80
View File
@@ -1,5 +1,13 @@
# This file was autogenerated by uv via the following command:
# ./scripts/update-lockfiles.sh
build==1.5.0 \
--hash=sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f \
--hash=sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647
# via ta-lib
colorama==0.4.6 \
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
# via build
finta==1.3 \
--hash=sha256:b94b94df311c18bf5402eb2fe8fd2db5e1bdaff08baf58a7367d05c7abdd10d3 \
--hash=sha256:f2fa0673748f4be8f57e57cf6d5c00a4d44bc6071ea69dbb9a1d329d045cbba2
@@ -97,6 +105,12 @@ numpy==2.4.6 \
# -r .github/requirements/bench.in
# finta
# pandas
# ta-lib
# tulipy
packaging==26.2 \
--hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e \
--hash=sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661
# via build
pandas==3.0.3 \
--hash=sha256:0383c72c75cdcca61a9e116e611143902dbfd08bff356829c2f6d1cf40a9ca8c \
--hash=sha256:05f1f1752b8533ea03f7f39a9c15b1a058d067bb48f4748948e7a8691e0510f2 \
@@ -149,6 +163,10 @@ pandas==3.0.3 \
# via
# -r .github/requirements/bench.in
# finta
pyproject-hooks==1.2.0 \
--hash=sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8 \
--hash=sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913
# via build
python-dateutil==2.9.0.post0 \
--hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \
--hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427
@@ -157,10 +175,72 @@ six==1.17.0 \
--hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \
--hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81
# via python-dateutil
ta-lib==0.6.8 \
--hash=sha256:02388054c059945e5f02625f5075bac20a1803573cb43e7d096091027511961f \
--hash=sha256:094677b279a59c3f01c3aca8a889fda3523fd641a3805f69a2d642121b72e55e \
--hash=sha256:0a08a29690a922ba92a6cf42902a8a93c6fbda4cfed62c3c5b0471560ef60135 \
--hash=sha256:0ccd478ff5735831bf2a61d653466bfda8afadc26ad58ca6b1edb9e7521cc674 \
--hash=sha256:0e371d14b49e70caa973a234c8823341dd446f5c5d7acc826868bb42b272bdc0 \
--hash=sha256:11a373c9308eae3bac2d56d37017f9ab63968cc074a8b95be879aae3d13133aa \
--hash=sha256:128ec92e6a0e9ff7a38edef80e3b74f15bb2ed1c531d5d3252c8dca22677651b \
--hash=sha256:1fb4028437201e19014e4e374272b739867c8a3eb655da46675ef4c2ff14b616 \
--hash=sha256:282e49c766b5952dd8796f77d7ed3ae412cdd88e31f845b1fbbb86ac6cb7bebf \
--hash=sha256:2b369cabb48485fbf444beb3f5a878075367b99c2c86db2f796afeabebc749e0 \
--hash=sha256:2bf714333788bf5175f2512b86d2ed129e89ae6f6c2923e8a297a1e3395e13b5 \
--hash=sha256:30de46b55873b51be945a09edf486afcc190dc47eff9fb5d2b12c9f7e3d743da \
--hash=sha256:34e3b12407ddf99f6627435aa8a165f094339bb7dc33de92e1d7472e9f237304 \
--hash=sha256:36b2a516fce57309840f5ef3fa2fd0c4449293fc72536a0400d2e1e26b414da8 \
--hash=sha256:3a9195299df9d7d2a6e9d16bebd6b706b0ea99e4b871864c4b034c2577e21a77 \
--hash=sha256:3c32fc0f546ceecc47dd45f33d72ab4a1e341b80d9081c2d77b100add5d49104 \
--hash=sha256:3d7333e907bff3e3997e54f89733ffa8d619842a3e1cd962bca34bdc11944c28 \
--hash=sha256:4795e93d130c9b7fb661f0cead49752ae6a980437df74b99d5918026c212443e \
--hash=sha256:490e19a45cd3cdd6dfe6b46019f7ffe1103500750b41b51996a870e7c1c5f066 \
--hash=sha256:4aa0fe08383f3e5fc7d2f8cf9b42ac778f4d53fd75bcd2799a858225954eab89 \
--hash=sha256:559326d8f3d904cd4aa61f6a392d5626f35eec6a9f6cc83bcddb0abf88c40516 \
--hash=sha256:5929c83bd8cb7572d1c17ffdbf0eac235bf3c4d53cde1950cf89d944eaf97525 \
--hash=sha256:5bfd21b6acb32e20d4e279c34405a34e63da345be4b2b6eabd683e1a88857406 \
--hash=sha256:613cf06313331f49dd7b85a5a24fbddb1156c9723b6921a231906241726e5aee \
--hash=sha256:66a8e1c1e899d15a2f7510e43527fba22d895e7f6058d027db3e3837d88a69de \
--hash=sha256:691a62926ba09f2653ec0908554b3635497efb7751c5d46b916cd1ebbb1d3c25 \
--hash=sha256:6c1fd18e45c39d5a4be4b0d6a20c141e43fe46daeb1b2e2f304ebae7015ab6e6 \
--hash=sha256:6c6a1e8f98de92e817491b50aa4d01d69a1b41a4ed3173747e8f16f0d4cf81cc \
--hash=sha256:6cf029b886cfb28a2701503b7c602b811f2daa45276bd6459b0c71e051deb497 \
--hash=sha256:71506116eac0d3e3598d6325b4b818c3a0f6acb3222b24d30ad726e8c4bf7ea8 \
--hash=sha256:7993164e8e9f78ec31d38c47850ca6ba5451788b5b49a8a2dbb3322b36b5693b \
--hash=sha256:7a5cc6bf60791d8274edfdfe2dd7cec3f00f656dcc92e2b0a9af06c8b18ce6a6 \
--hash=sha256:87c1cc1057d903b78a8257a7c5f497db6fd5284f5080392bd57b66031d7389a3 \
--hash=sha256:98376c75bd6c103c74396953084a5e0798ffe476aecbfcc51ec6d100a685ac38 \
--hash=sha256:a395524b0fafa10446d11e11acb4742e919523de58aac03b791f26d7a783bcf0 \
--hash=sha256:a5100a4be91b7d4b7c8fe16a3600bd0951e10205eb1066b6873afd3996b51ee4 \
--hash=sha256:a63a52221f8c73f82f4e00493351d987f594931198589287aee96f8da673cfd5 \
--hash=sha256:a89734a7bcb2ea3b6fd600a74d6fbcdb8d3fa3f7917dbd978e039710b5509c9c \
--hash=sha256:b165f5e6de1ccc964e863bd2035807a4d3bad3e0481f9db2dc52034d6ad4f9de \
--hash=sha256:b3845e4c2fa32963fb7f384ebbaa2761b0e6b96145239bf80e956d4aff4b071c \
--hash=sha256:b3b017d9103e7a7372a146773be32b184ff7330bd708d40b1f56f06a686756ed \
--hash=sha256:b6c6e4858d8c3f88e19b7aa94b6a7619108f0bee51da9fa67b0785a8b59955f9 \
--hash=sha256:bfad1202fb1f9140e3810cc607058395f59032d9128cc0d716900c78bea5f337 \
--hash=sha256:c01809fb602e2fefc8cbfb3b603bb59d2a2eaee8708410896d48a835ba00e7c5 \
--hash=sha256:cce8de9d48289927ed18aaa420740efd52b2cd9289da32e3799afbb3a02822e8 \
--hash=sha256:ce2bc1ea01200b6d8130ab917296d05d77a1a571ec6c1ee25cfca6d55cd5db4a \
--hash=sha256:d4601e2a8b46ffbf540601a4926fd6cc5aae8a13b36fdd467f1040f01f9edaed \
--hash=sha256:d556d1c256b3700b60b6b061664a667b2e49d599c2772d46a9f2348f2dc4ab5c \
--hash=sha256:ddf7453acd03b966624ebefdb38169b5bbbeea1a1a58c90b095667247f9de327 \
--hash=sha256:e781eeb65b2007af553389c8a7fb7bc53cb856118b0fcffb2c26b0f49561c686 \
--hash=sha256:e920c272cd9e70a6b10eae9203cc96845da142e1dd4482de9343dda3738a9862 \
--hash=sha256:f5b6174bf4bf9152e368561dff410203c6921e4dd2afbcda3283a95957158112 \
--hash=sha256:f69bd42fd2515060af69b120668213121264bb7976b113954b6f9db327727c65 \
--hash=sha256:f823d0f6b04a6797fbe253bcf91666e71a6b63c290683819650c68b2468ebe64 \
--hash=sha256:fa7e9f2e80a9535f9692e113d02b4268b5f88675a730d1b0ef0abeb74c9a4e80
# via -r .github/requirements/bench.in
talipp==2.7.0 \
--hash=sha256:567f59ad74366cb59a14a00d350f35fd9d22e6924d6228bad581e6dcf1de2205 \
--hash=sha256:f749f22b9ad615605e71faf26457bb7f5e3fe16f04d3287f4ca54fd16bc3d4eb
# via -r .github/requirements/bench.in
tulipy==0.4.0 \
--hash=sha256:540704956b5b940a5f6306aa393a37536a6d7c3cbc07efe47512f3496e5203ab \
--hash=sha256:95542e40537afdd345d875baf37485eac993c6a819d00c51432e9de8df21eba8 \
--hash=sha256:fbc31727ef7657c93ad910bfdce65fecc6aaa7a5e961fe00240718e7a3fc79d8
# via -r .github/requirements/bench.in
tzdata==2026.2 \
--hash=sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10 \
--hash=sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7
+26
View File
@@ -117,3 +117,29 @@ jobs:
with:
name: cross-library-bench
path: bindings/python/benchmark.txt
rust-cross-bench:
name: Rust cross-library benchmark report
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
timeout-minutes: 6
# Wickra vs the other Rust TA crates (kand, ta-rs, yata) on an identical
# candle series — the like-for-like engine comparison with no binding
# overhead. Streaming + batch, in crates/wickra-bench/benches/cross_lib.rs.
- name: Run Rust cross-library benchmark
run: cargo bench -p wickra-bench --bench cross_lib | tee rust_cross_bench.txt
- name: Upload Rust report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: rust-cross-bench
path: rust_cross_bench.txt
+316
View File
@@ -53,6 +53,22 @@ jobs:
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
timeout-minutes: 6
- name: Warm cargo registry (retry transient DNS/registry flakes)
shell: bash
# CARGO_NET_RETRY rides out short blips, but a longer runner DNS outage
# outlasts cargo's rapid in-process retries: the crates.io index fetch
# the first cargo step does ("Could not resolve host: index.crates.io")
# then fails the whole job. Pre-fetch the dependency graph here with real
# backoff so clippy/build/test resolve from the warmed local cache.
run: |
for attempt in 1 2 3 4 5; do
if cargo fetch; then exit 0; fi
echo "::warning::cargo fetch failed (attempt $attempt/5) — likely a registry/DNS flake; retrying in $((attempt * 20))s..."
sleep $((attempt * 20))
done
echo "::error::cargo fetch still failing after 5 attempts"
exit 1
- name: Format check
run: cargo fmt --all -- --check
@@ -232,6 +248,19 @@ jobs:
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
timeout-minutes: 6
- name: Warm cargo registry (retry transient DNS/registry flakes)
shell: bash
# See the rust job: pre-fetch with backoff so the clippy index update
# can't fail the job on a transient "Could not resolve host" DNS blip.
run: |
for attempt in 1 2 3 4 5; do
if cargo fetch; then exit 0; fi
echo "::warning::cargo fetch failed (attempt $attempt/5) — likely a registry/DNS flake; retrying in $((attempt * 20))s..."
sleep $((attempt * 20))
done
echo "::error::cargo fetch still failing after 5 attempts"
exit 1
- name: Clippy (bindings, all targets)
run: cargo clippy -p wickra-node -p wickra-python --all-targets -- -D warnings
@@ -272,6 +301,19 @@ jobs:
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
timeout-minutes: 6
- name: Warm cargo registry (retry transient DNS/registry flakes)
shell: bash
# See the rust job: pre-fetch with backoff so the first cargo step can't
# fail the job on a transient "Could not resolve host" DNS blip.
run: |
for attempt in 1 2 3 4 5; do
if cargo fetch; then exit 0; fi
echo "::warning::cargo fetch failed (attempt $attempt/5) — likely a registry/DNS flake; retrying in $((attempt * 20))s..."
sleep $((attempt * 20))
done
echo "::error::cargo fetch still failing after 5 attempts"
exit 1
- name: Build on MSRV
run: cargo build ${{ matrix.packages }} --verbose
@@ -580,6 +622,280 @@ jobs:
working-directory: bindings/node
run: node --test __tests__/
c-abi:
name: C ABI on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
- name: Cache cargo
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
timeout-minutes: 6
- name: Install cbindgen
uses: taiki-e/install-action@0fd46367812ee04360509b4169d9f659d6892bb2 # v2.79.15
timeout-minutes: 10 # fail fast on a stuck download instead of hanging the job
with:
tool: cbindgen
- name: Build the C ABI library (cdylib + staticlib)
run: cargo build -p wickra-c --release
- name: Rust unit tests
run: cargo test -p wickra-c
# The generated header is platform-independent, so checking drift on one OS
# is enough — and avoids a spurious CRLF/LF diff on the Windows runner.
- name: Check the committed header is in sync with cbindgen
if: runner.os == 'Linux'
shell: bash
run: |
cbindgen --config bindings/c/cbindgen.toml --crate wickra-c --output bindings/c/include/wickra.h
if ! git diff --quiet -- bindings/c/include/wickra.h; then
echo "::error::bindings/c/include/wickra.h is out of sync — run cbindgen and commit the result"
git --no-pager diff -- bindings/c/include/wickra.h
exit 1
fi
# The real cross-language test: a foreign C consumer links the generated
# header + the compiled library and runs. If this passes on all three OSes,
# every C-capable language can link the same way.
- name: Build and run the C smoke example (CMake + ctest)
shell: bash
run: |
cmake -S examples/c -B examples/c/build
cmake --build examples/c/build --config Release
ctest --test-dir examples/c/build -C Release --output-on-failure
csharp:
name: C# on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
- name: Cache cargo
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
timeout-minutes: 6
# The binding links against the C ABI hub at runtime; build it first so the
# DllImportResolver finds target/release/wickra.{dll,so,dylib}. .NET 8 SDK is
# preinstalled on the GitHub runners, so no setup-dotnet step is needed.
- name: Build the C ABI library
run: cargo build -p wickra-c --release
- name: .NET info
run: dotnet --info
- name: Test the C# binding
run: dotnet test bindings/csharp/Wickra.Tests/Wickra.Tests.csproj -c Release
- name: Build the C# examples
shell: bash
run: |
for d in streaming backtest multi_timeframe parallel_assets \
strategy_rsi_mean_reversion strategy_macd_adx strategy_bollinger_squeeze \
fetch_btcusdt live_binance; do
dotnet build "examples/csharp/$d" -c Release
done
# Run only the offline examples (fetch_btcusdt / live_binance need network).
- name: Run the offline C# examples
shell: bash
run: |
for d in streaming backtest multi_timeframe parallel_assets \
strategy_rsi_mean_reversion strategy_macd_adx strategy_bollinger_squeeze; do
dotnet run --project "examples/csharp/$d" -c Release --no-build
done
go:
name: Go on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# Go is not reliably on PATH on every runner image, so install it
# explicitly. cache: false — the cargo build dominates and the modules
# have no external Go deps worth caching.
- name: Set up Go
id: setup-go
continue-on-error: true
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: "stable"
cache: false
- name: Retry Go setup (CDN flake)
if: steps.setup-go.outcome == 'failure'
shell: bash
run: |
echo "::warning::setup-go failed (likely CDN flake), waiting 30s before retry..."
sleep 30
- name: Set up Go (retry)
if: steps.setup-go.outcome == 'failure'
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: "stable"
cache: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
- name: Cache cargo
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
timeout-minutes: 6
# The binding links against the C ABI hub via cgo; build it first and stage
# the platform library under bindings/go/lib so cgo's LDFLAGS find it. Go is
# preinstalled on the GitHub runners, so no setup-go step is needed.
- name: Build the C ABI library
run: cargo build -p wickra-c --release
- name: Stage the native library
shell: bash
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/ ;;
esac
- name: Go info
run: go version
- name: Check gofmt
shell: bash
run: |
unformatted="$(gofmt -l bindings/go examples/go)"
if [ -n "$unformatted" ]; then
echo "gofmt needed on:"; echo "$unformatted"; exit 1
fi
- name: Vet and test the Go binding
shell: bash
# On Windows there is no rpath; the loader resolves wickra.dll via PATH.
run: |
export PATH="$PWD/bindings/go/lib:$PATH"
cd bindings/go
go vet ./...
go test ./...
- name: Build the Go examples
shell: bash
run: |
export PATH="$PWD/bindings/go/lib:$PATH"
cd examples/go
go build ./...
# Run only the offline examples (fetch_btcusdt / live_binance need network).
- name: Run the offline Go examples
shell: bash
run: |
export PATH="$PWD/bindings/go/lib:$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 }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
env:
WICKRA_INCLUDE_DIR: ${{ github.workspace }}/bindings/c/include
WICKRA_LIB_DIR: ${{ github.workspace }}/target/release
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
- name: Cache cargo
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
continue-on-error: true
timeout-minutes: 6
# The binding compiles a thin .Call glue layer against the C ABI hub; build
# the library first. On Windows configure.win bundles a renamed copy
# (wickra_abi.dll) so the package's own wickra.dll does not collide with it.
- name: Build the C ABI library
run: cargo build -p wickra-c --release
- name: Set up R
uses: r-lib/actions/setup-r@a51a8012b0aab7c32ef9d19bf54da93f3254335e # v2
with:
r-version: "release"
use-public-rspm: true
# Use the repos configured by setup-r (use-public-rspm) so Linux installs
# binary packages — building testthat's deps from source is slow and flaky.
- name: Install test dependency
run: Rscript -e 'install.packages("testthat")'
- name: Install and test the R binding
shell: bash
# github.workspace is a backslash path on Windows; configure.win (sh) and
# mingw need forward slashes. On Linux/macOS the rpath points at
# WICKRA_LIB_DIR; export the loader path too as a belt-and-suspenders.
run: |
export WICKRA_INCLUDE_DIR="${WICKRA_INCLUDE_DIR//\\//}"
export WICKRA_LIB_DIR="${WICKRA_LIB_DIR//\\//}"
export LD_LIBRARY_PATH="$WICKRA_LIB_DIR:$LD_LIBRARY_PATH"
export DYLD_LIBRARY_PATH="$WICKRA_LIB_DIR:$DYLD_LIBRARY_PATH"
R CMD INSTALL bindings/r
Rscript -e 'library(testthat); library(wickra); test_dir("bindings/r/tests/testthat", stop_on_failure = TRUE)'
- name: Run the offline R examples
shell: bash
run: |
export WICKRA_LIB_DIR="${WICKRA_LIB_DIR//\\//}"
export LD_LIBRARY_PATH="$WICKRA_LIB_DIR:$LD_LIBRARY_PATH"
export DYLD_LIBRARY_PATH="$WICKRA_LIB_DIR:$DYLD_LIBRARY_PATH"
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")'
# 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
+142 -2
View File
@@ -569,9 +569,146 @@ jobs:
# the old "publish, then upload provenance" order would have the provenance
# upload rejected once immutability is enabled.
# --------------------------------------------------------------------------
# --------------------------------------------------------------------------
# C ABI native libraries (bindings/c) — built per target on a native runner
# (no cross toolchain needed) and attached to the GitHub Release as the
# distribution channel. There is no package registry for the C ABI.
# --------------------------------------------------------------------------
c-abi-build:
name: C ABI library (${{ matrix.target }})
strategy:
fail-fast: false
matrix:
include:
- { host: ubuntu-latest, target: x86_64-unknown-linux-gnu }
- { host: ubuntu-24.04-arm, target: aarch64-unknown-linux-gnu }
- { host: macos-latest, target: x86_64-apple-darwin }
- { host: macos-latest, target: aarch64-apple-darwin }
- { host: windows-latest, target: x86_64-pc-windows-msvc }
- { host: windows-11-arm, target: aarch64-pc-windows-msvc }
runs-on: ${{ matrix.host }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
timeout-minutes: 6
- name: Build the C ABI library (cdylib + staticlib)
run: cargo build -p wickra-c --release --target ${{ matrix.target }}
- name: Package header + libraries
shell: bash
run: |
set -e
dir="wickra-c-${{ matrix.target }}"
mkdir -p "$dir/include" "$dir/lib"
cp bindings/c/include/wickra.h bindings/c/include/wickra.hpp "$dir/include/"
for f in libwickra.so libwickra.a libwickra.dylib wickra.dll wickra.dll.lib wickra.lib; do
src="target/${{ matrix.target }}/release/$f"
[ -f "$src" ] && cp "$src" "$dir/lib/"
done
tar -czf "$dir.tar.gz" "$dir"
echo "packaged $dir:"; ls -lR "$dir"
- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: c-abi-${{ matrix.target }}
path: wickra-c-${{ matrix.target }}.tar.gz
if-no-files-found: error
# Pack and publish the .NET binding to NuGet. Independent of the GitHub-release
# job so a C# hiccup never blocks the C/C++ asset release. Authentication uses
# NuGet Trusted Publishing (OIDC) — no long-lived API key. The 'wickra-release'
# trusted-publishing policy on nuget.org (owner KingchenC, repo wickra-lib/wickra,
# workflow release.yml) exchanges the GitHub OIDC token for a short-lived key.
csharp-publish:
name: Publish to NuGet
needs: c-abi-build
runs-on: ubuntu-latest
environment: release
permissions:
contents: read
id-token: write # request the GitHub OIDC token for trusted publishing
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Download the C ABI native libraries
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: c-abi-*
path: c-abi-artifacts
- name: Stage native libraries into runtimes/<rid>/native
shell: bash
run: |
set -e
declare -A RID=(
[x86_64-unknown-linux-gnu]=linux-x64
[aarch64-unknown-linux-gnu]=linux-arm64
[x86_64-apple-darwin]=osx-x64
[aarch64-apple-darwin]=osx-arm64
[x86_64-pc-windows-msvc]=win-x64
[aarch64-pc-windows-msvc]=win-arm64
)
base=bindings/csharp/Wickra/runtimes
for target in "${!RID[@]}"; do
archive=$(find c-abi-artifacts -name "wickra-c-$target.tar.gz" | head -1)
if [ -z "$archive" ]; then
echo "::error::missing native artifact for $target"; exit 1
fi
tmp=$(mktemp -d); tar -xzf "$archive" -C "$tmp"
dest="$base/${RID[$target]}/native"; mkdir -p "$dest"
for f in libwickra.so libwickra.dylib wickra.dll; do
src=$(find "$tmp" -name "$f" | head -1)
[ -n "$src" ] && cp "$src" "$dest/"
done
echo "staged ${RID[$target]}:"; ls -l "$dest"
done
- name: Pack
shell: bash
run: |
version="${GITHUB_REF_NAME#v}"
dotnet pack bindings/csharp/Wickra/Wickra.csproj -c Release -p:Version="$version" -o nupkg
# Exchange the GitHub OIDC token for a short-lived (~1h) NuGet API key.
# 'user' is the nuget.org profile name (the package owner), not an email.
- name: NuGet login (OIDC -> temporary API key)
uses: NuGet/login@8d196754b4036150537f80ac539e15c2f1028841 # v1.2.0
id: nuget_login
with:
user: KingchenC
# Pass the temporary key through the environment (not string-interpolated
# into the script) so it cannot be parsed as shell — avoids template injection.
- name: Push to NuGet
shell: bash
env:
NUGET_API_KEY: ${{ steps.nuget_login.outputs.NUGET_API_KEY }}
run: |
dotnet nuget push "nupkg/*.nupkg" --api-key "$NUGET_API_KEY" \
--source https://api.nuget.org/v3/index.json --skip-duplicate
- name: Upload the NuGet package as a build artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: nuget-package
path: nupkg/*.nupkg
if-no-files-found: error
github-release:
name: Attach assets to the draft GitHub Release
needs: [cargo-publish, python-publish, node-publish, wasm-publish]
needs: [cargo-publish, python-publish, node-publish, wasm-publish, c-abi-build]
runs-on: ubuntu-latest
permissions:
contents: write
@@ -644,7 +781,7 @@ jobs:
# the provenance bundle is attached (P24, immutability-ready).
draft: true
body: |
Wickra ${{ github.ref_name }} — streaming-first technical indicators across 4 language registries.
Wickra ${{ github.ref_name }} — streaming-first technical indicators across 4 language registries plus a C ABI.
### Install
@@ -665,6 +802,9 @@ jobs:
darwin-arm64, win32-x64-msvc)
- `wickra-*.tgz` — npm-pack tarballs (main package + per-platform subpackages + WASM)
- `*.crate` — cargo source crates (wickra-core, wickra-data, wickra)
- `wickra-c-<target>.tar.gz` — C ABI: `include/wickra.h` + `wickra.hpp`
and the cdylib/staticlib per target (linux/macos/windows × x64/arm64),
the hub for C / C++ / Go / C# / Java / R
### Auto-generated changelog
+18 -11
View File
@@ -9,7 +9,7 @@ name: Sync indicator count
# 2. GitHub repo "About" description — synced on push to main / v* tag
# 3. Docs site: index.md / overview.md / Indicators-Overview.md
# (wickra-lib/wickra-docs) — synced on push to main / v* tag*
# 4. Marketing site count (wickra-lib/webpage: index.md /
# 4. Marketing site count (wickra-lib/webpage: index.md / about.md /
# .vitepress/config.ts) — push to main / v* tag*
# 5. org profile README count (wickra-lib/.github, profile/README.md)
# — synced on push to main / v* tag*
@@ -42,10 +42,10 @@ name: Sync indicator count
# single source of truth for what the bindings reach.
#
# Design: on PRs this workflow is a READ-ONLY check. The indicator wiring
# (ScriptHelpers/_common.py wire_readme_counter) bumps both README.md and
# docs/README.md inside the author's code commit, so the counter is already
# correct by the time CI runs. If it is not, the check below fails loud and
# asks the author to re-run the wiring — it never pushes a fix-up commit.
# bumps both README.md and docs/README.md inside the author's code commit, so
# the counter is already correct by the time CI runs. If it is not, the check
# below fails loud and asks the author to re-run the wiring — it never pushes a
# fix-up commit.
#
# (An earlier version pushed a GITHUB_TOKEN "sync indicator count" commit to
# the PR head. Because GITHUB_TOKEN pushes trigger no workflows, that commit
@@ -86,10 +86,17 @@ jobs:
# merge ref) so the counter check validates exactly what will land. On
# push events we check out the default ref. No push is made, so a shallow
# checkout is enough.
#
# Check out by head SHA, not head ref (branch name): a fast `gh pr merge
# --squash --delete-branch` deletes the head branch the moment the PR
# merges, often before this queued read-only check reaches its checkout.
# Fetching the now-gone `refs/heads/<branch>` then fails the run (exit 1).
# The head SHA stays reachable via `refs/pull/N/head` after the branch is
# gone, so the checkout — and the run — survives an instant merge.
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref }}
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
repository: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name || github.repository }}
- name: Count indicators
@@ -149,7 +156,7 @@ jobs:
# actually live (Cloudflare Pages, P8.1); merging this PR is therefore
# gated on the domain resolving, otherwise the About link would 404.
homepage="https://docs.wickra.org"
desc="Streaming-first technical indicators with a Rust core and Python, Node.js, and WebAssembly bindings. ${n} indicators, O(1) per-tick updates, no system dependencies. Drop-in TA-Lib replacement."
desc="Streaming-first technical indicators with a Rust core and Python, Node.js, WebAssembly, C ABI, .NET, Go, 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).
@@ -180,14 +187,14 @@ jobs:
exit 0
fi
cd docs-count
sed -i -E "s/[0-9]+ (streaming-first )?indicators/${n} \1indicators/g" index.md overview.md Indicators-Overview.md
sed -i -E "s/[0-9]+ (streaming-first )?indicators/${n} \1indicators/g" index.md overview.md Indicators-Overview.md .vitepress/config.ts
if git diff --quiet; then
echo "Docs indicator count unchanged."
exit 0
fi
git config user.name "wickra-bot"
git config user.email "wickra-bot@users.noreply.github.com"
git add index.md overview.md Indicators-Overview.md
git add index.md overview.md Indicators-Overview.md .vitepress/config.ts
git commit -m "chore: sync indicator count to ${n}"
if ! git push 2>/dev/null; then
echo "::warning::push to wickra-lib/wickra-docs failed — ABOUT_SYNC_TOKEN likely lacks write (findings P10.0a)."
@@ -366,14 +373,14 @@ jobs:
exit 0
fi
cd webpage-count
sed -i -E "s/[0-9]+ (streaming-first )?indicators/${n} \1indicators/g" index.md .vitepress/config.ts
sed -i -E "s/[0-9]+ (streaming-first )?indicators/${n} \1indicators/g" index.md about.md .vitepress/config.ts
if git diff --quiet; then
echo "Webpage indicator count unchanged."
exit 0
fi
git config user.name "wickra-bot"
git config user.email "wickra-bot@users.noreply.github.com"
git add index.md .vitepress/config.ts
git add index.md about.md .vitepress/config.ts
git commit -m "chore: sync indicator count to ${n}"
if ! git push 2>/dev/null; then
echo "::warning::push to wickra-lib/webpage failed — ABOUT_SYNC_TOKEN likely lacks write (findings P10.0a)."
+22 -8
View File
@@ -27,16 +27,29 @@ or replace lives behind a separate crate boundary.
│ no I/O, no deps │ │ optional features │
└──────────────────────┘ └────────────────────┘
(every binding wraps the same core)
┌────────────┴───────────┬─────────────────────┐
│ │ │
┌──▼──────┐ ┌───────▼──────┐ ┌───────▼────────┐
Python │ │ Node │ │ WASM
│ (PyO3) │ │ (napi-rs) │ │ (wasm-bindgen) │
└─────────┘ └──────────────┘ └────────────────┘
every binding wraps the same core
┌────────────┼────────────┬────────────────┐
│ │ │ │
┌──▼───────┐ ┌──▼───────┐ ┌──▼───────────┐ ┌──▼──────────────────┐
│ Python │ │ Node │ │ WASM │ │ C ABI (cbindgen) │
(PyO3) │ │ (napi-rs)│ │(wasm-bindgen)│ │ cdylib + header
└──────────┘ └──────────┘ └──────────────┘ └─────────┬───────────┘
│ linked by
┌──────────▼──────────┐
│ C · C++ · C# · Go │
│ · Java · R │
└─────────────────────┘
```
Python, Node and WASM are *native* Rust bindings (PyO3 / napi-rs /
wasm-bindgen). The C ABI is the *hub* every other C-capable language links
against: it builds to a `cdylib`/`staticlib` plus a generated `wickra.h`, and
downstream languages link that one artifact rather than each re-wrapping the
core. C and C++ link it directly; the **C# / .NET** binding (`bindings/csharp`,
on NuGet), the **Go** binding (`bindings/go`, cgo) and the **R** binding
(`bindings/r`, `.Call`) are generated from `wickra.h`, with Java planned the
same way.
| Crate | Path | What it owns | Public deps |
|---|---|---|---|
| `wickra-core` | `crates/wickra-core` | every indicator, the `Indicator` trait, `BatchExt`, `Candle`/`Tick` types, `Error` | `thiserror`, `rayon` (parallel batch) |
@@ -45,6 +58,7 @@ or replace lives behind a separate crate boundary.
| `wickra-python` | `bindings/python` | `_wickra` PyO3 module + Python package | `pyo3`, `numpy`, depends on `wickra-core` |
| `wickra-node` | `bindings/node` | NAPI-RS native binding | `napi`, depends on `wickra-core` |
| `wickra-wasm` | `bindings/wasm` | WebAssembly binding | `wasm-bindgen`, depends on `wickra-core` |
| `wickra-c` | `bindings/c` | C ABI hub — `cdylib`/`staticlib` + generated `wickra.h` (cbindgen) | depends on `wickra-core` |
| `wickra-examples` | `examples/rust` | runnable binary examples | depends on `wickra`, `wickra-data` |
The `fuzz/` directory is **excluded** from the workspace (it has its own
+96
View File
@@ -0,0 +1,96 @@
# Benchmarks
Read these as **relative** speedups on identical input — absolute µs depend on
CPU, memory clock and OS scheduler, not a universal contract. **Streaming is the
headline**: it is where Wickra's design pays off and where the gap is measured in
orders of magnitude, not percent. The batch numbers come second and are shown
honestly — the leanest crates edge Wickra out on the simple recurrences, and that
is a deliberate trade for warmup/NaN semantics, not a ceiling.
- **Reproduced on:** Windows 11 Pro 26200, AMD Ryzen 9 9950X, 64 GB DDR5,
Rust 1.92 (release: `lto = "fat"`, `codegen-units = 1`), Python 3.12.
- **Reproduce yourself:**
- Rust core vs Rust crates: `cargo bench -p wickra-bench`
- Python vs Python libs: `pip install -e bindings/python[bench]` then
`python -m benchmarks.compare_libraries` (auto-detects installed peers).
## 1. Streaming — the structural win
Live trading feeds one tick at a time. Wickra updates every indicator in **O(1)**;
batch-only libraries (TA-Lib, tulipy, finta, pandas-ta) have no incremental API
and must recompute the whole history on every tick. Only `talipp` (Python) and
`ta-rs` / `yata` (Rust) carry real per-tick state. This is the gap the library
was built to expose.
**Python — per-tick latency** (seed 5 000 bars, then feed ticks one at a time):
| Indicator | **★&nbsp;Wickra** | talipp | TA-Lib (recompute) |
|------------------|------------------:|------------------|-----------------------|
| SMA(20) | **0.089 µs ★** | 0.96 µs (11×) | 422 µs (4 700×) |
| EMA(20) | **0.111 µs ★** | 1.19 µs (11×) | 430 µs (3 900×) |
| RSI(14) | **0.061 µs ★** | 0.95 µs (16×) | 298 µs (4 900×) |
| MACD(12, 26, 9) | **0.079 µs ★** | 3.30 µs (42×) | 327 µs (4 100×) |
| Bollinger(20, 2) | **0.089 µs ★** | 4.97 µs (56×) | 296 µs (3 300×) |
Against the only other incremental Python peer Wickra is **1156× faster**;
against the recompute-on-every-tick libraries it is **2 80019 000× faster**
(`finta` RSI hits 19 000×). tulipy / pandas-ta land in the same recompute band
as TA-Lib.
**Rust — per-tick latency** (whole 50 000-bar series, lower = faster):
| Indicator | **★&nbsp;Wickra** | kand | ta-rs | yata |
|------------------|------------------:|-----:|------:|-----:|
| SMA(20) | 50 | 38 | 47 | 38 |
| EMA(20) | 154 | 69 | 56 | 69 |
| RSI(14) | 164 | 216 | 74 | — |
| MACD(12, 26, 9) | 275 | 143 | 66 | — |
| Bollinger(20, 2) | **128 ★** | 248 | 168 | — |
| ATR(14) | 152 | 166 | 61 | — |
`ta-rs` hands back a bare `f64` from the first tick with no warmup and no
validation; it leads several rows by giving those guarantees up. Against `kand`,
Wickra wins streaming RSI, Bollinger and ATR. `yata` exposes only SMA/EMA as
raw-value methods, so its other rows are omitted rather than faked.
## 2. Batch — competitive, not the headline
Whole series in one call. Here hand-tuned C (`tulipy`, TA-Lib) and the leanest
Rust crate (`kand`) win the simple recurrences — Wickra trades a few µs per pass
for the `None`-warmup, NaN-safety and bit-exact `batch == streaming` guarantees
none of them keep. It still wins several rows outright and beats the rest of the
field everywhere.
**Python** (20 000-bar pass, µs/op, lower = faster):
| Indicator | Wickra | TA-Lib | tulipy | pandas-ta | finta |
|------------------|---------:|---------:|---------:|----------:|---------:|
| SMA(20) | 22.2 | **15.6** | 15.9 | 32.7 | 290.1 |
| EMA(20) | 30.5 | **30.4** | 30.9 | 46.7 | 198.5 |
| RSI(14) | 52.3 | 72.0 | **34.2** | 88.8 | 812.3 |
| MACD(12, 26, 9) | 129.8 | 111.1 | **38.4** | 286.8 | 716.7 |
| Bollinger(20, 2) | 87.2 | 74.6 | **37.9** | 474.3 | 1255.5 |
| ATR(14) | 74.7 | 87.3 | **35.5** | — | 3496.4 |
Wickra beats pandas-ta and finta on every row and TA-Lib on RSI and ATR;
tulipy's SIMD C (and TA-Lib on SMA/EMA) lead the remaining rows.
**Rust** (50 000-bar pass, µs, lower = faster). Only Wickra and `kand` expose a
batch API; `ta-rs` and `yata` are streaming-only:
| Indicator | **★&nbsp;Wickra** | kand |
|------------------|------------------:|-------:|
| SMA(20) | 53 | **41** |
| EMA(20) | 111 | **71** |
| RSI(14) | **221 ★** | 259 |
| MACD(12, 26, 9) | 533 | **327** |
| Bollinger(20, 2) | **404 ★** | 460 |
| ATR(14) | **122 ★** | 169 |
Run the suite yourself:
```bash
cargo bench -p wickra-bench # Rust core vs kand / ta-rs / yata
pip install -e bindings/python[bench] # Python peers
python -m benchmarks.compare_libraries
```
+199 -2
View File
@@ -5,7 +5,184 @@ All notable changes to Wickra are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.7.8] - 2026-06-09
### Added
- **R binding (`bindings/r`)** — an R package reaching the C ABI hub through R's
native `.Call` interface, exposing all 514 indicators as constructors that
return a `wickra_indicator` object with `update`/`batch`/`reset` methods. The
C glue and R wrappers are generated from `wickra.h`; the native handle is freed
by a registered finalizer. Ships a full example suite mirroring the C, C# and
Go examples; distributed for r-universe / source install.
## [0.7.7] - 2026-06-09
### Added
- **Go binding (`bindings/go`)** — a cgo binding over the C ABI hub exposing all
514 indicators as idiomatic types with `New<Indicator>` constructors and
`Update`/`Batch`/`Reset`/`Close` methods, generated from `wickra.h`. Handles are
freed by `Close()` with a `runtime.SetFinalizer` backstop. Ships a full example
suite mirroring the C and C# examples; distributed as a subdirectory module
(`go get github.com/wickra-lib/wickra/bindings/go`).
## [0.7.6] - 2026-06-09
### Added
- **C# / .NET binding (`bindings/csharp`)** — the first language stecker on the
C ABI hub. Exposes all 514 indicators as idiomatic `IDisposable` classes via
`[LibraryImport]` source-generated P/Invoke, generated from `wickra.h`. Ships
on NuGet as `Wickra` with prebuilt native libraries for six target triples
(win/linux/osx × x64/arm64), plus a full example suite mirroring the C examples.
## [0.7.5] - 2026-06-09
### Added
- **C ABI (`bindings/c`)** — a `cdylib` + `staticlib` plus a generated
`include/wickra.h` exposing all 514 indicators and 10 bar builders over an
opaque-handle C ABI: the hub any C-capable language (C, C++, Go, C#, Java, R)
links against, complementing the native Python/Node/WASM bindings. Ships a
full example suite (streaming, backtest, multi-timeframe, OpenMP parallel
fan-out, three educational strategies, and Binance fetch/live over `curl`)
mirroring the other bindings, plus an optional `wickra.hpp` C++ RAII wrapper.
## [0.7.4] - 2026-06-08
- **Three-Line Break** — Three-line-break bars (reversal needs N-line break) (`THREE_LINE_BREAK_BARS`).
- **Run** — Run bars (consecutive same-direction tick runs) (`RUN_BARS`).
- **Imbalance** — Imbalance bars (tick-rule signed imbalance threshold) (`IMBALANCE_BARS`).
- **Dollar** — Dollar bars (fixed traded value per bar, Lopez de Prado) (`DOLLAR_BARS`).
- **Volume** — Volume bars (fixed traded volume per bar) (`VOLUME_BARS`).
- **Tick** — Tick bars (fixed candle count per bar) (`TICK_BARS`).
- **Range** — Range bars (fixed price-range bricks) (`RANGE_BARS`).
## [0.7.3] - 2026-06-08
- **M2Measure** — M2 measure (Modigliani; Sharpe expressed in benchmark return units) (`M2Measure`).
- **UpsidePotentialRatio** — Upside Potential Ratio (upside mean over downside deviation) (`UpsidePotentialRatio`).
- **GainToPainRatio** — Gain-to-Pain Ratio (sum of returns over sum of losses) (`GainToPainRatio`).
- **CommonSenseRatio** — Common Sense Ratio (tail ratio times gain-to-pain) (`CommonSenseRatio`).
- **KRatio** — K-Ratio (Kestner; equity-curve slope over its standard error) (`KRatio`).
- **TailRatio** — Tail Ratio (95th over absolute 5th return percentile) (`TailRatio`).
- **MartinRatio** — Martin Ratio (Ulcer Performance Index; return over RMS drawdown) (`MartinRatio`).
- **BurkeRatio** — Burke Ratio (return over root-sum-squared drawdowns) (`BurkeRatio`).
- **SterlingRatio** — Sterling Ratio (mean return over average drawdown) (`SterlingRatio`).
## [0.7.2] - 2026-06-08
- **Composite Profile** — multi-session composite volume profile exposing POC, VAH and VAL (`CompositeProfile`).
- **High/Low Volume Nodes** — highest- and lowest-volume price nodes in the profile (`HighLowVolumeNodes`).
- **Profile Shape** — profile shape classification (b/P/D normal) as a numeric code (`ProfileShape`).
- **Single Prints** — count of single-print (low-activity) price levels in the profile (`SinglePrints`).
- **Naked POC** — most recent untouched (naked) point of control level (`NakedPoc`).
## [0.7.1] - 2026-06-08
- **Open-Interest Momentum** — rate-of-change of open interest over a rolling window (`OpenInterestMomentum`).
- **Funding-Implied APR** — annualised funding rate (per-interval funding times intervals per year) (`FundingImpliedApr`).
- **Perpetual Premium Index** — relative premium of the mark price over the index price (`PerpetualPremiumIndex`).
- **OI-to-Volume Ratio** — open interest divided by taker volume (position turnover proxy) (`OiToVolumeRatio`).
- **Estimated Leverage Ratio** — open interest divided by aggregate long+short position size (leverage proxy) (`EstimatedLeverageRatio`).
## [0.7.0] - 2026-06-08
- **Hasbrouck Information Share** — variance-ratio proxy for each venue's share of price discovery (Hasbrouck information share) (`HasbrouckInformationShare`).
- **PIN** — probability of informed trading from rolling buy/sell imbalance (EKOP single-window estimator) (`Pin`).
- **Trade-Sign Autocorrelation** — lag-1 autocorrelation of the signed trade aggressor (order-flow persistence) (`TradeSignAutocorrelation`).
## [0.6.9] - 2026-06-08
- **Tristar** — a three-doji star reversal: three consecutive dojis with the middle gapped above (bearish) or below (bullish) its neighbours (`Tristar`).
- **Harami Cross** — a Harami whose second candle is a contained doji, a stronger reversal than a plain Harami (`HaramiCross`).
- **Tower Top/Bottom** — a tall bar, a small pause bar, then a tall opposite bar marking a reversal (`TowerTopBottom`).
- **Frying Pan Bottom** — a rounded (U-shaped) accumulation base over the lookback window, confirmed when price recovers above the rim (`FryPanBottom`).
- **Dumpling Top** — a rounded (dome-shaped) distribution top over the lookback window, confirmed when price breaks below the start (`DumplingTop`).
- **New Price Lines** — flags a run of N consecutive new closing highs (+1) or lows (-1), the eight/ten-new-price-lines exhaustion gauge (`NewPriceLines`).
## [0.6.8] - 2026-06-08
- **Smoothed Heikin-Ashi** — a Heikin-Ashi candle computed from EMA-smoothed OHLC, damping noise into a cleaner trend candle (`SmoothedHeikinAshi`).
- **Heikin-Ashi Oscillator** — the Heikin-Ashi candle body (`ha_close ha_open`), optionally EMA-smoothed, as a zero-line oscillator (`HeikinAshiOscillator`).
- **Three Line Break** — the trend direction of a line-break chart, reversing only when the close breaks the extreme of the last N lines (`ThreeLineBreak`).
- **Equivolume** — a chart box whose height is the bar range and whose width is volume-relative, fusing price range with activity (`Equivolume`).
- **CandleVolume** — a candle whose body is close-minus-open and whose width is volume-relative, a volume-weighted candle chart (`CandleVolume`).
## [0.6.7] - 2026-06-08
- **TD Camouflage** — a DeMark qualifier flagging hidden intrabar strength or weakness against the prior close (`TDCamouflage`).
- **TD Clop** — a DeMark two-bar open/close engulfing reversal where the bar opens beyond and closes back across the prior body (`TDClop`).
- **TD Clopwin** — the inside-body cousin of TD Clop, marking a compression bar whose direction hints at the next move (`TDClopwin`).
- **TD Propulsion** — a DeMark continuation thrust that opens on the trend side and closes beyond the prior bar's extreme (`TDPropulsion`).
- **TD Trap** — an inside ("trap") bar followed by a close beyond its range, triggering a directional breakout signal (`TDTrap`).
- **TD D-Wave** — a streaming Elliott-style swing-wave counter labelling the market's 15 impulse / AC correction sequence (`TDDWave`).
- **TD Moving Averages** — the DeMark ST1 (fast) and ST2 (slow) median-price trend ribbon whose crossover frames the trend (`TDMovingAverage`).
## [0.6.6] - 2026-06-08
- **Pivot Reversal** — a breakout signal when price closes through the most recently confirmed swing pivot (`PIVOT_REVERSAL`).
- **Volume-Weighted Support/Resistance** — a band whose edges are the volume-weighted average of recent highs and lows (`VOLUME_WEIGHTED_SR`).
- **Andrews Pitchfork** — median line and two parallels projected from the last three swing pivots (`ANDREWS_PITCHFORK`).
- **Murrey Math Lines** — T. H. Murrey's eighths grid over the recent trading range, each level acting as support/resistance (`MURREY_MATH_LINES`).
- **Central Pivot Range** — the classic pivot flanked by two central levels gauging the day's expected character (`CENTRAL_PIVOT_RANGE`).
- **Faster scalar batch paths** — `Ema`, `Rsi`, `BollingerBands`, `MacdIndicator` and `Atr` gained dedicated batch fast paths (used by the Python bindings) that strip per-element `Option`/validation overhead and the intermediate `Vec<Option<_>>` allocation, while staying *bit-for-bit* equal to replaying `update` (including the SMA/Bollinger drift-reseed). Python batch is ~2× faster on EMA/RSI/MACD/ATR; streaming is unchanged.
- **Cross-library benchmark refresh** — `benchmarks/compare_libraries.py` now measures the median across timing rounds (`--rounds` / `--streaming-rounds`), adds `--skip-batch` / `--skip-streaming`, and drives every peer through the streaming arena (recompute for batch-only libraries). `wickra-bench` compares the batch fast paths against `kand`.
## [0.6.5] - 2026-06-07
- **Autocorrelation Periodogram** — Ehlers autocorrelation periodogram: dominant cycle period estimate (`AUTOCORRPGRAM`).
- **Even Better Sinewave** — Ehlers Even Better Sinewave: normalized cycle-phase oscillator (`EVENBETTERSINE`).
- **Bandpass Filter** — Ehlers bandpass filter: isolates a frequency band around the dominant cycle (`BANDPASS`).
- **Adaptive CCI** — Adaptive CCI: efficiency-ratio-adaptive CCI on typical price (`ADAPTIVECCI`).
- **Universal Oscillator** — Ehlers Universal Oscillator: SuperSmoother-based normalized cycle oscillator (`UNIVERSALOSC`).
- **Adaptive RSI** — Adaptive RSI: dominant-cycle-tuned RSI length (Ehlers) (`ADAPTIVERSI`).
- **Correlation Trend Indicator** — Ehlers Correlation Trend Indicator: Pearson correlation of price vs time (`CTI`).
- **Trendflex** — Ehlers Trendflex: trend-following companion to Reflex (`TRENDFLEX`).
- **Reflex** — Ehlers Reflex: trend-cycle oscillator measuring slope-adjusted displacement (`REFLEX`).
- **Highpass Filter** — Ehlers highpass filter: removes low-frequency trend, leaving cyclic component (`HIGHPASS`).
## [0.6.4] - 2026-06-07
- **Kendall Tau** — Kendall rank correlation (tau-b) over a rolling window of paired observations (`KENDALLTAU`).
- **Sample Entropy** — Sample entropy: regularity/complexity of a rolling series (Richman-Moorman) (`SAMPLEENT`).
- **Shannon Entropy** — Shannon entropy of a rolling value distribution over fixed bins (`SHANNONENT`).
- **Rolling Min-Max Scaler** — Rolling min-max scaler mapping the latest value to 0..1 over a rolling window (`ROLLINGMINMAX`).
- **Jarque-Bera** — Jarque-Bera normality test statistic over a rolling window (`JARQUEBERA`).
## [0.6.3] - 2026-06-07
- **Volume-Weighted MACD** — Volume-Weighted MACD: MACD computed on VWMA instead of EMA, with signal line and histogram (`VWMACD`).
- **Better Volume** — Better Volume (VSA): classifies volume against bar spread to surface effort/result imbalance (`BETTERVOL`).
- **Intraday Intensity Index** — Intraday Intensity Index: volume weighted by close position within the bar range (`INTRADAYINT`).
- **Trade Volume Index** — Trade Volume Index: accumulates volume by tick direction past a min-tick threshold (distinct from TSV) (`TRADEVOLIDX`).
- **Twiggs Money Flow** — Twiggs Money Flow: volume-weighted accumulation using true range and Wilder smoothing (distinct from CMF) (`TWIGGSMF`).
- **Williams Accumulation/Distribution** — Williams Accumulation/Distribution: cumulative price-direction accumulator (distinct from Chaikin A/D) (`WILLIAMSAD`).
- **Volume RSI** — Volume RSI: Wilder-style RSI computed on signed volume flow (`VOLUMERSI`).
## [0.6.2] - 2026-06-07
- **Modified MA Stop** — Modified MA Stop — SMMA-ratcheted trailing stop with directional flip (`MODIFIED_MA_STOP`).
- **Time-Based Stop** — Time-Based Stop — bar-count timer that fires after a fixed holding period (`TIME_BASED_STOP`).
- **NRTR** — NRTR (Nick Rypock Trailing Reverse) — percentage trailing-reverse stop (`NRTR`).
- **ATR Ratchet** — ATR Ratchet — Kaufman per-bar tightening volatility trailing stop (`ATR_RATCHET`).
- **Elder SafeZone** — Elder SafeZone Stop — average noise-penetration trailing stop with directional flip (`ELDER_SAFE_ZONE`).
- **Kase DevStop** — Kase DevStop volatility trailing stop using standard-deviation of two-bar true range (`KASE_DEV_STOP`).
## [0.6.1] - 2026-06-07
- **Projection Oscillator** — Widner projection oscillator: close position inside the projection bands, scaled 0..100 (`ProjectionOscillator`).
- **Projection Bands** — Widner projection bands: forward-projected high/low regression envelope (`ProjectionBands`).
- **Median Channel** — robust median +/- multiplier*MAD envelope (`MedianChannel`).
- **Bomar Bands** — adaptive percentage bands containing a target coverage fraction of recent closes (`BomarBands`).
- **Quartile Bands** — rolling 25th/50th/75th-percentile (Q1/median/Q3) envelope (`QuartileBands`).
## [0.6.0] - 2026-06-06
- **Volatility Cone** — volatility cone: current realized volatility within its historical min/median/max envelope (`VolatilityCone`).
- **VolatilityRatio** — Schwager's volatility ratio: true range over the EMA of prior true ranges (`VolatilityRatio`).
- **BipowerVariation** — jump-robust realized bipower variation (pi/2 sum of adjacent absolute log-return products) (`BipowerVariation`).
- **VolatilityOfVolatility** — vol-of-vol: sample stddev of a rolling realized-volatility series (`VolatilityOfVolatility`).
- **Garch11** — GARCH(1,1) conditional volatility with a long-run-variance anchor (`Garch11`).
- **EwmaVolatility** — RiskMetrics exponentially-weighted volatility of log returns (lambda decay) (`EwmaVolatility`).
## [0.5.9] - 2026-06-06
### Added
- Internal Rust cross-library benchmark harness (`crates/wickra-bench`, not
published) comparing Wickra against `kand`, `ta-rs` and `yata` on an identical
candle series in both streaming and batch modes; wired into the nightly
`cross-library-bench` workflow.
- `tulipy` runners and expanded per-tick streaming coverage (SMA, EMA, RSI,
MACD, Bollinger) in the Python `compare_libraries` benchmark.
### Changed
- Faster streaming and batch updates for SMA, Bollinger Bands, RSI, EMA and ATR
(flat ring buffers replacing `VecDeque`, hoisted reciprocals in the Wilder
smoothing, leaner hot state) — indicator outputs are unchanged.
- Rewrote the README benchmark section into honest, tiered tables (Rust core vs
the other Rust crates, and Python vs the Python ecosystem) that show where
Wickra wins and where it loses, not only the favourable comparisons.
## [0.5.8] - 2026-06-04
- **TSF Oscillator** — the percentage gap of the close to the one-bar-ahead time-series forecast, a close-relative companion to CFO (`TsfOscillator`).
@@ -1273,7 +1450,27 @@ 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.5.8...HEAD
[Unreleased]: https://github.com/wickra-lib/wickra/compare/v0.7.8...HEAD
[0.7.8]: https://github.com/wickra-lib/wickra/compare/v0.7.7...v0.7.8
[0.7.7]: https://github.com/wickra-lib/wickra/compare/v0.7.6...v0.7.7
[0.7.6]: https://github.com/wickra-lib/wickra/compare/v0.7.5...v0.7.6
[0.7.5]: https://github.com/wickra-lib/wickra/compare/v0.7.4...v0.7.5
[0.7.4]: https://github.com/wickra-lib/wickra/compare/v0.7.3...v0.7.4
[0.7.3]: https://github.com/wickra-lib/wickra/compare/v0.7.2...v0.7.3
[0.7.2]: https://github.com/wickra-lib/wickra/compare/v0.7.1...v0.7.2
[0.7.1]: https://github.com/wickra-lib/wickra/compare/v0.7.0...v0.7.1
[0.7.0]: https://github.com/wickra-lib/wickra/compare/v0.6.9...v0.7.0
[0.6.9]: https://github.com/wickra-lib/wickra/compare/v0.6.8...v0.6.9
[0.6.8]: https://github.com/wickra-lib/wickra/compare/v0.6.7...v0.6.8
[0.6.7]: https://github.com/wickra-lib/wickra/compare/v0.6.6...v0.6.7
[0.6.6]: https://github.com/wickra-lib/wickra/compare/v0.6.5...v0.6.6
[0.6.5]: https://github.com/wickra-lib/wickra/compare/v0.6.4...v0.6.5
[0.6.4]: https://github.com/wickra-lib/wickra/compare/v0.6.3...v0.6.4
[0.6.3]: https://github.com/wickra-lib/wickra/compare/v0.6.2...v0.6.3
[0.6.2]: https://github.com/wickra-lib/wickra/compare/v0.6.1...v0.6.2
[0.6.1]: https://github.com/wickra-lib/wickra/compare/v0.6.0...v0.6.1
[0.6.0]: https://github.com/wickra-lib/wickra/compare/v0.5.9...v0.6.0
[0.5.9]: https://github.com/wickra-lib/wickra/compare/v0.5.8...v0.5.9
[0.5.8]: https://github.com/wickra-lib/wickra/compare/v0.5.7...v0.5.8
[0.5.7]: https://github.com/wickra-lib/wickra/compare/v0.5.6...v0.5.7
[0.5.6]: https://github.com/wickra-lib/wickra/compare/v0.5.5...v0.5.6
+12 -1
View File
@@ -21,6 +21,10 @@ licensed as above, without any additional terms or conditions.
| `bindings/python` | PyO3 bindings (`wickra` on PyPI). |
| `bindings/node` | napi-rs bindings (`wickra` on npm). |
| `bindings/wasm` | wasm-bindgen bindings (`wickra-wasm` on npm). |
| `bindings/c` | C ABI — `cdylib` + `staticlib` + generated `include/wickra.h`. The hub for C / C++ and any C-capable language. |
| `bindings/csharp` | .NET binding over the C ABI (`Wickra` on NuGet) — `[LibraryImport]` P/Invoke generated from `wickra.h`. |
| `bindings/go` | Go binding over the C ABI via cgo (module tag `bindings/go/vX.Y.Z`) — wrappers generated from `wickra.h`. |
| `bindings/r` | R binding over the C ABI via `.Call` (R package) — C glue + R wrappers generated from `wickra.h`. |
| `examples/` | Runnable examples. |
| `docs/` | Pointer to the documentation site (docs.wickra.org); the docs live in the `wickra-lib/wickra-docs` repo. |
@@ -102,7 +106,14 @@ installed. Dependabot also keeps the `.github/requirements` pins current.
- **Streaming parity.** An indicator's `batch` output must equal the sequence
of `update` calls.
- **Bindings.** A change to a public indicator API must be mirrored across the
Python, Node, and WASM bindings, including their type stubs / `.d.ts`.
Python, Node, and WASM bindings, including their type stubs / `.d.ts`. The C ABI
(`bindings/c`) is generated from the core, so regenerate it from the core and
commit `src/lib.rs` + `include/wickra.h`. The C# binding (`bindings/csharp`) is
generated from `wickra.h`, so regenerate and commit its `Generated/*.g.cs` too.
The Go binding (`bindings/go`) is likewise generated from `wickra.h`, so
regenerate and commit `indicators_gen.go` (`gofmt`-clean). The R binding
(`bindings/r`) is generated from `wickra.h` too, so regenerate and commit
`src/wickra.c` + `R/indicators.R`.
- **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
+121 -7
View File
@@ -702,6 +702,16 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "kand"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af1f41590bd014ef6c3dd815b45f07deb4c3198e355a4319bb7521b6a3a6aeb5"
dependencies = [
"num_enum",
"thiserror",
]
[[package]]
name = "leb128fmt"
version = "0.1.0"
@@ -911,6 +921,28 @@ dependencies = [
"libm",
]
[[package]]
name = "num_enum"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"
dependencies = [
"num_enum_derive",
"rustversion",
]
[[package]]
name = "num_enum_derive"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "numpy"
version = "0.28.0"
@@ -1081,6 +1113,15 @@ dependencies = [
"syn",
]
[[package]]
name = "proc-macro-crate"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
dependencies = [
"toml_edit",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
@@ -1498,6 +1539,12 @@ dependencies = [
"syn",
]
[[package]]
name = "ta"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "609409d472a0a7d8d4dd9e19891bbdef546b9dce670c3057d0e02192dc541226"
[[package]]
name = "target-lexicon"
version = "0.13.5"
@@ -1607,6 +1654,36 @@ dependencies = [
"tungstenite",
]
[[package]]
name = "toml_datetime"
version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_edit"
version = "0.25.12+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7"
dependencies = [
"indexmap",
"toml_datetime",
"toml_parser",
"winnow",
]
[[package]]
name = "toml_parser"
version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
dependencies = [
"winnow",
]
[[package]]
name = "tungstenite"
version = "0.29.0"
@@ -1867,7 +1944,7 @@ dependencies = [
[[package]]
name = "wickra"
version = "0.5.8"
version = "0.7.8"
dependencies = [
"approx",
"criterion",
@@ -1876,9 +1953,28 @@ dependencies = [
"wickra-data",
]
[[package]]
name = "wickra-bench"
version = "0.7.8"
dependencies = [
"criterion",
"kand",
"ta",
"wickra",
"wickra-data",
"yata",
]
[[package]]
name = "wickra-c"
version = "0.7.8"
dependencies = [
"wickra-core",
]
[[package]]
name = "wickra-core"
version = "0.5.8"
version = "0.7.8"
dependencies = [
"approx",
"proptest",
@@ -1888,7 +1984,7 @@ dependencies = [
[[package]]
name = "wickra-data"
version = "0.5.8"
version = "0.7.8"
dependencies = [
"approx",
"csv",
@@ -1905,7 +2001,7 @@ dependencies = [
[[package]]
name = "wickra-examples"
version = "0.0.0"
version = "0.7.8"
dependencies = [
"serde_json",
"tokio",
@@ -1915,7 +2011,7 @@ dependencies = [
[[package]]
name = "wickra-node"
version = "0.5.8"
version = "0.7.8"
dependencies = [
"napi",
"napi-build",
@@ -1925,7 +2021,7 @@ dependencies = [
[[package]]
name = "wickra-python"
version = "0.5.8"
version = "0.7.8"
dependencies = [
"numpy",
"pyo3",
@@ -1934,7 +2030,7 @@ dependencies = [
[[package]]
name = "wickra-wasm"
version = "0.5.8"
version = "0.7.8"
dependencies = [
"console_error_panic_hook",
"js-sys",
@@ -1991,6 +2087,15 @@ dependencies = [
"windows-link",
]
[[package]]
name = "winnow"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
dependencies = [
"memchr",
]
[[package]]
name = "wit-bindgen"
version = "0.51.0"
@@ -2091,6 +2196,15 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
[[package]]
name = "yata"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b4ef8ddfa3ccd93454262c0e60a43a2bbf403d404174e1815f7581d5028229f"
dependencies = [
"serde",
]
[[package]]
name = "yoke"
version = "0.8.2"
+4 -2
View File
@@ -7,12 +7,14 @@ members = [
"bindings/python",
"bindings/wasm",
"bindings/node",
"bindings/c",
"examples/rust",
"crates/wickra-bench",
]
exclude = ["fuzz"]
[workspace.package]
version = "0.5.8"
version = "0.7.8"
authors = ["kingchenc <support@wickra.org>"]
edition = "2021"
rust-version = "1.86"
@@ -24,7 +26,7 @@ keywords = ["finance", "trading", "indicators", "technical-analysis", "ta"]
categories = ["finance", "mathematics", "science"]
[workspace.dependencies]
wickra-core = { path = "crates/wickra-core", version = "0.5.8" }
wickra-core = { path = "crates/wickra-core", version = "0.7.8" }
thiserror = "2"
rayon = "1.10"
+158 -114
View File
@@ -1,25 +1,27 @@
<p align="center">
<a href="https://wickra.org"><img src="https://raw.githubusercontent.com/wickra-lib/.github/main/profile/wickra-banner.webp?v=423" alt="Wickra — streaming-first technical indicators" width="100%"></a>
<a href="https://wickra.org"><img src="https://raw.githubusercontent.com/wickra-lib/.github/main/profile/wickra-banner.webp?v=514" alt="Wickra — streaming-first technical indicators" width="100%"></a>
</p>
[![CI](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml/badge.svg)](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml)
[![CodeQL](https://github.com/wickra-lib/wickra/actions/workflows/codeql.yml/badge.svg)](https://github.com/wickra-lib/wickra/actions/workflows/codeql.yml)
[![codecov](https://codecov.io/gh/wickra-lib/wickra/branch/main/graph/badge.svg)](https://codecov.io/gh/wickra-lib/wickra)
[![GitHub release](https://img.shields.io/github/v/release/wickra-lib/wickra?logo=github&color=green)](https://github.com/wickra-lib/wickra/releases/latest)
[![crates.io](https://img.shields.io/crates/v/wickra.svg?logo=rust&color=orange)](https://crates.io/crates/wickra)
[![PyPI](https://img.shields.io/pypi/v/wickra.svg?logo=pypi&color=blue)](https://pypi.org/project/wickra/)
[![npm](https://img.shields.io/npm/v/wickra.svg?logo=npm&color=red)](https://www.npmjs.com/package/wickra)
[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT_OR_Apache--2.0-blue)](#license)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/wickra-lib/wickra/badge)](https://scorecard.dev/viewer/?uri=github.com/wickra-lib/wickra)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/13094/badge)](https://www.bestpractices.dev/projects/13094)
[![Build provenance](https://img.shields.io/badge/provenance-attested-brightgreen?logo=github)](https://github.com/wickra-lib/wickra/attestations)
[![Docs](https://img.shields.io/badge/docs-docs.wickra.org-0ea5e9?logo=readthedocs&logoColor=white)](https://docs.wickra.org)
[![CI](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/ci.svg)](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml)
[![CodeQL](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/codeql.svg)](https://github.com/wickra-lib/wickra/actions/workflows/codeql.yml)
[![codecov](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/codecov.svg)](https://codecov.io/gh/wickra-lib/wickra)
[![GitHub release](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/release.svg)](https://github.com/wickra-lib/wickra/releases/latest)
[![crates.io](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/crates.svg)](https://crates.io/crates/wickra)
[![PyPI](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/pypi.svg)](https://pypi.org/project/wickra/)
[![npm](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/npm.svg)](https://www.npmjs.com/package/wickra)
[![NuGet](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/nuget.svg)](https://www.nuget.org/packages/Wickra)
[![License: MIT OR Apache-2.0](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/license.svg)](#license)
[![OpenSSF Scorecard](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/scorecard.svg)](https://scorecard.dev/viewer/?uri=github.com/wickra-lib/wickra)
[![OpenSSF Best Practices](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/best-practices.svg)](https://www.bestpractices.dev/projects/13094)
[![Build provenance](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/provenance.svg)](https://github.com/wickra-lib/wickra/attestations)
[![Docs](https://raw.githubusercontent.com/wickra-lib/.github/main/profile/badges/docs.svg)](https://docs.wickra.org)
**Streaming-first technical indicators. Install with `pip install wickra` — no system dependencies.**
Wickra is a multi-language technical-analysis library with a Rust core and
bindings for Python, Node.js, and WebAssembly. Every indicator is a state
machine that updates in O(1) per new data point, so live trading bots and
native bindings for Python, Node.js and WebAssembly, plus a C ABI that C, C++,
C# / .NET, Go, R and any other C-capable language links against. Every indicator is a
state machine that updates in O(1) per new data point, so live trading bots and
historical backtests share the exact same implementation.
```python
@@ -46,9 +48,13 @@ Full documentation lives at **[docs.wickra.org](https://docs.wickra.org)**:
- **Quickstarts** — [Rust](https://docs.wickra.org/Quickstart-Rust),
[Python](https://docs.wickra.org/Quickstart-Python),
[Node](https://docs.wickra.org/Quickstart-Node),
[WASM](https://docs.wickra.org/Quickstart-WASM).
[WASM](https://docs.wickra.org/Quickstart-WASM),
[C](https://docs.wickra.org/Quickstart-C),
[C#](https://docs.wickra.org/Quickstart-CSharp),
[Go](https://docs.wickra.org/Quickstart-Go),
[R](https://docs.wickra.org/Quickstart-R).
- **Indicators** — a per-indicator deep dive (formula, parameters, warmup) for
every one of the 423 indicators; start at the
every one of the 514 indicators; start at the
[indicators overview](https://docs.wickra.org/Indicators-Overview).
- **Reference** — [warmup periods](https://docs.wickra.org/Warmup-Periods),
[streaming vs batch](https://docs.wickra.org/Streaming-vs-Batch),
@@ -58,85 +64,79 @@ Full documentation lives at **[docs.wickra.org](https://docs.wickra.org)**:
[TA-Lib migration](https://docs.wickra.org/TA-Lib-Migration),
[FAQ](https://docs.wickra.org/FAQ).
## Why Wickra
Most TA libraries are fast, *or* multi-language, *or* broad. Wickra refuses to
pick. It's the streaming-first engine built for the workload the others treat as
an afterthought — **live, tick-by-tick data** — without giving up the breadth of
a full batch library, and without making you reimplement your indicators four
times to get there.
- **The biggest streaming-native catalogue, period.** 514 indicators across 24
families — candlesticks, harmonic & chart patterns, market profile, market
breadth, Renko/Kagi/Point&Figure bars, Ehlers DSP cycles, risk/performance
metrics — every single one updating in **O(1) per tick**. TA-Lib ships ~150 and
none of them stream.
- **One Rust core, five first-class targets.** Native **Python · Node.js ·
WebAssembly · Rust** plus a **C ABI** for C / C++, C# / .NET, Go, R and any other C-capable language —
identical math, identical results, zero per-language reimplementation and zero
GIL bottleneck.
- **Correct by construction, not by hope.** Every `update` validates its input,
runs a real warmup, and returns an `Option` so a single bad tick can't silently
poison state. `batch == streaming` is **bit-exact, fuzzed and 100 %-line-covered
for all 514 indicators**.
- **Orders of magnitude faster where it counts.** In streaming Wickra is **1156×**
faster than the only other incremental peer and **thousands of times** faster
than recompute-on-every-tick libraries. On batch it wins several rows outright
and trades the simple recurrences (SMA, EMA, MACD) for its guarantees — and
the losses are shown, not hidden.
- **Install in one line, anywhere.** `pip install wickra` / `npm install wickra`
precompiled wheels and binaries, **no C toolchain, none of TA-Lib's setup pain**.
macOS · Linux · Windows.
- **Batteries included.** Indicator chaining, a streaming OHLCV CSV reader, and a
live Binance kline feed ship in the box.
- **Truly permissive.** **MIT OR Apache-2.0** — drop it straight into commercial
and closed-source work.
Every other library forces one of those compromises. Wickra doesn't:
| Library | Install | Streaming | Languages | Indicators | Active |
|------------------|-------------|-------------|-----------------------------|-----------:|--------|
| **★&nbsp;Wickra**| **clean** | **yes, O(1)** | **Rust · Python · Node · WASM** | **514** | **yes** |
| | | | **C · C# · Go · R** | | |
| kand | clean | yes | Python · WASM · Rust | ~60 | yes |
| ta-rs | clean | yes | Rust only | ~30 | stale |
| yata | clean | partial | Rust only | ~35 | yes |
| TA-Lib | yes (C deps)| no | many bindings | ~150 | barely |
| pandas-ta | clean | no | Python | ~130 | slow |
| finta | clean | no | Python | ~80 | stale |
| talipp | clean | yes | Python | ~40 | yes |
Broad, multi-language, streaming-native **and** honest about its trade-offs — at
the same time. That's the combination no one else ships.
## Why Wickra exists
The Python TA ecosystem has plenty of libraries — TA-Lib, pandas-ta, finta,
talipp, tulipy — and every one of them shares the same blind spot:
Wickra started as a personal itch. The existing TA libraries never quite fit the
projects I was building, so I decided to build one from the ground up — partly to
learn, partly because I genuinely enjoy taking something that already exists and
trying to do it differently (and, ideally, better). It's open source because the
useful version of that itch is the one other people can build on too.
| Library | Install pain | Streaming | Multi-language | Active |
|------------------------|-----------------|-----------|----------------|--------|
| **★&nbsp;Wickra** | **clean** | **yes** | **Python + Node + WASM + Rust** | **yes** |
| TA-Lib (Python) | yes (C deps) | no | no | barely |
| pandas-ta | clean | no | no | slow |
| finta | clean | no | no | stale |
| ta-lib-python | yes (C deps) | no | no | barely |
| talipp | clean | yes | no | yes |
| Tulip Indicators | yes (C deps) | no | partial | stale |
| ooples (C#) | clean | no | C# only | yes |
## Benchmarks
Wickra is the only library that combines all of: clean install, streaming,
multi-language reach, and active maintenance.
Wickra updates every indicator in **O(1)** per tick. In **streaming** — the
workload it is built for — it is **1156× faster** than the only other incremental
peer and **thousands of times** faster than recompute-on-every-tick libraries.
**Batch** is competitive: it wins several rows outright and trades a few µs
elsewhere for `None`-warmup, NaN-safety and bit-exact `batch == streaming`.
## Benchmark: how much faster is "streaming-first"?
The numbers below were measured on a single developer workstation and are not
guaranteed to reproduce identically on different hardware — absolute µs values
depend on CPU, memory clock and OS scheduler. Read them as **relative
speedups** between libraries on identical input, not as a universal
performance contract.
- **Reproduced on:** Windows 11 Pro 26200, AMD Ryzen 9 9950X, 64 GB DDR5,
Rust 1.92 (release profile, `lto = "fat"`, `codegen-units = 1`),
Python 3.12, Node 20.
- **Reproduce yourself:** `pip install -e bindings/python[bench]` then
`python -m benchmarks.compare_libraries`. The script auto-detects every
installed peer library and runs them on the same generated inputs as
Wickra. The CI job `cross-library-bench` runs the same script on every
push and uploads the raw report as a build artefact.
Lower µs/op = faster. Wickra wins every batch category outright, and the
streaming gap widens linearly with how much history a batch-only library has
to recompute on every tick.
### Batch — single full pass over a 20 000-bar series
Reading the table: each cell shows that library's runtime, plus how many times
slower it is than Wickra in parentheses. **★** marks the winner per row.
| Indicator | **★&nbsp;Wickra** | finta | talipp |
|---------------------|---------------------|-----------------------------|-------------------------------|
| SMA(20) | **95.6 µs ★** | 343.5 µs (3.6× slower) | 7 640.6 µs (79.9× slower) |
| EMA(20) | **64.6 µs ★** | 223.1 µs (3.5× slower) | 12 160.9 µs (188.2× slower) |
| RSI(14) | **126.2 µs ★** | 1 107.1 µs (8.8× slower) | 15 792.2 µs (125.1× slower) |
| MACD(12, 26, 9) | **119.0 µs ★** | 531.8 µs (4.5× slower) | 49 788.1 µs (418.2× slower) |
| Bollinger(20, 2.0) | **105.3 µs ★** | 812.0 µs (7.7× slower) | 130 938.3 µs (1 243.7× slower)|
| ATR(14) | **123.5 µs ★** | 5 144.8 µs (41.7× slower) | 28 816.0 µs (233.4× slower) |
### Streaming — per-tick latency after seeding with 5 000 historical bars
A batch-only library has to re-run its full indicator over the entire history on
every new tick; Wickra updates state in O(1).
| Indicator | **★&nbsp;Wickra (per tick)** | talipp (per tick) |
|-----------|---------------------|---------------------------|
| RSI(14) | **0.119 µs ★** | 1.644 µs (13.8× slower) |
> TA-Lib and pandas-ta are not included here because both fail to install
> cleanly on Windows without C build tooling — which is precisely the install
> pain Wickra was built to remove. The benchmark script auto-detects every
> peer library it can find and runs them on the same inputs as Wickra; install
> them in your environment to see those rows light up too.
Run the suite yourself:
```bash
pip install -e bindings/python[bench]
python -m benchmarks.compare_libraries
```
Full tables (Rust + Python, streaming + batch) and how to reproduce them live in
**[BENCHMARKS.md](BENCHMARKS.md)**.
## Indicators
423 streaming-first indicators across twenty-four families. Every one passes the
514 streaming-first indicators across twenty-four families. Every one passes the
`batch == streaming` equivalence test, reference-value tests, and reset
semantics tests. Each has a per-indicator deep dive (formula, parameters,
warmup) at [docs.wickra.org](https://docs.wickra.org/Indicators-Overview).
@@ -147,23 +147,23 @@ warmup) at [docs.wickra.org](https://docs.wickra.org/Indicators-Overview).
| Momentum Oscillators | RSI (Wilder), Anchored RSI, Stochastic, CCI, ROC, Williams %R, MFI, Awesome Oscillator, MOM, CMO, TSI, PMO, StochRSI, Ultimate Oscillator, RVI, PGO, KST, SMI, Laguerre RSI, Connors RSI, Inertia, ROC Percentage (ROCP), ROC Ratio (ROCR), ROC Ratio 100 (ROCR100), Disparity Index, Fisher RSI, RSX, Dynamic Momentum Index, Stochastic CCI, RMI, Derivative Oscillator, Elder Ray, Intraday Momentum Index, QQE |
| Trend & Directional | MACD, MACD Fixed (MACDFIX), MACD Extended (MACDEXT), ADX (+DI/-DI), ADXR, Aroon, TRIX, Aroon Oscillator, Vortex, Random Walk Index, Trend Intensity Index, Wave Trend Oscillator, Mass Index, Choppiness Index, Vertical Horizontal Filter, Plus DM, Minus DM, Plus DI, Minus DI, DX, TTM Trend, Trend Strength Index, Qstick, Polarized Fractal Efficiency, Wave PM, Gator Oscillator, Kase Permission Stochastic |
| Price Oscillators | PPO, DPO, Coppock, Accelerator Oscillator, Balance of Power, APO, AO Histogram, CFO, Zero-Lag MACD, Elder Impulse, STC, TSF Oscillator, MACD Histogram, PPO Histogram |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| Ehlers / Cycle (DSP) | MAMA, FAMA, Fisher Transform, Inverse Fisher Transform, SuperSmoother, Hilbert Dominant Cycle, Hilbert Phasor, Hilbert DC Phase, Hilbert Trend Mode, Sine Wave, Decycler, Decycler Oscillator, Roofing Filter, Center of Gravity, Cybernetic Cycle, Adaptive Cycle, Empirical Mode Decomposition, Ehlers Stochastic, Instantaneous Trendline |
| Pivots & S/R | Classic Pivots, Fibonacci Pivots, Camarilla, Woodie Pivots, DeMark Pivots, Williams Fractals, ZigZag |
| DeMark | TD Setup, TD Sequential, TD DeMarker, TD REI, TD Pressure, TD Combo, TD Countdown, TD Lines, TD Range Projection, TD Differential, TD Open, TD Risk Level |
| Ichimoku & Charts | Ichimoku Kinko Hyo (Tenkan, Kijun, Senkou A/B, Chikou), Heikin-Ashi |
| Alt-Chart Bars | Renko (box-size bricks), Kagi (reversal-amount lines), Point & Figure (X/O columns) |
| Candlestick Patterns | Doji, Hammer, Inverted Hammer, Hanging Man, Shooting Star, Engulfing, Harami, Morning/Evening Star, Three White Soldiers/Black Crows, Piercing Line/Dark Cloud Cover, Marubozu, Tweezer, Spinning Top, Three Inside Up/Down, Three Outside Up/Down, Two Crows, Upside Gap Two Crows, Identical Three Crows, Three Line Strike, Three Stars in the South, Abandoned Baby, Advance Block, Belt-hold, Breakaway, Counterattack, Doji Star, Dragonfly Doji, Gravestone Doji, Long-Legged Doji, Rickshaw Man, Evening Doji Star, Morning Doji Star, Gap Side-by-Side White, High-Wave, Hikkake, Modified Hikkake, Homing Pigeon, On-Neck, In-Neck, Thrusting, Separating Lines, Kicking, Kicking by Length, Ladder Bottom, Mat Hold, Matching Low, Long Line, Short Line, Rising Three Methods, Falling Three Methods, Upside Gap Three Methods, Downside Gap Three Methods, Stalled Pattern, Stick Sandwich, Takuri, Closing Marubozu, Opening Marubozu, Tasuki Gap, Unique Three River, Concealing Baby Swallow |
| 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 |
| 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 |
| DeMark | TD Setup, TD Sequential, TD DeMarker, TD REI, TD Pressure, TD Combo, TD Countdown, TD Lines, TD Range Projection, TD Differential, TD Open, TD Risk Level, TD Camouflage, TD Clop, TD Clopwin, TD Propulsion, TD Trap, TD D-Wave, TD Moving Averages |
| Ichimoku & Charts | Ichimoku Kinko Hyo (Tenkan, Kijun, Senkou A/B, Chikou), Heikin-Ashi, Heikin-Ashi Oscillator, Three Line Break, Smoothed Heikin-Ashi, Equivolume, CandleVolume |
| Alt-Chart Bars | Renko (box-size bricks), Kagi (reversal-amount lines), Point & Figure (X/O columns), Range, Tick, Volume, Dollar, Imbalance, Run, Three-Line Break |
| Candlestick Patterns | Doji, Hammer, Inverted Hammer, Hanging Man, Shooting Star, Engulfing, Harami, Morning/Evening Star, Three White Soldiers/Black Crows, Piercing Line/Dark Cloud Cover, Marubozu, Tweezer, Spinning Top, Three Inside Up/Down, Three Outside Up/Down, Two Crows, Upside Gap Two Crows, Identical Three Crows, Three Line Strike, Three Stars in the South, Abandoned Baby, Advance Block, Belt-hold, Breakaway, Counterattack, Doji Star, Dragonfly Doji, Gravestone Doji, Long-Legged Doji, Rickshaw Man, Evening Doji Star, Morning Doji Star, Gap Side-by-Side White, High-Wave, Hikkake, Modified Hikkake, Homing Pigeon, On-Neck, In-Neck, Thrusting, Separating Lines, Kicking, Kicking by Length, Ladder Bottom, Mat Hold, Matching Low, Long Line, Short Line, Rising Three Methods, Falling Three Methods, Upside Gap Three Methods, Downside Gap Three Methods, Stalled Pattern, Stick Sandwich, Takuri, Closing Marubozu, Opening Marubozu, Tasuki Gap, Unique Three River, Concealing Baby Swallow, Tristar, Harami Cross, Tower Top/Bottom, Dumpling Top, New Price Lines, Frying Pan Bottom |
| Chart Patterns | Double Top / Bottom, Triple Top / Bottom, Head and Shoulders, Triangle (asc/desc/sym), Wedge (rising/falling), Flag / Pennant, Rectangle / Range, Cup and Handle |
| Harmonic Patterns | AB=CD, Gartley, Butterfly, Bat, Crab, Shark, Cypher, Three Drives |
| Fibonacci | Fibonacci Retracement, Fibonacci Extension, Fibonacci Projection, Auto-Fibonacci, Golden Pocket, Fibonacci Confluence, Fibonacci Fan, Fibonacci Arcs, Fibonacci Channel, Fibonacci Time Zones |
| Microstructure | Order-Book Imbalance (Top-1 / Top-N / Full), Microprice, Quoted Spread, Depth Slope, Signed Volume, Cumulative Volume Delta, Trade Imbalance, Effective Spread, Realized Spread, Kyle's Lambda, Footprint, Order Flow Imbalance, VPIN, Amihud Illiquidity, Roll Measure |
| Derivatives | Funding Rate, Funding Rate Mean, Funding Rate Z-Score, Funding Basis, Open-Interest Delta, OI / Price Divergence, OI-Weighted Price, Long/Short Ratio, Taker Buy/Sell Ratio, Liquidation Features, Term-Structure Basis, Calendar Spread |
| Market Profile | Value Area (POC / VAH / VAL), Volume Profile (histogram), TPO Profile, Initial Balance, Opening Range |
| Microstructure | Order-Book Imbalance (Top-1 / Top-N / Full), Microprice, Quoted Spread, Depth Slope, Signed Volume, Cumulative Volume Delta, Trade Imbalance, Effective Spread, Realized Spread, Kyle's Lambda, Footprint, Order Flow Imbalance, VPIN, Amihud Illiquidity, Roll Measure, Trade-Sign Autocorrelation, Hasbrouck Information Share |
| Derivatives | Funding Rate, Funding Rate Mean, Funding Rate Z-Score, Funding Basis, Open-Interest Delta, OI / Price Divergence, OI-Weighted Price, Long/Short Ratio, Taker Buy/Sell Ratio, Liquidation Features, Term-Structure Basis, Calendar Spread, Estimated Leverage Ratio, OI-to-Volume Ratio, Perpetual Premium Index, Funding-Implied APR, Open-Interest Momentum |
| Market Profile | Value Area (POC / VAH / VAL), Volume Profile (histogram), TPO Profile, Initial Balance, Opening Range, Naked POC, Single Prints, Profile Shape, High/Low Volume Nodes, Composite Profile |
| Market Breadth | Advance/Decline Line, Advance/Decline Ratio, Advance/Decline Volume Line, McClellan Oscillator, McClellan Summation Index, TRIN / Arms Index, Breadth Thrust, New Highs - New Lows, High-Low Index, Percent Above Moving Average, Up/Down Volume Ratio, Bullish Percent Index, Cumulative Volume Index, Absolute Breadth Index, TICK Index |
| Risk / Performance | Sharpe Ratio, Sortino Ratio, Calmar Ratio, Omega Ratio, Max Drawdown, Average Drawdown, Drawdown Duration, Pain Index, Value at Risk, Conditional Value at Risk (CVaR), Profit Factor, Gain/Loss Ratio, Recovery Factor, Kelly Criterion, Treynor Ratio, Information Ratio, Alpha (Jensen) |
| Seasonality & Session | Session VWAP, Session High/Low, Session Range, Average Daily Range, Overnight Gap, Overnight/Intraday Return, Turn-of-Month, Seasonal Z-Score, Time-of-Day Return Profile, Day-of-Week Profile, Intraday Volatility Profile, Volume-by-Time Profile |
@@ -174,8 +174,9 @@ as one column each. `Doji` is direction-less by default (`+1.0` / `0.0`);
construct it in signed mode (`Doji::new().signed()`, `Doji(signed=True)`,
`new Doji(true)`) for a dragonfly / gravestone `±1` reading.
Adding a new indicator means implementing one trait in Rust; all four bindings
inherit it automatically.
Adding a new indicator means implementing one trait in Rust; every binding
inherits it automatically (the C ABI — and the C#, Go and R bindings generated from
it — regenerate from the core).
## Languages
@@ -185,12 +186,17 @@ inherit it automatically.
| Node.js (napi-rs) | `npm install wickra` | `examples/node/backtest.js` |
| Browser / WASM | `npm install wickra-wasm` | `examples/wasm/index.html` |
| Rust | `cargo add wickra` | `examples/rust/src/bin/backtest.rs` |
| C / C++ (C ABI) | header + library, see [`bindings/c`](bindings/c) | `examples/c/streaming.c` |
| C# / .NET (C ABI) | `dotnet add package Wickra`, see [`bindings/csharp`](bindings/csharp) | `examples/csharp/streaming` |
| Go (cgo, C ABI) | `go get github.com/wickra-lib/wickra/bindings/go`, see [`bindings/go`](bindings/go) | `examples/go/streaming` |
| R (`.Call`, C ABI) | `R CMD INSTALL bindings/r`, see [`bindings/r`](bindings/r) | `examples/r/streaming.R` |
Each binding ships several runnable examples (streaming, backtest, live feed);
[`examples/README.md`](examples/README.md) is the full cross-language index.
The wickra-core crate is `unsafe`-forbidden, so every binding inherits a
memory-safe implementation.
The wickra-core crate is `unsafe`-forbidden, so the native bindings are
memory-safe end to end. The C ABI runs the same safe core; only its thin FFI
boundary uses `unsafe`, and the caller owns handle lifetimes (`_new` / `_free`).
## Rust API
@@ -245,25 +251,35 @@ A Python live-trading example using the public `websockets` package lives at
```
wickra/
├── crates/
│ ├── wickra-core/ core engine + all 423 indicators
│ ├── wickra-core/ core engine + all 514 indicators
│ ├── wickra/ top-level facade crate (publishes on crates.io) + benches/
── wickra-data/ CSV reader, tick aggregator, live exchange feeds
── wickra-data/ CSV reader, tick aggregator, live exchange feeds
│ └── wickra-bench/ internal cross-library benchmark harness (not published)
├── bindings/
│ ├── python/ PyO3 + maturin (publishes on PyPI)
│ ├── node/ napi-rs (publishes on npm)
── wasm/ wasm-bindgen (browsers, bundlers, Node)
── wasm/ wasm-bindgen (browsers, bundlers, Node)
│ ├── c/ C ABI (cdylib + staticlib) + generated include/wickra.h
│ ├── csharp/ .NET binding over the C ABI (publishes on NuGet)
│ ├── go/ Go binding over the C ABI via cgo (module tag)
│ └── r/ R binding over the C ABI via .Call (R package)
├── examples/ examples/README.md indexes every language
│ ├── data/ real BTCUSDT OHLCV datasets, one per timeframe
│ ├── rust/ Rust workspace member (`wickra-examples`)
│ ├── python/ backtest, live trading, parallel assets, multi-tf
│ ├── node/ streaming, backtest, live trading (load `wickra`)
── wasm/ browser demo for `wickra-wasm`
── wasm/ browser demo for `wickra-wasm`
│ ├── c/ C smoke + streaming, C++ RAII wrapper
│ ├── csharp/ streaming, backtest, strategies (load `Wickra`)
│ ├── go/ streaming, backtest, strategies (cgo binding)
│ └── r/ streaming, backtest, strategies (.Call binding)
└── .github/workflows/ CI and release pipelines
```
Rust benchmarks live in `crates/wickra/benches/`; runnable Rust examples live
in the workspace member crate at `examples/rust/`. There is no top-level
`benches/` directory.
Wickra's own regression benchmarks live in `crates/wickra/benches/`; the
cross-library comparison against kand, ta-rs and yata lives in the internal
`crates/wickra-bench/` crate. Runnable Rust examples live in the workspace member
crate at `examples/rust/`. There is no top-level `benches/` directory.
## Building everything from source
@@ -271,7 +287,8 @@ in the workspace member crate at `examples/rust/`. There is no top-level
# Rust core + tests
cargo test --workspace
cargo clippy --workspace --all-targets -- -D warnings
cargo bench -p wickra
cargo bench -p wickra # Wickra's own regression benchmarks
cargo bench -p wickra-bench # cross-library comparison (kand, ta-rs, yata)
# Python binding (requires Rust toolchain + maturin)
cd bindings/python
@@ -283,6 +300,22 @@ wasm-pack build bindings/wasm --target web --release --features panic-hook
# Node binding (requires @napi-rs/cli)
cd bindings/node && npm install && npm run build && npm test
# C ABI (cdylib + staticlib + generated header)
cargo build -p wickra-c --release
cmake -S examples/c -B examples/c/build -DWICKRA_LIB_DIR="$PWD/target/release"
cmake --build examples/c/build && ctest --test-dir examples/c/build --output-on-failure
# C# / .NET binding (requires the .NET 8 SDK; links the C ABI above)
dotnet test bindings/csharp/Wickra.Tests/Wickra.Tests.csproj
# Go binding (requires a C compiler for cgo; links the C ABI above)
cp target/release/libwickra.so bindings/go/lib/ # .dylib on macOS, wickra.dll on Windows
cd bindings/go && go test ./...
# R binding (requires a C toolchain / Rtools; links the C ABI above)
WICKRA_INCLUDE_DIR="$PWD/bindings/c/include" WICKRA_LIB_DIR="$PWD/target/release" \
R CMD INSTALL bindings/r
```
## Testing
@@ -302,6 +335,10 @@ 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/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.
## Contributing
@@ -371,3 +408,10 @@ The library is provided **as is**, without warranty of any kind; see
<p align="center">
If Wickra saved you time, the cheapest way to say thanks is to ⭐ the repo.
</p>
<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">
</a>
</p>
+3 -2
View File
@@ -21,8 +21,9 @@ minor releases; breaking changes are called out in the changelog.
versioning stability for a 1.0 release.
- **Performance.** Keep per-tick updates O(1) and maintain the benchmark suite;
investigate further allocation and cache improvements.
- **Bindings parity.** Keep the Python, Node.js and WebAssembly bindings in
lockstep with the Rust core, including type stubs and platform coverage.
- **Bindings parity.** Keep the Python, Node.js and WebAssembly bindings — plus
the C ABI and the C# / .NET, Go and R bindings generated from it — in lockstep with the
Rust core, including type stubs and platform coverage.
- **Documentation.** Maintain a deep-dive page per indicator on
<https://docs.wickra.org>, plus quickstarts and cookbook material.
- **Project health.** Maintain test coverage, static and dynamic analysis,
+5 -1
View File
@@ -58,7 +58,11 @@ artifacts, and (4) a healthy dependency supply chain.
- *Memory safety* — the core and all bindings are written in Rust. The crates
forbid or minimise `unsafe`, so the compiler guarantees memory and thread
safety for the indicator logic.
safety for the indicator logic. The one exception is the C ABI
([`bindings/c`](bindings/c)), whose thin FFI shim is necessarily `unsafe`
because it dereferences caller-supplied pointers; it adds no indicator logic,
validates every handle for NULL, and never lets a panic cross the boundary, so
the safe core's guarantees still cover all computation.
- *Input robustness* — every indicator validates its parameters and rejects
non-finite inputs at construction; behaviour on edge cases (flat markets,
warmup, reset) is pinned by unit tests, and the public update paths are
+2 -2
View File
@@ -7,8 +7,8 @@ Thanks for using Wickra! Here is where to get help, depending on what you need.
Most questions are answered in the documentation:
- **Docs site:** <https://docs.wickra.org> — quickstarts for Rust, Python,
Node.js and WebAssembly, a per-indicator reference, warmup periods, the data
layer, and an FAQ.
Node.js, WebAssembly, C, C#, Go and R, a per-indicator reference, warmup periods, the
data layer, and an FAQ.
- **README:** <https://github.com/wickra-lib/wickra#readme> — installation and a
quick overview.
- **API docs (Rust):** <https://docs.rs/wickra>.
+5 -2
View File
@@ -3,8 +3,10 @@
This document describes Wickra's attack surface and the threats considered,
together with their mitigations. It complements the security assurance case in
[`SECURITY.md`](SECURITY.md). Wickra is a computational technical-analysis
library (a Rust core with Python, Node.js and WebAssembly bindings), not a
network service or trading system; the attack surface is correspondingly small.
library (a Rust core with Python, Node.js and WebAssembly bindings plus a C ABI
and the .NET, Go and R bindings built on it),
not a network service or trading system; the attack surface is correspondingly
small.
## Assets
@@ -31,6 +33,7 @@ network service or trading system; the attack surface is correspondingly small.
| Threat | Mitigation |
| --- | --- |
| Memory-safety exploit (buffer overflow, UAF) via crafted input | Pure safe Rust; `unsafe` is forbidden/minimised, so the compiler precludes these classes. |
| Misuse of the C ABI FFI boundary (invalid/dangling handle, undersized batch buffer) | The C ABI (`bindings/c`) is the sole `unsafe` surface. Its shim adds no logic, NULL-checks every handle (returning `NaN`/no-op), writes only into caller-sized buffers, and 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. |
| 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. |
+46
View File
@@ -0,0 +1,46 @@
[package]
name = "wickra-c"
description = "C ABI (cdylib + staticlib) for the Wickra streaming-first technical indicators library — the hub every C-capable language (C, C++, Go, C#, Java, R) links against."
version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
readme = "README.md"
keywords.workspace = true
categories.workspace = true
publish = false
[lib]
name = "wickra"
crate-type = ["cdylib", "staticlib"]
# The C ABI inherently needs `unsafe` (raw pointers across the FFI boundary,
# `#[export_name]` symbol control). The workspace forbids `unsafe_code`, so this
# crate cannot inherit `workspace = true`; it mirrors every workspace lint and
# only relaxes `unsafe_code` to `allow` (parity with how the proc-macro bindings
# emit their unsafe). The Rust core stays `unsafe`-forbidden — this is the one
# crate where the boundary lives.
[lints.rust]
unsafe_code = "allow"
missing_debug_implementations = "warn"
unreachable_pub = "warn"
unused_must_use = "deny"
[lints.clippy]
all = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -1 }
module_name_repetitions = "allow"
must_use_candidate = "allow"
missing_errors_doc = "allow"
missing_panics_doc = "allow"
cast_precision_loss = "allow"
cast_possible_truncation = "allow"
cast_sign_loss = "allow"
similar_names = "allow"
float_cmp = "allow"
[dependencies]
wickra-core = { workspace = true }
+80
View File
@@ -0,0 +1,80 @@
# Wickra — C / C++
[![CI](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml/badge.svg)](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/wickra-lib/wickra/branch/main/graph/badge.svg)](https://codecov.io/gh/wickra-lib/wickra)
[![GitHub release](https://img.shields.io/github/v/release/wickra-lib/wickra?logo=github&color=green)](https://github.com/wickra-lib/wickra/releases/latest)
[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT_OR_Apache--2.0-blue)](https://github.com/wickra-lib/wickra#license)
**Streaming-first technical indicators for C and C++. A prebuilt shared/static
library plus a generated `wickra.h` — no system dependencies.**
Wickra is a multi-language technical-analysis library with a Rust core and
bindings for Python, Node.js and WebAssembly, plus a C ABI for C/C++, C#, Go, R and any
other C-capable language. Every indicator is an O(1)
streaming state machine, so live trading bots and historical backtests share
the exact same implementation. This package is the **C ABI hub**: it compiles the
core to a C-compatible shared/static library plus a generated header, so any
C-capable language (C, C++, Go, C#, Java, R) links against one artifact instead
of re-wrapping every indicator natively.
## Install
Grab the prebuilt header + library for your platform from the
[GitHub releases](https://github.com/wickra-lib/wickra/releases) — each archive
has `wickra.h`, the optional `wickra.hpp` C++ wrapper, and the shared/static
library — or build from source:
```bash
cargo build -p wickra-c --release
# -> target/release/libwickra.{so,dylib} or wickra.dll (+ import lib) + a staticlib
```
Then compile against the header and link the library
(`cc app.c -I include -L lib -lwickra -lm -o app`).
## Quick start
```c
#include "wickra.h"
struct Rsi *rsi = wickra_rsi_new(14); /* NULL on invalid params */
for (size_t i = 0; i < n; ++i) {
double v = wickra_rsi_update(rsi, prices[i]); /* NaN during warmup */
if (v == v && v > 70.0) /* v == v is the NaN check */
printf("overbought\n");
}
wickra_rsi_free(rsi); /* exactly once per _new */
```
Every indicator is an opaque handle with the same five functions —
`_new` / `_update` / `_batch` / `_reset` / `_free`. `update` is O(1); there is no
RAII across the C boundary, so each `_new` needs exactly one `_free`, and every
function is NULL-safe (a NULL handle yields `NaN` or a no-op, never a crash).
Multi-output indicators (MACD, Bollinger, ADX, …) take a pointer to a `#[repr(C)]`
struct and return a `bool`. The optional `wickra.hpp` wraps any handle in a
move-only `wickra::Handle` for exception-safe C++ lifetimes.
## Documentation
The full indicator catalogue, guides, quickstarts, and API reference live in
the main repository and documentation site:
- **Repository & full indicator list:** <https://github.com/wickra-lib/wickra>
- **Docs** (C quickstart, cookbook, TA-Lib migration): <https://docs.wickra.org/Quickstart-C>
- **Runnable examples:** [`examples/c/`](https://github.com/wickra-lib/wickra/tree/main/examples/c)
Wickra ships native bindings for Python, Node.js, WebAssembly and Rust, plus this
C ABI hub that any C-capable language (C, C++, Go, C#, Java, R) links against —
all exposing the same indicators from the shared Rust core.
## Disclaimer
Wickra is an indicator toolkit, not a trading system. The values it computes
are deterministic transforms of the input data — they are not financial advice
and do not predict the market. Any use in a live trading context is at your own
risk. The library is provided **as is**, without warranty of any kind.
## License
Licensed under either of [Apache-2.0](https://github.com/wickra-lib/wickra/blob/main/LICENSE-APACHE)
or [MIT](https://github.com/wickra-lib/wickra/blob/main/LICENSE-MIT) at your option.
+20
View File
@@ -0,0 +1,20 @@
language = "C"
header = "/* Wickra C ABI — generated by cbindgen. Do not edit by hand. */"
include_guard = "WICKRA_H"
pragma_once = true
# Wrap the declarations in `extern "C"` under __cplusplus so the header is usable
# from C++ (the optional wickra.hpp RAII layer and any C++ consumer).
cpp_compat = true
tab_width = 4
# Off: cbindgen copies the Rust struct/fn doc comments verbatim, and some core
# indicator docs contain markdown (e.g. `**1/8**/**7/8**`) whose `*/` would close
# the C block comment early and break the header. Usage docs live in the crate
# README and examples; the header is a pure declaration contract.
documentation = false
[parse]
# Parse wickra-core too so the opaque indicator handle types (Sma, Ema, …) are
# discovered and emitted as forward-declared opaque structs. Their fields are
# never exposed — only `T *` handles cross the boundary.
parse_deps = true
include = ["wickra-core"]
File diff suppressed because it is too large Load Diff
+66
View File
@@ -0,0 +1,66 @@
// Optional C++ convenience layer over the Wickra C ABI (`wickra.h`).
//
// The C ABI hands out raw handles that must be released exactly once with the
// matching `wickra_<ind>_free`. `wickra::Handle` wraps that in a move-only RAII
// owner so the free happens automatically at scope exit:
//
// #include "wickra.hpp"
//
// wickra::Handle<Sma, wickra_sma_free> sma(wickra_sma_new(14));
// if (sma) {
// double v = wickra_sma_update(sma.get(), 42.0); // NaN during warmup
// }
// // sma is freed here
//
// This is header-only and adds no runtime cost beyond the C calls themselves.
#ifndef WICKRA_HPP
#define WICKRA_HPP
#include "wickra.h"
#include <utility>
namespace wickra {
/// Move-only RAII owner of a Wickra handle. `T` is the opaque indicator type and
/// `Free` its `wickra_<ind>_free` function.
template <typename T, void (*Free)(T *)>
class Handle {
public:
explicit Handle(T *ptr) noexcept : ptr_(ptr) {}
~Handle() {
if (ptr_ != nullptr) {
Free(ptr_);
}
}
Handle(const Handle &) = delete;
Handle &operator=(const Handle &) = delete;
Handle(Handle &&other) noexcept : ptr_(std::exchange(other.ptr_, nullptr)) {}
Handle &operator=(Handle &&other) noexcept {
if (this != &other) {
if (ptr_ != nullptr) {
Free(ptr_);
}
ptr_ = std::exchange(other.ptr_, nullptr);
}
return *this;
}
/// The raw handle, for passing to the `wickra_<ind>_*` functions.
T *get() const noexcept { return ptr_; }
/// True if the handle is non-null (construction succeeded).
explicit operator bool() const noexcept { return ptr_ != nullptr; }
private:
T *ptr_;
};
} // namespace wickra
#endif // WICKRA_HPP
+44290
View File
File diff suppressed because it is too large Load Diff
+12
View File
@@ -0,0 +1,12 @@
# .NET build output
bin/
obj/
*.user
# NuGet packaging output
*.nupkg
*.snupkg
# Native libraries staged for packaging (produced by the release pipeline from
# the wickra-c-<triple>.tar.gz assets; never committed to source).
Wickra/runtimes/
+78
View File
@@ -0,0 +1,78 @@
# Wickra — .NET
[![CI](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml/badge.svg)](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/wickra-lib/wickra/branch/main/graph/badge.svg)](https://codecov.io/gh/wickra-lib/wickra)
[![NuGet](https://img.shields.io/nuget/v/Wickra.svg?logo=nuget&color=blue)](https://www.nuget.org/packages/Wickra)
[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT_OR_Apache--2.0-blue)](https://github.com/wickra-lib/wickra#license)
**Streaming-first technical indicators for .NET. `dotnet add package Wickra`
prebuilt native library, no system dependencies.**
Wickra is a multi-language technical-analysis library with a Rust core and
bindings for Python, Node.js and WebAssembly, plus a C ABI for C/C++, C#, Go, R and any
other C-capable language. Every indicator is an O(1)
streaming state machine, so live trading bots and historical backtests share
the exact same implementation. This package is the .NET binding; it consumes the
C ABI hub through `[LibraryImport]` P/Invoke and exposes all 514 streaming-first
indicators as idiomatic `IDisposable` classes.
## Install
```bash
dotnet add package Wickra
```
The native library ships prebuilt per platform (Linux, macOS, Windows — x64 and
arm64) under `runtimes/<rid>/native/`, selected automatically. There is nothing
to compile. Targets .NET 8 and later.
## Quick start
```csharp
using Wickra;
// Batch: run an indicator over a whole series (NaN at warmup positions).
var prices = Enumerable.Range(0, 1000).Select(i => 100.0 + i * 0.1).ToArray();
using var sma = new Sma(20);
double[] values = sma.Batch(prices);
// Streaming: the same indicator, fed tick by tick in O(1).
using var rsi = new Rsi(14);
foreach (var price in liveFeed)
{
var value = rsi.Update(price); // NaN during warmup, no recomputation
if (double.IsFinite(value) && value > 70)
{
Console.WriteLine("overbought");
}
}
```
`Batch(prices)` and feeding the same prices through `Update()` produce identical
values — the equivalence is enforced by the test suite. Multi-output indicators
(MACD, Bollinger, ADX, …) return a nullable `record struct`, `null` while warming up.
## Documentation
The full indicator catalogue, guides, quickstarts, and API reference live in
the main repository and documentation site:
- **Repository & full indicator list:** <https://github.com/wickra-lib/wickra>
- **Docs** (quickstarts, cookbook, TA-Lib migration): <https://docs.wickra.org>
- **Runnable examples:** [`examples/csharp/`](https://github.com/wickra-lib/wickra/tree/main/examples/csharp)
Wickra ships native bindings for Python, Node.js, WebAssembly and Rust, plus a
C ABI hub that any C-capable language (C, C++, Go, C#, Java, R) links against —
all exposing the same indicators from the shared, `unsafe`-forbidden Rust core.
## Disclaimer
Wickra is an indicator toolkit, not a trading system. The values it computes
are deterministic transforms of the input data — they are not financial advice
and do not predict the market. Any use in a live trading context is at your own
risk. The library is provided **as is**, without warranty of any kind.
## License
Licensed under either of [Apache-2.0](https://github.com/wickra-lib/wickra/blob/main/LICENSE-APACHE)
or [MIT](https://github.com/wickra-lib/wickra/blob/main/LICENSE-MIT) at your option.
@@ -0,0 +1,144 @@
using Wickra;
using Xunit;
namespace Wickra.Tests;
/// <summary>
/// One representative per FFI archetype, exercising every marshalling path the
/// generator produces (scalar, candle, pairwise, multi-output, bars, profile,
/// values-profile, array-input). Garbage marshalling surfaces as NaN, wild
/// values, or crashes — so finite/sane assertions are the real check.
/// </summary>
public class ArchetypeTests
{
private static (double open, double high, double low, double close, double volume, long ts) Candle(int i)
{
var close = 100.0 + 10.0 * Math.Sin(i * 0.3);
var open = 100.0 + 10.0 * Math.Sin((i - 1) * 0.3);
var high = Math.Max(open, close) + 1.0;
var low = Math.Min(open, close) - 1.0;
return (open, high, low, close, 1_000.0, i * 60_000L);
}
[Fact]
public void Scalar_Ema_IsFiniteAfterWarmup()
{
using var ema = new Ema(3);
double last = double.NaN;
for (var i = 1; i <= 10; i++)
{
last = ema.Update(i);
}
Assert.True(double.IsFinite(last));
Assert.InRange(last, 1.0, 10.0);
}
[Fact]
public void Candle_Atr_IsFinitePositive()
{
using var atr = new Atr(3);
double last = double.NaN;
for (var i = 0; i < 20; i++)
{
var (o, h, l, c, v, ts) = Candle(i);
last = atr.Update(o, h, l, c, v, ts);
}
Assert.True(double.IsFinite(last));
Assert.True(last > 0.0);
}
[Fact]
public void Pairwise_Beta_IsFinite()
{
using var beta = new Beta(5);
double last = double.NaN;
for (var i = 0; i < 30; i++)
{
var market = 100.0 + 10.0 * Math.Sin(i * 0.5);
var asset = 50.0 + 6.0 * Math.Sin(i * 0.5 + 0.2);
last = beta.Update(market, asset);
}
Assert.True(double.IsFinite(last));
}
[Fact]
public void MultiOutput_Adx_ReturnsFiniteStruct()
{
using var adx = new Adx(5);
AdxOutput? result = null;
for (var i = 0; i < 60; i++)
{
var (o, h, l, c, v, ts) = Candle(i);
result = adx.Update(o, h, l, c, v, ts);
}
Assert.NotNull(result);
Assert.True(double.IsFinite(result!.Value.Adx));
Assert.True(double.IsFinite(result.Value.PlusDi));
Assert.True(double.IsFinite(result.Value.MinusDi));
}
[Fact]
public void Bars_DollarBars_EmitsBars()
{
using var bars = new DollarBars(5_000.0);
var total = 0;
for (var i = 0; i < 200; i++)
{
var (o, h, l, c, v, ts) = Candle(i);
total += bars.Update(o, h, l, c, v, ts).Length;
}
Assert.True(total > 0);
}
[Fact]
public void Profile_VolumeProfile_ReturnsValues()
{
using var profile = new VolumeProfile(20, 8);
VolumeProfileOutputScalars? result = null;
for (var i = 0; i < 60; i++)
{
var (o, h, l, c, v, ts) = Candle(i);
result = profile.Update(o, h, l, c, v, ts);
}
Assert.NotNull(result);
Assert.NotNull(result!.Value.Values);
Assert.True(result.Value.PriceLow <= result.Value.PriceHigh);
}
[Fact]
public void ProfileValues_DayOfWeekProfile_NoCrash()
{
using var profile = new DayOfWeekProfile(0);
double[]? result = null;
for (var i = 0; i < 60; i++)
{
var close = 100.0 + 5.0 * Math.Sin(i * 0.2);
// one day apart so the day-of-week buckets fill
result = profile.Update(close, close + 1, close - 1, close, 1_000.0, i * 86_400_000L);
}
if (result is not null)
{
Assert.All(result, v => Assert.True(double.IsFinite(v)));
}
}
[Fact]
public void ArrayInput_DepthSlope_IsFinite()
{
using var slope = new DepthSlope();
ReadOnlySpan<double> bidPrice = stackalloc double[] { 99.0, 98.0, 97.0 };
ReadOnlySpan<double> bidSize = stackalloc double[] { 10.0, 20.0, 30.0 };
ReadOnlySpan<double> askPrice = stackalloc double[] { 101.0, 102.0, 103.0 };
ReadOnlySpan<double> askSize = stackalloc double[] { 12.0, 22.0, 32.0 };
var result = slope.Update(bidPrice, bidSize, askPrice, askSize);
Assert.True(double.IsFinite(result));
}
}
+51
View File
@@ -0,0 +1,51 @@
using Wickra;
using Xunit;
namespace Wickra.Tests;
public class SmaTests
{
[Fact]
public void StreamingMatchesReference()
{
using var sma = new Sma(3);
Assert.True(double.IsNaN(sma.Update(1)));
Assert.True(double.IsNaN(sma.Update(2)));
Assert.Equal(2.0, sma.Update(3), 9);
Assert.Equal(3.0, sma.Update(4), 9);
Assert.Equal(4.0, sma.Update(5), 9);
}
[Fact]
public void BatchMatchesStreaming()
{
using var sma = new Sma(3);
var output = sma.Batch(new double[] { 1, 2, 3, 4, 5 });
Assert.True(double.IsNaN(output[0]));
Assert.True(double.IsNaN(output[1]));
Assert.Equal(2.0, output[2], 9);
Assert.Equal(3.0, output[3], 9);
Assert.Equal(4.0, output[4], 9);
}
[Fact]
public void ResetClearsState()
{
using var sma = new Sma(3);
sma.Update(1);
sma.Update(2);
sma.Update(3);
sma.Reset();
Assert.True(double.IsNaN(sma.Update(10)));
}
[Fact]
public void ZeroPeriodThrows()
{
// Zero is rejected by the native constructor (returns NULL) -> ArgumentException;
// a negative period is caught earlier by the wrapper guard.
Assert.Throws<ArgumentException>(() => new Sma(0));
Assert.Throws<ArgumentOutOfRangeException>(() => new Sma(-1));
}
}
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Wickra\Wickra.csproj" />
</ItemGroup>
</Project>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+57
View File
@@ -0,0 +1,57 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RootNamespace>Wickra</RootNamespace>
<AssemblyName>Wickra</AssemblyName>
<!-- NuGet package metadata -->
<PackageId>Wickra</PackageId>
<Version>0.7.8</Version>
<Authors>kingchenc</Authors>
<Description>High-performance streaming technical-analysis indicators (514 indicators) for .NET, backed by the native Rust core via the Wickra C ABI.</Description>
<PackageLicenseExpression>MIT OR Apache-2.0</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/wickra-lib/wickra</PackageProjectUrl>
<RepositoryUrl>https://github.com/wickra-lib/wickra</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>technical-analysis;indicators;trading;finance;streaming;ffi;native</PackageTags>
<PackageReadmeFile>README.md</PackageReadmeFile>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<!-- A managed package carrying per-RID native assets; not built per-RID itself. -->
<IncludeBuildOutput>true</IncludeBuildOutput>
<!-- NU5128: managed package carrying only per-RID native assets.
CS1591: generated members are self-descriptive; hand-written API is documented. -->
<NoWarn>$(NoWarn);NU5128;CS1591</NoWarn>
</PropertyGroup>
<!--
Supported native runtime identifiers. The release pipeline builds the C ABI
per target triple and stages the libraries under
Wickra/runtimes/<rid>/native/ before `dotnet pack`:
win-x64 win-arm64 linux-x64 linux-arm64 osx-x64 osx-arm64
-->
<PropertyGroup>
<WickraRuntimeIdentifiers>win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</WickraRuntimeIdentifiers>
</PropertyGroup>
<ItemGroup>
<None Include="..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>
<!--
Native libraries are packed under runtimes/<rid>/native/ by the release pipeline,
which unpacks the wickra-c-<triple>.tar.gz assets into the matching RID folders.
For local development and tests the natives are resolved from the cargo target dir
via WickraNative's DllImportResolver.
-->
<ItemGroup>
<None Include="runtimes/**/native/*" Pack="true" PackagePath="runtimes" Condition="Exists('runtimes')" />
</ItemGroup>
</Project>
+26
View File
@@ -0,0 +1,26 @@
using Microsoft.Win32.SafeHandles;
namespace Wickra;
/// <summary>
/// Owns an opaque native indicator handle and releases it via the indicator's
/// <c>_free</c> function. One generic handle type backs every indicator; the
/// correct free routine is captured at construction time.
/// </summary>
internal sealed class WickraHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private readonly Action<nint> _free;
internal WickraHandle(nint handle, Action<nint> free)
: base(ownsHandle: true)
{
_free = free;
SetHandle(handle);
}
protected override bool ReleaseHandle()
{
_free(handle);
return true;
}
}
+87
View File
@@ -0,0 +1,87 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Wickra;
/// <summary>
/// Native library resolution for the Wickra C ABI.
/// </summary>
/// <remarks>
/// When consumed as a NuGet package the native library ships under
/// <c>runtimes/&lt;rid&gt;/native/</c> and the default runtime resolver finds it
/// automatically. For local development (project reference against a cargo build)
/// the resolver additionally walks up the directory tree to locate
/// <c>target/release</c> or <c>target/debug</c>. Every candidate is validated to
/// actually export the Wickra ABI before it is accepted, so an unrelated library
/// of the same name cannot shadow the real one.
/// </remarks>
internal static class WickraNative
{
/// <summary>The library name passed to <c>[LibraryImport]</c>.</summary>
internal const string LibraryName = "wickra";
// Any exported symbol works as a fingerprint; sma_new exists in every build.
private const string SentinelSymbol = "wickra_sma_new";
[ModuleInitializer]
internal static void Register()
{
NativeLibrary.SetDllImportResolver(typeof(WickraNative).Assembly, Resolve);
}
private static nint Resolve(string libraryName, System.Reflection.Assembly assembly, DllImportSearchPath? searchPath)
{
if (libraryName != LibraryName)
{
return nint.Zero;
}
// 1. Default resolution (NuGet runtimes/ layout, app-local copies). Accept
// only if it is genuinely the Wickra ABI; otherwise discard and fall through.
if (NativeLibrary.TryLoad(libraryName, assembly, searchPath, out var handle))
{
if (Exports(handle))
{
return handle;
}
NativeLibrary.Free(handle);
}
// 2. Development fallback: locate the cargo build output.
var fileName = NativeFileName();
var dir = AppContext.BaseDirectory;
for (var i = 0; i < 16 && dir is not null; i++)
{
foreach (var profile in new[] { "release", "debug" })
{
var candidate = Path.Combine(dir, "target", profile, fileName);
if (File.Exists(candidate) && NativeLibrary.TryLoad(candidate, out var devHandle))
{
if (Exports(devHandle))
{
return devHandle;
}
NativeLibrary.Free(devHandle);
}
}
dir = Path.GetDirectoryName(dir.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
}
return nint.Zero;
}
private static bool Exports(nint handle) => NativeLibrary.TryGetExport(handle, SentinelSymbol, out _);
private static string NativeFileName()
{
if (OperatingSystem.IsWindows())
{
return "wickra.dll";
}
return OperatingSystem.IsMacOS() ? "libwickra.dylib" : "libwickra.so";
}
}
+100
View File
@@ -0,0 +1,100 @@
# Wickra — Go
[![CI](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml/badge.svg)](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/wickra-lib/wickra/branch/main/graph/badge.svg)](https://codecov.io/gh/wickra-lib/wickra)
[![Go 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)
[![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
any other C-capable language. Every indicator is an O(1) streaming state machine,
so live trading bots and historical backtests share the exact same
implementation. This package is the Go binding; it consumes the C ABI hub through
cgo and exposes all 514 streaming-first indicators as idiomatic types.
## Install
```bash
go get github.com/wickra-lib/wickra/bindings/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:
```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)
```
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
package main
import (
"fmt"
wickra "github.com/wickra-lib/wickra/bindings/go"
)
func main() {
// Batch: run an indicator over a whole series (NaN at warmup positions).
prices := make([]float64, 1000)
for i := range prices {
prices[i] = 100.0 + float64(i)*0.1
}
sma, _ := wickra.NewSma(20)
defer sma.Close()
values := sma.Batch(prices)
// Streaming: the same indicator, fed tick by tick in O(1).
rsi, _ := wickra.NewRsi(14)
defer rsi.Close()
for _, price := range prices {
value := rsi.Update(price) // NaN during warmup, no recomputation
if value > 70 {
fmt.Println("overbought")
}
}
_ = values
}
```
`Batch(prices)` and feeding the same prices through `Update()` produce identical
values — the equivalence is enforced by the test suite. Multi-output indicators
(MACD, Bollinger, ADX, …) return `(Output, bool)`, with `false` while warming up.
Every indicator owns a native handle freed by `Close()`; a finalizer is wired as
a backstop, but call `Close()` (e.g. with `defer`) to release memory promptly.
## Documentation
The full indicator catalogue, guides, quickstarts, and API reference live in the
main repository and documentation site:
- **Repository & full indicator list:** <https://github.com/wickra-lib/wickra>
- **Docs** (quickstarts, cookbook, TA-Lib migration): <https://docs.wickra.org>
- **Runnable examples:** [`examples/go/`](https://github.com/wickra-lib/wickra/tree/main/examples/go)
Wickra ships native bindings for Python, Node.js, WebAssembly and Rust, plus a
C ABI hub that any C-capable language (C, C++, C#, Go, Java, R) links against —
all exposing the same indicators from the shared, `unsafe`-forbidden Rust core.
## Disclaimer
Wickra is an indicator toolkit, not a trading system. The values it computes are
deterministic transforms of the input data — they are not financial advice and
do not predict the market. Any use in a live trading context is at your own risk.
The library is provided **as is**, without warranty of any kind.
## License
Licensed under either of [Apache-2.0](https://github.com/wickra-lib/wickra/blob/main/LICENSE-APACHE)
or [MIT](https://github.com/wickra-lib/wickra/blob/main/LICENSE-MIT) at your option.
+3
View File
@@ -0,0 +1,3 @@
module github.com/wickra-lib/wickra/bindings/go
go 1.23
File diff suppressed because it is too large Load Diff
+7
View File
@@ -0,0 +1,7 @@
# Prebuilt Wickra C ABI libraries are provisioned locally / in CI, not committed.
*.so
*.dylib
*.dll
*.a
*.lib
*.exp
+25
View File
@@ -0,0 +1,25 @@
// Package wickra provides idiomatic Go bindings for the Wickra
// technical-analysis library over its C ABI hub.
//
// Each indicator is an opaque-handle type with a New<Indicator> constructor and
// Update/Batch/Reset/Close methods. Handles are freed by Close and, as a
// backstop, by a finalizer; call Close explicitly to release native memory
// promptly. The binding links against the prebuilt Wickra C ABI library
// (libwickra.so/.dylib or wickra.dll) staged under ./lib — see the package
// README for how to provision it.
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
#include "wickra.h"
*/
import "C"
import "errors"
// ErrInvalidParams is returned by a New<Indicator> constructor when the native
// constructor rejects the supplied parameters (for example a zero period).
var ErrInvalidParams = errors.New("wickra: invalid indicator parameters")
+151
View File
@@ -0,0 +1,151 @@
package wickra
import (
"math"
"testing"
)
// One indicator per FFI archetype, exercising the full New/Update/Batch/Reset/
// Close surface against the real native library.
func TestScalarKnownValue(t *testing.T) {
s, err := NewSma(3)
if err != nil {
t.Fatalf("NewSma: %v", err)
}
defer s.Close()
var last float64
for _, v := range []float64{1, 2, 3, 4, 5} {
last = s.Update(v)
}
if math.Abs(last-4.0) > 1e-9 {
t.Fatalf("sma(3) last = %v, want 4.0", last)
}
}
func TestScalarBatchMatchesStreaming(t *testing.T) {
input := []float64{1, 2, 3, 4, 5, 6, 7, 8}
stream, _ := NewSma(3)
defer stream.Close()
want := make([]float64, len(input))
for i, v := range input {
want[i] = stream.Update(v)
}
batchInd, _ := NewSma(3)
defer batchInd.Close()
got := batchInd.Batch(input)
if len(got) != len(want) {
t.Fatalf("batch len = %d, want %d", len(got), len(want))
}
for i := range want {
if math.IsNaN(want[i]) && math.IsNaN(got[i]) {
continue
}
if math.Abs(got[i]-want[i]) > 1e-9 {
t.Fatalf("batch[%d] = %v, streaming = %v", i, got[i], want[i])
}
}
}
func TestMultiOutput(t *testing.T) {
m, err := NewMacdIndicator(3, 6, 3)
if err != nil {
t.Fatalf("NewMacdIndicator: %v", err)
}
defer m.Close()
var ok bool
var out MacdOutput
for i := 0; i < 30; i++ {
out, ok = m.Update(100 + float64(i))
}
if !ok {
t.Fatal("macd never produced a value after warmup")
}
if math.IsNaN(out.Macd) {
t.Fatal("macd value is NaN after warmup")
}
}
func TestBars(t *testing.T) {
rb, err := NewRangeBars(2.0)
if err != nil {
t.Fatalf("NewRangeBars: %v", err)
}
defer rb.Close()
total := 0
for _, p := range []float64{100, 101, 103, 104, 99, 96, 102, 108, 95, 110} {
bars := rb.Update(p, p, p, p, 1, 0)
total += len(bars)
}
if total == 0 {
t.Fatal("range bars produced no bars over a 15-point move")
}
}
func TestProfile(t *testing.T) {
vp, err := NewVolumeProfile(10, 24)
if err != nil {
t.Fatalf("NewVolumeProfile: %v", err)
}
defer vp.Close()
var ok bool
var snap VolumeProfileOutputScalars
for i := 0; i < 50; i++ {
price := 100 + 5*math.Sin(float64(i)*0.3)
snap, ok = vp.Update(price, price+1, price-1, price, 1000, int64(i))
}
if !ok {
t.Fatal("volume profile never produced a snapshot")
}
if len(snap.Values) == 0 {
t.Fatal("volume profile returned an empty values buffer")
}
}
func TestArrayInput(t *testing.T) {
ob, err := NewOrderBookImbalanceFull()
if err != nil {
t.Fatalf("NewOrderBookImbalanceFull: %v", err)
}
defer ob.Close()
bidPrice := []float64{99.9, 99.8, 99.7}
bidSize := []float64{5, 3, 2}
askPrice := []float64{100.1, 100.2, 100.3}
askSize := []float64{1, 1, 1}
v := ob.Update(bidPrice, bidSize, askPrice, askSize)
if math.IsNaN(v) {
t.Fatal("order-book imbalance is NaN on a populated book")
}
}
func TestResetReturnsToWarmup(t *testing.T) {
s, _ := NewSma(3)
defer s.Close()
for _, v := range []float64{1, 2, 3} {
s.Update(v)
}
s.Reset()
if got := s.Update(10); !math.IsNaN(got) {
t.Fatalf("after reset first update = %v, want NaN (warmup)", got)
}
}
func TestInvalidParams(t *testing.T) {
if _, err := NewSma(0); err == nil {
t.Fatal("NewSma(0) should return ErrInvalidParams")
}
}
func TestCloseIsIdempotent(t *testing.T) {
s, _ := NewSma(3)
s.Close()
s.Close() // must not panic or double-free
}
+5 -3
View File
@@ -9,7 +9,8 @@
prebuilt native binary, no system dependencies.**
Wickra is a multi-language technical-analysis library with a Rust core and
bindings for Python, Node.js, and WebAssembly. Every indicator is an O(1)
bindings for Python, Node.js and WebAssembly, plus a C ABI for C/C++, C#, Go, 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 Node.js binding (napi-rs);
it exposes 200+ streaming-first indicators across sixteen families.
@@ -55,8 +56,9 @@ the main repository and documentation site:
- **Docs** (quickstarts, cookbook, TA-Lib migration): <https://docs.wickra.org>
- **Runnable examples:** [`examples/node/`](https://github.com/wickra-lib/wickra/tree/main/examples/node)
Wickra ships four bindings Python, Node.js, WebAssembly, and Rust — that all
expose the same indicators from the shared, `unsafe`-forbidden Rust core.
Wickra ships native bindings for Python, Node.js, WebAssembly and Rust, plus a
C ABI hub that any C-capable language (C, C++, Go, C#, Java, R) links against —
all exposing the same indicators from the shared, `unsafe`-forbidden Rust core.
## Disclaimer
+12 -1
View File
@@ -14,7 +14,18 @@ const wickra = require('..');
// but intentionally not isReady/warmupPeriod, so they are excluded from the
// Indicator completeness contract below (their interface is covered by the
// dedicated bar-builder tests).
const BAR_BUILDERS = new Set(['RenkoBars', 'KagiBars', 'PointAndFigureBars']);
const BAR_BUILDERS = new Set([
'RenkoBars',
'KagiBars',
'PointAndFigureBars',
'RangeBars',
'TickBars',
'VolumeBars',
'DollarBars',
'ImbalanceBars',
'RunBars',
'ThreeLineBreakBars',
]);
// An "indicator class" is an exported constructor whose prototype carries the
// streaming `update` method. This excludes `version` (a plain function), the bar
+207 -1
View File
@@ -28,6 +28,32 @@ function num(v) {
// --- Scalar indicators: update(value) vs batch(prices) ---
const scalarFactories = {
M2Measure: () => new wickra.M2Measure(20, 0.0, 0.02),
UpsidePotentialRatio: () => new wickra.UpsidePotentialRatio(20, 0.0),
GainToPainRatio: () => new wickra.GainToPainRatio(12),
CommonSenseRatio: () => new wickra.CommonSenseRatio(20),
KRatio: () => new wickra.KRatio(30),
TailRatio: () => new wickra.TailRatio(20),
MartinRatio: () => new wickra.MartinRatio(14),
BurkeRatio: () => new wickra.BurkeRatio(12),
SterlingRatio: () => new wickra.SterlingRatio(12),
AUTOCORRPGRAM: () => new wickra.AUTOCORRPGRAM(10, 48),
EVENBETTERSINE: () => new wickra.EVENBETTERSINE(40, 10),
BANDPASS: () => new wickra.BANDPASS(20, 0.3),
UNIVERSALOSC: () => new wickra.UNIVERSALOSC(20),
ADAPTIVERSI: () => new wickra.ADAPTIVERSI(14),
CTI: () => new wickra.CTI(20),
TRENDFLEX: () => new wickra.TRENDFLEX(20),
REFLEX: () => new wickra.REFLEX(20),
HIGHPASS: () => new wickra.HIGHPASS(48),
SAMPLEENT: () => new wickra.SAMPLEENT(20, 2, 0.2),
SHANNONENT: () => new wickra.SHANNONENT(20, 8),
ROLLINGMINMAX: () => new wickra.ROLLINGMINMAX(20),
JARQUEBERA: () => new wickra.JARQUEBERA(20),
BipowerVariation: () => new wickra.BipowerVariation(20),
VolatilityOfVolatility: () => new wickra.VolatilityOfVolatility(20, 20),
Garch11: () => new wickra.Garch11(0.000002, 0.1, 0.88),
EwmaVolatility: () => new wickra.EwmaVolatility(0.94),
PpoHistogram: () => new wickra.PpoHistogram(3, 6, 3),
MacdHistogram: () => new wickra.MacdHistogram(3, 6, 3),
TsfOscillator: () => new wickra.TsfOscillator(3),
@@ -350,6 +376,34 @@ const candleScalar = {
IMI: { make: () => new wickra.IMI(14), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
TTM_TREND: { make: () => new wickra.TTM_TREND(6), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
Qstick: { make: () => new wickra.Qstick(10), step: (ind, i) => ind.update(open[i], close[i]), batch: (ind) => ind.batch(open, close) },
VolatilityRatio: { make: () => new wickra.VolatilityRatio(14), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
ProjectionOscillator: { make: () => new wickra.ProjectionOscillator(14), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
TimeBasedStop: { make: () => new wickra.TimeBasedStop(5), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
VolumeRsi: { make: () => new wickra.VolumeRsi(14), step: (ind, i) => ind.update(close[i], volume[i]), batch: (ind) => ind.batch(close, volume) },
Wad: { make: () => new wickra.Wad(), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
TwiggsMoneyFlow: { make: () => new wickra.TwiggsMoneyFlow(21), step: (ind, i) => ind.update(high[i], low[i], close[i], volume[i]), batch: (ind) => ind.batch(high, low, close, volume) },
TradeVolumeIndex: { make: () => new wickra.TradeVolumeIndex(0.25), step: (ind, i) => ind.update(close[i], volume[i]), batch: (ind) => ind.batch(close, volume) },
IntradayIntensity: { make: () => new wickra.IntradayIntensity(), step: (ind, i) => ind.update(high[i], low[i], close[i], volume[i]), batch: (ind) => ind.batch(high, low, close, volume) },
BetterVolume: { make: () => new wickra.BetterVolume(14), step: (ind, i) => ind.update(high[i], low[i], close[i], volume[i]), batch: (ind) => ind.batch(high, low, close, volume) },
ADAPTIVECCI: { make: () => new wickra.ADAPTIVECCI(20), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
PivotReversal: { make: () => new wickra.PivotReversal(1, 1), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
TDCamouflage: { make: () => new wickra.TDCamouflage(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
TDClop: { make: () => new wickra.TDClop(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
TDClopwin: { make: () => new wickra.TDClopwin(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
TDPropulsion: { make: () => new wickra.TDPropulsion(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
TDTrap: { make: () => new wickra.TDTrap(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
TDDWave: { make: () => new wickra.TDDWave(2), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
HeikinAshiOscillator: { make: () => new wickra.HeikinAshiOscillator(5), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
ThreeLineBreak: { make: () => new wickra.ThreeLineBreak(3), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
Tristar: { make: () => new wickra.Tristar(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
HaramiCross: { make: () => new wickra.HaramiCross(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
TowerTopBottom: { make: () => new wickra.TowerTopBottom(), step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
DumplingTop: { make: () => new wickra.DumplingTop(9), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
NewPriceLines: { make: () => new wickra.NewPriceLines(5), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
FryPanBottom: { make: () => new wickra.FryPanBottom(9), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
NakedPoc: { make: () => new wickra.NakedPoc(20, 24), step: (ind, i) => ind.update(high[i], low[i], close[i], volume[i]), batch: (ind) => ind.batch(high, low, close, volume) },
SinglePrints: { make: () => new wickra.SinglePrints(20, 24), step: (ind, i) => ind.update(high[i], low[i]), batch: (ind) => ind.batch(high, low) },
ProfileShape: { make: () => new wickra.ProfileShape(20, 24), step: (ind, i) => ind.update(high[i], low[i], volume[i]), batch: (ind) => ind.batch(high, low, volume) },
};
for (const [name, d] of Object.entries(candleScalar)) {
@@ -436,6 +490,27 @@ const multi = {
QQE: { make: () => new wickra.QQE(14, 5, 4.236), fields: ['rsiMa', 'trailingLine'], step: (ind, i) => ind.update(close[i]), batch: (ind) => ind.batch(close) },
GatorOscillator: { make: () => new wickra.GatorOscillator(13, 8, 5), fields: ['upper', 'lower'], step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
KasePermissionStochastic: { make: () => new wickra.KasePermissionStochastic(9, 3), fields: ['fast', 'slow'], step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
VolatilityCone: { make: () => new wickra.VolatilityCone(20, 60), fields: ['current', 'min', 'median', 'max', 'percentile'], step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
QuartileBands: { make: () => new wickra.QuartileBands(4), fields: ['upper', 'middle', 'lower'], step: (ind, i) => ind.update(close[i]), batch: (ind) => ind.batch(close) },
BomarBands: { make: () => new wickra.BomarBands(4, 0.85), fields: ['upper', 'middle', 'lower'], step: (ind, i) => ind.update(close[i]), batch: (ind) => ind.batch(close) },
MedianChannel: { make: () => new wickra.MedianChannel(5, 2.0), fields: ['upper', 'middle', 'lower'], step: (ind, i) => ind.update(close[i]), batch: (ind) => ind.batch(close) },
ProjectionBands: { make: () => new wickra.ProjectionBands(3), fields: ['upper', 'middle', 'lower'], step: (ind, i) => ind.update(high[i], low[i]), batch: (ind) => ind.batch(high, low) },
KaseDevStop: { make: () => new wickra.KaseDevStop(3, 1.0), fields: ['value', 'direction'], step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
ElderSafeZone: { make: () => new wickra.ElderSafeZone(14, 2.0), fields: ['value', 'direction'], step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
AtrRatchet: { make: () => new wickra.AtrRatchet(14, 4.0, 0.1), fields: ['value', 'direction'], step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
Nrtr: { make: () => new wickra.Nrtr(2.0), fields: ['value', 'direction'], step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
ModifiedMaStop: { make: () => new wickra.ModifiedMaStop(14), fields: ['value', 'direction'], step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
VolumeWeightedMacd: { make: () => new wickra.VolumeWeightedMacd(12, 26, 9), fields: ['macd', 'signal', 'histogram'], step: (ind, i) => ind.update(close[i], volume[i]), batch: (ind) => ind.batch(close, volume) },
CentralPivotRange: { make: () => new wickra.CentralPivotRange(), fields: ['pivot', 'tc', 'bc'], step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
MurreyMathLines: { make: () => new wickra.MurreyMathLines(4), fields: ['mm8_8', 'mm7_8', 'mm6_8', 'mm5_8', 'mm4_8', 'mm3_8', 'mm2_8', 'mm1_8', 'mm0_8'], step: (ind, i) => ind.update(high[i], low[i]), batch: (ind) => ind.batch(high, low) },
AndrewsPitchfork: { make: () => new wickra.AndrewsPitchfork(2), fields: ['median', 'upper', 'lower'], step: (ind, i) => ind.update(high[i], low[i]), batch: (ind) => ind.batch(high, low) },
VolumeWeightedSr: { make: () => new wickra.VolumeWeightedSr(3), fields: ['support', 'resistance'], step: (ind, i) => ind.update(high[i], low[i], volume[i]), batch: (ind) => ind.batch(high, low, volume) },
TDMovingAverage: { make: () => new wickra.TDMovingAverage(5, 13), fields: ['st1', 'st2'], step: (ind, i) => ind.update(high[i], low[i]), batch: (ind) => ind.batch(high, low) },
SmoothedHeikinAshi: { make: () => new wickra.SmoothedHeikinAshi(5), fields: ['open', 'high', 'low', 'close'], step: (ind, i) => ind.update(open[i], high[i], low[i], close[i]), batch: (ind) => ind.batch(open, high, low, close) },
Equivolume: { make: () => new wickra.Equivolume(20), fields: ['height', 'width'], step: (ind, i) => ind.update(high[i], low[i], volume[i]), batch: (ind) => ind.batch(high, low, volume) },
CandleVolume: { make: () => new wickra.CandleVolume(20), fields: ['body', 'width'], step: (ind, i) => ind.update(open[i], close[i], volume[i]), batch: (ind) => ind.batch(open, close, volume) },
HighLowVolumeNodes: { make: () => new wickra.HighLowVolumeNodes(20, 24), fields: ['hvn', 'lvn'], step: (ind, i) => ind.update(high[i], low[i], volume[i]), batch: (ind) => ind.batch(high, low, volume) },
CompositeProfile: { make: () => new wickra.CompositeProfile(20, 24, 0.7), fields: ['poc', 'vah', 'val'], step: (ind, i) => ind.update(high[i], low[i], volume[i]), batch: (ind) => ind.batch(high, low, volume) },
};
for (const [name, d] of Object.entries(multi)) {
@@ -605,6 +680,8 @@ const pairFactories = {
VarianceRatio: () => new wickra.VarianceRatio(60, 2),
GrangerCausality: () => new wickra.GrangerCausality(60, 1),
SpreadAr1Coefficient: () => new wickra.SpreadAr1Coefficient(40),
KendallTau: () => new wickra.KendallTau(20),
HasbrouckInformationShare: () => new wickra.HasbrouckInformationShare(2),
};
for (const [name, make] of Object.entries(pairFactories)) {
@@ -1208,7 +1285,7 @@ test('vpin / amihud / roll reference + streaming matches batch', () => {
const price = Array.from({ length: n }, (_, i) => 100 + Math.sin(i * 0.25) * 4);
const size = Array.from({ length: n }, (_, i) => 1 + (i % 5));
const isBuy = Array.from({ length: n }, (_, i) => i % 2 === 0);
for (const make of [() => new wickra.Vpin(8, 5), () => new wickra.AmihudIlliquidity(14), () => new wickra.RollMeasure(14)]) {
for (const make of [() => new wickra.Vpin(8, 5), () => new wickra.AmihudIlliquidity(14), () => new wickra.RollMeasure(14), () => new wickra.TradeSignAutocorrelation(10), () => new wickra.Pin(10)]) {
const batch = make().batch(price, size, isBuy);
const streamer = make();
assert.equal(batch.length, n);
@@ -1217,6 +1294,16 @@ test('vpin / amihud / roll reference + streaming matches batch', () => {
assert.ok((Number.isNaN(batch[i]) && s === null) || Math.abs(s - batch[i]) < 1e-9, `mismatch at ${i}`);
}
}
// Trade-sign autocorrelation: alternating signs -> -1, all buys -> +1.
let tsac = null;
const tsacInd = new wickra.TradeSignAutocorrelation(10);
for (let i = 0; i < 20; i++) tsac = tsacInd.update(100, 1, i % 2 === 0);
assert.ok(Math.abs(tsac - -1.0) < 1e-12);
// PIN: one-sided flow -> 1, balanced flow -> 0.
let pin = null;
const pinInd = new wickra.Pin(10);
for (let i = 0; i < 20; i++) pin = pinInd.update(100, 1, true);
assert.ok(Math.abs(pin - 1.0) < 1e-12);
});
test('price-impact indicators reference values', () => {
@@ -1370,6 +1457,56 @@ test('derivatives reject bad input', () => {
assert.throws(() => new wickra.FundingBasis().update(100, 0));
});
test('B16 derivatives reference values', () => {
// Estimated leverage: oi / (long + short) = 200 / 100 = 2.
assert.ok(Math.abs(new wickra.EstimatedLeverageRatio().update(200, 60, 40) - 2.0) < 1e-12);
// OI-to-volume: oi / (buy + sell) = 100 / 50 = 2.
assert.ok(Math.abs(new wickra.OiToVolumeRatio().update(100, 30, 20) - 2.0) < 1e-12);
// Perpetual premium: (mark - index) / index = 0.5 / 100 = 0.005.
assert.ok(Math.abs(new wickra.PerpetualPremiumIndex().update(100.5, 100.0) - 0.005) < 1e-12);
// Funding-implied APR: rate * intervals = 0.0001 * 1095 = 0.1095.
assert.ok(Math.abs(new wickra.FundingImpliedApr(1095).update(0.0001) - 0.1095) < 1e-12);
// Open-interest momentum (period 2): warmup then ROC% = 100*(120 - 100)/100 = 20.
const oim = new wickra.OpenInterestMomentum(2);
assert.equal(oim.update(100), null);
assert.equal(oim.update(110), null);
assert.ok(Math.abs(oim.update(120) - 20.0) < 1e-12);
});
test('B16 derivatives streaming matches batch', () => {
const n = 30;
const oi = Array.from({ length: n }, (_, i) => 1000 + 50 * Math.sin(i * 0.3));
const longSz = Array.from({ length: n }, (_, i) => 600 + 20 * Math.cos(i * 0.2));
const shortSz = Array.from({ length: n }, (_, i) => 400 + 15 * Math.sin(i * 0.4));
const buy = Array.from({ length: n }, (_, i) => 300 + 10 * Math.sin(i * 0.5));
const sell = Array.from({ length: n }, (_, i) => 250 + 12 * Math.cos(i * 0.35));
const index = Array.from({ length: n }, (_, i) => 100 + Math.sin(i * 0.2));
const mark = Array.from({ length: n }, (_, i) => index[i] + 0.05 * Math.cos(i * 0.3));
const rate = Array.from({ length: n }, (_, i) => 0.0001 * Math.sin(i * 0.3));
const cmp = (batch, s, i) =>
assert.ok((s === null && Number.isNaN(batch[i])) || Math.abs(s - batch[i]) < 1e-12, `mismatch at ${i}`);
let b = new wickra.EstimatedLeverageRatio().batch(oi, longSz, shortSz);
let st = new wickra.EstimatedLeverageRatio();
for (let i = 0; i < n; i++) cmp(b, st.update(oi[i], longSz[i], shortSz[i]), i);
b = new wickra.OiToVolumeRatio().batch(oi, buy, sell);
st = new wickra.OiToVolumeRatio();
for (let i = 0; i < n; i++) cmp(b, st.update(oi[i], buy[i], sell[i]), i);
b = new wickra.PerpetualPremiumIndex().batch(mark, index);
st = new wickra.PerpetualPremiumIndex();
for (let i = 0; i < n; i++) cmp(b, st.update(mark[i], index[i]), i);
b = new wickra.FundingImpliedApr(1095).batch(rate);
st = new wickra.FundingImpliedApr(1095);
for (let i = 0; i < n; i++) cmp(b, st.update(rate[i]), i);
b = new wickra.OpenInterestMomentum(10).batch(oi);
st = new wickra.OpenInterestMomentum(10);
for (let i = 0; i < n; i++) cmp(b, st.update(oi[i]), i);
});
test('market breadth: AdvanceDecline reference values', () => {
// A breadth tick is the universe as parallel arrays; the sign of `change`
// classifies each symbol as advancing / declining / unchanged.
@@ -1632,3 +1769,72 @@ test('PointAndFigureBars closes a column on a 3-box reversal', () => {
assert.equal(col[0].direction, 1);
assert.ok(Math.abs(col[0].high - 15) < 1e-9 && Math.abs(col[0].low - 10) < 1e-9);
});
test('RangeBars prints aligned bars on an up move', () => {
const rb = new wickra.RangeBars(1.0);
assert.deepEqual(rb.update(10), []); // seed
const up = rb.update(13);
assert.equal(up.length, 3);
assert.ok(Math.abs(up[0].open - 10) < 1e-9 && Math.abs(up[2].close - 13) < 1e-9);
assert.ok(up.every((b) => b.direction === 1));
});
test('TickBars groups a fixed number of candles', () => {
const tb = new wickra.TickBars(2);
assert.deepEqual(tb.update(10, 11, 9, 10.5, 100), []);
const out = tb.update(10.5, 12, 10, 11, 150);
assert.equal(out.length, 1);
assert.ok(Math.abs(out[0].open - 10) < 1e-9);
assert.ok(Math.abs(out[0].high - 12) < 1e-9);
assert.ok(Math.abs(out[0].low - 9) < 1e-9);
assert.ok(Math.abs(out[0].close - 11) < 1e-9);
assert.ok(Math.abs(out[0].volume - 250) < 1e-9);
});
test('VolumeBars closes when accumulated volume crosses the threshold', () => {
const vb = new wickra.VolumeBars(100);
assert.deepEqual(vb.update(10, 10, 10, 10, 60), []);
const out = vb.update(10.5, 10.5, 10.5, 10.5, 60);
assert.equal(out.length, 1);
assert.ok(Math.abs(out[0].volume - 120) < 1e-9);
});
test('DollarBars closes when traded value crosses the threshold', () => {
const db = new wickra.DollarBars(1000);
assert.deepEqual(db.update(10, 10, 10, 10, 60), []);
const out = db.update(10, 10, 10, 10, 60);
assert.equal(out.length, 1);
assert.ok(Math.abs(out[0].dollar - 1200) < 1e-9);
assert.ok(Math.abs(out[0].volume - 120) < 1e-9);
});
test('ImbalanceBars closes a buy bar at the threshold', () => {
const ib = new wickra.ImbalanceBars(3.0);
ib.update(10, 10, 10, 10);
ib.update(11, 11, 11, 11);
ib.update(12, 12, 12, 12);
const out = ib.update(13, 13, 13, 13);
assert.equal(out.length, 1);
assert.equal(out[0].direction, 1);
assert.ok(Math.abs(out[0].imbalance - 3) < 1e-9);
});
test('RunBars closes a buy run at the run length', () => {
const rb = new wickra.RunBars(3);
rb.update(10, 10, 10, 10);
rb.update(11, 11, 11, 11);
rb.update(12, 12, 12, 12);
const out = rb.update(13, 13, 13, 13);
assert.equal(out.length, 1);
assert.equal(out[0].direction, 1);
assert.equal(out[0].length, 3);
});
test('ThreeLineBreakBars draws a rising line', () => {
const tlb = new wickra.ThreeLineBreakBars(3);
assert.deepEqual(tlb.update(10), []); // seed
const out = tlb.update(11);
assert.equal(out.length, 1);
assert.equal(out[0].direction, 1);
assert.ok(Math.abs(out[0].open - 10) < 1e-9 && Math.abs(out[0].close - 11) < 1e-9);
});
+1000
View File
File diff suppressed because it is too large Load Diff
+92 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "wickra-darwin-arm64",
"version": "0.5.8",
"version": "0.7.8",
"description": "Native binding for wickra (macOS Apple Silicon). Installed automatically as an optional dependency of wickra on matching platforms.",
"main": "wickra.darwin-arm64.node",
"files": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "wickra-darwin-x64",
"version": "0.5.8",
"version": "0.7.8",
"description": "Native binding for wickra (macOS Intel). Installed automatically as an optional dependency of wickra on matching platforms.",
"main": "wickra.darwin-x64.node",
"files": [
@@ -1,6 +1,6 @@
{
"name": "wickra-linux-arm64-gnu",
"version": "0.5.8",
"version": "0.7.8",
"description": "Native binding for wickra (linux arm64 GNU). Installed automatically as an optional dependency of wickra on matching platforms.",
"main": "wickra.linux-arm64-gnu.node",
"files": [
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "wickra-linux-x64-gnu",
"version": "0.5.8",
"version": "0.7.8",
"description": "Native binding for wickra (linux x64 GNU). Installed automatically as an optional dependency of wickra on matching platforms.",
"main": "wickra.linux-x64-gnu.node",
"files": [
@@ -1,6 +1,6 @@
{
"name": "wickra-win32-arm64-msvc",
"version": "0.5.8",
"version": "0.7.8",
"description": "Native binding for wickra (Windows arm64 MSVC). Installed automatically as an optional dependency of wickra on matching platforms.",
"main": "wickra.win32-arm64-msvc.node",
"files": [
@@ -1,6 +1,6 @@
{
"name": "wickra-win32-x64-msvc",
"version": "0.5.8",
"version": "0.7.8",
"description": "Native binding for wickra (Windows x64 MSVC). Installed automatically as an optional dependency of wickra on matching platforms.",
"main": "wickra.win32-x64-msvc.node",
"files": [
+20 -20
View File
@@ -1,12 +1,12 @@
{
"name": "wickra",
"version": "0.5.8",
"version": "0.7.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "wickra",
"version": "0.5.8",
"version": "0.7.8",
"license": "MIT OR Apache-2.0",
"devDependencies": {
"@napi-rs/cli": "^2.18.0"
@@ -15,12 +15,12 @@
"node": ">= 18"
},
"optionalDependencies": {
"wickra-darwin-arm64": "0.5.8",
"wickra-darwin-x64": "0.5.8",
"wickra-linux-arm64-gnu": "0.5.8",
"wickra-linux-x64-gnu": "0.5.8",
"wickra-win32-arm64-msvc": "0.5.8",
"wickra-win32-x64-msvc": "0.5.8"
"wickra-darwin-arm64": "0.7.8",
"wickra-darwin-x64": "0.7.8",
"wickra-linux-arm64-gnu": "0.7.8",
"wickra-linux-x64-gnu": "0.7.8",
"wickra-win32-arm64-msvc": "0.7.8",
"wickra-win32-x64-msvc": "0.7.8"
}
},
"node_modules/@napi-rs/cli": {
@@ -41,8 +41,8 @@
}
},
"node_modules/wickra-darwin-arm64": {
"version": "0.5.8",
"resolved": "https://registry.npmjs.org/wickra-darwin-arm64/-/wickra-darwin-arm64-0.5.8.tgz",
"version": "0.7.8",
"resolved": "https://registry.npmjs.org/wickra-darwin-arm64/-/wickra-darwin-arm64-0.7.8.tgz",
"integrity": "sha512-4eZiBR/yGUdr4nzhEUFy2i69XgNx64iI2ax/LPamsThgylC0KpHOZKK19QzJ2d9KbK4C8nMjME5FLuR+4GNEwQ==",
"cpu": [
"arm64"
@@ -57,8 +57,8 @@
}
},
"node_modules/wickra-darwin-x64": {
"version": "0.5.8",
"resolved": "https://registry.npmjs.org/wickra-darwin-x64/-/wickra-darwin-x64-0.5.8.tgz",
"version": "0.7.8",
"resolved": "https://registry.npmjs.org/wickra-darwin-x64/-/wickra-darwin-x64-0.7.8.tgz",
"integrity": "sha512-6hf8zI3QPjTFp4zCpmgUwDvNtu6jHqNUHKD5e55POo0CgA52HkpyxSPtVm8TGTIZDI7kPjlbOdBM8CJ76mmXwA==",
"cpu": [
"x64"
@@ -73,8 +73,8 @@
}
},
"node_modules/wickra-linux-arm64-gnu": {
"version": "0.5.8",
"resolved": "https://registry.npmjs.org/wickra-linux-arm64-gnu/-/wickra-linux-arm64-gnu-0.5.8.tgz",
"version": "0.7.8",
"resolved": "https://registry.npmjs.org/wickra-linux-arm64-gnu/-/wickra-linux-arm64-gnu-0.7.8.tgz",
"integrity": "sha512-kSe6y0xBMSiqdPLXNjwop5WZdHtvdBNKSEBCwZ4hFq33p4apW25/wrlzv9/oDuyD4kuPabJEhCCnFOplh58CUg==",
"cpu": [
"arm64"
@@ -89,8 +89,8 @@
}
},
"node_modules/wickra-linux-x64-gnu": {
"version": "0.5.8",
"resolved": "https://registry.npmjs.org/wickra-linux-x64-gnu/-/wickra-linux-x64-gnu-0.5.8.tgz",
"version": "0.7.8",
"resolved": "https://registry.npmjs.org/wickra-linux-x64-gnu/-/wickra-linux-x64-gnu-0.7.8.tgz",
"integrity": "sha512-tWBWS4qz7hxM4xnpFb59bhf6TaLwXq0Z3jEa/2l7r8PiHA94g8r8S53NRMiT+4yiL5hSWe/nUiC/YXdRrhEZ4g==",
"cpu": [
"x64"
@@ -105,8 +105,8 @@
}
},
"node_modules/wickra-win32-arm64-msvc": {
"version": "0.5.8",
"resolved": "https://registry.npmjs.org/wickra-win32-arm64-msvc/-/wickra-win32-arm64-msvc-0.5.8.tgz",
"version": "0.7.8",
"resolved": "https://registry.npmjs.org/wickra-win32-arm64-msvc/-/wickra-win32-arm64-msvc-0.7.8.tgz",
"integrity": "sha512-EXIckHxAtF75PUGDKRzXyqMe9ldP0JjSdu68WFN6iJfp+McYrGu6h40TEJlQ/oUEIoPqiZB/xhVyo/el5Lg7zw==",
"cpu": [
"arm64"
@@ -121,8 +121,8 @@
}
},
"node_modules/wickra-win32-x64-msvc": {
"version": "0.5.8",
"resolved": "https://registry.npmjs.org/wickra-win32-x64-msvc/-/wickra-win32-x64-msvc-0.5.8.tgz",
"version": "0.7.8",
"resolved": "https://registry.npmjs.org/wickra-win32-x64-msvc/-/wickra-win32-x64-msvc-0.7.8.tgz",
"integrity": "sha512-Yfsqq1Xwp6hdxMyLze411vNdo7BDwI6+lPSe7A9XdqyPecNDbtKwYLpsal2r8EHbNzqM+R8XnuRtUaEQS5VlUQ==",
"cpu": [
"x64"
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "wickra",
"version": "0.5.8",
"version": "0.7.8",
"description": "Streaming-first technical indicators: incremental, fast, install-free. Node bindings powered by Rust.",
"author": "kingchenc <support@wickra.org>",
"main": "index.js",
@@ -47,12 +47,12 @@
"node": ">= 18"
},
"optionalDependencies": {
"wickra-linux-x64-gnu": "0.5.8",
"wickra-linux-arm64-gnu": "0.5.8",
"wickra-darwin-x64": "0.5.8",
"wickra-darwin-arm64": "0.5.8",
"wickra-win32-x64-msvc": "0.5.8",
"wickra-win32-arm64-msvc": "0.5.8"
"wickra-linux-x64-gnu": "0.7.8",
"wickra-linux-arm64-gnu": "0.7.8",
"wickra-darwin-x64": "0.7.8",
"wickra-darwin-arm64": "0.7.8",
"wickra-win32-x64-msvc": "0.7.8",
"wickra-win32-arm64-msvc": "0.7.8"
},
"scripts": {
"build": "napi build --platform --release",
File diff suppressed because it is too large Load Diff
+5 -3
View File
@@ -9,7 +9,8 @@
system dependencies, no C build tooling.**
Wickra is a multi-language technical-analysis library with a Rust core and
bindings for Python, Node.js, and WebAssembly. Every indicator is an O(1)
bindings for Python, Node.js and WebAssembly, plus a C ABI for C/C++, C#, Go, 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 Python binding (PyO3); it
exposes 200+ streaming-first indicators across sixteen families.
@@ -54,8 +55,9 @@ the main repository and documentation site:
- **Docs** (quickstarts, cookbook, TA-Lib migration): <https://docs.wickra.org>
- **Runnable examples:** [`examples/python/`](https://github.com/wickra-lib/wickra/tree/main/examples/python)
Wickra ships four bindings Python, Node.js, WebAssembly, and Rust — that all
expose the same indicators from the shared, `unsafe`-forbidden Rust core.
Wickra ships native bindings for Python, Node.js, WebAssembly and Rust, plus a
C ABI hub that any C-capable language (C, C++, Go, C#, Java, R) links against —
all exposing the same indicators from the shared, `unsafe`-forbidden Rust core.
## Disclaimer
+368 -16
View File
@@ -49,6 +49,7 @@ TALIB = _try_import("talib")
PANDAS_TA = _try_import("pandas_ta")
TALIPP = _try_import("talipp.indicators") or _try_import("talipp")
FINTA = _try_import("finta")
TULIPY = _try_import("tulipy")
PD = _try_import("pandas")
import wickra as WICKRA # noqa: E402 -- the library under test must be importable
@@ -71,13 +72,23 @@ class Sample:
return (self.seconds / self.iterations) * 1_000_000
def time_call(fn: Callable[[], None], iterations: int) -> float:
"""Time ``fn`` over ``iterations`` calls, returning total wall seconds."""
def time_call(fn: Callable[[], None], iterations: int, rounds: int = 5) -> float:
"""Time ``fn`` over ``iterations`` calls per round, across ``rounds`` rounds.
Returns the *median* round's wall seconds for one round of ``iterations``
calls. Taking the median across several rounds damps the OS scheduling and
GC jitter that a single timing pass would otherwise bake into the result,
so the per-iteration figure is stable run-to-run. Callers keep dividing the
return value by ``iterations``.
"""
fn() # one warmup call to populate caches
start = time.perf_counter()
for _ in range(iterations):
fn()
return time.perf_counter() - start
rounds_s: List[float] = []
for _ in range(rounds):
start = time.perf_counter()
for _ in range(iterations):
fn()
rounds_s.append(time.perf_counter() - start)
return statistics.median(rounds_s)
def gen_prices(n: int, seed: int = 0xC0FFEE) -> np.ndarray:
@@ -275,6 +286,34 @@ def talipp_bollinger_batch(prices: np.ndarray) -> Optional[Callable[[], None]]:
return lambda: BB(period=20, std_dev_mult=2.0, input_values=list(prices))
# tulipy wraps the C "Tulip Indicators" library; it takes contiguous float64
# arrays and indicator options as positional arguments.
def tulipy_sma_batch(prices: np.ndarray) -> Optional[Callable[[], None]]:
return None if TULIPY is None else (lambda: TULIPY.sma(prices, 20))
def tulipy_ema_batch(prices: np.ndarray) -> Optional[Callable[[], None]]:
return None if TULIPY is None else (lambda: TULIPY.ema(prices, 20))
def tulipy_rsi_batch(prices: np.ndarray) -> Optional[Callable[[], None]]:
return None if TULIPY is None else (lambda: TULIPY.rsi(prices, 14))
def tulipy_macd_batch(prices: np.ndarray) -> Optional[Callable[[], None]]:
return None if TULIPY is None else (lambda: TULIPY.macd(prices, 12, 26, 9))
def tulipy_bollinger_batch(prices: np.ndarray) -> Optional[Callable[[], None]]:
return None if TULIPY is None else (lambda: TULIPY.bbands(prices, 20, 2.0))
def tulipy_atr_batch(high: np.ndarray, low: np.ndarray, close: np.ndarray) -> Optional[Callable[[], None]]:
return None if TULIPY is None else (lambda: TULIPY.atr(high, low, close, 14))
# --------------------------------------------------------------------------- #
# Streaming scenario: per-tick latency
# --------------------------------------------------------------------------- #
@@ -329,6 +368,260 @@ def talipp_rsi_streaming(seed: np.ndarray, live: np.ndarray) -> Optional[Callabl
return run
# Scalar streaming peers: Wickra and talipp both update incrementally in O(1),
# so this is the like-for-like per-tick comparison (batch-only libs are covered
# by the batch tables and the recompute contrast on RSI above).
def wickra_sma_streaming(seed: np.ndarray, live: np.ndarray) -> Callable[[], None]:
def run() -> None:
sma = WICKRA.SMA(20)
sma.batch(seed)
for p in live:
sma.update(float(p))
return run
def talipp_sma_streaming(seed: np.ndarray, live: np.ndarray) -> Optional[Callable[[], None]]:
if TALIPP is None:
return None
from talipp.indicators import SMA # type: ignore
def run() -> None:
sma = SMA(period=20, input_values=list(seed))
for p in live:
sma.add(float(p))
return run
def wickra_ema_streaming(seed: np.ndarray, live: np.ndarray) -> Callable[[], None]:
def run() -> None:
ema = WICKRA.EMA(20)
ema.batch(seed)
for p in live:
ema.update(float(p))
return run
def talipp_ema_streaming(seed: np.ndarray, live: np.ndarray) -> Optional[Callable[[], None]]:
if TALIPP is None:
return None
from talipp.indicators import EMA # type: ignore
def run() -> None:
ema = EMA(period=20, input_values=list(seed))
for p in live:
ema.add(float(p))
return run
def wickra_macd_streaming(seed: np.ndarray, live: np.ndarray) -> Callable[[], None]:
def run() -> None:
macd = WICKRA.MACD()
macd.batch(seed)
for p in live:
macd.update(float(p))
return run
def talipp_macd_streaming(seed: np.ndarray, live: np.ndarray) -> Optional[Callable[[], None]]:
if TALIPP is None:
return None
from talipp.indicators import MACD # type: ignore
def run() -> None:
macd = MACD(
fast_period=12, slow_period=26, signal_period=9, input_values=list(seed)
)
for p in live:
macd.add(float(p))
return run
def wickra_bollinger_streaming(seed: np.ndarray, live: np.ndarray) -> Callable[[], None]:
def run() -> None:
bb = WICKRA.BollingerBands(20, 2.0)
bb.batch(seed)
for p in live:
bb.update(float(p))
return run
def talipp_bollinger_streaming(seed: np.ndarray, live: np.ndarray) -> Optional[Callable[[], None]]:
if TALIPP is None:
return None
from talipp.indicators import BB # type: ignore
def run() -> None:
bb = BB(period=20, std_dev_mult=2.0, input_values=list(seed))
for p in live:
bb.add(float(p))
return run
# Recompute streaming peers: batch-only libraries have no incremental API, so
# the only honest way to drive them tick-by-tick is to re-run the full batch
# over the grown history on every new price. These runners expose exactly that
# cost — the gap Wickra's O(1) update closes.
def _talib_recompute_streaming(seed, live, fn):
def run() -> None:
history = list(seed)
for p in live:
history.append(float(p))
fn(np.asarray(history))
return run
def _pandas_ta_recompute_streaming(seed, live, fn):
def run() -> None:
history = list(seed)
for p in live:
history.append(float(p))
fn(PD.Series(history))
return run
def _tulipy_recompute_streaming(seed, live, fn):
def run() -> None:
history = list(seed)
for p in live:
history.append(float(p))
fn(np.asarray(history, dtype=np.float64))
return run
def _finta_recompute_streaming(seed, live, fn):
def run() -> None:
history = list(seed)
for p in live:
history.append(float(p))
arr = np.asarray(history)
fn(PD.DataFrame({"open": arr, "high": arr, "low": arr, "close": arr, "volume": np.ones_like(arr)}))
return run
def talib_sma_streaming(seed, live):
if TALIB is None:
return None
return _talib_recompute_streaming(seed, live, lambda a: TALIB.SMA(a, timeperiod=20))
def pandas_ta_sma_streaming(seed, live):
if PANDAS_TA is None or PD is None:
return None
return _pandas_ta_recompute_streaming(seed, live, lambda s: PANDAS_TA.sma(s, length=20))
def tulipy_sma_streaming(seed, live):
if TULIPY is None:
return None
return _tulipy_recompute_streaming(seed, live, lambda a: TULIPY.sma(a, 20))
def finta_sma_streaming(seed, live):
if FINTA is None or PD is None:
return None
return _finta_recompute_streaming(seed, live, lambda df: FINTA.TA.SMA(df, period=20))
def talib_ema_streaming(seed, live):
if TALIB is None:
return None
return _talib_recompute_streaming(seed, live, lambda a: TALIB.EMA(a, timeperiod=20))
def pandas_ta_ema_streaming(seed, live):
if PANDAS_TA is None or PD is None:
return None
return _pandas_ta_recompute_streaming(seed, live, lambda s: PANDAS_TA.ema(s, length=20))
def tulipy_ema_streaming(seed, live):
if TULIPY is None:
return None
return _tulipy_recompute_streaming(seed, live, lambda a: TULIPY.ema(a, 20))
def finta_ema_streaming(seed, live):
if FINTA is None or PD is None:
return None
return _finta_recompute_streaming(seed, live, lambda df: FINTA.TA.EMA(df, period=20))
def tulipy_rsi_streaming(seed, live):
if TULIPY is None:
return None
return _tulipy_recompute_streaming(seed, live, lambda a: TULIPY.rsi(a, 14))
def finta_rsi_streaming(seed, live):
if FINTA is None or PD is None:
return None
return _finta_recompute_streaming(seed, live, lambda df: FINTA.TA.RSI(df, period=14))
def talib_macd_streaming(seed, live):
if TALIB is None:
return None
return _talib_recompute_streaming(seed, live, lambda a: TALIB.MACD(a))
def pandas_ta_macd_streaming(seed, live):
if PANDAS_TA is None or PD is None:
return None
return _pandas_ta_recompute_streaming(seed, live, lambda s: PANDAS_TA.macd(s))
def tulipy_macd_streaming(seed, live):
if TULIPY is None:
return None
return _tulipy_recompute_streaming(seed, live, lambda a: TULIPY.macd(a, 12, 26, 9))
def finta_macd_streaming(seed, live):
if FINTA is None or PD is None:
return None
return _finta_recompute_streaming(seed, live, lambda df: FINTA.TA.MACD(df))
def talib_bollinger_streaming(seed, live):
if TALIB is None:
return None
return _talib_recompute_streaming(seed, live, lambda a: TALIB.BBANDS(a, timeperiod=20, nbdevup=2, nbdevdn=2))
def pandas_ta_bollinger_streaming(seed, live):
if PANDAS_TA is None or PD is None:
return None
return _pandas_ta_recompute_streaming(seed, live, lambda s: PANDAS_TA.bbands(s, length=20, std=2.0))
def tulipy_bollinger_streaming(seed, live):
if TULIPY is None:
return None
return _tulipy_recompute_streaming(seed, live, lambda a: TULIPY.bbands(a, 20, 2.0))
def finta_bollinger_streaming(seed, live):
if FINTA is None or PD is None:
return None
return _finta_recompute_streaming(seed, live, lambda df: FINTA.TA.BBANDS(df, period=20, std_multiplier=2.0))
# --------------------------------------------------------------------------- #
# Runner
# --------------------------------------------------------------------------- #
@@ -339,6 +632,7 @@ BATCH_INDICATORS = [
("Wickra", wickra_sma_batch),
("TA-Lib", talib_sma_batch),
("pandas-ta", pandas_ta_sma_batch),
("tulipy", tulipy_sma_batch),
("finta", finta_sma_batch),
("talipp", talipp_sma_batch),
]),
@@ -346,6 +640,7 @@ BATCH_INDICATORS = [
("Wickra", wickra_ema_batch),
("TA-Lib", talib_ema_batch),
("pandas-ta", pandas_ta_ema_batch),
("tulipy", tulipy_ema_batch),
("finta", finta_ema_batch),
("talipp", talipp_ema_batch),
]),
@@ -353,6 +648,7 @@ BATCH_INDICATORS = [
("Wickra", wickra_rsi_batch),
("TA-Lib", talib_rsi_batch),
("pandas-ta", pandas_ta_rsi_batch),
("tulipy", tulipy_rsi_batch),
("finta", finta_rsi_batch),
("talipp", talipp_rsi_batch),
]),
@@ -360,6 +656,7 @@ BATCH_INDICATORS = [
("Wickra", wickra_macd_batch),
("TA-Lib", talib_macd_batch),
("pandas-ta", pandas_ta_macd_batch),
("tulipy", tulipy_macd_batch),
("finta", finta_macd_batch),
("talipp", talipp_macd_batch),
]),
@@ -367,6 +664,7 @@ BATCH_INDICATORS = [
("Wickra", wickra_bollinger_batch),
("TA-Lib", talib_bollinger_batch),
("pandas-ta", pandas_ta_bollinger_batch),
("tulipy", tulipy_bollinger_batch),
("finta", finta_bollinger_batch),
("talipp", talipp_bollinger_batch),
]),
@@ -376,29 +674,64 @@ OHLC_INDICATORS = [
("ATR(14)", [
("Wickra", wickra_atr_batch),
("TA-Lib", talib_atr_batch),
("tulipy", tulipy_atr_batch),
("finta", finta_atr_batch),
("talipp", talipp_atr_batch),
]),
]
STREAMING_INDICATORS = [
("SMA(20)", [
("Wickra", wickra_sma_streaming),
("talipp", talipp_sma_streaming),
("TA-Lib", talib_sma_streaming),
("pandas-ta", pandas_ta_sma_streaming),
("tulipy", tulipy_sma_streaming),
("finta", finta_sma_streaming),
]),
("EMA(20)", [
("Wickra", wickra_ema_streaming),
("talipp", talipp_ema_streaming),
("TA-Lib", talib_ema_streaming),
("pandas-ta", pandas_ta_ema_streaming),
("tulipy", tulipy_ema_streaming),
("finta", finta_ema_streaming),
]),
("RSI(14)", [
("Wickra", wickra_rsi_streaming),
("talipp", talipp_rsi_streaming),
("TA-Lib", talib_rsi_streaming),
("pandas-ta", pandas_ta_rsi_streaming),
("talipp", talipp_rsi_streaming),
("tulipy", tulipy_rsi_streaming),
("finta", finta_rsi_streaming),
]),
("MACD(12, 26, 9)", [
("Wickra", wickra_macd_streaming),
("talipp", talipp_macd_streaming),
("TA-Lib", talib_macd_streaming),
("pandas-ta", pandas_ta_macd_streaming),
("tulipy", tulipy_macd_streaming),
("finta", finta_macd_streaming),
]),
("Bollinger(20, 2.0)", [
("Wickra", wickra_bollinger_streaming),
("talipp", talipp_bollinger_streaming),
("TA-Lib", talib_bollinger_streaming),
("pandas-ta", pandas_ta_bollinger_streaming),
("tulipy", tulipy_bollinger_streaming),
("finta", finta_bollinger_streaming),
]),
]
def run_batch(prices: np.ndarray, iterations: int) -> List[Sample]:
def run_batch(prices: np.ndarray, iterations: int, rounds: int) -> List[Sample]:
out: List[Sample] = []
for indicator_name, libs in BATCH_INDICATORS:
for lib_name, factory in libs:
runner = factory(prices)
if runner is None:
continue
secs = time_call(runner, iterations)
secs = time_call(runner, iterations, rounds)
out.append(Sample(lib_name, indicator_name, "batch", secs, iterations))
return out
@@ -408,6 +741,7 @@ def run_ohlc(
low: np.ndarray,
close: np.ndarray,
iterations: int,
rounds: int,
) -> List[Sample]:
out: List[Sample] = []
for indicator_name, libs in OHLC_INDICATORS:
@@ -415,12 +749,12 @@ def run_ohlc(
runner = factory(high, low, close)
if runner is None:
continue
secs = time_call(runner, iterations)
secs = time_call(runner, iterations, rounds)
out.append(Sample(lib_name, indicator_name, "batch", secs, iterations))
return out
def run_streaming(prices: np.ndarray, streaming_window: int, iterations: int) -> List[Sample]:
def run_streaming(prices: np.ndarray, streaming_window: int, iterations: int, rounds: int) -> List[Sample]:
out: List[Sample] = []
seed = prices[:streaming_window]
live = prices[streaming_window:]
@@ -431,7 +765,7 @@ def run_streaming(prices: np.ndarray, streaming_window: int, iterations: int) ->
runner = factory(seed, live)
if runner is None:
continue
secs = time_call(runner, iterations)
secs = time_call(runner, iterations, rounds)
sample = Sample(lib_name, indicator_name, "streaming", secs, iterations)
sample.iterations = iterations * len(live) # per-tick normalization
out.append(sample)
@@ -479,6 +813,12 @@ def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0] if __doc__ else None)
parser.add_argument("--size", type=int, default=20_000, help="number of prices")
parser.add_argument("--iterations", type=int, default=20, help="batch repetitions per timing")
parser.add_argument(
"--rounds",
type=int,
default=5,
help="batch timing rounds; the median round is reported to damp jitter",
)
parser.add_argument(
"--streaming-window",
type=int,
@@ -491,6 +831,14 @@ def parse_args() -> argparse.Namespace:
default=3,
help="repetitions of the streaming workload (each iteration replays all live ticks)",
)
parser.add_argument(
"--streaming-rounds",
type=int,
default=2,
help="streaming timing rounds; the median round is reported",
)
parser.add_argument("--skip-batch", action="store_true", help="skip the batch tables")
parser.add_argument("--skip-streaming", action="store_true", help="skip the streaming tables")
return parser.parse_args()
@@ -501,6 +849,7 @@ def main() -> None:
available = []
if TALIB is not None: available.append("TA-Lib")
if PANDAS_TA is not None: available.append("pandas-ta")
if TULIPY is not None: available.append("tulipy")
if FINTA is not None: available.append("finta")
if TALIPP is not None: available.append("talipp")
print(f"Wickra benchmark suite — wickra=v{WICKRA.__version__}")
@@ -509,11 +858,14 @@ def main() -> None:
print(f"Streaming window: {args.streaming_window} seed, {args.size - args.streaming_window} live")
high, low, close, _ = gen_ohlc(args.size)
batch_rows = run_batch(prices, args.iterations)
ohlc_rows = run_ohlc(high, low, close, args.iterations)
streaming_rows = run_streaming(prices, args.streaming_window, args.streaming_iterations)
rows: List[Sample] = []
if not args.skip_batch:
rows += run_batch(prices, args.iterations, args.rounds)
rows += run_ohlc(high, low, close, args.iterations, args.rounds)
if not args.skip_streaming:
rows += run_streaming(prices, args.streaming_window, args.streaming_iterations, args.streaming_rounds)
print(render_table(batch_rows + ohlc_rows + streaming_rows))
print(render_table(rows))
if __name__ == "__main__":
+2 -1
View File
@@ -4,7 +4,7 @@ build-backend = "maturin"
[project]
name = "wickra"
version = "0.5.8"
version = "0.7.8"
description = "Streaming-first technical indicators: incremental, fast, install-free."
readme = "README.md"
license = "MIT OR Apache-2.0"
@@ -39,6 +39,7 @@ bench = [
"pytest-benchmark>=4",
"TA-Lib; platform_system != 'Windows'",
"pandas-ta>=0.3.14b",
"tulipy>=0.4; platform_system != 'Windows'",
"talipp>=2",
"finta>=1.3",
"pandas>=2",
+184
View File
@@ -25,6 +25,37 @@ from __future__ import annotations
from ._wickra import (
__version__,
M2Measure,
UpsidePotentialRatio,
GainToPainRatio,
CommonSenseRatio,
KRatio,
TailRatio,
MartinRatio,
BurkeRatio,
SterlingRatio,
AUTOCORRPGRAM,
EVENBETTERSINE,
BANDPASS,
ADAPTIVECCI,
UNIVERSALOSC,
ADAPTIVERSI,
CTI,
TRENDFLEX,
REFLEX,
HIGHPASS,
SAMPLEENT,
SHANNONENT,
ROLLINGMINMAX,
JARQUEBERA,
TimeBasedStop,
ProjectionOscillator,
VolatilityCone,
VolatilityRatio,
BipowerVariation,
VolatilityOfVolatility,
Garch11,
EwmaVolatility,
PpoHistogram,
MacdHistogram,
TsfOscillator,
@@ -161,6 +192,11 @@ from ._wickra import (
HistoricalVolatility,
BollingerBandwidth,
PercentB,
# Trailing Stops
ModifiedMaStop,
Nrtr,
AtrRatchet,
ElderSafeZone,
SuperTrend,
ChandelierExit,
ChandeKrollStop,
@@ -172,6 +208,7 @@ from ._wickra import (
PercentageTrailingStop,
StepTrailingStop,
RenkoTrailingStop,
KaseDevStop,
TrueRange,
ChaikinVolatility,
RVIVolatility,
@@ -180,6 +217,13 @@ from ._wickra import (
RogersSatchellVolatility,
YangZhangVolatility,
# Volume
VolumeWeightedMacd,
BetterVolume,
IntradayIntensity,
TradeVolumeIndex,
TwiggsMoneyFlow,
Wad,
VolumeRsi,
OBV,
VWAP,
RollingVWAP,
@@ -200,6 +244,7 @@ from ._wickra import (
MarketFacilitationIndex,
EaseOfMovement,
# Statistics
KendallTau,
SpreadBollingerBands,
KalmanHedgeRatio,
GrangerCausality,
@@ -257,6 +302,10 @@ from ._wickra import (
MAMA,
FAMA,
# Bands & Channels
ProjectionBands,
MedianChannel,
BomarBands,
QuartileBands,
MaEnvelope,
AccelerationBands,
StarcBands,
@@ -269,6 +318,11 @@ from ._wickra import (
FractalChaosBands,
VwapStdDevBands,
# Pivots & S/R
PivotReversal,
VolumeWeightedSr,
AndrewsPitchfork,
MurreyMathLines,
CentralPivotRange,
ClassicPivots,
FibonacciPivots,
Camarilla,
@@ -277,6 +331,13 @@ from ._wickra import (
WilliamsFractals,
ZigZag,
# DeMark
TDMovingAverage,
TDDWave,
TDTrap,
TDPropulsion,
TDClopwin,
TDClop,
TDCamouflage,
TDSetup,
TDSequential,
TDDeMarker,
@@ -292,17 +353,40 @@ from ._wickra import (
# Ichimoku & alternative charts
Ichimoku,
HeikinAshi,
SmoothedHeikinAshi,
HeikinAshiOscillator,
ThreeLineBreak,
Equivolume,
CandleVolume,
# Market Profile
CompositeProfile,
HighLowVolumeNodes,
ProfileShape,
SinglePrints,
NakedPoc,
ValueArea,
VolumeProfile,
TpoProfile,
InitialBalance,
OpeningRange,
# Alt-Chart Bars
ThreeLineBreakBars,
RunBars,
ImbalanceBars,
DollarBars,
VolumeBars,
TickBars,
RangeBars,
RenkoBars,
KagiBars,
PointAndFigureBars,
# Candlestick patterns
TowerTopBottom,
HaramiCross,
Tristar,
FryPanBottom,
DumplingTop,
NewPriceLines,
Doji,
Hammer,
InvertedHammer,
@@ -401,6 +485,8 @@ from ._wickra import (
QuotedSpread,
DepthSlope,
# Microstructure: trade flow
Pin,
TradeSignAutocorrelation,
RollMeasure,
AmihudIlliquidity,
Vpin,
@@ -408,12 +494,18 @@ from ._wickra import (
CumulativeVolumeDelta,
TradeImbalance,
# Microstructure: price impact
HasbrouckInformationShare,
EffectiveSpread,
RealizedSpread,
KylesLambda,
# Microstructure: footprint
Footprint,
# Derivatives
OpenInterestMomentum,
FundingImpliedApr,
PerpetualPremiumIndex,
OiToVolumeRatio,
EstimatedLeverageRatio,
FundingRate,
FundingRateMean,
FundingRateZScore,
@@ -476,6 +568,37 @@ from ._wickra import (
)
__all__ = [
"M2Measure",
"UpsidePotentialRatio",
"GainToPainRatio",
"CommonSenseRatio",
"KRatio",
"TailRatio",
"MartinRatio",
"BurkeRatio",
"SterlingRatio",
"AUTOCORRPGRAM",
"EVENBETTERSINE",
"BANDPASS",
"ADAPTIVECCI",
"UNIVERSALOSC",
"ADAPTIVERSI",
"CTI",
"TRENDFLEX",
"REFLEX",
"HIGHPASS",
"SAMPLEENT",
"SHANNONENT",
"ROLLINGMINMAX",
"JARQUEBERA",
"TimeBasedStop",
"ProjectionOscillator",
"VolatilityCone",
"VolatilityRatio",
"BipowerVariation",
"VolatilityOfVolatility",
"Garch11",
"EwmaVolatility",
"PpoHistogram",
"MacdHistogram",
"TsfOscillator",
@@ -613,6 +736,11 @@ __all__ = [
"HistoricalVolatility",
"BollingerBandwidth",
"PercentB",
# Trailing Stops
"ModifiedMaStop",
"Nrtr",
"AtrRatchet",
"ElderSafeZone",
"SuperTrend",
"ChandelierExit",
"ChandeKrollStop",
@@ -624,6 +752,7 @@ __all__ = [
"PercentageTrailingStop",
"StepTrailingStop",
"RenkoTrailingStop",
"KaseDevStop",
"TrueRange",
"ChaikinVolatility",
"RVIVolatility",
@@ -632,6 +761,13 @@ __all__ = [
"RogersSatchellVolatility",
"YangZhangVolatility",
# Volume
"VolumeWeightedMacd",
"BetterVolume",
"IntradayIntensity",
"TradeVolumeIndex",
"TwiggsMoneyFlow",
"Wad",
"VolumeRsi",
"OBV",
"VWAP",
"RollingVWAP",
@@ -652,6 +788,7 @@ __all__ = [
"MarketFacilitationIndex",
"EaseOfMovement",
# Statistics
"KendallTau",
"SpreadBollingerBands",
"KalmanHedgeRatio",
"GrangerCausality",
@@ -709,6 +846,10 @@ __all__ = [
"MAMA",
"FAMA",
# Bands & Channels
"ProjectionBands",
"MedianChannel",
"BomarBands",
"QuartileBands",
"MaEnvelope",
"AccelerationBands",
"StarcBands",
@@ -721,6 +862,11 @@ __all__ = [
"FractalChaosBands",
"VwapStdDevBands",
# Pivots & S/R
"PivotReversal",
"VolumeWeightedSr",
"AndrewsPitchfork",
"MurreyMathLines",
"CentralPivotRange",
"ClassicPivots",
"FibonacciPivots",
"Camarilla",
@@ -729,6 +875,13 @@ __all__ = [
"WilliamsFractals",
"ZigZag",
# DeMark
"TDMovingAverage",
"TDDWave",
"TDTrap",
"TDPropulsion",
"TDClopwin",
"TDClop",
"TDCamouflage",
"TDSetup",
"TDSequential",
"TDDeMarker",
@@ -744,17 +897,40 @@ __all__ = [
# Ichimoku & alternative charts
"Ichimoku",
"HeikinAshi",
"SmoothedHeikinAshi",
"HeikinAshiOscillator",
"ThreeLineBreak",
"Equivolume",
"CandleVolume",
# Market Profile
"CompositeProfile",
"HighLowVolumeNodes",
"ProfileShape",
"SinglePrints",
"NakedPoc",
"ValueArea",
"VolumeProfile",
"TpoProfile",
"InitialBalance",
"OpeningRange",
# Alt-Chart Bars
"ThreeLineBreakBars",
"RunBars",
"ImbalanceBars",
"DollarBars",
"VolumeBars",
"TickBars",
"RangeBars",
"RenkoBars",
"KagiBars",
"PointAndFigureBars",
# Candlestick patterns
"TowerTopBottom",
"HaramiCross",
"Tristar",
"FryPanBottom",
"DumplingTop",
"NewPriceLines",
"Doji",
"Hammer",
"InvertedHammer",
@@ -853,6 +1029,8 @@ __all__ = [
"QuotedSpread",
"DepthSlope",
# Microstructure: trade flow
"Pin",
"TradeSignAutocorrelation",
"RollMeasure",
"AmihudIlliquidity",
"Vpin",
@@ -860,12 +1038,18 @@ __all__ = [
"CumulativeVolumeDelta",
"TradeImbalance",
# Microstructure: price impact
"HasbrouckInformationShare",
"EffectiveSpread",
"RealizedSpread",
"KylesLambda",
# Microstructure: footprint
"Footprint",
# Derivatives
"OpenInterestMomentum",
"FundingImpliedApr",
"PerpetualPremiumIndex",
"OiToVolumeRatio",
"EstimatedLeverageRatio",
"FundingRate",
"FundingRateMean",
"FundingRateZScore",
+5482 -146
View File
File diff suppressed because it is too large Load Diff
+668 -1
View File
@@ -45,6 +45,32 @@ def ohlcv() -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
# --- Scalar (f64 -> f64) indicators ---------------------------------------
SCALAR = [
(ta.M2Measure, (20, 0.0, 0.02)),
(ta.UpsidePotentialRatio, (20, 0.0)),
(ta.GainToPainRatio, (12,)),
(ta.CommonSenseRatio, (20,)),
(ta.KRatio, (30,)),
(ta.TailRatio, (20,)),
(ta.MartinRatio, (14,)),
(ta.BurkeRatio, (12,)),
(ta.SterlingRatio, (12,)),
(ta.AUTOCORRPGRAM, (10, 48)),
(ta.EVENBETTERSINE, (40, 10)),
(ta.BANDPASS, (20, 0.3)),
(ta.UNIVERSALOSC, (20,)),
(ta.ADAPTIVERSI, (14,)),
(ta.CTI, (20,)),
(ta.TRENDFLEX, (20,)),
(ta.REFLEX, (20,)),
(ta.HIGHPASS, (48,)),
(ta.SAMPLEENT, (20, 2, 0.2)),
(ta.SHANNONENT, (20, 8)),
(ta.ROLLINGMINMAX, (20,)),
(ta.JARQUEBERA, (20,)),
(ta.BipowerVariation, (20,)),
(ta.VolatilityOfVolatility, (20, 20)),
(ta.Garch11, (0.000002, 0.1, 0.88)),
(ta.EwmaVolatility, (0.94,)),
(ta.PpoHistogram, (3, 6, 3)),
(ta.MacdHistogram, (3, 6, 3)),
(ta.TsfOscillator, (3,)),
@@ -169,6 +195,9 @@ SCALAR = [
# Family 05 band/channel indicators with scalar input and multi-output.
# `cols` is the expected number of band columns from `batch`.
SCALAR_MULTI = {
"MedianChannel": (lambda: ta.MedianChannel(5, 2.0), 3),
"BomarBands": (lambda: ta.BomarBands(4, 0.85), 3),
"QuartileBands": (lambda: ta.QuartileBands(4), 3),
"Qqe": (lambda: ta.QQE(14, 5, 4.236), 2),
"MaEnvelope": (lambda: ta.MaEnvelope(20, 0.025), 3),
"LinRegChannel": (lambda: ta.LinRegChannel(20, 2.0), 3),
@@ -197,6 +226,8 @@ def test_scalar_streaming_matches_batch(cls, args, sine_prices):
# --- Two-series (asset, benchmark) indicators -----------------------------
PAIR = [
(ta.HasbrouckInformationShare, (2,)),
(ta.KendallTau, (20,)),
(ta.SpreadAr1Coefficient, (40,)),
(ta.GrangerCausality, (60, 1)),
(ta.VarianceRatio, (60, 2)),
@@ -361,6 +392,106 @@ def test_relative_strength_streaming_matches_batch():
# 6-tuple candle; the batch helper takes only the columns it needs.
CANDLE_SCALAR = {
"ProfileShape": (
lambda: ta.ProfileShape(20, 24),
lambda ind, h, l, c, v: ind.batch(h, l, v),
),
"SinglePrints": (
lambda: ta.SinglePrints(20, 24),
lambda ind, h, l, c, v: ind.batch(h, l),
),
"NakedPoc": (
lambda: ta.NakedPoc(20, 24),
lambda ind, h, l, c, v: ind.batch(h, l, c, v),
),
"FryPanBottom": (
lambda: ta.FryPanBottom(9),
lambda ind, h, l, c, v: ind.batch(h, l, c),
),
"NewPriceLines": (
lambda: ta.NewPriceLines(5),
lambda ind, h, l, c, v: ind.batch(h, l, c),
),
"DumplingTop": (
lambda: ta.DumplingTop(9),
lambda ind, h, l, c, v: ind.batch(h, l, c),
),
"TowerTopBottom": (
lambda: ta.TowerTopBottom(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"HaramiCross": (
lambda: ta.HaramiCross(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"Tristar": (
lambda: ta.Tristar(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"ThreeLineBreak": (
lambda: ta.ThreeLineBreak(3),
lambda ind, h, l, c, v: ind.batch(h, l, c),
),
"HeikinAshiOscillator": (
lambda: ta.HeikinAshiOscillator(5),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"TDDWave": (
lambda: ta.TDDWave(2),
lambda ind, h, l, c, v: ind.batch(h, l, c),
),
"TDTrap": (
lambda: ta.TDTrap(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"TDPropulsion": (
lambda: ta.TDPropulsion(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"TDClopwin": (
lambda: ta.TDClopwin(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"TDClop": (
lambda: ta.TDClop(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"TDCamouflage": (
lambda: ta.TDCamouflage(),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
),
"PivotReversal": (
lambda: ta.PivotReversal(1, 1),
lambda ind, h, l, c, v: ind.batch(h, l, c),
),
"ADAPTIVECCI": (lambda: ta.ADAPTIVECCI(20), lambda ind, h, l, c, v: ind.batch(h, l, c)),
"BetterVolume": (
lambda: ta.BetterVolume(14),
lambda ind, h, l, c, v: ind.batch(h, l, c, v),
),
"IntradayIntensity": (
lambda: ta.IntradayIntensity(),
lambda ind, h, l, c, v: ind.batch(h, l, c, v),
),
"TradeVolumeIndex": (
lambda: ta.TradeVolumeIndex(0.25),
lambda ind, h, l, c, v: ind.batch(c, v),
),
"TwiggsMoneyFlow": (
lambda: ta.TwiggsMoneyFlow(21),
lambda ind, h, l, c, v: ind.batch(h, l, c, v),
),
"Wad": (
lambda: ta.Wad(),
lambda ind, h, l, c, v: ind.batch(h, l, c),
),
"VolumeRsi": (
lambda: ta.VolumeRsi(14),
lambda ind, h, l, c, v: ind.batch(c, v),
),
"TimeBasedStop": (lambda: ta.TimeBasedStop(5), lambda ind, h, l, c, v: ind.batch(h, l, c)),
"ProjectionOscillator": (lambda: ta.ProjectionOscillator(14), lambda ind, h, l, c, v: ind.batch(h, l, c)),
"VolatilityRatio": (lambda: ta.VolatilityRatio(14), lambda ind, h, l, c, v: ind.batch(h, l, c)),
"TTM_TREND": (lambda: ta.TTM_TREND(6), lambda ind, h, l, c, v: ind.batch(h, l, c)),
"StochasticCCI": (lambda: ta.StochasticCCI(14), lambda ind, h, l, c, v: ind.batch(h, l, c)),
# Per-bar OHLC transforms (open matters). The streaming harness feeds
@@ -899,6 +1030,96 @@ def test_candle_scalar_streaming_matches_batch(name, ohlcv):
# --- Candle-input, multi-output indicators --------------------------------
MULTI = {
"CompositeProfile": (
lambda: ta.CompositeProfile(20, 24, 0.7),
lambda ind, h, l, c, v: ind.batch(h, l, v),
3,
),
"HighLowVolumeNodes": (
lambda: ta.HighLowVolumeNodes(20, 24),
lambda ind, h, l, c, v: ind.batch(h, l, v),
2,
),
"CandleVolume": (
lambda: ta.CandleVolume(20),
lambda ind, h, l, c, v: ind.batch(c, c, v),
2,
),
"Equivolume": (
lambda: ta.Equivolume(20),
lambda ind, h, l, c, v: ind.batch(h, l, v),
2,
),
"SmoothedHeikinAshi": (
lambda: ta.SmoothedHeikinAshi(5),
lambda ind, h, l, c, v: ind.batch(c, h, l, c),
4,
),
"TDMovingAverage": (
lambda: ta.TDMovingAverage(5, 13),
lambda ind, h, l, c, v: ind.batch(h, l),
2,
),
"VolumeWeightedSr": (
lambda: ta.VolumeWeightedSr(3),
lambda ind, h, l, c, v: ind.batch(h, l, v),
2,
),
"AndrewsPitchfork": (
lambda: ta.AndrewsPitchfork(2),
lambda ind, h, l, c, v: ind.batch(h, l),
3,
),
"MurreyMathLines": (
lambda: ta.MurreyMathLines(4),
lambda ind, h, l, c, v: ind.batch(h, l),
9,
),
"CentralPivotRange": (
lambda: ta.CentralPivotRange(),
lambda ind, h, l, c, v: ind.batch(h, l, c),
3,
),
"VolumeWeightedMacd": (
lambda: ta.VolumeWeightedMacd(12, 26, 9),
lambda ind, h, l, c, v: ind.batch(c, v),
3,
),
"ModifiedMaStop": (
lambda: ta.ModifiedMaStop(14),
lambda ind, h, l, c, v: ind.batch(h, l, c),
2,
),
"Nrtr": (
lambda: ta.Nrtr(2.0),
lambda ind, h, l, c, v: ind.batch(h, l, c),
2,
),
"AtrRatchet": (
lambda: ta.AtrRatchet(14, 4.0, 0.1),
lambda ind, h, l, c, v: ind.batch(h, l, c),
2,
),
"ElderSafeZone": (
lambda: ta.ElderSafeZone(14, 2.0),
lambda ind, h, l, c, v: ind.batch(h, l, c),
2,
),
"KaseDevStop": (
lambda: ta.KaseDevStop(3, 1.0),
lambda ind, h, l, c, v: ind.batch(h, l, c),
2,
),
"ProjectionBands": (
lambda: ta.ProjectionBands(3),
lambda ind, h, l, c, v: ind.batch(h, l),
3,
),
"VolatilityCone": (
lambda: ta.VolatilityCone(20, 60),
lambda ind, h, l, c, v: ind.batch(h, l, c),
5,
),
"KasePermissionStochastic": (
lambda: ta.KasePermissionStochastic(9, 3),
lambda ind, h, l, c, v: ind.batch(h, l, c),
@@ -1535,7 +1756,7 @@ def test_kvo_constant_series_is_zero():
assert v == pytest.approx(0.0, abs=1e-12)
def test_williams_ad_reference():
def test_wad_reference():
# bar 0 seeds prev_close = 10.
# bar 1: prev=10, today high=13, low=8, close=12 (up day).
# TR_l = min(10, 8) = 8 -> delta = 12 - 8 = 4. AD = 4.
@@ -2890,6 +3111,277 @@ def test_ppo_histogram_reference():
assert t.update(100.0 + i * 2.0) is None
assert t.update(100.0 + 7 * 2.0) == pytest.approx(-0.052098, abs=1e-6)
def test_ewma_volatility_reference():
t = ta.EwmaVolatility(0.94)
assert t.update(100.0) is None
assert t.update(110.0) == pytest.approx(0.09531017980432493)
assert t.update(99.0) == pytest.approx(0.0959428936787596)
def test_garch11_reference():
t = ta.Garch11(0.000002, 0.1, 0.88)
assert t.update(100.0) is None
assert t.update(110.0) == pytest.approx(0.009999999999999995)
assert t.update(99.0) == pytest.approx(0.031597516317477786)
def test_volatility_cone_reference():
t = ta.VolatilityCone(20, 60)
def test_quartile_bands_reference():
t = ta.QuartileBands(4)
assert t.update(40.0) is None
assert t.update(30.0) is None
assert t.update(20.0) is None
assert t.update(10.0) == pytest.approx((32.5, 25.0, 17.5))
def test_bomar_bands_reference():
t = ta.BomarBands(4, 0.85)
assert t.update(100.0) is None
assert t.update(102.0) is None
assert t.update(98.0) is None
assert t.update(104.0) == pytest.approx((104.0, 101.0, 98.0))
def test_median_channel_reference():
t = ta.MedianChannel(5, 2.0)
assert t.update(1.0) is None
assert t.update(2.0) is None
assert t.update(3.0) is None
assert t.update(4.0) is None
assert t.update(5.0) == pytest.approx((5.0, 3.0, 1.0))
def test_projection_bands_reference():
t = ta.ProjectionBands(3)
assert t.update((8.0, 10.0, 8.0, 9.0, 1.0, 0)) is None
assert t.update((9.0, 12.0, 9.0, 11.0, 1.0, 1)) is None
assert t.update((10.0, 11.0, 10.0, 11.0, 1.0, 2)) == pytest.approx((12.5, 11.25, 10.0))
def test_projection_oscillator_reference():
# Same window as ProjectionBands: upper 12.5, lower 10; close 11 -> 40.
t = ta.ProjectionOscillator(3)
assert t.update((8.0, 10.0, 8.0, 9.0, 1.0, 0)) is None
assert t.update((9.0, 12.0, 9.0, 11.0, 1.0, 1)) is None
assert t.update((10.0, 11.0, 10.0, 11.0, 1.0, 2)) == pytest.approx(40.0)
def test_kase_devstop_reference():
t = ta.KaseDevStop(3, 1.0)
assert t.update((100.0, 101.0, 99.0, 100.0, 1.0, 0)) is None
assert t.update((101.0, 102.0, 100.0, 101.0, 1.0, 1)) is None
assert t.update((102.0, 103.0, 101.0, 102.0, 1.0, 2)) is None
assert t.update((102.5, 104.0, 102.0, 103.0, 1.0, 3)) == pytest.approx((101.0, 1.0))
def _stop_candles(n):
# Gently rising, valid OHLC: high >= open/close, low <= open/close.
return [(100.0 + i, 101.5 + i, 98.5 + i, 100.5 + i, 1.0, i) for i in range(n)]
def test_elder_safezone_reference():
t = ta.ElderSafeZone(14, 2.0)
candles = _stop_candles(15)
for c in candles[:14]:
assert t.update(c) is None
assert t.update(candles[14]) == pytest.approx((112.5, 1.0))
def test_atr_ratchet_reference():
t = ta.AtrRatchet(14, 4.0, 0.1)
candles = _stop_candles(14)
for c in candles[:13]:
assert t.update(c) is None
assert t.update(candles[13]) == pytest.approx((101.5, 1.0))
def test_nrtr_reference():
t = ta.Nrtr(2.0)
assert t.update((100.0, 100.0, 100.0, 100.0, 1.0, 0)) == pytest.approx((98.0, 1.0))
def test_time_based_stop_reference():
t = ta.TimeBasedStop(5)
assert t.update((100.0, 101.0, 99.0, 100.0, 1.0, 0)) == pytest.approx(0.2)
def test_modified_ma_stop_reference():
t = ta.ModifiedMaStop(14)
candles = _stop_candles(14)
for c in candles[:13]:
assert t.update(c) is None
assert t.update(candles[13]) == pytest.approx((107.0, 1.0))
def test_volume_rsi_reference():
t = ta.VolumeRsi(14)
def test_twiggs_money_flow_reference():
t = ta.TwiggsMoneyFlow(21)
def test_trade_volume_index_reference():
t = ta.TradeVolumeIndex(0.25)
def test_intraday_intensity_reference():
t = ta.IntradayIntensity()
def test_better_volume_reference():
t = ta.BetterVolume(14)
def test_volume_weighted_macd_reference():
t = ta.VolumeWeightedMacd(12, 26, 9)
def test_kendall_tau_reference():
t = ta.KendallTau(20)
def test_central_pivot_range_reference():
t = ta.CentralPivotRange()
assert t.update((105.0, 110.0, 90.0, 105.0, 1.0, 0)) == pytest.approx((101.66666666666667, 103.33333333333334, 100.0))
def test_murrey_math_lines_reference():
t = ta.MurreyMathLines(4)
assert t.update((140.0, 180.0, 100.0, 140.0, 1.0, 0)) is None
assert t.update((140.0, 180.0, 100.0, 140.0, 1.0, 1)) is None
assert t.update((140.0, 180.0, 100.0, 140.0, 1.0, 2)) is None
assert t.update((140.0, 180.0, 100.0, 140.0, 1.0, 3)) == pytest.approx((180.0, 170.0, 160.0, 150.0, 140.0, 130.0, 120.0, 110.0, 100.0))
def test_andrews_pitchfork_reference():
t = ta.AndrewsPitchfork(2)
# Warmup: no pitchfork until three alternating swing pivots are confirmed.
assert t.update((100.0, 101.0, 99.0, 100.0, 1.0, 0)) is None
def test_volume_weighted_sr_reference():
t = ta.VolumeWeightedSr(3)
assert t.update((100.0, 102.0, 98.0, 100.0, 1.0, 0)) is None
assert t.update((100.0, 104.0, 96.0, 100.0, 1.0, 1)) is None
assert t.update((100.0, 106.0, 94.0, 100.0, 1.0, 2)) == pytest.approx((96.0, 104.0))
def test_pivot_reversal_reference():
t = ta.PivotReversal(1, 1)
assert t.update((9.5, 10.0, 9.0, 9.5, 1.0, 0)) is None
assert t.update((11.5, 12.0, 11.0, 11.5, 1.0, 1)) is None
# Pivot high = 12 confirmed; close 9.5 has not crossed it.
assert t.update((9.5, 10.0, 9.0, 9.5, 1.0, 2)) == pytest.approx(0.0)
assert t.update((9.0, 11.0, 9.0, 9.0, 1.0, 3)) == pytest.approx(0.0)
# Close 13 > pivot high 12 with prev close 9 below it -> bullish reversal.
assert t.update((13.0, 14.0, 12.5, 13.0, 1.0, 4)) == pytest.approx(1.0)
def test_td_camouflage_reference():
t = ta.TDCamouflage()
assert t.update((10.0, 11.0, 8.0, 10.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((9.0, 10.0, 7.0, 9.5, 1.0, 1)) == pytest.approx(1.0)
def test_td_clop_reference():
t = ta.TDClop()
assert t.update((10.0, 12.0, 9.0, 11.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((9.0, 13.0, 8.0, 12.0, 1.0, 1)) == pytest.approx(1.0)
def test_td_clopwin_reference():
t = ta.TDClopwin()
assert t.update((10.0, 15.0, 9.0, 14.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((11.0, 14.0, 10.0, 13.0, 1.0, 1)) == pytest.approx(1.0)
def test_td_propulsion_reference():
t = ta.TDPropulsion()
assert t.update((9.5, 11.0, 9.0, 10.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((10.5, 12.0, 10.0, 11.5, 1.0, 1)) == pytest.approx(1.0)
def test_td_trap_reference():
t = ta.TDTrap()
assert t.update((100.0, 110.0, 90.0, 100.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((101.5, 108.0, 95.0, 102.0, 1.0, 1)) == pytest.approx(0.0)
assert t.update((106.0, 112.0, 100.0, 109.0, 1.0, 2)) == pytest.approx(1.0)
def test_heikin_ashi_oscillator_reference():
t = ta.HeikinAshiOscillator(5)
def test_three_line_break_reference():
t = ta.ThreeLineBreak(3)
def test_smoothed_heikin_ashi_reference():
t = ta.SmoothedHeikinAshi(5)
def test_equivolume_reference():
t = ta.Equivolume(20)
def test_candle_volume_reference():
t = ta.CandleVolume(20)
def test_tristar_reference():
t = ta.Tristar()
assert t.update((100.0, 101.0, 99.0, 100.02, 1.0, 0)) == pytest.approx(0.0)
assert t.update((105.0, 106.0, 104.0, 105.02, 1.0, 1)) == pytest.approx(0.0)
assert t.update((100.0, 101.0, 99.0, 100.02, 1.0, 2)) == pytest.approx(-1.0)
def test_harami_cross_reference():
t = ta.HaramiCross()
assert t.update((110.0, 110.2, 99.8, 100.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((105.0, 106.0, 104.0, 105.02, 1.0, 1)) == pytest.approx(1.0)
def test_tower_top_bottom_reference():
t = ta.TowerTopBottom()
assert t.update((100.0, 110.1, 99.9, 110.0, 1.0, 0)) == pytest.approx(0.0)
assert t.update((105.0, 107.0, 103.0, 105.1, 1.0, 1)) == pytest.approx(0.0)
assert t.update((110.0, 110.1, 99.9, 100.0, 1.0, 2)) == pytest.approx(-1.0)
def test_hasbrouck_information_share_reference():
t = ta.HasbrouckInformationShare(2)
assert t.update(7.0, 9.0) is None
assert t.update(7.0, 9.0) is None
assert t.update(7.0, 9.0) == pytest.approx(0.5)
def test_naked_poc_reference():
t = ta.NakedPoc(20, 24)
def test_single_prints_reference():
t = ta.SinglePrints(20, 24)
def test_profile_shape_reference():
t = ta.ProfileShape(20, 24)
def test_high_low_volume_nodes_reference():
t = ta.HighLowVolumeNodes(20, 24)
def test_composite_profile_reference():
t = ta.CompositeProfile(20, 24, 0.7)
# --- Lifecycle ------------------------------------------------------------
@@ -3230,6 +3722,8 @@ def test_tradeflow_indicators_streaming_equals_batch():
lambda: ta.Vpin(8.0, 5),
lambda: ta.AmihudIlliquidity(14),
lambda: ta.RollMeasure(14),
lambda: ta.TradeSignAutocorrelation(10),
lambda: ta.Pin(10),
):
batch = make().batch(price, size, is_buy)
streamer = make()
@@ -3241,6 +3735,34 @@ def test_tradeflow_indicators_streaming_equals_batch():
assert _eq_nan(batch, streamed)
def test_trade_sign_autocorrelation_reference():
# Perfectly alternating aggressor signs -> lag-1 autocorrelation -1.
t = ta.TradeSignAutocorrelation(10)
last = None
for i in range(20):
last = t.update(100.0, 1.0, i % 2 == 0)
assert last == pytest.approx(-1.0)
# All buys -> perfectly persistent flow -> +1.
t2 = ta.TradeSignAutocorrelation(10)
for _ in range(20):
last2 = t2.update(100.0, 1.0, True)
assert last2 == pytest.approx(1.0)
def test_pin_reference():
# One-sided flow (all buys) -> maximally informed -> PIN 1.
p = ta.Pin(10)
last = None
for _ in range(20):
last = p.update(100.0, 1.0, True)
assert last == pytest.approx(1.0)
# Balanced flow -> uninformed -> PIN 0.
p2 = ta.Pin(10)
for i in range(20):
last2 = p2.update(100.0, 1.0, i % 2 == 0)
assert last2 == pytest.approx(0.0)
def test_price_impact_indicators_streaming_equals_batch():
n = 40
mid = np.array([100.0 + 0.5 * math.sin(i * 0.4) for i in range(n)], dtype=np.float64)
@@ -3611,6 +4133,71 @@ def test_basis_indicators_streaming_equals_batch():
assert _eq_nan(batch, streamed)
def test_b16_derivatives_reference():
# Estimated leverage: oi / (long + short) = 200 / 100 = 2.
assert ta.EstimatedLeverageRatio().update(200.0, 60.0, 40.0) == pytest.approx(2.0)
# OI-to-volume: oi / (buy + sell) = 100 / 50 = 2.
assert ta.OiToVolumeRatio().update(100.0, 30.0, 20.0) == pytest.approx(2.0)
# Perpetual premium: (mark - index) / index = 0.5 / 100 = 0.005.
assert ta.PerpetualPremiumIndex().update(100.5, 100.0) == pytest.approx(0.005)
# Funding-implied APR: rate * intervals = 0.0001 * 1095 = 0.1095.
assert ta.FundingImpliedApr(1095.0).update(0.0001) == pytest.approx(0.1095)
# Open-interest momentum (period 2): warmup then ROC% = 100*(120-100)/100 = 20.
oim = ta.OpenInterestMomentum(2)
assert oim.update(100.0) is None
assert oim.update(110.0) is None
assert oim.update(120.0) == pytest.approx(20.0)
def test_b16_derivatives_streaming_equals_batch():
n = 40
oi = np.array([1000.0 + 50.0 * math.sin(i * 0.3) for i in range(n)], dtype=np.float64)
long_sz = np.array([600.0 + 20.0 * math.cos(i * 0.2) for i in range(n)], dtype=np.float64)
short_sz = np.array([400.0 + 15.0 * math.sin(i * 0.4) for i in range(n)], dtype=np.float64)
buy = np.array([300.0 + 10.0 * math.sin(i * 0.5) for i in range(n)], dtype=np.float64)
sell = np.array([250.0 + 12.0 * math.cos(i * 0.35) for i in range(n)], dtype=np.float64)
index = np.array([100.0 + math.sin(i * 0.2) for i in range(n)], dtype=np.float64)
mark = np.array([index[i] + 0.05 * math.cos(i * 0.3) for i in range(n)], dtype=np.float64)
rate = np.array([0.0001 * math.sin(i * 0.3) for i in range(n)], dtype=np.float64)
# EstimatedLeverageRatio; update(open_interest, long_size, short_size).
batch = ta.EstimatedLeverageRatio().batch(oi, long_sz, short_sz)
streamer = ta.EstimatedLeverageRatio()
streamed = np.array(
[streamer.update(oi[i], long_sz[i], short_sz[i]) for i in range(n)], dtype=np.float64
)
assert batch.shape == (n,)
assert _eq_nan(batch, streamed)
# OiToVolumeRatio; update(open_interest, taker_buy_volume, taker_sell_volume).
batch = ta.OiToVolumeRatio().batch(oi, buy, sell)
streamer = ta.OiToVolumeRatio()
streamed = np.array(
[streamer.update(oi[i], buy[i], sell[i]) for i in range(n)], dtype=np.float64
)
assert _eq_nan(batch, streamed)
# PerpetualPremiumIndex; update(mark_price, index_price).
batch = ta.PerpetualPremiumIndex().batch(mark, index)
streamer = ta.PerpetualPremiumIndex()
streamed = np.array(
[streamer.update(mark[i], index[i]) for i in range(n)], dtype=np.float64
)
assert _eq_nan(batch, streamed)
# FundingImpliedApr; update(funding_rate).
batch = ta.FundingImpliedApr(1095.0).batch(rate)
streamer = ta.FundingImpliedApr(1095.0)
streamed = np.array([streamer.update(rate[i]) for i in range(n)], dtype=np.float64)
assert _eq_nan(batch, streamed)
# OpenInterestMomentum; update(open_interest).
batch = ta.OpenInterestMomentum(10).batch(oi)
streamer = ta.OpenInterestMomentum(10)
streamed = np.array([streamer.update(oi[i]) for i in range(n)], dtype=np.float64)
assert _eq_nan(batch, streamed)
# --- Alt-Chart Bars ------------------------------------------------------
@@ -3650,3 +4237,83 @@ def test_bar_builders_reset():
r.update(15.0)
r.reset()
assert r.update(50.0) == [] # re-seeds after reset
def test_range_bars_reference():
rb = ta.RangeBars(1.0)
assert rb.update(10.0) == [] # seed
assert rb.update(13.0) == [(10.0, 11.0, 1), (11.0, 12.0, 1), (12.0, 13.0, 1)]
def test_range_bars_batch_shape():
rb = ta.RangeBars(1.0)
out = rb.batch(np.array([10.0, 11.0, 12.0, 13.0]))
assert out.shape == (3, 3)
np.testing.assert_allclose(out[:, 2], [1.0, 1.0, 1.0])
def test_tick_bars_reference():
tb = ta.TickBars(2)
assert tb.update(10.0, 11.0, 9.0, 10.5, 100.0) == []
out = tb.update(10.5, 12.0, 10.0, 11.0, 150.0)
assert len(out) == 1
assert out[0] == (10.0, 12.0, 9.0, 11.0, 250.0)
def test_tick_bars_batch_shape():
tb = ta.TickBars(2)
col = np.array([10.0, 10.0, 10.0, 10.0])
vol = np.array([1.0, 1.0, 1.0, 1.0])
out = tb.batch(col, col, col, col, vol)
assert out.shape == (2, 5)
def test_volume_bars_reference():
vb = ta.VolumeBars(100.0)
assert vb.update(10.0, 10.0, 10.0, 10.0, 60.0) == []
out = vb.update(10.5, 10.5, 10.5, 10.5, 60.0)
assert len(out) == 1
assert out[0][4] == 120.0 # accumulated volume
def test_dollar_bars_reference():
db = ta.DollarBars(1000.0)
assert db.update(10.0, 10.0, 10.0, 10.0, 60.0) == [] # 600
out = db.update(10.0, 10.0, 10.0, 10.0, 60.0) # 1200 >= 1000
assert len(out) == 1
assert out[0][4] == 120.0 # volume
assert out[0][5] == 1200.0 # traded value
def test_imbalance_bars_reference():
ib = ta.ImbalanceBars(3.0)
assert ib.update(10.0, 10.0, 10.0, 10.0) == [] # seed
ib.update(11.0, 11.0, 11.0, 11.0) # +1
ib.update(12.0, 12.0, 12.0, 12.0) # +2
out = ib.update(13.0, 13.0, 13.0, 13.0) # +3 -> close
assert len(out) == 1
assert out[0][4] == 3.0 # imbalance
assert out[0][5] == 1 # direction
def test_run_bars_reference():
rb = ta.RunBars(3)
assert rb.update(10.0, 10.0, 10.0, 10.0) == [] # seed
rb.update(11.0, 11.0, 11.0, 11.0) # run 1
rb.update(12.0, 12.0, 12.0, 12.0) # run 2
out = rb.update(13.0, 13.0, 13.0, 13.0) # run 3 -> close
assert len(out) == 1
assert out[0][4] == 3 # length
assert out[0][5] == 1 # direction
def test_three_line_break_bars_reference():
tlb = ta.ThreeLineBreakBars(3)
assert tlb.update(10.0) == [] # seed
assert tlb.update(11.0) == [(10.0, 11.0, 1)]
def test_three_line_break_bars_batch_shape():
tlb = ta.ThreeLineBreakBars(3)
out = tlb.batch(np.array([10.0, 11.0, 12.0, 13.0]))
assert out.shape[1] == 3
+8
View File
@@ -0,0 +1,8 @@
^src/wickra_abi\.dll$
^src/wickra_abi\.def$
^src/libwickra_abi\.dll\.a$
^src/.*\.o$
^src/wickra\.dll$
^src/wickra\.so$
^src/symbols\.rds$
^\.gitignore$
+20
View File
@@ -0,0 +1,20 @@
Package: wickra
Type: Package
Title: Streaming-First Technical Indicators
Version: 0.7.8
Authors@R: person("Wickra contributors", role = c("aut", "cre"), email = "support@wickra.org")
Description: R bindings for the Wickra technical-analysis library over its C ABI
hub. Exposes 514 indicators, each an O(1) streaming state machine shared with
the Rust core and the other language bindings, so that live and historical
evaluation use the exact same implementation.
License: MIT + file LICENSE | Apache License 2.0
URL: https://github.com/wickra-lib/wickra, https://docs.wickra.org
BugReports: https://github.com/wickra-lib/wickra/issues
Encoding: UTF-8
NeedsCompilation: yes
SystemRequirements: the Wickra C ABI library (libwickra); set WICKRA_INCLUDE_DIR
and WICKRA_LIB_DIR when installing from source.
Roxygen: list(markdown = TRUE)
Suggests: testthat (>= 3.0.0)
Config/testthat/edition: 3
Config/roxygen2/version: 8.0.0
+2
View File
@@ -0,0 +1,2 @@
YEAR: 2026
COPYRIGHT HOLDER: Wickra contributors
+522
View File
@@ -0,0 +1,522 @@
# Generated by roxygen2: do not edit by hand
S3method(batch,wickra_indicator)
S3method(reset,wickra_indicator)
S3method(update,wickra_indicator)
export(AbandonedBaby)
export(Abcd)
export(AbsoluteBreadthIndex)
export(AccelerationBands)
export(AcceleratorOscillator)
export(AdOscillator)
export(AdVolumeLine)
export(AdaptiveCci)
export(AdaptiveCycle)
export(AdaptiveLaguerreFilter)
export(AdaptiveRsi)
export(Adl)
export(AdvanceBlock)
export(AdvanceDecline)
export(AdvanceDeclineRatio)
export(Adx)
export(Adxr)
export(Alligator)
export(Alma)
export(Alpha)
export(AmihudIlliquidity)
export(AnchoredRsi)
export(AnchoredVwap)
export(AndrewsPitchfork)
export(Apo)
export(Aroon)
export(AroonOscillator)
export(Atr)
export(AtrBands)
export(AtrRatchet)
export(AtrTrailingStop)
export(AutoFib)
export(Autocorrelation)
export(AutocorrelationPeriodogram)
export(AverageDailyRange)
export(AverageDrawdown)
export(AvgPrice)
export(AwesomeOscillator)
export(AwesomeOscillatorHistogram)
export(BalanceOfPower)
export(BandpassFilter)
export(Bat)
export(BeltHold)
export(Beta)
export(BetaNeutralSpread)
export(BetterVolume)
export(BipowerVariation)
export(BodySizePct)
export(BollingerBands)
export(BollingerBandwidth)
export(BomarBands)
export(BreadthThrust)
export(Breakaway)
export(BullishPercentIndex)
export(BurkeRatio)
export(Butterfly)
export(CalendarSpread)
export(CalmarRatio)
export(Camarilla)
export(CandleVolume)
export(Cci)
export(CenterOfGravity)
export(CentralPivotRange)
export(Cfo)
export(ChaikinMoneyFlow)
export(ChaikinOscillator)
export(ChaikinVolatility)
export(ChandeKrollStop)
export(ChandelierExit)
export(ChoppinessIndex)
export(ClassicPivots)
export(CloseVsOpen)
export(ClosingMarubozu)
export(Cmo)
export(CoefficientOfVariation)
export(Cointegration)
export(CommonSenseRatio)
export(CompositeProfile)
export(ConcealingBabySwallow)
export(ConditionalValueAtRisk)
export(ConnorsRsi)
export(Coppock)
export(CorrelationTrendIndicator)
export(Counterattack)
export(Crab)
export(CumulativeVolumeDelta)
export(CumulativeVolumeIndex)
export(CupAndHandle)
export(CyberneticCycle)
export(Cypher)
export(DayOfWeekProfile)
export(Decycler)
export(DecyclerOscillator)
export(Dema)
export(DemandIndex)
export(DemarkPivots)
export(DepthSlope)
export(DerivativeOscillator)
export(DetrendedStdDev)
export(DisparityIndex)
export(DistanceSsd)
export(Doji)
export(DojiStar)
export(DollarBars)
export(Donchian)
export(DonchianStop)
export(DoubleBollinger)
export(DoubleTopBottom)
export(DownsideGapThreeMethods)
export(Dpo)
export(DragonflyDoji)
export(DrawdownDuration)
export(DumplingTop)
export(Dx)
export(DynamicMomentumIndex)
export(EaseOfMovement)
export(EffectiveSpread)
export(EhlersStochastic)
export(Ehma)
export(ElderImpulse)
export(ElderRay)
export(ElderSafeZone)
export(Ema)
export(EmpiricalModeDecomposition)
export(Engulfing)
export(Equivolume)
export(EstimatedLeverageRatio)
export(EvenBetterSinewave)
export(EveningDojiStar)
export(Evwma)
export(EwmaVolatility)
export(Expectancy)
export(FallingThreeMethods)
export(Fama)
export(FibArcs)
export(FibChannel)
export(FibConfluence)
export(FibExtension)
export(FibFan)
export(FibProjection)
export(FibRetracement)
export(FibTimeZones)
export(FibonacciPivots)
export(FisherRsi)
export(FisherTransform)
export(FlagPennant)
export(Footprint)
export(ForceIndex)
export(FractalChaosBands)
export(Frama)
export(FryPanBottom)
export(FundingBasis)
export(FundingImpliedApr)
export(FundingRate)
export(FundingRateMean)
export(FundingRateZScore)
export(GainLossRatio)
export(GainToPainRatio)
export(GapSideBySideWhite)
export(Garch11)
export(GarmanKlassVolatility)
export(Gartley)
export(GatorOscillator)
export(GeneralizedDema)
export(GeometricMa)
export(GoldenPocket)
export(GrangerCausality)
export(GravestoneDoji)
export(Hammer)
export(HangingMan)
export(Harami)
export(HaramiCross)
export(HasbrouckInformationShare)
export(HeadAndShoulders)
export(HeikinAshi)
export(HeikinAshiOscillator)
export(HiLoActivator)
export(HighLowIndex)
export(HighLowRange)
export(HighLowVolumeNodes)
export(HighWave)
export(HighpassFilter)
export(Hikkake)
export(HikkakeModified)
export(HilbertDominantCycle)
export(HistoricalVolatility)
export(Hma)
export(HoltWinters)
export(HomingPigeon)
export(HtDcPhase)
export(HtPhasor)
export(HtTrendMode)
export(HurstChannel)
export(HurstExponent)
export(Ichimoku)
export(IdenticalThreeCrows)
export(ImbalanceBars)
export(InNeck)
export(Inertia)
export(InformationRatio)
export(InitialBalance)
export(InstantaneousTrendline)
export(IntradayIntensity)
export(IntradayMomentumIndex)
export(IntradayVolatilityProfile)
export(InverseFisherTransform)
export(InvertedHammer)
export(JarqueBera)
export(Jma)
export(JumpIndicator)
export(KRatio)
export(KagiBars)
export(KalmanHedgeRatio)
export(Kama)
export(KaseDevStop)
export(KasePermissionStochastic)
export(KellyCriterion)
export(Keltner)
export(KendallTau)
export(Kicking)
export(KickingByLength)
export(Kst)
export(Kurtosis)
export(Kvo)
export(KylesLambda)
export(LadderBottom)
export(LaguerreRsi)
export(LeadLagCrossCorrelation)
export(LinRegAngle)
export(LinRegChannel)
export(LinRegIntercept)
export(LinRegSlope)
export(LinearRegression)
export(LiquidationFeatures)
export(LogReturn)
export(LongLeggedDoji)
export(LongLine)
export(LongShortRatio)
export(M2Measure)
export(MaEnvelope)
export(MacdExt)
export(MacdFix)
export(MacdHistogram)
export(MacdIndicator)
export(Mama)
export(MarketFacilitationIndex)
export(MartinRatio)
export(Marubozu)
export(MassIndex)
export(MatHold)
export(MatchingLow)
export(MaxDrawdown)
export(McClellanOscillator)
export(McClellanSummationIndex)
export(McGinleyDynamic)
export(MedianAbsoluteDeviation)
export(MedianChannel)
export(MedianMa)
export(MedianPrice)
export(Mfi)
export(Microprice)
export(MidPoint)
export(MidPrice)
export(MinusDi)
export(MinusDm)
export(ModifiedMaStop)
export(Mom)
export(MorningDojiStar)
export(MorningEveningStar)
export(MurreyMathLines)
export(NakedPoc)
export(Natr)
export(NewHighsNewLows)
export(NewPriceLines)
export(Nrtr)
export(Nvi)
export(OIPriceDivergence)
export(OIWeighted)
export(Obv)
export(OiToVolumeRatio)
export(OmegaRatio)
export(OnNeck)
export(OpenInterestDelta)
export(OpenInterestMomentum)
export(OpeningMarubozu)
export(OpeningRange)
export(OrderBookImbalanceFull)
export(OrderBookImbalanceTop1)
export(OrderBookImbalanceTopN)
export(OrderFlowImbalance)
export(OuHalfLife)
export(OvernightGap)
export(OvernightIntradayReturn)
export(PainIndex)
export(PairSpreadZScore)
export(PairwiseBeta)
export(ParkinsonVolatility)
export(PearsonCorrelation)
export(PercentAboveMa)
export(PercentB)
export(PercentageTrailingStop)
export(PerpetualPremiumIndex)
export(Pgo)
export(PiercingDarkCloud)
export(Pin)
export(PivotReversal)
export(PlusDi)
export(PlusDm)
export(Pmo)
export(PointAndFigureBars)
export(PolarizedFractalEfficiency)
export(Ppo)
export(PpoHistogram)
export(ProfileShape)
export(ProfitFactor)
export(ProjectionBands)
export(ProjectionOscillator)
export(Psar)
export(Pvi)
export(Qqe)
export(Qstick)
export(QuartileBands)
export(QuotedSpread)
export(RSquared)
export(RangeBars)
export(RealizedSpread)
export(RealizedVolatility)
export(RecoveryFactor)
export(RectangleRange)
export(Reflex)
export(RegimeLabel)
export(RelativeStrengthAB)
export(RenkoBars)
export(RenkoTrailingStop)
export(RickshawMan)
export(RisingThreeMethods)
export(Rmi)
export(Roc)
export(Rocp)
export(Rocr)
export(Rocr100)
export(RogersSatchellVolatility)
export(RollMeasure)
export(RollingCorrelation)
export(RollingCovariance)
export(RollingIqr)
export(RollingMinMaxScaler)
export(RollingPercentileRank)
export(RollingQuantile)
export(RollingVwap)
export(RoofingFilter)
export(Rsi)
export(Rsx)
export(RunBars)
export(Rvi)
export(RviVolatility)
export(Rwi)
export(SampleEntropy)
export(SarExt)
export(SeasonalZScore)
export(SeparatingLines)
export(SessionHighLow)
export(SessionRange)
export(SessionVwap)
export(ShannonEntropy)
export(Shark)
export(SharpeRatio)
export(ShootingStar)
export(ShortLine)
export(SignedVolume)
export(SineWave)
export(SineWeightedMa)
export(SinglePrints)
export(Skewness)
export(Sma)
export(Smi)
export(Smma)
export(SmoothedHeikinAshi)
export(SortinoRatio)
export(SpearmanCorrelation)
export(SpinningTop)
export(SpreadAr1Coefficient)
export(SpreadBollingerBands)
export(SpreadHurst)
export(StalledPattern)
export(StandardError)
export(StandardErrorBands)
export(StarcBands)
export(Stc)
export(StdDev)
export(StepTrailingStop)
export(SterlingRatio)
export(StickSandwich)
export(StochRsi)
export(Stochastic)
export(StochasticCci)
export(SuperSmoother)
export(SuperTrend)
export(T3)
export(TailRatio)
export(TakerBuySellRatio)
export(Takuri)
export(TasukiGap)
export(TdCamouflage)
export(TdClop)
export(TdClopwin)
export(TdCombo)
export(TdCountdown)
export(TdDWave)
export(TdDeMarker)
export(TdDifferential)
export(TdLines)
export(TdMovingAverage)
export(TdOpen)
export(TdPressure)
export(TdPropulsion)
export(TdRangeProjection)
export(TdRei)
export(TdRiskLevel)
export(TdSequential)
export(TdSetup)
export(TdTrap)
export(Tema)
export(TermStructureBasis)
export(ThreeDrives)
export(ThreeInside)
export(ThreeLineBreak)
export(ThreeLineBreakBars)
export(ThreeLineStrike)
export(ThreeOutside)
export(ThreeSoldiersOrCrows)
export(ThreeStarsInSouth)
export(Thrusting)
export(TickBars)
export(TickIndex)
export(Tii)
export(TimeBasedStop)
export(TimeOfDayReturnProfile)
export(TowerTopBottom)
export(TpoProfile)
export(TradeImbalance)
export(TradeSignAutocorrelation)
export(TradeVolumeIndex)
export(TrendLabel)
export(TrendStrengthIndex)
export(Trendflex)
export(TreynorRatio)
export(Triangle)
export(Trima)
export(Trin)
export(TripleTopBottom)
export(Tristar)
export(Trix)
export(TrueRange)
export(Tsf)
export(TsfOscillator)
export(Tsi)
export(Tsv)
export(TtmSqueeze)
export(TtmTrend)
export(TurnOfMonth)
export(Tweezer)
export(TwiggsMoneyFlow)
export(TwoCrows)
export(TypicalPrice)
export(UlcerIndex)
export(UltimateOscillator)
export(UniqueThreeRiver)
export(UniversalOscillator)
export(UpDownVolumeRatio)
export(UpsideGapThreeMethods)
export(UpsideGapTwoCrows)
export(UpsidePotentialRatio)
export(ValueArea)
export(ValueAtRisk)
export(Variance)
export(VarianceRatio)
export(VerticalHorizontalFilter)
export(Vidya)
export(VolatilityCone)
export(VolatilityOfVolatility)
export(VolatilityRatio)
export(VoltyStop)
export(VolumeBars)
export(VolumeByTimeProfile)
export(VolumeOscillator)
export(VolumePriceTrend)
export(VolumeProfile)
export(VolumeRsi)
export(VolumeWeightedMacd)
export(VolumeWeightedSr)
export(Vortex)
export(Vpin)
export(Vwap)
export(VwapStdDevBands)
export(Vwma)
export(Vzo)
export(Wad)
export(WavePm)
export(WaveTrend)
export(Wedge)
export(WeightedClose)
export(WickRatio)
export(WilliamsFractals)
export(WilliamsR)
export(WinRate)
export(Wma)
export(WoodiePivots)
export(YangZhangVolatility)
export(YoyoExit)
export(ZScore)
export(ZeroLagMacd)
export(ZigZag)
export(Zlema)
export(batch)
export(reset)
importFrom(stats,update)
File diff suppressed because it is too large Load Diff
+78
View File
@@ -0,0 +1,78 @@
#' wickra: streaming-first technical indicators
#'
#' R bindings for the Wickra technical-analysis library over its C ABI hub. Each
#' indicator is a constructor (for example [Sma()], [Rsi()], [MacdIndicator()])
#' returning a `wickra_indicator` object; feed it one observation at a time with
#' [update()], run a whole series in one call with [batch()], and clear its
#' state with [reset()]. The native handle is freed automatically when the object
#' is garbage-collected.
#'
#' @keywords internal
#' @importFrom stats update
"_PACKAGE"
#' Update an indicator with one observation
#'
#' @param object A `wickra_indicator` created by an indicator constructor.
#' @param ... The observation: a single value for scalar indicators, the OHLCV
#' fields plus a timestamp for candle indicators, or two values for pairwise
#' indicators.
#' @return The indicator value: a numeric scalar; a named numeric vector for
#' multi-output indicators (`NA` during warmup); a matrix of completed bars for
#' bar builders; or a list / numeric vector for profile indicators
#' (`NULL` during warmup).
#' @examples
#' sma <- Sma(3)
#' for (x in c(1, 2, 3, 4, 5)) v <- update(sma, x)
#' v # 4
#' @export
update.wickra_indicator <- function(object, ...) {
args <- list(object$ptr, ...)
if (!is.na(object$values_cap)) {
args <- c(args, object$values_cap)
}
do.call(".Call", c(list(paste0("wk_", object$prefix, "_update")), args,
list(PACKAGE = "wickra")))
}
#' Run an indicator over a whole series in one call
#'
#' Available for scalar indicators. The result is identical to feeding the same
#' inputs through [update()] one at a time, with `NA` at warmup positions.
#'
#' @param object A `wickra_indicator`.
#' @param ... The input vector(s).
#' @return A numeric vector the same length as the input.
#' @examples
#' batch(Sma(3), c(1, 2, 3, 4, 5)) # NA NA 2 3 4
#' @export
batch <- function(object, ...) {
UseMethod("batch")
}
#' @rdname batch
#' @export
batch.wickra_indicator <- function(object, ...) {
do.call(".Call", c(list(paste0("wk_", object$prefix, "_batch"), object$ptr),
list(...), list(PACKAGE = "wickra")))
}
#' Reset an indicator to its warmup state
#'
#' @param object A `wickra_indicator`.
#' @return The indicator, invisibly.
#' @examples
#' sma <- Sma(3)
#' update(sma, 1)
#' reset(sma)
#' @export
reset <- function(object) {
UseMethod("reset")
}
#' @rdname reset
#' @export
reset.wickra_indicator <- function(object) {
.Call(paste0("wk_", object$prefix, "_reset"), object$ptr, PACKAGE = "wickra")
invisible(object)
}
+18
View File
@@ -0,0 +1,18 @@
.onLoad <- function(libname, pkgname) {
# On Windows the package's wickra.dll depends on the bundled C ABI
# wickra_abi.dll; the loader searches PATH for it, so prepend the package's own
# libs directory. On Linux/macOS the rpath baked at build time locates the
# shared library, so no PATH change is needed.
if (.Platform$OS.type == "windows") {
libs <- system.file(paste0("libs", .Platform$r_arch),
package = pkgname, lib.loc = libname)
if (nzchar(libs)) {
Sys.setenv(PATH = paste(libs, Sys.getenv("PATH"), sep = .Platform$path.sep))
}
}
library.dynam("wickra", pkgname, libname)
}
.onUnload <- function(libpath) {
library.dynam.unload("wickra", libpath)
}
+85
View File
@@ -0,0 +1,85 @@
# Wickra — R
[![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)
[![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 R, over the Wickra C ABI hub via `.Call`.**
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, R
and any other C-capable language. Every indicator is an O(1) streaming state
machine, so live trading and historical backtests share the exact same
implementation. This package is the R binding; it reaches the C ABI hub through
R's native `.Call` interface and exposes all 514 indicators as constructors that
return a lightweight `wickra_indicator` object.
## Install
The package compiles a thin C glue layer (`.Call`) against the prebuilt Wickra
C ABI library, so a C toolchain (Rtools on Windows) is required, plus the C ABI
header and library. Build the library from the workspace, then install the
package pointing at it:
```bash
cargo build -p wickra-c --release
WICKRA_INCLUDE_DIR="$PWD/bindings/c/include" \
WICKRA_LIB_DIR="$PWD/target/release" \
R CMD INSTALL bindings/r
```
On Windows the C ABI DLL is bundled into the package and put on the load path
automatically; on Linux and macOS the library path is baked in via rpath.
## Quick start
```r
library(wickra)
# Batch: run an indicator over a whole series (NaN at warmup positions).
prices <- 100 + (0:999) * 0.1
sma <- Sma(20)
values <- batch(sma, prices)
# Streaming: the same indicator, fed one observation at a time in O(1).
rsi <- Rsi(14)
for (price in prices) {
v <- update(rsi, price) # NaN during warmup
if (!is.na(v) && v > 70) message("overbought")
}
# Multi-output indicators return a named vector (NA while warming up).
macd <- MacdIndicator(12, 26, 9)
update(macd, 42) # c(macd = NA, signal = NA, histogram = NA)
```
`batch(ind, prices)` and feeding the same prices through `update()` produce
identical values — the equivalence is enforced by the test suite. Candle-input
indicators take the OHLCV fields plus a timestamp, e.g.
`update(atr, open, high, low, close, volume, timestamp)`. The native handle is
freed automatically when the object is garbage-collected.
## 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/r/`](https://github.com/wickra-lib/wickra/tree/main/examples/r)
Wickra ships native bindings for Python, Node.js, WebAssembly and Rust, plus a
C ABI hub that any C-capable language (C, C++, C#, Go, Java, R) links against —
all exposing the same indicators from the shared, `unsafe`-forbidden Rust core.
## Disclaimer
Wickra is an indicator toolkit, not a trading system. The values it computes are
deterministic transforms of the input data — they are not financial advice and
do not predict the market. Any use in a live trading context is at your own risk.
The library is provided **as is**, without warranty of any kind.
## License
Licensed under either of [Apache-2.0](https://github.com/wickra-lib/wickra/blob/main/LICENSE-APACHE)
or [MIT](https://github.com/wickra-lib/wickra/blob/main/LICENSE-MIT) at your option.
+17
View File
@@ -0,0 +1,17 @@
#!/bin/sh
# Windows build: the package compiles to wickra.dll, which would collide with the
# C ABI's own wickra.dll (the loader would resolve the import to the package
# itself). Stage a renamed copy, wickra_abi.dll, into src/ and build a mingw
# import library that references it by that name (objdump + dlltool, both shipped
# with Rtools — no gendef/pexports needed). install.libs.R then bundles the DLL.
set -e
: "${WICKRA_LIB_DIR:?set WICKRA_LIB_DIR to the directory containing wickra.dll}"
cp "${WICKRA_LIB_DIR}/wickra.dll" src/wickra_abi.dll
{
echo 'LIBRARY wickra_abi.dll'
echo 'EXPORTS'
objdump -p src/wickra_abi.dll | awk '/\[ *[0-9]+\]/ {print $NF}' | grep '^wickra_'
} > src/wickra_abi.def
dlltool --input-def src/wickra_abi.def --dllname wickra_abi.dll \
--output-lib src/libwickra_abi.dll.a
exit 0
+12
View File
@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/indicators.R
\name{AbandonedBaby}
\alias{AbandonedBaby}
\title{AbandonedBaby indicator}
\usage{
AbandonedBaby()
}
\description{
AbandonedBaby indicator
}
\keyword{internal}
+12
View File
@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/indicators.R
\name{Abcd}
\alias{Abcd}
\title{Abcd indicator}
\usage{
Abcd()
}
\description{
Abcd indicator
}
\keyword{internal}
+12
View File
@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/indicators.R
\name{AbsoluteBreadthIndex}
\alias{AbsoluteBreadthIndex}
\title{AbsoluteBreadthIndex indicator}
\usage{
AbsoluteBreadthIndex()
}
\description{
AbsoluteBreadthIndex indicator
}
\keyword{internal}
+12
View File
@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/indicators.R
\name{AccelerationBands}
\alias{AccelerationBands}
\title{AccelerationBands indicator}
\usage{
AccelerationBands(period, factor)
}
\description{
AccelerationBands indicator
}
\keyword{internal}
+12
View File
@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/indicators.R
\name{AcceleratorOscillator}
\alias{AcceleratorOscillator}
\title{AcceleratorOscillator indicator}
\usage{
AcceleratorOscillator(ao_fast, ao_slow, signal_period)
}
\description{
AcceleratorOscillator indicator
}
\keyword{internal}
+12
View File
@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/indicators.R
\name{AdOscillator}
\alias{AdOscillator}
\title{AdOscillator indicator}
\usage{
AdOscillator()
}
\description{
AdOscillator indicator
}
\keyword{internal}
+12
View File
@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/indicators.R
\name{AdVolumeLine}
\alias{AdVolumeLine}
\title{AdVolumeLine indicator}
\usage{
AdVolumeLine()
}
\description{
AdVolumeLine indicator
}
\keyword{internal}
+12
View File
@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/indicators.R
\name{AdaptiveCci}
\alias{AdaptiveCci}
\title{AdaptiveCci indicator}
\usage{
AdaptiveCci(period)
}
\description{
AdaptiveCci indicator
}
\keyword{internal}
+12
View File
@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/indicators.R
\name{AdaptiveCycle}
\alias{AdaptiveCycle}
\title{AdaptiveCycle indicator}
\usage{
AdaptiveCycle()
}
\description{
AdaptiveCycle indicator
}
\keyword{internal}
+12
View File
@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/indicators.R
\name{AdaptiveLaguerreFilter}
\alias{AdaptiveLaguerreFilter}
\title{AdaptiveLaguerreFilter indicator}
\usage{
AdaptiveLaguerreFilter(period)
}
\description{
AdaptiveLaguerreFilter indicator
}
\keyword{internal}
+12
View File
@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/indicators.R
\name{AdaptiveRsi}
\alias{AdaptiveRsi}
\title{AdaptiveRsi indicator}
\usage{
AdaptiveRsi(period)
}
\description{
AdaptiveRsi indicator
}
\keyword{internal}
+12
View File
@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/indicators.R
\name{Adl}
\alias{Adl}
\title{Adl indicator}
\usage{
Adl()
}
\description{
Adl indicator
}
\keyword{internal}
+12
View File
@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/indicators.R
\name{AdvanceBlock}
\alias{AdvanceBlock}
\title{AdvanceBlock indicator}
\usage{
AdvanceBlock()
}
\description{
AdvanceBlock indicator
}
\keyword{internal}
+12
View File
@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/indicators.R
\name{AdvanceDecline}
\alias{AdvanceDecline}
\title{AdvanceDecline indicator}
\usage{
AdvanceDecline()
}
\description{
AdvanceDecline indicator
}
\keyword{internal}
+12
View File
@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/indicators.R
\name{AdvanceDeclineRatio}
\alias{AdvanceDeclineRatio}
\title{AdvanceDeclineRatio indicator}
\usage{
AdvanceDeclineRatio()
}
\description{
AdvanceDeclineRatio indicator
}
\keyword{internal}
+12
View File
@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/indicators.R
\name{Adx}
\alias{Adx}
\title{Adx indicator}
\usage{
Adx(period)
}
\description{
Adx indicator
}
\keyword{internal}
+12
View File
@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/indicators.R
\name{Adxr}
\alias{Adxr}
\title{Adxr indicator}
\usage{
Adxr(period)
}
\description{
Adxr indicator
}
\keyword{internal}
+12
View File
@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/indicators.R
\name{Alligator}
\alias{Alligator}
\title{Alligator indicator}
\usage{
Alligator(jaw_period, teeth_period, lips_period)
}
\description{
Alligator indicator
}
\keyword{internal}
+12
View File
@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/indicators.R
\name{Alma}
\alias{Alma}
\title{Alma indicator}
\usage{
Alma(period, offset, sigma)
}
\description{
Alma indicator
}
\keyword{internal}
+12
View File
@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/indicators.R
\name{Alpha}
\alias{Alpha}
\title{Alpha indicator}
\usage{
Alpha(period, risk_free)
}
\description{
Alpha indicator
}
\keyword{internal}
+12
View File
@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/indicators.R
\name{AmihudIlliquidity}
\alias{AmihudIlliquidity}
\title{AmihudIlliquidity indicator}
\usage{
AmihudIlliquidity(period)
}
\description{
AmihudIlliquidity indicator
}
\keyword{internal}
+12
View File
@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/indicators.R
\name{AnchoredRsi}
\alias{AnchoredRsi}
\title{AnchoredRsi indicator}
\usage{
AnchoredRsi()
}
\description{
AnchoredRsi indicator
}
\keyword{internal}
+12
View File
@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/indicators.R
\name{AnchoredVwap}
\alias{AnchoredVwap}
\title{AnchoredVwap indicator}
\usage{
AnchoredVwap()
}
\description{
AnchoredVwap indicator
}
\keyword{internal}
+12
View File
@@ -0,0 +1,12 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/indicators.R
\name{AndrewsPitchfork}
\alias{AndrewsPitchfork}
\title{AndrewsPitchfork indicator}
\usage{
AndrewsPitchfork(strength)
}
\description{
AndrewsPitchfork indicator
}
\keyword{internal}

Some files were not shown because too many files have changed in this diff Show More