diff --git a/docs/crypto_cross_platform_strategy_spec.md b/docs/crypto_cross_platform_strategy_spec.md index cf5c1ff..63a6df3 100644 --- a/docs/crypto_cross_platform_strategy_spec.md +++ b/docs/crypto_cross_platform_strategy_spec.md @@ -44,6 +44,18 @@ A crypto runtime adapter must declare at least: - `available_inputs` - `available_capabilities` - `portfolio_input_name` when the strategy needs `ctx.portfolio` +- `artifact_contract` when the strategy consumes upstream artifacts + +`crypto_leader_rotation` currently declares an explicit artifact contract: + +- `requires_snapshot_artifacts = true` +- `requires_snapshot_manifest_path = true` +- `snapshot_contract_version = crypto_leader_rotation.live_pool.v1` +- `config_source_policy = none` + +The strategy package owns this declaration. Downstream platforms may decide how +to fetch the artifact, but they should not infer artifact requirements from +profile-name branches. ## Allowed and forbidden boundaries diff --git a/docs/crypto_cross_platform_strategy_spec.zh-CN.md b/docs/crypto_cross_platform_strategy_spec.zh-CN.md index aa22517..b832291 100644 --- a/docs/crypto_cross_platform_strategy_spec.zh-CN.md +++ b/docs/crypto_cross_platform_strategy_spec.zh-CN.md @@ -44,6 +44,16 @@ - `available_inputs` - `available_capabilities` - 当策略需要 `ctx.portfolio` 时的 `portfolio_input_name` +- 当策略依赖上游 artifact 时的 `artifact_contract` + +`crypto_leader_rotation` 当前显式声明的 artifact contract 是: + +- `requires_snapshot_artifacts = true` +- `requires_snapshot_manifest_path = true` +- `snapshot_contract_version = crypto_leader_rotation.live_pool.v1` +- `config_source_policy = none` + +策略包负责声明这些需求。下游平台可以决定从 Firestore、GCS、本地文件或状态里取 artifact,但不能再靠 profile 名称分支来猜这条策略需要什么 artifact。 ## 允许和禁止 diff --git a/docs/crypto_portability_checklist.md b/docs/crypto_portability_checklist.md index 31efdca..7cce152 100644 --- a/docs/crypto_portability_checklist.md +++ b/docs/crypto_portability_checklist.md @@ -5,6 +5,7 @@ Use this before enabling a crypto profile on any downstream platform. - [ ] `required_inputs` only use canonical crypto input names - [ ] `target_mode` is explicitly declared - [ ] every compatible platform has a runtime adapter +- [ ] upstream artifact needs are declared through `artifact_contract`, not platform profile branches - [ ] strategy code does not branch on platform names - [ ] strategy code does not read exchange env vars - [ ] downstream runtime builds canonical inputs, not exchange-shaped names diff --git a/docs/crypto_portability_checklist.zh-CN.md b/docs/crypto_portability_checklist.zh-CN.md index fae23b5..0d8d625 100644 --- a/docs/crypto_portability_checklist.zh-CN.md +++ b/docs/crypto_portability_checklist.zh-CN.md @@ -5,6 +5,7 @@ - [ ] `required_inputs` 只使用 canonical 加密输入名 - [ ] `target_mode` 已显式声明 - [ ] 每个兼容平台都有对应 runtime adapter +- [ ] 上游 artifact 需求通过 `artifact_contract` 声明,而不是下游平台 profile 分支 - [ ] 策略代码没有平台分支 - [ ] 策略代码没有直接读取交易所环境变量 - [ ] 下游 runtime 传给策略的是 canonical 输入,而不是交易所专属名字 diff --git a/docs/crypto_strategy_template.md b/docs/crypto_strategy_template.md index ff31f7f..bd1b545 100644 --- a/docs/crypto_strategy_template.md +++ b/docs/crypto_strategy_template.md @@ -20,7 +20,8 @@ At minimum, a new profile should touch these places: 3. add a unified entrypoint in `src/crypto_strategies/entrypoints/__init__.py` 4. add a runtime adapter in `src/crypto_strategies/runtime_adapters.py` 5. add catalog and entrypoint tests -6. if the profile becomes live, add platform status and portability checks downstream +6. if the profile consumes upstream artifacts, add an explicit `StrategyArtifactContract` +7. if the profile becomes live, add platform status and portability checks downstream ## StrategyDefinition @@ -59,9 +60,13 @@ A new runtime adapter must declare at least: - `available_inputs` - `portfolio_input_name` when the strategy reads `ctx.portfolio` +- `artifact_contract` when the strategy needs upstream artifacts If a platform does not support the profile yet, do not add it to `supported_platforms`. +The artifact contract belongs in this strategy package. Platform repositories +should consume it and map it to their own env/filesystem/runtime source rules. + ## Forbidden shortcuts Do not: @@ -84,6 +89,7 @@ Do not: - [ ] `StrategyManifest` matches the catalog definition - [ ] entrypoint reads only canonical inputs - [ ] runtime adapter added for every compatible platform +- [ ] artifact contract added when upstream artifacts are required - [ ] strategy code has no platform branch or env reads - [ ] catalog, entrypoint, and governance tests were updated - [ ] downstream platform status script or adapter smoke was updated when rollout changed diff --git a/docs/crypto_strategy_template.zh-CN.md b/docs/crypto_strategy_template.zh-CN.md index 797682b..1f7f9a7 100644 --- a/docs/crypto_strategy_template.zh-CN.md +++ b/docs/crypto_strategy_template.zh-CN.md @@ -20,7 +20,8 @@ 3. 在 `src/crypto_strategies/entrypoints/__init__.py` 里加统一 entrypoint 4. 在 `src/crypto_strategies/runtime_adapters.py` 里加 runtime adapter 5. 补 catalog 和 entrypoint 测试 -6. 如果要变成 live,再去下游平台补状态和 portability 检查 +6. 如果策略消费上游 artifact,补显式 `StrategyArtifactContract` +7. 如果要变成 live,再去下游平台补状态和 portability 检查 ## StrategyDefinition 必填项 @@ -59,9 +60,12 @@ entrypoint 必须做到: - `available_inputs` - 如果策略会读 `ctx.portfolio`,则声明 `portfolio_input_name` +- 如果策略依赖上游 artifact,则声明 `artifact_contract` 如果某个平台还不支持这条策略,就不要先把它写进 `supported_platforms`。 +artifact contract 属于策略包。平台仓库只负责把它映射到自己的环境变量、文件、Firestore/GCS 或 runtime 状态来源,不能靠 profile 名称分支来猜策略需要什么 artifact。 + ## 禁止的偷懒方式 不要: @@ -84,6 +88,7 @@ entrypoint 必须做到: - [ ] `StrategyManifest` 和 catalog 定义一致 - [ ] entrypoint 只读取 canonical 输入 - [ ] 每个兼容平台都补了 runtime adapter +- [ ] 需要上游 artifact 时已新增 artifact contract - [ ] 策略代码里没有平台分支和环境变量读取 - [ ] catalog、entrypoint、governance 测试已更新 - [ ] 如果 rollout 变了,下游平台的状态脚本或 adapter smoke 也已更新 diff --git a/pyproject.toml b/pyproject.toml index 1565f49..f81483b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "crypto-strategies" -version = "0.4.3" +version = "0.4.4" description = "Shared crypto strategy catalog and implementations" readme = "README.md" requires-python = ">=3.11" diff --git a/src/crypto_strategies/runtime_adapters.py b/src/crypto_strategies/runtime_adapters.py index ff31dfe..0d680d2 100644 --- a/src/crypto_strategies/runtime_adapters.py +++ b/src/crypto_strategies/runtime_adapters.py @@ -2,6 +2,7 @@ from quant_platform_kit.common.strategies import resolve_catalog_profile from quant_platform_kit.strategy_contracts import ( + StrategyArtifactContract, StrategyRuntimeAdapter, validate_strategy_runtime_adapter, ) @@ -23,12 +24,22 @@ ) +CRYPTO_LEADER_ROTATION_ARTIFACT_CONTRACT = StrategyArtifactContract( + requires_snapshot_artifacts=True, + requires_snapshot_manifest_path=True, + requires_strategy_config_path=False, + snapshot_contract_version="crypto_leader_rotation.live_pool.v1", + config_source_policy="none", +) + + PLATFORM_RUNTIME_ADAPTERS: dict[str, dict[str, StrategyRuntimeAdapter]] = { BINANCE_PLATFORM: { "crypto_leader_rotation": StrategyRuntimeAdapter( status_icon="🪙", available_inputs=CRYPTO_CANONICAL_REQUIRED_INPUTS, portfolio_input_name="portfolio_snapshot", + artifact_contract=CRYPTO_LEADER_ROTATION_ARTIFACT_CONTRACT, ), } } @@ -55,6 +66,7 @@ def get_platform_runtime_adapter(profile: str | None, *, platform_id: str) -> St __all__ = [ "BINANCE_PLATFORM", "CRYPTO_CANONICAL_REQUIRED_INPUTS", + "CRYPTO_LEADER_ROTATION_ARTIFACT_CONTRACT", "PLATFORM_RUNTIME_ADAPTERS", "get_platform_runtime_adapter", "resolve_canonical_profile", diff --git a/tests/test_contract_governance.py b/tests/test_contract_governance.py index d22650c..8541b83 100644 --- a/tests/test_contract_governance.py +++ b/tests/test_contract_governance.py @@ -14,9 +14,11 @@ from crypto_strategies.runtime_adapters import ( BINANCE_PLATFORM, CRYPTO_CANONICAL_REQUIRED_INPUTS, + CRYPTO_LEADER_ROTATION_ARTIFACT_CONTRACT, PLATFORM_RUNTIME_ADAPTERS, get_platform_runtime_adapter, ) +from quant_platform_kit.strategy_contracts import resolve_strategy_artifact_contract ALLOWED_TARGET_MODES = frozenset({"weight", "value"}) @@ -56,6 +58,16 @@ def test_every_compatible_platform_has_runtime_adapter_coverage(self) -> None: elif definition.target_mode != PLATFORM_NATIVE_TARGET_MODES[platform_id]: self.assertTrue(adapter.portfolio_input_name) + def test_live_profiles_declare_explicit_artifact_contract(self) -> None: + adapter = get_platform_runtime_adapter("crypto_leader_rotation", platform_id=BINANCE_PLATFORM) + contract = resolve_strategy_artifact_contract(adapter) + + self.assertEqual(contract, CRYPTO_LEADER_ROTATION_ARTIFACT_CONTRACT) + self.assertTrue(contract.requires_snapshot_artifacts) + self.assertTrue(contract.requires_snapshot_manifest_path) + self.assertEqual(contract.snapshot_contract_version, "crypto_leader_rotation.live_pool.v1") + self.assertEqual(contract.config_source_policy, "none") + def test_runtime_adapter_map_matches_catalog_compatibility(self) -> None: compatibility_map = { profile: frozenset(definition.supported_platforms)