diff --git a/docs/wiki/Data-Layer.md b/docs/wiki/Data-Layer.md new file mode 100644 index 00000000..3c57319e --- /dev/null +++ b/docs/wiki/Data-Layer.md @@ -0,0 +1,160 @@ +# Data Layer (`wickra-data`) + +`wickra-data` is a separate crate that feeds candles into Wickra's indicators. +It is not part of `wickra-core` — depend on it explicitly: + +```toml +[dependencies] +wickra = "0.1" +wickra-data = "0.1" +``` + +It provides four pieces: + +- a streaming OHLCV **CSV reader**, +- a **tick-to-candle aggregator**, +- a **candle resampler** for multi-timeframe analysis, +- an optional **Binance Spot WebSocket** kline feed (feature `live-binance`). + +## CSV reader + +`CandleReader` streams OHLCV rows out of a CSV file into validated `Candle` +values. + +```rust +use wickra_data::csv::CandleReader; + +let mut reader = CandleReader::open("ohlcv.csv")?; +let candles = reader.read_all()?; // Vec + +// Or stream row by row without buffering the whole file: +let mut reader = CandleReader::open("ohlcv.csv")?; +for candle in reader.candles() { + let candle = candle?; + // feed `candle` into an indicator... +} +``` + +The reader is defensive about real-world files: + +- The first line **must** be a header naming the columns + `timestamp,open,high,low,close,volume`. A missing column, or a file with no + header at all, is rejected with a clear `Error::Malformed` instead of + silently consuming the first data row. +- A leading UTF-8 byte-order mark (Excel exports one) is stripped. +- Whitespace around values is trimmed. +- Each row is validated through `Candle::new`, so an inconsistent OHLC row + (e.g. `high < low`) surfaces as an error. + +## Tick aggregator + +`TickAggregator` rolls a stream of trade `Tick`s up into `Candle`s of an +arbitrary timeframe. The timeframe's bucket size is in the same unit as the +tick timestamps (milliseconds for Binance, seconds for daily bars, …). + +```rust +use wickra_data::aggregator::{TickAggregator, Timeframe}; +use wickra_core::Tick; + +let mut agg = TickAggregator::new(Timeframe::one_minute_ms()); + +for tick in trade_feed { + // push returns every candle that closed because of this tick — + // empty while the bar grows, one candle when a bar boundary is crossed. + for closed in agg.push(tick)? { + // feed `closed` into an indicator... + } +} + +// Capture the final, still-open bar at the end of the stream. +if let Some(last) = agg.flush()? { + // ... +} +``` + +Out-of-order ticks — across or within a bucket — are rejected with +`Error::Malformed` rather than silently corrupting a bar. + +### Gap filling + +By default a tick that jumps across one or more empty buckets simply opens +the next non-empty bar, leaving a time hole in the output. Enable +`with_gap_fill` to emit a flat placeholder candle +(`open == high == low == close`, `volume == 0`) for every skipped bucket, so +downstream indicators see an unbroken, evenly spaced series: + +```rust +let mut agg = TickAggregator::new(Timeframe::one_minute_ms()).with_gap_fill(true); +``` + +## Resampler + +`Resampler` rolls an existing candle stream up to a coarser timeframe — for +example 1-minute bars into 5-minute bars, without touching the original tick +stream. + +```rust +use wickra_data::aggregator::Timeframe; +use wickra_data::resample::{resample_all, Resampler}; + +// One-shot over an iterator: +let five_min = resample_all(Timeframe::millis(5 * 60_000), one_min_candles)?; + +// Or incrementally: +let mut r = Resampler::new(Timeframe::millis(60 * 60_000)); // 1-hour bars +for candle in one_min_candles { + if let Some(closed) = r.push(candle?)? { + // a coarser bar just closed + } +} +let last = r.flush()?; +``` + +The output timeframe's bucket must be a multiple of the input timeframe's +bucket — picking sensible aggregations (1m → 5m → 1h) is the caller's +responsibility. A candle that arrives in a bucket earlier than the open bar +is rejected as out of order. + +## Binance live feed + +With the `live-binance` feature enabled, `BinanceKlineStream` connects to the +Binance Spot WebSocket and yields closed klines as candles. + +```toml +wickra-data = { version = "0.1", features = ["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}"); + } + } +} +``` + +The stream is resilient: it reconnects with exponential backoff after a +dropped connection, skips non-kline frames (subscription acks, heartbeats), +applies a read timeout and message-size limits, and tracks a closed flag so a +deliberately closed stream is not reused. + +A runnable example lives at `crates/wickra-data/examples/live_binance.rs`: + +```bash +cargo run -p wickra-data --example live_binance --features live-binance +``` + +## See also + +- [Quickstart: Rust](Quickstart-Rust.md) — the core indicator API. +- [Indicators Overview](Indicators-Overview.md) — every indicator and its + parameters. +- Source: diff --git a/docs/wiki/Home.md b/docs/wiki/Home.md index 88f68448..7068d49e 100644 --- a/docs/wiki/Home.md +++ b/docs/wiki/Home.md @@ -45,6 +45,12 @@ Release notes and tagged builds: - [Quickstart: Node](Quickstart-Node.md) — `npm install wickra`, basic `SMA` and `MACD` calls, and the current Windows install caveat (`wickra-win32-x64-msvc@0.1.4` is held by the npm spam filter). +- [Quickstart: WASM](Quickstart-WASM.md) — `npm install wickra-wasm`, + building with `wasm-pack`, and running indicators client-side in a + browser or bundler. +- [Data Layer](Data-Layer.md) — the `wickra-data` crate: the CSV reader, + the tick-to-candle aggregator, the multi-timeframe resampler, and the + Binance live feed. - [Streaming vs Batch](Streaming-vs-Batch.md) — the conceptual difference between Wickra's O(1) `update` and the recompute-everything loops in batch-only libraries, with the benchmark numbers from the project README. diff --git a/docs/wiki/Quickstart-WASM.md b/docs/wiki/Quickstart-WASM.md new file mode 100644 index 00000000..2faea5fd --- /dev/null +++ b/docs/wiki/Quickstart-WASM.md @@ -0,0 +1,133 @@ +# Quickstart: WebAssembly + +A five-minute tour of the Wickra WebAssembly binding. The same Rust core that +powers the Python, Node, and Rust APIs is compiled to WebAssembly with +[wasm-bindgen](https://rustwasm.github.io/wasm-bindgen/), so indicators run +entirely client-side — in a browser tab, a bundler build, or Node — with no +server round-trips. + +## Install + +The published package is `wickra-wasm` on npm: + +```bash +npm install wickra-wasm +``` + +The npm package is built for the bundler target, so it works directly with +Webpack, Vite, Rollup, and similar toolchains. + +## Build from source + +To build the binding yourself you need [`wasm-pack`](https://rustwasm.github.io/wasm-pack/) +and the `wasm32-unknown-unknown` target: + +```bash +rustup target add wasm32-unknown-unknown +cargo install wasm-pack + +# Browser ES-module build (import directly from a +``` + +`init()` must be awaited once before any indicator is constructed — it fetches +and instantiates the WebAssembly binary. After that the API mirrors the other +bindings. + +## Streaming + +```javascript +import init, { RSI } from "wickra-wasm"; + +await init(); + +const rsi = new RSI(14); +for (const price of liveFeed) { + const value = rsi.update(price); // number, or null/undefined during warmup + if (value != null && value > 70) { + console.log("overbought"); + } +} +``` + +Every indicator is an O(1)-per-update state machine: `update` advances the +indicator by exactly one input, so a browser charting app pays no cost for +recomputing history on each tick. + +## Multi-output indicators + +`MACD` and `BollingerBands` return a structured object from `update`: + +```javascript +import init, { MACD } from "wickra-wasm"; + +await init(); + +const macd = new MACD(12, 26, 9); +let last = null; +for (let i = 0; i < 40; i++) { + last = macd.update(100 + i * 0.5); // null during warmup, else { macd, signal, histogram } +} +console.log(last); +``` + +`batch` returns a flat `Float64Array`; multi-output indicators interleave +their fields per row (`[macd0, signal0, hist0, macd1, ...]`). The exact +layout is documented in the generated `pkg/wickra_wasm.d.ts`. + +## Errors + +Unlike the Node binding (whose constructors clamp pathological values), the +WASM binding's constructors throw a JavaScript error for invalid parameters: + +```javascript +try { + new MACD(0, 0, 0); +} catch (e) { + console.error("invalid MACD parameters:", e); +} +``` + +## A complete example + +`bindings/wasm/examples/index.html` is a self-contained browser demo: it +streams a synthetic price series through six indicators and draws a live +chart on a ``. Open it after a `--target web` build: + +```bash +wasm-pack build bindings/wasm --target web --release --features panic-hook +# then serve the bindings/wasm directory and open examples/index.html +``` + +## See also + +- [Quickstart: Node](Quickstart-Node.md) — the native (non-WASM) Node binding. +- [Streaming vs Batch](Streaming-vs-Batch.md) — why `update` is the primary + entry point. +- [Indicators Overview](Indicators-Overview.md) — every indicator and its + parameters. +- Source: