Skip to content

Commit 101a7c5

Browse files
committed
Expand crypto strategy governance checklist
1 parent 1cee13c commit 101a7c5

3 files changed

Lines changed: 124 additions & 2 deletions

File tree

docs/crypto_strategy_template.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22

33
Use this template when adding a new crypto strategy profile.
44

5+
## Minimum layout
6+
7+
At minimum, a new profile should touch these places:
8+
9+
- `src/crypto_strategies/catalog.py`
10+
- `src/crypto_strategies/manifests/__init__.py`
11+
- `src/crypto_strategies/entrypoints/__init__.py`
12+
- `src/crypto_strategies/runtime_adapters.py`
13+
- one implementation module under `src/crypto_strategies/strategies/`
14+
- tests under `tests/`
15+
516
## Minimum checklist
617

718
1. add `StrategyDefinition` in `src/crypto_strategies/catalog.py`
@@ -25,6 +36,23 @@ Every new profile must declare:
2536

2637
`required_inputs` must use canonical names only.
2738

39+
## Manifest and entrypoint
40+
41+
The manifest and the catalog entry must agree on:
42+
43+
- `profile`
44+
- `domain`
45+
- `display_name`
46+
- `required_inputs`
47+
- `default_config`
48+
49+
The entrypoint must:
50+
51+
- read only canonical inputs from `StrategyContext`
52+
- read `ctx.portfolio` when portfolio data is required
53+
- return only `StrategyDecision`
54+
- keep exchange-specific execution details out of the strategy repo
55+
2856
## Runtime adapter
2957

3058
A new runtime adapter must declare at least:
@@ -49,3 +77,13 @@ Do not:
4977
- entrypoint test
5078
- governance test for canonical inputs and explicit target mode
5179
- downstream adapter smoke test before enabling a new platform
80+
81+
## New strategy PR checklist
82+
83+
- [ ] `StrategyDefinition` added with explicit `target_mode`
84+
- [ ] `StrategyManifest` matches the catalog definition
85+
- [ ] entrypoint reads only canonical inputs
86+
- [ ] runtime adapter added for every compatible platform
87+
- [ ] strategy code has no platform branch or env reads
88+
- [ ] catalog, entrypoint, and governance tests were updated
89+
- [ ] downstream platform status script or adapter smoke was updated when rollout changed

docs/crypto_strategy_template.zh-CN.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22

33
以后新增加密策略时,按这个模板落。
44

5+
## 最小目录结构
6+
7+
至少会碰这些位置:
8+
9+
- `src/crypto_strategies/catalog.py`
10+
- `src/crypto_strategies/manifests/__init__.py`
11+
- `src/crypto_strategies/entrypoints/__init__.py`
12+
- `src/crypto_strategies/runtime_adapters.py`
13+
- `src/crypto_strategies/strategies/` 下面的一份实现模块
14+
- `tests/` 下面对应测试
15+
516
## 最小接入清单
617

718
1.`src/crypto_strategies/catalog.py` 里加 `StrategyDefinition`
@@ -25,6 +36,23 @@
2536

2637
其中 `required_inputs` 只能用 canonical 名。
2738

39+
## Manifest 和 entrypoint
40+
41+
manifest 和 catalog 里的定义必须一致,至少包括:
42+
43+
- `profile`
44+
- `domain`
45+
- `display_name`
46+
- `required_inputs`
47+
- `default_config`
48+
49+
entrypoint 必须做到:
50+
51+
- 只从 `StrategyContext` 读取 canonical 输入
52+
- 需要组合信息时读 `ctx.portfolio`
53+
- 只返回 `StrategyDecision`
54+
- 不把交易所专属执行细节放进策略仓
55+
2856
## Runtime adapter
2957

3058
新 adapter 至少要声明:
@@ -49,3 +77,13 @@
4977
- entrypoint test
5078
- 检查 canonical inputs 和显式 target mode 的治理测试
5179
- 变成 live 前,下游平台要补 adapter smoke test
80+
81+
## 新策略 PR checklist
82+
83+
- [ ] 已新增带显式 `target_mode``StrategyDefinition`
84+
- [ ] `StrategyManifest` 和 catalog 定义一致
85+
- [ ] entrypoint 只读取 canonical 输入
86+
- [ ] 每个兼容平台都补了 runtime adapter
87+
- [ ] 策略代码里没有平台分支和环境变量读取
88+
- [ ] catalog、entrypoint、governance 测试已更新
89+
- [ ] 如果 rollout 变了,下游平台的状态脚本或 adapter smoke 也已更新

tests/test_contract_governance.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@
33
import unittest
44
from pathlib import Path
55

6-
from crypto_strategies import get_strategy_definitions
6+
from crypto_strategies import (
7+
get_strategy_definition,
8+
get_strategy_definitions,
9+
get_strategy_entrypoint,
10+
get_strategy_index_rows,
11+
get_strategy_metadata,
12+
)
13+
from crypto_strategies.manifests import get_strategy_manifest
714
from crypto_strategies.runtime_adapters import (
815
BINANCE_PLATFORM,
916
CRYPTO_CANONICAL_REQUIRED_INPUTS,
@@ -44,7 +51,9 @@ def test_every_compatible_platform_has_runtime_adapter_coverage(self) -> None:
4451
with self.subTest(profile=profile, platform_id=platform_id):
4552
adapter = get_platform_runtime_adapter(profile, platform_id=platform_id)
4653
self.assertLessEqual(definition.required_inputs, adapter.available_inputs)
47-
if definition.target_mode != PLATFORM_NATIVE_TARGET_MODES[platform_id]:
54+
if "portfolio_snapshot" in definition.required_inputs:
55+
self.assertEqual(adapter.portfolio_input_name, "portfolio_snapshot")
56+
elif definition.target_mode != PLATFORM_NATIVE_TARGET_MODES[platform_id]:
4857
self.assertTrue(adapter.portfolio_input_name)
4958

5059
def test_runtime_adapter_map_matches_catalog_compatibility(self) -> None:
@@ -59,6 +68,43 @@ def test_runtime_adapter_map_matches_catalog_compatibility(self) -> None:
5968
with self.subTest(platform_id=platform_id):
6069
self.assertEqual(frozenset(adapters), supported_profiles)
6170

71+
def test_manifest_matches_catalog_definition_and_metadata(self) -> None:
72+
for profile in get_strategy_definitions():
73+
definition = get_strategy_definition(profile)
74+
metadata = get_strategy_metadata(profile)
75+
manifest = get_strategy_manifest(profile)
76+
with self.subTest(profile=profile):
77+
self.assertEqual(manifest.profile, definition.profile)
78+
self.assertEqual(manifest.domain, definition.domain)
79+
self.assertEqual(manifest.display_name, metadata.display_name)
80+
self.assertEqual(manifest.required_inputs, definition.required_inputs)
81+
self.assertEqual(dict(manifest.default_config), dict(definition.default_config))
82+
83+
def test_entrypoint_manifest_matches_catalog_when_importable(self) -> None:
84+
for profile in get_strategy_definitions():
85+
try:
86+
entrypoint = get_strategy_entrypoint(profile)
87+
except ModuleNotFoundError as exc:
88+
if exc.name == "pandas":
89+
self.skipTest("pandas is not installed")
90+
raise
91+
definition = get_strategy_definition(profile)
92+
metadata = get_strategy_metadata(profile)
93+
with self.subTest(profile=profile):
94+
self.assertEqual(entrypoint.manifest.profile, definition.profile)
95+
self.assertEqual(entrypoint.manifest.domain, definition.domain)
96+
self.assertEqual(entrypoint.manifest.display_name, metadata.display_name)
97+
self.assertEqual(entrypoint.manifest.required_inputs, definition.required_inputs)
98+
99+
def test_strategy_index_rows_expose_expected_contract_fields(self) -> None:
100+
rows = {row["canonical_profile"]: row for row in get_strategy_index_rows()}
101+
for profile, definition in get_strategy_definitions().items():
102+
row = rows[profile]
103+
with self.subTest(profile=profile):
104+
self.assertEqual(row["required_inputs"], definition.required_inputs)
105+
self.assertEqual(row["target_mode"], definition.target_mode)
106+
self.assertEqual(row["compatible_platforms"], definition.supported_platforms)
107+
62108
def test_strategy_and_entrypoint_sources_do_not_branch_on_platform_or_env(self) -> None:
63109
for root in GOVERNED_SOURCE_ROOTS:
64110
for path in sorted(root.rglob("*.py")):

0 commit comments

Comments
 (0)