# 可运行策略包 V1

`strategy_package_v2` 的 JSON 只能描述策略、回测证明和风险边界；它本身不是可执行策略。

若希望零界星图在审核后直接启动策略，必须提交一个完整的 **可运行策略包**。一个包只对应一个 `strategy_id` 和一个确定的源码/模型版本。运行目标永远是 Docker 中的模拟盘信号生成，不包含券商凭据或实盘下单权限。

## 必交目录

```text
my_strategy_bundle/
├── strategy_package.json       # strategy_package_v2 清单
└── runtime/
    ├── strategy.py             # runtime.entrypoint 指向的 Python 文件
    ├── requirements.lock       # 精确版本及 SHA256；无额外依赖时提交空文件
    ├── wheels/                 # requirements.lock 中第三方依赖的离线 wheel，可为空
    └── models/                 # 推理模型、词表、归一化参数；无模型时为空目录
```

允许把整个目录打成一个 ZIP 上传。ZIP 不得包含符号链接、绝对路径、`..` 路径、可执行二进制、秘密文件或超过平台公布大小上限的单个文件。

## 传输与兼容性（V1 固定）

- ZIP 总大小最多 **512MB**，解压后总大小最多 **768MB**，单文件最多 **256MB**，最多 **2,000** 个常规文件。
- ZIP 可以直接以包根目录打包，也可以带且只带一个顶层目录；两种形式都会被规范化为同一个包根。
- `runtime/wheels/` 内只能有 `.whl`；`requirements.lock` 中每一个非注释依赖都必须是 `name==version --hash=sha256:<64位小写hex>`，并有对应离线 wheel。容器绝不会联网安装依赖。
- `runtime/models/` 中的每一个文件都必须在 `runtime.model_files` 中逐一列出并校验哈希；未声明模型、额外模型或哈希不符都会拒绝。
- V1 运行镜像、Python ABI 和 CPU 架构必须在平台发布的运行时镜像清单中明确。提交方不得假设任意 wheel 都兼容；例如 Linux `x86_64` 与 `arm64` wheel 不可混用。

## `strategy_package.json` 的运行时字段

```json
{
  "strategy_kind": "reinforcement_learning",
  "runtime": {
    "type": "reinforcement_learning",
    "bundle_format": "MarketWorldRunnableStrategyBundleV1",
    "entrypoint": "runtime/strategy.py:generate_signals",
    "requirements_lock": "runtime/requirements.lock",
    "wheelhouse": "runtime/wheels",
    "model_files": [
      {"path": "runtime/models/policy.onnx", "sha256": "sha256:<64-lowercase-hex>"}
    ],
    "source_files": [
      {"path": "runtime/strategy.py", "sha256": "sha256:<64-lowercase-hex>"}
    ],
    "rl_assets": {
      "weights": {"path": "runtime/models/policy.onnx", "sha256": "sha256:<64-lowercase-hex>"},
      "normalizer": {"path": "runtime/models/normalizer.json", "sha256": "sha256:<64-lowercase-hex>"},
      "action_mapping": {"path": "runtime/models/action_mapping.json", "sha256": "sha256:<64-lowercase-hex>"},
      "observation": {"path": "runtime/models/observation_contract.json", "sha256": "sha256:<64-lowercase-hex>"},
      "manifest": {"path": "runtime/models/asset_manifest.json", "sha256": "sha256:<64-lowercase-hex>"},
      "random_seed": 42
    },
    "source_bundle_sha256": "sha256:<64-lowercase-hex>",
    "timeout_seconds": 20
  },
  "risk": {
    "paper_only": true,
    "allow_live_trading": false,
    "max_notional_usd": 500000
  }
}
```

`source_bundle_sha256` 必须覆盖 `strategy.py`、同目录 Python 模块、`requirements.lock`、wheel 文件和模型文件。它必须与 `backtest_evidence.commitments.strategy_source_sha256` 相同；否则回测所用代码与拟运行代码不一致，直接拒绝。

## 时间粒度与调度合约（必交）

所有可执行策略必须在包顶层提供 `execution_schedule`。`time_views` 仅决定策略进入哪个账本，**不能**代替数据粒度或运行频率。

```json
{
  "execution_schedule": {
    "schema": "StrategyExecutionScheduleV1",
    "data_granularity": "1d",
    "decision_frequency": "trading_day",
    "calendar": "CN_A_SHARE",
    "timezone": "Asia/Shanghai",
    "as_of_policy": "previous_session_close",
    "decision_at": "09:20",
    "execution_at": "09:30",
    "idempotency_scope": "trading_day"
  }
}
```

- `data_granularity` 只能为 `tick`、`1s`、`1m`、`5m`、`15m`、`1h` 或 `1d`；策略只能使用已收盘的对应粒度数据。
- `decision_frequency` 为 `event`、`interval` 或 `trading_day`。`interval` 还必须填写 60–86400 秒的 `interval_seconds` 和 `idempotency_scope: "interval_slot"`。
- `trading_day` 必须填写交易日历、IANA 时区、`decision_at`、`execution_at`（`HH:MM`）及 `idempotency_scope: "trading_day"`。同一策略、账本和交易日只会调用一次。
- 日线 A 股策略推荐使用 `CN_A_SHARE`、`Asia/Shanghai`、`previous_session_close`；决策时点不得晚于模拟执行时点。

在打包和回测完成后，用随项目发布的工具计算该值，再把同一个值填写到两个字段：

```bash
python scripts/marketworld_strategy_bundle_hash.py --bundle ./my_strategy_bundle
```

## 函数契约

入口函数默认名为 `generate_signals(snapshot)`，也可在 `runtime.entrypoint` 之后用 `:函数名` 指定。它必须：

1. 只读取传入的 `snapshot`；不得读取本机账户、环境变量秘密、网络或宿主文件。
2. 返回 `list[SignalIntentV1]`，或 `{ "signals": list[SignalIntentV1] }`。
3. 不得下单、写文件、启动子进程或访问网络。
4. 在声明的 `timeout_seconds` 内完成；允许范围为 1–30 秒。

容器为无网络、只读根目录、非 root 用户、无 Linux capabilities、PID/CPU/内存受限。`requirements.lock` 仅允许精确版本和 SHA256；自定义依赖必须把所有 wheel 一同放到 `runtime/wheels/`，容器不会联网下载依赖。

`snapshot` 的当前版本为 `marketworld_runtime_snapshot_v1`，至少包含 `time_view`、`rows`、`edges` 和只允许纸面模拟的 `risk`。它只提供截至当前时点的市场候选快照；策略不得把快照当作文件路径、命令或代码执行。烟测返回的每个元素必须是 JSON 对象，并在正式模拟盘前按 `SignalIntentV1` 的 `schema`、标的、方向、风险额度和上限再次校验。

## 审核与启动顺序

```text
完整 ZIP + JSON
  → 文件路径/哈希/依赖预检
  → Docker 冷启动烟测（只生成 SignalIntent）
  → 回测证明与源码哈希一致性检查
  → 私有审核
  → 纸面模拟盘
```

任何一步失败都不会执行宿主机 Python，也不会创建真实订单。只有 `paper_only=true` 的策略可进入最后一步；实盘授权不属于此协议。

预检成功令牌绑定 **规范化后的 `strategy_package.json` 与 ZIP SHA256**，并且有短时有效期。修改 JSON、源码、模型、wheel 或重新打包后，都必须重新预检；正式提交只保存已通过校验的 ZIP 副本，模拟盘不会访问提交者电脑上的路径。

## PPO 特别要求

PPO/RL 包除上述文件外，必须提供推理所需的完整策略权重、特征归一化器、动作映射、观察窗口定义和随机种子。为避免“只上传 JSON 描述”的伪交付，`runtime.source_files` 必须逐个列出并哈希可审计的 `.py` 源码，`runtime.rl_assets` 的五项必须逐一引用 `model_files` 中已声明、哈希相同的真实文件。只提供回测指标、策略源码哈希或训练日志，均不属于可运行提交。

## 发布规范仍需平台确定的项目

下列信息不应由提交者猜测，平台必须在运行时镜像清单/API 中公布后才能接受对应依赖的包：

1. Docker 镜像的不可变 digest、Python 版本和支持的 CPU 架构；
2. 每类运行时的 CPU、内存、临时磁盘和并发额度；
3. `SignalIntentV1` 的完整公开 JSON Schema 与单个 snapshot/单次信号数量上限；
4. 预检令牌的确切绑定字段、有效期，以及审核通过后运行包的保留/撤销周期；
5. 镜像内置库清单。未在清单中的库必须提交带哈希的 wheel。
