Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 682bf063ca |
+38
-325
@@ -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
|
||||
|
||||
@@ -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/
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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/
|
||||
@@ -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 }}
|
||||
|
||||
+24
-1
@@ -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
|
||||
|
||||
Generated
+2
-2
@@ -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",
|
||||
|
||||
+2
-2
@@ -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"] }
|
||||
|
||||
@@ -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"]
|
||||
|
||||
|
||||
@@ -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
|
||||
+1
-1
@@ -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" }
|
||||
|
||||
+2
-1
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user