diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 49259a0b..6ec5620b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -507,6 +507,33 @@ jobs:
- name: Build sdist and wheel
run: python -m build
+ - name: Compare static root API with wheel
+ run: |
+ # This schema-v2 source scan does not import optional dependencies.
+ python - <<'PY'
+ from pathlib import Path
+ import subprocess
+ import sys
+
+ wheels = sorted(Path("dist").glob("fincore-*.whl"))
+ if len(wheels) != 1:
+ raise SystemExit(f"expected exactly one fincore wheel in dist/, found {len(wheels)}")
+ subprocess.run(
+ [
+ sys.executable,
+ "scripts/snapshot_public_api.py",
+ "--source-root",
+ ".",
+ "--wheel",
+ str(wheels[0]),
+ "--surface",
+ "fincore",
+ "--compare",
+ ],
+ check=True,
+ )
+ PY
+
- name: Check dist
run: twine check dist/*
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index b8d10d43..9e8ed64f 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -118,6 +118,33 @@ jobs:
- name: Verify candidate digest
run: python scripts/check_release_candidate.py --dist dist/ --manifest dist/release-manifest.json
+ - name: Compare static root API with candidate wheel
+ run: |
+ # This schema-v2 source scan does not import optional dependencies.
+ python - <<'PY'
+ from pathlib import Path
+ import subprocess
+ import sys
+
+ wheels = sorted(Path("dist").glob("fincore-*.whl"))
+ if len(wheels) != 1:
+ raise SystemExit(f"expected exactly one fincore wheel in dist/, found {len(wheels)}")
+ subprocess.run(
+ [
+ sys.executable,
+ "scripts/snapshot_public_api.py",
+ "--source-root",
+ ".",
+ "--wheel",
+ str(wheels[0]),
+ "--surface",
+ "fincore",
+ "--compare",
+ ],
+ check=True,
+ )
+ PY
+
- name: Verify release consistency
run: python scripts/check_release_consistency.py --dist dist/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 65781b64..383a1f97 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -36,20 +36,7 @@ repos:
entry: mypy
args:
- --ignore-missing-imports
- - fincore/core
- - fincore/constants
- - fincore/metrics
- - fincore/plugin
- - fincore/data
- - fincore/optimization
- - fincore/attribution
- - fincore/report
- - fincore/risk
- - fincore/simulation
- - fincore/utils
- - fincore/viz
- - fincore/empyrical.py
- - fincore/tearsheets
- - fincore/pyfolio.py
+ - fincore
language: system
+ pass_filenames: false
files: ^fincore/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fe2ce062..40b7782e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,11 +1,29 @@
# Changelog
-All notable changes to Fincore are documented here. This changelog reports version **0.5.1.dev0**, the current development version; **0.5.0** is the latest release.
+All notable changes to Fincore are documented here. This changelog reports version **0.5.1**, the current release; **0.5.0** is the preceding breaking unified-core release.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and version labels follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-## [Unreleased]
+## [0.5.1] - 2026-09-20
+
+### Fixed
+
+- **Quality gates** — repaired the MyPy pre-commit hook so it checks the
+ current `fincore` package rather than paths removed by the 0.5 cutover.
+- **Reproducible packaging contracts** — excluded pytest's repository-local
+ temporary staging directory from clean-source copies used by wheel and sdist
+ contract tests.
+- **Static root contract** — made the source-only public-API scanner recognise
+ the explicitly exported `fincore.__version__` value, allowing the source and
+ built wheel root namespaces to be compared without importing optional
+ dependencies.
+
+### Changed
+
+- Release validation now treats the frozen 0.4 API snapshot as historical
+ compatibility evidence and validates the 0.5 root namespace through the
+ schema-v2 static contract instead. No public API or runtime behavior changed.
## [0.5.0] - 2026-09-01
diff --git a/CITATION.cff b/CITATION.cff
index 2006af3c..97365c4a 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -6,7 +6,7 @@ authors:
email: "yunjinqi@gmail.com"
repository-code: "https://github.com/cloudQuant/fincore"
license: MIT
-version: 0.4.0.dev0
+version: 0.5.1
keywords:
- quantitative-finance
- performance-analytics
diff --git a/README.md b/README.md
index 0264d4f4..71e81449 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# fincore | Quantitative Performance & Risk Analytics
-
+
@@ -22,10 +22,11 @@ Pyfolio, and Alphalens, but rebuilds them as one low-coupling core rather than
three package-shaped APIs. The public contract is organized by domain and each
capability has one canonical implementation path.
-Version **0.5.0** is a deliberately breaking release. It does **not**
-provide `fincore.empyrical`, `fincore.pyfolio`, `fincore.alphalens`, flat root
-metric functions, compatibility aliases, or façade classes. Update imports to
-the focused domain modules described in the [migration guide](docs/MIGRATION.md).
+Version **0.5.1** is a quality-maintenance release that preserves the
+deliberately breaking 0.5.0 public contract. It does **not** provide
+`fincore.empyrical`, `fincore.pyfolio`, `fincore.alphalens`, flat root metric
+functions, compatibility aliases, or façade classes. Update imports to the
+focused domain modules described in the [migration guide](docs/MIGRATION.md).
| Domain | Use it for | Canonical examples |
| --- | --- | --- |
diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md
index 8ca85977..c5499bb7 100644
--- a/THIRD_PARTY_NOTICES.md
+++ b/THIRD_PARTY_NOTICES.md
@@ -31,7 +31,7 @@ Do not hand-edit the JSON block without updating the paired evidence in
"schema_version": 2,
"project": {
"name": "fincore",
- "version": "0.5.1.dev0",
+ "version": "0.5.1",
"license": "MIT"
},
"empyrical": {
diff --git a/docs/plans/2026-09-20-cq-2026-09-quality-stabilization.md b/docs/plans/2026-09-20-cq-2026-09-quality-stabilization.md
new file mode 100644
index 00000000..7053e0cd
--- /dev/null
+++ b/docs/plans/2026-09-20-cq-2026-09-quality-stabilization.md
@@ -0,0 +1,54 @@
+# CQ-2026-09 质量稳态迭代计划
+
+## 目标与边界
+
+| 字段 | 结论 |
+| --- | --- |
+| 候选基线 | `master@2354bb5` 的隔离工作树;不使用原工作区的未提交治理改动。 |
+| 目标 | 修复失效的本地质量门、隔离测试构建输入、建立 0.5 根命名空间的静态契约检查,并让发布流水线验证这些证据。 |
+| 不变性 | 不修改 `fincore/` 运行时代码、数值结果、异常语义、公开 API、依赖解析或性能目标。 |
+| 发布目标 | `0.5.1`(补丁版本);不重新解释 0.5.0 的破坏性 API 决策。 |
+| 非目标 | 兼容层恢复、旧 0.4 API fixture 重写、清理原工作区的忽略工件、改变用户拥有的未提交文件。 |
+
+## 已验证的发现
+
+| ID | 发现 | 状态 | 处置 |
+| --- | --- | --- | --- |
+| CQ-01 | pre-commit 的 MyPy 参数仍指向已删除目录,hook 退出而不是检查当前包。 | `PASS`(独立复验) | 改为一次 `mypy --ignore-missing-imports fincore`,关闭文件名自动追加。 |
+| CQ-02 | 打包合同测试复制源码时会纳入仓库内 `.pytest_tmp`,可能造成递归复制/竞态。 | `PASS`(独立复验) | 两个 staging ignore 列表加入该目录,并有断言。 |
+| CQ-03 | `check_quality_snapshot.py --allow-snapshot-output-commit` 在干净候选基线通过。 | `PASS` | 把它作为候选证据,不把输出型快照提交误判为陈旧。 |
+| CQ-04 | `public-api-0.4.0.dev0.json` 是原子切换前的 schema-v1 历史投影;它与 0.5 源码不同是预期结果。 | `EXPECTED_HISTORICAL` | 不覆盖、不删除;发布门改用 0.5 的 schema-v2 源码/轮子契约。 |
+| CQ-05 | schema-v2 扫描根包时遗漏 `__all__` 明确导出的动态 `__version__` 赋值。 | `PASS`(独立复验) | 静态解析器与回归用例已修复;不接触包运行时代码。 |
+| CQ-06 | 隔离 Ruff 扫描有 37 个 C901 热点、5 个 F401 和 6 个 E402。 | `DEFERRED` | 生产源码重构必须先取得缺失的项目开发规范与逐模块行为基线。 |
+| CQ-07 | `AGENTS.md` 引用的 `.joyincode/rules/backend.md`、`frontend.md` 在候选基线不存在。 | `BLOCKED` | 在规则恢复或项目负责人明确 waiver 前,不进行 `fincore/` 源码重构。 |
+
+## 实施编排
+
+| 阶段 | 修改范围 | 验收证据 | 状态 |
+| --- | --- | --- | --- |
+| Q1:本地类型门 | `.pre-commit-config.yaml` | `pre-commit run mypy --all-files` 为 0 | `PASS` |
+| Q2:构建输入隔离 | 两个 `tests/packaging/` 文件 | 在仓库外 `--basetemp` 的 20 个打包合同用例通过 | `PASS` |
+| Q3:根 API 静态契约 | `scripts/snapshot_public_api.py`、质量/契约测试 | 当前源根和 wheel 根快照均可生成,源/轮一致 | `PASS`(本地) |
+| Q4:CI/CD 绑定 | `ci.yml`、`publish.yml` 的最小必要门 | tag 版本、静态契约、构建候选和已有 release 证据均 fail-closed | `PASS`(本地;远端待运行) |
+| Q5:在线文档与版本 | `pyproject.toml`、CHANGELOG、MkDocs、README、release note | `0.5.1` 一致且 `mkdocs build --strict` 通过 | `PASS`(本地;部署待运行) |
+| Q6:发布验收 | 候选 commit、tag、GitHub CI/Docs/Publish 运行、PyPI 项目页 | 所有本地与远端门 `PASS`;否则明确 `NO-GO` | `PENDING` |
+
+## 不变性与验收矩阵
+
+| 用例 | 命令/证据 | 通过条件 |
+| --- | --- | --- |
+| AC-01 | `conda run -n base … python -m pre_commit run mypy --all-files` | hook 实际检查 `fincore` 且 exit 0。 |
+| AC-02 | `pytest tests/packaging/test_release_consistency.py tests/packaging/test_wheel_contents.py --basetemp ` | staging source 不含 `.pytest_tmp`;全部节点通过。 |
+| AC-03 | `snapshot_public_api.py --source-root . --surface fincore` | 仅语法扫描即可产出 schema-v2 根契约;不导入可选依赖。 |
+| AC-04 | 构建后的 `--source-root . --wheel --surface fincore --compare` | 源码与候选 wheel 的根公开契约逐字一致。 |
+| AC-05 | `pytest -m 'not integration_online' --basetemp ` | 非在线回归完整通过;网络型检查单独报告。 |
+| AC-06 | `python -m mkdocs build --strict`、release consistency、twine、wheel-consumer | 文档与可发布工件一致。 |
+| AC-07 | tag 的 GitHub CI、Docs 与 Publish-to-PyPI 运行;PyPI `0.5.1` 元数据 | 远端证据全部成功后才宣布发布。 |
+
+## 风险控制与放行条件
+
+1. `public-api-0.4.0.dev0.json` 仅保留为历史发现输入;不能用“更新 fixture”掩盖 0.5 公共契约变化。
+2. Q3 只允许测试工具和合同文件变化;任何 `fincore/` 源码差异立即停止并重新评审。
+3. Q4 不复制已有的完整 CI;只增加当前缺失且能从候选工件验证的 fail-closed 门。
+4. Q6 之前必须有干净候选提交、精确 tag/版本一致性、完整 CI 与文档构建成功。PyPI trusted publishing 失败时状态为 `NO-GO`,不得声称已发布。
+5. CQ-06/CQ-07 未关闭前,本迭代不把“降低复杂度”作为已完成成果;它们将作为后续、规则齐全且逐模块基线完备的源码重构迭代。
diff --git a/docs/quality/current-baseline.json b/docs/quality/current-baseline.json
index 1383f808..b80672ca 100644
--- a/docs/quality/current-baseline.json
+++ b/docs/quality/current-baseline.json
@@ -3,7 +3,7 @@
"docs/quality/current-baseline.json",
"docs/quality/current-baseline.md"
],
- "copy_manifest_sha256": "f44c243bb0503a3cfb68649037d5283e2582608ec1070a21ce5e611c24f18706",
+ "copy_manifest_sha256": "aae6ff55dde15aa706dcb6f49ba30a15b0d5519f8f6c3ed2eecb15187c4316ff",
"environment": {
"matplotlib": "3.10.9",
"numpy": "1.26.4",
@@ -11,7 +11,7 @@
"python": "3.11.8 | packaged by conda-forge | (main, Feb 16 2024, 20:49:36) [Clang 16.0.6 ]",
"scipy": "1.17.1"
},
- "generated_at": "2026-09-01T14:00:47.471138+00:00",
+ "generated_at": "2026-09-20T02:06:36.731364+00:00",
"non_serial_counts_match": true,
"outcome": "pass",
"runs": [
@@ -28,14 +28,14 @@
"-m",
"not slow and not integration"
],
- "discovered": 1854,
- "duration_seconds": 104.972,
+ "discovered": 1857,
+ "duration_seconds": 204.036,
"integrity_ok": true,
"label": "trusted-baseline",
- "output_tail": "...... [ 64%]\ntests/test_metrics/test_perf_stats_more_coverage.py . [ 64%]\ntests/test_metrics/test_positions_metrics.py ............. [ 64%]\ntests/test_metrics/test_ratios_additional.py ................... [ 65%]\ntests/test_metrics/test_ratios_complete.py ............................. [ 67%]\n....................... [ 68%]\ntests/test_metrics/test_ratios_line_417.py ... [ 68%]\ntests/test_metrics/test_remaining_missing_coverage.py .................. [ 69%]\n [ 69%]\ntests/test_metrics/test_risk_complete.py ............................... [ 71%]\n..... [ 71%]\ntests/test_metrics/test_risk_more_coverage.py ......... [ 72%]\ntests/test_metrics/test_rolling_additional.py ............... [ 73%]\ntests/test_metrics/test_rolling_detailed.py ....... [ 73%]\ntests/test_metrics/test_round_trips_agg.py ..... [ 73%]\ntests/test_metrics/test_round_trips_extract.py ......... [ 74%]\ntests/test_metrics/test_round_trips_groupby.py .... [ 74%]\ntests/test_metrics/test_round_trips_stats.py ...... [ 74%]\ntests/test_metrics/test_stats_additional.py ................. [ 75%]\ntests/test_metrics/test_stats_hurst_coverage.py .... [ 75%]\ntests/test_metrics/test_stats_missing_lines.py ...... [ 76%]\ntests/test_metrics/test_stats_unreachable.py ... [ 76%]\ntests/test_metrics/test_timing_additional.py ....... [ 76%]\ntests/test_metrics/test_transactions_edge_cases.py ... [ 76%]\ntests/test_optimization/risk_parity_coverage/test_budget_validation.py . [ 77%]\n..... [ 77%]\ntests/test_optimization/risk_parity_coverage/test_custom_budget.py .. [ 77%]\ntests/test_optimization/risk_parity_coverage/test_edge_cases.py ....... [ 77%]\ntests/test_optimization/risk_parity_coverage/test_objective_function.py . [ 77%]\n... [ 77%]\ntests/test_optimization/risk_parity_coverage/test_output_structure.py .. [ 78%]\n... [ 78%]\ntests/test_optimization/test_edge_cases.py .......... [ 78%]\ntests/test_optimization/test_efficient_frontier.py ........ [ 79%]\ntests/test_optimization/test_frontier_full_coverage.py .............. [ 79%]\ntests/test_optimization/test_frontier_missing_coverage.py ... [ 80%]\ntests/test_optimization/test_objectives_full_coverage.py ............... [ 80%]\n..... [ 81%]\ntests/test_optimization/test_optimization.py ........................... [ 82%]\n............... [ 83%]\ntests/test_optimization/test_optimize.py ........... [ 84%]\ntests/test_optimization/test_risk_parity.py ............. [ 84%]\ntests/test_optimization/test_risk_parity_full_coverage.py .............. [ 85%]\n.......... [ 86%]\ntests/test_optimization/test_utils_full_coverage.py ............ [ 86%]\ntests/test_risk/evt/test_evt_cvar.py .............. [ 87%]\ntests/test_risk/evt/test_evt_nan.py ..... [ 87%]\ntests/test_risk/evt/test_evt_var.py ........... [ 88%]\ntests/test_risk/evt/test_extreme_risk.py .... [ 88%]\ntests/test_risk/evt/test_gev_fit.py .. [ 88%]\ntests/test_risk/evt/test_gpd_fit.py .......... [ 89%]\ntests/test_risk/evt/test_hill_estimator.py ......... [ 89%]\ntests/test_risk/risk_models/test_garch.py ............ [ 90%]\ntests/test_risk/test_backtesting.py .............. [ 91%]\ntests/test_risk/test_evt_remaining_coverage.py ...... [ 91%]\ntests/test_risk/test_garch_detailed.py ............................ [ 92%]\ntests/test_risk/test_models.py ........................ [ 94%]\ntests/test_simulation/test_base.py ..... [ 94%]\ntests/test_simulation/test_bootstrap.py ................ [ 95%]\ntests/test_simulation/test_monte_carlo.py ............. [ 96%]\ntests/test_simulation/test_paths.py ............... [ 96%]\ntests/test_simulation/test_scenarios.py ........... [ 97%]\ntests/test_viz/test_bokeh_coverage.py ......... [ 97%]\ntests/test_viz/test_html_backend.py ..... [ 98%]\ntests/test_viz/test_interactive/test_interactive.py .................... [ 99%]\n [ 99%]\ntests/test_viz/test_matplotlib_backend.py .. [ 99%]\ntests/test_viz/test_plotly_coverage.py ........... [100%]\n\n=============================== warnings summary ===============================\ntests/numerical/test_factor_multihorizon_preparation.py::test_prepares_each_horizon_without_discarding_short_horizon_rows\n /private/var/folders/7d/hnmknylj1w91h3cvq6mh3thm0000gn/T/fincore-quality-baseline-rujjt3zf/run-01-trusted-baseline/fincore/factor_analysis/portfolio.py:188: UserWarning: 'freq' not set, using business day calendar\n return positions(weights, period)\n\n-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html\n==== 1838 passed, 15 skipped, 1 deselected, 1 warning in 100.82s (0:01:40) =====\n",
- "passed": 1838,
+ "output_tail": "...... [ 64%]\ntests/test_metrics/test_perf_stats_more_coverage.py . [ 64%]\ntests/test_metrics/test_positions_metrics.py ............. [ 64%]\ntests/test_metrics/test_ratios_additional.py ................... [ 66%]\ntests/test_metrics/test_ratios_complete.py ............................. [ 67%]\n....................... [ 68%]\ntests/test_metrics/test_ratios_line_417.py ... [ 68%]\ntests/test_metrics/test_remaining_missing_coverage.py .................. [ 69%]\n [ 69%]\ntests/test_metrics/test_risk_complete.py ............................... [ 71%]\n..... [ 71%]\ntests/test_metrics/test_risk_more_coverage.py ......... [ 72%]\ntests/test_metrics/test_rolling_additional.py ............... [ 73%]\ntests/test_metrics/test_rolling_detailed.py ....... [ 73%]\ntests/test_metrics/test_round_trips_agg.py ..... [ 73%]\ntests/test_metrics/test_round_trips_extract.py ......... [ 74%]\ntests/test_metrics/test_round_trips_groupby.py .... [ 74%]\ntests/test_metrics/test_round_trips_stats.py ...... [ 74%]\ntests/test_metrics/test_stats_additional.py ................. [ 75%]\ntests/test_metrics/test_stats_hurst_coverage.py .... [ 75%]\ntests/test_metrics/test_stats_missing_lines.py ...... [ 76%]\ntests/test_metrics/test_stats_unreachable.py ... [ 76%]\ntests/test_metrics/test_timing_additional.py ....... [ 76%]\ntests/test_metrics/test_transactions_edge_cases.py ... [ 76%]\ntests/test_optimization/risk_parity_coverage/test_budget_validation.py . [ 77%]\n..... [ 77%]\ntests/test_optimization/risk_parity_coverage/test_custom_budget.py .. [ 77%]\ntests/test_optimization/risk_parity_coverage/test_edge_cases.py ....... [ 77%]\ntests/test_optimization/risk_parity_coverage/test_objective_function.py . [ 77%]\n... [ 78%]\ntests/test_optimization/risk_parity_coverage/test_output_structure.py .. [ 78%]\n... [ 78%]\ntests/test_optimization/test_edge_cases.py .......... [ 78%]\ntests/test_optimization/test_efficient_frontier.py ........ [ 79%]\ntests/test_optimization/test_frontier_full_coverage.py .............. [ 80%]\ntests/test_optimization/test_frontier_missing_coverage.py ... [ 80%]\ntests/test_optimization/test_objectives_full_coverage.py ............... [ 80%]\n..... [ 81%]\ntests/test_optimization/test_optimization.py ........................... [ 82%]\n............... [ 83%]\ntests/test_optimization/test_optimize.py ........... [ 84%]\ntests/test_optimization/test_risk_parity.py ............. [ 84%]\ntests/test_optimization/test_risk_parity_full_coverage.py .............. [ 85%]\n.......... [ 86%]\ntests/test_optimization/test_utils_full_coverage.py ............ [ 86%]\ntests/test_risk/evt/test_evt_cvar.py .............. [ 87%]\ntests/test_risk/evt/test_evt_nan.py ..... [ 87%]\ntests/test_risk/evt/test_evt_var.py ........... [ 88%]\ntests/test_risk/evt/test_extreme_risk.py .... [ 88%]\ntests/test_risk/evt/test_gev_fit.py .. [ 88%]\ntests/test_risk/evt/test_gpd_fit.py .......... [ 89%]\ntests/test_risk/evt/test_hill_estimator.py ......... [ 89%]\ntests/test_risk/risk_models/test_garch.py ............ [ 90%]\ntests/test_risk/test_backtesting.py .............. [ 91%]\ntests/test_risk/test_evt_remaining_coverage.py ...... [ 91%]\ntests/test_risk/test_garch_detailed.py ............................ [ 92%]\ntests/test_risk/test_models.py ........................ [ 94%]\ntests/test_simulation/test_base.py ..... [ 94%]\ntests/test_simulation/test_bootstrap.py ................ [ 95%]\ntests/test_simulation/test_monte_carlo.py ............. [ 96%]\ntests/test_simulation/test_paths.py ............... [ 96%]\ntests/test_simulation/test_scenarios.py ........... [ 97%]\ntests/test_viz/test_bokeh_coverage.py ......... [ 97%]\ntests/test_viz/test_html_backend.py ..... [ 98%]\ntests/test_viz/test_interactive/test_interactive.py .................... [ 99%]\n [ 99%]\ntests/test_viz/test_matplotlib_backend.py .. [ 99%]\ntests/test_viz/test_plotly_coverage.py ........... [100%]\n\n=============================== warnings summary ===============================\ntests/numerical/test_factor_multihorizon_preparation.py::test_prepares_each_horizon_without_discarding_short_horizon_rows\n /private/var/folders/7d/hnmknylj1w91h3cvq6mh3thm0000gn/T/fincore-quality-baseline-32293h70/run-01-trusted-baseline/fincore/factor_analysis/portfolio.py:188: UserWarning: 'freq' not set, using business day calendar\n return positions(weights, period)\n\n-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html\n==== 1841 passed, 15 skipped, 1 deselected, 1 warning in 199.39s (0:03:19) =====\n",
+ "passed": 1841,
"returncode": 0,
- "selected": 1853,
+ "selected": 1856,
"selector": "not slow and not integration",
"skipped": 15,
"warnings": 1,
@@ -59,11 +59,11 @@
"-m",
"serial"
],
- "discovered": 1854,
- "duration_seconds": 6.476,
+ "discovered": 1857,
+ "duration_seconds": 9.555,
"integrity_ok": true,
"label": "serial",
- "output_tail": "============================= test session starts ==============================\nplatform darwin -- Python 3.11.8, pytest-8.4.2, pluggy-1.6.0\nbenchmark: 5.1.0 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)\nrootdir: /private/var/folders/7d/hnmknylj1w91h3cvq6mh3thm0000gn/T/fincore-quality-baseline-rujjt3zf/run-02-serial\nconfigfile: pyproject.toml\ntestpaths: tests\nplugins: asyncio-0.24.0, cov-6.0.0, Faker-37.5.3, anyio-4.11.0, flask-1.3.0, doctestplus-1.6.0, html-4.1.1, xdist-3.8.0, time-machine-2.16.0, timeout-2.4.0, metadata-3.1.1, rerunfailures-14.0, playwright-0.7.0, mock-3.14.1, ordering-0.6, sugar-1.1.1, benchmark-5.1.0, allure-pytest-2.15.2, langsmith-0.4.38, base-url-2.1.0, dash-2.18.2, picked-0.5.1, hypothesis-6.148.5, requests-mock-1.12.1\nasyncio: mode=Mode.STRICT, default_loop_scope=None\ncollected 1854 items / 1851 deselected / 3 selected\n\ntests/test_metrics/test_alpha_beta_line_543_596.py .. [ 66%]\ntests/test_metrics/test_alpha_beta_missing_coverage.py . [100%]\n\n====================== 3 passed, 1851 deselected in 3.93s ======================\n",
+ "output_tail": "============================= test session starts ==============================\nplatform darwin -- Python 3.11.8, pytest-8.4.2, pluggy-1.6.0\nbenchmark: 5.1.0 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)\nrootdir: /private/var/folders/7d/hnmknylj1w91h3cvq6mh3thm0000gn/T/fincore-quality-baseline-32293h70/run-02-serial\nconfigfile: pyproject.toml\ntestpaths: tests\nplugins: asyncio-0.24.0, cov-6.0.0, Faker-37.5.3, anyio-4.11.0, flask-1.3.0, doctestplus-1.6.0, html-4.1.1, xdist-3.8.0, time-machine-2.16.0, timeout-2.4.0, metadata-3.1.1, rerunfailures-14.0, playwright-0.7.0, mock-3.14.1, ordering-0.6, sugar-1.1.1, benchmark-5.1.0, allure-pytest-2.15.2, langsmith-0.4.38, base-url-2.1.0, dash-2.18.2, picked-0.5.1, hypothesis-6.148.5, requests-mock-1.12.1\nasyncio: mode=Mode.STRICT, default_loop_scope=None\ncollected 1857 items / 1854 deselected / 3 selected\n\ntests/test_metrics/test_alpha_beta_line_543_596.py .. [ 66%]\ntests/test_metrics/test_alpha_beta_missing_coverage.py . [100%]\n\n====================== 3 passed, 1854 deselected in 6.08s ======================\n",
"passed": 3,
"returncode": 0,
"selected": 3,
@@ -90,14 +90,14 @@
"-m",
"not serial and not slow and not integration"
],
- "discovered": 1854,
- "duration_seconds": 103.155,
+ "discovered": 1857,
+ "duration_seconds": 215.449,
"integrity_ok": true,
"label": "non-serial-single",
- "output_tail": "..... [ 64%]\ntests/test_metrics/test_perf_stats_more_coverage.py . [ 64%]\ntests/test_metrics/test_positions_metrics.py ............. [ 64%]\ntests/test_metrics/test_ratios_additional.py ................... [ 65%]\ntests/test_metrics/test_ratios_complete.py ............................. [ 67%]\n....................... [ 68%]\ntests/test_metrics/test_ratios_line_417.py ... [ 68%]\ntests/test_metrics/test_remaining_missing_coverage.py .................. [ 69%]\n [ 69%]\ntests/test_metrics/test_risk_complete.py ............................... [ 71%]\n..... [ 71%]\ntests/test_metrics/test_risk_more_coverage.py ......... [ 72%]\ntests/test_metrics/test_rolling_additional.py ............... [ 73%]\ntests/test_metrics/test_rolling_detailed.py ....... [ 73%]\ntests/test_metrics/test_round_trips_agg.py ..... [ 73%]\ntests/test_metrics/test_round_trips_extract.py ......... [ 74%]\ntests/test_metrics/test_round_trips_groupby.py .... [ 74%]\ntests/test_metrics/test_round_trips_stats.py ...... [ 74%]\ntests/test_metrics/test_stats_additional.py ................. [ 75%]\ntests/test_metrics/test_stats_hurst_coverage.py .... [ 75%]\ntests/test_metrics/test_stats_missing_lines.py ...... [ 76%]\ntests/test_metrics/test_stats_unreachable.py ... [ 76%]\ntests/test_metrics/test_timing_additional.py ....... [ 76%]\ntests/test_metrics/test_transactions_edge_cases.py ... [ 76%]\ntests/test_optimization/risk_parity_coverage/test_budget_validation.py . [ 76%]\n..... [ 77%]\ntests/test_optimization/risk_parity_coverage/test_custom_budget.py .. [ 77%]\ntests/test_optimization/risk_parity_coverage/test_edge_cases.py ....... [ 77%]\ntests/test_optimization/risk_parity_coverage/test_objective_function.py . [ 77%]\n... [ 77%]\ntests/test_optimization/risk_parity_coverage/test_output_structure.py .. [ 78%]\n... [ 78%]\ntests/test_optimization/test_edge_cases.py .......... [ 78%]\ntests/test_optimization/test_efficient_frontier.py ........ [ 79%]\ntests/test_optimization/test_frontier_full_coverage.py .............. [ 79%]\ntests/test_optimization/test_frontier_missing_coverage.py ... [ 80%]\ntests/test_optimization/test_objectives_full_coverage.py ............... [ 80%]\n..... [ 81%]\ntests/test_optimization/test_optimization.py ........................... [ 82%]\n............... [ 83%]\ntests/test_optimization/test_optimize.py ........... [ 84%]\ntests/test_optimization/test_risk_parity.py ............. [ 84%]\ntests/test_optimization/test_risk_parity_full_coverage.py .............. [ 85%]\n.......... [ 86%]\ntests/test_optimization/test_utils_full_coverage.py ............ [ 86%]\ntests/test_risk/evt/test_evt_cvar.py .............. [ 87%]\ntests/test_risk/evt/test_evt_nan.py ..... [ 87%]\ntests/test_risk/evt/test_evt_var.py ........... [ 88%]\ntests/test_risk/evt/test_extreme_risk.py .... [ 88%]\ntests/test_risk/evt/test_gev_fit.py .. [ 88%]\ntests/test_risk/evt/test_gpd_fit.py .......... [ 89%]\ntests/test_risk/evt/test_hill_estimator.py ......... [ 89%]\ntests/test_risk/risk_models/test_garch.py ............ [ 90%]\ntests/test_risk/test_backtesting.py .............. [ 91%]\ntests/test_risk/test_evt_remaining_coverage.py ...... [ 91%]\ntests/test_risk/test_garch_detailed.py ............................ [ 92%]\ntests/test_risk/test_models.py ........................ [ 94%]\ntests/test_simulation/test_base.py ..... [ 94%]\ntests/test_simulation/test_bootstrap.py ................ [ 95%]\ntests/test_simulation/test_monte_carlo.py ............. [ 96%]\ntests/test_simulation/test_paths.py ............... [ 96%]\ntests/test_simulation/test_scenarios.py ........... [ 97%]\ntests/test_viz/test_bokeh_coverage.py ......... [ 97%]\ntests/test_viz/test_html_backend.py ..... [ 98%]\ntests/test_viz/test_interactive/test_interactive.py .................... [ 99%]\n [ 99%]\ntests/test_viz/test_matplotlib_backend.py .. [ 99%]\ntests/test_viz/test_plotly_coverage.py ........... [100%]\n\n=============================== warnings summary ===============================\ntests/numerical/test_factor_multihorizon_preparation.py::test_prepares_each_horizon_without_discarding_short_horizon_rows\n /private/var/folders/7d/hnmknylj1w91h3cvq6mh3thm0000gn/T/fincore-quality-baseline-rujjt3zf/run-03-non-serial-single/fincore/factor_analysis/portfolio.py:188: UserWarning: 'freq' not set, using business day calendar\n return positions(weights, period)\n\n-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html\n===== 1835 passed, 15 skipped, 4 deselected, 1 warning in 99.12s (0:01:39) =====\n",
- "passed": 1835,
+ "output_tail": "..... [ 64%]\ntests/test_metrics/test_perf_stats_more_coverage.py . [ 64%]\ntests/test_metrics/test_positions_metrics.py ............. [ 64%]\ntests/test_metrics/test_ratios_additional.py ................... [ 65%]\ntests/test_metrics/test_ratios_complete.py ............................. [ 67%]\n....................... [ 68%]\ntests/test_metrics/test_ratios_line_417.py ... [ 68%]\ntests/test_metrics/test_remaining_missing_coverage.py .................. [ 69%]\n [ 69%]\ntests/test_metrics/test_risk_complete.py ............................... [ 71%]\n..... [ 71%]\ntests/test_metrics/test_risk_more_coverage.py ......... [ 72%]\ntests/test_metrics/test_rolling_additional.py ............... [ 73%]\ntests/test_metrics/test_rolling_detailed.py ....... [ 73%]\ntests/test_metrics/test_round_trips_agg.py ..... [ 73%]\ntests/test_metrics/test_round_trips_extract.py ......... [ 74%]\ntests/test_metrics/test_round_trips_groupby.py .... [ 74%]\ntests/test_metrics/test_round_trips_stats.py ...... [ 74%]\ntests/test_metrics/test_stats_additional.py ................. [ 75%]\ntests/test_metrics/test_stats_hurst_coverage.py .... [ 75%]\ntests/test_metrics/test_stats_missing_lines.py ...... [ 76%]\ntests/test_metrics/test_stats_unreachable.py ... [ 76%]\ntests/test_metrics/test_timing_additional.py ....... [ 76%]\ntests/test_metrics/test_transactions_edge_cases.py ... [ 76%]\ntests/test_optimization/risk_parity_coverage/test_budget_validation.py . [ 77%]\n..... [ 77%]\ntests/test_optimization/risk_parity_coverage/test_custom_budget.py .. [ 77%]\ntests/test_optimization/risk_parity_coverage/test_edge_cases.py ....... [ 77%]\ntests/test_optimization/risk_parity_coverage/test_objective_function.py . [ 77%]\n... [ 77%]\ntests/test_optimization/risk_parity_coverage/test_output_structure.py .. [ 78%]\n... [ 78%]\ntests/test_optimization/test_edge_cases.py .......... [ 78%]\ntests/test_optimization/test_efficient_frontier.py ........ [ 79%]\ntests/test_optimization/test_frontier_full_coverage.py .............. [ 79%]\ntests/test_optimization/test_frontier_missing_coverage.py ... [ 80%]\ntests/test_optimization/test_objectives_full_coverage.py ............... [ 80%]\n..... [ 81%]\ntests/test_optimization/test_optimization.py ........................... [ 82%]\n............... [ 83%]\ntests/test_optimization/test_optimize.py ........... [ 84%]\ntests/test_optimization/test_risk_parity.py ............. [ 84%]\ntests/test_optimization/test_risk_parity_full_coverage.py .............. [ 85%]\n.......... [ 86%]\ntests/test_optimization/test_utils_full_coverage.py ............ [ 86%]\ntests/test_risk/evt/test_evt_cvar.py .............. [ 87%]\ntests/test_risk/evt/test_evt_nan.py ..... [ 87%]\ntests/test_risk/evt/test_evt_var.py ........... [ 88%]\ntests/test_risk/evt/test_extreme_risk.py .... [ 88%]\ntests/test_risk/evt/test_gev_fit.py .. [ 88%]\ntests/test_risk/evt/test_gpd_fit.py .......... [ 89%]\ntests/test_risk/evt/test_hill_estimator.py ......... [ 89%]\ntests/test_risk/risk_models/test_garch.py ............ [ 90%]\ntests/test_risk/test_backtesting.py .............. [ 91%]\ntests/test_risk/test_evt_remaining_coverage.py ...... [ 91%]\ntests/test_risk/test_garch_detailed.py ............................ [ 92%]\ntests/test_risk/test_models.py ........................ [ 94%]\ntests/test_simulation/test_base.py ..... [ 94%]\ntests/test_simulation/test_bootstrap.py ................ [ 95%]\ntests/test_simulation/test_monte_carlo.py ............. [ 96%]\ntests/test_simulation/test_paths.py ............... [ 96%]\ntests/test_simulation/test_scenarios.py ........... [ 97%]\ntests/test_viz/test_bokeh_coverage.py ......... [ 97%]\ntests/test_viz/test_html_backend.py ..... [ 98%]\ntests/test_viz/test_interactive/test_interactive.py .................... [ 99%]\n [ 99%]\ntests/test_viz/test_matplotlib_backend.py .. [ 99%]\ntests/test_viz/test_plotly_coverage.py ........... [100%]\n\n=============================== warnings summary ===============================\ntests/numerical/test_factor_multihorizon_preparation.py::test_prepares_each_horizon_without_discarding_short_horizon_rows\n /private/var/folders/7d/hnmknylj1w91h3cvq6mh3thm0000gn/T/fincore-quality-baseline-32293h70/run-03-non-serial-single/fincore/factor_analysis/portfolio.py:188: UserWarning: 'freq' not set, using business day calendar\n return positions(weights, period)\n\n-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html\n==== 1838 passed, 15 skipped, 4 deselected, 1 warning in 210.07s (0:03:30) =====\n",
+ "passed": 1838,
"returncode": 0,
- "selected": 1850,
+ "selected": 1853,
"selector": "not serial and not slow and not integration",
"skipped": 15,
"warnings": 1,
@@ -125,14 +125,14 @@
"auto",
"--dist=loadscope"
],
- "discovered": 1854,
- "duration_seconds": 54.304,
+ "discovered": 1857,
+ "duration_seconds": 90.98,
"integrity_ok": true,
"label": "non-serial-xdist",
- "output_tail": "============================= test session starts ==============================\nplatform darwin -- Python 3.11.8, pytest-8.4.2, pluggy-1.6.0\nbenchmark: 5.1.0 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)\nrootdir: /private/var/folders/7d/hnmknylj1w91h3cvq6mh3thm0000gn/T/fincore-quality-baseline-rujjt3zf/run-04-non-serial-xdist\nconfigfile: pyproject.toml\ntestpaths: tests\nplugins: asyncio-0.24.0, cov-6.0.0, Faker-37.5.3, anyio-4.11.0, flask-1.3.0, doctestplus-1.6.0, html-4.1.1, xdist-3.8.0, time-machine-2.16.0, timeout-2.4.0, metadata-3.1.1, rerunfailures-14.0, playwright-0.7.0, mock-3.14.1, ordering-0.6, sugar-1.1.1, benchmark-5.1.0, allure-pytest-2.15.2, langsmith-0.4.38, base-url-2.1.0, dash-2.18.2, picked-0.5.1, hypothesis-6.148.5, requests-mock-1.12.1\nasyncio: mode=Mode.STRICT, default_loop_scope=None\ncreated: 8/8 workers\n8 workers [1850 items]\n\n........................................................................ [ 3%]\n........................................................................ [ 7%]\n........................................................................ [ 11%]\n........................................................................ [ 15%]\n........................................................................ [ 19%]\n........................................................................ [ 23%]\n........................................................................ [ 27%]\n........................................................................ [ 31%]\n........................................................................ [ 35%]\n........................................................................ [ 38%]\n...............................................................s........ [ 42%]\n........................................................................ [ 46%]\n........................................................................ [ 50%]\n........................................................................ [ 54%]\n........................................................................ [ 58%]\n........................................................................ [ 62%]\n....ssssssss............................................................ [ 66%]\n........................................................................ [ 70%]\n........................................................................ [ 73%]\n........................................................................ [ 77%]\n........................................................................ [ 81%]\n........................................................................ [ 85%]\n........................................................................ [ 89%]\n...ssssss............................................................... [ 93%]\n........................................................................ [ 97%]\n.................................................. [100%]\n=============================== warnings summary ===============================\ntests/numerical/test_factor_multihorizon_preparation.py::test_prepares_each_horizon_without_discarding_short_horizon_rows\n /private/var/folders/7d/hnmknylj1w91h3cvq6mh3thm0000gn/T/fincore-quality-baseline-rujjt3zf/run-04-non-serial-xdist/fincore/factor_analysis/portfolio.py:188: UserWarning: 'freq' not set, using business day calendar\n return positions(weights, period)\n\n-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html\n================= 1835 passed, 15 skipped, 1 warning in 48.72s =================\n/Users/yunjinqi/opt/anaconda3/lib/python3.11/site-packages/pytest_benchmark/logger.py:39: PytestBenchmarkWarning: Benchmarks are automatically disabled because xdist plugin is active.Benchmarks cannot be performed reliably in a parallelized environment.\n warner(PytestBenchmarkWarning(text))\n",
- "passed": 1835,
+ "output_tail": "============================= test session starts ==============================\nplatform darwin -- Python 3.11.8, pytest-8.4.2, pluggy-1.6.0\nbenchmark: 5.1.0 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)\nrootdir: /private/var/folders/7d/hnmknylj1w91h3cvq6mh3thm0000gn/T/fincore-quality-baseline-32293h70/run-04-non-serial-xdist\nconfigfile: pyproject.toml\ntestpaths: tests\nplugins: asyncio-0.24.0, cov-6.0.0, Faker-37.5.3, anyio-4.11.0, flask-1.3.0, doctestplus-1.6.0, html-4.1.1, xdist-3.8.0, time-machine-2.16.0, timeout-2.4.0, metadata-3.1.1, rerunfailures-14.0, playwright-0.7.0, mock-3.14.1, ordering-0.6, sugar-1.1.1, benchmark-5.1.0, allure-pytest-2.15.2, langsmith-0.4.38, base-url-2.1.0, dash-2.18.2, picked-0.5.1, hypothesis-6.148.5, requests-mock-1.12.1\nasyncio: mode=Mode.STRICT, default_loop_scope=None\ncreated: 8/8 workers\n8 workers [1853 items]\n\n........................................................................ [ 3%]\n........................................................................ [ 7%]\n........................................................................ [ 11%]\n........................................................................ [ 15%]\n........................................................................ [ 19%]\n........................................................................ [ 23%]\n........................................................................ [ 27%]\n........................................................................ [ 31%]\n........................................................................ [ 34%]\n........................................................................ [ 38%]\n........................................................................ [ 42%]\n............................ss.ssssss.s................................. [ 46%]\n........................................................................ [ 50%]\n........................................................................ [ 54%]\n........................................................................ [ 58%]\n........................................................................ [ 62%]\n........................................................................ [ 66%]\n........................................................................ [ 69%]\n........................................................................ [ 73%]\n........................................................................ [ 77%]\n........................................................................ [ 81%]\n........................................................................ [ 85%]\n........................................................................ [ 89%]\n.......................ssssss........................................... [ 93%]\n........................................................................ [ 97%]\n..................................................... [100%]\n=============================== warnings summary ===============================\ntests/numerical/test_factor_multihorizon_preparation.py::test_prepares_each_horizon_without_discarding_short_horizon_rows\n /private/var/folders/7d/hnmknylj1w91h3cvq6mh3thm0000gn/T/fincore-quality-baseline-32293h70/run-04-non-serial-xdist/fincore/factor_analysis/portfolio.py:188: UserWarning: 'freq' not set, using business day calendar\n return positions(weights, period)\n\n-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html\n============ 1838 passed, 15 skipped, 1 warning in 75.51s (0:01:15) ============\n/Users/yunjinqi/opt/anaconda3/lib/python3.11/site-packages/pytest_benchmark/logger.py:39: PytestBenchmarkWarning: Benchmarks are automatically disabled because xdist plugin is active.Benchmarks cannot be performed reliably in a parallelized environment.\n warner(PytestBenchmarkWarning(text))\n",
+ "passed": 1838,
"returncode": 0,
- "selected": 1850,
+ "selected": 1853,
"selector": "not serial and not slow and not integration",
"skipped": 15,
"warnings": 1,
@@ -156,18 +156,18 @@
"--ignore=tests/benchmarks",
"-m",
"not slow and not integration",
- "--cov=/private/var/folders/7d/hnmknylj1w91h3cvq6mh3thm0000gn/T/fincore-quality-baseline-rujjt3zf/run-05-branch-coverage/fincore",
+ "--cov=/private/var/folders/7d/hnmknylj1w91h3cvq6mh3thm0000gn/T/fincore-quality-baseline-32293h70/run-05-branch-coverage/fincore",
"--cov-branch",
"--cov-report=term-missing"
],
- "discovered": 1854,
- "duration_seconds": 134.966,
+ "discovered": 1857,
+ "duration_seconds": 302.923,
"integrity_ok": true,
"label": "branch-coverage",
- "output_tail": "ns.py 11 0 0 0 100%\nfincore/portfolio/positions.py 163 7 20 7 92% 194->200, 222->225, 269-270, 335, 463-467, 479, 529->532\nfincore/portfolio/round_trips.py 169 8 62 5 94% 96-99, 124-127, 260->262, 271->284, 303->310\nfincore/portfolio/transactions.py 149 44 40 11 66% 86-93, 128->131, 174->177, 178->181, 210-223, 253-271, 294, 301-308, 317, 354-355, 362, 373-383, 391->393, 496\nfincore/report/__init__.py 2 2 0 0 0% 8-10\nfincore/report/factor/__init__.py 2 2 0 0 0% 3-5\nfincore/report/factor/compute.py 21 5 6 2 67% 18-21, 28\nfincore/report/models.py 128 0 60 0 100%\nfincore/report/operations.py 10 0 0 0 100%\nfincore/report/portfolio/__init__.py 2 2 0 0 0% 3-5\nfincore/report/portfolio/compute.py 129 0 50 2 99% 74->76, 201->203\nfincore/report/renderers/__init__.py 2 2 0 0 0% 3-5\nfincore/report/renderers/html.py 66 3 20 3 93% 28, 30, 43\nfincore/report/renderers/interactive.py 24 0 8 0 100%\nfincore/report/renderers/matplotlib.py 37 1 10 2 94% 33, 58->60\nfincore/report/renderers/pdf.py 25 0 0 0 100%\nfincore/report/renderers/xlsx.py 27 0 6 0 100%\nfincore/report/risk.py 17 2 4 2 81% 23, 33\nfincore/report/styles.py 3 0 0 0 100%\nfincore/risk/__init__.py 2 2 0 0 0% 7-9\nfincore/risk/backtesting.py 111 1 30 1 99% 90\nfincore/risk/calibration.py 25 2 8 2 88% 35, 63\nfincore/risk/diagnostics.py 259 39 132 35 81% 70, 72, 74, 80, 82, 84, 88, 90, 92, 104-105, 111, 113, 121, 125, 129, 157, 163, 177, 179, 181, 183, 186, 197, 208, 213, 267, 339, 349, 354, 356, 358, 360, 363-364, 366, 374, 376, 378\nfincore/risk/evt.py 220 21 88 18 87% 37, 57, 61, 74, 213, 218-220, 228, 285, 290, 351-355, 430, 447, 460, 472, 537, 557, 572\nfincore/risk/garch.py 214 4 52 4 97% 157, 411, 427, 449\nfincore/risk/models.py 108 2 44 2 97% 189, 197\nfincore/risk/operations.py 14 0 0 0 100%\nfincore/risk/report.py 201 32 86 24 80% 73, 75, 82, 84, 86, 206, 210, 237-238, 240, 288, 290, 292, 294-296, 302, 325, 334, 337, 353-354, 372, 375, 382-383, 391, 400, 402, 408, 414, 418\nfincore/risk/specs.py 50 2 8 0 97% 97-98\nfincore/runtime/__init__.py 11 11 0 0 0% 8-20\nfincore/runtime/artifacts.py 76 0 32 2 98% 89->91, 113->103\nfincore/runtime/backends.py 16 0 2 0 100%\nfincore/runtime/builtins.py 22 0 8 0 100%\nfincore/runtime/catalog.py 68 1 22 1 98% 122\nfincore/runtime/data.py 70 0 32 0 100%\nfincore/runtime/engine.py 60 1 12 0 99% 61\nfincore/runtime/results.py 208 0 136 0 100%\nfincore/runtime/session.py 65 1 14 3 95% 73->79, 87->98, 97\nfincore/runtime/specs.py 59 0 22 0 100%\nfincore/runtime/time_series.py 85 22 40 12 68% 21-23, 34, 39, 51-53, 57, 63-64, 69, 70->exit, 84, 92, 102-105, 142, 145-148\nfincore/runtime/types.py 9 9 0 0 0% 3-14\nfincore/runtime/validation.py 36 0 16 0 100%\nfincore/simulation/__init__.py 2 2 0 0 0% 7-9\nfincore/simulation/base.py 53 0 10 0 100%\nfincore/simulation/bootstrap.py 57 0 20 0 100%\nfincore/simulation/monte_carlo.py 51 0 6 1 98% 197->205\nfincore/simulation/operations.py 12 0 0 0 100%\nfincore/simulation/paths.py 49 2 20 2 91% 135-136, 221->224\nfincore/simulation/scenarios.py 63 0 20 1 99% 71->70\nfincore/viz/__init__.py 2 2 0 0 0% 14-16\nfincore/viz/base.py 34 4 14 3 85% 74->78, 79-81, 84-86\nfincore/viz/contracts.py 8 0 0 0 100%\nfincore/viz/html_backend.py 89 6 18 2 93% 93->95, 124-126, 130-132, 144->147\nfincore/viz/interactive/__init__.py 9 9 2 0 0% 8-22\nfincore/viz/interactive/bokeh_backend.py 138 1 20 3 97% 358->361, 442, 492->503\nfincore/viz/interactive/plotly_backend.py 110 0 20 2 98% 450->466, 466->481\nfincore/viz/matplotlib_backend.py 76 0 20 6 94% 39->41, 58->60, 79->81, 82->92, 92->94, 110->112\n------------------------------------------------------------------------------------------\nTOTAL 11934 2468 4238 641 76%\n\n==== 1838 passed, 15 skipped, 1 deselected, 1 warning in 130.29s (0:02:10) =====\n",
- "passed": 1838,
+ "output_tail": "ns.py 11 0 0 0 100%\nfincore/portfolio/positions.py 163 7 20 7 92% 194->200, 222->225, 269-270, 335, 463-467, 479, 529->532\nfincore/portfolio/round_trips.py 169 8 62 5 94% 96-99, 124-127, 260->262, 271->284, 303->310\nfincore/portfolio/transactions.py 149 44 40 11 66% 86-93, 128->131, 174->177, 178->181, 210-223, 253-271, 294, 301-308, 317, 354-355, 362, 373-383, 391->393, 496\nfincore/report/__init__.py 2 2 0 0 0% 8-10\nfincore/report/factor/__init__.py 2 2 0 0 0% 3-5\nfincore/report/factor/compute.py 21 5 6 2 67% 18-21, 28\nfincore/report/models.py 128 0 60 0 100%\nfincore/report/operations.py 10 0 0 0 100%\nfincore/report/portfolio/__init__.py 2 2 0 0 0% 3-5\nfincore/report/portfolio/compute.py 129 0 50 2 99% 74->76, 201->203\nfincore/report/renderers/__init__.py 2 2 0 0 0% 3-5\nfincore/report/renderers/html.py 66 3 20 3 93% 28, 30, 43\nfincore/report/renderers/interactive.py 24 0 8 0 100%\nfincore/report/renderers/matplotlib.py 37 1 10 2 94% 33, 58->60\nfincore/report/renderers/pdf.py 25 0 0 0 100%\nfincore/report/renderers/xlsx.py 27 0 6 0 100%\nfincore/report/risk.py 17 2 4 2 81% 23, 33\nfincore/report/styles.py 3 0 0 0 100%\nfincore/risk/__init__.py 2 2 0 0 0% 7-9\nfincore/risk/backtesting.py 111 1 30 1 99% 90\nfincore/risk/calibration.py 25 2 8 2 88% 35, 63\nfincore/risk/diagnostics.py 259 39 132 35 81% 70, 72, 74, 80, 82, 84, 88, 90, 92, 104-105, 111, 113, 121, 125, 129, 157, 163, 177, 179, 181, 183, 186, 197, 208, 213, 267, 339, 349, 354, 356, 358, 360, 363-364, 366, 374, 376, 378\nfincore/risk/evt.py 220 21 88 18 87% 37, 57, 61, 74, 213, 218-220, 228, 285, 290, 351-355, 430, 447, 460, 472, 537, 557, 572\nfincore/risk/garch.py 214 4 52 4 97% 157, 411, 427, 449\nfincore/risk/models.py 108 2 44 2 97% 189, 197\nfincore/risk/operations.py 14 0 0 0 100%\nfincore/risk/report.py 201 32 86 24 80% 73, 75, 82, 84, 86, 206, 210, 237-238, 240, 288, 290, 292, 294-296, 302, 325, 334, 337, 353-354, 372, 375, 382-383, 391, 400, 402, 408, 414, 418\nfincore/risk/specs.py 50 2 8 0 97% 97-98\nfincore/runtime/__init__.py 11 11 0 0 0% 8-20\nfincore/runtime/artifacts.py 76 0 32 2 98% 89->91, 113->103\nfincore/runtime/backends.py 16 0 2 0 100%\nfincore/runtime/builtins.py 22 0 8 0 100%\nfincore/runtime/catalog.py 68 1 22 1 98% 122\nfincore/runtime/data.py 70 0 32 0 100%\nfincore/runtime/engine.py 60 1 12 0 99% 61\nfincore/runtime/results.py 208 0 136 0 100%\nfincore/runtime/session.py 65 1 14 3 95% 73->79, 87->98, 97\nfincore/runtime/specs.py 59 0 22 0 100%\nfincore/runtime/time_series.py 85 22 40 12 68% 21-23, 34, 39, 51-53, 57, 63-64, 69, 70->exit, 84, 92, 102-105, 142, 145-148\nfincore/runtime/types.py 9 9 0 0 0% 3-14\nfincore/runtime/validation.py 36 0 16 0 100%\nfincore/simulation/__init__.py 2 2 0 0 0% 7-9\nfincore/simulation/base.py 53 0 10 0 100%\nfincore/simulation/bootstrap.py 57 0 20 0 100%\nfincore/simulation/monte_carlo.py 51 0 6 1 98% 197->205\nfincore/simulation/operations.py 12 0 0 0 100%\nfincore/simulation/paths.py 49 2 20 2 91% 135-136, 221->224\nfincore/simulation/scenarios.py 63 0 20 1 99% 71->70\nfincore/viz/__init__.py 2 2 0 0 0% 14-16\nfincore/viz/base.py 34 4 14 3 85% 74->78, 79-81, 84-86\nfincore/viz/contracts.py 8 0 0 0 100%\nfincore/viz/html_backend.py 89 6 18 2 93% 93->95, 124-126, 130-132, 144->147\nfincore/viz/interactive/__init__.py 9 9 2 0 0% 8-22\nfincore/viz/interactive/bokeh_backend.py 138 1 20 3 97% 358->361, 442, 492->503\nfincore/viz/interactive/plotly_backend.py 110 0 20 2 98% 450->466, 466->481\nfincore/viz/matplotlib_backend.py 76 0 20 6 94% 39->41, 58->60, 79->81, 82->92, 92->94, 110->112\n------------------------------------------------------------------------------------------\nTOTAL 11934 2468 4238 641 76%\n\n==== 1841 passed, 15 skipped, 1 deselected, 1 warning in 293.05s (0:04:53) =====\n",
+ "passed": 1841,
"returncode": 0,
- "selected": 1853,
+ "selected": 1856,
"selector": "not slow and not integration",
"skipped": 15,
"warnings": 1,
@@ -181,7 +181,7 @@
],
"schema_version": 1,
"source": {
- "commit": "fb3c9289409ac73e0fe89bdf2abff5db1cf6a4e0",
+ "commit": "75de146098de6a1fe81245f9c345ba8f9ba9f8a3",
"dirty": false,
"tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"untracked_inventory": {},
@@ -194,12 +194,12 @@
".github/PAGES_SETUP.md": "6de153556154fbe6b2237ad68da2548d13f7d5fa020055231f09f68cb709c2e1",
".github/dependabot.yml": "e0dfdd6d9f46b5181a3f397c7c358a0bff8c6116b78795b1fe19f6f449dbf80d",
".github/pull_request_template.md": "e7f827e799592d4854090a769b43e089dd1d7c891aae7f8a32d5d229842c5c15",
- ".github/workflows/ci.yml": "c4227efaa6aeaf9ff66352bafbf13f791b77bf5097a7f446c5e89f526965c68b",
+ ".github/workflows/ci.yml": "b8353254711238994b65228e2a69e3a402599680fa9aa3e1f6100fb8720e2b9e",
".github/workflows/docs.yml": "eef29779845306e16278dabd9221eb74ec4c0be9b4b4269f232d14a7e4059b58",
- ".github/workflows/publish.yml": "e2bcc26d0a20515ad383b356d233ed42ba440a4b5f5fb489ed12115341e75ef0",
+ ".github/workflows/publish.yml": "e6a99313b970c22b36958d34ef4fbca644520107cff68dd246c681029e0c9713",
".github/workflows/test-priority.yml": "472d9eeba62f53ecbde24911f9f6159dbce622eafcd94b283619376e839e4e4a",
".gitignore": "84de05a29c235f5f66c11d26106478222aa95688f078b79cec063a1c207da63e",
- ".pre-commit-config.yaml": "5e8d317b5defaed6677da0f9d60163c4b6092c39166a09fa3c195caea6e942a2",
+ ".pre-commit-config.yaml": "ced9c2806d7936ab1012f1ca8c3ab6b3d0183b7b4d95031c6f10e46a65e98b09",
".readthedocs.yaml": "e189d43e175719e522c8b9cd5460774616b0f357ef3ba2ed65376309de213f92",
".superpowers/sdd/2026-08-12-fincore-empyrical-pyfolio-convergence/progress.md": "029e68bf90a7e837b07326c2d8eef138ccb9ab6b71dca3b34b501b05656c8402",
".superpowers/sdd/2026-08-12-fincore-empyrical-pyfolio-convergence/review-eea7fb4..03d4e70.diff": "e93c2a07197c69d2da49cb816cd611d27971bf2986576afae4253718e87ea2a7",
@@ -213,8 +213,8 @@
".superpowers/sdd/2026-08-12-fincore-empyrical-pyfolio-convergence/task-7-report.md": "718325ad79b3451de1dae666c61b0b377d98bba335e30de7d2f28121f9376667",
".superpowers/sdd/2026-08-12-fincore-empyrical-pyfolio-convergence/task-8-report.md": "a636306ec2eea1ae9742ee8dde563da6390e935e35a24d61851a9a02ecc442ed",
".superpowers/sdd/2026-08-13-fincore-alphalens-integration/task-9-report.md": "2acae8452fe2d24420cd5d4e7a0554eafeab9590151559c5fe7f7013b2b971dc",
- "CHANGELOG.md": "7557fef8a10943ff8836cd7598f7e4647bfa24eaf1b984bdf8fa564affec505d",
- "CITATION.cff": "ededc736deb5157945c70782118948e59363dc186fd56c44bdf587dc050c3195",
+ "CHANGELOG.md": "4d7817bef3459ac0928f90bc7e885106f4b5aede386bb125b1eefe43a7315c1e",
+ "CITATION.cff": "31a7221c4ef7f48475983d8a194c296771bfd65dafaa84e26ac6dd18c4c4dc27",
"CLAUDE.md": "1310585d68372a43292a003c22e5433b77cb8fe202a0a03990cc2a606e68c544",
"CODE_OF_CONDUCT.md": "56a37169dc4da0de32e0d96fc15fe682d26679defdc8e96d5bad20f425421238",
"CONTRIBUTING.md": "b3a5db0b2c6b9ac8ae152ccb07f861681da1bdacbe1b482643b769d040c5bd73",
@@ -222,11 +222,11 @@
"LICENSE": "91678e4c2826977300350d3f428b962db60fb4b01772d28b94d7b5ae38dff4df",
"MANIFEST.in": "91ed76ae35626981276ce9b11a8d228f87074866f5f969029196b60fa7a2560d",
"NOTICE": "873c4b9adc3133648521bc8a0ed7768b0d6aa511040da3647ba2fcccbd25ae31",
- "README.md": "724500458baac9f2e70ccf3ee74e2d3ac8f22b9ff80433d8fae26443bd95099c",
+ "README.md": "c8f6e1a375936e15c60a19d5dd8c8d2d82d4badb74f42030d466912d03a2b712",
"SECURITY.md": "8ff10c2369e7b1736f183515ff71f759af8a0ab93fbb8c8a4679c74c59a03873",
"SUPPORT.md": "50ce6554b5340e89c8490e7357e2009d75d181ac05cd30d742146b79232947d1",
"THIRD_PARTY_LICENSES/Apache-2.0.txt": "cc370d11750096e1d59fb0e36e17b393150b3f1bf9b216e8482fdddb0d704698",
- "THIRD_PARTY_NOTICES.md": "4dd799549e8a216ff5281888abd8354c308e43cbe4292b9635ee62774aa38a35",
+ "THIRD_PARTY_NOTICES.md": "92f534e9dd78669549a2a8a20409184348c3e5e70d6cb3da93844ddcfeec0882",
"_bmad-output/COMPLETE-report.md": "c7cdc3ff3fb76f6087637d74d364994bff904d0404ba6226376ba759d629b0c7",
"_bmad-output/FINAL-COMPLETION-REPORT.md": "45b7b2afe1cca78858e5d790dcb27a81aa43896c94ac017501485d5fad25dc52",
"_bmad-output/FINAL-SUMMARY.md": "338bccdd814f6edec381219c7fbec145be30f7fcd01ab6d0b614bb04a1f9d519",
@@ -293,6 +293,7 @@
"docs/plans/2026-08-17-fincore-platform-excellence.md": "4f7d6a8bbfce9dcc38f8d17c9da03d9c65f7ba62d1a262372c24d74ddf81d2f4",
"docs/plans/2026-08-20-fincore-unified-analytics-platform.md": "c90f0ffe8896637061e593af1a717ed715baa6ed3cf94a37e22cc1c76868e2b2",
"docs/plans/2026-08-30-fincore-0042-r2-breaking-unified-core.md": "6c30a22d14d8c2e6b22c8f986a519ed4e5f99df7544c21e8a74216d6d2daf8ae",
+ "docs/plans/2026-09-20-cq-2026-09-quality-stabilization.md": "3a5a5f8d6f610a8dec9c3837828bdbf9bb92e06d8bed4ec80bc81d03a72f4a91",
"docs/quality/0042-r2-acceptance.md": "1202cdc12d72a7b1ca9b2db008590ccde1d29f1ee4df7ccceebce44f96abe73b",
"docs/quality/0042-r2-architecture-baseline.json": "99f870c392a8bfec2af0ae76fdeb54920b7a4e67f3306ec92d0b0a78de74d746",
"docs/quality/0042-r2-capability-baseline.json": "b6b7d6941d4a3e39ab3fa4e47d73026d97d1e9bdb985a6d015ea55b98084d0cb",
@@ -314,7 +315,7 @@
"docs/quality/release-candidate-checklist.md": "4c3b5a7cc5cad2764d489e9316362ab2647fa27c584bcf1a94336cc74582ca3d",
"docs/requirements-docs.txt": "b925c61fe99e15b5fd0118b9dd5ce23dd1730e021937477909fe1fd212344b6e",
"docs/testing-guide.md": "ad7a7f47b74897d20fb3f467a27b62d7a368907d185fae2c78a319b04dadbd46",
- "docs/upstream-provenance.md": "6fe8622a7687e6ffa4a4fbe8626e25966ab662e05c029da3957c371a1c644c2d",
+ "docs/upstream-provenance.md": "8f53327651c722e889634c65553d8f2f0d21ed1c1c38fccc379e63436197db71",
"docs/user_guide.md": "29fa6e17ec38e3566a9ee183ecd151fc24e7025f902a7300a82814a0b8d9ffb7",
"docs/\u5df2\u5b9e\u73b0\u51fd\u6570\u7d22\u5f15.md": "d49c4dc233bd5f0fe6a121a05636c62f030e9c34f0522f6e2e430367b7525ab8",
"docs/\u6559\u7a0b/01_\u5165\u95e8_\u7b56\u7565\u7ee9\u6548\u5206\u6790.md": "4e07f4c14d9323a7285a2d95cee7ecdadc992601d8c18c9df381f8f1dd116bd2",
@@ -540,7 +541,7 @@
"fincore/viz/interactive/bokeh_backend.py": "49e7cf6612f2fdae3206b91c1b6f205120e0326fe576cc64f2f64caac5f53ccd",
"fincore/viz/interactive/plotly_backend.py": "b0a5540460be6d814d456a4994c95c613f212239142a69ccf019aa136368b2ed",
"fincore/viz/matplotlib_backend.py": "b9a8194011fa133fde1a1b0382755b6310d3d690d4d4dad4ea07a01b16ff24ba",
- "mkdocs.yml": "e199a7f7c869118b2954f55ca217778e7a86656973a6333703bf6daefdeb0a63",
+ "mkdocs.yml": "ecb97d3b20bbc7d5643b83ecf01b75142ce1fbf6e4a624a1b42d234e50ac3356",
"mkdocs_docs/api/attribution.md": "511c5ec978d307776433ae82782f32c1d9b3485c4eb114e723e69ed47b7cf6d8",
"mkdocs_docs/api/factor-analysis.md": "0039357450d4aa1c8a99f49f980b19127107eaf6e8fd8228847f0a82ebdfb732",
"mkdocs_docs/api/fincore.md": "7c7a0e27e6de4489abdbfeda1bbe064fb9d5c4f131b70ddfe9d9db48a2d1a6c9",
@@ -556,11 +557,12 @@
"mkdocs_docs/api/viz.md": "317691354039ed39f265bcc64715ed76b39bd46ce1480afdb2b49ffe7a871fa2",
"mkdocs_docs/concepts/factor-research-protocol.md": "53ad7f99c2628ff7f18a5ff3b3c0ca5cffec31f71e1dfad8d34fb46ef736757d",
"mkdocs_docs/development/api-stability.md": "767e4504ab9dbcf825d88a30384e84d53b1fe14bbb25b64ed62ffe9309261568",
- "mkdocs_docs/development/changelog.md": "e29191b186a61872431974ad6bcb58362aff3915bcc7320f84bf5d8b8cd8e112",
+ "mkdocs_docs/development/changelog.md": "8dd053184616505a340053431b0cee334c722eecd331a7109d6376bb519e1da9",
"mkdocs_docs/development/compatibility.md": "ec94d6338596f938acf76c531c61ade0e9cd802ba353ab43003dece8ca885d74",
"mkdocs_docs/development/contributing.md": "90bb96664062803b0ce25a1e87101bbb6a5921297f84e40863ccc2a8ccd7cc6f",
"mkdocs_docs/development/guide.md": "c6d969cddd8cd7a6b8a665c19246f335f054109cbda78b52ece4076456d474b0",
"mkdocs_docs/development/releases/0.5.0.md": "8a1a3dd74961a1adfb1aa67187689a7cdc98aeb0f2c5105d845436072e35b191",
+ "mkdocs_docs/development/releases/0.5.1.md": "3038d199da463cc6dbd9528ca5e2d56662a3d049600eb1f148940d5a787fc960",
"mkdocs_docs/ecosystem.md": "295a85cdd3d50ed6362b175394292763b9b9bc4032b7eedd0a0ab6897e76befb",
"mkdocs_docs/getting-started/installation.md": "a1af545f4a557f65c67a8ff0095a17ca029a190042857922503e969aede0b804",
"mkdocs_docs/getting-started/migration.md": "59993055c89d474b458b58bb316cdd0dfcc04bac90452095fd04b43e96a3556e",
@@ -574,8 +576,8 @@
"mkdocs_docs/guide/reproducible-research.md": "09940692a3a76e5697c5c3bab97c6642f63fcff169c5bd10b2088e9548f69a67",
"mkdocs_docs/guide/risk-validation.md": "ab1a61620028264c87356e8c9864a520645a8a3579bf49fe617798fbdf4aa22c",
"mkdocs_docs/guide/visualization.md": "0a69c0d70a685c0442e5858185a6c35dd85ca29212b095292a384a4cabdb64db",
- "mkdocs_docs/index.md": "96c0165d99f8ee014d5eaa30d7d879ccda915a7b5f7aa823797d0cc0d097b6fa",
- "pyproject.toml": "7c7f46077fdf5837b7b8d856d60212de4f755daabe9e17074e37f0949c6ee691",
+ "mkdocs_docs/index.md": "37295c700440fba1fa690157a93b02f0dd0d6a3b2090f9d60f462af424baaf49",
+ "pyproject.toml": "13a20f630de55f1c891c848638fb1435ac6679349a107a2e622e46300da6247a",
"requirements-0042-r2-acceptance.txt": "efc0b40798237b74abb390e7b50fec256379947e842c0f7408dd38270dbc2245",
"requirements-test.txt": "e2b434696134b45e23851283f8b8c47d07a2f88b415b753855cde82bc684e90f",
"requirements.txt": "1d9f31fe8c13c5095b4364d96888520e1b505cc638fbc3c663e267ae728a9b40",
@@ -629,7 +631,7 @@
"scripts/run_round_trip_benchmarks.py": "c2fbe9c5d11f35555b8236b30094b243892398122860e31ac07e99e90cd451a9",
"scripts/simple_import_test.py": "1ea674dce8ce839faf8751cd70ad9ce9a496a82f14dbaa187a5bffb476d92222",
"scripts/simple_test.py": "3fe765fe1789455854293e9b5c724edf1b4e9d0e57dc78068ebf239e23c76823",
- "scripts/snapshot_public_api.py": "7292af23c1ac2589d57577bc7d1f71f8e9434bb4a9b46928d6197531eff6e5cd",
+ "scripts/snapshot_public_api.py": "089e03fd56324e72571ca740e6ae2a07eba9fddda076b887a7e7b736447e1dbe",
"scripts/test_installed_wheel.py": "19ea9e70fe50810021717d8af974d42c9332e66b135ce47dac531387c550c600",
"scripts/test_python_versions_simple.bat": "efdc43ca139067b65e3a9cfd834b137a175027392fef59dd5dd8ece9269fd0db",
"scripts/test_python_versions_simple.sh": "5da6f4d9cfdda166d8a7027f6574bc0e012a7514cdc2e9966bf5abe938fa1959",
@@ -776,9 +778,9 @@
"tests/packaging/test_extras_union.py": "f55a88d944c246e86d2de17a51d70bc5f6b4b1579a7fa16df18be700e0740196",
"tests/packaging/test_notices.py": "fafaebee4b8db54cb78cb0300ca1c8341bbf4fe6bdd623e7a816969ce8bf828c",
"tests/packaging/test_optional_extras.py": "f181eeb77dedc2e9aff3441aac5e78b446a9dc76da9d936284a4eb2b6f8f3938",
- "tests/packaging/test_release_candidate.py": "358e9e69c0d91d9d7023cd9e18a40f12f8ae950c350fa06a04c71915311e293d",
- "tests/packaging/test_release_consistency.py": "fe9daac453f536aded9082624b4fa31ae3f8983cc677b4da6175893b21a5c01f",
- "tests/packaging/test_wheel_contents.py": "ffb0860dde85a3ce9ca21db62da5df365ed3f8a372aa73072035c1293f270fd8",
+ "tests/packaging/test_release_candidate.py": "0247433d4a9526c776d934f432b6b5070d8c6fc3b20440e4b0240a1af5b71427",
+ "tests/packaging/test_release_consistency.py": "97e0e1c8edc2815117ea58ce4d2a433af50fd403d73965973e3960638aa9a74d",
+ "tests/packaging/test_wheel_contents.py": "88ad60ad81f95089bf02036f6ca7fc489574c4b6c67a4187c3a64f2fdecd216f",
"tests/parity/fixtures/0042-r2-architecture-threshold-policy.json": "d216ff3a35026b9f9deea65a34d489fe4592cc8a5767beff6f4686b5b99e4c88",
"tests/parity/fixtures/0042-r2-gate-manifest.json": "703ddefea944094ba0c4c06892bf06317e9b5c456b4e7e60fdb55bb979d03c02",
"tests/parity/fixtures/0042-r2-matrix-evidence.schema.json": "61168cb4d0cb94193c1adb6091168a5cacf74390acd74159718083e5b891a44a",
@@ -823,7 +825,7 @@
"tests/quality/test_0042_r2_matrix_evidence.py": "b6356cbb9280e0cdfd7c30976aec3b9029cdb951339f99379a6d87e607ea9ef6",
"tests/quality/test_0042_r2_matrix_workflow.py": "1b2f4e4f535914e80f1b15817cdd470c7ead96ddc139b08a4b893c1d3ebdeffb",
"tests/quality/test_0042_r2_module_disposition.py": "ef444dd73388475273232b1e1a26e432f8d75b2e4647b514a53dc8fdce8f44d7",
- "tests/quality/test_0042_r2_planned_api.py": "43869d71e154fc350bfa6f49ec24e7dab0139a14cec330f1d55686abf7058cbb",
+ "tests/quality/test_0042_r2_planned_api.py": "fcabaccd7be79e290d561481f171231c98b76ed36d9b72ed6357a4f91ff5ff60",
"tests/quality/test_0042_r2_profiling_contract.py": "df243e36e8a942e04292a52ea21fc60e019229756f8baeccb9019682458ccf09",
"tests/quality/test_0042_r2_test_node_disposition.py": "cfc08bc16d6f3e664ac004ceeb61412a2b3fa801abe627c66ac371cfb3584dac",
"tests/quality/test_0042_r2_tooling_source_root.py": "bbe59079b80665906ed4839dff69300e98b1d2a0c011685b309acd277f036cb0",
@@ -842,7 +844,7 @@
"tests/quality/test_render_capability_inventory.py": "1ee92ec0324c49bbe3dc8809bb13e9ba3111132805b703c8b774409fbe014075",
"tests/quality/test_repository_surface_disposition.py": "349343921ae332da7c3eb9bd621470c5b180c526738fcbc5849791a71c01d962",
"tests/quality/test_run_0042_r2_acceptance.py": "986d0807954ac2649cc8e5bad10cc7b364c2cab52e5c1f58630db7874f551f29",
- "tests/quality/test_snapshot_public_api_source.py": "7dc5b1ec890efcc22e2cba63c5d27c3c3edd451d88cee363a5b2d5b1be2e8f72",
+ "tests/quality/test_snapshot_public_api_source.py": "7bb64cc40ef9efad8b981b6d1b221274ad6085b7540b2a3c05bf3a7a99994a7f",
"tests/quality/test_workflow_integrity.py": "843aafa24a773c3f135fdb35e07dc993fd1742898850c28183af66995162a3e3",
"tests/risk/test_risk_canonical_domain.py": "e61b613dc6bde9ba40b6bd349bbe6b4f34c2da55233752eae887b293cc0a59ca",
"tests/runtime/test_artifacts.py": "ebd4c9b462a3687df387b2d95d571741b2a0e34f41ae63a983fa2bb6e0f9a599",
@@ -1009,6 +1011,6 @@
"tests/test_viz/test_matplotlib_backend.py": "65215188a84983aa74c900e72f70486b1b16a1830b7763f6f3579e5d6d047a38",
"tests/test_viz/test_plotly_coverage.py": "6c46c55f5397073c7771d50e1f34f9f6dd86c9297e1ae0159fa37d0fd4a12aa6"
},
- "included_file_manifest_sha256": "f44c243bb0503a3cfb68649037d5283e2582608ec1070a21ce5e611c24f18706"
+ "included_file_manifest_sha256": "aae6ff55dde15aa706dcb6f49ba30a15b0d5519f8f6c3ed2eecb15187c4316ff"
}
}
diff --git a/docs/quality/current-baseline.md b/docs/quality/current-baseline.md
index a5e10bc8..c3202bc7 100644
--- a/docs/quality/current-baseline.md
+++ b/docs/quality/current-baseline.md
@@ -1,14 +1,14 @@
# Current Quality Baseline
-Generated: `2026-09-01T14:00:47.471138+00:00`
+Generated: `2026-09-20T02:06:36.731364+00:00`
## Provenance
-- Source commit: `fb3c9289409ac73e0fe89bdf2abff5db1cf6a4e0`
+- Source commit: `75de146098de6a1fe81245f9c345ba8f9ba9f8a3`
- Dirty state: `False`
- Tracked diff SHA256: `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`
- Untracked manifest SHA256: `44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a`
-- Disposable-copy manifest SHA256: `f44c243bb0503a3cfb68649037d5283e2582608ec1070a21ce5e611c24f18706`
+- Disposable-copy manifest SHA256: `aae6ff55dde15aa706dcb6f49ba30a15b0d5519f8f6c3ed2eecb15187c4316ff`
- Manifest exclusions: `docs/quality/current-baseline.json, docs/quality/current-baseline.md`
## Environment
@@ -23,11 +23,11 @@ Generated: `2026-09-01T14:00:47.471138+00:00`
| Run | Selector | Discovered | Selected | Passed | Skipped | Warnings | Duration | Exit |
| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
-| trusted-baseline | `not slow and not integration` | 1854 | 1853 | 1838 | 15 | 1 | 104.972s | 0 |
-| serial | `serial` | 1854 | 3 | 3 | 0 | 0 | 6.476s | 0 |
-| non-serial-single | `not serial and not slow and not integration` | 1854 | 1850 | 1835 | 15 | 1 | 103.155s | 0 |
-| non-serial-xdist | `not serial and not slow and not integration` | 1854 | 1850 | 1835 | 15 | 1 | 54.304s | 0 |
-| branch-coverage | `not slow and not integration` | 1854 | 1853 | 1838 | 15 | 1 | 134.966s | 0 |
+| trusted-baseline | `not slow and not integration` | 1857 | 1856 | 1841 | 15 | 1 | 204.036s | 0 |
+| serial | `serial` | 1857 | 3 | 3 | 0 | 0 | 9.555s | 0 |
+| non-serial-single | `not serial and not slow and not integration` | 1857 | 1853 | 1838 | 15 | 1 | 215.449s | 0 |
+| non-serial-xdist | `not serial and not slow and not integration` | 1857 | 1853 | 1838 | 15 | 1 | 90.980s | 0 |
+| branch-coverage | `not slow and not integration` | 1857 | 1856 | 1841 | 15 | 1 | 302.923s | 0 |
## Branch Coverage
diff --git a/docs/upstream-provenance.md b/docs/upstream-provenance.md
index bc60d0f1..80911825 100644
--- a/docs/upstream-provenance.md
+++ b/docs/upstream-provenance.md
@@ -107,3 +107,10 @@ That review has not occurred in this task. The distributed NOTICE and Apache
license copy preserve observed attribution and terms. Pending review does not
block CI/CD, but it also does not create a release-approval claim or legal
conclusion.
+
+## 2026-09-20 Release-identity refresh
+
+Only the Fincore product version advances from the development identifier
+`0.5.1.dev0` to stable `0.5.1`. Upstream commit identifiers, content digests,
+observed license information, and all `pending-human-review` statuses remain
+unchanged. This identity update is not a legal approval or review.
diff --git a/mkdocs.yml b/mkdocs.yml
index fbaf0d68..8d133e74 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -95,5 +95,6 @@ nav:
- Development Guide: development/guide.md
- API Stability: development/api-stability.md
- Changelog: development/changelog.md
+ - Release notes 0.5.1: development/releases/0.5.1.md
- Release notes 0.5.0: development/releases/0.5.0.md
- Ecosystem: ecosystem.md
diff --git a/mkdocs_docs/development/changelog.md b/mkdocs_docs/development/changelog.md
index 43b995d0..f0937703 100644
--- a/mkdocs_docs/development/changelog.md
+++ b/mkdocs_docs/development/changelog.md
@@ -3,7 +3,8 @@
The authoritative changelog is maintained in
[`CHANGELOG.md`](https://github.com/cloudQuant/fincore/blob/master/CHANGELOG.md).
-The current release is **0.5.0**, a breaking unified-core release. It removes
-legacy package façades and documents the canonical domain paths required by
-new integrations. See the [0.5.0 release notes](releases/0.5.0.md) for the
-upgrade boundary and installation guidance.
+The current release is **0.5.1**, a quality-maintenance update that preserves
+the 0.5 unified-core API. It makes local, packaging, and source-to-wheel
+contract checks reproducible without changing the public namespace. See the
+[0.5.1 release notes](releases/0.5.1.md) for the verification changes and the
+[0.5.0 release notes](releases/0.5.0.md) for the breaking upgrade boundary.
diff --git a/mkdocs_docs/development/releases/0.5.1.md b/mkdocs_docs/development/releases/0.5.1.md
new file mode 100644
index 00000000..1e035ae2
--- /dev/null
+++ b/mkdocs_docs/development/releases/0.5.1.md
@@ -0,0 +1,35 @@
+# fincore 0.5.1
+
+Released 2026-09-20.
+
+fincore 0.5.1 is a quality-maintenance release. It preserves the canonical,
+breaking 0.5 public API introduced in 0.5.0: no legacy Empyrical, Pyfolio, or
+Alphalens façades, flat root metric functions, compatibility aliases, or
+stateful compatibility registries are restored.
+
+## Quality and release verification
+
+- The MyPy pre-commit hook checks the current package tree instead of removed
+ pre-0.5 directories.
+- Packaging contract tests isolate pytest's temporary staging output, so a
+ local test run cannot accidentally alter the source copy used to construct a
+ wheel or sdist.
+- The static API scanner now validates the explicitly exported root version
+ value without importing optional dependencies. Release validation compares
+ that source root contract with the built wheel.
+
+These are verification and developer-experience changes only. Financial
+calculations, errors, dependencies, and the public namespace are unchanged.
+
+## Install
+
+```bash
+pip install "fincore==0.5.1"
+```
+
+Python 3.11 or newer is required. Install only the capability extras needed by
+your workflow, for example `fincore[visualization]`, `fincore[factor-analysis]`,
+`fincore[report-pdf]`, or `fincore[report-xlsx]`.
+
+If upgrading from a pre-0.5 integration, read the
+[0.5 migration guide](../../getting-started/migration.md) first.
diff --git a/mkdocs_docs/index.md b/mkdocs_docs/index.md
index 3be53013..25ecc0ad 100644
--- a/mkdocs_docs/index.md
+++ b/mkdocs_docs/index.md
@@ -1,8 +1,9 @@
# fincore 0.5
fincore is a unified Python platform for quantitative performance, portfolio,
-factor, attribution, and risk analysis. Version **0.5.0** reorganises
-those capabilities into focused canonical domains.
+factor, attribution, and risk analysis. Version **0.5.0** reorganised those
+capabilities into focused canonical domains; **0.5.1** preserves that public
+contract while improving release-quality verification.
This is a breaking release: upstream-shaped Empyrical, Pyfolio, and Alphalens
facades, root-level metric calls, and compatibility extras are retired. Import
diff --git a/pyproject.toml b/pyproject.toml
index db923c43..7ddd9357 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "fincore"
-version = "0.5.1.dev0"
+version = "0.5.1"
description = "Quantitative performance and risk analytics for Python"
readme = "README.md"
license = "MIT"
diff --git a/scripts/snapshot_public_api.py b/scripts/snapshot_public_api.py
index 7c283c4f..326cd8ad 100644
--- a/scripts/snapshot_public_api.py
+++ b/scripts/snapshot_public_api.py
@@ -551,7 +551,7 @@ def bindings(self, module_path: str) -> dict[str, _Binding]:
if isinstance(node, (ast.Assign, ast.AnnAssign)):
targets = node.targets if isinstance(node, ast.Assign) else [node.target]
for target in targets:
- if isinstance(target, ast.Name) and not target.id.startswith("_"):
+ if isinstance(target, ast.Name):
bindings.setdefault(target.id, _Binding("direct", _ResolvedExport("value", None)))
bindings.update(self._dynamic_bindings(module_path, tree, source))
self._bindings[module_path] = bindings
diff --git a/tests/packaging/test_release_candidate.py b/tests/packaging/test_release_candidate.py
index 5ae37814..ef729b75 100644
--- a/tests/packaging/test_release_candidate.py
+++ b/tests/packaging/test_release_candidate.py
@@ -14,8 +14,9 @@
import pytest
-def test_pyproject_version_is_current_development_release() -> None:
- assert _pyproject_version() == "0.5.1.dev0"
+def test_pyproject_version_is_current_stable_release() -> None:
+ """The project metadata targets the current stable release."""
+ assert _pyproject_version() == "0.5.1"
def test_sha256_is_deterministic(tmp_path: Path) -> None:
diff --git a/tests/packaging/test_release_consistency.py b/tests/packaging/test_release_consistency.py
index 2c4711db..cb64fc4e 100644
--- a/tests/packaging/test_release_consistency.py
+++ b/tests/packaging/test_release_consistency.py
@@ -24,6 +24,7 @@
"dist",
"__pycache__",
".pytest_cache",
+ ".pytest_tmp",
".mypy_cache",
".ruff_cache",
".hypothesis",
diff --git a/tests/packaging/test_wheel_contents.py b/tests/packaging/test_wheel_contents.py
index b5d70e67..f280ebd2 100644
--- a/tests/packaging/test_wheel_contents.py
+++ b/tests/packaging/test_wheel_contents.py
@@ -49,6 +49,7 @@
"dist",
"__pycache__",
".pytest_cache",
+ ".pytest_tmp",
".mypy_cache",
".ruff_cache",
".hypothesis",
@@ -179,6 +180,7 @@ def test_build_source_staging_excludes_generated_outputs(tmp_path: Path) -> None
assert not (source / "build").exists()
assert not (source / "dist").exists()
+ assert not (source / ".pytest_tmp").exists()
# ---------------------------------------------------------------------------
diff --git a/tests/quality/test_0042_r2_planned_api.py b/tests/quality/test_0042_r2_planned_api.py
index 8d685cf9..4eb14e72 100644
--- a/tests/quality/test_0042_r2_planned_api.py
+++ b/tests/quality/test_0042_r2_planned_api.py
@@ -12,6 +12,8 @@
import os
from pathlib import Path
+from scripts.snapshot_public_api import build_static_snapshot
+
REPOSITORY_ROOT = Path(os.environ.get("FINCORE_0042R2_SOURCE_ROOT", Path(__file__).parents[2])).resolve()
FIXTURES = REPOSITORY_ROOT / "tests" / "parity" / "fixtures"
PLANNED_API = FIXTURES / "planned-api-0.5.0.json"
@@ -75,6 +77,19 @@ def test_root_shape_is_namespace_only_without_flat_callables() -> None:
assert root["rule"].strip()
+def test_actual_source_root_matches_planned_root_shape() -> None:
+ planned = _load_planned()
+ actual = build_static_snapshot(source_root=REPOSITORY_ROOT, surfaces=("fincore",))["surfaces"]["fincore"]
+
+ allowed_symbols = set(planned["root_shape"]["allowed_symbols"])
+ assert set(actual["public_symbols"]) == allowed_symbols
+
+ entries = actual["entries"]
+ assert entries["__version__"]["kind"] == "value"
+ namespace_symbols = allowed_symbols - {"__version__", "errors"}
+ assert all(entries[name]["kind"] == "module" for name in namespace_symbols)
+
+
def test_target_namespaces_are_disjoint_from_removed_surfaces() -> None:
planned = _load_planned()
namespaces = planned["target_namespaces"]
diff --git a/tests/quality/test_snapshot_public_api_source.py b/tests/quality/test_snapshot_public_api_source.py
index 4fdd4ba1..b53c420b 100644
--- a/tests/quality/test_snapshot_public_api_source.py
+++ b/tests/quality/test_snapshot_public_api_source.py
@@ -176,6 +176,53 @@ def test_static_snapshot_does_not_import_optional_or_heavy_dependencies(tmp_path
assert '"available"' in result.stdout
+def test_static_snapshot_resolves_explicitly_exported_value_from_call(tmp_path: Path) -> None:
+ """A statically exported private assignment is included without executing it."""
+
+ source_root = tmp_path / "source"
+ _write_package(source_root, '__all__ = ["__version__"]\n__version__ = resolve_version()\n')
+ output = tmp_path / "snapshot.json"
+
+ result = _run_snapshot(
+ "--source-root",
+ str(source_root),
+ "--surface",
+ "fincore",
+ "--output",
+ str(output),
+ cwd=tmp_path,
+ )
+
+ assert result.returncode == 0, result.stderr
+ snapshot = json.loads(output.read_text(encoding="utf-8"))
+ assert snapshot["schema_version"] == 2
+ assert snapshot["surfaces"]["fincore"]["entries"]["__version__"] == {
+ "kind": "value",
+ "public_path": "fincore.__version__",
+ }
+
+
+def test_repository_fincore_surface_is_statically_resolvable(tmp_path: Path) -> None:
+ """The real root namespace can be scanned without importing its dependencies."""
+
+ source_root = _snapshot_script().parent.parent
+ output = tmp_path / "fincore-snapshot.json"
+
+ result = _run_snapshot(
+ "--source-root",
+ str(source_root),
+ "--surface",
+ "fincore",
+ "--output",
+ str(output),
+ cwd=tmp_path,
+ )
+
+ assert result.returncode == 0, result.stderr
+ snapshot = json.loads(output.read_text(encoding="utf-8"))
+ assert snapshot["surfaces"]["fincore"]["entries"]["__version__"]["kind"] == "value"
+
+
def test_selected_empty_public_surface_fails_closed(tmp_path: Path) -> None:
"""An explicitly selected surface cannot disappear behind an empty export list."""