Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ These profiles depend on artifacts produced by `CryptoLivePoolPipelines` before

| Profile | Name | Notes |
| --- | --- | --- |
| `crypto_leader_rotation` | Crypto Leader Rotation | runtime-enabled trend-following rotation that consumes the ordered live pool published by CryptoLivePoolPipelines. Runtime code may gate and size trades inside that pool, but monthly selection and order remain upstream. |
| `crypto_live_pool_rotation` | Crypto Live Pool Rotation | runtime-enabled trend-following rotation that consumes the ordered live pool published by CryptoLivePoolPipelines. Runtime code may gate and size trades inside that pool, but monthly selection and order remain upstream. |

### Research-only candidates

Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ CryptoStrategies 是 QuantStrategyLab 的加密货币策略包。为 Binance 执

| Profile | 名称 | 说明 |
| --- | --- | --- |
| `crypto_leader_rotation` | Crypto Leader Rotation | 消费 CryptoLivePoolPipelines 发布的有序 live pool 的趋势轮动策略。运行时代码可以在该池内做交易门控和仓位 sizing,但月度选池和排序属于上游。 |
| `crypto_live_pool_rotation` | Crypto Live Pool Rotation | 消费 CryptoLivePoolPipelines 发布的有序 live pool 的趋势轮动策略。运行时代码可以在该池内做交易门控和仓位 sizing,但月度选池和排序属于上游。 |

### 研究侧候选

Expand Down
10 changes: 5 additions & 5 deletions docs/crypto_cross_platform_strategy_spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Crypto profiles must declare exactly one `target_mode`.

Current default is:

- `crypto_leader_rotation` -> `weight`
- `crypto_live_pool_rotation` -> `weight`

Downstream platforms should translate only at the runtime boundary. Strategy code must not emit exchange-specific order fields.

Expand All @@ -55,11 +55,11 @@ A crypto runtime adapter must declare at least:
- `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:
`crypto_live_pool_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`
- `snapshot_contract_version = crypto_live_pool_rotation.live_pool.v1`
- `config_source_policy = none`

The strategy package owns this declaration. Downstream platforms may decide how
Expand All @@ -68,7 +68,7 @@ profile-name branches.

## Live-pool authority boundary

For `crypto_leader_rotation`, `CryptoLivePoolPipelines` is the authority for monthly live-pool membership, ranking, and order. The execution platform validates and preserves the ordered `live_pool.json["symbols"]` list, then passes it into `StrategyContext.market_data["universe_snapshot"]`.
For `crypto_live_pool_rotation`, `CryptoLivePoolPipelines` is the authority for monthly live-pool membership, ranking, and order. The execution platform validates and preserves the ordered `live_pool.json["symbols"]` list, then passes it into `StrategyContext.market_data["universe_snapshot"]`.

Strategy code may apply runtime gates, sell rules, top-N selection, inverse-volatility sizing, BTC core allocation, and buy-budget allocation inside that upstream pool. It must not rebuild the monthly live pool from local indicators, replace the upstream order with a local ranking, or treat research CSVs as a substitute for the validated artifact contract.

Expand All @@ -93,7 +93,7 @@ Forbidden inside strategy code:

Today only one profile is live:

- `crypto_leader_rotation`
- `crypto_live_pool_rotation`

Today only one platform adapter exists:

Expand Down
10 changes: 5 additions & 5 deletions docs/crypto_cross_platform_strategy_spec.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@

当前默认是:

- `crypto_leader_rotation` -> `weight`
- `crypto_live_pool_rotation` -> `weight`

如果以后某个平台原生不吃 `weight`,只能在 runtime 边界做翻译,不能把平台分支写进策略代码。

Expand All @@ -56,18 +56,18 @@
- 当策略需要 `ctx.portfolio` 时的 `portfolio_input_name`
- 当策略依赖上游 artifact 时的 `artifact_contract`

`crypto_leader_rotation` 当前显式声明的 artifact contract 是:
`crypto_live_pool_rotation` 当前显式声明的 artifact contract 是:

- `requires_snapshot_artifacts = true`
- `requires_snapshot_manifest_path = true`
- `snapshot_contract_version = crypto_leader_rotation.live_pool.v1`
- `snapshot_contract_version = crypto_live_pool_rotation.live_pool.v1`
- `config_source_policy = none`

策略包负责声明这些需求。下游平台可以决定从 Firestore、GCS、本地文件或状态里取 artifact,但不能再靠 profile 名称分支来猜这条策略需要什么 artifact。

## live-pool 权威边界

对 `crypto_leader_rotation` 来说,`CryptoLivePoolPipelines` 是月度 live pool 成员、ranking 和顺序的权威来源。执行平台负责校验并保留 `live_pool.json["symbols"]` 的有序列表,然后把它传给 `StrategyContext.market_data["universe_snapshot"]`。
对 `crypto_live_pool_rotation` 来说,`CryptoLivePoolPipelines` 是月度 live pool 成员、ranking 和顺序的权威来源。执行平台负责校验并保留 `live_pool.json["symbols"]` 的有序列表,然后把它传给 `StrategyContext.market_data["universe_snapshot"]`。

策略代码可以在这个上游池内做运行时门控、卖出规则、top-N 选择、逆波动 sizing、BTC core allocation 和买入预算分配。策略代码不能用本地指标重建月度 live pool,不能用本地 ranking 替换上游顺序,也不能把 research CSV 当作已验证 artifact contract 的替代品。

Expand All @@ -92,7 +92,7 @@

现在只有一条 live profile:

- `crypto_leader_rotation`
- `crypto_live_pool_rotation`

现在也只有一个平台 adapter:

Expand Down
28 changes: 14 additions & 14 deletions src/crypto_strategies/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
load_strategy_entrypoint,
)

CRYPTO_LEADER_ROTATION_PROFILE = "crypto_leader_rotation"
CRYPTO_LIVE_POOL_ROTATION_PROFILE = "crypto_live_pool_rotation"

CRYPTO_CANONICAL_REQUIRED_INPUTS = frozenset(
{
Expand All @@ -27,10 +27,10 @@
)

STRATEGY_TARGET_MODES: dict[str, str] = {
CRYPTO_LEADER_ROTATION_PROFILE: "weight",
CRYPTO_LIVE_POOL_ROTATION_PROFILE: "weight",
}

CRYPTO_LEADER_ROTATION_DEFAULT_CONFIG = {
CRYPTO_LIVE_POOL_ROTATION_DEFAULT_CONFIG = {
"trend_pool_size": 5,
"rotation_top_n": 2,
"min_history_days": 365,
Expand All @@ -39,40 +39,40 @@
"weight_mode": "inverse_vol",
"allow_rotation_refresh": True,
"atr_multiplier": 2.5,
"artifact_contract_version": "crypto_leader_rotation.live_pool.v1",
"artifact_contract_version": "crypto_live_pool_rotation.live_pool.v1",
"artifact_max_age_days": 45,
"artifact_acceptable_modes": ("core_major",),
}

STRATEGY_DEFINITIONS: dict[str, StrategyDefinition] = {
CRYPTO_LEADER_ROTATION_PROFILE: StrategyDefinition(
profile=CRYPTO_LEADER_ROTATION_PROFILE,
CRYPTO_LIVE_POOL_ROTATION_PROFILE: StrategyDefinition(
profile=CRYPTO_LIVE_POOL_ROTATION_PROFILE,
domain=CRYPTO_DOMAIN,
supported_platforms=frozenset({"binance"}),
components=(
StrategyComponentDefinition(
name="core",
module_path="crypto_strategies.strategies.crypto_leader_rotation.core",
module_path="crypto_strategies.strategies.crypto_live_pool_rotation.core",
),
StrategyComponentDefinition(
name="rotation",
module_path="crypto_strategies.strategies.crypto_leader_rotation.rotation",
module_path="crypto_strategies.strategies.crypto_live_pool_rotation.rotation",
),
),
entrypoint=StrategyEntrypointDefinition(
module_path="crypto_strategies.entrypoints",
attribute_name="crypto_leader_rotation_entrypoint",
attribute_name="crypto_live_pool_rotation_entrypoint",
),
required_inputs=CRYPTO_CANONICAL_REQUIRED_INPUTS,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the previous profile as an alias

Existing deployments or platform configs that still pass the former public profile crypto_leader_rotation now stop resolving after this rename because the new catalog metadata leaves aliases empty. Since this package already routes profile lookups through the catalog/adapter resolution helpers and profile strings are persisted outside the codebase, keep the old profile as an alias for crypto_live_pool_rotation so callers can migrate without breaking runtime adapter and entrypoint lookup.

Useful? React with 👍 / 👎.

default_config=CRYPTO_LEADER_ROTATION_DEFAULT_CONFIG,
target_mode=STRATEGY_TARGET_MODES[CRYPTO_LEADER_ROTATION_PROFILE],
default_config=CRYPTO_LIVE_POOL_ROTATION_DEFAULT_CONFIG,
target_mode=STRATEGY_TARGET_MODES[CRYPTO_LIVE_POOL_ROTATION_PROFILE],
),
}

STRATEGY_METADATA: dict[str, StrategyMetadata] = {
CRYPTO_LEADER_ROTATION_PROFILE: StrategyMetadata(
canonical_profile=CRYPTO_LEADER_ROTATION_PROFILE,
display_name="Crypto Leader Rotation",
CRYPTO_LIVE_POOL_ROTATION_PROFILE: StrategyMetadata(
canonical_profile=CRYPTO_LIVE_POOL_ROTATION_PROFILE,
display_name="Crypto Live Pool Rotation",
description="Trend-following crypto rotation with staged entries, degradation controls, and cash parking.",
aliases=(),
cadence="daily",
Expand Down
18 changes: 9 additions & 9 deletions src/crypto_strategies/entrypoints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@
StrategyDecision,
)

from crypto_strategies.manifests import crypto_leader_rotation_manifest
from crypto_strategies.manifests import crypto_live_pool_rotation_manifest


"""Unified crypto strategy entrypoints built on top of legacy core/rotation modules."""


def _merge_runtime_config(ctx: StrategyContext) -> dict[str, object]:
config = dict(crypto_leader_rotation_manifest.default_config)
config = dict(crypto_live_pool_rotation_manifest.default_config)
config.update(dict(ctx.runtime_config))
return config

Expand Down Expand Up @@ -110,13 +110,13 @@ def _set_symbol_trade_state(state, symbol, symbol_state):


def _load_legacy_modules():
from crypto_strategies.strategies.crypto_leader_rotation import core as legacy_core
from crypto_strategies.strategies.crypto_leader_rotation import rotation as legacy_rotation
from crypto_strategies.strategies.crypto_live_pool_rotation import core as legacy_core
from crypto_strategies.strategies.crypto_live_pool_rotation import rotation as legacy_rotation

return legacy_core, legacy_rotation


def evaluate_crypto_leader_rotation(ctx: StrategyContext) -> StrategyDecision:
def evaluate_crypto_live_pool_rotation(ctx: StrategyContext) -> StrategyDecision:
legacy_core, legacy_rotation = _load_legacy_modules()
config = _merge_runtime_config(ctx)
prices = _require_market_data(ctx, "market_prices")
Expand Down Expand Up @@ -256,10 +256,10 @@ def evaluate_crypto_leader_rotation(ctx: StrategyContext) -> StrategyDecision:
)


crypto_leader_rotation_entrypoint = CallableStrategyEntrypoint(
manifest=crypto_leader_rotation_manifest,
_evaluate=evaluate_crypto_leader_rotation,
crypto_live_pool_rotation_entrypoint = CallableStrategyEntrypoint(
manifest=crypto_live_pool_rotation_manifest,
_evaluate=evaluate_crypto_live_pool_rotation,
)


__all__ = ["crypto_leader_rotation_entrypoint", "evaluate_crypto_leader_rotation"]
__all__ = ["crypto_live_pool_rotation_entrypoint", "evaluate_crypto_live_pool_rotation"]
12 changes: 6 additions & 6 deletions src/crypto_strategies/manifests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
from quant_platform_kit.strategy_contracts import StrategyManifest


crypto_leader_rotation_manifest = StrategyManifest(
profile="crypto_leader_rotation",
crypto_live_pool_rotation_manifest = StrategyManifest(
profile="crypto_live_pool_rotation",
domain="crypto",
display_name="Crypto Leader Rotation",
display_name="Crypto Live Pool Rotation",
description="Trend-following crypto rotation with staged entries, degradation controls, and cash parking.",
required_inputs=frozenset(
{
Expand All @@ -26,17 +26,17 @@
"weight_mode": "inverse_vol",
"allow_rotation_refresh": True,
"atr_multiplier": 2.5,
"artifact_contract_version": "crypto_leader_rotation.live_pool.v1",
"artifact_contract_version": "crypto_live_pool_rotation.live_pool.v1",
"artifact_max_age_days": 45,
"artifact_acceptable_modes": ("core_major",),
},
)

MANIFESTS = {crypto_leader_rotation_manifest.profile: crypto_leader_rotation_manifest}
MANIFESTS = {crypto_live_pool_rotation_manifest.profile: crypto_live_pool_rotation_manifest}


def get_strategy_manifest(profile: str) -> StrategyManifest:
return MANIFESTS[profile]


__all__ = ["MANIFESTS", "crypto_leader_rotation_manifest", "get_strategy_manifest"]
__all__ = ["MANIFESTS", "crypto_live_pool_rotation_manifest", "get_strategy_manifest"]
10 changes: 5 additions & 5 deletions src/crypto_strategies/runtime_adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,22 @@
)


CRYPTO_LEADER_ROTATION_ARTIFACT_CONTRACT = StrategyArtifactContract(
CRYPTO_LIVE_POOL_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",
snapshot_contract_version="crypto_live_pool_rotation.live_pool.v1",
config_source_policy="none",
)


PLATFORM_RUNTIME_ADAPTERS: dict[str, dict[str, StrategyRuntimeAdapter]] = {
BINANCE_PLATFORM: {
"crypto_leader_rotation": StrategyRuntimeAdapter(
"crypto_live_pool_rotation": StrategyRuntimeAdapter(
status_icon="🪙",
available_inputs=CRYPTO_CANONICAL_REQUIRED_INPUTS,
portfolio_input_name="portfolio_snapshot",
artifact_contract=CRYPTO_LEADER_ROTATION_ARTIFACT_CONTRACT,
artifact_contract=CRYPTO_LIVE_POOL_ROTATION_ARTIFACT_CONTRACT,
),
}
}
Expand All @@ -66,7 +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",
"CRYPTO_LIVE_POOL_ROTATION_ARTIFACT_CONTRACT",
"PLATFORM_RUNTIME_ADAPTERS",
"get_platform_runtime_adapter",
"resolve_canonical_profile",
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Crypto live pool rotation strategy package."""
26 changes: 13 additions & 13 deletions tests/test_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,39 @@
from crypto_strategies import get_strategy_definitions
from crypto_strategies.catalog import (
CRYPTO_CANONICAL_REQUIRED_INPUTS,
CRYPTO_LEADER_ROTATION_PROFILE,
CRYPTO_LIVE_POOL_ROTATION_PROFILE,
get_strategy_definition,
)
from crypto_strategies.runtime_adapters import BINANCE_PLATFORM, get_platform_runtime_adapter


class CatalogTest(unittest.TestCase):
def test_catalog_contains_crypto_leader_rotation(self):
def test_catalog_contains_crypto_live_pool_rotation(self):
catalog = get_strategy_definitions()
self.assertIn(CRYPTO_LEADER_ROTATION_PROFILE, catalog)
self.assertEqual(catalog[CRYPTO_LEADER_ROTATION_PROFILE].domain, "crypto")
self.assertEqual(catalog[CRYPTO_LEADER_ROTATION_PROFILE].supported_platforms, frozenset({"binance"}))
self.assertEqual(catalog[CRYPTO_LEADER_ROTATION_PROFILE].target_mode, "weight")
self.assertEqual(catalog[CRYPTO_LEADER_ROTATION_PROFILE].required_inputs, CRYPTO_CANONICAL_REQUIRED_INPUTS)
self.assertIn(CRYPTO_LIVE_POOL_ROTATION_PROFILE, catalog)
self.assertEqual(catalog[CRYPTO_LIVE_POOL_ROTATION_PROFILE].domain, "crypto")
self.assertEqual(catalog[CRYPTO_LIVE_POOL_ROTATION_PROFILE].supported_platforms, frozenset({"binance"}))
self.assertEqual(catalog[CRYPTO_LIVE_POOL_ROTATION_PROFILE].target_mode, "weight")
self.assertEqual(catalog[CRYPTO_LIVE_POOL_ROTATION_PROFILE].required_inputs, CRYPTO_CANONICAL_REQUIRED_INPUTS)

def test_known_profile_resolves(self):
definition = get_strategy_definition("crypto_leader_rotation")
self.assertEqual(definition.profile, CRYPTO_LEADER_ROTATION_PROFILE)
definition = get_strategy_definition("crypto_live_pool_rotation")
self.assertEqual(definition.profile, CRYPTO_LIVE_POOL_ROTATION_PROFILE)
component_map = get_strategy_component_map(definition)
core_module = component_map["core"]
self.assertEqual(
core_module.module_path,
"crypto_strategies.strategies.crypto_leader_rotation.core",
"crypto_strategies.strategies.crypto_live_pool_rotation.core",
)
rotation_module = component_map["rotation"]
self.assertEqual(
rotation_module.module_path,
"crypto_strategies.strategies.crypto_leader_rotation.rotation",
"crypto_strategies.strategies.crypto_live_pool_rotation.rotation",
)

def test_runtime_adapter_covers_canonical_inputs(self):
definition = get_strategy_definition(CRYPTO_LEADER_ROTATION_PROFILE)
adapter = get_platform_runtime_adapter(CRYPTO_LEADER_ROTATION_PROFILE, platform_id=BINANCE_PLATFORM)
definition = get_strategy_definition(CRYPTO_LIVE_POOL_ROTATION_PROFILE)
adapter = get_platform_runtime_adapter(CRYPTO_LIVE_POOL_ROTATION_PROFILE, platform_id=BINANCE_PLATFORM)
self.assertLessEqual(definition.required_inputs, adapter.available_inputs)
self.assertEqual(adapter.portfolio_input_name, "portfolio_snapshot")

Expand Down
8 changes: 4 additions & 4 deletions tests/test_contract_governance.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from crypto_strategies.runtime_adapters import (
BINANCE_PLATFORM,
CRYPTO_CANONICAL_REQUIRED_INPUTS,
CRYPTO_LEADER_ROTATION_ARTIFACT_CONTRACT,
CRYPTO_LIVE_POOL_ROTATION_ARTIFACT_CONTRACT,
PLATFORM_RUNTIME_ADAPTERS,
get_platform_runtime_adapter,
)
Expand Down Expand Up @@ -59,13 +59,13 @@ def test_every_compatible_platform_has_runtime_adapter_coverage(self) -> None:
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)
adapter = get_platform_runtime_adapter("crypto_live_pool_rotation", platform_id=BINANCE_PLATFORM)
contract = resolve_strategy_artifact_contract(adapter)

self.assertEqual(contract, CRYPTO_LEADER_ROTATION_ARTIFACT_CONTRACT)
self.assertEqual(contract, CRYPTO_LIVE_POOL_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.snapshot_contract_version, "crypto_live_pool_rotation.live_pool.v1")
self.assertEqual(contract.config_source_policy, "none")

def test_runtime_adapter_map_matches_catalog_compatibility(self) -> None:
Expand Down
Loading