Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc584dbff9 | ||
|
|
58516672d7 | ||
|
|
7b560463f6 | ||
|
|
6cd1714a9f | ||
|
|
6e45da2636 |
@@ -339,7 +339,9 @@ jobs:
|
|||||||
sbom:
|
sbom:
|
||||||
name: Generate SBOM (Python + Rust)
|
name: Generate SBOM (Python + Rust)
|
||||||
runs-on: ubuntu-latest
|
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)
|
if: (github.event_name == 'release' && github.event.action == 'published') || (github.event_name == 'workflow_dispatch' && inputs.release)
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: "20"
|
node-version: "20"
|
||||||
|
|
||||||
@@ -31,9 +31,9 @@ jobs:
|
|||||||
working-directory: wasm
|
working-directory: wasm
|
||||||
run: wasm-pack test --node
|
run: wasm-pack test --node
|
||||||
|
|
||||||
- name: Build WASM package (nodejs target)
|
- name: Build WASM package (both targets)
|
||||||
working-directory: wasm
|
working-directory: wasm
|
||||||
run: wasm-pack build --target nodejs --out-dir pkg
|
run: npm run build
|
||||||
|
|
||||||
- name: Check API manifest is current
|
- name: Check API manifest is current
|
||||||
run: python3 scripts/check_api_manifest.py
|
run: python3 scripts/check_api_manifest.py
|
||||||
@@ -42,11 +42,17 @@ jobs:
|
|||||||
working-directory: wasm
|
working-directory: wasm
|
||||||
run: node bench.js --json ../wasm_benchmark.json
|
run: node bench.js --json ../wasm_benchmark.json
|
||||||
|
|
||||||
- name: Upload WASM package artifact
|
- name: Upload WASM node package artifact
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: wasm-pkg
|
name: wasm-pkg-node
|
||||||
path: wasm/pkg/
|
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
|
- name: Upload WASM benchmark artifact
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ on:
|
|||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
release:
|
||||||
|
description: "Publish WASM package to npm"
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -13,13 +18,16 @@ jobs:
|
|||||||
publish:
|
publish:
|
||||||
name: Build and publish to npm
|
name: Build and publish to npm
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: >-
|
||||||
|
(github.event_name == 'release' && github.event.action == 'published')
|
||||||
|
|| (github.event_name == 'workflow_dispatch' && inputs.release)
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: "24"
|
node-version: "20"
|
||||||
registry-url: "https://registry.npmjs.org"
|
registry-url: "https://registry.npmjs.org"
|
||||||
|
|
||||||
- name: Install Rust and wasm-pack
|
- name: Install Rust and wasm-pack
|
||||||
@@ -28,6 +36,10 @@ jobs:
|
|||||||
targets: wasm32-unknown-unknown
|
targets: wasm32-unknown-unknown
|
||||||
- run: cargo install wasm-pack
|
- run: cargo install wasm-pack
|
||||||
|
|
||||||
|
- name: Run WASM tests
|
||||||
|
working-directory: wasm
|
||||||
|
run: wasm-pack test --node
|
||||||
|
|
||||||
- name: Build WASM package
|
- name: Build WASM package
|
||||||
run: |
|
run: |
|
||||||
cd wasm
|
cd wasm
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ env/
|
|||||||
# WASM build output
|
# WASM build output
|
||||||
wasm/pkg/
|
wasm/pkg/
|
||||||
wasm/pkg-web/
|
wasm/pkg-web/
|
||||||
|
wasm/node/
|
||||||
|
wasm/web/
|
||||||
benchmark_vs_talib.json
|
benchmark_vs_talib.json
|
||||||
wasm_benchmark.json
|
wasm_benchmark.json
|
||||||
.wasm_benchmark.prepush.json
|
.wasm_benchmark.prepush.json
|
||||||
|
|||||||
@@ -9,6 +9,19 @@ and the project uses [Semantic Versioning](https://semver.org/).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [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
|
## [1.1.1] — 2026-04-01
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
Generated
+2
-2
@@ -207,7 +207,7 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ferro_ta"
|
name = "ferro_ta"
|
||||||
version = "1.1.1"
|
version = "1.1.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"criterion",
|
"criterion",
|
||||||
"ferro_ta_core",
|
"ferro_ta_core",
|
||||||
@@ -222,7 +222,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ferro_ta_core"
|
name = "ferro_ta_core"
|
||||||
version = "1.1.1"
|
version = "1.1.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"criterion",
|
"criterion",
|
||||||
"serde",
|
"serde",
|
||||||
|
|||||||
+2
-2
@@ -5,7 +5,7 @@ resolver = "2"
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "ferro_ta"
|
name = "ferro_ta"
|
||||||
version = "1.1.1"
|
version = "1.1.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Rust-powered Python technical analysis library with a TA-Lib-compatible API"
|
description = "Rust-powered Python technical analysis library with a TA-Lib-compatible API"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@@ -30,7 +30,7 @@ ndarray = "0.16"
|
|||||||
rayon = "1.10"
|
rayon = "1.10"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
pyo3-log = "0.12"
|
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]
|
[dev-dependencies]
|
||||||
criterion = { version = "0.8", features = ["html_reports"] }
|
criterion = { version = "0.8", features = ["html_reports"] }
|
||||||
|
|||||||
+1
-1
@@ -71,6 +71,6 @@ pip install ferro-ta --no-binary ferro-ta
|
|||||||
|
|
||||||
## Known limitations
|
## 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`.
|
- Python 3.14+: untested; may work with `PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1`.
|
||||||
- 32-bit platforms: not officially supported; source builds may succeed.
|
- 32-bit platforms: not officially supported; source builds may succeed.
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{% set name = "ferro-ta" %}
|
{% set name = "ferro-ta" %}
|
||||||
{% set version = "1.1.1" %}
|
{% set version = "1.1.2" %}
|
||||||
|
|
||||||
package:
|
package:
|
||||||
name: {{ name|lower }}
|
name: {{ name|lower }}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ferro_ta_core"
|
name = "ferro_ta_core"
|
||||||
version = "1.1.1"
|
version = "1.1.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Pure Rust core indicator library — no PyO3, no numpy dependency"
|
description = "Pure Rust core indicator library — no PyO3, no numpy dependency"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ PyO3, NumPy, or Python runtime dependency, which makes it a good fit for:
|
|||||||
|
|
||||||
- Rust-native technical analysis workloads
|
- Rust-native technical analysis workloads
|
||||||
- custom services and backtesting engines
|
- custom services and backtesting engines
|
||||||
- future non-Python bindings such as WASM and other FFI layers
|
- non-Python bindings (WASM, FFI)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ferro_ta_core = "1.1.1"
|
ferro_ta_core = "1.1.2"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Design
|
## Design
|
||||||
@@ -25,12 +25,31 @@ ferro_ta_core = "1.1.1"
|
|||||||
|
|
||||||
## Modules
|
## Modules
|
||||||
|
|
||||||
- `overlap` - moving averages, MACD, Bollinger Bands
|
| Module | Functions | Highlights |
|
||||||
- `momentum` - RSI, MOM
|
|--------|-----------|------------|
|
||||||
- `volatility` - ATR, TRANGE
|
| `overlap` | 20 | SMA, EMA, WMA, DEMA, TEMA, TRIMA, KAMA, T3, BBANDS, MACD, MACDFIX, MACDEXT, SAR, SAREXT, MAMA, MIDPOINT, MIDPRICE, MA, MAVP, Hull MA |
|
||||||
- `volume` - OBV
|
| `momentum` | 26 | RSI, MOM, STOCH, STOCHF, ADX, ADXR, DX, +DI, -DI, +DM, -DM, ROC, WILLR, AROON, CCI, BOP, STOCHRSI, APO, PPO, CMO, TRIX, ULTOSC |
|
||||||
- `statistic` - STDDEV
|
| `volatility` | 3 | ATR, NATR, TRANGE |
|
||||||
- `math` - rolling SUM/MAX/MIN helpers
|
| `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
|
## Example
|
||||||
|
|
||||||
@@ -49,18 +68,7 @@ fn main() {
|
|||||||
|
|
||||||
## Relationship To `ferro-ta`
|
## Relationship To `ferro-ta`
|
||||||
|
|
||||||
The published Python package:
|
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.
|
||||||
|
|
||||||
- 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
|
|
||||||
|
|
||||||
If you only need Rust indicator functions, use `ferro_ta_core` directly.
|
If you only need Rust indicator functions, use `ferro_ta_core` directly.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
Release Notes
|
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)
|
1.1.0-audit (2026-03-28)
|
||||||
------------------------
|
------------------------
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ For source builds, packaging details, and platform notes, see
|
|||||||
Release status
|
Release status
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
These docs track package version ``1.1.1``.
|
These docs track package version ``1.1.2``.
|
||||||
|
|
||||||
- Release notes by version: :doc:`changelog`
|
- Release notes by version: :doc:`changelog`
|
||||||
- Canonical project changelog: `CHANGELOG.md <https://github.com/pratikbhadane24/ferro-ta/blob/main/CHANGELOG.md>`_
|
- Canonical project changelog: `CHANGELOG.md <https://github.com/pratikbhadane24/ferro-ta/blob/main/CHANGELOG.md>`_
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "ferro-ta"
|
name = "ferro-ta"
|
||||||
version = "1.1.1"
|
version = "1.1.2"
|
||||||
description = "Rust-powered Python technical analysis library with a TA-Lib-compatible API"
|
description = "Rust-powered Python technical analysis library with a TA-Lib-compatible API"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = { text = "MIT" }
|
license = { text = "MIT" }
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ def _extract_wasm_exports(root: Path) -> list[str]:
|
|||||||
return sorted(exports)
|
return sorted(exports)
|
||||||
|
|
||||||
# Fallback to generated declarations if source parsing did not find 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():
|
if dts_path.exists():
|
||||||
for line in dts_path.read_text(encoding="utf-8").splitlines():
|
for line in dts_path.read_text(encoding="utf-8").splitlines():
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ run_wasm() {
|
|||||||
cd wasm
|
cd wasm
|
||||||
trap 'rm -f "$benchmark_json"' EXIT
|
trap 'rm -f "$benchmark_json"' EXIT
|
||||||
run_cmd wasm-pack test --node
|
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"
|
run_cmd node bench.js --json "$benchmark_json"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ SCRIPT = WASM_DIR / "conformance_node.js"
|
|||||||
def _write_node_conformance_script(path: Path) -> None:
|
def _write_node_conformance_script(path: Path) -> None:
|
||||||
path.write_text(
|
path.write_text(
|
||||||
"""
|
"""
|
||||||
const wasm = require("./pkg/ferro_ta_wasm.js");
|
const wasm = require("./node/ferro_ta_wasm.js");
|
||||||
|
|
||||||
function toArray(x) {
|
function toArray(x) {
|
||||||
return Array.from(x, (v) => (Number.isNaN(v) ? null : Number(v)));
|
return Array.from(x, (v) => (Number.isNaN(v) ? null : Number(v)));
|
||||||
|
|||||||
@@ -950,7 +950,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ferro-ta"
|
name = "ferro-ta"
|
||||||
version = "1.1.0"
|
version = "1.1.2"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "numpy" },
|
{ name = "numpy" },
|
||||||
|
|||||||
Generated
+2
-2
@@ -49,11 +49,11 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ferro_ta_core"
|
name = "ferro_ta_core"
|
||||||
version = "1.1.1"
|
version = "1.1.2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ferro_ta_wasm"
|
name = "ferro_ta_wasm"
|
||||||
version = "1.1.1"
|
version = "1.1.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ferro_ta_core",
|
"ferro_ta_core",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ferro_ta_wasm"
|
name = "ferro_ta_wasm"
|
||||||
version = "1.1.1"
|
version = "1.1.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "WebAssembly bindings for ferro-ta technical analysis indicators"
|
description = "WebAssembly bindings for ferro-ta technical analysis indicators"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
+62
-110
@@ -1,53 +1,42 @@
|
|||||||
# ferro-ta WASM
|
# 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
|
## Install from npm
|
||||||
|
|
||||||
Once published, install the Node.js build from npm:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install ferro-ta-wasm
|
npm install ferro-ta-wasm
|
||||||
```
|
```
|
||||||
|
|
||||||
```javascript
|
```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 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(ferro.sma(close, 3)));
|
||||||
console.log('SMA:', Array.from(smaOut));
|
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
|
## Available Indicators (200+ exports)
|
||||||
> 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
|
| Category | Functions | Examples |
|
||||||
|
|----------|-----------|----------|
|
||||||
| Category | Function | Parameters | Returns |
|
| 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` |
|
||||||
| Overlap | `sma` | `close: Float64Array, timeperiod: number` | `Float64Array` |
|
| Candlestick Patterns (61) | All TA-Lib patterns | `cdlhammer`, `cdlengulfing`, `cdldoji`, `cdlmorningstar`, `cdlshootingstar`, ... (all 61) |
|
||||||
| Overlap | `ema` | `close: Float64Array, timeperiod: number` | `Float64Array` |
|
| Volatility (3) | True range, ATR | `atr`, `natr`, `trange` |
|
||||||
| Overlap | `wma` | `close: Float64Array, timeperiod: number` | `Float64Array` |
|
| Volume (6) | On-balance volume, accumulation | `obv`, `mfi`, `vwap`, `vwma`, `ad`, `adosc` |
|
||||||
| Overlap | `bbands` | `close, timeperiod, nbdevup, nbdevdn` | `Array[upper, middle, lower]` |
|
| Price Transforms (4) | Synthetic prices | `avgprice`, `medprice`, `typprice`, `wclprice` |
|
||||||
| Momentum | `rsi` | `close: Float64Array, timeperiod: number` | `Float64Array` |
|
| Cycle / Hilbert (6) | Hilbert Transform suite | `ht_trendline`, `ht_dcperiod`, `ht_dcphase`, `ht_phasor`, `ht_sine`, `ht_trendmode` |
|
||||||
| Momentum | `adx` | `high, low, close: Float64Array, timeperiod` | `Float64Array` |
|
| Statistics (10) | Regression, correlation | `stddev`, `var`, `linearreg`, `linearreg_slope`, `linearreg_intercept`, `linearreg_angle`, `tsf`, `beta_rolling`, `correl` |
|
||||||
| Momentum | `macd` | `close, fastperiod, slowperiod, signalperiod` | `Array[macd, signal, hist]` |
|
| Math (19) | Operators and transforms | `math_add`, `math_sub`, `math_mult`, `math_div`, `transform_sin`, `transform_cos`, `transform_exp`, `transform_sqrt`, ... |
|
||||||
| Momentum | `mom` | `close: Float64Array, timeperiod: number` | `Float64Array` |
|
| Extended (10) | Supertrend, channels, Ichimoku | `supertrend`, `donchian`, `keltner_channels`, `ichimoku`, `pivot_points`, `chandelier_exit`, `choppiness_index` |
|
||||||
| Momentum | `stochf` | `high, low, close, fastk_period, fastd_period` | `Array[fastk, fastd]` |
|
| Streaming API (9 classes) | Bar-by-bar stateful | `WasmStreamingSMA`, `WasmStreamingEMA`, `WasmStreamingRSI`, `WasmStreamingATR`, `WasmStreamingBBands`, `WasmStreamingMACD`, `WasmStreamingStoch`, `WasmStreamingVWAP`, `WasmStreamingSupertrend` |
|
||||||
| Volatility | `atr` | `high, low, close: Float64Array, timeperiod` | `Float64Array` |
|
| Options (14) | Pricing, Greeks, IV | `black_scholes_price`, `black_76_price`, `black_scholes_greeks`, `implied_volatility`, `iv_rank`, `smile_metrics`, ... |
|
||||||
| Volume | `obv` | `close: Float64Array, volume: Float64Array` | `Float64Array` |
|
| Futures (12) | Basis, roll, curve | `futures_basis`, `annualized_basis`, `roll_yield`, `weighted_continuous`, `calendar_spreads`, `curve_summary`, ... |
|
||||||
| Volume | `mfi` | `high, low, close, volume: Float64Array, timeperiod` | `Float64Array` |
|
| 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` |
|
||||||
### Adding more indicators
|
| 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` |
|
||||||
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.
|
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
@@ -57,91 +46,64 @@ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
|||||||
|
|
||||||
# Install wasm-pack
|
# Install wasm-pack
|
||||||
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||||
# OR via cargo:
|
|
||||||
cargo install wasm-pack
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd wasm/
|
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:
|
This produces two directories:
|
||||||
- `ferro_ta_wasm.js` — JavaScript glue code
|
- `node/` -- CommonJS glue for Node.js (`require()`)
|
||||||
- `ferro_ta_wasm_bg.wasm` — compiled WebAssembly binary
|
- `web/` -- ESM glue for browsers and web workers (`import`)
|
||||||
- `ferro_ta_wasm.d.ts` — TypeScript declarations
|
|
||||||
|
|
||||||
For a browser build:
|
Both contain `ferro_ta_wasm.js`, `ferro_ta_wasm_bg.wasm`, and `ferro_ta_wasm.d.ts`.
|
||||||
|
|
||||||
```bash
|
|
||||||
wasm-pack build --target web --out-dir pkg-web
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage (Node.js)
|
## Usage (Node.js)
|
||||||
|
|
||||||
```javascript
|
```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 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)
|
// Indicators
|
||||||
const smaOut = sma(close, 3);
|
console.log('SMA:', Array.from(sma(close, 3)));
|
||||||
console.log('SMA:', Array.from(smaOut)); // [ NaN, NaN, 44.193, ... ]
|
console.log('RSI:', Array.from(rsi(close, 5)));
|
||||||
|
|
||||||
// RSI (period 5)
|
// Multi-output
|
||||||
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]
|
|
||||||
const [upper, middle, lower] = bbands(close, 5, 2.0, 2.0);
|
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]
|
// Streaming (bar-by-bar)
|
||||||
const [macdLine, signalLine, histogram] = macd(close, 3, 5, 2);
|
const stream = new WasmStreamingSMA(3);
|
||||||
console.log('MACD:', Array.from(macdLine));
|
for (const price of close) {
|
||||||
console.log('Signal:', Array.from(signalLine));
|
console.log('streaming SMA:', stream.update(price));
|
||||||
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));
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage (Browser)
|
## Usage (Browser)
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import init, { sma, macd } from './pkg-web/ferro_ta_wasm.js';
|
import init, { sma, rsi, macd } from './pkg-web/ferro_ta_wasm.js';
|
||||||
await init(); // loads the .wasm binary
|
await init();
|
||||||
|
|
||||||
const close = new Float64Array([44.34, 44.09, 44.15, 43.61, 44.33]);
|
const close = new Float64Array([44.34, 44.09, 44.15, 43.61, 44.33]);
|
||||||
const smaOut = sma(close, 3);
|
console.log('SMA:', Array.from(sma(close, 3)));
|
||||||
console.log('SMA:', Array.from(smaOut));
|
|
||||||
|
|
||||||
// MACD
|
|
||||||
const [macdLine, signal, hist] = macd(close, 3, 5, 2);
|
|
||||||
console.log('MACD line:', Array.from(macdLine));
|
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -152,22 +114,12 @@ cd wasm/
|
|||||||
wasm-pack test --node
|
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
|
## Limitations
|
||||||
|
|
||||||
- Only 12 indicators are currently exposed (SMA, EMA, WMA, BBANDS, RSI, ADX, MACD, MOM, STOCHF, ATR, OBV, MFI).
|
- Large arrays (> 10M bars) may be slow due to JS-WASM memory copies. For high-throughput use cases prefer the Python (PyO3) binding.
|
||||||
Additional indicators will be added following the same pattern in `src/lib.rs`.
|
- 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
|
- The npm package ships both Node.js (`require`) and browser/web worker (`import`) builds. Conditional exports in `package.json` select the right one automatically.
|
||||||
use cases prefer the Python (PyO3) binding.
|
|
||||||
- WASM does not support multi-threading natively in browsers (SharedArrayBuffer requires
|
## License
|
||||||
COOP/COEP headers).
|
|
||||||
|
MIT
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ const fs = require("node:fs");
|
|||||||
const path = require("node:path");
|
const path = require("node:path");
|
||||||
const { performance } = require("node:perf_hooks");
|
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) {
|
function parseArgs(argv) {
|
||||||
const args = { bars: 100000, json: null };
|
const args = { bars: 100000, json: null };
|
||||||
|
|||||||
+21
-6
@@ -1,14 +1,29 @@
|
|||||||
{
|
{
|
||||||
"name": "ferro-ta-wasm",
|
"name": "ferro-ta-wasm",
|
||||||
"version": "1.1.1",
|
"version": "1.1.2",
|
||||||
"description": "WebAssembly bindings for ferro-ta technical analysis indicators",
|
"description": "WebAssembly bindings for ferro-ta technical analysis indicators",
|
||||||
"main": "pkg/ferro_ta_wasm.js",
|
"main": "node/ferro_ta_wasm.js",
|
||||||
"types": "pkg/ferro_ta_wasm.d.ts",
|
"module": "web/ferro_ta_wasm.js",
|
||||||
"files": ["pkg"],
|
"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": {
|
"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",
|
"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"
|
"test": "wasm-pack test --node"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
Reference in New Issue
Block a user