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
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ CryptoStrategies is the QuantStrategyLab crypto strategy package. It provides sh
It is one layer of a multi-repository system:

- **Strategy packages**: hold reusable strategy code, metadata, and runtime entrypoints.
- **Snapshot pipelines**: produce feature snapshots, rankings, backtests, and release evidence.
- **Snapshot pipelines**: produce authoritative live-pool snapshots, rankings, backtests, and release evidence.
- **Platform runtimes**: connect strategies to brokers, dry-run checks, notifications, and live deployment controls.
- **Shared infrastructure**: keeps contracts, settings, adapters, plugins, and audit workflows reusable across repositories.

This repository owns strategy code and metadata. It does not hold broker credentials, submit orders by itself, or replace the snapshot/backtest evidence required before a profile is enabled for live runtime settings.
This repository owns strategy code and metadata. For snapshot-backed crypto profiles, it consumes the upstream live pool and does not rebuild monthly pool membership or ordering locally. It does not hold broker credentials, submit orders by itself, or replace the snapshot/backtest evidence required before a profile is enabled for live runtime settings.

## Strategy profiles

Expand All @@ -31,7 +31,7 @@ These profiles depend on artifacts produced by `CryptoSnapshotPipelines` before

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

### Research-only candidates

Expand All @@ -47,7 +47,7 @@ Use the platform repositories for broker credentials, dry-run/live switches, ord

## Evidence and live enablement

Use this README as a map of the project, not as live performance data. Before enabling or changing a live profile, rerun the relevant snapshot/backtest pipeline and review short, medium, and long windows: return, max drawdown, benchmark-relative return, turnover, data freshness, and artifact version. If evidence is stale, incomplete, or the profile is marked research-only, keep it out of live runtime settings.
Use this README as a map of the project, not as live performance data. Before enabling or changing a live profile, rerun the relevant snapshot/backtest pipeline and review short, medium, and long windows: return, max drawdown, benchmark-relative return, turnover, data freshness, and artifact version. Monthly live-pool selection, ranking, and promotion evidence belong in CryptoSnapshotPipelines; strategy changes here should preserve that upstream authority. If evidence is stale, incomplete, or the profile is marked research-only, keep it out of live runtime settings.

## Repository layout

Expand Down
8 changes: 4 additions & 4 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ CryptoStrategies 是 QuantStrategyLab 的加密货币策略包。为 Binance 执
它属于一套多仓库量化系统中的一层:

- **策略包**:保存可复用策略代码、元数据和运行入口。
- **Snapshot 流水线**:生成 feature snapshot、ranking、回测和发布证据。
- **Snapshot 流水线**:生成官方 live-pool snapshot、ranking、回测和发布证据。
- **执行平台**:把策略接到券商、dry-run 检查、通知和 live 部署控制。
- **共享基础设施**:沉淀契约、配置、适配器、插件和审计 workflow,供多仓复用。

本仓库负责策略代码和元数据,不保存券商凭据,不直接提交订单,也不替代 live enable 前需要看的 snapshot 和回测证据。
本仓库负责策略代码和元数据。对 snapshot-backed 加密策略来说,本仓库只消费上游 live pool,不在本地重建月度池成员或顺序。本仓库不保存券商凭据,不直接提交订单,也不替代 live enable 前需要看的 snapshot 和回测证据。

## 策略 profile

Expand All @@ -31,7 +31,7 @@ CryptoStrategies 是 QuantStrategyLab 的加密货币策略包。为 Binance 执

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

### 研究侧候选

Expand All @@ -47,7 +47,7 @@ CryptoStrategies 是 QuantStrategyLab 的加密货币策略包。为 Binance 执

## 策略证据和 live enablement

README 只作为项目地图,不替代最新表现数据。启用或调整 live profile 前,需要重新运行相关 snapshot/backtest pipeline,并分别看短、中、长周期的收益、最大回撤、相对基准收益、换手、数据新鲜度和 artifact 版本。证据过期、不完整,或者 profile 仍标记为 research-only,就不要放进 live runtime settings。
README 只作为项目地图,不替代最新表现数据。启用或调整 live profile 前,需要重新运行相关 snapshot/backtest pipeline,并分别看短、中、长周期的收益、最大回撤、相对基准收益、换手、数据新鲜度和 artifact 版本。月度 live-pool 选择、ranking 和 promotion 证据属于 CryptoSnapshotPipelines;本仓库的策略改动应保留这个上游权威边界。证据过期、不完整,或者 profile 仍标记为 research-only,就不要放进 live runtime settings。

## 仓库结构

Expand Down
9 changes: 8 additions & 1 deletion docs/crypto_cross_platform_strategy_spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Current meaning for the live profile:
- `derived_indicators`: strategy-ready trend metrics keyed by symbol
- `benchmark_snapshot`: benchmark regime snapshot, currently BTC
- `portfolio_snapshot`: exchange-agnostic portfolio and cash snapshot
- `universe_snapshot`: candidate tradable symbols for this cycle
- `universe_snapshot`: ordered official live-pool symbols from the validated `CryptoSnapshotPipelines` artifact for this cycle

## Target mode

Expand Down Expand Up @@ -66,6 +66,12 @@ 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.

## Live-pool authority boundary

For `crypto_leader_rotation`, `CryptoSnapshotPipelines` 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.

## Allowed and forbidden boundaries

Allowed inside strategy code:
Expand All @@ -81,6 +87,7 @@ Forbidden inside strategy code:
- direct environment reads
- exchange-specific order payloads
- artifact-path lookup and freshness validation
- local monthly live-pool rebuilds for snapshot-backed profiles

## Current rollout

Expand Down
9 changes: 8 additions & 1 deletion docs/crypto_cross_platform_strategy_spec.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
- `derived_indicators`:按 symbol 组织的策略级趋势指标
- `benchmark_snapshot`:基准状态快照,当前是 BTC
- `portfolio_snapshot`:与交易所无关的组合和现金快照
- `universe_snapshot`:本轮候选可交易标的集合
- `universe_snapshot`:来自已验证 `CryptoSnapshotPipelines` artifact 的本轮官方有序 live-pool 标的集合

## target mode

Expand Down Expand Up @@ -65,6 +65,12 @@

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

## live-pool 权威边界

对 `crypto_leader_rotation` 来说,`CryptoSnapshotPipelines` 是月度 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 @@ -80,6 +86,7 @@
- 直接读环境变量
- 直接输出交易所专属下单字段
- 直接查 artifact 路径或做 artifact 新鲜度校验
- 为 snapshot-backed profile 在本地重建月度 live pool

## 当前落地状态

Expand Down
15 changes: 2 additions & 13 deletions src/crypto_strategies/entrypoints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,21 +136,10 @@ def evaluate_crypto_leader_rotation(ctx: StrategyContext) -> StrategyDecision:
account_metrics.get("dca_value", 0.0),
)

selected_pool, ranking = legacy_rotation.refresh_rotation_pool(
selected_pool = legacy_rotation.resolve_authoritative_rotation_pool(
working_state,
indicators_map,
btc_snapshot,
trend_universe_symbols=trend_universe_symbols,
trend_pool_size=config["trend_pool_size"],
build_stable_quality_pool_fn=lambda indicators, btc, previous_pool: legacy_core.build_stable_quality_pool(
indicators,
btc,
previous_pool,
pool_size=config["trend_pool_size"],
min_history_days=config["min_history_days"],
min_avg_quote_vol_180=config["min_avg_quote_vol_180"],
membership_bonus=config["membership_bonus"],
),
allow_refresh=bool(config.get("allow_rotation_refresh", True)),
Comment on lines +139 to 143
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 rotated-out holdings in the sell evaluation

With the new contract, universe_snapshot is only the current artifact live pool (docs/crypto_cross_platform_strategy_spec.md:71), so a trend holding from last month that was removed from the new artifact is no longer present in trend_universe_symbols. After resolving the pool solely from that upstream list here, the later sell loop still iterates only trend_universe_symbols, meaning get_trend_sell_reason() is never called for the stale holding and no rotated_out sell reason is emitted for it; include existing non-BTC portfolio holdings in the sell evaluation even while restricting new buys to the authoritative pool.

Useful? React with 👍 / 👎.

now_utc=config.get("now_utc"),
)
Expand Down Expand Up @@ -242,7 +231,7 @@ def evaluate_crypto_leader_rotation(ctx: StrategyContext) -> StrategyDecision:
}
for symbol, payload in selected_candidates.items()
},
"ranking_preview": tuple(item["symbol"] for item in ranking[: int(config["trend_pool_size"])]),
"ranking_preview": tuple(selected_pool[: int(config["trend_pool_size"])]),
"rotation_pool_source_version": working_state.get("rotation_pool_source_version"),
"rotation_pool_source_as_of_date": working_state.get("rotation_pool_source_as_of_date"),
"rotation_pool_last_month": working_state.get("rotation_pool_last_month"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@
from datetime import datetime, timezone


def _normalize_symbol_list(symbols):
normalized = []
seen = set()
for value in symbols or ():
symbol = str(value or "").strip().upper()
if not symbol or symbol in seen:
continue
normalized.append(symbol)
seen.add(symbol)
return normalized


def _set_rotation_pool_lock(state, *, source_version, source_as_of_date, now_utc):
locked_version = str(source_version or "").strip()
locked_as_of_date = str(source_as_of_date or "").strip()
Expand All @@ -16,6 +28,46 @@ def _set_rotation_pool_lock(state, *, source_version, source_as_of_date, now_utc
state["rotation_pool_last_month"] = (now_utc or datetime.now(timezone.utc)).strftime("%Y-%m")


def resolve_authoritative_rotation_pool(
state,
*,
trend_universe_symbols,
trend_pool_size,
allow_refresh=True,
now_utc=None,
):
now_utc = now_utc or datetime.now(timezone.utc)
upstream_pool = _normalize_symbol_list(trend_universe_symbols)
available_symbols = set(upstream_pool)
cached_pool = [
symbol
for symbol in _normalize_symbol_list(state.get("rotation_pool_symbols", []))
if not available_symbols or symbol in available_symbols
]
current_source_version = str(state.get("trend_pool_version", "")).strip()
current_source_as_of_date = str(state.get("trend_pool_as_of_date", "")).strip()

if not allow_refresh:
try:
fallback_size = max(0, int(trend_pool_size))
except Exception:
fallback_size = len(upstream_pool)
selected_pool = cached_pool or upstream_pool[:fallback_size]
elif upstream_pool:
selected_pool = upstream_pool
else:
selected_pool = cached_pool

_set_rotation_pool_lock(
state,
source_version=current_source_version,
source_as_of_date=current_source_as_of_date,
now_utc=now_utc,
)
state["rotation_pool_symbols"] = selected_pool
return selected_pool


def refresh_rotation_pool(
state,
indicators_map,
Expand Down
Loading