Skip to content

Commit f358059

Browse files
Pigbibicodex
andcommitted
refactor(snapshot): remove strategy reverse dependency
Co-Authored-By: Codex <noreply@openai.com>
1 parent 0ff5474 commit f358059

6 files changed

Lines changed: 25 additions & 42 deletions

File tree

docs/us_equity_cross_platform_strategy_spec.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,10 @@ stable contract:
178178
- freshness rule
179179
- optional manifest/checksum rule
180180

181+
Feature-snapshot manifests and config names must already use canonical profile
182+
keys. Retired aliases are rejected by shared guards instead of being silently
183+
resolved inside `QuantPlatformKit`.
184+
181185
The platform runtime owns:
182186

183187
- artifact transport

docs/us_equity_cross_platform_strategy_spec.zh-CN.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,10 @@ adapter 可以描述:
178178
- 新鲜度规则
179179
- 是否需要 manifest / checksum
180180

181+
`feature_snapshot` 的 manifest 里的 `strategy_profile` / `config_name`
182+
必须直接使用 canonical profile key。已经退役的 alias 会被共享 guard
183+
直接拒绝,不再由 `QuantPlatformKit` 静默兜底解析。
184+
181185
平台仓库负责:
182186

183187
- artifact transport

qsl.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
tier = "core"
2-
ring = 0
2+
upgrade_ring = "ring_a"
33
allow_legacy = true
44
legacy_reason = "constraints.txt remains as the pip-compatible mirror for qsl-pins.txt and update-qpk-pin workflow."
55

src/quant_platform_kit/common/feature_snapshot.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
import pandas as pd
1515

16+
from .strategies import normalize_profile_name
17+
1618

1719
DEFAULT_SNAPSHOT_DATE_COLUMNS = ("as_of", "snapshot_date")
1820
DEFAULT_MAX_SNAPSHOT_MONTH_LAG = 1
@@ -39,17 +41,7 @@
3941

4042

4143
def _normalize_strategy_profile_label(value: object) -> str:
42-
label = str(value or "").strip()
43-
if not label:
44-
return ""
45-
try:
46-
from us_equity_strategies.catalog import resolve_canonical_profile
47-
except Exception:
48-
return label
49-
try:
50-
return str(resolve_canonical_profile(label)).strip()
51-
except Exception:
52-
return label
44+
return normalize_profile_name(str(value or "").strip())
5345

5446

5547
def _normalize_contract_version_label(value: object) -> str:

tests/test_feature_snapshot.py

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,24 @@
22

33
import hashlib
44
import json
5-
import sys
65
import tempfile
76
import unittest
87
from pathlib import Path
9-
from types import ModuleType
108

119
from quant_platform_kit.common.feature_snapshot import load_feature_snapshot_guarded
1210

1311

1412
class FeatureSnapshotGuardAliasTests(unittest.TestCase):
15-
def test_guard_accepts_legacy_strategy_profile_alias_in_manifest(self) -> None:
16-
package = ModuleType("us_equity_strategies")
17-
catalog = ModuleType("us_equity_strategies.catalog")
18-
19-
def resolve_canonical_profile(profile: str | None) -> str:
20-
mapping = {
21-
"tech_pullback_cash_buffer": "qqq_tech_enhancement",
22-
"qqq_tech_enhancement": "qqq_tech_enhancement",
23-
}
24-
return mapping.get(str(profile), str(profile))
25-
26-
catalog.resolve_canonical_profile = resolve_canonical_profile
27-
sys.modules[package.__name__] = package
28-
sys.modules[catalog.__name__] = catalog
29-
self.addCleanup(sys.modules.pop, package.__name__, None)
30-
self.addCleanup(sys.modules.pop, catalog.__name__, None)
31-
13+
def test_guard_rejects_retired_strategy_profile_alias_in_manifest(self) -> None:
3214
with tempfile.TemporaryDirectory() as tmp_dir:
3315
tmp_path = Path(tmp_dir)
3416
snapshot_path = tmp_path / "snapshot.csv"
3517
manifest_path = tmp_path / "snapshot.csv.manifest.json"
36-
config_path = tmp_path / "tech_pullback_cash_buffer.json"
3718
snapshot_path.write_text(
3819
"as_of,symbol,close\n2026-04-01,QQQ,500\n",
3920
encoding="utf-8",
4021
)
41-
config_path.write_text('{"name": "tech_pullback_cash_buffer"}', encoding="utf-8")
4222
snapshot_sha256 = hashlib.sha256(snapshot_path.read_bytes()).hexdigest()
43-
config_sha256 = hashlib.sha256(config_path.read_bytes()).hexdigest()
4423
manifest_path.write_text(
4524
json.dumps(
4625
{
@@ -49,7 +28,7 @@ def resolve_canonical_profile(profile: str | None) -> str:
4928
"config_name": "tech_pullback_cash_buffer",
5029
"contract_version": "tech_pullback_cash_buffer.feature_snapshot.v1",
5130
"snapshot_sha256": snapshot_sha256,
52-
"config_sha256": config_sha256,
31+
"config_sha256": "abc",
5332
"price_as_of": "2026-04-01",
5433
"universe_as_of": "2026-03-31",
5534
"source_input_status": "fresh",
@@ -70,13 +49,13 @@ def resolve_canonical_profile(profile: str | None) -> str:
7049
expected_config_name="qqq_tech_enhancement",
7150
)
7251

73-
self.assertIsNotNone(result.frame)
74-
self.assertEqual(result.metadata["snapshot_guard_decision"], "proceed")
52+
self.assertIsNone(result.frame)
53+
self.assertEqual(result.metadata["snapshot_guard_decision"], "fail_closed")
7554
self.assertEqual(result.metadata["snapshot_manifest_strategy_profile"], "tech_pullback_cash_buffer")
76-
self.assertEqual(result.metadata["snapshot_manifest_price_as_of"], "2026-04-01")
77-
self.assertEqual(result.metadata["snapshot_manifest_universe_as_of"], "2026-03-31")
78-
self.assertEqual(result.metadata["snapshot_manifest_source_input_status"], "fresh")
79-
self.assertEqual(result.metadata["snapshot_manifest_source_refresh_run_id"], "12345")
55+
self.assertIn(
56+
"feature_snapshot_manifest_strategy_profile_mismatch",
57+
str(result.metadata["fail_reason"]),
58+
)
8059

8160
def test_guard_includes_manifest_diagnostics_when_stale(self) -> None:
8261
with tempfile.TemporaryDirectory() as tmp_dir:

tests/test_live_return_collector.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,13 +151,17 @@ def test_collect_merges_live_run_returns(self) -> None:
151151
{"platform": "schwab", "total_equity": 100.0},
152152
domain="us_equity",
153153
)
154+
first_record = store.list_live_run_records("us_equity", strategy_profile="global_etf_rotation")[0]
155+
second_recorded_at = (
156+
pd.Timestamp(first_record["recorded_at"]).normalize() + pd.Timedelta(days=1, hours=10)
157+
).isoformat()
154158
# Second record on a later day so pct_change has one observation.
155159
second = store._local_path("live_runs/us_equity/global_etf_rotation/manual-day2.json")
156160
second.parent.mkdir(parents=True, exist_ok=True)
157161
second.write_text(
158162
(
159163
'{"strategy_profile":"global_etf_rotation","domain":"us_equity",'
160-
'"recorded_at":"2026-07-10T10:00:00+00:00","record_kind":"execution",'
164+
f'"recorded_at":"{second_recorded_at}","record_kind":"execution",'
161165
'"execution_result":{"total_equity":102.0}}'
162166
),
163167
encoding="utf-8",

0 commit comments

Comments
 (0)