feat(bindings): expose RollingVWAP in Python, Node and WASM (R4)

The rolling-window VWAP indicator (`wickra_core::RollingVwap`) was only
available in the Rust crate, even though the README's Volume-family
table already advertised "VWAP (cumulative + rolling)" as a cross-
language feature. Users on Python, Node or in the browser had to fall
back to the cumulative `VWAP` or re-implement the rolling variant
themselves.

This commit closes the gap end-to-end:

- Python: `wickra.RollingVWAP(period)` — same constructor / `update` /
  `batch` / `reset` / `is_ready` / `warmup_period` surface as `VWAP`,
  plus a `period` property and a typed `__repr__`. The `__init__.py`
  re-exports it and `__all__` lists it; the `.pyi` stub matches.
- Node: `RollingVWAP(period)` — napi class with the same lifecycle,
  exported from `index.js` and declared in `index.d.ts`.
- WASM: `RollingVWAP(period)` — wasm-bindgen class with the same
  `Float64Array` I/O as `VWAP`.

Tests added:

- Python: `test_rolling_vwap_streaming_matches_batch` — exercises
  `update == batch` plus the full lifecycle on the shared OHLC fixture.
- Node: `RollingVWAP` row in the `candleScalar` parity table — covered
  by the generic streaming-vs-batch + lifecycle harness.
- WASM: dedicated `wasm-bindgen-test` mirrors the Python test.

The wiki page `Indicator-Vwap.md` drops the "Rust-only" caveat and
gains Python / Node / WASM examples.
This commit is contained in:
kingchenc
2026-05-23 01:43:00 +02:00
parent 3a6b5ebae3
commit efcd6216c1
10 changed files with 322 additions and 5 deletions
+51 -4
View File
@@ -178,8 +178,9 @@ fair-price benchmark instead of an unbounded session aggregate.
| `period` | `usize` | (no default) | `> 0` | `RollingVwap::new` (`vwap.rs:89`) |
`period == 0` returns `Error::PeriodZero`. `RollingVwap` is exposed in
Rust only — Python's `VWAP` / Node's `VWAP` correspond to the cumulative
form.
**all four bindings**: as `RollingVwap` in Rust and `RollingVWAP` in
Python, Node and WASM. The plain `VWAP` class in each binding remains the
cumulative form; `RollingVWAP` is the finite-window variant.
### Inputs / Outputs
@@ -245,8 +246,54 @@ Hand check at `t = 3` with window `[10@1, 20@3, 30@1]`:
At `t = 4` with window `[20@3, 30@1, 40@2]`:
`(60 + 30 + 80) / (3+1+2) = 170/6 ≈ 28.333`.
(`RollingVwap` is currently exposed only in the Rust API; the Python
`VWAP` and Node `VWAP` classes correspond to the cumulative form.)
#### Python
```python
import numpy as np
import wickra as ta
high = np.array([10.0, 20.0, 30.0, 40.0])
low = np.array([10.0, 20.0, 30.0, 40.0])
close = np.array([10.0, 20.0, 30.0, 40.0])
volume = np.array([ 1.0, 3.0, 1.0, 2.0])
rv = ta.RollingVWAP(3)
print(rv.batch(high, low, close, volume))
# [nan, nan, 20.0, 28.333333333333332]
```
#### Node
```js
const { RollingVWAP } = require('wickra');
const high = [10, 20, 30, 40];
const low = [10, 20, 30, 40];
const close = [10, 20, 30, 40];
const volume = [ 1, 3, 1, 2];
const rv = new RollingVWAP(3);
console.log(rv.batch(high, low, close, volume));
// [ NaN, NaN, 20, 28.333333333333332 ]
```
#### WASM
```html
<script type="module">
import init, { RollingVWAP } from './pkg/wickra_wasm.js';
await init();
const rv = new RollingVWAP(3);
const out = rv.batch(
new Float64Array([10, 20, 30, 40]),
new Float64Array([10, 20, 30, 40]),
new Float64Array([10, 20, 30, 40]),
new Float64Array([ 1, 3, 1, 2]),
);
console.log(Array.from(out));
// [ NaN, NaN, 20, 28.333333333333332 ]
</script>
```
## Interpretation