From 21c86f348f48cdaca550bfbfed67d5b3b5a05201 Mon Sep 17 00:00:00 2001 From: kingchenc Date: Sun, 31 May 2026 05:09:26 +0200 Subject: [PATCH] examples + bindings: Node/WASM strategy parity + test/benchmark parity (P2 + P3) (#81) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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). --- .github/workflows/ci.yml | 57 +++++ bindings/node/__tests__/completeness.test.js | 70 ++++++ .../node/__tests__/input_validation.test.js | 84 +++++++ bindings/node/benchmarks/throughput.js | 99 ++++++++ bindings/node/package.json | 3 +- bindings/wasm/src/lib.rs | 146 +++++++++++ examples/README.md | 6 + examples/node/package-lock.json | 18 +- examples/node/strategy_bollinger_squeeze.js | 212 ++++++++++++++++ examples/node/strategy_macd_adx.js | 189 +++++++++++++++ examples/node/strategy_rsi_mean_reversion.js | 189 +++++++++++++++ examples/wasm/README.md | 13 + examples/wasm/strategy_bollinger_squeeze.html | 226 ++++++++++++++++++ examples/wasm/strategy_macd_adx.html | 203 ++++++++++++++++ .../wasm/strategy_rsi_mean_reversion.html | 202 ++++++++++++++++ 15 files changed, 1707 insertions(+), 10 deletions(-) create mode 100644 bindings/node/__tests__/completeness.test.js create mode 100644 bindings/node/__tests__/input_validation.test.js create mode 100644 bindings/node/benchmarks/throughput.js create mode 100644 examples/node/strategy_bollinger_squeeze.js create mode 100644 examples/node/strategy_macd_adx.js create mode 100644 examples/node/strategy_rsi_mean_reversion.js create mode 100644 examples/wasm/strategy_bollinger_squeeze.html create mode 100644 examples/wasm/strategy_macd_adx.html create mode 100644 examples/wasm/strategy_rsi_mean_reversion.html diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 551a056d..d4b98278 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,6 +55,63 @@ jobs: # streaming. run: cargo build -p wickra-examples --bins + # Syntax/parse smoke for the non-Rust examples. The Rust examples are built + # in the `rust` job above (`cargo build -p wickra-examples --bins`); the Node, + # browser-WASM and Python examples otherwise have no build gate, so a broken + # edit could land unnoticed. This is a parse-only smoke — actually running the + # examples needs the built native binding / wasm module / wheel, which the + # binding jobs provide separately. + examples-smoke: + name: Examples (syntax smoke) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "20" + + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.12" + + - name: Node examples — syntax check + run: | + shopt -s nullglob + count=0 + for f in examples/node/*.js examples/wasm/*.js; do + echo "node --check $f" + node --check "$f" + count=$((count + 1)) + done + echo "checked $count Node/WASM .js files" + + - name: WASM demo module scripts — syntax check + # The .html demos embed an ES module; extract it and parse-check so a + # broken edit to the in-page strategy logic fails CI. + run: | + shopt -s nullglob + count=0 + for f in examples/wasm/*.html; do + node -e 'const fs=require("fs");const h=fs.readFileSync(process.argv[1],"utf8");const m=h.match(/ + + diff --git a/examples/wasm/strategy_macd_adx.html b/examples/wasm/strategy_macd_adx.html new file mode 100644 index 00000000..6abe5d23 --- /dev/null +++ b/examples/wasm/strategy_macd_adx.html @@ -0,0 +1,203 @@ + + + + + Wickra WASM — MACD + ADX trend filter + + + +

Wickra WASM — MACD + ADX trend filter

+

+ Long-only trend follower: enters on a MACD(12,26,9) histogram crossover up + while ADX(14) > 20, exits on the opposite crossover, 0.1% fees. The + browser counterpart of examples/python/strategy_macd_adx.py, + examples/node/strategy_macd_adx.js and the Rust + strategy_macd_adx.rs — same loop, same summary. +

+ +

+ + +

+ +

Loading WASM module…

+ + + + + + + + + + + diff --git a/examples/wasm/strategy_rsi_mean_reversion.html b/examples/wasm/strategy_rsi_mean_reversion.html new file mode 100644 index 00000000..305d3e33 --- /dev/null +++ b/examples/wasm/strategy_rsi_mean_reversion.html @@ -0,0 +1,202 @@ + + + + + Wickra WASM — RSI mean-reversion + + + +

Wickra WASM — RSI mean-reversion

+

+ Goes long when RSI(14) crosses below 30 and exits above 70, with 0.1% fees + and a full-in / full-out position. The browser counterpart of + examples/python/strategy_rsi_mean_reversion.py, + examples/node/strategy_rsi_mean_reversion.js and the Rust + strategy_rsi_mean_reversion.rs — same signal → fill → PnL → + equity loop, same summary. +

+ +

+ + +

+ +

Loading WASM module…

+ + + + + + + + + + +