Prepare the first public 1.0.0 release and finish the remaining CI hardening work. Highlights: - align Python, Rust, WASM, Conda, API, MCP, and docs version metadata to 1.0.0 - promote package metadata to Production/Stable and update stability/versioning docs for the stable series - move the accumulated Unreleased notes into a dated 1.0.0 changelog section and keep a fresh top-level Unreleased block - strengthen the changelog checker so it validates a single top-level Unreleased section - fix the CI/package support mismatch by declaring Python >=3.10 consistently and gating pandas-ta extras to Python 3.12+ - restore Sphinx autodoc compatibility for documented ferro_ta.<module> imports by registering module aliases - make the TA-Lib benchmark guardrail less flaky by checking median and tail-percentile speedups instead of failing on a single mild outlier - switch PyPI publishing to OIDC-only trusted publishing and wire the changelog check into the required CI gate - apply the Ruff-driven cleanup across the Python and test tree and refresh uv/cargo lockfiles Validated locally: - python3 scripts/check_changelog.py - uv run --with ruff ruff check python tests - uv run --with ruff ruff format --check python tests - uv lock --check - sphinx-build -b html docs docs/_build -W --keep-going - build/install the ferro_ta 1.0.0 wheel successfully
ferro-ta WASM
WebAssembly bindings for the ferro-ta technical analysis library.
Install from npm
Once published, install the Node.js build from npm:
npm install ferro-ta-wasm
const { sma, ema, rsi, bbands, atr, obv, macd } = 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));
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
.wasmfile.
Available Indicators
| Category | Function | Parameters | Returns |
|---|---|---|---|
| Overlap | sma |
close: Float64Array, timeperiod: number |
Float64Array |
| Overlap | ema |
close: Float64Array, timeperiod: number |
Float64Array |
| Overlap | bbands |
close, timeperiod, nbdevup, nbdevdn |
Array[upper, middle, lower] |
| Momentum | rsi |
close: Float64Array, timeperiod: number |
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 |
Adding more indicators
All implementations are self-contained in src/lib.rs — no external crate dependency needed.
To add a new indicator:
- Implement the algorithm in a
#[wasm_bindgen]function insrc/lib.rs. - Add at least two
#[wasm_bindgen_test]tests covering output length and a known value. - Update this README table.
- Run
wasm-pack test --nodeto verify.
Prerequisites
# Install Rust (if not already present)
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
cd wasm/
wasm-pack build --target nodejs --out-dir pkg
This produces a pkg/ directory containing:
ferro_ta_wasm.js— JavaScript glue codeferro_ta_wasm_bg.wasm— compiled WebAssembly binaryferro_ta_wasm.d.ts— TypeScript declarations
For a browser build:
wasm-pack build --target web --out-dir pkg-web
Usage (Node.js)
const { sma, ema, rsi, bbands, atr, obv, macd } = require('./pkg/ferro_ta_wasm.js');
const close = new Float64Array([44.34, 44.09, 44.15, 43.61, 44.33, 44.83, 45.10]);
// Simple Moving Average (period 3)
const smaOut = sma(close, 3);
console.log('SMA:', Array.from(smaOut)); // [ NaN, NaN, 44.193, ... ]
// RSI (period 5)
const rsiOut = rsi(close, 5);
console.log('RSI:', Array.from(rsiOut));
// Bollinger Bands (period 5, ±2σ) — returns [upper, middle, lower]
const [upper, middle, lower] = bbands(close, 5, 2.0, 2.0);
console.log('BBANDS upper:', Array.from(upper));
// 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));
// OBV
const volume = new Float64Array([1000, 1200, 900, 1500, 800, 600, 700]);
const obvOut = obv(close, volume);
console.log('OBV:', Array.from(obvOut));
Usage (Browser)
<script type="module">
import init, { sma, macd } from './pkg-web/ferro_ta_wasm.js';
await init(); // loads the .wasm binary
const close = new Float64Array([44.34, 44.09, 44.15, 43.61, 44.33]);
const smaOut = 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>
Run Tests
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:
- Go to the Actions tab.
- Open the latest successful CI run.
- Download the
wasm-pkgartifact from the Artifacts section. - Unzip and use
pkg/ferro_ta_wasm.jsdirectly in your project.
Limitations
- Only 9 indicators are currently exposed (SMA, EMA, BBANDS, RSI, MACD, MOM, STOCHF, ATR, OBV).
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).