feat: add explicit retrospective v2 computation contracts (#20)
CI / lite (push) Successful in 19s
CI / lite (push) Successful in 19s
This commit was merged in pull request #20.
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user