Wickra 0.1.0: streaming-first technical indicators
A multi-language technical analysis library: 25 indicators across trend,
momentum, volatility, and volume families, every one a state machine with
O(1) per-tick updates. Batch evaluation is provided by a blanket extension
trait over the streaming primitive, so live trading bots and historical
backtests run the same code path.
What ships in this initial drop:
crates/wickra-core - 25 indicators, Indicator/BatchExt/Chain traits,
OHLCV types with validation; 171 unit tests,
property tests, Wilder/Bollinger textbook tests.
crates/wickra - top-level facade + criterion benches for every
indicator at 1K/10K/100K series sizes.
crates/wickra-data - streaming CSV reader, tick-to-candle aggregator,
multi-timeframe resampler, Binance Spot kline
WebSocket adapter behind feature live-binance;
11 unit + 1 doctest.
bindings/python - PyO3 + maturin, NumPy I/O, type stubs (.pyi),
56 pytest tests including streaming==batch
equivalence, Wilder reference values, lifecycle.
bindings/node - napi-rs native module, TypeScript .d.ts
auto-generated, 7 node --test cases.
bindings/wasm - wasm-bindgen ES module for browser/bundler/Node;
interactive HTML demo at examples/index.html.
examples/ - Python and Rust scripts: backtest, live trading,
parallel multi-asset, multi-timeframe, Binance.
benchmarks/ - cross-library comparison against TA-Lib,
pandas-ta, finta, talipp; Wickra wins every
category by 11-1030x (batch) and 17x+ streaming.
.github/workflows/ - CI matrix (Rust + Python + Node + WASM on
Linux/macOS/Windows), release pipeline for
PyPI wheels and npm.
Indicators (25):
Trend SMA EMA WMA DEMA TEMA HMA KAMA
Momentum RSI MACD Stochastic CCI ROC WilliamsR ADX MFI TRIX
AwesomeOscillator Aroon
Volatility BollingerBands ATR Keltner Donchian PSAR
Volume OBV VWAP (cumulative + rolling)
cargo clippy --workspace --all-targets -D warnings is clean. License: Apache-2.0.
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
// Smoke tests for the Wickra Node bindings.
|
||||
//
|
||||
// Run with:
|
||||
// cd bindings/node && npm install && npm run build && npm test
|
||||
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const wickra = require('..');
|
||||
|
||||
test('version is non-empty', () => {
|
||||
assert.ok(typeof wickra.version() === 'string');
|
||||
assert.ok(wickra.version().length > 0);
|
||||
});
|
||||
|
||||
test('SMA batch matches reference values', () => {
|
||||
const sma = new wickra.SMA(3);
|
||||
const out = sma.batch([2, 4, 6, 8, 10]);
|
||||
assert.ok(Number.isNaN(out[0]));
|
||||
assert.ok(Number.isNaN(out[1]));
|
||||
assert.equal(out[2], 4);
|
||||
assert.equal(out[3], 6);
|
||||
assert.equal(out[4], 8);
|
||||
});
|
||||
|
||||
test('RSI pure uptrend yields 100', () => {
|
||||
const rsi = new wickra.RSI(14);
|
||||
const prices = Array.from({ length: 20 }, (_, i) => i + 1);
|
||||
const out = rsi.batch(prices);
|
||||
for (let i = 14; i < out.length; i++) {
|
||||
assert.equal(out[i], 100);
|
||||
}
|
||||
});
|
||||
|
||||
test('streaming and batch agree on EMA', () => {
|
||||
const prices = Array.from({ length: 60 }, (_, i) => 100 + Math.sin(i * 0.3) * 5);
|
||||
const batch = new wickra.EMA(14).batch(prices);
|
||||
const ema = new wickra.EMA(14);
|
||||
const streamed = prices.map((p) => {
|
||||
const v = ema.update(p);
|
||||
return v === null || v === undefined ? NaN : v;
|
||||
});
|
||||
for (let i = 0; i < prices.length; i++) {
|
||||
if (Number.isNaN(batch[i])) {
|
||||
assert.ok(Number.isNaN(streamed[i]));
|
||||
} else {
|
||||
assert.ok(Math.abs(batch[i] - streamed[i]) < 1e-9);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('MACD returns macd/signal/histogram object', () => {
|
||||
const macd = new wickra.MACD(12, 26, 9);
|
||||
let value = null;
|
||||
for (let i = 1; i <= 60; i++) {
|
||||
value = macd.update(i);
|
||||
}
|
||||
assert.ok(value);
|
||||
assert.equal(typeof value.macd, 'number');
|
||||
assert.equal(typeof value.signal, 'number');
|
||||
assert.equal(typeof value.histogram, 'number');
|
||||
assert.ok(Math.abs(value.histogram - (value.macd - value.signal)) < 1e-9);
|
||||
});
|
||||
|
||||
test('ATR batch shape', () => {
|
||||
const high = Array.from({ length: 30 }, () => 11);
|
||||
const low = Array.from({ length: 30 }, () => 9);
|
||||
const close = Array.from({ length: 30 }, () => 10);
|
||||
const out = new wickra.ATR(14).batch(high, low, close);
|
||||
assert.equal(out.length, 30);
|
||||
// Once seeded, ATR is the constant TR of 2.
|
||||
for (let i = 13; i < 30; i++) {
|
||||
assert.ok(Math.abs(out[i] - 2) < 1e-9);
|
||||
}
|
||||
});
|
||||
|
||||
test('zero period is clamped to a valid window', () => {
|
||||
// Constructors cannot throw from JS (napi-rs 2.16 limitation), so they
|
||||
// clamp pathological values like period=0 to the smallest valid window.
|
||||
const sma = new wickra.SMA(0);
|
||||
assert.equal(sma.warmupPeriod(), 1);
|
||||
assert.equal(sma.update(42), 42);
|
||||
});
|
||||
Reference in New Issue
Block a user