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
33 changes: 17 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ The current `crypto_leader_rotation` pure strategy modules are sourced from `Cry

Full strategy documentation now lives in [`CryptoStrategies`](https://github.com/QuantStrategyLab/CryptoStrategies#crypto_leader_rotation). The sections below focus on downstream execution assumptions and runtime behavior.

**Artifact contract:** Local replay and monitoring helpers now follow an explicit live-pool artifact contract: runtime payload, Firestore payload, `TREND_POOL_FILE`, repo-local `artifacts/live_pool_legacy.json`, then compatible fallback candidates. A sibling `../CryptoLeaderRotation` checkout is only one fallback candidate, not the sole default source.
**Artifact contract:** Local replay and monitoring helpers now follow an explicit strategy artifact contract: runtime payload, Firestore payload, `STRATEGY_ARTIFACT_FILE`, repo-local `artifacts/live_pool_legacy.json`, then compatible fallback candidates. The old `TREND_POOL_*` settings remain compatibility aliases for `crypto_leader_rotation`. A sibling `../CryptoLeaderRotation` checkout is only one fallback candidate, not the sole default source.

**Python runtime:** Prefer Python `3.11`. CI is pinned to 3.11, and local helper commands now prefer `python3.11` when available while still falling back to `python3`.

Expand Down Expand Up @@ -170,9 +170,9 @@ Runs hourly; signals are daily trend and risk, not high-frequency.

**Default:** CryptoLeaderRotation monthly output.

1. Firestore `strategy` / `CRYPTO_LEADER_ROTATION_LIVE_POOL` (override: `TREND_POOL_FIRESTORE_COLLECTION`, `TREND_POOL_FIRESTORE_DOCUMENT`).
1. Firestore `strategy` / `CRYPTO_LEADER_ROTATION_LIVE_POOL` (override: `STRATEGY_ARTIFACT_FIRESTORE_COLLECTION`, `STRATEGY_ARTIFACT_FIRESTORE_DOCUMENT`; legacy aliases: `TREND_POOL_FIRESTORE_COLLECTION`, `TREND_POOL_FIRESTORE_DOCUMENT`).
2. Last known good upstream payload persisted in Firestore state after a successful accepted upstream read.
3. Local `live_pool_legacy.json` or `live_pool.json` style file (override: `TREND_POOL_FILE`).
3. Local `live_pool_legacy.json` or `live_pool.json` style file (override: `STRATEGY_ARTIFACT_FILE`; legacy alias: `TREND_POOL_FILE`).
4. Static `TREND_UNIVERSE` as emergency fallback only.

**Stable upstream contract fields:**
Expand Down Expand Up @@ -223,9 +223,9 @@ The monthly execution pool is rebuilt when the accepted upstream `version` / `as
**Validation and degraded mode:**

- Upstream payloads must have a non-empty symbol set, a parseable `as_of_date`, and an acceptable `mode`.
- Freshness is validated with `TREND_POOL_MAX_AGE_DAYS` against the upstream `as_of_date`.
- Freshness is validated with `STRATEGY_ARTIFACT_MAX_AGE_DAYS` against the upstream `as_of_date`; `TREND_POOL_MAX_AGE_DAYS` remains a compatibility alias.
- If the fresh upstream payload is stale or malformed, the runtime does not silently treat weaker fallbacks as equivalent.
- In degraded mode, the script prefers the last known good upstream payload, then a validated local file fallback, and pauses new trend buys by default unless `TREND_POOL_ALLOW_NEW_ENTRIES_ON_DEGRADED=1`.
- In degraded mode, the script prefers the last known good upstream payload, then a validated local file fallback, and pauses new trend buys by default unless `STRATEGY_ARTIFACT_ALLOW_NEW_ENTRIES_ON_DEGRADED=1` or the legacy alias `TREND_POOL_ALLOW_NEW_ENTRIES_ON_DEGRADED=1`.
- Retired symbols stay in state until sold; active pool changes are source-tagged in state for auditability.

## Environment
Expand All @@ -245,6 +245,18 @@ Across multiple quant repositories, `GLOBAL_TELEGRAM_CHAT_ID` and `NOTIFY_LANG`
Optional:

| Variable | Description |
|----------|-------------|
| `STRATEGY_PROFILE` | Strategy profile selector (default: `crypto_leader_rotation`; supported value: `crypto_leader_rotation`) |
| `STRATEGY_ARTIFACT_FILE` | Local live-pool artifact path; legacy alias: `TREND_POOL_FILE` |
| `STRATEGY_ARTIFACT_MANIFEST_FILE` | Optional local artifact manifest path for operator tooling |
| `STRATEGY_ARTIFACT_FIRESTORE_COLLECTION` | Firestore collection for the live artifact (default `strategy`; legacy alias: `TREND_POOL_FIRESTORE_COLLECTION`) |
| `STRATEGY_ARTIFACT_FIRESTORE_DOCUMENT` | Firestore document for the live artifact (default `CRYPTO_LEADER_ROTATION_LIVE_POOL`; legacy alias: `TREND_POOL_FIRESTORE_DOCUMENT`) |
| `STRATEGY_ARTIFACT_MAX_AGE_DAYS` | Max allowed upstream `as_of_date` age before payload is stale (default `45`; legacy alias: `TREND_POOL_MAX_AGE_DAYS`) |
| `STRATEGY_ARTIFACT_ACCEPTABLE_MODES` | Comma-separated acceptable upstream modes (default `core_major`; legacy alias: `TREND_POOL_ACCEPTABLE_MODES`) |
| `STRATEGY_ARTIFACT_EXPECTED_SIZE` | Expected live-pool size for contract checks (default `5`; legacy alias: `TREND_POOL_EXPECTED_SIZE`) |
| `STRATEGY_ARTIFACT_ALLOW_NEW_ENTRIES_ON_DEGRADED` | Allow trend buys while using last-known-good or fallback sources (default `false`; legacy alias: `TREND_POOL_ALLOW_NEW_ENTRIES_ON_DEGRADED`) |
| `BTC_STATUS_REPORT_INTERVAL_HOURS` | Interval for BTC status report (default `24`) |
| `NOTIFY_LANG` | Log and notification language: `en` (English, default) or `zh` (Chinese) |

---

Expand Down Expand Up @@ -294,17 +306,6 @@ Optional:
- `GOOGLE_APPLICATION_CREDENTIALS`

可选环境变量、趋势池契约、日志和月度审阅的完整中文说明,见 [README.zh-CN.md](README.zh-CN.md)。
|----------|-------------|
| `BTC_STATUS_REPORT_INTERVAL_HOURS` | Interval for BTC status report (default 24) |
| `TREND_POOL_FILE` | Path to `live_pool_legacy.json` |
| `TREND_POOL_FIRESTORE_COLLECTION` | Firestore collection for live pool (default `strategy`) |
| `TREND_POOL_FIRESTORE_DOCUMENT` | Firestore document for live pool (default `CRYPTO_LEADER_ROTATION_LIVE_POOL`) |
| `TREND_POOL_MAX_AGE_DAYS` | Max allowed age for upstream `as_of_date` before payload is treated as stale (default `45`) |
| `TREND_POOL_ACCEPTABLE_MODES` | Comma-separated allowed upstream modes (default `core_major`) |
| `TREND_POOL_EXPECTED_SIZE` | Expected upstream live-pool size for contract checks (default `5`) |
| `TREND_POOL_ALLOW_NEW_ENTRIES_ON_DEGRADED` | Allow trend buys when running on last-known-good or fallback pool sources (default `false`) |
| `STRATEGY_PROFILE` | Strategy profile selector (default: `crypto_leader_rotation`; supported value: `crypto_leader_rotation`) |
| `NOTIFY_LANG` | Log and notification language: `en` (English, default) or `zh` (Chinese) |

## Notification Format

Expand Down
30 changes: 16 additions & 14 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

完整策略说明现在放在 [`CryptoStrategies`](https://github.com/QuantStrategyLab/CryptoStrategies#crypto_leader_rotation)。下面这些章节主要保留下游执行侧的约束、运行时行为和运维说明。

**artifact contract:** 本地 replay、monitor 和 review 工具现在按显式 live-pool artifact contract 取数:runtime 注入 payload、Firestore payload、`TREND_POOL_FILE`、仓库内 `artifacts/live_pool_legacy.json`,最后才是兼容 fallback 候选。`../CryptoLeaderRotation` 只是不保证存在的候选之一,不再是默认唯一来源。
**artifact contract:** 本地 replay、monitor 和 review 工具现在按显式 strategy artifact contract 取数:runtime 注入 payload、Firestore payload、`STRATEGY_ARTIFACT_FILE`、仓库内 `artifacts/live_pool_legacy.json`,最后才是兼容 fallback 候选。旧的 `TREND_POOL_*` 仍作为 `crypto_leader_rotation` 的兼容别名。`../CryptoLeaderRotation` 只是不保证存在的候选之一,不再是默认唯一来源。

**Python 版本:** 推荐 `Python 3.11`。CI 固定在 `3.11`,本地辅助命令会优先使用 `python3.11`,没有时回退到 `python3`。

Expand Down Expand Up @@ -101,9 +101,9 @@

**候选池来源:** 优先使用上游 live pool。读取顺序为:

1. 新鲜的上游 Firestore payload
1. 新鲜的上游 Firestore payload(主配置:`STRATEGY_ARTIFACT_FIRESTORE_COLLECTION` / `STRATEGY_ARTIFACT_FIRESTORE_DOCUMENT`;兼容别名:`TREND_POOL_FIRESTORE_COLLECTION` / `TREND_POOL_FIRESTORE_DOCUMENT`)
2. Firestore 状态中记录的 last known good 上游 payload
3. 通过校验的本地 upstream 文件 fallback
3. 通过校验的本地 upstream 文件 fallback(主配置:`STRATEGY_ARTIFACT_FILE`;兼容别名:`TREND_POOL_FILE`)
4. 静态 `TREND_UNIVERSE` 紧急 fallback

**官方输入池:** 上游发布 5 币 live pool,本仓库把这 5 个币视为月度官方输入集。
Expand Down Expand Up @@ -167,9 +167,9 @@

读取顺序:

1. Firestore `strategy` / `CRYPTO_LEADER_ROTATION_LIVE_POOL`
1. Firestore `strategy` / `CRYPTO_LEADER_ROTATION_LIVE_POOL`(主配置:`STRATEGY_ARTIFACT_FIRESTORE_COLLECTION` / `STRATEGY_ARTIFACT_FIRESTORE_DOCUMENT`;兼容别名:`TREND_POOL_FIRESTORE_COLLECTION` / `TREND_POOL_FIRESTORE_DOCUMENT`)
2. 状态里保存的 last known good 上游 payload
3. 本地 `live_pool_legacy.json` 或 `live_pool.json`
3. 本地 `live_pool_legacy.json` 或 `live_pool.json`(主配置:`STRATEGY_ARTIFACT_FILE`;兼容别名:`TREND_POOL_FILE`)
4. 静态 `TREND_UNIVERSE`

**稳定字段:**
Expand All @@ -185,9 +185,9 @@
**降级模式规则:**

- 上游 payload 必须有非空币种列表、可解析的 `as_of_date`、可接受的 `mode`
- 新鲜度由 `TREND_POOL_MAX_AGE_DAYS` 和 `as_of_date` 控制
- 新鲜度由 `STRATEGY_ARTIFACT_MAX_AGE_DAYS` 和 `as_of_date` 控制;`TREND_POOL_MAX_AGE_DAYS` 仍可兼容使用
- 如果 fresh upstream 过期或格式错误,不会把弱 fallback 当成等价替代
- 进入 degraded mode 后,默认暂停新的趋势买入,除非显式设置 `TREND_POOL_ALLOW_NEW_ENTRIES_ON_DEGRADED=1`
- 进入 degraded mode 后,默认暂停新的趋势买入,除非显式设置 `STRATEGY_ARTIFACT_ALLOW_NEW_ENTRIES_ON_DEGRADED=1`,或使用兼容别名 `TREND_POOL_ALLOW_NEW_ENTRIES_ON_DEGRADED=1`
- 已退役币种会保留在状态中直到真正卖出

## 环境变量
Expand All @@ -209,13 +209,15 @@
| 变量 | 说明 |
|---|---|
| `BTC_STATUS_REPORT_INTERVAL_HOURS` | BTC 状态报告间隔,默认 `24` |
| `TREND_POOL_FILE` | 本地 `live_pool_legacy.json` 路径 |
| `TREND_POOL_FIRESTORE_COLLECTION` | 趋势池 Firestore collection,默认 `strategy` |
| `TREND_POOL_FIRESTORE_DOCUMENT` | 趋势池 Firestore document,默认 `CRYPTO_LEADER_ROTATION_LIVE_POOL` |
| `TREND_POOL_MAX_AGE_DAYS` | 上游 `as_of_date` 允许的最大天数,默认 `45` |
| `TREND_POOL_ACCEPTABLE_MODES` | 可接受的上游 mode,默认 `core_major` |
| `TREND_POOL_EXPECTED_SIZE` | 上游 live pool 期望数量,默认 `5` |
| `TREND_POOL_ALLOW_NEW_ENTRIES_ON_DEGRADED` | degraded mode 下是否允许趋势新开仓,默认 `false` |
| `STRATEGY_PROFILE` | 策略 profile 选择器,当前默认并仅支持 `crypto_leader_rotation` |
| `STRATEGY_ARTIFACT_FILE` | 本地 live-pool artifact 路径;兼容别名:`TREND_POOL_FILE` |
| `STRATEGY_ARTIFACT_MANIFEST_FILE` | 可选本地 artifact manifest 路径,供运维工具使用 |
| `STRATEGY_ARTIFACT_FIRESTORE_COLLECTION` | live artifact 的 Firestore collection,默认 `strategy`;兼容别名:`TREND_POOL_FIRESTORE_COLLECTION` |
| `STRATEGY_ARTIFACT_FIRESTORE_DOCUMENT` | live artifact 的 Firestore document,默认 `CRYPTO_LEADER_ROTATION_LIVE_POOL`;兼容别名:`TREND_POOL_FIRESTORE_DOCUMENT` |
| `STRATEGY_ARTIFACT_MAX_AGE_DAYS` | 上游 `as_of_date` 允许的最大天数,默认 `45`;兼容别名:`TREND_POOL_MAX_AGE_DAYS` |
| `STRATEGY_ARTIFACT_ACCEPTABLE_MODES` | 可接受的上游 mode,默认 `core_major`;兼容别名:`TREND_POOL_ACCEPTABLE_MODES` |
| `STRATEGY_ARTIFACT_EXPECTED_SIZE` | 上游 live pool 期望数量,默认 `5`;兼容别名:`TREND_POOL_EXPECTED_SIZE` |
| `STRATEGY_ARTIFACT_ALLOW_NEW_ENTRIES_ON_DEGRADED` | degraded mode 下是否允许趋势新开仓,默认 `false`;兼容别名:`TREND_POOL_ALLOW_NEW_ENTRIES_ON_DEGRADED` |
| `NOTIFY_LANG` | 日志和通知语言: `en`(英文,默认)或 `zh`(中文) |

## 通知格式
Expand Down
12 changes: 6 additions & 6 deletions degraded_mode_support.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from __future__ import annotations

import os
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from notify_i18n_support import translate as t
from strategy_artifact_support import build_strategy_artifact_file_candidates, get_strategy_artifact_env
from trend_pool_support import (
build_static_trend_pool_resolution,
build_trend_pool_resolution,
Expand Down Expand Up @@ -72,11 +72,11 @@ def resolve_trend_pool_source(
)
messages.extend(last_good_result.get("errors", []))

configured_path = str(os.getenv("TREND_POOL_FILE", "")).strip()
file_candidates: list[Path] = []
if configured_path:
file_candidates.append(Path(configured_path).expanduser())
file_candidates.extend(get_default_live_pool_candidates(default_live_pool_legacy_path))
configured_path = get_strategy_artifact_env("STRATEGY_ARTIFACT_FILE", "TREND_POOL_FILE")
file_candidates = build_strategy_artifact_file_candidates(
configured_path=configured_path,
default_candidates=get_default_live_pool_candidates(default_live_pool_legacy_path),
)

seen_candidates: set[str] = set()
for pool_path in file_candidates:
Expand Down
21 changes: 18 additions & 3 deletions docs/operator_runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ It is not responsible for:
## Normal Live Flow

1. Load runtime credentials and Firestore state.
2. Resolve the upstream trend pool in this order:
2. Resolve the upstream strategy artifact in this order:
- fresh upstream Firestore payload
- last known good upstream payload from state
- validated local upstream file fallback
Expand Down Expand Up @@ -75,14 +75,29 @@ Degraded mode:

- Source is `last_known_good`, `local_file`, or `static`
- New trend buys are paused by default
- Set `TREND_POOL_ALLOW_NEW_ENTRIES_ON_DEGRADED=1` only if you intentionally want degraded-mode entries
- Set `STRATEGY_ARTIFACT_ALLOW_NEW_ENTRIES_ON_DEGRADED=1` only if you intentionally want degraded-mode entries; `TREND_POOL_ALLOW_NEW_ENTRIES_ON_DEGRADED=1` remains a compatibility alias for the current live profile

Interpretation:

- `last_known_good` means fresh upstream validation failed, but a previously accepted upstream payload is still available in state
- `local_file` means upstream live access failed and the runtime fell back to a validated local file from CryptoLeaderRotation
- `local_file` means upstream live access failed and the runtime fell back to a validated local file from the configured `STRATEGY_ARTIFACT_FILE`, the repo-local artifact, or a compatible `CryptoLeaderRotation` checkout
- `static` is emergency-only and should be treated as lowest-confidence operation

## Strategy Artifact Settings

Use the generic `STRATEGY_ARTIFACT_*` names for new crypto strategies. The older `TREND_POOL_*` names are accepted only as compatibility aliases for `crypto_leader_rotation`.

Primary settings:

- `STRATEGY_PROFILE`: live profile selector; current supported value is `crypto_leader_rotation`
- `STRATEGY_ARTIFACT_FIRESTORE_COLLECTION`: upstream artifact collection, default `strategy`
- `STRATEGY_ARTIFACT_FIRESTORE_DOCUMENT`: upstream artifact document, default `CRYPTO_LEADER_ROTATION_LIVE_POOL`
- `STRATEGY_ARTIFACT_FILE`: local fallback artifact path
- `STRATEGY_ARTIFACT_MAX_AGE_DAYS`: freshness window for upstream `as_of_date`
- `STRATEGY_ARTIFACT_ACCEPTABLE_MODES`: comma-separated accepted upstream modes
- `STRATEGY_ARTIFACT_EXPECTED_SIZE`: expected live-pool size
- `STRATEGY_ARTIFACT_ALLOW_NEW_ENTRIES_ON_DEGRADED`: explicit degraded-entry override

## Runtime Expectations By Failure Type

### Upstream stale or malformed
Expand Down
2 changes: 1 addition & 1 deletion requirements-lock.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@v0.7.12
crypto-strategies @ git+https://github.com/QuantStrategyLab/CryptoStrategies.git@v0.4.3
crypto-strategies @ git+https://github.com/QuantStrategyLab/CryptoStrategies.git@v0.4.4
python-binance==1.0.36
pandas==3.0.2
numpy==2.4.4
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@v0.7.12
crypto-strategies @ git+https://github.com/QuantStrategyLab/CryptoStrategies.git@v0.4.3
crypto-strategies @ git+https://github.com/QuantStrategyLab/CryptoStrategies.git@v0.4.4
python-binance
pandas
numpy
Expand Down
11 changes: 10 additions & 1 deletion runtime_config_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from notify_i18n_support import build_strategy_display_name, build_translator, get_notify_lang
from runtime_support import ExecutionRuntime
from strategy_artifact_support import get_strategy_artifact_env
from strategy_registry import (
BINANCE_PLATFORM,
resolve_strategy_definition,
Expand All @@ -28,6 +29,13 @@ def get_env_bool(name: str, default: bool = False) -> bool:
return str(value).strip().lower() in {"1", "true", "yes", "y", "on"}


def get_env_bool_alias(name: str, legacy_name: str, default: bool = False) -> bool:
value = get_strategy_artifact_env(name, legacy_name)
if not value:
return bool(default)
return str(value).strip().lower() in {"1", "true", "yes", "y", "on"}


def get_env_csv(name: str, default_values: list[str] | tuple[str, ...]) -> list[str]:
raw = os.getenv(name)
if raw is None or not str(raw).strip():
Expand Down Expand Up @@ -61,7 +69,8 @@ def load_cycle_execution_settings() -> CycleExecutionSettings:
)
return CycleExecutionSettings(
btc_status_report_interval_hours=max(1, min(24, get_env_int("BTC_STATUS_REPORT_INTERVAL_HOURS", 24))),
allow_new_trend_entries_on_degraded=get_env_bool(
allow_new_trend_entries_on_degraded=get_env_bool_alias(
"STRATEGY_ARTIFACT_ALLOW_NEW_ENTRIES_ON_DEGRADED",
"TREND_POOL_ALLOW_NEW_ENTRIES_ON_DEGRADED",
False,
),
Expand Down
16 changes: 13 additions & 3 deletions scripts/print_strategy_switch_env_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ def build_switch_plan(profile: str) -> dict[str, object]:
optional_env = [
"NOTIFY_LANG",
"BTC_STATUS_REPORT_INTERVAL_HOURS",
"STRATEGY_ARTIFACT_FILE",
"STRATEGY_ARTIFACT_MANIFEST_FILE",
"STRATEGY_ARTIFACT_FIRESTORE_COLLECTION",
"STRATEGY_ARTIFACT_FIRESTORE_DOCUMENT",
"STRATEGY_ARTIFACT_MAX_AGE_DAYS",
"STRATEGY_ARTIFACT_ACCEPTABLE_MODES",
"STRATEGY_ARTIFACT_EXPECTED_SIZE",
"STRATEGY_ARTIFACT_ALLOW_NEW_ENTRIES_ON_DEGRADED",
"TREND_POOL_FILE",
"TREND_POOL_FIRESTORE_COLLECTION",
"TREND_POOL_FIRESTORE_DOCUMENT",
Expand All @@ -51,7 +59,8 @@ def build_switch_plan(profile: str) -> dict[str, object]:
"TREND_POOL_ALLOW_NEW_ENTRIES_ON_DEGRADED",
]
notes = [
"Binance runtime has no broker-side profile-specific snapshot env today; switching is mainly STRATEGY_PROFILE plus the shared trend-pool artifact settings.",
"Binance runtime resolves strategy artifacts through STRATEGY_ARTIFACT_* settings; TREND_POOL_* remains accepted as a compatibility alias for crypto_leader_rotation.",
"Switching is mainly STRATEGY_PROFILE plus the shared strategy artifact settings.",
"Keep exchange credentials and Telegram settings stable across strategy switches.",
]

Expand All @@ -68,9 +77,10 @@ def build_switch_plan(profile: str) -> dict[str, object]:
"optional_env": optional_env,
"remove_if_present": [],
"hints": {
"trend_pool_default_firestore_collection": "strategy",
"trend_pool_default_firestore_document": "CRYPTO_LEADER_ROTATION_LIVE_POOL",
"strategy_artifact_default_firestore_collection": "strategy",
"strategy_artifact_default_firestore_document": "CRYPTO_LEADER_ROTATION_LIVE_POOL",
"default_local_artifact": str(ROOT / "artifacts" / "live_pool_legacy.json"),
"default_local_artifact_manifest": str(ROOT / "artifacts" / "artifact_manifest.json"),
},
"notes": notes,
}
Expand Down
Loading