From 682bf063ca54d560c2fd699934f5fea05bf1b61f Mon Sep 17 00:00:00 2001 From: Pratik Bhadane Date: Tue, 24 Mar 2026 00:38:50 +0530 Subject: [PATCH] release: cut v1.0.1 Prepare the first patch release after v1.0.0 by finalizing the outstanding release, packaging, and workflow fixes. This release keeps PyPI and crates.io publishing anchored to CI.yml, splits the large CI workflow into focused rust/python/wasm/docs suites, fixes the ci-complete gate, and updates the release SBOM action pin. It also switches the npm publish workflow to GitHub OIDC, installs the wasm32 target required for packaging, ensures the WASM npm tarball includes pkg/ artifacts during prepack, and adds a dedicated README plus docs.rs metadata for ferro_ta_core. Finally, bump all published package versions to 1.0.1 and record the patch release notes in CHANGELOG.md. --- .github/workflows/CI.yml | 363 +++-------------------------- .github/workflows/ci-docs.yml | 49 ++++ .github/workflows/ci-python.yml | 127 ++++++++++ .github/workflows/ci-rust.yml | 110 +++++++++ .github/workflows/ci-wasm.yml | 37 +++ .github/workflows/wasm-publish.yml | 12 +- CHANGELOG.md | 25 +- Cargo.lock | 4 +- Cargo.toml | 4 +- crates/ferro_ta_core/Cargo.toml | 5 +- crates/ferro_ta_core/README.md | 79 +++++++ pyproject.toml | 2 +- wasm/package.json | 3 +- 13 files changed, 482 insertions(+), 338 deletions(-) create mode 100644 .github/workflows/ci-docs.yml create mode 100644 .github/workflows/ci-python.yml create mode 100644 .github/workflows/ci-rust.yml create mode 100644 .github/workflows/ci-wasm.yml create mode 100644 crates/ferro_ta_core/README.md diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index d48c1f9..15dbf01 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -10,43 +10,8 @@ on: permissions: contents: read - pages: write jobs: - # ------------------------------------------------------------------------- - # Dependency audits — cargo audit (Rust) and pip-audit (Python) - # ------------------------------------------------------------------------- - audit: - name: Dependency audit (cargo + pip) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Install cargo-audit and run cargo audit - run: | - cargo install cargo-audit - cargo audit - - - name: Install cargo-deny and run cargo deny check - run: | - cargo install cargo-deny --locked - cargo deny check - - - name: Set up Python 3.12 - uses: actions/setup-python@v6 - with: - python-version: "3.12" - - - name: Install uv - run: pip install uv - - - name: Install pip-audit via uv and run pip-audit - run: | - uv run --with pip-audit pip-audit - - - name: Verify uv.lock is up-to-date - run: uv lock --check - # ------------------------------------------------------------------------- # Version consistency — Cargo.toml and pyproject.toml must have same version # ------------------------------------------------------------------------- @@ -76,285 +41,28 @@ jobs: fi echo "OK: versions match ($CARGO_VERSION)" - rust: - name: Rust (fmt + clippy) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@v1 - with: - toolchain: stable - components: rustfmt, clippy - - run: cargo fmt --all -- --check - - run: cargo clippy --release -- -D warnings - - name: Verify benchmarks compile (ferro_ta_core) - run: cargo bench -p ferro_ta_core --no-run - # ------------------------------------------------------------------------- - # Rust coverage — optional, runs in a separate non-blocking job + # Modularized CI suites. + # Keep release publishing jobs in this file so trusted-publisher bindings + # remain anchored to CI.yml for PyPI and crates.io. # ------------------------------------------------------------------------- - rust-coverage: - name: Rust coverage (tarpaulin, optional) - runs-on: ubuntu-latest - continue-on-error: true - steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@v1 - with: - toolchain: stable - - name: Install cargo-tarpaulin - run: cargo install cargo-tarpaulin --locked - - name: Collect Rust coverage (ferro_ta_core) - run: cargo tarpaulin -p ferro_ta_core --out Xml --output-dir coverage/ - - name: Upload Rust coverage artifact - uses: actions/upload-artifact@v7 - with: - name: rust-coverage - path: coverage/ - if-no-files-found: ignore + rust-suite: + name: Rust and audit + uses: ./.github/workflows/ci-rust.yml - lint: - name: Lint (ruff) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 + python-suite: + name: Python quality and tests + uses: ./.github/workflows/ci-python.yml - - name: Set up Python 3.12 - uses: actions/setup-python@v6 - with: - python-version: "3.12" - - - name: Install uv - run: pip install uv - - - name: Run ruff check via uv - run: uv run --with ruff ruff check python/ tests/ - - name: Run ruff format check via uv - run: uv run --with ruff ruff format --check python/ tests/ - - typecheck: - name: Type checking (mypy + pyright) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Set up Python 3.12 - uses: actions/setup-python@v6 - with: - python-version: "3.12" - - - name: Install uv - run: pip install uv - - - name: Run mypy on ferro_ta via uv - run: uv run --with mypy --with numpy mypy python/ferro_ta --ignore-missing-imports --no-error-summary - - - name: Run pyright on ferro_ta via uv - run: uv run --with pyright pyright python/ferro_ta - - test: - name: Test (ubuntu-latest / Python ${{ matrix.python-version }}) - runs-on: ubuntu-latest - strategy: - # Python 3.10–3.13 supported (requires-python in pyproject.toml) - fail-fast: false - matrix: - python-version: ["3.10", "3.11", "3.12", "3.13"] - steps: - - uses: actions/checkout@v6 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v6 - with: - python-version: ${{ matrix.python-version }} - - - name: Install maturin and test dependencies - run: | - pip install maturin numpy pytest pytest-cov pandas polars hypothesis pyyaml - - - name: Build and install ferro_ta (dev mode) - run: | - maturin build --release --out dist - pip install dist/*.whl - - - name: Run unit tests with coverage - run: pytest tests/unit/ tests/integration/ -v --cov=ferro_ta --cov-report=xml --cov-report=term-missing --cov-fail-under=65 - - - name: Upload coverage report - uses: actions/upload-artifact@v7 - if: matrix.python-version == '3.12' - with: - name: coverage-python-${{ matrix.python-version }} - path: coverage.xml - - # ------------------------------------------------------------------------- - # WASM binding — build and test with wasm-pack - # ------------------------------------------------------------------------- wasm: - name: WASM binding (wasm-pack test --node) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 + name: WASM binding + uses: ./.github/workflows/ci-wasm.yml - - name: Install Rust (stable) - uses: dtolnay/rust-toolchain@v1 - with: - toolchain: stable - targets: wasm32-unknown-unknown - - - name: Install wasm-pack - run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - - - name: Build and test WASM binding - working-directory: wasm - run: wasm-pack test --node - - - name: Build WASM package (nodejs target) - working-directory: wasm - run: wasm-pack build --target nodejs --out-dir pkg - - - name: Upload WASM package artifact - uses: actions/upload-artifact@v7 - with: - name: wasm-pkg - path: wasm/pkg/ - - # ------------------------------------------------------------------------- - # ferro_ta vs TA-Lib speed comparison — required for PRs. - # Performance regressions block merging. - # ------------------------------------------------------------------------- - benchmark-vs-talib: - name: Benchmark vs TA-Lib - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Set up Python 3.12 - uses: actions/setup-python@v6 - with: - python-version: "3.12" - - - name: Install TA-Lib C library (Ubuntu) - run: | - sudo apt-get update - sudo apt-get install -y build-essential curl - curl -sL https://sourceforge.net/projects/ta-lib/files/ta-lib/0.4.0/ta-lib-0.4.0-src.tar.gz/download -o ta-lib-0.4.0-src.tar.gz - tar -xzf ta-lib-0.4.0-src.tar.gz - cd ta-lib - ./configure --prefix=/usr - make - sudo make install - sudo ldconfig - - - name: Install maturin and ta-lib Python package - run: | - pip install maturin numpy - pip install ta-lib - - - name: Build and install ferro_ta - run: | - maturin build --release --out dist - pip install dist/*.whl - - - name: Run benchmark comparison - run: | - python benchmarks/bench_vs_talib.py --sizes 10000 100000 --json benchmark_vs_talib.json - - - name: Enforce benchmark regression policy - run: | - python3 benchmarks/check_vs_talib_regression.py --input benchmark_vs_talib.json - - - name: Upload benchmark results - uses: actions/upload-artifact@v7 - with: - name: benchmark-vs-talib - path: benchmark_vs_talib.json - - # ------------------------------------------------------------------------- - # Pure Rust core library — build and test without PyO3 / numpy - # ------------------------------------------------------------------------- - rust-core: - name: Rust core library (ferro_ta_core) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@v1 - with: - toolchain: stable - - name: Build core crate - run: cargo build -p ferro_ta_core - - name: Test core crate - run: cargo test -p ferro_ta_core - - # ------------------------------------------------------------------------- - # Fuzzing (short run in CI to catch panics) — optional, non-blocking - # This job is explicitly marked continue-on-error because cargo-fuzz - # requires nightly Rust and may not be available in all environments. - # Failures here are visible in the job log but do not block the PR. - # ------------------------------------------------------------------------- - fuzz: - name: Fuzz targets (short CI run, optional) - runs-on: ubuntu-latest - continue-on-error: true - steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@v1 - with: - toolchain: nightly - - name: Install cargo-fuzz - run: cargo install cargo-fuzz --locked - - name: Run fuzz_sma (10000 iterations) - working-directory: fuzz - run: cargo fuzz run fuzz_sma -- -runs=10000 -max_len=512 - - name: Run fuzz_rsi (10000 iterations) - working-directory: fuzz - run: cargo fuzz run fuzz_rsi -- -runs=10000 -max_len=512 - - name: Upload fuzz artifacts on crash - uses: actions/upload-artifact@v7 - if: always() - with: - name: fuzz-artifacts - path: fuzz/artifacts/ - if-no-files-found: ignore - - # ------------------------------------------------------------------------- - # Sphinx documentation build - # ------------------------------------------------------------------------- docs: - name: Documentation (Sphinx build) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Set up Python 3.12 - uses: actions/setup-python@v6 - with: - python-version: "3.12" - - - name: Install maturin and docs dependencies - run: | - pip install maturin numpy - pip install sphinx sphinx-rtd-theme - - - name: Build and install ferro_ta wheel - run: | - maturin build --release --out dist - pip install dist/*.whl - - - name: Build Sphinx documentation - run: sphinx-build -b html docs docs/_build -W --keep-going - - - name: Upload docs artifact - uses: actions/upload-artifact@v7 - with: - name: sphinx-docs - path: docs/_build/ - - - name: Upload GitHub Pages artifact - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: actions/upload-pages-artifact@v4 - with: - path: docs/_build/ + name: Documentation + uses: ./.github/workflows/ci-docs.yml + with: + upload-pages-artifact: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} # ------------------------------------------------------------------------- # Deploy docs to GitHub Pages (on push to main only) @@ -384,16 +92,11 @@ jobs: name: CI complete (required gate) runs-on: ubuntu-latest needs: - - audit - changelog-check - version-check - - rust - - rust-core - - lint - - typecheck - - test + - rust-suite + - python-suite - wasm - - benchmark-vs-talib - docs if: always() steps: @@ -401,21 +104,31 @@ jobs: run: | results='${{ toJSON(needs) }}' echo "Job results: $results" - failed=$(echo "$results" | python3 -c " - import json, sys - needs = json.load(sys.stdin) - failed = [name for name, data in needs.items() if data['result'] not in ('success', 'skipped')] + failed="$( + RESULTS_JSON="$results" python3 - <<'PY' + import json + import os + + needs = json.loads(os.environ["RESULTS_JSON"]) + failed = [ + name + for name, data in needs.items() + if data["result"] not in ("success", "skipped") + ] if failed: - print(' '.join(failed)) - ") - if [ -n \"\$failed\" ]; then - echo \"FAILED jobs: \$failed\" + print(" ".join(failed)) + PY + )" + if [ -n "$failed" ]; then + echo "FAILED jobs: $failed" exit 1 fi - echo \"All required CI jobs passed.\" + echo "All required CI jobs passed." # ------------------------------------------------------------------------- # Build wheels for all platforms and publish to PyPI on release + # Keep release jobs here because trusted-publisher configuration points to + # CI.yml specifically. # ------------------------------------------------------------------------- build-wheels: name: Build wheels (${{ matrix.os }}) @@ -503,7 +216,7 @@ jobs: - uses: actions/checkout@v6 - name: Set up Python 3.12 - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.12" @@ -516,7 +229,7 @@ jobs: pip install dist/*.whl - name: Generate Python SBOM (CycloneDX via anchore/sbom-action) - uses: anchore/sbom-action@v0.17 + uses: anchore/sbom-action@v0.24.0 with: artifact-name: ferro-ta-python-sbom.spdx.json output-file: ferro-ta-python-sbom.spdx.json diff --git a/.github/workflows/ci-docs.yml b/.github/workflows/ci-docs.yml new file mode 100644 index 0000000..f662568 --- /dev/null +++ b/.github/workflows/ci-docs.yml @@ -0,0 +1,49 @@ +name: CI Docs + +on: + workflow_call: + inputs: + upload-pages-artifact: + required: false + type: boolean + default: false + +permissions: + contents: read + +jobs: + docs: + name: Documentation (Sphinx build) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Set up Python 3.12 + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install maturin and docs dependencies + run: | + pip install maturin numpy + pip install sphinx sphinx-rtd-theme + + - name: Build and install ferro_ta wheel + run: | + maturin build --release --out dist + pip install dist/*.whl + + - name: Build Sphinx documentation + run: sphinx-build -b html docs docs/_build -W --keep-going + + - name: Upload docs artifact + uses: actions/upload-artifact@v7 + with: + name: sphinx-docs + path: docs/_build/ + + - name: Upload GitHub Pages artifact + if: ${{ inputs.upload-pages-artifact }} + uses: actions/upload-pages-artifact@v4 + with: + path: docs/_build/ diff --git a/.github/workflows/ci-python.yml b/.github/workflows/ci-python.yml new file mode 100644 index 0000000..dc87723 --- /dev/null +++ b/.github/workflows/ci-python.yml @@ -0,0 +1,127 @@ +name: CI Python + +on: + workflow_call: + +permissions: + contents: read + +jobs: + lint: + name: Lint (ruff) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Set up Python 3.12 + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install uv + run: pip install uv + + - name: Run ruff check via uv + run: uv run --with ruff ruff check python/ tests/ + - name: Run ruff format check via uv + run: uv run --with ruff ruff format --check python/ tests/ + + typecheck: + name: Type checking (mypy + pyright) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Set up Python 3.12 + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install uv + run: pip install uv + + - name: Run mypy on ferro_ta via uv + run: uv run --with mypy --with numpy mypy python/ferro_ta --ignore-missing-imports --no-error-summary + + - name: Run pyright on ferro_ta via uv + run: uv run --with pyright pyright python/ferro_ta + + test: + name: Test (ubuntu-latest / Python ${{ matrix.python-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13"] + steps: + - uses: actions/checkout@v6 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + + - name: Install maturin and test dependencies + run: | + pip install maturin numpy pytest pytest-cov pandas polars hypothesis pyyaml + + - name: Build and install ferro_ta (dev mode) + run: | + maturin build --release --out dist + pip install dist/*.whl + + - name: Run unit tests with coverage + run: pytest tests/unit/ tests/integration/ -v --cov=ferro_ta --cov-report=xml --cov-report=term-missing --cov-fail-under=65 + + - name: Upload coverage report + uses: actions/upload-artifact@v7 + if: matrix.python-version == '3.12' + with: + name: coverage-python-${{ matrix.python-version }} + path: coverage.xml + + benchmark-vs-talib: + name: Benchmark vs TA-Lib + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Set up Python 3.12 + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install TA-Lib C library (Ubuntu) + run: | + sudo apt-get update + sudo apt-get install -y build-essential curl + curl -sL https://sourceforge.net/projects/ta-lib/files/ta-lib/0.4.0/ta-lib-0.4.0-src.tar.gz/download -o ta-lib-0.4.0-src.tar.gz + tar -xzf ta-lib-0.4.0-src.tar.gz + cd ta-lib + ./configure --prefix=/usr + make + sudo make install + sudo ldconfig + + - name: Install maturin and ta-lib Python package + run: | + pip install maturin numpy + pip install ta-lib + + - name: Build and install ferro_ta + run: | + maturin build --release --out dist + pip install dist/*.whl + + - name: Run benchmark comparison + run: | + python benchmarks/bench_vs_talib.py --sizes 10000 100000 --json benchmark_vs_talib.json + + - name: Enforce benchmark regression policy + run: python3 benchmarks/check_vs_talib_regression.py --input benchmark_vs_talib.json + + - name: Upload benchmark results + uses: actions/upload-artifact@v7 + with: + name: benchmark-vs-talib + path: benchmark_vs_talib.json diff --git a/.github/workflows/ci-rust.yml b/.github/workflows/ci-rust.yml new file mode 100644 index 0000000..4a6639b --- /dev/null +++ b/.github/workflows/ci-rust.yml @@ -0,0 +1,110 @@ +name: CI Rust + +on: + workflow_call: + +permissions: + contents: read + +jobs: + audit: + name: Dependency audit (cargo + pip) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Install cargo-audit and run cargo audit + run: | + cargo install cargo-audit + cargo audit + + - name: Install cargo-deny and run cargo deny check + run: | + cargo install cargo-deny --locked + cargo deny check + + - name: Set up Python 3.12 + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Install uv + run: pip install uv + + - name: Install pip-audit via uv and run pip-audit + run: uv run --with pip-audit pip-audit + + - name: Verify uv.lock is up-to-date + run: uv lock --check + + rust: + name: Rust (fmt + clippy) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: dtolnay/rust-toolchain@v1 + with: + toolchain: stable + components: rustfmt, clippy + - run: cargo fmt --all -- --check + - run: cargo clippy --release -- -D warnings + - name: Verify benchmarks compile (ferro_ta_core) + run: cargo bench -p ferro_ta_core --no-run + + rust-core: + name: Rust core library (ferro_ta_core) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: dtolnay/rust-toolchain@v1 + with: + toolchain: stable + - name: Build core crate + run: cargo build -p ferro_ta_core + - name: Test core crate + run: cargo test -p ferro_ta_core + + rust-coverage: + name: Rust coverage (tarpaulin, optional) + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v6 + - uses: dtolnay/rust-toolchain@v1 + with: + toolchain: stable + - name: Install cargo-tarpaulin + run: cargo install cargo-tarpaulin --locked + - name: Collect Rust coverage (ferro_ta_core) + run: cargo tarpaulin -p ferro_ta_core --out Xml --output-dir coverage/ + - name: Upload Rust coverage artifact + uses: actions/upload-artifact@v7 + with: + name: rust-coverage + path: coverage/ + if-no-files-found: ignore + + fuzz: + name: Fuzz targets (short CI run, optional) + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v6 + - uses: dtolnay/rust-toolchain@v1 + with: + toolchain: nightly + - name: Install cargo-fuzz + run: cargo install cargo-fuzz --locked + - name: Run fuzz_sma (10000 iterations) + working-directory: fuzz + run: cargo fuzz run fuzz_sma -- -runs=10000 -max_len=512 + - name: Run fuzz_rsi (10000 iterations) + working-directory: fuzz + run: cargo fuzz run fuzz_rsi -- -runs=10000 -max_len=512 + - name: Upload fuzz artifacts on crash + uses: actions/upload-artifact@v7 + if: always() + with: + name: fuzz-artifacts + path: fuzz/artifacts/ + if-no-files-found: ignore diff --git a/.github/workflows/ci-wasm.yml b/.github/workflows/ci-wasm.yml new file mode 100644 index 0000000..91d493f --- /dev/null +++ b/.github/workflows/ci-wasm.yml @@ -0,0 +1,37 @@ +name: CI WASM + +on: + workflow_call: + +permissions: + contents: read + +jobs: + wasm: + name: WASM binding (wasm-pack test --node) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Install Rust (stable) + uses: dtolnay/rust-toolchain@v1 + with: + toolchain: stable + targets: wasm32-unknown-unknown + + - name: Install wasm-pack + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + + - name: Build and test WASM binding + working-directory: wasm + run: wasm-pack test --node + + - name: Build WASM package (nodejs target) + working-directory: wasm + run: wasm-pack build --target nodejs --out-dir pkg + + - name: Upload WASM package artifact + uses: actions/upload-artifact@v7 + with: + name: wasm-pkg + path: wasm/pkg/ diff --git a/.github/workflows/wasm-publish.yml b/.github/workflows/wasm-publish.yml index eb69bbc..9c845be 100644 --- a/.github/workflows/wasm-publish.yml +++ b/.github/workflows/wasm-publish.yml @@ -4,6 +4,10 @@ on: release: types: [published] +permissions: + contents: read + id-token: write + jobs: publish: name: Build and publish to npm @@ -12,13 +16,15 @@ jobs: - uses: actions/checkout@v6 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: "20" + node-version: "24" registry-url: "https://registry.npmjs.org" - name: Install Rust and wasm-pack uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-unknown-unknown - run: cargo install wasm-pack - name: Build WASM package @@ -29,5 +35,3 @@ jobs: - name: Publish to npm working-directory: wasm run: npm publish --access public - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index ca43b30..c819211 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,28 @@ and the project uses [Semantic Versioning](https://semver.org/). ## [Unreleased] +## [1.0.1] — 2026-03-24 + +### Added + +- `crates/ferro_ta_core/README.md` is now shipped with the published Rust crate, and + `ferro_ta_core` metadata now points documentation to docs.rs. + +### Fixed + +- CI has been modularized into focused workflow files (`ci-rust.yml`, + `ci-python.yml`, `ci-wasm.yml`, `ci-docs.yml`) while keeping the release + publishing jobs in `CI.yml` for PyPI and crates.io trusted-publisher + compatibility. +- The `ci-complete` gate no longer fails successful runs because of an escaped + shell variable, and the release SBOM job now uses a valid `anchore/sbom-action` + version. +- The npm publish workflow now uses GitHub OIDC trusted publishing, installs + the `wasm32-unknown-unknown` target, and no longer depends on an `NPM_TOKEN` + secret. +- The WASM npm package now removes the generated `pkg/.gitignore` during + `prepack`, so the published tarball includes the built `pkg/` artifacts. + ## [1.0.0] — 2026-03-23 *(initial stable release)* ### Performance @@ -252,5 +274,6 @@ and the project uses [Semantic Versioning](https://semver.org/). --- -[Unreleased]: https://github.com/pratikbhadane24/ferro-ta/compare/v1.0.0...HEAD +[Unreleased]: https://github.com/pratikbhadane24/ferro-ta/compare/v1.0.1...HEAD +[1.0.1]: https://github.com/pratikbhadane24/ferro-ta/compare/v1.0.0...v1.0.1 [1.0.0]: https://github.com/pratikbhadane24/ferro-ta/releases/tag/v1.0.0 diff --git a/Cargo.lock b/Cargo.lock index 0d95e25..ff595fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -207,7 +207,7 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "ferro_ta" -version = "1.0.0" +version = "1.0.1" dependencies = [ "criterion", "ferro_ta_core", @@ -222,7 +222,7 @@ dependencies = [ [[package]] name = "ferro_ta_core" -version = "1.0.0" +version = "1.0.1" dependencies = [ "criterion", "wide", diff --git a/Cargo.toml b/Cargo.toml index 52c2802..e394178 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ resolver = "2" [package] name = "ferro_ta" -version = "1.0.0" +version = "1.0.1" edition = "2021" license = "MIT" publish = false @@ -23,7 +23,7 @@ ndarray = "0.16" rayon = "1.10" log = "0.4" pyo3-log = "0.12" -ferro_ta_core = { path = "crates/ferro_ta_core", version = "1.0.0" } +ferro_ta_core = { path = "crates/ferro_ta_core", version = "1.0.1" } [dev-dependencies] criterion = { version = "0.8", features = ["html_reports"] } diff --git a/crates/ferro_ta_core/Cargo.toml b/crates/ferro_ta_core/Cargo.toml index 03fca4a..093efd5 100644 --- a/crates/ferro_ta_core/Cargo.toml +++ b/crates/ferro_ta_core/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "ferro_ta_core" -version = "1.0.0" +version = "1.0.1" edition = "2021" description = "Pure Rust core indicator library — no PyO3, no numpy dependency" license = "MIT" +readme = "README.md" repository = "https://github.com/pratikbhadane24/ferro-ta" homepage = "https://github.com/pratikbhadane24/ferro-ta#readme" -documentation = "https://github.com/pratikbhadane24/ferro-ta#readme" +documentation = "https://docs.rs/ferro_ta_core" keywords = ["technical-analysis", "trading", "indicators", "finance", "ta-lib"] categories = ["finance", "mathematics"] diff --git a/crates/ferro_ta_core/README.md b/crates/ferro_ta_core/README.md new file mode 100644 index 0000000..e4859ce --- /dev/null +++ b/crates/ferro_ta_core/README.md @@ -0,0 +1,79 @@ +# ferro_ta_core + +`ferro_ta_core` is the pure Rust indicator engine behind [`ferro-ta`](https://github.com/pratikbhadane24/ferro-ta). + +It provides allocation-friendly indicator functions over `&[f64]` slices without any +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 + +## Installation + +```toml +[dependencies] +ferro_ta_core = "1.0.1" +``` + +## Design + +- Pure functions over Rust slices +- No Python or NumPy dependency +- Shared core for the Python package and WASM bindings +- Output shape matches TA-Lib-style full-length series with `NaN` warm-up values where applicable + +## Modules + +- `overlap` - moving averages, MACD, Bollinger Bands +- `momentum` - RSI, MOM +- `volatility` - ATR, TRANGE +- `volume` - OBV +- `statistic` - STDDEV +- `math` - rolling SUM/MAX/MIN helpers + +## Example + +```rust +use ferro_ta_core::overlap; + +fn main() { + let close = vec![1.0, 2.0, 3.0, 4.0, 5.0]; + let sma = overlap::sma(&close, 3); + + assert!(sma[0].is_nan()); + assert!(sma[1].is_nan()); + assert!((sma[2] - 2.0).abs() < 1e-10); +} +``` + +## 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 + +If you only need Rust indicator functions, use `ferro_ta_core` directly. + +## Development + +From the repository root: + +```bash +cargo build -p ferro_ta_core +cargo test -p ferro_ta_core +cargo bench -p ferro_ta_core --no-run +``` + +## License + +MIT diff --git a/pyproject.toml b/pyproject.toml index 53cd5c4..5ad0309 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "ferro-ta" -version = "1.0.0" +version = "1.0.1" description = "A fast Technical Analysis library — TA-Lib alternative powered by Rust and PyO3" readme = "README.md" license = { text = "MIT" } diff --git a/wasm/package.json b/wasm/package.json index fa16527..6c4f0ce 100644 --- a/wasm/package.json +++ b/wasm/package.json @@ -1,12 +1,13 @@ { "name": "ferro-ta-wasm", - "version": "1.0.0", + "version": "1.0.1", "description": "WebAssembly bindings for ferro-ta technical analysis indicators", "main": "pkg/ferro_ta_wasm.js", "types": "pkg/ferro_ta_wasm.d.ts", "files": ["pkg"], "scripts": { "build": "wasm-pack build --target nodejs --out-dir pkg", + "prepack": "npm run build && node -e \"require('fs').rmSync('pkg/.gitignore', { force: true })\"", "test": "wasm-pack test --node" }, "license": "MIT",