diff --git a/CLAUDE.md b/CLAUDE.md index af30721e..fec36b24 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,7 +7,7 @@ Guidance for Claude Code sessions working in this repository. **jquantstats** — portfolio analytics for quants, built on [Polars](https://pola.rs/) (zero pandas at runtime) with interactive [Plotly](https://plotly.com/python/) charts. Python `>=3.11`, MIT-licensed, published to PyPI. This is a -[Rhiza](https://github.com/jebel-quant/rhiza)-managed repository (template `v1.1.2`). +[Rhiza](https://github.com/jebel-quant/rhiza)-managed repository (template `v1.2.0`). ## Commands diff --git a/README.md b/README.md index e31837be..1386f208 100644 --- a/README.md +++ b/README.md @@ -390,6 +390,11 @@ categorized metric tables, rendered via Jinja2 templates. For detailed documentation, visit [jQuantStats Documentation](https://jebel-quant.github.io/jquantstats). +The public API (`Portfolio` and `Data`) follows [Semantic Versioning](https://semver.org/) +from **v1.0.0** onwards; see the +[API Stability & deprecation policy](https://jebel-quant.github.io/jquantstats/STABILITY/) +for the exact stability guarantee and deprecation timeline. + ## Contributing Contributions are welcome! Please feel free to submit a Pull Request. diff --git a/pyproject.toml b/pyproject.toml index d26bc31c..c34aa3eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -147,3 +147,11 @@ ignore-init-method = true ignore-magic = true ignore-overloaded-functions = true +# Test-layout parity (rhiza check_test_layout): tests here are organised by +# behaviour under tests/test_jquantstats/… rather than mirroring src/ 1:1, and +# per-module coverage is guaranteed by the 100% line+branch pytest gate above +# (a stronger guarantee than file-mirroring). Opt out of the 1:1 mirror rule. +[tool.check_test_layout] +enforce = false +reason = "Tests are organised by behaviour under tests/test_jquantstats/; per-module coverage is guaranteed by the 100% pytest coverage gate rather than 1:1 file mirroring." + diff --git a/src/jquantstats/_portfolio_cost.py b/src/jquantstats/_portfolio_cost.py index 3a937cd4..3b672305 100644 --- a/src/jquantstats/_portfolio_cost.py +++ b/src/jquantstats/_portfolio_cost.py @@ -4,9 +4,11 @@ import math +import numpy as np import polars as pl from ._portfolio_base import _PortfolioMembers +from ._stats._core import _std_is_negligible from .exceptions import InvalidMaxBpsError, NegativeCostBpsError @@ -181,10 +183,6 @@ def trading_cost_impact(self, max_bps: int = 20) -> pl.DataFrame: """ if not isinstance(max_bps, int) or max_bps < 1: raise InvalidMaxBpsError(max_bps) - import numpy as np - - from ._stats._core import _std_is_negligible - periods = self.data._periods_per_year # one Data object, outside the loop sqrt_periods = float(np.sqrt(periods)) cost_levels = list(range(max_bps + 1)) diff --git a/src/jquantstats/data.py b/src/jquantstats/data.py index ca314755..76bc5e99 100644 --- a/src/jquantstats/data.py +++ b/src/jquantstats/data.py @@ -520,6 +520,9 @@ def plots(self) -> DataPlots: DataPlots: An instance of the DataPlots class initialized with this data. """ + # Deferred to break the data <-> accessors import cycle: _plots imports + # DataLike from this module, so hoisting this to module top re-forms the + # cycle. Keep the import local. from ._plots import DataPlots return DataPlots(self) @@ -532,6 +535,7 @@ def stats(self) -> Stats: Stats: An instance of the Stats class initialized with this data. """ + # Deferred to break the data <-> accessors import cycle (see .plots). from ._stats import Stats return Stats(self) @@ -544,6 +548,7 @@ def reports(self) -> Reports: Reports: An instance of the Reports class initialized with this data. """ + # Deferred to break the data <-> accessors import cycle (see .plots). from ._reports import Reports return Reports(self) @@ -556,6 +561,7 @@ def utils(self) -> DataUtils: DataUtils: An instance of the DataUtils class initialized with this data. """ + # Deferred to break the data <-> accessors import cycle (see .plots). from ._utils import DataUtils return DataUtils(self)