feat: add explicit retrospective v2 computation contracts (#20)
CI / lite (push) Successful in 19s

This commit was merged in pull request #20.
This commit is contained in:
2026-09-09 01:31:46 +08:00
parent 68dd68392a
commit 861c1e97a8
19 changed files with 7688 additions and 4 deletions
@@ -0,0 +1,157 @@
"""Offline synthetic v2 backtest evidence and replay boundaries."""
from __future__ import annotations
from typing import Any
import pytest
from quant_engine.factor_contracts import FactorContractError, PayloadValidation
from quant_engine.retrospective_backtest_contracts import RetrospectiveBacktestRunRef
from quant_engine.retrospective_factor_contracts import RetrospectiveFactorSetRef
from test_retrospective_data_contracts import digest, identify, replace_at
from test_retrospective_factor_contracts import factor_arguments, decoding_arguments
def run_arguments() -> dict[str, Any]:
arguments = factor_arguments()
factor = RetrospectiveFactorSetRef.create(**arguments)
view = next(iter(arguments["foundation"].views.values()))
return {
"dataset_snapshot": arguments["dataset_snapshot"],
"foundation": arguments["foundation"],
"factor_set": factor,
"universe_digest": digest({"synthetic_universe": 2}),
"trading_calendar_revision_ids": view.trading_calendar_revision_ids,
"corporate_action_revision_ids": view.corporate_action_revision_ids,
"strategy_id": "synthetic.top1",
"strategy_version": "1.0.0",
"strategy_digest": digest({"synthetic_strategy": "top1"}),
"execution_model_version": "1.0.0",
"execution_model_digest": digest({"synthetic_execution": 1}),
"cost_model_version": "1.0.0",
"cost_model_digest": digest({"synthetic_cost": 1}),
"random_seed": 7,
"code_revision": "d" * 40,
"environment_lock_digest": digest({"synthetic_lock": 1}),
"configuration_digest": digest({"lag_sessions": 1, "top_k": 1}),
"evaluation_at": "2026-09-08T01:09:00Z",
"computed_at": "2026-09-08T01:10:00Z",
}
def run_context(arguments: dict[str, Any]) -> dict[str, Any]:
return {key: arguments[key] for key in ("dataset_snapshot", "foundation", "factor_set")}
def test_run_identity_closes_observation_inputs_and_preserves_configurations() -> None:
arguments = run_arguments()
run = RetrospectiveBacktestRunRef.create(**arguments)
document = run.to_dict()
assert document["schema_version"] == "2.0.0"
assert run.run_id.startswith("rhbacktestrunv2:sha256:")
assert run.dataset_snapshot_id == arguments["dataset_snapshot"].snapshot_id
assert run.foundation_id == arguments["foundation"].foundation_id
assert run.factor_set_id == arguments["factor_set"].factor_set_id
assert document["usage"] == "retrospective_research"
assert document["historical_availability"] == "not_established"
assert document["decision_eligible"] is False
assert document["execution_validation"] == "not_validated"
assert document["observation_cutoff"] == arguments["foundation"].observation_cutoff
assert document["replay_attempt"] == 0
assert RetrospectiveBacktestRunRef.from_json(run.to_json(), **run_context(arguments)) == run
@pytest.mark.parametrize(
("path", "value"),
[
("schema_version", "1.0.0"),
("schema_version", "2.1.0"),
("historical_availability", "established"),
("usage", "as_available"),
("execution_validation", "validated"),
("decision_eligible", True),
("decision_eligible", 0),
("dataset_content_digest", "sha256:" + "0" * 64),
("foundation_digest", "sha256:" + "0" * 64),
("factor_set_digest", "sha256:" + "0" * 64),
("factor_output_content_digest", "sha256:" + "0" * 64),
("observation_cutoff", "2018-01-02T07:00:00Z"),
("evidence_scope", "real_data"),
("trading_calendar_revision_ids", []),
("corporate_action_revision_ids", ["rhcav2:sha256:" + "0" * 64]),
("evaluation_at", "2026-09-08T01:07:00Z"),
("computed_at", "2026-09-08T01:08:00Z"),
("computed_at", "2026-09-08T01:10:00.0000001Z"),
("random_seed", True),
("strategy_version", "latest"),
("configuration_digest", "../private/a"),
("code_revision", "unknown"),
("replay_attempt", 1),
("replay_reason", "retry"),
("replay_spec_digest", "sha256:" + "0" * 64),
("replay_ancestor_run_ids", ["rhbacktestrunv2:sha256:" + "0" * 64]),
],
)
def test_reidentified_run_must_match_exact_context(path: str, value: Any) -> None:
arguments = run_arguments()
row = RetrospectiveBacktestRunRef.create(**arguments).to_dict()
replace_at(row, path, value)
identify(row, "run_id", "rhbacktestrunv2:")
with pytest.raises(FactorContractError):
RetrospectiveBacktestRunRef.from_dict(row, **run_context(arguments))
def test_replay_keeps_input_spec_but_requires_new_actual_attempt_times() -> None:
arguments = run_arguments()
root = RetrospectiveBacktestRunRef.create(**arguments)
replay_args = {
**arguments,
"parent": root,
"replay_reason": "synthetic.retry",
"replay_attempt": 1,
"evaluation_at": "2026-09-08T01:12:00Z",
"computed_at": "2026-09-08T01:13:00Z",
}
replay = RetrospectiveBacktestRunRef.create(**replay_args)
assert replay.replay_spec_digest == root.replay_spec_digest
assert replay.run_id != root.run_id
assert replay.replay_ancestor_run_ids == (root.run_id,)
assert replay.evaluation_at != root.evaluation_at
assert (
RetrospectiveBacktestRunRef.from_json(
replay.to_json(), **run_context(arguments), parent=root
)
== replay
)
for changes in (
{"random_seed": 9},
{"configuration_digest": digest({"different_configuration": 1})},
{"evaluation_at": root.evaluation_at},
{"replay_attempt": 2},
{"replay_reason": None},
{"parent": None},
):
with pytest.raises(FactorContractError):
RetrospectiveBacktestRunRef.create(**{**replay_args, **changes})
def test_reference_only_factors_can_be_read_but_not_used_to_create_new_runs() -> None:
arguments = run_arguments()
run = RetrospectiveBacktestRunRef.create(**arguments)
factor = arguments["factor_set"]
reference = RetrospectiveFactorSetRef.from_dict(
factor.to_dict(),
definitions=factor._definitions,
dataset_snapshot=arguments["dataset_snapshot"],
foundation=arguments["foundation"],
)
with pytest.raises(FactorContractError):
RetrospectiveBacktestRunRef.create(**{**arguments, "factor_set": reference})
restored = RetrospectiveBacktestRunRef.from_dict(
run.to_dict(), **{**run_context(arguments), "factor_set": reference}
)
assert restored.input_payload_validation is PayloadValidation.REFERENCE_ONLY
with pytest.raises(FactorContractError):
restored.require_inputs_revalidated()
run.require_inputs_revalidated()