Files
quant-os/runbooks/PLATFORM_DEPLOYMENT.md
T

21 KiB
Raw Blame History

Quant OS platform deployment and verification runbook

从仓库中的 quant-os 项目根目录运行命令。本文没有任何命令会启用真实订单。 Quant OS 是项目名;quant60 是第一版 A 股 Baseline 内核和 CLI namespace。

当前结论是 NOT_BASELINE_60。最近一次标准库套件为 211 tests、OK、 3 个可选路径 skip。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。核心路径无第三方运行依赖:

make local

等价入口:

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 的单文件:

python3 tools/bundle_platforms.py

输出:

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

PYTHONPATH=src:. python3 tools/capability_probe.py

只有在明确允许导入 proprietary/optional modules 的环境中才用:

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。

python3.12 -m venv .venv-jqdata
source .venv-jqdata/bin/activate
python -m pip install -r requirements/jqdata.txt

下载中证 500 示例:

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_closehigh_limitlow_limitpaused,通过 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

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

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

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

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。 它可以用于验证上传与运行步骤,但不能替代步骤 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. 选择 1dbacktest
  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。
$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=backtestquote_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_PATHQMT_SESSION_IDQMT_ACCOUNT_ID
  • 本地 QMT_ACCOUNT_HASH_KEY_FILE(文件内容是独立 secret);
  • broker-distributed xtquant
  • 不与其他策略冲突的 numeric session ID。

read-only preflight

$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

$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) 盘前窗口内运行:

python tools/qmt_shadow_plan.py plan `
  --decision artifacts\latest-decision\manifest.json `
  --output artifacts\qmt-shadow-bootstrap

若账户/资产/持仓/委托/成交查询均可信,首次仍会以 DECISION_NOT_ACCOUNT_BOUND 返回预期退出码 2,同时写出:

broker_observation.json
broker_snapshot.json
manifest.json
shadow_plan.json

broker_snapshot.json 若为 JSON null,表示查询事实不可信,禁止继续。 否则用该 snapshot 在同一个 $env:DECISION_DATE 重新生成账户绑定决策:

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 manifestQMT_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_sessionfirst_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_amountprice × quantity 的真实容差探针。当前 amount 是被认证的 informational fact,不参与 target/delta。之后还需脱敏 callback replay、restart recovery、 完整对账、至少 20 trading days shadow 和 G10。当前 .BJ mutation 不支持。

8. Qlib 0.9.7 research

创建隔离环境:

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

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

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

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。其声明必须保持:

evidence_class = mock_contract_only
real_platform_pass = false
gate_credit = []

因此 mock parity 不计 G9。真实 G9 仍要求真实 JoinQuant/QMT export、同输入 的 L1—L5 difference report 和至少 20 trading days QMT/broker shadow。

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 classsyntheticfakeprovider-snapshotreal-platformbroker
  • compared layer、tolerance 和 reason code
  • 数据许可、脱敏与 artifact retention 记录。

真实 JoinQuant smoke 已运行;QMT、broker 以及相同输入跨引擎比较仍未 运行,G1—G10 全部 not_passed。完整层级见 CROSS_ENGINE_CONSISTENCY.md 安全规则见 SECURITY.md