From 7b560463f6438aab57bbf5a73054e4c492aa0de4 Mon Sep 17 00:00:00 2001 From: Pratik Bhadane Date: Wed, 1 Apr 2026 23:47:46 +0530 Subject: [PATCH] chore: update version to 1.1.2 and enhance WASM package - Bumped version numbers across Cargo.toml, Cargo.lock, pyproject.toml, and conda/meta.yaml to 1.1.2. - Updated .gitignore to include new WASM build directories for Node.js and web. - Enhanced WASM npm package to support both Node.js and browser builds with conditional exports. - Improved CI workflows for WASM publishing and testing. - Updated documentation to reflect new features and full indicator parity in the WASM package. --- .github/workflows/CI.yml | 4 +- .github/workflows/ci-wasm.yml | 18 +- .github/workflows/wasm-publish.yml | 14 +- .gitignore | 2 + CHANGELOG.md | 13 ++ Cargo.lock | 4 +- Cargo.toml | 4 +- PLATFORMS.md | 2 +- conda/meta.yaml | 2 +- crates/ferro_ta_core/Cargo.toml | 2 +- crates/ferro_ta_core/README.md | 48 +++-- docs/changelog.rst | 2 +- docs/support_matrix.rst | 2 +- pyproject.toml | 2 +- scripts/build_api_manifest.py | 2 +- scripts/pre_push_checks.sh | 2 +- .../integration/test_wasm_node_conformance.py | 2 +- wasm/Cargo.toml | 2 +- wasm/README.md | 172 +++++++----------- wasm/bench.js | 2 +- wasm/package.json | 27 ++- 21 files changed, 169 insertions(+), 159 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 5493ee6..d4788e5 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -339,7 +339,9 @@ jobs: sbom: name: Generate SBOM (Python + Rust) runs-on: ubuntu-latest - needs: publish + needs: + - publish + - publish-cratesio if: (github.event_name == 'release' && github.event.action == 'published') || (github.event_name == 'workflow_dispatch' && inputs.release) permissions: contents: write diff --git a/.github/workflows/ci-wasm.yml b/.github/workflows/ci-wasm.yml index 8a26669..cc44c07 100644 --- a/.github/workflows/ci-wasm.yml +++ b/.github/workflows/ci-wasm.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v6 - name: Set up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: "20" @@ -31,9 +31,9 @@ jobs: working-directory: wasm run: wasm-pack test --node - - name: Build WASM package (nodejs target) + - name: Build WASM package (both targets) working-directory: wasm - run: wasm-pack build --target nodejs --out-dir pkg + run: npm run build - name: Check API manifest is current run: python3 scripts/check_api_manifest.py @@ -42,11 +42,17 @@ jobs: working-directory: wasm run: node bench.js --json ../wasm_benchmark.json - - name: Upload WASM package artifact + - name: Upload WASM node package artifact uses: actions/upload-artifact@v7 with: - name: wasm-pkg - path: wasm/pkg/ + name: wasm-pkg-node + path: wasm/node/ + + - name: Upload WASM web package artifact + uses: actions/upload-artifact@v7 + with: + name: wasm-pkg-web + path: wasm/web/ - name: Upload WASM benchmark artifact uses: actions/upload-artifact@v7 diff --git a/.github/workflows/wasm-publish.yml b/.github/workflows/wasm-publish.yml index a56702a..4bb13df 100644 --- a/.github/workflows/wasm-publish.yml +++ b/.github/workflows/wasm-publish.yml @@ -4,6 +4,11 @@ on: release: types: [published] workflow_dispatch: + inputs: + release: + description: "Publish WASM package to npm" + type: boolean + default: true permissions: contents: read @@ -13,13 +18,16 @@ jobs: publish: name: Build and publish to npm runs-on: ubuntu-latest + if: >- + (github.event_name == 'release' && github.event.action == 'published') + || (github.event_name == 'workflow_dispatch' && inputs.release) steps: - uses: actions/checkout@v6 - name: Setup Node.js uses: actions/setup-node@v6 with: - node-version: "24" + node-version: "20" registry-url: "https://registry.npmjs.org" - name: Install Rust and wasm-pack @@ -28,6 +36,10 @@ jobs: targets: wasm32-unknown-unknown - run: cargo install wasm-pack + - name: Run WASM tests + working-directory: wasm + run: wasm-pack test --node + - name: Build WASM package run: | cd wasm diff --git a/.gitignore b/.gitignore index b82768b..77ff27d 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,8 @@ env/ # WASM build output wasm/pkg/ wasm/pkg-web/ +wasm/node/ +wasm/web/ benchmark_vs_talib.json wasm_benchmark.json .wasm_benchmark.prepush.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 28deef2..7780c54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,19 @@ and the project uses [Semantic Versioning](https://semver.org/). ## [Unreleased] +## [1.1.2] — 2026-04-01 + +### Changed + +- WASM npm package now ships both Node.js (CommonJS) and browser/web worker + (ESM) builds via conditional exports in package.json. +- Fixed wasm-publish.yml: added job condition, workflow_dispatch inputs, and + pre-publish test gate. +- Fixed CI.yml: SBOM job now waits for both PyPI and crates.io publish. +- Aligned Node.js version to 20 across all CI workflows. +- Rewrote wasm/README.md and ferro_ta_core/README.md to reflect full feature + parity (200+ WASM exports, 22 core modules). + ## [1.1.1] — 2026-04-01 ### Added diff --git a/Cargo.lock b/Cargo.lock index da98fde..484076c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -207,7 +207,7 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "ferro_ta" -version = "1.1.1" +version = "1.1.2" dependencies = [ "criterion", "ferro_ta_core", @@ -222,7 +222,7 @@ dependencies = [ [[package]] name = "ferro_ta_core" -version = "1.1.1" +version = "1.1.2" dependencies = [ "criterion", "serde", diff --git a/Cargo.toml b/Cargo.toml index e32ae38..51ec52c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ resolver = "2" [package] name = "ferro_ta" -version = "1.1.1" +version = "1.1.2" edition = "2021" description = "Rust-powered Python technical analysis library with a TA-Lib-compatible API" license = "MIT" @@ -30,7 +30,7 @@ ndarray = "0.16" rayon = "1.10" log = "0.4" pyo3-log = "0.12" -ferro_ta_core = { path = "crates/ferro_ta_core", version = "1.1.1", features = ["serde"] } +ferro_ta_core = { path = "crates/ferro_ta_core", version = "1.1.2", features = ["serde"] } [dev-dependencies] criterion = { version = "0.8", features = ["html_reports"] } diff --git a/PLATFORMS.md b/PLATFORMS.md index 25b8055..3829077 100644 --- a/PLATFORMS.md +++ b/PLATFORMS.md @@ -71,6 +71,6 @@ pip install ferro-ta --no-binary ferro-ta ## Known limitations -- WASM binding: only 6 indicators exposed (see `wasm/README.md`). +- WASM binding: full feature parity with 200+ exports including all TA-Lib indicators, candlestick patterns, streaming API, options, futures, and backtesting (see `wasm/README.md`). - Python 3.14+: untested; may work with `PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1`. - 32-bit platforms: not officially supported; source builds may succeed. diff --git a/conda/meta.yaml b/conda/meta.yaml index 6acd8a9..86a1c37 100644 --- a/conda/meta.yaml +++ b/conda/meta.yaml @@ -1,5 +1,5 @@ {% set name = "ferro-ta" %} -{% set version = "1.1.1" %} +{% set version = "1.1.2" %} package: name: {{ name|lower }} diff --git a/crates/ferro_ta_core/Cargo.toml b/crates/ferro_ta_core/Cargo.toml index 0b836d7..42bb469 100644 --- a/crates/ferro_ta_core/Cargo.toml +++ b/crates/ferro_ta_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ferro_ta_core" -version = "1.1.1" +version = "1.1.2" edition = "2021" description = "Pure Rust core indicator library — no PyO3, no numpy dependency" license = "MIT" diff --git a/crates/ferro_ta_core/README.md b/crates/ferro_ta_core/README.md index 7ca372e..a294394 100644 --- a/crates/ferro_ta_core/README.md +++ b/crates/ferro_ta_core/README.md @@ -7,13 +7,13 @@ PyO3, NumPy, or Python runtime dependency, which makes it a good fit for: - Rust-native technical analysis workloads - custom services and backtesting engines -- future non-Python bindings such as WASM and other FFI layers +- non-Python bindings (WASM, FFI) ## Installation ```toml [dependencies] -ferro_ta_core = "1.1.1" +ferro_ta_core = "1.1.2" ``` ## Design @@ -25,12 +25,31 @@ ferro_ta_core = "1.1.1" ## Modules -- `overlap` - moving averages, MACD, Bollinger Bands -- `momentum` - RSI, MOM -- `volatility` - ATR, TRANGE -- `volume` - OBV -- `statistic` - STDDEV -- `math` - rolling SUM/MAX/MIN helpers +| Module | Functions | Highlights | +|--------|-----------|------------| +| `overlap` | 20 | SMA, EMA, WMA, DEMA, TEMA, TRIMA, KAMA, T3, BBANDS, MACD, MACDFIX, MACDEXT, SAR, SAREXT, MAMA, MIDPOINT, MIDPRICE, MA, MAVP, Hull MA | +| `momentum` | 26 | RSI, MOM, STOCH, STOCHF, ADX, ADXR, DX, +DI, -DI, +DM, -DM, ROC, WILLR, AROON, CCI, BOP, STOCHRSI, APO, PPO, CMO, TRIX, ULTOSC | +| `volatility` | 3 | ATR, NATR, TRANGE | +| `volume` | 4 | OBV, MFI, AD, ADOSC | +| `pattern` | 61 | All TA-Lib candlestick patterns (CDL2CROWS through CDLXSIDEGAP3METHODS) | +| `statistic` | 9 | STDDEV, VAR, LINEARREG, LINEARREG_SLOPE/INTERCEPT/ANGLE, TSF, BETA, CORREL | +| `math` | 24 | Rolling SUM/MAX/MIN/MAXINDEX/MININDEX, element-wise ADD/SUB/MULT/DIV, 15 transforms (trig, exp, log, sqrt, ceil, floor) | +| `price_transform` | 4 | AVGPRICE, MEDPRICE, TYPPRICE, WCLPRICE | +| `cycle` | 7 | Hilbert Transform: TRENDLINE, DCPERIOD, DCPHASE, PHASOR, SINE, TRENDMODE | +| `extended` | 10 | VWAP, VWMA, Supertrend, Donchian, Keltner, Ichimoku, Pivot Points, Hull MA, Chandelier Exit, Choppiness Index | +| `streaming` | 9 | Stateful bar-by-bar: SMA, EMA, RSI, ATR, BBands, MACD, Stoch, VWAP, Supertrend | +| `batch` | 8 | Vectorized multi-column: batch_sma/ema/rsi/atr/stoch/adx, run_close/hlc_indicators | +| `backtest` | 19 | Signal generators, close-only and OHLCV engines, walk-forward, Monte Carlo, performance metrics | +| `options` | 18 | Black-Scholes/Black-76 pricing, Greeks, implied volatility, IV rank/percentile/zscore, smile metrics, chain analytics | +| `futures` | 14 | Basis, annualized basis, carry, roll (weighted/back-adjusted/ratio), curve analysis, synthetic forward/spot | +| `portfolio` | 10 | Beta, correlation matrix, drawdown, relative strength, spread, ratio, z-score, portfolio volatility | +| `signals` | 4 | Rank values, compose rank, top/bottom N indices | +| `alerts` | 3 | Threshold crossings, cross detection, alert bar collection | +| `regime` | 4 | ADX regime, combined regime, CUSUM breaks, variance breaks | +| `aggregation` | 3 | Tick bars, volume bars, time bars from trade data | +| `resampling` | 2 | Volume bars, OHLCV aggregation by label | +| `chunked` | 4 | Trim overlap, stitch chunks, make chunk ranges, forward fill NaN | +| `crypto` | 3 | Funding cumulative PnL, continuous bar labels, session boundaries | ## Example @@ -49,18 +68,7 @@ fn main() { ## Relationship To `ferro-ta` -The published Python package: - -- crate: `ferro_ta` -- PyPI package: `ferro-ta` - -wraps this crate with PyO3 bindings and adds: - -- NumPy conversion -- pandas/polars wrappers -- streaming classes -- batch helpers -- higher-level Python tooling +The published Python package (`ferro-ta` on PyPI) wraps this crate with PyO3 bindings and adds NumPy conversion, pandas/polars wrappers, and higher-level Python tooling. The WASM package (`ferro-ta-wasm` on npm) also wraps this crate with full feature parity. If you only need Rust indicator functions, use `ferro_ta_core` directly. diff --git a/docs/changelog.rst b/docs/changelog.rst index 532c4d7..25ac6ad 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,7 +1,7 @@ Release Notes ============= -These docs track package version ``1.1.1``. +These docs track package version ``1.1.2``. 1.1.0-audit (2026-03-28) ------------------------ diff --git a/docs/support_matrix.rst b/docs/support_matrix.rst index ae3f970..988624d 100644 --- a/docs/support_matrix.rst +++ b/docs/support_matrix.rst @@ -180,7 +180,7 @@ For source builds, packaging details, and platform notes, see Release status -------------- -These docs track package version ``1.1.1``. +These docs track package version ``1.1.2``. - Release notes by version: :doc:`changelog` - Canonical project changelog: `CHANGELOG.md `_ diff --git a/pyproject.toml b/pyproject.toml index ad9c39e..df807a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "ferro-ta" -version = "1.1.1" +version = "1.1.2" description = "Rust-powered Python technical analysis library with a TA-Lib-compatible API" readme = "README.md" license = { text = "MIT" } diff --git a/scripts/build_api_manifest.py b/scripts/build_api_manifest.py index 279ba26..9f92a3e 100644 --- a/scripts/build_api_manifest.py +++ b/scripts/build_api_manifest.py @@ -192,7 +192,7 @@ def _extract_wasm_exports(root: Path) -> list[str]: return sorted(exports) # Fallback to generated declarations if source parsing did not find exports. - dts_path = root / "wasm" / "pkg" / "ferro_ta_wasm.d.ts" + dts_path = root / "wasm" / "node" / "ferro_ta_wasm.d.ts" if dts_path.exists(): for line in dts_path.read_text(encoding="utf-8").splitlines(): line = line.strip() diff --git a/scripts/pre_push_checks.sh b/scripts/pre_push_checks.sh index 86cff24..71bab4a 100755 --- a/scripts/pre_push_checks.sh +++ b/scripts/pre_push_checks.sh @@ -132,7 +132,7 @@ run_wasm() { cd wasm trap 'rm -f "$benchmark_json"' EXIT run_cmd wasm-pack test --node - run_cmd wasm-pack build --target nodejs --out-dir pkg + run_cmd npm run build run_cmd node bench.js --json "$benchmark_json" ) } diff --git a/tests/integration/test_wasm_node_conformance.py b/tests/integration/test_wasm_node_conformance.py index 8041988..1f0f48b 100644 --- a/tests/integration/test_wasm_node_conformance.py +++ b/tests/integration/test_wasm_node_conformance.py @@ -19,7 +19,7 @@ SCRIPT = WASM_DIR / "conformance_node.js" def _write_node_conformance_script(path: Path) -> None: path.write_text( """ -const wasm = require("./pkg/ferro_ta_wasm.js"); +const wasm = require("./node/ferro_ta_wasm.js"); function toArray(x) { return Array.from(x, (v) => (Number.isNaN(v) ? null : Number(v))); diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml index 3313837..6d4059a 100644 --- a/wasm/Cargo.toml +++ b/wasm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ferro_ta_wasm" -version = "1.1.1" +version = "1.1.2" edition = "2021" description = "WebAssembly bindings for ferro-ta technical analysis indicators" license = "MIT" diff --git a/wasm/README.md b/wasm/README.md index 18c862c..a78c0a7 100644 --- a/wasm/README.md +++ b/wasm/README.md @@ -1,53 +1,42 @@ # ferro-ta WASM -WebAssembly bindings for the [ferro-ta](https://github.com/pratikbhadane24/ferro-ta) technical analysis library. +WebAssembly bindings for the [ferro-ta](https://github.com/pratikbhadane24/ferro-ta) technical analysis library. Full feature parity with the Python and Rust core packages. ## Install from npm -Once published, install the Node.js build from npm: - ```bash npm install ferro-ta-wasm ``` ```javascript -const { sma, ema, wma, rsi, adx, mfi, bbands, atr, obv, macd } = require('ferro-ta-wasm'); +const ferro = require('ferro-ta-wasm'); const close = new Float64Array([44.34, 44.09, 44.15, 43.61, 44.33, 44.83, 45.10]); -const smaOut = sma(close, 3); -console.log('SMA:', Array.from(smaOut)); +console.log('SMA:', Array.from(ferro.sma(close, 3))); +console.log('RSI:', Array.from(ferro.rsi(close, 14))); ``` -> **Decision**: We chose WebAssembly (wasm-bindgen / wasm-pack) as the second binding because it runs in -> browsers *and* Node.js without any native addons, and shares zero unsafe FFI surface with the Python -> build. Node.js users get a pure-JS entry point; browser users get the same `.wasm` file. +## Available Indicators (200+ exports) -## Available Indicators - -| Category | Function | Parameters | Returns | -|------------|---------------|----------------------------------------------------|---------| -| Overlap | `sma` | `close: Float64Array, timeperiod: number` | `Float64Array` | -| Overlap | `ema` | `close: Float64Array, timeperiod: number` | `Float64Array` | -| Overlap | `wma` | `close: Float64Array, timeperiod: number` | `Float64Array` | -| Overlap | `bbands` | `close, timeperiod, nbdevup, nbdevdn` | `Array[upper, middle, lower]` | -| Momentum | `rsi` | `close: Float64Array, timeperiod: number` | `Float64Array` | -| Momentum | `adx` | `high, low, close: Float64Array, timeperiod` | `Float64Array` | -| Momentum | `macd` | `close, fastperiod, slowperiod, signalperiod` | `Array[macd, signal, hist]` | -| Momentum | `mom` | `close: Float64Array, timeperiod: number` | `Float64Array` | -| Momentum | `stochf` | `high, low, close, fastk_period, fastd_period` | `Array[fastk, fastd]` | -| Volatility | `atr` | `high, low, close: Float64Array, timeperiod` | `Float64Array` | -| Volume | `obv` | `close: Float64Array, volume: Float64Array` | `Float64Array` | -| Volume | `mfi` | `high, low, close, volume: Float64Array, timeperiod` | `Float64Array` | - -### Adding more indicators - -WASM exports live in `src/lib.rs` and can either implement logic directly or delegate to `ferro_ta_core`. -To add a new indicator: - -1. Add a `#[wasm_bindgen]` export in `src/lib.rs` (prefer delegating to `ferro_ta_core` where possible). -2. Add at least two `#[wasm_bindgen_test]` tests covering output length and a known value. -3. Update this README table. -4. Run `wasm-pack test --node` to verify. +| Category | Functions | Examples | +|----------|-----------|----------| +| Overlap Studies (20) | Moving averages, bands, SAR | `sma`, `ema`, `wma`, `dema`, `tema`, `trima`, `kama`, `t3`, `bbands`, `macd`, `macdfix`, `macdext`, `sar`, `sarext`, `mama`, `midpoint`, `midprice`, `ma`, `mavp`, `hull_ma` | +| Momentum (26) | Oscillators, directional movement | `rsi`, `mom`, `stoch`, `stochf`, `adx`, `adxr`, `dx`, `plus_di`, `minus_di`, `roc`, `willr`, `aroon`, `aroonosc`, `cci`, `bop`, `stochrsi`, `apo`, `ppo`, `cmo`, `trix_indicator`, `ultosc` | +| Candlestick Patterns (61) | All TA-Lib patterns | `cdlhammer`, `cdlengulfing`, `cdldoji`, `cdlmorningstar`, `cdlshootingstar`, ... (all 61) | +| Volatility (3) | True range, ATR | `atr`, `natr`, `trange` | +| Volume (6) | On-balance volume, accumulation | `obv`, `mfi`, `vwap`, `vwma`, `ad`, `adosc` | +| Price Transforms (4) | Synthetic prices | `avgprice`, `medprice`, `typprice`, `wclprice` | +| Cycle / Hilbert (6) | Hilbert Transform suite | `ht_trendline`, `ht_dcperiod`, `ht_dcphase`, `ht_phasor`, `ht_sine`, `ht_trendmode` | +| Statistics (10) | Regression, correlation | `stddev`, `var`, `linearreg`, `linearreg_slope`, `linearreg_intercept`, `linearreg_angle`, `tsf`, `beta_rolling`, `correl` | +| Math (19) | Operators and transforms | `math_add`, `math_sub`, `math_mult`, `math_div`, `transform_sin`, `transform_cos`, `transform_exp`, `transform_sqrt`, ... | +| Extended (10) | Supertrend, channels, Ichimoku | `supertrend`, `donchian`, `keltner_channels`, `ichimoku`, `pivot_points`, `chandelier_exit`, `choppiness_index` | +| Streaming API (9 classes) | Bar-by-bar stateful | `WasmStreamingSMA`, `WasmStreamingEMA`, `WasmStreamingRSI`, `WasmStreamingATR`, `WasmStreamingBBands`, `WasmStreamingMACD`, `WasmStreamingStoch`, `WasmStreamingVWAP`, `WasmStreamingSupertrend` | +| Options (14) | Pricing, Greeks, IV | `black_scholes_price`, `black_76_price`, `black_scholes_greeks`, `implied_volatility`, `iv_rank`, `smile_metrics`, ... | +| Futures (12) | Basis, roll, curve | `futures_basis`, `annualized_basis`, `roll_yield`, `weighted_continuous`, `calendar_spreads`, `curve_summary`, ... | +| Backtesting (9) | Signal generation, engines | `backtest_core`, `backtest_ohlcv`, `rsi_threshold_signals`, `macd_crossover_signals`, `walk_forward_indices`, `monte_carlo_bootstrap`, ... | +| Alerts & Regime (7) | Signals and regime detection | `check_threshold`, `check_cross`, `regime_adx`, `regime_combined`, `detect_breaks_cusum` | +| Batch & Portfolio (9) | Multi-asset analytics | `batch_sma`, `batch_ema`, `batch_rsi`, `correlation_matrix`, `portfolio_volatility`, `drawdown_series` | +| Aggregation (8) | Tick/volume/time bars | `aggregate_tick_bars`, `aggregate_volume_bars_ticks`, `volume_bars`, `ohlcv_agg` | ## Prerequisites @@ -57,91 +46,64 @@ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # Install wasm-pack curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -# OR via cargo: -cargo install wasm-pack ``` ## Build ```bash cd wasm/ -wasm-pack build --target nodejs --out-dir pkg + +# Build both Node.js and web targets +npm run build + +# Or build individually: +npm run build:node # → node/ +npm run build:web # → web/ ``` -This produces a `pkg/` directory containing: -- `ferro_ta_wasm.js` — JavaScript glue code -- `ferro_ta_wasm_bg.wasm` — compiled WebAssembly binary -- `ferro_ta_wasm.d.ts` — TypeScript declarations +This produces two directories: +- `node/` -- CommonJS glue for Node.js (`require()`) +- `web/` -- ESM glue for browsers and web workers (`import`) -For a browser build: - -```bash -wasm-pack build --target web --out-dir pkg-web -``` +Both contain `ferro_ta_wasm.js`, `ferro_ta_wasm_bg.wasm`, and `ferro_ta_wasm.d.ts`. ## Usage (Node.js) ```javascript -const { sma, ema, wma, rsi, adx, mfi, bbands, atr, obv, macd } = require('./pkg/ferro_ta_wasm.js'); +const { + sma, ema, rsi, bbands, macd, atr, adx, obv, mfi, + cdlhammer, cdlengulfing, + WasmStreamingSMA, +} = require('ferro-ta-wasm'); const close = new Float64Array([44.34, 44.09, 44.15, 43.61, 44.33, 44.83, 45.10]); +const high = new Float64Array([45.0, 46.0, 47.0, 46.0, 45.0, 44.0, 45.0]); +const low = new Float64Array([43.0, 44.0, 45.0, 44.0, 43.0, 42.0, 43.0]); -// Simple Moving Average (period 3) -const smaOut = sma(close, 3); -console.log('SMA:', Array.from(smaOut)); // [ NaN, NaN, 44.193, ... ] +// Indicators +console.log('SMA:', Array.from(sma(close, 3))); +console.log('RSI:', Array.from(rsi(close, 5))); -// RSI (period 5) -const rsiOut = rsi(close, 5); -console.log('RSI:', Array.from(rsiOut)); - -// WMA (period 5) -const wmaOut = wma(close, 5); -console.log('WMA:', Array.from(wmaOut)); - -// Bollinger Bands (period 5, ±2σ) — returns [upper, middle, lower] +// Multi-output const [upper, middle, lower] = bbands(close, 5, 2.0, 2.0); -console.log('BBANDS upper:', Array.from(upper)); +const [macdLine, signal, hist] = macd(close, 3, 5, 2); -// MACD (fast=3, slow=5, signal=2) — returns [macd_line, signal_line, histogram] -const [macdLine, signalLine, histogram] = macd(close, 3, 5, 2); -console.log('MACD:', Array.from(macdLine)); -console.log('Signal:', Array.from(signalLine)); -console.log('Histogram:', Array.from(histogram)); - -// ATR (period 3) -const high = new Float64Array([45.0, 46.0, 47.0, 46.0, 45.0, 44.0, 45.0]); -const low = new Float64Array([43.0, 44.0, 45.0, 44.0, 43.0, 42.0, 43.0]); -const atrOut = atr(high, low, close, 3); -console.log('ATR:', Array.from(atrOut)); - -// ADX (period 3) -const adxOut = adx(high, low, close, 3); -console.log('ADX:', Array.from(adxOut)); - -// OBV -const volume = new Float64Array([1000, 1200, 900, 1500, 800, 600, 700]); -const obvOut = obv(close, volume); -console.log('OBV:', Array.from(obvOut)); - -// MFI (period 3) -const mfiOut = mfi(high, low, close, volume, 3); -console.log('MFI:', Array.from(mfiOut)); +// Streaming (bar-by-bar) +const stream = new WasmStreamingSMA(3); +for (const price of close) { + console.log('streaming SMA:', stream.update(price)); +} ``` ## Usage (Browser) ```html ``` @@ -152,22 +114,12 @@ cd wasm/ wasm-pack test --node ``` -## CI Artifact - -Every CI run on `main` builds the WASM package and uploads it as a GitHub Actions -artifact named `wasm-pkg`. To download the latest pre-built package without building -from source: - -1. Go to the [Actions tab](https://github.com/pratikbhadane24/ferro-ta/actions). -2. Open the latest successful CI run. -3. Download the `wasm-pkg` artifact from the **Artifacts** section. -4. Unzip and use `pkg/ferro_ta_wasm.js` directly in your project. - ## Limitations -- Only 12 indicators are currently exposed (SMA, EMA, WMA, BBANDS, RSI, ADX, MACD, MOM, STOCHF, ATR, OBV, MFI). - Additional indicators will be added following the same pattern in `src/lib.rs`. -- Large arrays (> 10M bars) may be slow due to JS↔WASM memory copies. For high-throughput - use cases prefer the Python (PyO3) binding. -- WASM does not support multi-threading natively in browsers (SharedArrayBuffer requires - COOP/COEP headers). +- Large arrays (> 10M bars) may be slow due to JS-WASM memory copies. For high-throughput use cases prefer the Python (PyO3) binding. +- WASM does not support multi-threading natively in browsers (SharedArrayBuffer requires COOP/COEP headers). +- The npm package ships both Node.js (`require`) and browser/web worker (`import`) builds. Conditional exports in `package.json` select the right one automatically. + +## License + +MIT diff --git a/wasm/bench.js b/wasm/bench.js index 59312a8..2fd0995 100644 --- a/wasm/bench.js +++ b/wasm/bench.js @@ -2,7 +2,7 @@ const fs = require("node:fs"); const path = require("node:path"); const { performance } = require("node:perf_hooks"); -const wasm = require("./pkg/ferro_ta_wasm.js"); +const wasm = require("./node/ferro_ta_wasm.js"); function parseArgs(argv) { const args = { bars: 100000, json: null }; diff --git a/wasm/package.json b/wasm/package.json index dbdccc4..fe796e3 100644 --- a/wasm/package.json +++ b/wasm/package.json @@ -1,14 +1,29 @@ { "name": "ferro-ta-wasm", - "version": "1.1.1", + "version": "1.1.2", "description": "WebAssembly bindings for ferro-ta technical analysis indicators", - "main": "pkg/ferro_ta_wasm.js", - "types": "pkg/ferro_ta_wasm.d.ts", - "files": ["pkg"], + "main": "node/ferro_ta_wasm.js", + "module": "web/ferro_ta_wasm.js", + "types": "node/ferro_ta_wasm.d.ts", + "exports": { + ".": { + "node": { + "types": "./node/ferro_ta_wasm.d.ts", + "require": "./node/ferro_ta_wasm.js" + }, + "default": { + "types": "./web/ferro_ta_wasm.d.ts", + "import": "./web/ferro_ta_wasm.js" + } + } + }, + "files": ["node", "web"], "scripts": { - "build": "wasm-pack build --target nodejs --out-dir pkg", + "build": "npm run build:node && npm run build:web", + "build:node": "wasm-pack build --target nodejs --out-dir node && node -e \"require('fs').rmSync('node/.gitignore', { force: true }); require('fs').rmSync('node/package.json', { force: true });\"", + "build:web": "wasm-pack build --target web --out-dir web && node -e \"require('fs').rmSync('web/.gitignore', { force: true }); require('fs').rmSync('web/package.json', { force: true });\"", "bench": "node bench.js", - "prepack": "npm run build && node -e \"require('fs').rmSync('pkg/.gitignore', { force: true })\"", + "prepack": "npm run build", "test": "wasm-pack test --node" }, "license": "MIT",