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
6 changes: 3 additions & 3 deletions .github/workflows/monthly_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
PRODUCTION_PROFILE: binance_only_core_major_monthly
DOWNLOAD_TOP_LIQUID: ${{ vars.DOWNLOAD_TOP_LIQUID || '90' }}
FIRESTORE_COLLECTION: ${{ vars.FIRESTORE_COLLECTION || 'strategy' }}
FIRESTORE_DOCUMENT: ${{ vars.FIRESTORE_DOCUMENT || 'CRYPTO_LEADER_ROTATION_LIVE_POOL' }}
FIRESTORE_DOCUMENT: ${{ vars.FIRESTORE_DOCUMENT || 'CRYPTO_LIVE_POOL_ROTATION_LIVE_POOL' }}
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 Preserve the existing Firestore pointer during migration

When the scheduled monthly workflow runs in an environment that has not explicitly set FIRESTORE_DOCUMENT, it will now publish only to strategy/CRYPTO_LIVE_POOL_ROTATION_LIVE_POOL; the previous v1 contract documented strategy/CRYPTO_LEADER_ROTATION_LIVE_POOL as the compatibility pointer for downstream readers. Without a dual-write, alias, or staged migration, those existing readers will keep seeing the old document and stop receiving monthly pool updates after this workflow publishes.

Useful? React with 👍 / 👎.

GCP_PROJECT_ID: ${{ vars.GCP_PROJECT_ID }}
GCS_BUCKET: ${{ vars.GCS_BUCKET }}
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
Expand Down Expand Up @@ -121,7 +121,7 @@ jobs:
"Authorization": f"Bearer {token}",
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
"User-Agent": "crypto-leader-rotation-monthly-publish",
"User-Agent": "crypto-live-pool-pipelines-monthly-publish",
}
api_base = f"https://api.github.com/repos/{repository}"
label_name = "monthly-review"
Expand Down Expand Up @@ -235,7 +235,7 @@ jobs:
"Accept": "application/vnd.github+json",
"Content-Type": "application/json",
"X-GitHub-Api-Version": "2022-11-28",
"User-Agent": "crypto-leader-rotation-monthly-publish",
"User-Agent": "crypto-live-pool-pipelines-monthly-publish",
},
)
with urllib.request.urlopen(request) as response:
Expand Down
8 changes: 4 additions & 4 deletions config/default.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
project:
name: "crypto-leader-rotation"
name: "crypto-live-pool-pipelines"
timezone: "UTC"

release:
Expand Down Expand Up @@ -269,10 +269,10 @@ publish:
mode: "core_major"
gcp_project_id: null
gcs_bucket: null
gcs_root_prefix: "crypto-leader-rotation"
gcs_root_prefix: "crypto-live-pool-pipelines"
firestore_collection: "strategy"
firestore_document: "CRYPTO_LEADER_ROTATION_LIVE_POOL"
source_project: "crypto-leader-rotation"
firestore_document: "CRYPTO_LIVE_POOL_ROTATION_LIVE_POOL"
source_project: "crypto-live-pool-pipelines"
upload_current_pointer: true

external_data:
Expand Down
68 changes: 34 additions & 34 deletions docs/integration_contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
- 风险提示:涉及实盘、密钥、权限、Cloud Run、交易所或券商 API 的变更,必须先在测试环境或 dry-run 验证;不要只凭示例直接修改生产。
- 英文正文保留更完整的命令、字段名和配置键;如果摘要和正文不一致,以正文中的实际命令和配置为准。
This document defines the production contract exposed by `CryptoLivePoolPipelines` to downstream strategy systems.
The current v1 artifact namespace remains `crypto-leader-rotation` for compatibility with existing published objects and downstream readers.
The current v1 artifact namespace is `crypto-live-pool-pipelines`.

The upstream project publishes a monthly `core_major` live pool and exposes it through:

Expand All @@ -19,7 +19,7 @@ The upstream project publishes a monthly `core_major` live pool and exposes it t

## Authority Boundary

`CryptoLivePoolPipelines` owns monthly live-pool membership, ranking, and order for `crypto_leader_rotation`. Downstream strategy and execution repositories should treat the ordered `symbols` list from `live_pool.json` or `artifact_manifest.json` as canonical.
`CryptoLivePoolPipelines` owns monthly live-pool membership, ranking, and order for `crypto_live_pool_rotation`. Downstream strategy and execution repositories should treat the ordered `symbols` list from `live_pool.json` or `artifact_manifest.json` as canonical.

Downstream systems may apply runtime execution gates, sell rules, top-N selection, sizing, and degraded-source policy after artifact validation. They should not recalculate monthly membership or replace the published order from local indicators. `latest_ranking.csv` and `selection_meta` are upstream evidence and diagnostics; `live_pool.json` plus `artifact_manifest.json` are the stable execution contract.

Expand All @@ -34,10 +34,10 @@ binding to `CryptoLivePoolPipelines` internals.
Required stable fields:

- `manifest_type = strategy_artifact`
- `contract_version = crypto_leader_rotation.live_pool.v1`
- `strategy_profile = crypto_leader_rotation`
- `contract_version = crypto_live_pool_rotation.live_pool.v1`
- `strategy_profile = crypto_live_pool_rotation`
- `artifact_type = live_pool`
- `artifact_name = crypto_leader_rotation_live_pool`
- `artifact_name = crypto_live_pool_rotation_live_pool`
- `as_of_date`
- `snapshot_as_of`
- `version`
Expand All @@ -57,8 +57,8 @@ The `artifacts` mapping includes relative file paths and SHA-256 checksums for:
- `live_pool_legacy`

Downstream platforms should treat this manifest as the strategy artifact
contract and keep legacy `live_pool_legacy.json` parsing as a compatibility
path, not as the only contract shape.
contract. `live_pool_legacy.json` remains a secondary payload shape for scripts
that need a direct symbol mapping.

### `live_pool_legacy.json`

Expand Down Expand Up @@ -86,7 +86,7 @@ Schema:
"NEARUSDT": {"base_asset": "NEAR"},
"LTCUSDT": {"base_asset": "LTC"}
},
"source_project": "crypto-leader-rotation"
"source_project": "crypto-live-pool-pipelines"
}
```

Expand Down Expand Up @@ -119,7 +119,7 @@ This file contains both the ordered list and the symbol mapping:
"NEARUSDT": {"base_asset": "NEAR"},
"LTCUSDT": {"base_asset": "LTC"}
},
"source_project": "crypto-leader-rotation"
"source_project": "crypto-live-pool-pipelines"
}
```

Expand Down Expand Up @@ -152,7 +152,7 @@ Optional additive research extension:
Collection and document defaults:

- collection: `strategy`
- document: `CRYPTO_LEADER_ROTATION_LIVE_POOL`
- document: `CRYPTO_LIVE_POOL_ROTATION_LIVE_POOL`

Payload example:

Expand All @@ -170,18 +170,18 @@ Payload example:
"NEARUSDT": {"base_asset": "NEAR"},
"LTCUSDT": {"base_asset": "LTC"}
},
"storage_prefix": "gs://example-bucket/crypto-leader-rotation/releases/2026-03-13-core_major",
"current_prefix": "gs://example-bucket/crypto-leader-rotation/current",
"live_pool_legacy_uri": "gs://example-bucket/crypto-leader-rotation/current/live_pool_legacy.json",
"live_pool_uri": "gs://example-bucket/crypto-leader-rotation/current/live_pool.json",
"artifact_manifest_uri": "gs://example-bucket/crypto-leader-rotation/current/artifact_manifest.json",
"latest_universe_uri": "gs://example-bucket/crypto-leader-rotation/current/latest_universe.json",
"latest_ranking_uri": "gs://example-bucket/crypto-leader-rotation/current/latest_ranking.csv",
"versioned_live_pool_legacy_uri": "gs://example-bucket/crypto-leader-rotation/releases/2026-03-13-core_major/live_pool_legacy.json",
"versioned_artifact_manifest_uri": "gs://example-bucket/crypto-leader-rotation/releases/2026-03-13-core_major/artifact_manifest.json",
"artifact_contract_version": "crypto_leader_rotation.live_pool.v1",
"storage_prefix": "gs://example-bucket/crypto-live-pool-pipelines/releases/2026-03-13-core_major",
"current_prefix": "gs://example-bucket/crypto-live-pool-pipelines/current",
"live_pool_legacy_uri": "gs://example-bucket/crypto-live-pool-pipelines/current/live_pool_legacy.json",
"live_pool_uri": "gs://example-bucket/crypto-live-pool-pipelines/current/live_pool.json",
"artifact_manifest_uri": "gs://example-bucket/crypto-live-pool-pipelines/current/artifact_manifest.json",
"latest_universe_uri": "gs://example-bucket/crypto-live-pool-pipelines/current/latest_universe.json",
"latest_ranking_uri": "gs://example-bucket/crypto-live-pool-pipelines/current/latest_ranking.csv",
"versioned_live_pool_legacy_uri": "gs://example-bucket/crypto-live-pool-pipelines/releases/2026-03-13-core_major/live_pool_legacy.json",
"versioned_artifact_manifest_uri": "gs://example-bucket/crypto-live-pool-pipelines/releases/2026-03-13-core_major/artifact_manifest.json",
"artifact_contract_version": "crypto_live_pool_rotation.live_pool.v1",
"generated_at": "2026-03-13T13:00:00+00:00",
"source_project": "crypto-leader-rotation"
"source_project": "crypto-live-pool-pipelines"
}
```

Expand All @@ -199,21 +199,21 @@ Stable vs additive fields:
Versioned release objects:

```text
gs://<bucket>/crypto-leader-rotation/releases/<YYYY-MM-DD-mode>/latest_universe.json
gs://<bucket>/crypto-leader-rotation/releases/<YYYY-MM-DD-mode>/latest_ranking.csv
gs://<bucket>/crypto-leader-rotation/releases/<YYYY-MM-DD-mode>/live_pool.json
gs://<bucket>/crypto-leader-rotation/releases/<YYYY-MM-DD-mode>/live_pool_legacy.json
gs://<bucket>/crypto-leader-rotation/releases/<YYYY-MM-DD-mode>/artifact_manifest.json
gs://<bucket>/crypto-live-pool-pipelines/releases/<YYYY-MM-DD-mode>/latest_universe.json
gs://<bucket>/crypto-live-pool-pipelines/releases/<YYYY-MM-DD-mode>/latest_ranking.csv
gs://<bucket>/crypto-live-pool-pipelines/releases/<YYYY-MM-DD-mode>/live_pool.json
gs://<bucket>/crypto-live-pool-pipelines/releases/<YYYY-MM-DD-mode>/live_pool_legacy.json
gs://<bucket>/crypto-live-pool-pipelines/releases/<YYYY-MM-DD-mode>/artifact_manifest.json
```

Current pointers:

```text
gs://<bucket>/crypto-leader-rotation/current/latest_universe.json
gs://<bucket>/crypto-leader-rotation/current/latest_ranking.csv
gs://<bucket>/crypto-leader-rotation/current/live_pool.json
gs://<bucket>/crypto-leader-rotation/current/live_pool_legacy.json
gs://<bucket>/crypto-leader-rotation/current/artifact_manifest.json
gs://<bucket>/crypto-live-pool-pipelines/current/latest_universe.json
gs://<bucket>/crypto-live-pool-pipelines/current/latest_ranking.csv
gs://<bucket>/crypto-live-pool-pipelines/current/live_pool.json
gs://<bucket>/crypto-live-pool-pipelines/current/live_pool_legacy.json
gs://<bucket>/crypto-live-pool-pipelines/current/artifact_manifest.json
```

## Local Shadow Release History
Expand Down Expand Up @@ -286,7 +286,7 @@ This wrapper refreshes the official baseline artifacts, runs the publish dry-run

## Recommended Downstream Read Priority

1. Read Firestore `strategy/CRYPTO_LEADER_ROTATION_LIVE_POOL`
1. Read Firestore `strategy/CRYPTO_LIVE_POOL_ROTATION_LIVE_POOL`
2. If the latest Firestore payload is invalid or unavailable, prefer the downstream script's last known good upstream payload
3. If explicitly configured, read the synchronized `live_pool_legacy.json` or `live_pool.json`
4. If all upstream-aware layers fail, fall back to the downstream script's static universe as an emergency-only path
Expand All @@ -302,7 +302,7 @@ Freshness semantics:

```python
def load_trend_pool():
payload = try_read_firestore("strategy", "CRYPTO_LEADER_ROTATION_LIVE_POOL")
payload = try_read_firestore("strategy", "CRYPTO_LIVE_POOL_ROTATION_LIVE_POOL")
if is_valid_and_fresh(payload):
return payload["symbol_map"], {"source": "fresh_upstream"}

Expand All @@ -321,7 +321,7 @@ def load_trend_pool():

Preferred rollback:

1. choose the previous version under `gs://<bucket>/crypto-leader-rotation/releases/`
1. choose the previous version under `gs://<bucket>/crypto-live-pool-pipelines/releases/`
2. copy its four artifacts back onto the `current/` prefix
3. update the Firestore summary document so `version`, `as_of_date`, and URIs point to that release

Expand Down
15 changes: 7 additions & 8 deletions docs/operator_runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,12 @@ Primary production outputs:

Primary publish targets:

- GCS current pointers under `crypto-leader-rotation/current`
- GCS versioned release objects under `crypto-leader-rotation/releases/<version>`
- Firestore `strategy/CRYPTO_LEADER_ROTATION_LIVE_POOL`
- GCS current pointers under `crypto-live-pool-pipelines/current`
- GCS versioned release objects under `crypto-live-pool-pipelines/releases/<version>`
- Firestore `strategy/CRYPTO_LIVE_POOL_ROTATION_LIVE_POOL`

The GCS prefix, Firestore document, and `source_project` value intentionally
remain in the `crypto-leader-rotation` v1 artifact namespace for downstream
compatibility.
The GCS prefix, Firestore document, and `source_project` value use the
`crypto-live-pool-pipelines` v1 artifact namespace.

This repository owns the monthly live-pool membership, ranking, and published
symbol order. Downstream runtime repositories should consume the validated
Expand Down Expand Up @@ -221,7 +220,7 @@ Use rollback only when the newest publish is clearly bad or malformed.
1. Identify the last known good version from:

- Firestore document history
- GCS `crypto-leader-rotation/releases/<version>/`
- GCS `crypto-live-pool-pipelines/releases/<version>/`
- the last good `data/output/release_manifest.json`

2. Restore the five canonical artifacts from that version into `data/output/`:
Expand Down Expand Up @@ -256,6 +255,6 @@ Rollback note:
- Confirm `release_status_summary.json` reports `status=ok` for the published month.
- Confirm `data/output/monthly_report_bundle/job_summary.md` matches the released month and includes the expected bundle file list.
- Confirm the Actions run uploaded a `monthly-report-<as_of_date>` artifact for download.
- Confirm Firestore `strategy/CRYPTO_LEADER_ROTATION_LIVE_POOL` contains the expected `version`, `mode`, `symbols`, and `source_project`.
- Confirm Firestore `strategy/CRYPTO_LIVE_POOL_ROTATION_LIVE_POOL` contains the expected `version`, `mode`, `symbols`, and `source_project`.
- Confirm GCS current pointers and versioned objects exist for the same version.
- Confirm downstream consumers are reading the new version without falling back to degraded sources.
2 changes: 1 addition & 1 deletion scripts/build_live_pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def main() -> None:
config["paths"].output_dir,
expected_mode=result["universe_mode"],
expected_source_project=str(
config.get("publish", {}).get("source_project", config.get("project", {}).get("name", "crypto-leader-rotation"))
config.get("publish", {}).get("source_project", config.get("project", {}).get("name", "crypto-live-pool-pipelines"))
),
expected_pool_size=int(config["export"]["live_pool_size"]),
max_age_days=args.contract_max_age_days,
Expand Down
2 changes: 1 addition & 1 deletion scripts/run_research_backtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@


def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Run the full crypto leader rotation research pipeline.")
parser = argparse.ArgumentParser(description="Run the full crypto live pool rotation research pipeline.")
parser.add_argument("--config", default="config/default.yaml", help="Path to the YAML config file.")
parser.add_argument("--universe-mode", default=None, help="Optional universe mode override, e.g. broad_liquid.")
return parser.parse_args()
Expand Down
2 changes: 1 addition & 1 deletion scripts/validate_release_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def parse_args() -> argparse.Namespace:
)
parser.add_argument("--output-dir", default="data/output", help="Directory containing release artifacts.")
parser.add_argument("--mode", default=None, help="Optional expected mode, for example core_major.")
parser.add_argument("--source-project", default="crypto-leader-rotation", help="Expected source_project value.")
parser.add_argument("--source-project", default="crypto-live-pool-pipelines", help="Expected source_project value.")
parser.add_argument("--expected-pool-size", type=int, default=None, help="Optional expected live pool size.")
parser.add_argument(
"--max-age-days",
Expand Down
2 changes: 1 addition & 1 deletion src/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Crypto leader rotation research and live pool package."""
"""Crypto live pool rotation research and live pool package."""

__all__ = ["__version__"]
__version__ = "0.1.0"
Expand Down
12 changes: 6 additions & 6 deletions src/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
from .utils import date_to_str, write_json


DEFAULT_STRATEGY_PROFILE = "crypto_leader_rotation"
DEFAULT_STRATEGY_PROFILE = "crypto_live_pool_rotation"
DEFAULT_ARTIFACT_TYPE = "live_pool"
DEFAULT_ARTIFACT_CONTRACT_VERSION = "crypto_leader_rotation.live_pool.v1"
DEFAULT_ARTIFACT_CONTRACT_VERSION = "crypto_live_pool_rotation.live_pool.v1"


def _sha256_file(path: Path) -> str:
Expand Down Expand Up @@ -75,7 +75,7 @@ def build_live_pool_payload(
as_of_date: pd.Timestamp,
pool_size: int,
mode: str = "core_major",
source_project: str = "crypto-leader-rotation",
source_project: str = "crypto-live-pool-pipelines",
selection_meta_fields: list[str] | None = None,
) -> tuple[dict[str, Any], dict[str, Any]]:
"""Build additive live-pool payloads without performing I/O."""
Expand Down Expand Up @@ -137,7 +137,7 @@ def export_live_pool(
as_of_date: pd.Timestamp,
pool_size: int,
mode: str = "core_major",
source_project: str = "crypto-leader-rotation",
source_project: str = "crypto-live-pool-pipelines",
selection_meta_fields: list[str] | None = None,
save_legacy: bool = True,
) -> dict[str, Any]:
Expand Down Expand Up @@ -165,7 +165,7 @@ def build_strategy_artifact_manifest(
strategy_profile: str = DEFAULT_STRATEGY_PROFILE,
artifact_type: str = DEFAULT_ARTIFACT_TYPE,
contract_version: str = DEFAULT_ARTIFACT_CONTRACT_VERSION,
source_project: str = "crypto-leader-rotation",
source_project: str = "crypto-live-pool-pipelines",
generated_at: Any | None = None,
) -> dict[str, Any]:
"""Build the profile-aware artifact manifest consumed by downstream runtimes."""
Expand Down Expand Up @@ -231,7 +231,7 @@ def export_strategy_artifact_manifest(
strategy_profile: str = DEFAULT_STRATEGY_PROFILE,
artifact_type: str = DEFAULT_ARTIFACT_TYPE,
contract_version: str = DEFAULT_ARTIFACT_CONTRACT_VERSION,
source_project: str = "crypto-leader-rotation",
source_project: str = "crypto-live-pool-pipelines",
) -> dict[str, Any]:
manifest = build_strategy_artifact_manifest(
output_dir=output_dir,
Expand Down
2 changes: 1 addition & 1 deletion src/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ def build_live_pool_outputs(
source_project = str(
config.get("publish", {}).get(
"source_project",
config.get("project", {}).get("name", "crypto-leader-rotation"),
config.get("project", {}).get("name", "crypto-live-pool-pipelines"),
)
)
live_payload = export_live_pool(
Expand Down
6 changes: 3 additions & 3 deletions src/publish.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def resolve_publish_settings(
mode=str(effective_mode),
gcp_project_id=gcp_project_id or os.getenv("GCP_PROJECT_ID") or publish_cfg.get("gcp_project_id"),
gcs_bucket=gcs_bucket or os.getenv("GCS_BUCKET") or publish_cfg.get("gcs_bucket"),
gcs_root_prefix=str(publish_cfg.get("gcs_root_prefix", "crypto-leader-rotation")).strip("/"),
gcs_root_prefix=str(publish_cfg.get("gcs_root_prefix", "crypto-live-pool-pipelines")).strip("/"),
firestore_collection=(
firestore_collection
or os.getenv("FIRESTORE_COLLECTION")
Expand All @@ -98,9 +98,9 @@ def resolve_publish_settings(
firestore_document=(
firestore_document
or os.getenv("FIRESTORE_DOCUMENT")
or publish_cfg.get("firestore_document", "CRYPTO_LEADER_ROTATION_LIVE_POOL")
or publish_cfg.get("firestore_document", "CRYPTO_LIVE_POOL_ROTATION_LIVE_POOL")
),
source_project=str(publish_cfg.get("source_project", config.get("project", {}).get("name", "crypto-leader-rotation"))),
source_project=str(publish_cfg.get("source_project", config.get("project", {}).get("name", "crypto-live-pool-pipelines"))),
upload_current_pointer=parse_bool(
os.getenv("UPLOAD_CURRENT_POINTER"),
publish_cfg.get("upload_current_pointer", True),
Expand Down
6 changes: 3 additions & 3 deletions src/release_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"live_pool": "live_pool.json",
"live_pool_legacy": "live_pool_legacy.json",
}
EXPECTED_ARTIFACT_CONTRACT_VERSION = "crypto_leader_rotation.live_pool.v1"
EXPECTED_ARTIFACT_CONTRACT_VERSION = "crypto_live_pool_rotation.live_pool.v1"


def _sha256_file(path: Path) -> str:
Expand Down Expand Up @@ -512,8 +512,8 @@ def validate_release_outputs(
errors.append(
f"artifact_manifest.json contract_version must be {EXPECTED_ARTIFACT_CONTRACT_VERSION}"
)
if str(artifact_manifest.get("strategy_profile", "")).strip() != "crypto_leader_rotation":
errors.append("artifact_manifest.json strategy_profile must be crypto_leader_rotation")
if str(artifact_manifest.get("strategy_profile", "")).strip() != "crypto_live_pool_rotation":
errors.append("artifact_manifest.json strategy_profile must be crypto_live_pool_rotation")
if str(artifact_manifest.get("artifact_type", "")).strip() != "live_pool":
errors.append("artifact_manifest.json artifact_type must be live_pool")
if str(artifact_manifest.get("primary_artifact", "")).strip() != "live_pool":
Expand Down
Loading