From e6375746d39c6dd90e5a17d7d16d544b9753004a Mon Sep 17 00:00:00 2001 From: kingchenc Date: Sat, 23 May 2026 22:45:16 +0200 Subject: [PATCH] docs: unify README across crates.io / PyPI / npm / GitHub MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three separate README files (root, bindings/node, bindings/python) had been drifting independently — each registry showed a different project page, which is exactly the consistency debt I want to avoid. Single source of truth: /README.md. The three binding READMEs are overwritten with the root README content as a baseline, and release.yml gets a one-line cp step right before every publishing call so future edits to /README.md propagate automatically: - python-wheels job: cp README.md bindings/python/README.md before PyO3/maturin-action runs the wheel build - python-sdist job: same, before the sdist build - node-publish job: cp ../../README.md README.md (working-directory bindings/node) before the main 'npm publish wickra' - wasm-publish job: cp README.md bindings/wasm/README.md before wasm-pack build (which copies the crate README into pkg/ on its own) Cargo crates (wickra, wickra-core, wickra-data) already inherit readme.workspace = true pointing at /README.md, so crates.io was already correct — no change needed there. The per-platform npm subpackages (bindings/node/npm//) keep their tiny package.json with no README; they are install-time optionalDependencies that the loader reads through, never user-facing on the registry. Effect: same README on github.com/kingchenc/wickra, crates.io/crates/wickra, pypi.org/project/wickra, and npmjs.com/package/wickra. Will be live on the registries with the next tag-push. --- .github/workflows/release.yml | 19 ++ bindings/node/README.md | 315 +++++++++++++++++++++++++++++--- bindings/python/README.md | 330 +++++++++++++++++++++++++++++----- bindings/wasm/README.md | 317 ++++++++++++++++++++++++++++---- 4 files changed, 879 insertions(+), 102 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7a56874a..db957e70 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -106,6 +106,9 @@ jobs: - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.11" + - name: Sync root README into bindings/python so it ships with the wheel + shell: bash + run: cp README.md bindings/python/README.md - uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: working-directory: bindings/python @@ -124,6 +127,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Sync root README into bindings/python so it ships in the sdist + run: cp README.md bindings/python/README.md - uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 with: working-directory: bindings/python @@ -289,6 +294,14 @@ jobs: done exit $fail + - name: Sync root README into bindings/node so it ships with the npm tarball + # npm reads README.md from the package directory at publish time. Copy + # the canonical root README in just before the publish so every + # registry shows the same project page. + shell: bash + run: cp ../../README.md README.md + working-directory: bindings/node + - name: Publish main package to npm (idempotent) working-directory: bindings/node env: @@ -359,6 +372,12 @@ jobs: - uses: jetli/wasm-pack-action@0d096b08b4e5a7de8c28de67e11e945404e9eefa # v0.4.0 + - name: Sync root README into bindings/wasm so wasm-pack ships it in pkg/ + # wasm-pack copies the crate's README.md into the generated pkg/ + # directory it then publishes. Refresh it from the canonical root + # README right before the build. + run: cp README.md bindings/wasm/README.md + - name: Build WASM package (bundler target) run: wasm-pack build bindings/wasm --target bundler --release --features panic-hook diff --git a/bindings/node/README.md b/bindings/node/README.md index 70a7c86f..cc9da99e 100644 --- a/bindings/node/README.md +++ b/bindings/node/README.md @@ -1,45 +1,306 @@ -# wickra +# Wickra -Node.js bindings for the Wickra streaming-first technical indicators library. +[![CI](https://github.com/kingchenc/wickra/actions/workflows/ci.yml/badge.svg)](https://github.com/kingchenc/wickra/actions/workflows/ci.yml) +[![codecov](https://codecov.io/gh/kingchenc/wickra/branch/main/graph/badge.svg)](https://codecov.io/gh/kingchenc/wickra) +[![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: PolyForm-NC](https://img.shields.io/badge/license-PolyForm--NC--1.0.0-purple)](LICENSE) -## Install +**Streaming-first technical indicators. Install with `pip install wickra` — no system dependencies.** -Once published, install per platform via the precompiled native package: +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 +historical backtests share the exact same implementation. -```bash -npm install wickra +```python +import numpy as np +import wickra as ta + +# Batch: classic TA-Lib-style usage +prices = np.linspace(100, 200, 1000) +rsi = ta.RSI(14) +values = rsi.batch(prices) # numpy array, NaN during warmup + +# Streaming: same indicator, fed tick by tick +rsi = ta.RSI(14) +for price in live_feed: + value = rsi.update(price) # O(1) — no recomputation over history + if value is not None and value > 70: + print("overbought") ``` -## Build from source +## 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: + +| Library | Install pain | Streaming | Multi-language | Active | +|--------------------|-----------------|-----------|----------------|--------| +| 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 | +| **Wickra** | **clean** | **yes** | **Python+Node+WASM+Rust** | **yes** | + +Wickra is the only library that combines all of: clean install, streaming, +multi-language reach, and active maintenance. + +## 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 7950X3D, 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 | 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 | 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 -cd bindings/node -npm install -npm run build -npm test +pip install -e bindings/python[bench] +python -m benchmarks.compare_libraries ``` -The native module is built via [napi-rs](https://napi.rs/). The build script -produces a `wickra.-.node` binary in the package root that -`index.js` loads at runtime. +## Indicators -## Usage +71 streaming-first indicators across eight families. Every one passes the +`batch == streaming` equivalence test, reference-value tests, and reset +semantics tests. -```js -import { SMA, RSI, MACD, version } from 'wickra'; +| Family | Indicators | +|--------|-----------| +| Moving Averages | SMA, EMA, WMA, DEMA, TEMA, HMA, KAMA, SMMA, TRIMA, ZLEMA, T3, VWMA | +| Momentum Oscillators | RSI (Wilder), Stochastic, CCI, ROC, Williams %R, MFI, Awesome Oscillator, MOM, CMO, TSI, PMO, StochRSI, Ultimate Oscillator | +| Trend & Directional | MACD, ADX (+DI/-DI), Aroon, TRIX, Aroon Oscillator, Vortex, Mass Index, Choppiness Index, Vertical Horizontal Filter | +| Price Oscillators | PPO, DPO, Coppock, Accelerator Oscillator, Balance of Power | +| Volatility & Bands | ATR, Bollinger Bands, Keltner Channels, Donchian Channels, NATR, StdDev, Ulcer Index, Historical Volatility, Bollinger Bandwidth, %B, True Range, Chaikin Volatility | +| Trailing Stops | Parabolic SAR, SuperTrend, Chandelier Exit, Chande Kroll Stop, ATR Trailing Stop | +| Volume | OBV, VWAP (cumulative + rolling), ADL, Volume-Price Trend, Chaikin Money Flow, Chaikin Oscillator, Force Index, Ease of Movement | +| Price Statistics | Typical Price, Median Price, Weighted Close, Linear Regression, Linear Regression Slope, Z-Score, Linear Regression Angle | -console.log('wickra', version()); +Adding a new indicator means implementing one trait in Rust; all four bindings +inherit it automatically. -// Batch: -const prices = Array.from({ length: 1000 }, (_, i) => 100 + Math.sin(i * 0.1) * 5); -const rsi = new RSI(14).batch(prices); +## Languages -// Streaming: -const macd = new MACD(12, 26, 9); -for (const p of livePriceStream) { - const v = macd.update(p); - if (v && v.histogram > 0) console.log('bullish crossover candidate'); +| Binding | Install | Example | +|-------------------|-----------------------------------------------|---------| +| Python (PyO3) | `pip install wickra` | `examples/python/backtest.py` | +| 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` | + +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. + +## Rust API + +```rust +use wickra::{Indicator, BatchExt, Chain, Ema, Rsi, Sma}; + +// Streaming or batch — same trait, same code. +let mut sma = Sma::new(14)?; +let out: Vec> = sma.batch(&[1.0, 2.0, 3.0, 4.0, 5.0]); + +let mut rsi = Rsi::new(14)?; +for price in live_feed { + if let Some(v) = rsi.update(price) { + println!("RSI = {v}"); + } +} + +// Compose indicators: RSI(7) on top of EMA(14). +let mut chain = Chain::new(Ema::new(14)?, Rsi::new(7)?); +chain.update(price); +``` + +## Live data sources + +`wickra-data` (separate crate, opt-in) ships: + +- A streaming OHLCV **CSV reader**. +- A **tick-to-candle aggregator** with arbitrary timeframes. +- A **candle resampler** for multi-timeframe analysis (1m → 5m → 1h on the fly). +- A **Binance Spot WebSocket** kline adapter (feature `live-binance`). + +```rust +use wickra::{Indicator, Rsi}; +use wickra_data::live::binance::{BinanceKlineStream, Interval}; + +let mut stream = BinanceKlineStream::connect(&["BTCUSDT".into()], Interval::OneMinute).await?; +let mut rsi = Rsi::new(14)?; +while let Some(event) = stream.next_event().await? { + if event.is_closed { + if let Some(v) = rsi.update(event.candle.close) { + println!("RSI = {v:.2}"); + } + } } ``` -See `index.d.ts` for the full TypeScript surface. +A Python live-trading example using the public `websockets` package lives at +`examples/python/live_trading.py`. + +## Project layout + +``` +wickra/ +├── crates/ +│ ├── wickra-core/ core engine + all 71 indicators +│ ├── wickra/ top-level facade crate (publishes on crates.io) + benches/ +│ └── wickra-data/ CSV reader, tick aggregator, live exchange feeds +├── bindings/ +│ ├── python/ PyO3 + maturin (publishes on PyPI) +│ ├── node/ napi-rs (publishes on npm) +│ └── wasm/ wasm-bindgen (browsers, bundlers, Node) +├── 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` +└── .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. + +## Building everything from source + +```bash +# Rust core + tests +cargo test --workspace +cargo clippy --workspace --all-targets -- -D warnings +cargo bench -p wickra + +# Python binding (requires Rust toolchain + maturin) +cd bindings/python +maturin develop --release +pytest + +# WASM binding (requires wasm-pack + wasm32-unknown-unknown target) +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 +``` + +## Testing + +Every layer is covered; run the suites with the commands in +[Building everything from source](#building-everything-from-source). + +- `wickra-core`: unit tests per indicator — textbook reference values + (Wilder RSI, Bollinger Bands, MACD, ATR, Stochastic), `batch == streaming` + equivalence, `reset` semantics, NaN/Inf handling, and property tests. +- `wickra-data`: unit tests for CSV decoding, the tick aggregator, the + resampler, and the Binance payload parser. +- `bindings/python`: pytest covering smoke checks, streaming/batch + equivalence, reference values, lifecycle, input validation, and + dict/tuple candle inputs. +- `bindings/node`: `node --test` cases for batch, streaming, and reference + values across all indicators. +- `bindings/wasm`: `wasm-bindgen-test` cases for constructors, equivalence, + and reference values. + +## Contributing + +Contributions are very welcome — issues, bug reports, ideas, and pull requests +all land in the same place: . + +A short orientation for first-time contributors: + +- **Adding an indicator.** Implement the `Indicator` trait in + `crates/wickra-core/src/indicators/.rs`, wire it into + `indicators/mod.rs` and the crate root, and add reference-value tests, + a `batch == streaming` equivalence test, and (where it makes sense) a + proptest. The four bindings inherit your indicator automatically once + you expose it in the language wrappers. +- **Fixing a numeric bug.** Add a failing test that pins the textbook value + first, then fix the math. Property tests in `crates/wickra-core` catch + most regressions; please don't disable them. +- **Improving a binding.** Each binding lives under `bindings/` with + its own tests; please keep the `batch == streaming` invariant. +- **Style.** `cargo fmt --all` + `cargo clippy --workspace --all-targets -- -D warnings` + are CI gates; running them locally before pushing keeps reviews short. + +For larger architectural changes, open an issue first so we can sketch the +shape together before you invest the time. + +## License + +Licensed under the **PolyForm Noncommercial License 1.0.0**. See [LICENSE](LICENSE). + +In plain English: use it, fork it, modify it, redistribute it, file issues, send +pull requests — all welcome. Personal projects, research, education, non-profits, +government, hobby trading bots: all fine. The one thing that's not allowed is +commercial sale of the software or of services built around it. If you want to +use Wickra commercially, get in touch about a license. + +--- + +

+ + GitHub stars + + + GitHub forks + + + GitHub issues + +

+ +

+ If Wickra saved you time, the cheapest way to say thanks is to ⭐ the repo. +

diff --git a/bindings/python/README.md b/bindings/python/README.md index ce6dc84d..cc9da99e 100644 --- a/bindings/python/README.md +++ b/bindings/python/README.md @@ -1,66 +1,306 @@ -# Wickra — Python bindings +# Wickra -Streaming-first technical indicators powered by a Rust core. +[![CI](https://github.com/kingchenc/wickra/actions/workflows/ci.yml/badge.svg)](https://github.com/kingchenc/wickra/actions/workflows/ci.yml) +[![codecov](https://codecov.io/gh/kingchenc/wickra/branch/main/graph/badge.svg)](https://codecov.io/gh/kingchenc/wickra) +[![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: PolyForm-NC](https://img.shields.io/badge/license-PolyForm--NC--1.0.0-purple)](LICENSE) -```bash -pip install wickra -``` +**Streaming-first technical indicators. Install with `pip install wickra` — no system dependencies.** -## Quick start +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 +historical backtests share the exact same implementation. ```python import numpy as np import wickra as ta -# Batch — TA-Lib-style usage +# Batch: classic TA-Lib-style usage prices = np.linspace(100, 200, 1000) -rsi = ta.RSI(14).batch(prices) # NumPy array; NaN during warmup - -# Streaming — feed ticks one at a time rsi = ta.RSI(14) -for price in live_prices: - v = rsi.update(price) # O(1) per tick - if v is not None and v > 70: - ... +values = rsi.batch(prices) # numpy array, NaN during warmup + +# Streaming: same indicator, fed tick by tick +rsi = ta.RSI(14) +for price in live_feed: + value = rsi.update(price) # O(1) — no recomputation over history + if value is not None and value > 70: + print("overbought") ``` -## What's included +## Why Wickra exists -71 streaming-first indicators across eight families. Every one passes a -`batch == streaming` equivalence test and reference-value tests: +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: -- **Moving Averages** — SMA, EMA, WMA, DEMA, TEMA, HMA, KAMA, SMMA, TRIMA, - ZLEMA, T3, VWMA -- **Momentum Oscillators** — RSI (Wilder), Stochastic, CCI, ROC, Williams %R, - MFI, Awesome Oscillator, MOM, CMO, TSI, PMO, StochRSI, Ultimate Oscillator -- **Trend & Directional** — MACD, ADX (+DI/-DI), Aroon, TRIX, Aroon - Oscillator, Vortex, Mass Index, Choppiness Index, Vertical Horizontal Filter -- **Price Oscillators** — PPO, DPO, Coppock, Accelerator Oscillator, Balance - of Power -- **Volatility & Bands** — ATR, Bollinger Bands, Keltner Channels, Donchian - Channels, NATR, StdDev, Ulcer Index, Historical Volatility, Bollinger - Bandwidth, %B, True Range, Chaikin Volatility -- **Trailing Stops** — Parabolic SAR, SuperTrend, Chandelier Exit, Chande - Kroll Stop, ATR Trailing Stop -- **Volume** — OBV, VWAP (cumulative + rolling), ADL, Volume-Price Trend, - Chaikin Money Flow, Chaikin Oscillator, Force Index, Ease of Movement -- **Price Statistics** — Typical Price, Median Price, Weighted Close, Linear - Regression, Linear Regression Slope, Z-Score, Linear Regression Angle +| Library | Install pain | Streaming | Multi-language | Active | +|--------------------|-----------------|-----------|----------------|--------| +| 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 | +| **Wickra** | **clean** | **yes** | **Python+Node+WASM+Rust** | **yes** | -## Why streaming-first matters +Wickra is the only library that combines all of: clean install, streaming, +multi-language reach, and active maintenance. -Classic TA libraries are batch-only: every live tick triggers a full -recomputation over the entire history. Wickra updates indicator state in -O(1) per tick. On a 5K-bar history the streaming RSI gap is ~17× over the -nearest peer with a streaming API and 100×+ over batch-only libraries. +## Benchmark: how much faster is "streaming-first"? -## Full project +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. -See for benchmarks, the Rust core, -Node.js and WebAssembly bindings, examples, and CI. +- **Reproduced on:** Windows 11 Pro 26200, AMD Ryzen 9 7950X3D, 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 | 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 | 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 +``` + +## Indicators + +71 streaming-first indicators across eight families. Every one passes the +`batch == streaming` equivalence test, reference-value tests, and reset +semantics tests. + +| Family | Indicators | +|--------|-----------| +| Moving Averages | SMA, EMA, WMA, DEMA, TEMA, HMA, KAMA, SMMA, TRIMA, ZLEMA, T3, VWMA | +| Momentum Oscillators | RSI (Wilder), Stochastic, CCI, ROC, Williams %R, MFI, Awesome Oscillator, MOM, CMO, TSI, PMO, StochRSI, Ultimate Oscillator | +| Trend & Directional | MACD, ADX (+DI/-DI), Aroon, TRIX, Aroon Oscillator, Vortex, Mass Index, Choppiness Index, Vertical Horizontal Filter | +| Price Oscillators | PPO, DPO, Coppock, Accelerator Oscillator, Balance of Power | +| Volatility & Bands | ATR, Bollinger Bands, Keltner Channels, Donchian Channels, NATR, StdDev, Ulcer Index, Historical Volatility, Bollinger Bandwidth, %B, True Range, Chaikin Volatility | +| Trailing Stops | Parabolic SAR, SuperTrend, Chandelier Exit, Chande Kroll Stop, ATR Trailing Stop | +| Volume | OBV, VWAP (cumulative + rolling), ADL, Volume-Price Trend, Chaikin Money Flow, Chaikin Oscillator, Force Index, Ease of Movement | +| Price Statistics | Typical Price, Median Price, Weighted Close, Linear Regression, Linear Regression Slope, Z-Score, Linear Regression Angle | + +Adding a new indicator means implementing one trait in Rust; all four bindings +inherit it automatically. + +## Languages + +| Binding | Install | Example | +|-------------------|-----------------------------------------------|---------| +| Python (PyO3) | `pip install wickra` | `examples/python/backtest.py` | +| 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` | + +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. + +## Rust API + +```rust +use wickra::{Indicator, BatchExt, Chain, Ema, Rsi, Sma}; + +// Streaming or batch — same trait, same code. +let mut sma = Sma::new(14)?; +let out: Vec> = sma.batch(&[1.0, 2.0, 3.0, 4.0, 5.0]); + +let mut rsi = Rsi::new(14)?; +for price in live_feed { + if let Some(v) = rsi.update(price) { + println!("RSI = {v}"); + } +} + +// Compose indicators: RSI(7) on top of EMA(14). +let mut chain = Chain::new(Ema::new(14)?, Rsi::new(7)?); +chain.update(price); +``` + +## Live data sources + +`wickra-data` (separate crate, opt-in) ships: + +- A streaming OHLCV **CSV reader**. +- A **tick-to-candle aggregator** with arbitrary timeframes. +- A **candle resampler** for multi-timeframe analysis (1m → 5m → 1h on the fly). +- A **Binance Spot WebSocket** kline adapter (feature `live-binance`). + +```rust +use wickra::{Indicator, Rsi}; +use wickra_data::live::binance::{BinanceKlineStream, Interval}; + +let mut stream = BinanceKlineStream::connect(&["BTCUSDT".into()], Interval::OneMinute).await?; +let mut rsi = Rsi::new(14)?; +while let Some(event) = stream.next_event().await? { + if event.is_closed { + if let Some(v) = rsi.update(event.candle.close) { + println!("RSI = {v:.2}"); + } + } +} +``` + +A Python live-trading example using the public `websockets` package lives at +`examples/python/live_trading.py`. + +## Project layout + +``` +wickra/ +├── crates/ +│ ├── wickra-core/ core engine + all 71 indicators +│ ├── wickra/ top-level facade crate (publishes on crates.io) + benches/ +│ └── wickra-data/ CSV reader, tick aggregator, live exchange feeds +├── bindings/ +│ ├── python/ PyO3 + maturin (publishes on PyPI) +│ ├── node/ napi-rs (publishes on npm) +│ └── wasm/ wasm-bindgen (browsers, bundlers, Node) +├── 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` +└── .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. + +## Building everything from source + +```bash +# Rust core + tests +cargo test --workspace +cargo clippy --workspace --all-targets -- -D warnings +cargo bench -p wickra + +# Python binding (requires Rust toolchain + maturin) +cd bindings/python +maturin develop --release +pytest + +# WASM binding (requires wasm-pack + wasm32-unknown-unknown target) +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 +``` + +## Testing + +Every layer is covered; run the suites with the commands in +[Building everything from source](#building-everything-from-source). + +- `wickra-core`: unit tests per indicator — textbook reference values + (Wilder RSI, Bollinger Bands, MACD, ATR, Stochastic), `batch == streaming` + equivalence, `reset` semantics, NaN/Inf handling, and property tests. +- `wickra-data`: unit tests for CSV decoding, the tick aggregator, the + resampler, and the Binance payload parser. +- `bindings/python`: pytest covering smoke checks, streaming/batch + equivalence, reference values, lifecycle, input validation, and + dict/tuple candle inputs. +- `bindings/node`: `node --test` cases for batch, streaming, and reference + values across all indicators. +- `bindings/wasm`: `wasm-bindgen-test` cases for constructors, equivalence, + and reference values. + +## Contributing + +Contributions are very welcome — issues, bug reports, ideas, and pull requests +all land in the same place: . + +A short orientation for first-time contributors: + +- **Adding an indicator.** Implement the `Indicator` trait in + `crates/wickra-core/src/indicators/.rs`, wire it into + `indicators/mod.rs` and the crate root, and add reference-value tests, + a `batch == streaming` equivalence test, and (where it makes sense) a + proptest. The four bindings inherit your indicator automatically once + you expose it in the language wrappers. +- **Fixing a numeric bug.** Add a failing test that pins the textbook value + first, then fix the math. Property tests in `crates/wickra-core` catch + most regressions; please don't disable them. +- **Improving a binding.** Each binding lives under `bindings/` with + its own tests; please keep the `batch == streaming` invariant. +- **Style.** `cargo fmt --all` + `cargo clippy --workspace --all-targets -- -D warnings` + are CI gates; running them locally before pushing keeps reviews short. + +For larger architectural changes, open an issue first so we can sketch the +shape together before you invest the time. ## License -Licensed under the **PolyForm Noncommercial License 1.0.0**. Personal, -research, educational, and non-profit use are all permitted. Commercial -sale requires a separate license — contact via the GitHub repo. +Licensed under the **PolyForm Noncommercial License 1.0.0**. See [LICENSE](LICENSE). + +In plain English: use it, fork it, modify it, redistribute it, file issues, send +pull requests — all welcome. Personal projects, research, education, non-profits, +government, hobby trading bots: all fine. The one thing that's not allowed is +commercial sale of the software or of services built around it. If you want to +use Wickra commercially, get in touch about a license. + +--- + +

+ + GitHub stars + + + GitHub forks + + + GitHub issues + +

+ +

+ If Wickra saved you time, the cheapest way to say thanks is to ⭐ the repo. +

diff --git a/bindings/wasm/README.md b/bindings/wasm/README.md index 8da4f8e5..cc9da99e 100644 --- a/bindings/wasm/README.md +++ b/bindings/wasm/README.md @@ -1,49 +1,306 @@ -# wickra-wasm +# Wickra -WebAssembly bindings for the Wickra streaming-first technical indicators library. +[![CI](https://github.com/kingchenc/wickra/actions/workflows/ci.yml/badge.svg)](https://github.com/kingchenc/wickra/actions/workflows/ci.yml) +[![codecov](https://codecov.io/gh/kingchenc/wickra/branch/main/graph/badge.svg)](https://codecov.io/gh/kingchenc/wickra) +[![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: PolyForm-NC](https://img.shields.io/badge/license-PolyForm--NC--1.0.0-purple)](LICENSE) -## Build +**Streaming-first technical indicators. Install with `pip install wickra` — no system dependencies.** -You need [`wasm-pack`](https://rustwasm.github.io/wasm-pack/) and the -`wasm32-unknown-unknown` Rust target: +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 +historical backtests share the exact same implementation. -```bash -rustup target add wasm32-unknown-unknown -cargo install wasm-pack +```python +import numpy as np +import wickra as ta + +# Batch: classic TA-Lib-style usage +prices = np.linspace(100, 200, 1000) +rsi = ta.RSI(14) +values = rsi.batch(prices) # numpy array, NaN during warmup + +# Streaming: same indicator, fed tick by tick +rsi = ta.RSI(14) +for price in live_feed: + value = rsi.update(price) # O(1) — no recomputation over history + if value is not None and value > 70: + print("overbought") ``` -Then from the repository root: +## 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: + +| Library | Install pain | Streaming | Multi-language | Active | +|--------------------|-----------------|-----------|----------------|--------| +| 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 | +| **Wickra** | **clean** | **yes** | **Python+Node+WASM+Rust** | **yes** | + +Wickra is the only library that combines all of: clean install, streaming, +multi-language reach, and active maintenance. + +## 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 7950X3D, 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 | 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 | 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 -wasm-pack build bindings/wasm --target web --release --features panic-hook +pip install -e bindings/python[bench] +python -m benchmarks.compare_libraries ``` -The compiled package lands in `bindings/wasm/pkg/`. Targets: +## Indicators -- `--target web` for native ES modules in browsers -- `--target bundler` for webpack/Vite/Rollup -- `--target nodejs` for Node.js +71 streaming-first indicators across eight families. Every one passes the +`batch == streaming` equivalence test, reference-value tests, and reset +semantics tests. -## Example +| Family | Indicators | +|--------|-----------| +| Moving Averages | SMA, EMA, WMA, DEMA, TEMA, HMA, KAMA, SMMA, TRIMA, ZLEMA, T3, VWMA | +| Momentum Oscillators | RSI (Wilder), Stochastic, CCI, ROC, Williams %R, MFI, Awesome Oscillator, MOM, CMO, TSI, PMO, StochRSI, Ultimate Oscillator | +| Trend & Directional | MACD, ADX (+DI/-DI), Aroon, TRIX, Aroon Oscillator, Vortex, Mass Index, Choppiness Index, Vertical Horizontal Filter | +| Price Oscillators | PPO, DPO, Coppock, Accelerator Oscillator, Balance of Power | +| Volatility & Bands | ATR, Bollinger Bands, Keltner Channels, Donchian Channels, NATR, StdDev, Ulcer Index, Historical Volatility, Bollinger Bandwidth, %B, True Range, Chaikin Volatility | +| Trailing Stops | Parabolic SAR, SuperTrend, Chandelier Exit, Chande Kroll Stop, ATR Trailing Stop | +| Volume | OBV, VWAP (cumulative + rolling), ADL, Volume-Price Trend, Chaikin Money Flow, Chaikin Oscillator, Force Index, Ease of Movement | +| Price Statistics | Typical Price, Median Price, Weighted Close, Linear Regression, Linear Regression Slope, Z-Score, Linear Regression Angle | -```js -import init, { SMA, RSI, MACD, version } from "./pkg/wickra_wasm.js"; +Adding a new indicator means implementing one trait in Rust; all four bindings +inherit it automatically. -await init(); -console.log("wickra:", version()); +## Languages -// Streaming -const rsi = new RSI(14); -for (const price of livePrices) { - const v = rsi.update(price); - if (v !== undefined && v > 70) console.log("overbought"); +| Binding | Install | Example | +|-------------------|-----------------------------------------------|---------| +| Python (PyO3) | `pip install wickra` | `examples/python/backtest.py` | +| 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` | + +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. + +## Rust API + +```rust +use wickra::{Indicator, BatchExt, Chain, Ema, Rsi, Sma}; + +// Streaming or batch — same trait, same code. +let mut sma = Sma::new(14)?; +let out: Vec> = sma.batch(&[1.0, 2.0, 3.0, 4.0, 5.0]); + +let mut rsi = Rsi::new(14)?; +for price in live_feed { + if let Some(v) = rsi.update(price) { + println!("RSI = {v}"); + } } -// Batch (returns a Float64Array; NaN for warmup positions) -const sma = new SMA(20).batch(new Float64Array(historicalPrices)); +// Compose indicators: RSI(7) on top of EMA(14). +let mut chain = Chain::new(Ema::new(14)?, Rsi::new(7)?); +chain.update(price); ``` -An interactive demo lives in [`examples/wasm/index.html`](../../examples/wasm/index.html) -(top-level alongside the other language examples). After building the package -with `wasm-pack build`, serve the repository root and open -`examples/wasm/index.html` in a browser. +## Live data sources + +`wickra-data` (separate crate, opt-in) ships: + +- A streaming OHLCV **CSV reader**. +- A **tick-to-candle aggregator** with arbitrary timeframes. +- A **candle resampler** for multi-timeframe analysis (1m → 5m → 1h on the fly). +- A **Binance Spot WebSocket** kline adapter (feature `live-binance`). + +```rust +use wickra::{Indicator, Rsi}; +use wickra_data::live::binance::{BinanceKlineStream, Interval}; + +let mut stream = BinanceKlineStream::connect(&["BTCUSDT".into()], Interval::OneMinute).await?; +let mut rsi = Rsi::new(14)?; +while let Some(event) = stream.next_event().await? { + if event.is_closed { + if let Some(v) = rsi.update(event.candle.close) { + println!("RSI = {v:.2}"); + } + } +} +``` + +A Python live-trading example using the public `websockets` package lives at +`examples/python/live_trading.py`. + +## Project layout + +``` +wickra/ +├── crates/ +│ ├── wickra-core/ core engine + all 71 indicators +│ ├── wickra/ top-level facade crate (publishes on crates.io) + benches/ +│ └── wickra-data/ CSV reader, tick aggregator, live exchange feeds +├── bindings/ +│ ├── python/ PyO3 + maturin (publishes on PyPI) +│ ├── node/ napi-rs (publishes on npm) +│ └── wasm/ wasm-bindgen (browsers, bundlers, Node) +├── 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` +└── .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. + +## Building everything from source + +```bash +# Rust core + tests +cargo test --workspace +cargo clippy --workspace --all-targets -- -D warnings +cargo bench -p wickra + +# Python binding (requires Rust toolchain + maturin) +cd bindings/python +maturin develop --release +pytest + +# WASM binding (requires wasm-pack + wasm32-unknown-unknown target) +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 +``` + +## Testing + +Every layer is covered; run the suites with the commands in +[Building everything from source](#building-everything-from-source). + +- `wickra-core`: unit tests per indicator — textbook reference values + (Wilder RSI, Bollinger Bands, MACD, ATR, Stochastic), `batch == streaming` + equivalence, `reset` semantics, NaN/Inf handling, and property tests. +- `wickra-data`: unit tests for CSV decoding, the tick aggregator, the + resampler, and the Binance payload parser. +- `bindings/python`: pytest covering smoke checks, streaming/batch + equivalence, reference values, lifecycle, input validation, and + dict/tuple candle inputs. +- `bindings/node`: `node --test` cases for batch, streaming, and reference + values across all indicators. +- `bindings/wasm`: `wasm-bindgen-test` cases for constructors, equivalence, + and reference values. + +## Contributing + +Contributions are very welcome — issues, bug reports, ideas, and pull requests +all land in the same place: . + +A short orientation for first-time contributors: + +- **Adding an indicator.** Implement the `Indicator` trait in + `crates/wickra-core/src/indicators/.rs`, wire it into + `indicators/mod.rs` and the crate root, and add reference-value tests, + a `batch == streaming` equivalence test, and (where it makes sense) a + proptest. The four bindings inherit your indicator automatically once + you expose it in the language wrappers. +- **Fixing a numeric bug.** Add a failing test that pins the textbook value + first, then fix the math. Property tests in `crates/wickra-core` catch + most regressions; please don't disable them. +- **Improving a binding.** Each binding lives under `bindings/` with + its own tests; please keep the `batch == streaming` invariant. +- **Style.** `cargo fmt --all` + `cargo clippy --workspace --all-targets -- -D warnings` + are CI gates; running them locally before pushing keeps reviews short. + +For larger architectural changes, open an issue first so we can sketch the +shape together before you invest the time. + +## License + +Licensed under the **PolyForm Noncommercial License 1.0.0**. See [LICENSE](LICENSE). + +In plain English: use it, fork it, modify it, redistribute it, file issues, send +pull requests — all welcome. Personal projects, research, education, non-profits, +government, hobby trading bots: all fine. The one thing that's not allowed is +commercial sale of the software or of services built around it. If you want to +use Wickra commercially, get in touch about a license. + +--- + +

+ + GitHub stars + + + GitHub forks + + + GitHub issues + +

+ +

+ If Wickra saved you time, the cheapest way to say thanks is to ⭐ the repo. +