name: CI Python on: workflow_call: permissions: contents: read jobs: # ------------------------------------------------------------------------- # Lint — no Rust, no wheel build, very fast # ------------------------------------------------------------------------- lint: name: Lint (ruff) runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: "3.12" - run: pip install uv - run: uv run --with ruff ruff check python/ tests/ - run: uv run --with ruff ruff format --check python/ tests/ # ------------------------------------------------------------------------- # Type checking — needs wheel; build once with cache # ------------------------------------------------------------------------- typecheck: name: Type checking (mypy + pyright) runs-on: ubuntu-latest env: RUSTFLAGS: "" # override target-cpu=native from .cargo/config.toml steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: "3.12" - uses: dtolnay/rust-toolchain@v1 with: toolchain: stable - uses: Swatinem/rust-cache@v2 - run: pip install maturin numpy - run: maturin build --release --out dist && pip install dist/*.whl - run: pip install uv - run: uv run --with mypy --with numpy python -m mypy python/ferro_ta --ignore-missing-imports --no-error-summary - run: uv run --with pyright python -m pyright python/ferro_ta # ------------------------------------------------------------------------- # Build wheel artifact (once per run, reused by all test matrix entries) # ------------------------------------------------------------------------- build-wheel: name: Build wheel (ubuntu / Python ${{ matrix.python-version }}) runs-on: ubuntu-latest env: RUSTFLAGS: "" # override target-cpu=native from .cargo/config.toml strategy: matrix: python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - uses: dtolnay/rust-toolchain@v1 with: toolchain: stable - uses: Swatinem/rust-cache@v2 with: shared-key: "wheel-${{ matrix.python-version }}" - run: pip install maturin numpy - run: maturin build --release --out dist - uses: actions/upload-artifact@v7 with: name: wheel-${{ matrix.python-version }} path: dist/*.whl # ------------------------------------------------------------------------- # Test matrix — downloads pre-built wheel, no Rust compilation # ------------------------------------------------------------------------- test: name: Test (Python ${{ matrix.python-version }}) runs-on: ubuntu-latest needs: build-wheel strategy: fail-fast: false matrix: python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - uses: actions/download-artifact@v8 with: name: wheel-${{ matrix.python-version }} path: dist - name: Install wheel + test deps run: | pip install dist/*.whl pip install pytest pytest-cov pandas polars hypothesis pyyaml mcp scipy - name: Run 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: Check API manifest is current if: matrix.python-version == '3.12' run: python scripts/check_api_manifest.py - uses: actions/upload-artifact@v7 if: matrix.python-version == '3.12' with: name: coverage-python-${{ matrix.python-version }} path: coverage.xml # ------------------------------------------------------------------------- # Benchmark vs TA-Lib (downloads wheel from build-wheel job) # ------------------------------------------------------------------------- benchmark-vs-talib: name: Benchmark vs TA-Lib runs-on: ubuntu-latest needs: build-wheel steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: "3.12" - uses: actions/download-artifact@v8 with: name: wheel-3.12 path: dist - name: Install TA-Lib C library 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 - run: pip install dist/*.whl numpy ta-lib - run: python benchmarks/bench_vs_talib.py --sizes 10000 100000 --json benchmark_vs_talib.json - run: python3 benchmarks/check_vs_talib_regression.py --input benchmark_vs_talib.json - uses: actions/upload-artifact@v7 with: name: benchmark-vs-talib path: benchmark_vs_talib.json # ------------------------------------------------------------------------- # Performance smoke (downloads wheel from build-wheel job) # ------------------------------------------------------------------------- perf-smoke: name: Performance smoke and contracts runs-on: ubuntu-latest needs: build-wheel steps: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: python-version: "3.12" - uses: actions/download-artifact@v8 with: name: wheel-3.12 path: dist - run: pip install dist/*.whl numpy pytest - run: | python benchmarks/run_perf_contract.py \ --output-dir perf-contract \ --skip-talib \ --skip-simd \ --batch-samples 20000 \ --batch-series 32 \ --streaming-bars 20000 \ --price-bars 20000 \ --iv-bars 50000 - run: python benchmarks/check_hotspot_regression.py --input perf-contract/runtime_hotspots.json - uses: actions/upload-artifact@v7 with: name: perf-contract path: perf-contract/