feat: add portfolio risk computation contracts
CI / lite (pull_request) Successful in 10s

This commit is contained in:
ao gong
2026-09-01 13:57:03 +08:00
parent d4ee6f005f
commit 8c5af40dad
6 changed files with 3026 additions and 13 deletions
+6 -2
View File
@@ -1,7 +1,7 @@
{ {
"schema_version": 1, "schema_version": 1,
"module_id": "quant_engine", "module_id": "quant_engine",
"authority": {"scope": "module_metadata", "subject": "quant_engine", "owner": "quant-engine-owner", "source": "MODULE_SPEC.yaml", "revision": 3, "effective_from": "2026-09-01T00:00:00+08:00"}, "authority": {"scope": "module_metadata", "subject": "quant_engine", "owner": "quant-engine-owner", "source": "MODULE_SPEC.yaml", "revision": 4, "effective_from": "2026-09-01T00:00:00+08:00"},
"repository": {"name": "quant_engine", "workspace_id": "researchhub", "type": "research_engine", "maturity": "operational"}, "repository": {"name": "quant_engine", "workspace_id": "researchhub", "type": "research_engine", "maturity": "operational"},
"bounded_context": { "bounded_context": {
"domain": "quantitative-research-engine", "domain": "quantitative-research-engine",
@@ -11,6 +11,7 @@
"Submitting live orders, routing trades, managing brokerage accounts, or claiming transaction execution", "Submitting live orders, routing trades, managing brokerage accounts, or claiming transaction execution",
"Owning market-data source facts, research-result publication, or platform presentation state", "Owning market-data source facts, research-result publication, or platform presentation state",
"Loading provider credentials, brokerage credentials, or production secrets", "Loading provider credentials, brokerage credentials, or production secrets",
"Granting portfolio approval, maker-checker decisions, publication eligibility, paper execution, or live execution authority",
"Changing financial model semantics through module metadata" "Changing financial model semantics through module metadata"
] ]
}, },
@@ -19,6 +20,7 @@
{"id": "execution-simulation", "summary": "Simulate costs, slippage, market constraints, fills, NAV, and PnL without live order routing.", "status": "operational"}, {"id": "execution-simulation", "summary": "Simulate costs, slippage, market constraints, fills, NAV, and PnL without live order routing.", "status": "operational"},
{"id": "portfolio-backtesting", "summary": "Run weight-based backtests and benchmark comparisons.", "status": "operational"}, {"id": "portfolio-backtesting", "summary": "Run weight-based backtests and benchmark comparisons.", "status": "operational"},
{"id": "backtest-evidence-contracts", "summary": "Identify governed offline backtest inputs and close existing research artifact evidence without persistence or decision authority.", "status": "operational"}, {"id": "backtest-evidence-contracts", "summary": "Identify governed offline backtest inputs and close existing research artifact evidence without persistence or decision authority.", "status": "operational"},
{"id": "portfolio-risk-computation-contracts", "summary": "Verify deterministic portfolio-computation receipts and expose S3-bound portfolio decisions and risk assessments without adding algorithms or execution authority.", "status": "operational"},
{"id": "risk-and-performance-analysis", "summary": "Calculate portfolio decomposition, risk contribution, and performance statistics.", "status": "operational"} {"id": "risk-and-performance-analysis", "summary": "Calculate portfolio decomposition, risk contribution, and performance statistics.", "status": "operational"}
], ],
"data": {"owns": [ "data": {"owns": [
@@ -30,7 +32,9 @@
{"contract_id": "researchhub.factor-definition", "version": "1.0.0", "authority": "quant_engine", "path": "src/quant_engine/factor_contracts.py"}, {"contract_id": "researchhub.factor-definition", "version": "1.0.0", "authority": "quant_engine", "path": "src/quant_engine/factor_contracts.py"},
{"contract_id": "researchhub.factor-set-ref", "version": "1.0.0", "authority": "quant_engine", "path": "src/quant_engine/factor_contracts.py"}, {"contract_id": "researchhub.factor-set-ref", "version": "1.0.0", "authority": "quant_engine", "path": "src/quant_engine/factor_contracts.py"},
{"contract_id": "researchhub.backtest-run-ref", "version": "1.0.0", "authority": "quant_engine", "path": "src/quant_engine/governed_pipeline.py"}, {"contract_id": "researchhub.backtest-run-ref", "version": "1.0.0", "authority": "quant_engine", "path": "src/quant_engine/governed_pipeline.py"},
{"contract_id": "researchhub.backtest-evidence-manifest", "version": "1.0.0", "authority": "quant_engine", "path": "src/quant_engine/artifact.py"} {"contract_id": "researchhub.backtest-evidence-manifest", "version": "1.0.0", "authority": "quant_engine", "path": "src/quant_engine/artifact.py"},
{"contract_id": "researchhub.portfolio-decision", "version": "1.0.0", "authority": "quant_engine", "path": "src/quant_engine/portfolio_risk_contracts.py"},
{"contract_id": "researchhub.risk-assessment", "version": "1.0.0", "authority": "quant_engine", "path": "src/quant_engine/portfolio_risk_contracts.py"}
], ],
"consumes": [ "consumes": [
{"contract_id": "researchhub.dataset-snapshot", "version": "1.0.0", "authority": "researchhub.data", "admission": "qualified_immutable_envelope"}, {"contract_id": "researchhub.dataset-snapshot", "version": "1.0.0", "authority": "researchhub.data", "admission": "qualified_immutable_envelope"},
+106
View File
@@ -28,6 +28,7 @@
- `research_pipeline` — 因子日 → 下一真实交易日 → 显式执行价 → 日末估值 → 成本后绩效(防前视编排) - `research_pipeline` — 因子日 → 下一真实交易日 → 显式执行价 → 日末估值 → 成本后绩效(防前视编排)
- `governed_pipeline` — 数据快照 → 因子版本 → 策略版本 → 回测运行 → 目标组合 → 风险决策 → Paper 订单意图;同时拥有输入/配置/重放血缘决定的 `BacktestRunRef` - `governed_pipeline` — 数据快照 → 因子版本 → 策略版本 → 回测运行 → 目标组合 → 风险决策 → Paper 订单意图;同时拥有输入/配置/重放血缘决定的 `BacktestRunRef`
- `artifact` — 版本化、确定性、存储中立的完整 research run 事实表,以及只映射现有表的 `BacktestEvidenceManifest` - `artifact` — 版本化、确定性、存储中立的完整 research run 事实表,以及只映射现有表的 `BacktestEvidenceManifest`
- `portfolio_risk_contracts` — S3 证据闭合的 `PortfolioDecision` / `RiskAssessment` v1;独立复核 freshness、约束与 computation receipt,并复用既有标签安全风险分解
- `attribution` — 基于实际成交后持仓的隔夜 / 日内 / 交易成本逐日收益归因与闭合审计 - `attribution` — 基于实际成交后持仓的隔夜 / 日内 / 交易成本逐日收益归因与闭合审计
- `metrics` — 绝对绩效 + 严格日期对齐的 TE / IR / alpha / beta 基准相对绩效 - `metrics` — 绝对绩效 + 严格日期对齐的 TE / IR / alpha / beta 基准相对绩效
- `factor_library` — 通用方法(turnover / winsorize / IC / OLS / jb_test) - `factor_library` — 通用方法(turnover / winsorize / IC / OLS / jb_test)
@@ -259,6 +260,111 @@ digest 等价,也不会把旧 run 静默升级为新合同。
`LEGACY_EXPLORATORY`;不能隐式提升为 `CONTRACT_QUALIFIED`。所有资格均只描述离线证据闭合, `LEGACY_EXPLORATORY`;不能隐式提升为 `CONTRACT_QUALIFIED`。所有资格均只描述离线证据闭合,
不表示投资有效、组合获批、Paper、生产或实盘就绪。 不表示投资有效、组合获批、Paper、生产或实盘就绪。
## 组合决策与风险评估合同 v1
`quant_engine.portfolio_risk_contracts` 是现有计算 owner 外围的薄合同层。创建
`PortfolioDecision` 必须同时提供完整 `BacktestRunRef`、嵌入同一 RunRef 的
`CONTRACT_QUALIFIED` 非 legacy `BacktestEvidenceManifest`、现有 `PortfolioTarget`、
`FreshnessPolicy`、`ConstraintSetV1` 与 `ComputationReceipt`。适配器会从权威输入独立重算
receipt 的 input/constraint/output digest、敞口、持仓数和 L1 turnover 残差;receipt 自报
成功、fallback 或放宽 tolerance 均不能替代复核。
```python
from quant_engine.portfolio_risk_contracts import (
ComputationReceipt,
ConstraintSetV1,
FreshnessPolicy,
assess_portfolio_risk,
build_portfolio_decision,
compute_portfolio_receipt_digests,
)
freshness = FreshnessPolicy(
max_manifest_age_seconds=3600,
max_covariance_age_days=5,
)
constraints = ConstraintSetV1(
gross_exposure_max=1.0,
single_asset_max=0.10,
position_count_max=20,
turnover_max=0.30,
)
# 生产者先形成公开 canonical digest;decision 构建时仍会独立重算。
expected = compute_portfolio_receipt_digests(
backtest_run_ref=run_ref,
manifest=evidence_manifest,
target=portfolio_target,
objective_name="long_only_allocation",
objective_version="1.0.0",
objective_digest=objective_digest,
model_name="factor_weighting",
model_version="1.0.0",
model_digest=model_digest,
expected_return_digest=expected_return_digest,
covariance_digest=covariance_digest,
scenario_digest=scenario_digest,
constraints=constraints,
freshness_policy=freshness,
prior_weights=prior_weights,
)
receipt = ComputationReceipt(
algorithm="factor_weighting",
algorithm_version="1.0.0",
implementation_digest=implementation_digest,
parameter_digest=parameter_digest,
input_digest=expected["input_digest"],
constraint_digest=expected["constraint_digest"],
output_digest=expected["output_digest"],
status="completed",
solver_required=False,
solver_name=None,
solver_version=None,
solver_config_digest=None,
iterations=None,
objective_value=None,
max_constraint_residual=expected["max_constraint_residual"],
tolerance=1e-12,
computed_at=computed_at,
)
decision = build_portfolio_decision(
backtest_run_ref=run_ref,
manifest=evidence_manifest,
target=portfolio_target,
objective_name="long_only_allocation",
objective_version="1.0.0",
objective_digest=objective_digest,
model_name="factor_weighting",
model_version="1.0.0",
model_digest=model_digest,
expected_return_digest=expected_return_digest,
covariance_digest=covariance_digest,
scenario_digest=scenario_digest,
constraints=constraints,
freshness_policy=freshness,
receipt=receipt,
computed_at=computed_at,
prior_weights=prior_weights,
)
assessment = assess_portfolio_risk(
portfolio_decision=decision,
backtest_run_ref=run_ref,
manifest=evidence_manifest,
covariance=covariance_snapshot,
risk_model_name="euler_volatility",
risk_model_version="1.0.0",
risk_model_digest=risk_model_digest,
)
```
`source_universe_digest` 保留 S3 研究 universe 身份,`portfolio_asset_set_digest` 只描述实际
目标资产标签;二者不会互相冒充成员证明。风险评估在任何数值计算前要求 covariance、target、
RunRef 的 dataset identity 三方一致,并且只调用一次现有 `labeled_component_risk()`。合同中的
`qualified` 仅表示 S4.1 计算证据闭合,不授予 maker-checker、发布、订单、Paper、生产或实盘权限。
## 治理垂直切片 ## 治理垂直切片
`governed_pipeline` 不复制因子、回测、组合或执行算法,只编排现有能力并补充版本与风险契约。 `governed_pipeline` 不复制因子、回测、组合或执行算法,只编排现有能力并补充版本与风险契约。
File diff suppressed because it is too large Load Diff
+129
View File
@@ -0,0 +1,129 @@
{
"portfolio_decision": {
"computed_at": "2026-01-08T03:01:00Z",
"constraint_residuals": {
"gross_exposure_max": 0.0,
"net_exposure_max": 0.0,
"net_exposure_min": 0.0,
"position_count_max": 0.0,
"single_asset_max": 0.0,
"single_asset_min": 0.0,
"turnover_max": 0.0
},
"constraints": {
"gross_exposure_max": 1.0,
"net_exposure_max": 1.0,
"net_exposure_min": 1.0,
"position_count_max": 2,
"schema_version": "1.0.0",
"single_asset_max": 0.7,
"single_asset_min": 0.2,
"turnover_max": 0.2
},
"contract_name": "researchhub.portfolio-decision",
"covariance_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"dataset_snapshot_id": "rhdsv1:sha256:f63a29b4795c63fb7d6b2d3b5544cee9274b633db77c75c63d50a340c0827d57",
"decision_id": "rhportfoliodecisionv1:sha256:0e6e5ce2fa08de8006cc392610695a013327fc80645bfa4d7a908ad3f615bebd",
"effective_at": "2026-01-08T03:00:00Z",
"evidence_digest": "sha256:f3913894d032c389c64eef59058b3cbc694cb9cc14ce9cee2699f0068200b650",
"expected_return_digest": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
"freshness_policy": {
"max_covariance_age_days": 0,
"max_manifest_age_seconds": 3600,
"schema_version": "1.0.0"
},
"gross_exposure": 1.0,
"manifest_document_sha256": "fbf54218f770528978f9ccd35577e1ab00877143ea397a576e071e75f0afbab0",
"manifest_id": "rhbacktestevidencev1:sha256:681c49cbdfb3e221b273e7bc616602ad80a7ab01206970807ad4294b176ebc75",
"model_digest": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
"model_name": "deterministic_weights",
"model_version": "1.0.0",
"net_exposure": 1.0,
"objective_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"objective_name": "long_only_allocation",
"objective_version": "1.0.0",
"output_digest": "sha256:bd3b964c628c8648322d036e57dd6f444ca287017d1578bab3689d07d32b28ce",
"portfolio_asset_set_digest": "sha256:b64e3448a83a5b86466465080361c1a7e1157a27ddccd4b68069cb18caffb74a",
"position_count": 2,
"prior_weights": {
"A": 0.5,
"B": 0.5
},
"receipt": {
"algorithm": "bounded_allocation",
"algorithm_version": "1.0.0",
"computed_at": "2026-01-08T03:01:00Z",
"constraint_digest": "sha256:34df0e5c00f503748ff936f9cd415a8947169181f8ca0917bce97dd606e08e94",
"implementation_digest": "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"input_digest": "sha256:ebd8ff957115e1adfd84eafa5cea49470356194d747b64ee5897858f9dd067b7",
"iterations": null,
"max_constraint_residual": 0.0,
"objective_value": null,
"output_digest": "sha256:bd3b964c628c8648322d036e57dd6f444ca287017d1578bab3689d07d32b28ce",
"parameter_digest": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
"schema_version": "1.0.0",
"solver_config_digest": null,
"solver_name": null,
"solver_required": false,
"solver_version": null,
"status": "completed",
"tolerance": 1e-12
},
"run_id": "rhbacktestrunv1:sha256:5036c771c44a2adade9ea590eee0d8cf824ff8f519fadd8ba424e5d914856386",
"run_ref_document_sha256": "6a798adb3e0568aca84ed3e3a285b92d181ec569462fc003952a8c0d8382ae3a",
"scenario_digest": "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
"schema_version": "1.0.0",
"source_universe_digest": "sha256:5555555555555555555555555555555555555555555555555555555555555555",
"target_id": "portfolio-target:synthetic-v1",
"target_weights": {
"A": 0.6,
"B": 0.4
},
"turnover_l1": 0.19999999999999996
},
"risk_assessment": {
"assessment_id": "rhriskassessmentv1:sha256:dbc38825cffcf6d95bd0216d22dbba4e0d4a1359ca5924a3ee529b99a7d78b6d",
"component_risk": {
"A": 1.4549226783578566,
"B": 1.4549226783578568
},
"contract_name": "researchhub.risk-assessment",
"covariance_as_of_date": "2026-01-08",
"covariance_data_snapshot_id": "rhdsv1:sha256:f63a29b4795c63fb7d6b2d3b5544cee9274b633db77c75c63d50a340c0827d57",
"covariance_input_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"covariance_snapshot_id": "covariance:synthetic-v1",
"dataset_snapshot_id": "rhdsv1:sha256:f63a29b4795c63fb7d6b2d3b5544cee9274b633db77c75c63d50a340c0827d57",
"decision_id": "rhportfoliodecisionv1:sha256:0e6e5ce2fa08de8006cc392610695a013327fc80645bfa4d7a908ad3f615bebd",
"findings": [],
"freshness_policy_digest": "sha256:833f58f4d1056f0450f7369fd4edd70534cc74e9e55cd60f05b2b3d7a2979763",
"group_exposure": {
"equity": 1.4549226783578566,
"fixed_income": 1.4549226783578568
},
"manifest_id": "rhbacktestevidencev1:sha256:681c49cbdfb3e221b273e7bc616602ad80a7ab01206970807ad4294b176ebc75",
"marginal_risk": {
"A": 2.424871130596428,
"B": 3.637306695894642
},
"percentage_risk": {
"A": 0.49999999999999983,
"B": 0.49999999999999994
},
"periods_per_year": 252,
"portfolio_volatility": 2.909845356715714,
"portfolio_volatility_limit": 10.0,
"qualified": true,
"return_frequency": "1d",
"risk_budget": {
"A": 0.8,
"B": 0.8
},
"risk_model_digest": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
"risk_model_name": "euler_volatility",
"risk_model_version": "1.0.0",
"run_id": "rhbacktestrunv1:sha256:5036c771c44a2adade9ea590eee0d8cf824ff8f519fadd8ba424e5d914856386",
"scenario_digest": "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
"schema_version": "1.0.0",
"status": "ready"
}
}
+13 -1
View File
@@ -16,7 +16,7 @@ def test_module_spec_declares_pure_research_engine_boundary() -> None:
prohibited = " ".join(spec["bounded_context"]["prohibited_responsibilities"]).lower() prohibited = " ".join(spec["bounded_context"]["prohibited_responsibilities"]).lower()
for term in ("investment advice", "live order", "credentials", "source facts"): for term in ("investment advice", "live order", "credentials", "source facts"):
assert term in prohibited assert term in prohibited
assert spec["authority"]["revision"] == 3 assert spec["authority"]["revision"] == 4
assert { assert {
(item["contract_id"], item["version"]) (item["contract_id"], item["version"])
for item in spec["contracts"]["provides"] for item in spec["contracts"]["provides"]
@@ -25,12 +25,16 @@ def test_module_spec_declares_pure_research_engine_boundary() -> None:
("researchhub.factor-set-ref", "1.0.0"), ("researchhub.factor-set-ref", "1.0.0"),
("researchhub.backtest-run-ref", "1.0.0"), ("researchhub.backtest-run-ref", "1.0.0"),
("researchhub.backtest-evidence-manifest", "1.0.0"), ("researchhub.backtest-evidence-manifest", "1.0.0"),
("researchhub.portfolio-decision", "1.0.0"),
("researchhub.risk-assessment", "1.0.0"),
} }
expected_paths = { expected_paths = {
"researchhub.factor-definition": "src/quant_engine/factor_contracts.py", "researchhub.factor-definition": "src/quant_engine/factor_contracts.py",
"researchhub.factor-set-ref": "src/quant_engine/factor_contracts.py", "researchhub.factor-set-ref": "src/quant_engine/factor_contracts.py",
"researchhub.backtest-run-ref": "src/quant_engine/governed_pipeline.py", "researchhub.backtest-run-ref": "src/quant_engine/governed_pipeline.py",
"researchhub.backtest-evidence-manifest": "src/quant_engine/artifact.py", "researchhub.backtest-evidence-manifest": "src/quant_engine/artifact.py",
"researchhub.portfolio-decision": "src/quant_engine/portfolio_risk_contracts.py",
"researchhub.risk-assessment": "src/quant_engine/portfolio_risk_contracts.py",
} }
assert all(item["authority"] == "quant_engine" for item in spec["contracts"]["provides"]) assert all(item["authority"] == "quant_engine" for item in spec["contracts"]["provides"])
assert { assert {
@@ -48,6 +52,14 @@ def test_module_spec_declares_pure_research_engine_boundary() -> None:
for item in spec["contracts"]["consumes"] for item in spec["contracts"]["consumes"]
) )
assert spec["dependencies"] == [] assert spec["dependencies"] == []
capabilities = {item["id"]: item for item in spec["capabilities"]}
portfolio_contract = capabilities["portfolio-risk-computation-contracts"]
assert portfolio_contract["status"] == "operational"
summary = portfolio_contract["summary"].lower()
for term in ("receipt", "portfolio decisions", "risk assessments", "without"):
assert term in summary
for term in ("approval", "maker-checker", "publication", "paper", "live"):
assert term in prohibited
assert all( assert all(
command["required"] and not command["network"] command["required"] and not command["network"]
for command in spec["verification"]["commands"] for command in spec["verification"]["commands"]
+979 -1
View File
@@ -1,17 +1,428 @@
"""S4.1 portfolio-decision and risk-assessment contract conformance."""
from __future__ import annotations from __future__ import annotations
import ast
import hashlib
import json
from datetime import UTC, date, datetime
from pathlib import Path
from typing import Any
def test_s41_public_contract_surface_exists() -> None: import pandas as pd
import pytest
import quant_engine.portfolio_risk_contracts as contracts_module
from quant_engine.artifact import (
BacktestEvidenceManifest,
EvidenceQualification,
ResearchRunArtifact,
build_backtest_evidence_manifest,
build_research_run_artifact,
)
from quant_engine.execution import ExecutionConfig
from quant_engine.factor_contracts import (
ActorIdentity,
Causation,
DataFoundationEnvelope,
DatasetSnapshotEnvelope,
FactorInput,
FactorSetRef,
InputBinding,
OutputArtifactRef,
OutputCoverage,
OutputQuality,
OutputQualityCheck,
ProducerIdentity,
ViewAvailability,
canonical_json_bytes,
factor_definition_from_alpha158,
factor_input_schema_digest,
)
from quant_engine.governed_pipeline import (
BacktestRun,
BacktestRunRef,
PortfolioTarget,
RiskDecision,
RiskDecisionStatus,
RiskPolicy,
create_paper_order_intent,
evaluate_portfolio_risk,
)
from quant_engine.portfolio_risk_contracts import ( from quant_engine.portfolio_risk_contracts import (
ComputationReceipt, ComputationReceipt,
ConstraintSetV1, ConstraintSetV1,
FreshnessPolicy, FreshnessPolicy,
PortfolioDecision, PortfolioDecision,
PortfolioRiskContractError,
PortfolioRiskContractErrorCode,
ReceiptStatus,
RiskAssessment, RiskAssessment,
RiskAssessmentStatus,
RiskFindingCode,
assess_portfolio_risk, assess_portfolio_risk,
build_portfolio_decision, build_portfolio_decision,
compute_portfolio_receipt_digests,
)
from quant_engine.research_pipeline import FactorBacktestResult, run_factor_backtest_research
from quant_engine.risk import ComponentRiskResult, CovarianceSnapshot, labeled_component_risk
ROOT = Path(__file__).resolve().parents[1]
FACTOR_FIXTURE = ROOT / "tests" / "fixtures" / "factor-contracts-v1.golden.json"
GOLDEN_FIXTURE = ROOT / "tests" / "fixtures" / "portfolio-risk-computation-v1.golden.json"
VIEW_REF_ID = "rhviewrefv1:sha256:bf776bcd26d940fafde1d650776a5505fb3fe8b5b068c351622bf2c42385629c"
VIEW_SCHEMA_DIGEST = "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
CALENDAR_REVISION_ID = "rhcalv1:sha256:1f4ca22557063389badd669cf774bb35234066e847646682dccfe411e252a078"
ACTION_REVISION_ID = "rhcav1:sha256:0f947df29f152bfa2c6ab0da7a0d464670c7ad2526cd10f2a7939b42fee5c275"
PARAMETERS = {"lag_sessions": 1, "top_k": 1}
DECISION_FIELDS: dict[str, str] = {
"objective_name": "long_only_allocation",
"objective_version": "1.0.0",
"objective_digest": "sha256:" + "b" * 64,
"model_name": "deterministic_weights",
"model_version": "1.0.0",
"model_digest": "sha256:" + "c" * 64,
"expected_return_digest": "sha256:" + "d" * 64,
"covariance_digest": "sha256:" + "a" * 64,
"scenario_digest": "sha256:" + "e" * 64,
}
def _sha256(value: bytes) -> str:
return f"sha256:{hashlib.sha256(value).hexdigest()}"
def _accepted_authorities() -> tuple[
DatasetSnapshotEnvelope,
DataFoundationEnvelope,
FactorSetRef,
]:
fixture = json.loads(FACTOR_FIXTURE.read_text(encoding="utf-8"))
snapshot = DatasetSnapshotEnvelope.from_dict(fixture["dataset_snapshot"])
foundation = DataFoundationEnvelope.from_dict(fixture["data_foundation"])
factor_input = FactorInput("market", VIEW_SCHEMA_DIGEST, ("close", "volume"))
definition = factor_definition_from_alpha158(
"alpha_005",
version="1.0.0",
parameters={},
inputs=(factor_input,),
implementation_digest="sha256:" + "1" * 64,
input_schema_digest=factor_input_schema_digest((factor_input,)),
valid_from="2026-01-01T00:00:00.000000Z",
valid_until="2027-01-01T00:00:00Z",
warmup_sessions=10,
lag_sessions=1,
producer=ProducerIdentity("quant_engine", "1.0.0"),
code_revision="c" * 40,
)
output_schema_bytes = canonical_json_bytes(fixture["output_schema"])
output_content_bytes = canonical_json_bytes(fixture["output_content"])
artifact_ref = OutputArtifactRef.create(
schema_digest=_sha256(output_schema_bytes),
content_digest=_sha256(output_content_bytes),
)
factor_set = FactorSetRef.create(
definitions=(definition,),
dataset_snapshot=snapshot,
foundation=foundation,
selected_view_ref_ids=(VIEW_REF_ID,),
input_bindings=(
InputBinding(definition.definition_id, "market", VIEW_REF_ID, VIEW_SCHEMA_DIGEST),
),
view_availability=(
ViewAvailability(VIEW_REF_ID, "2026-01-02T23:50:00Z", "sha256:" + "2" * 64),
),
output_quality=OutputQuality(
"passed",
(OutputQualityCheck("finite_values", "passed", "sha256:" + "3" * 64),),
),
output_coverage=OutputCoverage(
"complete", 1, 1, "row", "alpha_005.cn_a", "sha256:" + "4" * 64
),
output_schema_bytes=output_schema_bytes,
output_content_bytes=output_content_bytes,
output_artifact_ref=artifact_ref,
availability_mode="as_available",
evaluation_at="2026-01-03T11:00:00Z",
computed_at="2026-01-03T10:15:00Z",
artifact_available_at="2026-01-03T10:20:00Z",
producer=ProducerIdentity("quant_engine", "1.0.0"),
code_revision="c" * 40,
actor=ActorIdentity("service", "factor_worker_v1"),
correlation_id="research_run_001",
causation=Causation("foundation", foundation.foundation_id),
evidence_scope="synthetic_fixture",
decision_eligible=False,
)
return snapshot, foundation, factor_set
def _config_digest() -> str:
encoded = json.dumps(
PARAMETERS,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode("utf-8")
return _sha256(encoded)
def _run_ref(*, universe_character: str = "5") -> BacktestRunRef:
snapshot, foundation, factor_set = _accepted_authorities()
return BacktestRunRef.create(
dataset_snapshot=snapshot,
foundation=foundation,
factor_set=factor_set,
universe_digest="sha256:" + universe_character * 64,
trading_calendar_revision_ids=(CALENDAR_REVISION_ID,),
corporate_action_revision_ids=(ACTION_REVISION_ID,),
strategy_id="alpha-top1",
strategy_version="1.0.0",
strategy_digest="sha256:" + "6" * 64,
execution_model_version="1.0.0",
execution_model_digest="sha256:" + "7" * 64,
cost_model_version="1.0.0",
cost_model_digest="sha256:" + "8" * 64,
random_seed=7,
code_revision="d" * 40,
environment_lock_digest="sha256:" + "9" * 64,
configuration_digest=_config_digest(),
evaluation_at="2026-01-08T01:00:00Z",
computed_at="2026-01-08T02:00:00Z",
) )
def _backtest_result() -> FactorBacktestResult:
dates = pd.date_range("2026-01-05", periods=4, freq="B")
scores = pd.DataFrame({"A": [2.0, 0.0], "B": [1.0, 3.0]}, index=dates[:2])
opens = pd.DataFrame(
{"A": [10.0, 10.0, 15.0, 15.0], "B": [20.0, 20.0, 20.0, 21.0]},
index=dates,
)
closes = pd.DataFrame(
{"A": [10.0, 12.0, 15.0, 15.0], "B": [20.0, 20.0, 18.0, 21.0]},
index=dates,
)
return run_factor_backtest_research(
scores,
opens,
closes,
top_k=1,
execution_price_field="open",
valuation_price_field="close",
initial_cash=1_000.0,
config=ExecutionConfig(
commission_bps=0,
stamp_tax_bps=0,
slippage_bps=0,
min_trade_amount=0,
),
)
def _artifact(run_ref: BacktestRunRef) -> ResearchRunArtifact:
result = _backtest_result()
benchmark = pd.Series(
[0.0, 0.01, -0.01, 0.02],
index=result.returns.index,
name="benchmark_return",
)
return build_research_run_artifact(
result,
run_id=run_ref.run_id,
strategy_id=run_ref.strategy_id,
strategy_name="Alpha Top 1",
strategy_version=run_ref.strategy_version,
engine_version="1.2.0",
code_revision=run_ref.code_revision,
data_snapshot_id=run_ref.dataset_snapshot_id,
calendar="CN-A",
timezone="Asia/Shanghai",
started_at="2026-01-08T10:00:00+08:00",
finished_at="2026-01-08T10:01:00+08:00",
parameters=PARAMETERS,
benchmark_id="000300.SH",
benchmark_returns=benchmark,
)
def _covariance(
context: dict[str, Any],
*,
matrix: pd.DataFrame | None = None,
data_snapshot_id: str | None = None,
as_of_date: date = date(2026, 1, 8),
input_sha256: str = "a" * 64,
) -> CovarianceSnapshot:
return CovarianceSnapshot(
snapshot_id="covariance:synthetic-v1",
as_of_date=as_of_date,
covariance=(
pd.DataFrame([[0.04, 0.01], [0.01, 0.09]], index=["A", "B"], columns=["A", "B"])
if matrix is None
else matrix
),
return_frequency="1d",
periods_per_year=252,
method="provided",
window_start_date=date(2026, 1, 1),
window_end_date=as_of_date,
observations=6,
lookback_sessions=6,
missing_policy="complete_case",
data_snapshot_id=(
context["run_ref"].dataset_snapshot_id
if data_snapshot_id is None
else data_snapshot_id
),
input_sha256=input_sha256,
)
def _context(
*,
universe_character: str = "5",
artifact_available_at: str = "2026-01-08T02:01:00Z",
) -> dict[str, Any]:
run_ref = _run_ref(universe_character=universe_character)
artifact = _artifact(run_ref)
manifest = build_backtest_evidence_manifest(
run_ref,
artifact,
artifact_available_at=artifact_available_at,
)
target = PortfolioTarget(
target_id="portfolio-target:synthetic-v1",
backtest_run_id=run_ref.run_id,
dataset_snapshot_id=run_ref.dataset_snapshot_id,
weights={"B": 0.4, "A": 0.6},
created_at=datetime(2026, 1, 8, 3, 0, tzinfo=UTC),
)
result: dict[str, Any] = {
"run_ref": run_ref,
"artifact": artifact,
"manifest": manifest,
"target": target,
"constraints": ConstraintSetV1(
gross_exposure_max=1.0,
net_exposure_min=1.0,
net_exposure_max=1.0,
single_asset_min=0.2,
single_asset_max=0.7,
position_count_max=2,
turnover_max=0.2,
),
"freshness_policy": FreshnessPolicy(
max_manifest_age_seconds=3_600,
max_covariance_age_days=0,
),
"prior_weights": {"A": 0.5, "B": 0.5},
"computed_at": datetime(2026, 1, 8, 3, 1, tzinfo=UTC),
}
result["covariance"] = _covariance(result)
return result
@pytest.fixture(scope="module")
def context() -> dict[str, Any]:
return _context()
def _decision_values(context: dict[str, Any], **overrides: Any) -> dict[str, Any]:
values: dict[str, Any] = {
"backtest_run_ref": context["run_ref"],
"manifest": context["manifest"],
"target": context["target"],
**DECISION_FIELDS,
"constraints": context["constraints"],
"freshness_policy": context["freshness_policy"],
"computed_at": context["computed_at"],
"prior_weights": context["prior_weights"],
}
values.update(overrides)
return values
def _receipt(
values: dict[str, Any],
*,
solver_required: bool = False,
status: ReceiptStatus = ReceiptStatus.COMPLETED,
overrides: dict[str, Any] | None = None,
) -> ComputationReceipt:
digest_arguments = {
key: value
for key, value in values.items()
if key not in {"computed_at", "receipt"}
}
digests = compute_portfolio_receipt_digests(**digest_arguments)
receipt_values: dict[str, Any] = {
"algorithm": "bounded_allocation",
"algorithm_version": "1.0.0",
"implementation_digest": "sha256:" + "f" * 64,
"parameter_digest": "sha256:" + "0" * 64,
"input_digest": digests["input_digest"],
"constraint_digest": digests["constraint_digest"],
"output_digest": digests["output_digest"],
"status": status,
"solver_required": solver_required,
"solver_name": "slsqp" if solver_required else None,
"solver_version": "1.0.0" if solver_required else None,
"solver_config_digest": "sha256:" + "1" * 64 if solver_required else None,
"iterations": 4 if solver_required else None,
"objective_value": 0.25 if solver_required else None,
"max_constraint_residual": digests["max_constraint_residual"],
"tolerance": 1.0 if solver_required else 1e-12,
"computed_at": values["computed_at"],
}
if overrides:
receipt_values.update(overrides)
return ComputationReceipt(**receipt_values)
def _decision(context: dict[str, Any], **overrides: Any) -> PortfolioDecision:
values = _decision_values(context, **overrides)
receipt = values.pop("receipt", None)
if receipt is None:
receipt = _receipt(values)
return build_portfolio_decision(**values, receipt=receipt)
def _assessment(
context: dict[str, Any],
*,
decision: PortfolioDecision | None = None,
covariance: CovarianceSnapshot | None = None,
**overrides: Any,
) -> RiskAssessment:
values: dict[str, Any] = {
"portfolio_decision": _decision(context) if decision is None else decision,
"backtest_run_ref": context["run_ref"],
"manifest": context["manifest"],
"covariance": context["covariance"] if covariance is None else covariance,
"risk_model_name": "euler_volatility",
"risk_model_version": "1.0.0",
"risk_model_digest": "sha256:" + "2" * 64,
"risk_budget": {"A": 0.8, "B": 0.8},
"portfolio_volatility_limit": 10.0,
"groups": {"A": "equity", "B": "fixed_income"},
}
values.update(overrides)
return assess_portfolio_risk(**values)
def _assert_error(
error: pytest.ExceptionInfo[PortfolioRiskContractError],
code: PortfolioRiskContractErrorCode,
path: str,
) -> None:
assert error.value.code is code
assert error.value.path == path
def test_s41_public_contract_surface_exists() -> None:
assert all( assert all(
symbol is not None symbol is not None
for symbol in ( for symbol in (
@@ -24,3 +435,570 @@ def test_s41_public_contract_surface_exists() -> None:
build_portfolio_decision, build_portfolio_decision,
) )
) )
def test_positive_decision_and_assessment_match_canonical_golden(
context: dict[str, Any],
) -> None:
decision = _decision(context)
assessment = _assessment(context, decision=decision)
assert decision.run_ref_document_sha256 == hashlib.sha256(
context["run_ref"].to_json().encode("utf-8")
).hexdigest()
assert decision.manifest_document_sha256 == hashlib.sha256(
context["manifest"].to_json().encode("utf-8")
).hexdigest()
assert decision.source_universe_digest == context["run_ref"].universe_digest
assert decision.source_universe_digest != decision.portfolio_asset_set_digest
assert decision.gross_exposure == decision.net_exposure == 1.0
assert decision.turnover_l1 == pytest.approx(0.2)
assert assessment.status is RiskAssessmentStatus.READY
assert assessment.qualified is True
assert assessment.findings == ()
assert sum(assessment.component_risk.values()) == pytest.approx(
assessment.portfolio_volatility
)
assert sum(assessment.percentage_risk.values()) == pytest.approx(1.0)
actual = {
"portfolio_decision": decision.to_dict(),
"risk_assessment": assessment.to_dict(),
}
expected = json.loads(GOLDEN_FIXTURE.read_text(encoding="utf-8"))
assert actual == expected
assert decision.to_json() == json.dumps(
decision.to_dict(), ensure_ascii=False, sort_keys=True, separators=(",", ":")
)
def test_container_order_is_identity_neutral(context: dict[str, Any]) -> None:
target = PortfolioTarget(
target_id=context["target"].target_id,
backtest_run_id=context["target"].backtest_run_id,
dataset_snapshot_id=context["target"].dataset_snapshot_id,
weights={"A": 0.6, "B": 0.4},
created_at=context["target"].created_at,
)
reordered = _decision(context, target=target, prior_weights={"B": 0.5, "A": 0.5})
assert reordered.decision_id == _decision(context).decision_id
def test_run_manifest_target_and_model_mutations_change_identity(
context: dict[str, Any],
) -> None:
baseline = _decision(context)
other_run_context = _context(universe_character="4")
other_run = _decision(other_run_context)
later_manifest_context = _context(artifact_available_at="2026-01-08T02:02:00Z")
later_manifest = _decision(later_manifest_context)
changed_target = PortfolioTarget(
target_id="portfolio-target:synthetic-v2",
backtest_run_id=context["run_ref"].run_id,
dataset_snapshot_id=context["run_ref"].dataset_snapshot_id,
weights={"A": 0.5, "B": 0.5},
created_at=context["target"].created_at,
)
changed_weights = _decision(context, target=changed_target)
changed_assets_target = PortfolioTarget(
target_id="portfolio-target:synthetic-v3",
backtest_run_id=context["run_ref"].run_id,
dataset_snapshot_id=context["run_ref"].dataset_snapshot_id,
weights={"A": 0.6, "C": 0.4},
created_at=context["target"].created_at,
)
changed_assets = _decision(
context,
target=changed_assets_target,
prior_weights={"A": 0.5, "C": 0.5},
)
changed_model = _decision(
context,
model_version="1.0.1",
model_digest="sha256:" + "3" * 64,
)
assert other_run.run_ref_document_sha256 != baseline.run_ref_document_sha256
assert other_run.source_universe_digest != baseline.source_universe_digest
assert later_manifest.manifest_document_sha256 != baseline.manifest_document_sha256
assert changed_weights.portfolio_asset_set_digest == baseline.portfolio_asset_set_digest
assert changed_weights.output_digest != baseline.output_digest
assert changed_assets.portfolio_asset_set_digest != baseline.portfolio_asset_set_digest
assert len(
{
baseline.decision_id,
other_run.decision_id,
later_manifest.decision_id,
changed_weights.decision_id,
changed_assets.decision_id,
changed_model.decision_id,
}
) == 6
@pytest.mark.parametrize(
("payload", "path"),
[
({"schema_version": "1.0.0", "max_covariance_age_days": 1}, "$.max_manifest_age_seconds"),
(
{
"schema_version": "1.0.0",
"max_manifest_age_seconds": 1,
"max_covariance_age_days": 1,
"extra": 1,
},
"$.extra",
),
(
{
"schema_version": "1.0.0",
"max_manifest_age_seconds": -1,
"max_covariance_age_days": 1,
},
"$.max_manifest_age_seconds",
),
(
{
"schema_version": "1.0.0",
"max_manifest_age_seconds": True,
"max_covariance_age_days": 1,
},
"$.max_manifest_age_seconds",
),
(
{
"schema_version": "1.0.0",
"max_manifest_age_seconds": 1.0,
"max_covariance_age_days": 1,
},
"$.max_manifest_age_seconds",
),
(
{
"schema_version": "1.0.0",
"max_manifest_age_seconds": 2**53,
"max_covariance_age_days": 1,
},
"$.max_manifest_age_seconds",
),
],
)
def test_freshness_policy_has_strict_schema_and_safe_thresholds(
payload: dict[str, object], path: str
) -> None:
with pytest.raises(PortfolioRiskContractError) as error:
FreshnessPolicy.from_dict(payload)
_assert_error(error, PortfolioRiskContractErrorCode.INVALID_FRESHNESS_POLICY, path)
def test_manifest_freshness_boundary_and_timezone_normalization(
context: dict[str, Any],
) -> None:
equal_boundary = _decision(
context,
computed_at="2026-01-08T11:01:00+08:00",
)
assert equal_boundary.computed_at == "2026-01-08T03:01:00Z"
stale_values = _decision_values(
context,
computed_at="2026-01-08T03:01:01Z",
)
stale_receipt = _receipt(stale_values)
with pytest.raises(PortfolioRiskContractError) as error:
build_portfolio_decision(**stale_values, receipt=stale_receipt)
_assert_error(
error,
PortfolioRiskContractErrorCode.MANIFEST_STALE,
"$.manifest.artifact_available_at",
)
naive_values = _decision_values(
context,
computed_at=datetime(2026, 1, 8, 3, 1),
)
with pytest.raises(PortfolioRiskContractError) as error:
_receipt(naive_values)
_assert_error(
error,
PortfolioRiskContractErrorCode.TIME_ORDER_VIOLATION,
"$.computed_at",
)
def test_constraint_set_rejects_unknown_nonfinite_and_unsafe_fields() -> None:
payload = ConstraintSetV1().to_dict()
payload["custom_constraint"] = 1.0
with pytest.raises(PortfolioRiskContractError) as error:
ConstraintSetV1.from_dict(payload)
_assert_error(error, PortfolioRiskContractErrorCode.UNKNOWN_FIELD, "$.custom_constraint")
with pytest.raises(PortfolioRiskContractError) as error:
ConstraintSetV1(gross_exposure_max=float("nan"))
_assert_error(error, PortfolioRiskContractErrorCode.INVALID_VALUE, "$.gross_exposure_max")
with pytest.raises(PortfolioRiskContractError) as error:
ConstraintSetV1(position_count_max=True)
_assert_error(error, PortfolioRiskContractErrorCode.TYPE_ERROR, "$.position_count_max")
def test_s3_qualification_embedded_run_and_dataset_mismatch_fail_closed(
context: dict[str, Any],
) -> None:
exploratory = build_backtest_evidence_manifest(
context["run_ref"],
context["artifact"],
artifact_available_at="2026-01-08T02:01:00Z",
qualification=EvidenceQualification.EXPLORATORY,
)
values = _decision_values(context, manifest=exploratory)
with pytest.raises(PortfolioRiskContractError) as error:
build_portfolio_decision(**values, receipt=_receipt(values))
_assert_error(
error,
PortfolioRiskContractErrorCode.QUALIFICATION_REJECTED,
"$.manifest.qualification",
)
other_run = _run_ref(universe_character="4")
manifest = context["manifest"]
mismatched = object.__new__(BacktestEvidenceManifest)
for field_name in manifest.__dataclass_fields__:
object.__setattr__(
mismatched,
field_name,
other_run if field_name == "backtest_run_ref" else getattr(manifest, field_name),
)
mismatch_values = _decision_values(context, manifest=mismatched)
with pytest.raises(PortfolioRiskContractError) as error:
build_portfolio_decision(**mismatch_values, receipt=_receipt(mismatch_values))
_assert_error(
error,
PortfolioRiskContractErrorCode.IDENTITY_MISMATCH,
"$.manifest.run_reference.value",
)
target = PortfolioTarget(
target_id="portfolio-target:dataset-drift",
backtest_run_id=context["run_ref"].run_id,
dataset_snapshot_id="dataset:other",
weights={"A": 0.6, "B": 0.4},
created_at=context["target"].created_at,
)
drift_values = _decision_values(context, target=target)
with pytest.raises(PortfolioRiskContractError) as error:
build_portfolio_decision(**drift_values, receipt=_receipt(drift_values))
_assert_error(
error,
PortfolioRiskContractErrorCode.DATASET_IDENTITY_MISMATCH,
"$.portfolio_target.dataset_snapshot_id",
)
def test_explicit_legacy_manifest_cannot_form_a_decision(context: dict[str, Any]) -> None:
manifest = context["manifest"]
legacy_run = BacktestRun(
run_id=context["run_ref"].run_id,
dataset_snapshot_id=context["run_ref"].dataset_snapshot_id,
factor_version_id="legacy-factor@1.0.0",
strategy_version_id="alpha-top1@1.0.0",
code_revision=context["run_ref"].code_revision,
config_hash=context["run_ref"].configuration_digest.removeprefix("sha256:"),
created_at=datetime(2026, 1, 8, 2, tzinfo=UTC),
)
legacy = object.__new__(BacktestEvidenceManifest)
overrides = {
"qualification": EvidenceQualification.LEGACY_EXPLORATORY,
"backtest_run_ref": None,
"legacy_backtest_run": legacy_run,
}
for field_name in manifest.__dataclass_fields__:
object.__setattr__(
legacy,
field_name,
overrides.get(field_name, getattr(manifest, field_name)),
)
values = _decision_values(context, manifest=legacy)
with pytest.raises(PortfolioRiskContractError) as error:
build_portfolio_decision(**values, receipt=_receipt(values))
_assert_error(
error,
PortfolioRiskContractErrorCode.QUALIFICATION_REJECTED,
"$.manifest.qualification",
)
def test_receipt_field_matrix_and_digest_recomputation(context: dict[str, Any]) -> None:
values = _decision_values(context)
with pytest.raises(PortfolioRiskContractError) as error:
_receipt(values, overrides={"algorithm_version": "v1"})
_assert_error(
error,
PortfolioRiskContractErrorCode.INVALID_FORMAT,
"$.algorithm_version",
)
with pytest.raises(PortfolioRiskContractError) as error:
_receipt(values, overrides={"solver_name": "unexpected"})
_assert_error(
error,
PortfolioRiskContractErrorCode.INVALID_VALUE,
"$.solver_name",
)
bad_receipt = _receipt(values, overrides={"input_digest": "sha256:" + "9" * 64})
with pytest.raises(PortfolioRiskContractError) as error:
build_portfolio_decision(**values, receipt=bad_receipt)
_assert_error(
error,
PortfolioRiskContractErrorCode.RECEIPT_MISMATCH,
"$.receipt.input_digest",
)
for status in (ReceiptStatus.FAILED, ReceiptStatus.FALLBACK):
rejected = _receipt(
values,
solver_required=True,
status=status,
)
with pytest.raises(PortfolioRiskContractError) as error:
build_portfolio_decision(**values, receipt=rejected)
_assert_error(
error,
PortfolioRiskContractErrorCode.QUALIFICATION_REJECTED,
"$.receipt.status",
)
def test_constraints_are_independently_recomputed(context: dict[str, Any]) -> None:
target = PortfolioTarget(
target_id="portfolio-target:violating",
backtest_run_id=context["run_ref"].run_id,
dataset_snapshot_id=context["run_ref"].dataset_snapshot_id,
weights={"A": 0.8, "B": 0.2},
created_at=context["target"].created_at,
)
values = _decision_values(context, target=target)
receipt = _receipt(values, solver_required=True, status=ReceiptStatus.CONVERGED)
assert receipt.max_constraint_residual == pytest.approx(0.4)
with pytest.raises(PortfolioRiskContractError) as error:
build_portfolio_decision(**values, receipt=receipt)
_assert_error(
error,
PortfolioRiskContractErrorCode.CONSTRAINT_VIOLATION,
"$.constraints",
)
def test_covariance_target_and_run_dataset_identity_is_three_way_closed(
context: dict[str, Any],
) -> None:
covariance = _covariance(context, data_snapshot_id="dataset:other")
with pytest.raises(PortfolioRiskContractError) as error:
_assessment(context, covariance=covariance)
_assert_error(
error,
PortfolioRiskContractErrorCode.DATASET_IDENTITY_MISMATCH,
"$.covariance.data_snapshot_id",
)
wrong_digest = _covariance(context, input_sha256="b" * 64)
with pytest.raises(PortfolioRiskContractError) as error:
_assessment(context, covariance=wrong_digest)
_assert_error(
error,
PortfolioRiskContractErrorCode.IDENTITY_MISMATCH,
"$.covariance.input_sha256",
)
def test_covariance_pit_and_freshness_are_enforced(context: dict[str, Any]) -> None:
future = _covariance(context, as_of_date=date(2026, 1, 9))
with pytest.raises(PortfolioRiskContractError) as error:
_assessment(context, covariance=future)
_assert_error(
error,
PortfolioRiskContractErrorCode.TIME_ORDER_VIOLATION,
"$.covariance.as_of_date",
)
stale = _covariance(context, as_of_date=date(2026, 1, 7))
with pytest.raises(PortfolioRiskContractError) as error:
_assessment(context, covariance=stale)
_assert_error(
error,
PortfolioRiskContractErrorCode.COVARIANCE_STALE,
"$.covariance.as_of_date",
)
def test_risk_decomposition_is_delegated_exactly_once(
context: dict[str, Any], monkeypatch: pytest.MonkeyPatch
) -> None:
calls = 0
def wrapped(weights: pd.Series[Any], covariance: pd.DataFrame) -> ComponentRiskResult:
nonlocal calls
calls += 1
return labeled_component_risk(weights, covariance)
monkeypatch.setattr(contracts_module, "labeled_component_risk", wrapped)
assessment = _assessment(context)
assert calls == 1
assert assessment.qualified is True
@pytest.mark.parametrize(
("matrix", "finding"),
[
(
pd.DataFrame([[1.0, 2.0], [2.0, 1.0]], index=["A", "B"], columns=["A", "B"]),
RiskFindingCode.COVARIANCE_NOT_PSD,
),
(
pd.DataFrame([[0.0, 0.0], [0.0, 0.0]], index=["A", "B"], columns=["A", "B"]),
RiskFindingCode.PORTFOLIO_VARIANCE_NON_POSITIVE,
),
],
)
def test_named_numerical_failures_return_stable_unavailable_findings(
context: dict[str, Any], matrix: pd.DataFrame, finding: RiskFindingCode
) -> None:
result = _assessment(context, covariance=_covariance(context, matrix=matrix))
assert result.status is RiskAssessmentStatus.UNAVAILABLE
assert result.qualified is False
assert result.findings == (finding,)
assert result.portfolio_volatility is None
assert result.marginal_risk == result.component_risk == result.percentage_risk == {}
def test_unknown_numerical_error_is_sanitized(
context: dict[str, Any], monkeypatch: pytest.MonkeyPatch
) -> None:
def fail_unknown(weights: pd.Series[Any], covariance: pd.DataFrame) -> ComponentRiskResult:
raise ValueError("sensitive lower-level details")
monkeypatch.setattr(contracts_module, "labeled_component_risk", fail_unknown)
with pytest.raises(PortfolioRiskContractError) as error:
_assessment(context)
_assert_error(
error,
PortfolioRiskContractErrorCode.COMPUTATION_FAILURE,
"$.covariance",
)
assert "sensitive" not in str(error.value)
def test_non_closed_risk_and_budget_breach_have_distinct_semantics(
context: dict[str, Any], monkeypatch: pytest.MonkeyPatch
) -> None:
def non_closed(weights: pd.Series[Any], covariance: pd.DataFrame) -> ComponentRiskResult:
return ComponentRiskResult(
portfolio_volatility=1.0,
marginal=pd.Series({"A": 0.1, "B": 0.1}),
component=pd.Series({"A": 0.1, "B": 0.1}),
percentage=pd.Series({"A": 0.5, "B": 0.4}),
)
monkeypatch.setattr(contracts_module, "labeled_component_risk", non_closed)
unavailable = _assessment(context)
assert unavailable.status is RiskAssessmentStatus.UNAVAILABLE
assert unavailable.findings == (RiskFindingCode.RISK_CONTRIBUTION_NOT_CLOSED,)
monkeypatch.setattr(contracts_module, "labeled_component_risk", labeled_component_risk)
breached = _assessment(context, risk_budget={"A": 0.0})
assert breached.status is RiskAssessmentStatus.READY
assert breached.qualified is False
assert breached.findings == (RiskFindingCode.RISK_BUDGET_BREACH,)
assert breached.component_risk
def test_existing_target_risk_decision_artifact_and_paper_slice_remain_legacy_only(
context: dict[str, Any],
) -> None:
target = context["target"]
legacy_decision = evaluate_portfolio_risk(
target,
RiskPolicy(
policy_id="legacy-paper-policy",
max_gross_exposure=1.0,
max_single_asset_weight=0.7,
max_positions=2,
),
created_at=target.created_at,
)
intent = create_paper_order_intent(target, legacy_decision)
assert isinstance(legacy_decision, RiskDecision)
assert legacy_decision.status is RiskDecisionStatus.APPROVED
assert intent.environment == "paper"
assert isinstance(context["artifact"], ResearchRunArtifact)
assert not isinstance(target, PortfolioDecision)
assert not isinstance(legacy_decision, RiskAssessment)
def test_architecture_dependency_no_copy_and_authority_boundaries() -> None:
new_path = ROOT / "src" / "quant_engine" / "portfolio_risk_contracts.py"
source = new_path.read_text(encoding="utf-8")
tree = ast.parse(source)
called = {
node.func.id
for node in ast.walk(tree)
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name)
}
assert not called & {
"evaluate_portfolio_risk",
"create_paper_order_intent",
"run_governed_factor_slice",
}
imports = {
alias.name
for node in ast.walk(tree)
if isinstance(node, ast.Import)
for alias in node.names
} | {
node.module or ""
for node in ast.walk(tree)
if isinstance(node, ast.ImportFrom)
}
assert not any(name.startswith(("research_results", "research_platform")) for name in imports)
for candidate in ("riskfolio", "pyp", "skfolio", "cvxportfolio"):
assert candidate not in source.lower()
for owner_path in (
ROOT / "src" / "quant_engine" / "governed_pipeline.py",
ROOT / "src" / "quant_engine" / "artifact.py",
ROOT / "src" / "quant_engine" / "portfolio_construction.py",
ROOT / "src" / "quant_engine" / "portfolio_decomp.py",
ROOT / "src" / "quant_engine" / "risk.py",
):
assert "portfolio_risk_contracts" not in owner_path.read_text(encoding="utf-8")
forbidden_fields = {
"approved",
"approval",
"maker",
"checker",
"owner",
"publication",
"order",
"broker",
"environment",
"paper",
"live",
"uri",
"path",
"locator",
}
for contract_type in (
FreshnessPolicy,
ConstraintSetV1,
ComputationReceipt,
PortfolioDecision,
RiskAssessment,
):
assert not forbidden_fields & set(contract_type.__dataclass_fields__)
def test_read_only_owner_dependency_lock_and_ci_hashes_match_baseline() -> None:
expected = {
"src/quant_engine/governed_pipeline.py": "3b334f340898db78ed869375ab532f156e8c1fee595a8318f544bebdd391049d",
"src/quant_engine/artifact.py": "e15feec412d3bfff10d8f21ca20813fc65cabc0703940371ed661896147bc379",
"src/quant_engine/portfolio_construction.py": "e93d71da8d61b2047c19d4b99dace934a8cbc96d8d2b150ad62a9ceebd9163d4",
"src/quant_engine/portfolio_decomp.py": "1a4f9f9aac2c46bf6ed2826d1b0d1723f06e3ce7c4b3f6479e098cbbd135bea6",
"src/quant_engine/risk.py": "4a66c312d517d40f6f67bb71f438523e135645624ae78d49fa9c0fda2c02074e",
"pyproject.toml": "9340a25f3710778945765f14a02c91844aa66bd47b77c55d8fa4bd36849b4bd5",
"uv.lock": "076a16a01bc363109174909999652e81055954200a3ab93eab5f76df2249ee1a",
"ci-profile.yml": "dfe1b7c2820747fa28eaad409153623c468ee3e10f38f4fd94241731c5a55803",
}
actual = {
path: hashlib.sha256((ROOT / path).read_bytes()).hexdigest()
for path in expected
}
assert actual == expected