Files
quant-os/runbooks/PLATFORM_DEPLOYMENT.md

555 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Quant OS platform deployment and verification runbook
从仓库中的 `quant-os` 项目根目录运行命令。本文没有任何命令会启用真实订单。
Quant OS 是项目名;`quant60` 是第一版 A 股 Baseline 内核和 CLI namespace。
当前结论是 `NOT_BASELINE_60`。最近一次完整套件的精确计数以
`gate_scorecard.json` 和 CI 运行记录为准,测试通过只属于本地/fixture
证据。CPython 3.12 + `pyqlib==0.9.7` 的 native momentum 和
Alpha158/LightGBM/Recorder fixture 已真实跑通;JoinQuant、QMT 与 broker
中,JoinQuant 已有一次 2024 全年真实 hosted smokeQMT 与 broker 尚无
真实运行证据;聚宽仍缺完整导出与同输入 peer。
## 0. One-command local verification
要求 Python ≥3.10。核心路径无第三方运行依赖:
```bash
make local
```
等价入口:
```bash
bash scripts/validate_all.sh
```
流程包括:
1. standard-library unit/contract suite
2. JoinQuant/QMT hosted bundle
3. synthetic execution smoke + exact manifest verifier
4. synthetic research-to-target + research manifest verifier
5. JoinQuant/QMT mock parity export + verifier
6. capability probe
7. Colab notebook syntax check + local cell execution。
`make local` 成功是本地代码证据,不是平台或 Gate 证据。
## 1. Build hosted single-file artifacts
JoinQuant 和 QMT built-in 不能假设已安装本地 `quant60` 包。生成内联
Python 3.6-compatible portable core 的单文件:
```bash
python3 tools/bundle_platforms.py
```
输出:
```text
dist/joinquant_strategy.py
dist/qmt_builtin_strategy.py
dist/bundle_manifest.json
```
manifest 把两个 bundle 绑定到相同 `portable_core.py` SHA-256。QMT bundle
另做 legacy source compatibility 检查。
静态 capability
```bash
PYTHONPATH=src:. python3 tools/capability_probe.py
```
只有在明确允许导入 proprietary/optional modules 的环境中才用:
```bash
PYTHONPATH=src:. python3 tools/capability_probe.py --deep
```
probe 不执行平台回测,也不发送 broker mutation。
## 2. JQData authorized snapshot to local PIT backtest
### Prerequisites
- 用户授权的 JQData 账号和对应数据权益;
- provider licence 允许的本地/Colab 保存方式;
- CPython 3.12 独立环境。
凭据仅由 environment 或无回显 prompt 注入,不能写入 Git、OB、notebook
source/output 或 snapshot。
```bash
python3.12 -m venv .venv-jqdata
source .venv-jqdata/bin/activate
python -m pip install -r requirements/jqdata.txt
```
下载中证 500 示例:
```bash
PYTHONPATH=src:. python tools/jqdata_snapshot.py \
--index 000905.XSHG \
--start 2024-01-02 \
--end 2024-12-31 \
--output data/snapshots/csi500-2024
```
adapter 使用 `get_bars` 保存未复权 OHLCV/money、factor、`pre_close`
`high_limit``low_limit``paused`,通过 `get_extras("is_st", ...)`
保存 PIT ST 状态,并逐交易日获取历史指数成员。`skip_paused=False` 保留
停牌日。请求的 `end` 必须早于 Asia/Shanghai 的 retrieval date,确保日线在
官方 24:00 完成后才被标为 `T_CLOSE_COMPLETE`;字段、日期、ST 或成员覆盖
不完整即 fail closed。manifest 记录 query、provider version、retrieval
time、内容 hash 与 `data_version`,不记录 credential。
显式运行 semantic verifier
```bash
PYTHONPATH=src:. python -c \
"from quant60.data_snapshot import verify_data_snapshot; verify_data_snapshot('data/snapshots/csi500-2024/manifest.json')"
```
verifier 要求:
- 无 incomplete marker
- 目录是精确 artifact set
- 文件与 canonical row hash 正确;
- bar/membership 可以重建为合法领域对象;
- quality summary、query semantic 和 `data_version` 可重算。
- `is_st` 是严格布尔值并覆盖所有 member-date;缺失不能当作非 ST。
运行动态 PIT 成分本地事件回测并验证 release artifacts
```bash
PYTHONPATH=src:. python -m quant60 snapshot-backtest \
--snapshot data/snapshots/csi500-2024/manifest.json \
--config configs/baseline.json \
--output artifacts/csi500-2024-backtest
PYTHONPATH=src:. python -m quant60 verify-manifest \
artifacts/csi500-2024-backtest/manifest.json
```
生成单日 Signal/Target/Order Delta
```bash
PYTHONPATH=src:. python -m quant60 snapshot-decision \
--snapshot data/snapshots/csi500-2024/manifest.json \
--config configs/baseline.json \
--as-of 2024-12-31 \
--equity 1000000 \
--output artifacts/csi500-2024-decision
PYTHONPATH=src:. python -m quant60 verify-snapshot-decision \
artifacts/csi500-2024-decision/manifest.json
```
生产账户输入应把 `--equity` 替换成 `--broker-snapshot`。broker snapshot
必须满足 schema、日期/新鲜度、持仓/可卖量和 no-unreconciled-open-order
要求;否则 fail closed。
这个 backtest 用 raw price 执行,以每个 decision `as_of` 的 raw close +
factor 构造 point-in-time front-adjusted momentum;使用 provider daily
paused/limits/pre-close/is_st、prior-session volume、T+1、百股单位和显式
费用。ST 不进入新目标;已持有 ST 只允许卖出,停牌持仓冻结。
它仍是本地 fill model,不是 JoinQuant/QMT 真实成交证据。
## 3. Colab
打开 `notebooks/quant_os_colab.ipynb`,默认执行全部 cell。默认流程 clone
代码后运行测试、bundle、两条 synthetic pipeline 和 verifier,不需要账号。
可选:
- `RUN_JQDATA=True`:无回显获取 credential,生成 snapshot、PIT backtest
和 decision
- `RUN_QLIB=True`:仅在 CPython 3.12 安装固定 Qlib stack 并跑 native
fixture。
禁止把 password/token/credential clone URL 写进 cell。不要保存带 secret、
授权数据或账户信息的 notebook output;完成后清除 runtime。
本地验证 notebook
```bash
python3 tools/run_colab_notebook.py notebooks/quant_os_colab.ipynb
python3 tools/run_colab_notebook.py notebooks/quant_os_colab.ipynb --execute
```
## 4. JoinQuant hosted backtest
### Prerequisites
- 用户已登录 JoinQuant
- project/data entitlement
- 已记录 backtest dates、capital、benchmark 和预期 evidence path。
步骤:
1. 运行 `python3 tools/bundle_platforms.py`
2. 新建 JoinQuant 策略;
3.`dist/joinquant_strategy.py` 完整上传/粘贴;
4. 保留 `set_option("avoid_future_data", True)`
`set_option("use_real_price", True)`
5. 先选 backtest,不直接提升 simulation
6. 记录 runtime/data version、dates、capital、benchmark、
fee/slippage 和 bundle manifest hash
7. 导出 `g.quant60_last_plan`、orders/fills、logs 和完整结果。
canonical weekly clock 是 ISO 周第一实际交易日完整 close,紧接着下一实际
交易日 open。wrapper 以 daily-open state machine 保存 close token,因此
单交易日长假周会跨到下一周执行。它以 `context.previous_date` 查询 PIT
指数成员和 `get_extras(..., "is_st")`,用批量
`history(..., fq="pre")` 读取已完成日线;卖单先行,以 `order_target`
提交 `current + executable delta`,并在提交前刷新 current data gate。
手续费、滑点和 10% 最大成交量占比均在策略内显式设置。
科创板订单使用当日价格上下限作为 `LimitOrderStyle` 最坏价保护;共享核心
不会生成不足 200 股的普通申报,并保留不足 200 股余额一次性卖清的例外。
2026-07-26 的已完成参考 run 见
[`JOINQUANT_HOSTED_EVIDENCE_2026-07-26.md`](../docs/JOINQUANT_HOSTED_EVIDENCE_2026-07-26.md)。
它可以用于验证上传与运行步骤,但不能替代步骤 7 的完整导出。
JoinQuant 拥有 hosted fill semantics。要做 G9,必须再保存和本地可比较的
close arrays 或 hash;“平台显示回测成功”本身不够。
rollback:停止 hosted simulation,保留失败 run/export,重新部署上一个已
review 且 manifest hash 已知的 bundle。不要直接编辑 generated file;修改
wrapper/core 后重新测试和 bundle。
## 5. QMT built-in Python backtest
### Prerequisites
- 券商授权的 QMT research terminal 和历史数据;
- QMT Python editor/backtest
- 已记录 client/build、broker plugin 和 data version。
步骤:
1. 运行 `python3 tools/bundle_platforms.py`
2. 在 QMT 导入 `dist/qmt_builtin_strategy.py`
3. 选择 `1d`**backtest**
4. 主图/benchmark 选择 `000905.SH`,在 UI 设置 capital/date range
5. 在回测设置中把最大成交量占比设为 **10%**
6. 确认“中证 500”历史板块成员数据以及历史 ST 数据已下载且账号有权限;
7. 用一只已知历史 ST 股票做 `get_his_st_data` 正例探针;
8. 运行并导出结果、日志、UI 配置截图和 `bundle_manifest.json`
QMT built-in 文档环境是 Python 3.6,所以上传 bundle,不安装 Python ≥3.10
本地包。策略状态存放在模块全局 `g`,避免 `ContextInfo` 用户属性在下个
`handlebar` 回滚。bundle 硬 backtest-only;参数、账号或环境变量都不能
开启 live `passorder`
wrapper 用原始毫秒 timetag 查询决策日历史“中证 500”成分,用
`ContextInfo.get_his_st_data` 排除 ST/*ST/PT。接口缺失、返回格式异常或
覆盖不足会 fail closed。QMT 可能用空结果同时表示“无数据”或“无 ST”,所以
正例探针和版本/权限记录是正式证据的硬前置。
## 6. qmttools native-Python backtest
### Prerequisites
- Windows 上由券商/QMT 分发且匹配 Python 的 `xtquant`
- 已登录 QMT terminal
- driver/universe 历史数据已下载且有 entitlement。
```powershell
$env:PYTHONPATH = "src;."
python tools/bundle_platforms.py
python -m platforms.qmt_research_runner `
dist/qmt_builtin_strategy.py `
--stock-code 000905.SH `
--start 20240102 `
--end 20241231 `
--period 1d `
--asset 1000000 `
--account-id test
```
runner 固定 `trade_mode=backtest``quote_mode=history`,从 baseline
配置固定 benchmark `000905.SH` 与 10% `max_vol_rate`,并在
`run_strategy_file` 前做只读 one-bar preflight。真实证据禁止
`--skip-data-preflight`
slippage、volume participation、stamp duty、commission 和 minimum
commission 来自 `configs/baseline.json` 并绑定 hash。QMT 没有独立 transfer
fee 参数,所以 transfer fee 折入 open/close commissionminimum commission
和 fill behavior 仍是必须单列的 engine difference。
当前 CLI 只输出摘要,真实 evidence 还必须在授权环境保存完整平台结果。
## 7. XtTrader read-only preflight and shadow boundary
### Prerequisites
- logged-in MiniQMT/QMT 与 broker permission
- 本地 `QMT_USERDATA_PATH``QMT_SESSION_ID``QMT_ACCOUNT_ID`
- 本地 `QMT_ACCOUNT_HASH_KEY_FILE`(文件内容是独立 secret);
- broker-distributed `xtquant`
- 不与其他策略冲突的 numeric session ID。
read-only preflight
```powershell
$env:PYTHONPATH = "src;."
@'
import os
from adapters.xttrader_live import XtTraderLiveAdapter
adapter = XtTraderLiveAdapter.from_xtquant(
userdata_path=os.environ["QMT_USERDATA_PATH"],
session_id=int(os.environ["QMT_SESSION_ID"]),
account_id=os.environ["QMT_ACCOUNT_ID"],
)
try:
adapter.connect()
asset = adapter.query_asset()
asset.pop("account_id", None)
print({
"state": adapter.state,
"asset": asset,
"position_count": len(adapter.query_positions()),
"order_count": len(adapter.query_orders()),
"trade_count": len(adapter.query_trades()),
})
finally:
adapter.close()
'@ | python -
```
以上原始查询只用于诊断;正式只读 shadow 使用下面的账户绑定流程。工具始终
`allow_live_orders=False` 创建 adapter,代码中没有报单或撤单入口。
先设置本机 secret/path
```powershell
$env:PYTHONPATH = "src;."
$env:QMT_USERDATA_PATH = "C:\local\qmt\userdata_mini"
$env:QMT_ACCOUNT_ID = "<local-account-id>"
$env:QMT_SESSION_ID = "<unique-numeric-session-id>"
$env:QMT_ACCOUNT_HASH_KEY_FILE = "C:\local\quant-os\account-hash.key"
$env:DECISION_DATE = "<fresh-completed-trading-date>"
```
首次只能在 decision 所声明的唯一下一交易日
Asia/Shanghai `[09:00,09:30)` 盘前窗口内运行:
```powershell
python tools/qmt_shadow_plan.py plan `
--decision artifacts\latest-decision\manifest.json `
--output artifacts\qmt-shadow-bootstrap
```
若账户/资产/持仓/委托/成交查询均可信,首次仍会以
`DECISION_NOT_ACCOUNT_BOUND` 返回预期退出码 2,同时写出:
```text
broker_observation.json
broker_snapshot.json
manifest.json
shadow_plan.json
```
`broker_snapshot.json` 若为 JSON `null`,表示查询事实不可信,禁止继续。
否则用该 snapshot 在**同一个 `$env:DECISION_DATE`** 重新生成账户绑定决策:
```powershell
python -m quant60 snapshot-decision `
--snapshot data\snapshots\latest\manifest.json `
--config configs\baseline.json `
--as-of $env:DECISION_DATE `
--broker-snapshot artifacts\qmt-shadow-bootstrap\broker_snapshot.json `
--output artifacts\qmt-bound-decision
python tools/qmt_shadow_plan.py plan `
--decision artifacts\qmt-bound-decision\manifest.json `
--output artifacts\qmt-shadow
python tools/qmt_shadow_plan.py verify `
--manifest artifacts\qmt-shadow\manifest.json `
--decision artifacts\qmt-bound-decision\manifest.json
```
planner 会检查账户 HMAC、query duration、连接/live-mode 二次状态、账户归属、
资产恒等式、持仓市值、委托/成交唯一性与数量恒等式;所有观察事实会进入脱敏
`broker_observation.json` 并被 manifest/verifier 绑定。任一 blocker 会把
proposed delta 强制为零。
`verify``--decision` 是必需参数,必须指向生成 shadow 时的原始、未改写
decision manifest`QMT_ACCOUNT_HASH_KEY_FILE` 必须指向 plan 时使用的既有
至少 32-byte key。verify 不会创建新 key,缺失、错误或已轮换 key 都失败。
query duration 的 canonical 上限是 10 秒,资产/持仓市值
`cash + market_value = total_asset` 的绝对容差上限是 1 元;
`--max-query-duration-seconds``--market-value-tolerance` 只能收紧,
不能放宽。不要为“让本次通过”改大参数。
verifier 会用原始 decision 和 read-only observation 做 semantic replay
独立重建 blocker、status、snapshot-valid、完整 snapshot、cash/portfolio、
原始目标绑定、board-lot/sellable feasible delta 和 proposed delta,然后
exact compare。必须把 blockers 被删/新增、target quantity/weight/lot 被改、
before/after orders 不一致、snapshot 为 `null` 或 observation/plan 派生字段
不同步视为 artifact 验证失败,而不是人工豁免。READY 必须有 replay 可重建的
非空 broker snapshot。
manifest 中的 `QMT_SHADOW_EVIDENCE_V1` HMAC envelope 还绑定完整
plan/observation/snapshot 的 canonical 内容与三份磁盘发布文件 byte
SHA-256、原始 decision、planner/engine/`tools/qmt_shadow_plan.py` source 和
canonical policy。四个发布 JSON(含 manifest)必须保持唯一 writer bytes
不要 minify、重排 key 或手工“美化”。它阻止不知道 key 的人靠重算普通 hash
重新封装证据。它只证明持 key
的 Quant OS 本地发布与后续完整性,**不是券商对 query 原始事实的 attestation**
仍必须完成官方 API 失败语义、终端安全、callback 和真实对账认证。
它还会对比账户绑定 decision 的 `equity` 与当前 `total_asset`。固定容差是
`max(1 元, 0.01% × abs(decision equity))`(包含边界),不可通过 CLI
放宽。超差时出现 `DECISION_EQUITY_DRIFT`;应使用本次可信
`broker_snapshot.json` 重建 decision,禁止继续采用旧绝对目标股数。缺失、
负数或非有限权益同样失败关闭。
这里的 Signal `as_of` 始终是 T 日 15:00 完整收盘。snapshot 会冻结
JQData 两套交易日历 API 一致性校验后的 session 序列;decision、
broker snapshot 和 shadow plan 都保存相同的 `next_trading_session`
`first_executable_window`。broker query 的开始和完成必须都落在唯一下一
session 的 `[09:00,09:30)` 内;周末、同日 15:01、09:30、错误 session
及迟到多日均失败关闭。长假按冻结日历处理,不按 elapsed-day 或 weekday
近似推算。
positions 和 sellable quantity 始终以当前 broker query 为准重新做 target
diff;逐 symbol target quantity/weight/lot size 必须 exact 绑定原始
decision,未完成委托会阻断;这使持仓变化不会复用旧 residual。当前 cash、
market value、total asset 与 position market value 由 replay 独立核算。
但无实时执行价就
不能做可靠购买力检查,所以 `NO_BUYING_POWER_CHECK_WITHOUT_EXECUTION_PRICE`
必须保留,`SHADOW_READY` 仅表示可人工复核,不表示可提交。
HMAC key 在 plan 首次运行时以安全随机数生成;Unix 要求 0600。verify 只读
已有 key。备份时保持访问控制,绝不提交 Git、OB、notebook、日志或聊天。
更换 key 会使旧账户绑定决策失配并使历史 evidence 无法验签。
这些是 local contract,不是 broker certification。当前 adapter
`uncertified / not live-ready`,本文故意没有 live-order command。正式
QMT 账号还要先探测官方 query API 在“失败”与“空列表”时的实际合同,并完成
terminal/xtquant build 记录以及 `XtTrade.traded_amount`
`price × quantity` 的真实容差探针。当前 amount 是被认证的 informational
fact,不参与 target/delta。之后还需脱敏 callback replay、restart recovery、
完整对账和 G10Baseline 60 至少需要 20 trading days shadowProduction 80
当前要求同一冻结候选至少 60 trading days。当前 `.BJ` mutation 不支持。
## 8. Qlib 0.9.7 research
创建隔离环境:
```bash
python3.12 -m venv .venv-qlib312
source .venv-qlib312/bin/activate
python -c "import sys; assert sys.version_info[:2] == (3, 12)"
python -m pip install -r requirements/research-py312.txt
python -c "import qlib; assert qlib.__version__ == '0.9.7'"
```
### Native momentum fixture
```bash
python tools/build_qlib_tiny_fixture.py artifacts/qlib-fixture --days 80
PYTHONPATH=src:. python -m platforms.qlib_runner \
--provider-uri artifacts/qlib-fixture \
--market csi300 \
--benchmark SH000300 \
--start 2024-02-01 \
--end 2024-04-19 \
--lookback 20 \
--topk 1 \
--n-drop 1 \
--rebalance weekly \
--output-json artifacts/qlib-native/result.json
```
该固定运行时已实际得到 24 条 signal,使用
`SimulatorExecutor + TopkDropoutStrategy`。结果 JSON 保存 signal/report
稳定 hash、表行列与时间边界,以及从 report 重算的累计收益、最大回撤、基准
收益、成本和换手,并明确 `investment_value_claim=false`。Qlib 单一
`limit_threshold` 是 A 股逐日板块/ST 限制的近似,所以只参加 L1/L2 和
诊断 L6。
### Alpha158 + LightGBM + Recorder fixture
```bash
python tools/build_qlib_tiny_fixture.py \
artifacts/qlib-fixture-alpha --days 400
PYTHONPATH=src:. python -m platforms.qlib_runner \
--workflow alpha158 \
--provider-uri artifacts/qlib-fixture-alpha \
--market csi300 \
--benchmark SH000300 \
--train-start 2024-04-01 \
--train-end 2024-10-31 \
--valid-start 2024-11-01 \
--valid-end 2025-01-31 \
--test-start 2025-02-03 \
--test-end 2025-05-16 \
--topk 1 \
--n-drop 1 \
--experiment-name quant-os-alpha158-fixture \
--output-json artifacts/qlib-alpha158/result.json
```
本项目已经实际跑通该类 fixture,完成 model fit、SignalRecord、
SigAnaRecord、PortAnaRecord 和 Recorder。runner 使用
`os.environ.setdefault("MLFLOW_ALLOW_FILE_STORE", "true")` 自动确认
local MLflow file store;不需要人工先设置该变量。它还读取
`portfolio_analysis/report_normal_1day.pkl`,保存表级 hash、行列/时间边界、
重算指标和 artifact path。
fixture 只有两只 synthetic 股票和一个 benchmark,所有 performance/IC
都没有投资意义。真实研究必须换成授权、immutable provider,保存 data/model/
Recorder version,并重新做 OOSAlpha158 不是 portable-momentum 的 L2 peer
也不声称 L3/L4 parity。
## 9. Mock parity and real G9
生成/验证 wrapper mock contract
```bash
PYTHONPATH=src:. python tools/export_mock_parity.py \
export --output artifacts/mock-parity/report.json
PYTHONPATH=src:. python tools/export_mock_parity.py \
verify artifacts/mock-parity/report.json
```
报告逐层比较 score、weight、target quantity 和 order delta,并固定 source
hash。其声明必须保持:
```text
evidence_class = mock_contract_only
real_platform_pass = false
gate_credit = []
```
因此 mock parity 不计 G9。Baseline 60 的真实 G9 仍要求真实 JoinQuant/QMT
export、同输入的 L1—L5 difference report 和至少 20 trading days QMT/broker
shadowProduction 80 当前要求同一冻结候选至少 60 trading days。
## 10. Evidence checklist
每个真实平台 run 保存:
- Git commit 或 `DIRTY`
- `bundle_manifest.json`(适用时);
- Python/platform/client/broker plugin version
- config/data/rules/model hash
- dates、capital、universe、adjustment、fee/fill setting
- stdout、platform export 和所有 failure
- evidence class`synthetic``fake``provider-snapshot`
`real-platform``broker`
- compared layer、tolerance 和 reason code
- 数据许可、脱敏与 artifact retention 记录。
真实 JoinQuant smoke 已运行;QMT、broker 以及相同输入跨引擎比较仍未
运行,G1—G10 全部 `not_passed`。完整层级见
[`CROSS_ENGINE_CONSISTENCY.md`](../docs/CROSS_ENGINE_CONSISTENCY.md)
安全规则见 [`SECURITY.md`](../docs/SECURITY.md)。