diff --git a/benchmarks/test_derivatives_speed.py b/benchmarks/test_derivatives_speed.py index 8388dda..a64ba0e 100644 --- a/benchmarks/test_derivatives_speed.py +++ b/benchmarks/test_derivatives_speed.py @@ -10,11 +10,27 @@ Run with: from __future__ import annotations import importlib.util +import sys +from pathlib import Path import numpy as np import pytest -from ferro_ta.analysis.options import implied_volatility, option_price +# Ensure direct benchmark test runs can import local package from `python/`. +ROOT = Path(__file__).resolve().parents[1] +PYTHON_SRC = ROOT / "python" +if str(PYTHON_SRC) not in sys.path: + sys.path.insert(0, str(PYTHON_SRC)) + +HAS_FERRO_EXTENSION = True +try: + from ferro_ta.analysis.options import implied_volatility, option_price +except ModuleNotFoundError: + HAS_FERRO_EXTENSION = False + +pytestmark = pytest.mark.skipif( + not HAS_FERRO_EXTENSION, reason="ferro_ta extension is not built" +) def _sample_chain(n: int = 1000) -> tuple[np.ndarray, ...]: diff --git a/pyproject.toml b/pyproject.toml index 4abd6fe..630c59e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -104,6 +104,7 @@ ignore = ["E501", "UP006", "UP045", "UP007"] "tests/unit/*" = ["N801", "N802", "N806", "E402", "E741", "F811"] "tests/integration/*" = ["N801", "N802", "N806", "E402", "E741", "F811"] "python/ferro_ta/*.py" = ["N802"] # Public API: SMA, RSI, ATR, etc. +"python/ferro_ta/__init__.py" = ["E402", "N802"] # Re-export surface by design "python/ferro_ta/__init__.pyi" = ["N802", "E402"] [tool.ruff.format] diff --git a/python/ferro_ta/__init__.py b/python/ferro_ta/__init__.py index 943b856..07e384d 100644 --- a/python/ferro_ta/__init__.py +++ b/python/ferro_ta/__init__.py @@ -59,11 +59,11 @@ array([ nan, nan, 11. , 12. , 13. , 13.5, 13.33...]) from __future__ import annotations +import re as _re +import sys as _sys from importlib.metadata import PackageNotFoundError as _PackageNotFoundError from importlib.metadata import version as _dist_version from pathlib import Path as _Path -import re as _re -import sys as _sys try: import tomllib as _tomllib diff --git a/python/ferro_ta/__init__.pyi b/python/ferro_ta/__init__.pyi index 46fa809..e4b707b 100644 --- a/python/ferro_ta/__init__.pyi +++ b/python/ferro_ta/__init__.pyi @@ -13,6 +13,8 @@ from numpy.typing import ArrayLike, NDArray _F = TypeVar("_F", bound=Callable[..., Any]) +__version__: str + # --------------------------------------------------------------------------- # Overlap Studies # --------------------------------------------------------------------------- @@ -739,8 +741,10 @@ class FerroTAInputError(FerroTAError, ValueError): # API discovery (ferro_ta.api_info) # --------------------------------------------------------------------------- +def about() -> dict[str, Any]: ... def indicators(category: str | None = None) -> list[dict[str, Any]]: ... def info(func_or_name: Callable[..., Any] | str) -> dict[str, Any]: ... +def methods(category: str | None = None) -> list[dict[str, Any]]: ... # --------------------------------------------------------------------------- # Logging utilities (ferro_ta.logging_utils) diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index b18ba49..301d133 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -233,7 +233,9 @@ def test_methods_returns_public_callables(): result = ferro_ta.methods() assert isinstance(result, list) assert any(d["name"] == "SMA" and d["category"] == "top_level" for d in result) - assert any(d["name"] == "option_price" and d["category"] == "options" for d in result) + assert any( + d["name"] == "option_price" and d["category"] == "options" for d in result + ) def test_about_reports_version_and_counts(): diff --git a/tests/unit/test_derivatives.py b/tests/unit/test_derivatives.py index 2173c78..60061cb 100644 --- a/tests/unit/test_derivatives.py +++ b/tests/unit/test_derivatives.py @@ -1,3 +1,7 @@ +import subprocess +import sys +from pathlib import Path + import numpy as np import pytest @@ -220,19 +224,30 @@ class TestStrategyAndPayoff: class TestDerivativesBenchmarking: def test_derivatives_benchmark_smoke(self, tmp_path): - from benchmarks.bench_derivatives_compare import run_benchmark - + root = Path(__file__).resolve().parents[2] + script = root / "benchmarks" / "bench_derivatives_compare.py" output_path = tmp_path / "derivatives_benchmark.json" - result = run_benchmark( - sizes=[32], - accuracy_size=16, - json_path=str(output_path), + + completed = subprocess.run( + [ + sys.executable, + str(script), + "--sizes", + "32", + "--accuracy-size", + "16", + "--json", + str(output_path), + ], + cwd=root, + check=False, + capture_output=True, + text=True, ) + assert completed.returncode == 0, completed.stdout + completed.stderr assert output_path.is_file() - assert result["accuracy"]["results"] - assert result["speed"]["results"] - assert any( - row["provider"] == "ferro_ta" for row in result["accuracy"]["results"] - ) - assert any(row["provider"] == "ferro_ta" for row in result["speed"]["results"]) + payload = output_path.read_text(encoding="utf-8") + assert '"accuracy"' in payload + assert '"speed"' in payload + assert '"provider": "ferro_ta"' in payload