21c86f348f
* examples(node): add RSI mean-reversion strategy
Node counterpart of strategy_rsi_mean_reversion.{py,rs}: RSI(14) < 30 long,
> 70 exit, 0.1% fees, hourly BTCUSDT. Output verified byte-identical to the
Rust reference (37 trades W24/L13, -17.84% return, 46.89% max drawdown).
* examples(node): add MACD + ADX trend-filter strategy
Node counterpart of strategy_macd_adx.{py,rs}: MACD(12,26,9) histogram
crossover entries gated by ADX(14) > 20, hourly BTCUSDT, 0.1% fees. Output
verified byte-identical to the Rust reference (246 trades W90/L156, -47.19%
return, 53.75% max drawdown).
* examples(node): add Bollinger-squeeze breakout strategy
Node counterpart of strategy_bollinger_squeeze.{py,rs}: enter on a fresh
180-bar Bollinger-bandwidth low + close above the upper band, exit on a
2*ATR(14) stop or upper-band collapse, daily BTCUSDT, 0.1% fees. Output
verified byte-identical to the Rust reference (1 trade, -7.82% return,
13.01% max drawdown).
* examples(wasm): add RSI mean-reversion strategy demo
Browser counterpart of strategy_rsi_mean_reversion.{py,js,rs}: RSI(14) < 30
long, > 70 exit, 0.1% fees, summary table. Same signal/fill/PnL/equity loop as
the runtime-verified Node example; loads via the established wickra_wasm.js
init + fetch-CSV pattern. (wasm32 build runs in CI.)
* examples(wasm): add MACD + ADX trend-filter strategy demo
Browser counterpart of strategy_macd_adx.{py,js,rs}: MACD(12,26,9) histogram
crossover gated by ADX(14) > 20, hourly BTCUSDT, 0.1% fees. Logic identical to
the runtime-verified Node example; standard wickra_wasm.js init + fetch-CSV
loader. (wasm32 build runs in CI.)
* examples(wasm): add Bollinger-squeeze breakout strategy demo
Browser counterpart of strategy_bollinger_squeeze.{py,js,rs}: fresh 180-bar
Bollinger-bandwidth low + upper-band breakout, 2*ATR(14) stop, daily BTCUSDT,
0.1% fees. Logic identical to the runtime-verified Node example; standard
wickra_wasm.js init + fetch-CSV loader. (wasm32 build runs in CI.)
* ci: add examples syntax-smoke job (P2.3)
The Rust examples are built by 'cargo build -p wickra-examples --bins'; the
Node, browser-WASM and Python examples had no build gate. New job parse-checks
every examples/{node,wasm}/*.js, extracts and node --checks each WASM .html
module script, and python -m py_compile's every examples/python/*.py — so a
broken example edit fails CI instead of landing silently.
* docs(examples): list the new Node + WASM strategy examples
Add the three Node strategy scripts and three WASM strategy demos to the
examples README tables, bringing Node and WASM to parity with the existing
Rust and Python strategy rows.
* chore(examples): refresh examples/node lockfile for the linked wickra binding
npm install rewrote the file: dependency snapshot of the local wickra binding
that the examples link against (version 0.1.4 -> 0.3.1, license + engines
fields), which had gone stale in the committed lockfile.
* test(node): add input-validation suite
Node counterpart of bindings/python/tests/test_input_validation.py: invalid
constructor parameters (ATR zero period, MACD non-increasing fast/slow,
BollingerBands negative multiplier, PSAR step > max, ValueArea period/pct,
InitialBalance/OpeningRange zero period, Ichimoku non-increasing periods,
Ehlers-family ordering) and unequal-length candle/ValueArea batch inputs all
throw a JS Error. Validated against the built binding.
* test(node): add indicator completeness contract
Introspects every exported indicator class and asserts the full interface
(update / batch / reset / isReady / warmupPeriod) plus the pre-warmup contract
for zero-arg indicators, and guards that the full catalogue (>= 200 classes)
is exported. Catches a new indicator wired without the standard methods, or a
stale/partial native build dropping exports, with no per-indicator boilerplate.
* test(wasm): broaden scalar streaming-vs-batch coverage
Extend the inline wasm-bindgen-test suite with a streaming==batch check across
~70 scalar indicators spanning moving averages, momentum, volatility,
statistics/regression, Ehlers/cycle and risk/performance families (previously
only EMA + the candle-input group were covered per-indicator), plus four more
invalid-constructor assertions. Constructor args mirror the CI-passing Node
factories. Host-compiles (cargo test -p wickra-wasm --no-run); executed in CI
via wasm-pack test --node.
* bench(node): add indicator throughput benchmark
Node counterpart of the Rust criterion benches / Python compare_libraries:
measures streaming (per-tick update) and batch throughput in Mupd/s across a
representative indicator set over a synthetic OHLCV series (--bars, default
200k). Dependency-free; wired as 'npm run bench'.
* docs(wasm): list strategy demos + document the benchmark story
Add the three new strategy demos to the WASM examples table and a Performance
section: parallel_assets.html is the in-browser benchmark, with raw throughput
covered by the Rust criterion / Python / Node benchmarks (the WASM engine is the
same core compiled to wasm32).
85 lines
3.7 KiB
JavaScript
85 lines
3.7 KiB
JavaScript
// Input-validation tests for the Wickra Node bindings: malformed constructor
|
|
// parameters and mismatched batch inputs must raise a JS Error (the napi
|
|
// wrapper turns the Rust `Err` into a thrown Error), not crash the process.
|
|
// Node counterpart of bindings/python/tests/test_input_validation.py.
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const wickra = require('..');
|
|
|
|
// --- Constructors reject invalid periods / parameters ---
|
|
|
|
test('ATR rejects a zero period at construction', () => {
|
|
// ATR validates its period (it drives the Wilder-smoothing length). The
|
|
// plain moving averages (SMA/EMA/RSI/StdDev) instead treat period 0 as a
|
|
// warmup-1 pass-through rather than an error, so they are not asserted here.
|
|
assert.throws(() => new wickra.ATR(0), /.*/);
|
|
});
|
|
|
|
test('MACD rejects zero and non-increasing fast/slow periods', () => {
|
|
assert.throws(() => new wickra.MACD(0, 0, 0), /.*/);
|
|
// fast must be strictly less than slow.
|
|
assert.throws(() => new wickra.MACD(26, 12, 9), /.*/);
|
|
});
|
|
|
|
test('BollingerBands rejects a negative standard-deviation multiplier', () => {
|
|
assert.throws(() => new wickra.BollingerBands(20, -1), /.*/);
|
|
});
|
|
|
|
test('PSAR rejects a step greater than its maximum', () => {
|
|
assert.throws(() => new wickra.PSAR(0.3, 0.02, 0.2), /.*/);
|
|
});
|
|
|
|
test('ValueArea rejects zero periods and out-of-range value-area percentages', () => {
|
|
assert.throws(() => new wickra.ValueArea(0, 50, 0.7), /.*/);
|
|
assert.throws(() => new wickra.ValueArea(20, 0, 0.7), /.*/);
|
|
assert.throws(() => new wickra.ValueArea(20, 50, 0.0), /.*/);
|
|
assert.throws(() => new wickra.ValueArea(20, 50, 1.5), /.*/);
|
|
});
|
|
|
|
test('InitialBalance and OpeningRange reject a zero period', () => {
|
|
assert.throws(() => new wickra.InitialBalance(0), /.*/);
|
|
assert.throws(() => new wickra.OpeningRange(0), /.*/);
|
|
});
|
|
|
|
test('Ichimoku rejects zero and non-increasing periods', () => {
|
|
assert.throws(() => new wickra.Ichimoku(0, 26, 52, 26), /.*/);
|
|
assert.throws(() => new wickra.Ichimoku(9, 26, 52, 0), /.*/);
|
|
// Periods must satisfy tenkan < kijun < senkouB.
|
|
assert.throws(() => new wickra.Ichimoku(26, 9, 52, 26), /.*/);
|
|
assert.throws(() => new wickra.Ichimoku(9, 52, 52, 26), /.*/);
|
|
});
|
|
|
|
test('Family 10 (Ehlers / cycle) indicators reject invalid parameters', () => {
|
|
// InverseFisherTransform needs a non-zero scaling factor.
|
|
assert.throws(() => new wickra.InverseFisherTransform(0.0), /.*/);
|
|
// DecyclerOscillator / RoofingFilter need the short cutoff below the long one.
|
|
assert.throws(() => new wickra.DecyclerOscillator(30, 10), /.*/);
|
|
assert.throws(() => new wickra.RoofingFilter(48, 10), /.*/);
|
|
// MAMA needs fast limit > slow limit.
|
|
assert.throws(() => new wickra.MAMA(0.05, 0.5), /.*/);
|
|
// EmpiricalModeDecomposition needs a positive fraction.
|
|
assert.throws(() => new wickra.EmpiricalModeDecomposition(20, 0.0), /.*/);
|
|
// NOTE: SuperSmoother(0) / FisherTransform(0) are NOT asserted: the Node
|
|
// binding treats their period 0 as a warmup-1 pass-through (same as the
|
|
// simple moving averages) rather than an error.
|
|
});
|
|
|
|
// --- Batch methods reject mismatched input lengths ---
|
|
|
|
test('candle batch methods reject unequal-length columns', () => {
|
|
const high = [10, 11, 12];
|
|
const low = [9, 10]; // one short
|
|
const close = [9.5, 10.5, 11.5];
|
|
assert.throws(() => new wickra.ATR(14).batch(high, low, close), /.*/);
|
|
assert.throws(() => new wickra.WilliamsR(14).batch(high, low, close), /.*/);
|
|
assert.throws(() => new wickra.Aroon(14).batch(high, low), /.*/);
|
|
});
|
|
|
|
test('ValueArea batch rejects unequal-length columns', () => {
|
|
const high = [1, 2, 3];
|
|
const low = [0.5, 1.5]; // short
|
|
const volume = [10, 10, 10];
|
|
assert.throws(() => new wickra.ValueArea(2, 10, 0.7).batch(high, low, volume), /.*/);
|
|
});
|