feat: establish Quant OS production-80 architecture
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
name: quant-os-ci
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
deterministic-local:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Validate deterministic Quant OS
|
||||
run: bash scripts/validate_all.sh
|
||||
- name: Reject generated bundle drift
|
||||
run: git diff --exit-code -- dist status/status-data.js
|
||||
- name: Reject tracked credential paths
|
||||
shell: bash
|
||||
run: |
|
||||
if git ls-files \
|
||||
| grep -E '(^|/)(\.env(\.[^/]+)?|secrets|credentials|userdata_mini)(/|$)' \
|
||||
| grep -Ev '(^|/)\.env\.example$'; then
|
||||
echo "credential-like tracked path detected"
|
||||
exit 1
|
||||
fi
|
||||
- name: Reject credential-like tracked content
|
||||
run: python3 tools/check_tracked_secrets.py
|
||||
+7
-5
@@ -14,13 +14,15 @@ htmlcov/
|
||||
venv/
|
||||
|
||||
# Generated evidence and local data
|
||||
artifacts/
|
||||
/artifacts/
|
||||
reports/
|
||||
runs/
|
||||
data/raw/
|
||||
data/canonical/
|
||||
data/snapshots/
|
||||
data/qlib/
|
||||
/build/
|
||||
/data/
|
||||
/data/raw/
|
||||
/data/canonical/
|
||||
/data/snapshots/
|
||||
/data/qlib/
|
||||
mlruns/
|
||||
*.parquet
|
||||
*.duckdb
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This directory is the executable source of truth for Quant OS. `quant60` is
|
||||
its first A-share Baseline kernel/package; the project itself also owns data,
|
||||
research, portfolio, execution, evidence, and platform adapters. Obsidian
|
||||
contains research and status documentation only.
|
||||
This independent repository is the executable source of truth for Quant OS.
|
||||
`quant_os` is the long-lived product namespace. `quant60` is the compatibility
|
||||
namespace for the first A-share Baseline protocol and V1 artifacts; maturity
|
||||
scores belong to profiles, never to package names. Obsidian contains research
|
||||
and status documentation only.
|
||||
|
||||
## Development commands
|
||||
|
||||
Run all commands from this directory:
|
||||
|
||||
```bash
|
||||
make architecture
|
||||
make standard
|
||||
make test
|
||||
make bundle
|
||||
make smoke
|
||||
@@ -50,5 +53,8 @@ A change is complete only when:
|
||||
1. unit/integration tests pass;
|
||||
2. hosted bundles rebuild and pass Python 3.6 syntax checks;
|
||||
3. the deterministic smoke and manifest verifier pass;
|
||||
4. docs state the exact evidence class and remaining external gate;
|
||||
5. no secret or machine-specific absolute path is added to tracked artifacts.
|
||||
4. architecture and maturity profile checks pass;
|
||||
5. docs state the exact evidence class and remaining external gate;
|
||||
6. no secret or machine-specific absolute path is added to executable or public
|
||||
artifacts. Migration/getting-started docs may name the user-confirmed local
|
||||
checkout, but generated evidence must stay repository-relative.
|
||||
|
||||
@@ -102,7 +102,8 @@ Qlib 两次 smoke 使用的是确定性两股票 synthetic fixture,性能没
|
||||
|
||||
- 同一个 TargetPackage 的聚宽完整 package/plan/orders/fills 导出和本地 peer;
|
||||
- 同一个 TargetPackage 的授权 QMT built-in 与 qmttools 回测导出;
|
||||
- 至少 20 个交易日的真实 QMT/broker shadow;
|
||||
- Baseline 60 至少 20 个交易日、Production 80 同一冻结候选至少 60 个交易日
|
||||
的真实 QMT/broker shadow;
|
||||
- 同一 canonical 输入的 L1—L5 差异报告。
|
||||
|
||||
**状态**:部分完成;JoinQuant runtime smoke 已执行,但 strict comparison、
|
||||
@@ -130,7 +131,8 @@ guarded TWAP/POV 和影子证据。
|
||||
- 券商实际状态码映射与脱敏回调;
|
||||
- 实际 QMT query API 的失败/空列表合同探针;
|
||||
- durable scheduler/recovery daemon;
|
||||
- 20 个交易日 shadow、日终零未知账差和故障恢复演练;
|
||||
- Baseline 60 的 20 个交易日 shadow(Production 80 为同一冻结候选 60 日)、
|
||||
日终零未知账差和故障恢复演练;
|
||||
- 券商权限、频率、软件和程序化交易报告确认。
|
||||
|
||||
**状态**:仅有未认证的安全边界;不可视为 live-ready。
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# Quant OS 独立仓迁移记录
|
||||
|
||||
迁移日期:2026-07-30
|
||||
|
||||
## 目标位置
|
||||
|
||||
- 本地:`/Users/zhangbiao/boat-workspace/Code/quant-os`
|
||||
- 远端:`https://git.gomars.fun/boat/quant-os.git`
|
||||
- 公共状态:`https://gomars.fun/quant-os/status/`
|
||||
- 原仓:`https://git.gomars.fun/boat/quants-strategies.git`
|
||||
|
||||
## 历史保留方式
|
||||
|
||||
从原仓已提交 `master` 执行:
|
||||
|
||||
```text
|
||||
git subtree split --prefix=quant-os
|
||||
```
|
||||
|
||||
路径提交映射:
|
||||
|
||||
| 原仓提交 | 独立历史提交 | 内容 |
|
||||
| --- | --- | --- |
|
||||
| `e561efd` | `48c5f64` | A 股 60 分 Baseline 初始实现 |
|
||||
| `f998f4d` | `e5911ca` | Tushare → Qlib 数据桥 |
|
||||
| `692f6ef` | `00efeb7` | 公共状态控制台 |
|
||||
|
||||
SHA 改变是路径提取的正常结果;作者、时间、顺序和文件历史保留。
|
||||
|
||||
原工作树中尚未提交的 TargetPackage、五层链、release reachability 和真实
|
||||
JoinQuant execution smoke 被叠加到独立分支,并形成单独 checkpoint
|
||||
`919c64c`。原 `quants-strategies` 的 cash-layer 脏改没有进入新仓。
|
||||
|
||||
## 本地运行状态
|
||||
|
||||
以下目录被 Git 忽略,不进入远端;这是数据安全边界,不只是迁移便利:
|
||||
|
||||
- `data/`
|
||||
- `artifacts/`
|
||||
- `mlruns/`
|
||||
- `.venv-*`
|
||||
|
||||
迁移时复制前三类并校验清单;只记录文件数、总字节和相对路径/hash,不把
|
||||
数据内容或绝对源路径写入公开文档。旧 `.venv-qlib312` 接近 1 GB,脚本中包含原
|
||||
绝对路径,不能把“目录搬过去”视为有效环境;新仓应按固定 requirements 重建。
|
||||
旧环境在新仓完整验证前不删除。
|
||||
|
||||
清单与复核命令:
|
||||
|
||||
```bash
|
||||
python3 tools/inventory_local_assets.py <old-project-root> \
|
||||
--output /tmp/quant-os-local-assets.json
|
||||
python3 tools/inventory_local_assets.py <new-project-root> \
|
||||
--compare /tmp/quant-os-local-assets.json
|
||||
```
|
||||
|
||||
清单不记录根目录,只记录 `data/`、`artifacts/`、`mlruns/` 的相对路径、字节数
|
||||
和 SHA-256;遇到 symlink 会拒绝继续。
|
||||
|
||||
## 兼容政策
|
||||
|
||||
- `quant-os` / `quant_os` 是新产品入口;
|
||||
- `quant60` CLI/import 和 V1 schemas 保留至少两个发布周期;
|
||||
- `portable-momentum` 只保留 smoke/example 身份;
|
||||
- 旧公共 URL 先保持 302 兼容跳转;稳定观察后再单独升级 308,不能直接 404;
|
||||
- 原仓最终只保留独立项目指针,不复制代码。
|
||||
|
||||
## 完成条件
|
||||
|
||||
1. 独立仓 clean clone 通过 `make local`;
|
||||
2. generated bundle/status 无 drift;
|
||||
3. secret/绝对路径检查通过;
|
||||
4. 新远端 commit 与本地 commit 相同;
|
||||
5. 新公共 URL 返回正确独立仓快照;
|
||||
6. 旧 URL 有兼容跳转;
|
||||
7. 最后才从旧仓移除 `quant-os/` 和旧专属 CI。
|
||||
@@ -4,11 +4,18 @@ SMOKE_DIR := artifacts/local-smoke
|
||||
RESEARCH_DIR := artifacts/research-smoke
|
||||
PARITY_REPORT := artifacts/mock-parity/report.json
|
||||
|
||||
.PHONY: test bundle smoke verify research research-verify parity parity-verify doctor status status-check notebook-check notebook-smoke local
|
||||
.PHONY: test architecture standard bundle smoke verify research research-verify parity parity-verify doctor secret-scan status status-check notebook-check notebook-smoke local
|
||||
|
||||
test:
|
||||
PYTHONPATH=$(PROJECT_PYTHONPATH) $(PYTHON) -m unittest discover -s tests -p '*.py' -q
|
||||
|
||||
architecture:
|
||||
PYTHONPATH=$(PROJECT_PYTHONPATH) $(PYTHON) -m quant_os architecture check
|
||||
|
||||
standard:
|
||||
PYTHONPATH=$(PROJECT_PYTHONPATH) $(PYTHON) -m quant_os standard validate
|
||||
PYTHONPATH=$(PROJECT_PYTHONPATH) $(PYTHON) -m quant_os standard evaluate
|
||||
|
||||
bundle:
|
||||
$(PYTHON) tools/bundle_platforms.py
|
||||
|
||||
@@ -31,8 +38,12 @@ parity-verify:
|
||||
PYTHONPATH=$(PROJECT_PYTHONPATH) $(PYTHON) tools/export_mock_parity.py verify $(PARITY_REPORT)
|
||||
|
||||
doctor:
|
||||
PYTHONPATH=$(PROJECT_PYTHONPATH) $(PYTHON) -m quant_os doctor
|
||||
PYTHONPATH=$(PROJECT_PYTHONPATH) $(PYTHON) tools/capability_probe.py
|
||||
|
||||
secret-scan:
|
||||
$(PYTHON) tools/check_tracked_secrets.py
|
||||
|
||||
status:
|
||||
$(PYTHON) tools/build_public_status.py
|
||||
|
||||
@@ -45,4 +56,4 @@ notebook-check:
|
||||
notebook-smoke:
|
||||
$(PYTHON) tools/run_colab_notebook.py notebooks/quant_os_colab.ipynb --execute
|
||||
|
||||
local: test bundle smoke verify research research-verify parity parity-verify doctor status-check notebook-check notebook-smoke
|
||||
local: architecture standard test bundle smoke verify research research-verify parity parity-verify doctor secret-scan status-check notebook-check notebook-smoke
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
# Quant OS:个人 A 股 60 分 Baseline 工程
|
||||
# Quant OS:个人 A 股量化研究到受限生产的独立工程
|
||||
|
||||
Quant OS 是项目名;`quant60` 是第一版 A 股 Baseline 的 Python
|
||||
包、CLI namespace 与 artifact 协议前缀。
|
||||
Quant OS 已从 `quants-strategies` 拆成独立项目。`quant-os` / `quant_os`
|
||||
是长期 CLI/import;`quant60` 是第一版 A 股 Baseline 的兼容包、CLI namespace
|
||||
与 V1 artifact 协议前缀。60/80 是证据 profile,不再写进产品名。
|
||||
|
||||
本项目把 2026-07-22 的技术选型文章落成一个可执行、可审计的模块化单体:
|
||||
本地与 Colab 可零账号运行工程 smoke;JQData 登录后可生成不可变快照并直接
|
||||
运行 PIT 本地回测/目标决策;聚宽、QMT 与 Qlib 有各自明确的运行入口。
|
||||
> 当前交易执行范围仅覆盖上交所、深交所(沪深)。北交所尚未接入,
|
||||
> 所有北交所委托必须 `fail closed`;代码映射支持 `.BJ` / `.XBSE`
|
||||
> 不代表具备北交所回测或实盘交易能力。
|
||||
|
||||
> 当前状态仍是 `NOT_BASELINE_60`。本地五层 vertical slice、单测、
|
||||
本项目正在把 2026-07-22 的技术选型实现,从 `quant60` legacy 内核迁移成
|
||||
严格边界、可执行、可审计的模块化单体:本地与 Colab 可零账号运行工程
|
||||
smoke;JQData 登录后可生成不可变快照并直接运行 PIT 本地回测/目标决策;
|
||||
聚宽、QMT 与 Qlib 有各自明确的运行入口。
|
||||
|
||||
> 当前状态仍是 `NOT_BASELINE_60`,80 分评估为
|
||||
> `BLOCKED_FROM_LIMITED_LIVE`、0/100 已验证证据分。本地五层 vertical slice、单测、
|
||||
> synthetic、fake harness、Qlib fixture、真实聚宽 hosted 动量 smoke,以及
|
||||
> 单个 synthetic-research TargetPackage 的真实聚宽 **execution smoke**,
|
||||
> 都不能代替授权真实数据 OOS、逐层同输入 parity、真实 QMT、券商影子盘、
|
||||
@@ -17,15 +24,30 @@ Quant OS 是项目名;`quant60` 是第一版 A 股 Baseline 的 Python
|
||||
> [`docs/AUDIT_2026-07-25.md`](docs/AUDIT_2026-07-25.md) 与
|
||||
> [`gate_scorecard.json`](gate_scorecard.json)。
|
||||
|
||||
> 新依赖检查当前只覆盖 `src/quant_os/`;约 26k 行 legacy 仍位于
|
||||
> `src/quant60/`、根级 `adapters/`、`platforms/` 与 `tools/`,按 vertical
|
||||
> slice 迁移。`doctor.ok = true` 只表示独立仓必需文件和**新 namespace**
|
||||
> 边界有效,不表示 legacy 已分层,也不表示达到 60/80。生产 trust provider /
|
||||
> issuer registry 尚未实现,仓库不能自行签发正向 80 分资格。
|
||||
|
||||
先读:
|
||||
|
||||
- [80 分标准](docs/standards/QUANT_OS_80_STANDARD.md)
|
||||
- [60 与 80 的差别](docs/standards/BASELINE_60_VS_PRODUCTION_80.md)
|
||||
- [面向 80 的三平面架构](docs/architecture/ARCHITECTURE_80.md)
|
||||
- [新架构上手](docs/getting-started/GETTING_STARTED_80.md)
|
||||
- [独立仓迁移记录](MIGRATION.md)
|
||||
|
||||
## 在线公开验收面板
|
||||
|
||||
公开只读入口:
|
||||
|
||||
[`https://gomars.fun/quants-strategies/quant-os/status/index.html`](https://gomars.fun/quants-strategies/quant-os/status/index.html)
|
||||
[`https://gomars.fun/quant-os/status/`](https://gomars.fun/quant-os/status/)
|
||||
|
||||
页面从 `gate_scorecard.json`、平台矩阵、bundle manifest 和证据文档生成
|
||||
脱敏的构建时快照,逐层展示数据、模型、回测平台、券商影子盘与 G1—G10
|
||||
缺口。它不是实时服务健康页,不发布本地行情、账户状态或运行 artifacts。
|
||||
页面从 Production 80 machine profile、`gate_scorecard.json`、平台矩阵、
|
||||
bundle manifest 和证据文档生成脱敏的构建时快照,逐层展示数据、模型、
|
||||
回测平台、券商影子盘与证据缺口。它不是实时服务健康页,不发布本地行情、
|
||||
账户状态或运行 artifacts。
|
||||
|
||||
更新权威记录后必须重新生成并检查页面数据:
|
||||
|
||||
@@ -49,7 +71,7 @@ python3 tools/build_public_status.py --check
|
||||
| QMT built-in/qmttools | 默认动量 smoke;TargetPackage 模式为 Python 3.6 backtest-only 消费器 | Python 3.6 语法、TargetPackage/fake/qmttools 合约 | 需要已授权 QMT 客户端、历史数据权限与 TargetPackage 实跑导出 |
|
||||
| Qlib 0.9.7 | native momentum backtest;Alpha158/LightGBM CLI/Recorder 工作流 | Python 3.12 两条 fixture 已真实跑通,并保存表级 hash、行列/时间边界和重算指标 | synthetic 两股票没有投资价值;无真实 OOS、L3/L4 parity |
|
||||
| Tushare → Qlib | 只读盘点 completed Parquet、校验 checksum、构建/验证不可变 provider、运行本地回测 | 93 个 completed 文件已验证;早期未复权 provider 与两次 byte-identical momentum run | 下载仅到 1993-10;缺复权、真实 benchmark、历史成分/ST/停牌/涨跌停,不能进入生产 decision |
|
||||
| XtTrader shadow | 只读账户查询、HMAC 账户绑定、broker observation/snapshot、零下单 target-diff 计划与完整 verifier | fake broker/QMT 合约;所有 broker mutation 均不可达 | 需要授权 QMT 环境的账户合同探针、20 日影子、恢复/对账和券商确认 |
|
||||
| XtTrader shadow | 只读账户查询、HMAC 账户绑定、broker observation/snapshot、零下单 target-diff 计划与完整 verifier | fake broker/QMT 合约;所有 broker mutation 均不可达 | 需要授权 QMT 环境的账户合同探针、恢复/对账和券商确认;Baseline 60 至少 20 个交易日,Production 80 当前缺口为同一冻结候选至少 60 个交易日 |
|
||||
|
||||
`portable-momentum-v1` 只是当前真实跑过聚宽的跨引擎连通性 smoke,不是
|
||||
60 分 Baseline。Baseline 的权威决策留在本地:冻结 Ridge model bundle,
|
||||
@@ -68,13 +90,14 @@ make local
|
||||
|
||||
它依次执行:
|
||||
|
||||
1. 全量单元/合约测试;
|
||||
2. 聚宽与 QMT 单文件 bundle;
|
||||
3. synthetic execution smoke 与完整 manifest verifier;
|
||||
4. synthetic research-to-target 与 research manifest verifier;
|
||||
5. JoinQuant/QMT mock parity 报告及校验;
|
||||
6. capability probe;
|
||||
7. Colab notebook 语法检查和逐 cell 本地执行。
|
||||
1. 新 namespace 依赖边界和 80 分 profile 评估;
|
||||
2. 全量单元/合约测试;
|
||||
3. 聚宽与 QMT 单文件 bundle;
|
||||
4. synthetic execution smoke 与完整 manifest verifier;
|
||||
5. synthetic research-to-target 与 research manifest verifier;
|
||||
6. JoinQuant/QMT mock parity 报告及校验;
|
||||
7. capability probe;
|
||||
8. Colab notebook 语法检查和逐 cell 本地执行。
|
||||
|
||||
等价的脚本入口:
|
||||
|
||||
@@ -437,7 +460,9 @@ key。它必须稳定备份、限制访问且绝不
|
||||
官方 query API 对“失败”和“空列表”的返回合同,并记录实际
|
||||
terminal/xtquant build、确认 `XtTrade.traded_amount` 成交金额语义与允许
|
||||
误差。当前 amount 只是被认证的 informational fact,不参与 target quantity
|
||||
或 proposed delta。之后还要累计至少 20 个交易日、恢复演练和零未知账差。
|
||||
或 proposed delta。之后还要先满足 Baseline 60 的至少 20 个交易日 shadow、
|
||||
恢复演练和零未知账差;Production 80 当前要求同一冻结候选累计至少 60 个
|
||||
交易日。
|
||||
|
||||
## Qlib 0.9.7
|
||||
|
||||
@@ -553,7 +578,8 @@ real_platform_pass = false
|
||||
gate_credit = []
|
||||
```
|
||||
|
||||
真实 G9 仍要求聚宽/QMT 导出与至少 20 个交易日 QMT 影子记录。完整层级、
|
||||
Baseline 60 的真实 G9 仍要求聚宽/QMT 导出与至少 20 个交易日 QMT 影子记录;
|
||||
Production 80 当前要求同一冻结候选至少 60 个交易日。完整层级、
|
||||
容差和 reason code 见
|
||||
[`docs/CROSS_ENGINE_CONSISTENCY.md`](docs/CROSS_ENGINE_CONSISTENCY.md)。
|
||||
|
||||
@@ -593,7 +619,8 @@ factor risk、CVXPY、TWAP/POV 或 Ridge 上游四层在 hosted 环境被调用
|
||||
- 聚宽已有单包 TargetPackage execution 导出,但仍缺授权真实数据的多期
|
||||
TargetPackage、本地/聚宽逐层同输入对账;QMT 尚无真实 peer 导出,QMT
|
||||
历史 ST 权限正例、账户 query 合同、
|
||||
20 日只读影子、回调/恢复/对账尚未形成真实证据;
|
||||
Baseline 60 所需 20 日只读影子、回调/恢复/对账尚未形成真实证据;
|
||||
Production 80 还要求同一冻结候选累计至少 60 个交易日 shadow;
|
||||
- 自动 scheduler、日终对账服务、监控告警与 kill-switch 演练未形成连续证据;
|
||||
- 实际券商程序化交易报告、权限、频率与软件要求尚未书面确认。
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Public status application
|
||||
|
||||
The deployable static artifact currently remains in `status/` so the canonical
|
||||
public URL stays `/quant-os/status/`. It is a build-time, non-secret evidence
|
||||
snapshot, not a live health endpoint.
|
||||
|
||||
The next migration replaces Markdown parsing with a machine claims index.
|
||||
@@ -300,8 +300,9 @@ synthetic/fake 数据是合同测试,不是市场证据;Qlib synthetic fixtu
|
||||
- 授权真实数据训练的冻结模型包和真实 OOS 证据;
|
||||
- 真实数据校准的风险、冲击、容量和 TCA;
|
||||
- 授权真实数据的多期 JoinQuant 导出、逐层本地对账与真实 QMT peer;
|
||||
- 券商状态映射、restart recovery daemon、20 日 shadow 与连续对账;
|
||||
- 运维监控、kill-switch 演练和程序化交易合规确认。
|
||||
- 券商状态映射、restart recovery daemon、连续对账,以及 Production 80
|
||||
要求的同一冻结候选 60 日 shadow(Baseline 60 的前置门槛为 20 日);
|
||||
- 运维监控、kill-switch 演练和券商程序化交易报告/核查确认。
|
||||
|
||||
所以 Quant OS 已具备本地五层 vertical slice 与 TargetPackage 平台边界,
|
||||
但仍只是可继续填证据的候选骨架,不是已经达到整体 60 分的实盘系统。证据政策见
|
||||
|
||||
@@ -117,7 +117,9 @@ true;Universe、Alpha、Portfolio、Risk 仍为 false,因此
|
||||
smoke 作为线路先验,但仍要导出完整多期 identity/plan/orders/fills。
|
||||
5. 做 local/JoinQuant/QMT 逐层差异;任何 `UNEXPLAINED` 必须为零。当前无
|
||||
QMT peer,所以 G9 仍未通过。
|
||||
6. 完成至少 20 个交易日 QMT 只读 shadow、重启/回调/对账和合规确认。
|
||||
6. 完成 Baseline 60 所需的至少 20 个交易日 QMT 只读 shadow、重启/回调/
|
||||
对账和券商程序化交易报告/核查确认。Production 80 当前要求同一冻结候选
|
||||
至少 60 个交易日。
|
||||
|
||||
## 用户什么时候需要参与
|
||||
|
||||
|
||||
@@ -244,7 +244,7 @@ PYTHONPATH=src:. python -m platforms.qlib_runner \
|
||||
| real JoinQuant export vs local canonical run | Yes,覆盖的 layers/dates |
|
||||
| licensed qmttools run with saved version/result | Yes,覆盖的 layers |
|
||||
| XtTrader shadow using real broker snapshot | Yes,pre-trade/order semantics |
|
||||
| ≥20 trading days QMT shadow, zero unexplained differences | 文章 G9 必需 |
|
||||
| ≥20 trading days QMT shadow, zero unexplained differences | Baseline 60 的 G9 必需;Production 80 另要求同一冻结候选 ≥60 日 |
|
||||
|
||||
真实 JoinQuant smoke 已运行;QMT 和 broker shadow 尚未运行,而且
|
||||
JoinQuant 仍缺同输入导出与 local peer,所以 G9 继续为 `not_passed`。
|
||||
|
||||
@@ -68,6 +68,8 @@ still missing:
|
||||
portable plan, orders and fills;
|
||||
- a local canonical run over the exact same frozen inputs;
|
||||
- layer-by-layer L1—L4 comparison with enumerated difference reasons;
|
||||
- a real QMT peer run and at least 20 trading days of broker shadow evidence.
|
||||
- a real QMT peer run and the Baseline-60 minimum of 20 trading days of broker
|
||||
shadow evidence; Production 80 requires at least 60 trading days for the same
|
||||
frozen candidate.
|
||||
|
||||
Accordingly, `gate_scorecard.json` remains `NOT_BASELINE_60`.
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
| QMT local harness | built-in wrapper 合同 | Python ≥3.10,无账号 | Python 3.6 source、`ContextInfo` 回滚模拟、动量 smoke 与 TargetPackage 合同 | 不证明 QMT |
|
||||
| QMT built-in bundle | GUI backtest-only | 授权 QMT 客户端/历史数据 | 两种模式都硬限制 backtest;TargetPackage 模式不重算上游四层;模块全局 `g` | 尚无 TargetPackage 真实 QMT run/export;10% GUI 配置和执行时钟待取证 |
|
||||
| qmttools runner | native-Python QMT backtest/history | 券商分发 `xtquant` + 已登录 terminal | 固定中证 500/10% 参与率、参数、只读 preflight 和 hard backtest/history 合同已测 | 尚无专有运行时实跑 |
|
||||
| XtTrader read-only shadow | 账户绑定 target-diff 与 broker observation | MiniQMT/QMT、账户查询权限、既有本地 HMAC key | HMAC 账户绑定 + authenticated evidence envelope、decision/observation semantic replay、资产/持仓/委托/成交恒等式、fail-closed exact verifier 与 fake broker 合同 | HMAC 不是 broker attestation;仍缺官方 query 失败/空结果合同、callback/restart、20 日真实 shadow;not live-ready |
|
||||
| XtTrader read-only shadow | 账户绑定 target-diff 与 broker observation | MiniQMT/QMT、账户查询权限、既有本地 HMAC key | HMAC 账户绑定 + authenticated evidence envelope、decision/observation semantic replay、资产/持仓/委托/成交恒等式、fail-closed exact verifier 与 fake broker 合同 | HMAC 不是 broker attestation;仍缺官方 query 失败/空结果合同及 callback/restart;Baseline 60 至少 20 个交易日,Production 80 当前缺口为同一冻结候选至少 60 个交易日;not live-ready |
|
||||
| Qlib native momentum | signal/model research | CPython 3.12 + exactly `pyqlib==0.9.7` | 真实本地 fixture 成功,24 signal;保存 signal/report hash、表边界和重算 portfolio 指标 | synthetic 两股票;无真实数据/OOS,无 L3/L4 parity |
|
||||
| Qlib Alpha158/LightGBM | model fit + Recorder workflow | 同上 + LightGBM/MLflow stack | 真实 fixture 完成 model/Recorder;读取 portfolio report,保存表 hash、边界、重算指标与 artifact path | synthetic 两股票性能无意义;真实 provider/OOS 和冻结模型缺失 |
|
||||
| Tushare local → Qlib | completed Parquet 的只读盘点、不可变 provider 与本地研究 smoke | 同上 + `pyarrow==24.0.0`;本地镜像 | 93 个 completed 文件全部通过 size/SHA/row-count;v4 provider verifier 成功;同一动量命令连续两次 evidence JSON 逐 byte 相同 | `daily` 仅完成约 8.2%,缺复权、真实 benchmark、PIT 成分/ST/停牌/涨跌停;`production_ready=false` |
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# Quant OS documentation map
|
||||
|
||||
- 80 分标准:[`standards/QUANT_OS_80_STANDARD.md`](standards/QUANT_OS_80_STANDARD.md)
|
||||
- 60 与 80:[`standards/BASELINE_60_VS_PRODUCTION_80.md`](standards/BASELINE_60_VS_PRODUCTION_80.md)
|
||||
- 新架构:[`architecture/ARCHITECTURE_80.md`](architecture/ARCHITECTURE_80.md)
|
||||
- 上手:[`getting-started/GETTING_STARTED_80.md`](getting-started/GETTING_STARTED_80.md)
|
||||
- 独立仓 ADR:[`adr/0001-independent-project-and-three-planes.md`](adr/0001-independent-project-and-three-planes.md)
|
||||
- 当前实现架构:[`../docs/ARCHITECTURE.md`](ARCHITECTURE.md)
|
||||
- 平台矩阵:[`PLATFORM_MATRIX.md`](PLATFORM_MATRIX.md)
|
||||
- Tushare 数据:[`TUSHARE_LOCAL_DATA.md`](TUSHARE_LOCAL_DATA.md)
|
||||
@@ -111,7 +111,7 @@ Signal/Target/Order/Broker 合同。当前 Tushare adapter 只实现第一条链
|
||||
从 Quant OS 根目录执行。镜像路径只放在当前 shell 环境变量中,不写入 Git:
|
||||
|
||||
```bash
|
||||
export QUANT_OS_ROOT=/path/to/quants-strategies/quant-os
|
||||
export QUANT_OS_ROOT=/path/to/quant-os
|
||||
export TUSHARE_MIRROR_ROOT=/path/to/tushare-mirror
|
||||
cd "$QUANT_OS_ROOT"
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# ADR-0001:Quant OS 独立仓与三平面模块化单体
|
||||
|
||||
- 状态:Accepted
|
||||
- 日期:2026-07-30
|
||||
|
||||
## 背景
|
||||
|
||||
Quant OS 原位于 `quants-strategies/quant-os`,把操作系统框架和具体策略仓库
|
||||
混在一起;包名 `quant60` 又把成熟度写进产品身份。继续迭代到 80 会造成路径、
|
||||
CI、发布 URL、import 和 artifact 身份持续破坏。
|
||||
|
||||
## 决策
|
||||
|
||||
1. Quant OS 迁为独立项目 `boat/quant-os`。
|
||||
2. `quant-os` / `quant_os` 是长期产品 CLI/import。
|
||||
3. `baseline-60` / `production-80` 是 profile,不是新包。
|
||||
4. 使用三平面模块化单体,专有 SDK 通过 adapter/runtime 隔离。
|
||||
5. `quant60` 和 V1 artifact 至少保留两个兼容发布周期。
|
||||
6. 迁仓和大模块重构分成独立提交;先证明行为等价,再按 vertical slice 拆分。
|
||||
|
||||
## 后果
|
||||
|
||||
正面:
|
||||
|
||||
- 策略仓与框架生命周期解耦;
|
||||
- 80 分标准成为机器可验收控制面;
|
||||
- QMT mutation authority 可以独立封闭;
|
||||
- 历史证据和 Colab/平台入口有稳定产品名。
|
||||
|
||||
代价:
|
||||
|
||||
- 一段时间内同时存在 `quant_os` 与 `quant60`;
|
||||
- 根级 legacy adapters/platforms 仍需逐步迁移;
|
||||
- 新旧公开 URL 需要并行和重定向;
|
||||
- subtree split 会重写原路径提交 SHA,必须保存映射。
|
||||
@@ -0,0 +1,167 @@
|
||||
# Quant OS 面向 80 分的目标架构
|
||||
|
||||
状态:本轮已建立边界、长期 namespace、机器标准和兼容入口;大模块拆分按
|
||||
vertical slice 继续进行,不声称已经完成 26k 行重写。
|
||||
|
||||
## 1. 架构选择
|
||||
|
||||
采用**严格边界的模块化单体 + 专有 runtime 代理**,不是微服务。
|
||||
|
||||
原因:
|
||||
|
||||
- 个人系统不需要承担微服务的网络、部署和分布式一致性成本;
|
||||
- QMT、聚宽、Qlib、JQData 有互不兼容的 Python/SDK 运行时,必须物理隔离;
|
||||
- 决策身份、证据链和本地重放需要单一仓库和明确的事务边界;
|
||||
- 模块边界可以先在进程内强制,达到真实吞吐或隔离需求后再拆服务。
|
||||
|
||||
## 2. 三平面
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
B["Composition root<br/>CLI · runtime wiring"] --> C
|
||||
B --> X
|
||||
C["Control plane<br/>RunSpec · orchestration · release policy"] --> A["Application use cases"]
|
||||
A --> D["Data plane<br/>PIT → research → portfolio → execution"]
|
||||
D --> P["Ports"]
|
||||
X["Adapters / proprietary runtimes<br/>JQData · Qlib · JoinQuant · QMT · XtTrader"] --> P
|
||||
D --> E["EvidenceSink port"]
|
||||
C --> E
|
||||
X --> E
|
||||
E --> V["Evidence plane<br/>manifest · parity · scorecard · claims"]
|
||||
```
|
||||
|
||||
硬规则:
|
||||
|
||||
- Control plane 只编排 RunSpec、状态和发布,不计算信号,不直接调用券商 SDK。
|
||||
- Data plane 只消费不可变输入,产生 model、weight 和 execution intent;它不读
|
||||
网页状态或 Gate。
|
||||
- Evidence plane 只记录已经发生的事实,不得修改 target、order 或 broker fact。
|
||||
- Adapter 向内实现 port;core 永远不 import `jqdatasdk`、`qlib`、`xtquant`
|
||||
或 hosted 平台全局 API。
|
||||
- `ReadBrokerPort` 与 `TradeBrokerPort` 分离。拥有查询权不等于拥有下单权。
|
||||
- `composition` 是唯一同时依赖 application/control 与 adapters 的受检装配根;
|
||||
adapter 只实现 port,不反向 import application。
|
||||
|
||||
## 3. 依赖方向
|
||||
|
||||
```text
|
||||
foundation
|
||||
↑
|
||||
contracts / ports
|
||||
↑
|
||||
data | research | portfolio | execution
|
||||
↑
|
||||
application
|
||||
↑
|
||||
control
|
||||
↑
|
||||
composition / CLI
|
||||
|
||||
adapters ──implements──> ports
|
||||
evidence <── consumes immutable envelopes
|
||||
composition ──wires──> application + control + adapters
|
||||
```
|
||||
|
||||
新 namespace 的边界由 `quant_os.architecture.validate_architecture` 扫描 AST
|
||||
执行。CI 会拒绝例如 `research -> execution`、`data -> control` 的倒置依赖,
|
||||
也会拒绝新层绕回根级 `quant60`、`adapters`、`platforms` 或 `tools`。
|
||||
只有 `composition` 可以临时调用 compatibility 内核;每个 vertical slice
|
||||
迁完后继续收窄。
|
||||
|
||||
## 4. 目录
|
||||
|
||||
```text
|
||||
src/quant_os/
|
||||
foundation/ # ID、双时钟、symbol、money
|
||||
contracts/ # DTO、schema registry、版本迁移
|
||||
ports/ # Data/Engine/ReadBroker/TradeBroker/EvidenceSink
|
||||
data/ # PIT snapshot、catalog、quality
|
||||
research/ # feature、split、model、evaluate
|
||||
portfolio/ # risk、cost、capacity、optimizer
|
||||
execution/ # intent、OMS、reconcile、kill switch
|
||||
application/ # ingest/train/backtest/publish/shadow use cases
|
||||
control/ # RunSpec、orchestrator、release policy
|
||||
evidence/ # manifest、claim、parity、maturity
|
||||
adapters/ # provider/platform/broker implementations
|
||||
composition/ # 唯一受检进程装配根
|
||||
|
||||
runtimes/ # portable36、JoinQuant、QMT built-in、Colab
|
||||
profiles/ # baseline-60、production-80
|
||||
examples/ # 永不晋级为候选的 smoke 策略
|
||||
status/ # 可发布静态验收快照
|
||||
```
|
||||
|
||||
`quant_os` 是长期产品 namespace;成熟度只存在于 profile。当前 AST checker
|
||||
只覆盖新 namespace(现为迁移壳层与新增控制代码),不会把约 26k 行 legacy
|
||||
自动宣称为已分层。`quant60` 保留两个
|
||||
兼容发布周期,继续读取/写入现有 V1 artifact,避免历史证据失效。
|
||||
|
||||
## 5. 当前代码如何迁移
|
||||
|
||||
| 当前模块 | 问题 | 目标位置 | 迁移条件 |
|
||||
| --- | --- | --- | --- |
|
||||
| `portable_core.py` | symbol、TargetPackage、动量、sizing、delta 混合 | foundation/contracts/portfolio/execution + example | golden vectors 先行 |
|
||||
| `experiment.py` | fixture、研究、编排、证据混合 | research + application + evidence | real snapshot Ridge slice 接通 |
|
||||
| `qmt_shadow.py` | 规划、签名、落盘、重放 3595 行 | execution/application/evidence | 真 QMT contract probe 后冻结 |
|
||||
| `snapshot_pipeline.py` | 数据、决策、broker clock 混合并依赖 backtest 私有函数 | data + application + execution | 先建立公共合同 |
|
||||
| `backtest.py` | engine 与 artifact publisher 混合 | local adapter + evidence | V1 manifest reader 保持兼容 |
|
||||
| 平台 wrapper | JoinQuant/QMT 重复执行逻辑 | shared portable36 consumer + thin mapping | Python 3.6 golden test |
|
||||
| status builder | 解析 Markdown 形成机器状态 | 只读 machine claims index | 标准/assessment 已先机器化 |
|
||||
|
||||
## 6. 唯一决策权威
|
||||
|
||||
当前最大结构问题不是文件太大,而是存在两个上游决策权威:
|
||||
|
||||
1. 真实 provider snapshot 仍走 `portable-momentum-v1`;
|
||||
2. Ridge 五层链只在 synthetic research 中产生 TargetPackage。
|
||||
|
||||
目标必须统一为:
|
||||
|
||||
```text
|
||||
authorized immutable PIT snapshot
|
||||
-> frozen feature/model
|
||||
-> portfolio + independent post-risk
|
||||
-> TargetPackage (weights, no account quantities)
|
||||
-> JoinQuant/QMT thin execution consumer
|
||||
```
|
||||
|
||||
hosted runtime 不再现场重算 Alpha。`portable-momentum` 永远留在 example/smoke,
|
||||
不能被默认 bundler 静默提升成生产候选。
|
||||
|
||||
跨引擎 strict peer 是 local、JoinQuant 与 QMT 的 L1—L5。Qlib 只比较其声明
|
||||
参与的层级;只有显式导出同一冻结模型和组合实现时,才可提升比较范围。
|
||||
Alpha158/LightGBM 研究工作流不能冒充 portable candidate 的 L3/L4 peer。
|
||||
|
||||
## 7. 分阶段路线
|
||||
|
||||
### 已在本轮落地
|
||||
|
||||
- 独立仓历史分支;
|
||||
- `quant_os` 长期 CLI/import,`quant60` 兼容;
|
||||
- 七维 80 分 machine profile 和 fail-closed evaluator;
|
||||
- standard/profile canonical hash、可信 Baseline 前置、不可跳过的 77→canary→80
|
||||
授权链及 `TEST_ONLY` 信任隔离;
|
||||
- 新 namespace 层级和依赖检查;
|
||||
- composition root 与 adapter 单向依赖;
|
||||
- ReadBroker/TradeBroker/EvidenceSink port;
|
||||
- 独立根 CI;
|
||||
- 独立仓 clean-clone 测试修复;
|
||||
- 架构、标准、迁移和上手文档。
|
||||
|
||||
### 下一阶段
|
||||
|
||||
1. 建立 golden vectors 和 V1/V2 schema registry;
|
||||
2. 把真实 snapshot 接入 Ridge 主链,消除双决策权威;
|
||||
3. 先拆一个完整 vertical slice,而不是按文件批量搬家;
|
||||
4. 平台 wrapper 收敛为同一个 portable consumer;
|
||||
5. status builder 改读统一 claims index,不再解析 Markdown;
|
||||
6. 取得真实 QMT 后接通 read-only contract probe、shadow 和对账。
|
||||
|
||||
### 依赖外部环境的阶段
|
||||
|
||||
- 完整授权 JQData/OOS;
|
||||
- 多期真实聚宽导出;
|
||||
- 真 QMT/qmttools/XtTrader;
|
||||
- 60 个交易日 shadow;
|
||||
- 10 个交易日受限资金 canary;
|
||||
- 生产 SLO、RPO/RTO、凭据轮换、券商程序化交易确认。
|
||||
@@ -0,0 +1,299 @@
|
||||
# Quant OS 新架构上手
|
||||
|
||||
这份文档只给出当前真实可运行入口。每一步都标明它需要什么、产生什么,以及
|
||||
不能证明什么。
|
||||
|
||||
## 0. 先认清当前声明
|
||||
|
||||
```text
|
||||
Baseline 60: NOT_BASELINE_60 (0/10 gates)
|
||||
Production 80: BLOCKED_FROM_LIMITED_LIVE (0/100 verified evidence points)
|
||||
Broker mutation: disabled
|
||||
Investment value claim: false
|
||||
```
|
||||
|
||||
这是 fail-closed 结论,不妨碍运行本地工程 smoke。
|
||||
|
||||
## 1. 本地路径和安装
|
||||
|
||||
独立仓:
|
||||
|
||||
```text
|
||||
~/boat-workspace/Code/quant-os
|
||||
```
|
||||
|
||||
推荐 Python 3.12:
|
||||
|
||||
```bash
|
||||
cd ~/boat-workspace/Code/quant-os
|
||||
python3.12 -m venv .venv-core
|
||||
source .venv-core/bin/activate
|
||||
python -m pip install -e .
|
||||
```
|
||||
|
||||
核心无第三方运行依赖。JQData、Qlib 和 QMT 必须使用各自独立环境。
|
||||
当前 CLI 的 profile、配置和 bundle 是仓库资产,不嵌入 wheel。已安装的
|
||||
`quant-os` 应在 checkout 内运行;从其他目录调用新命令时显式写:
|
||||
|
||||
```bash
|
||||
quant-os --project-root ~/boat-workspace/Code/quant-os doctor
|
||||
```
|
||||
|
||||
wheel 不是独立的数据/runtime 分发包。
|
||||
|
||||
## 2. 十分钟理解三平面
|
||||
|
||||
- Control plane:选择 RunSpec、编排 use case、做发布判定。
|
||||
- Data plane:不可变 PIT 数据 → 特征/模型 → 权重 → execution intent。
|
||||
- Evidence plane:记录 hash、来源、平台事实、差异和 Gate;不能修改决策。
|
||||
|
||||
平台和供应商只通过 adapter/port 进入。读券商与下单是两个独立接口。
|
||||
|
||||
## 3. 工程体检
|
||||
|
||||
状态:`[已实现|无需账号]`
|
||||
|
||||
```bash
|
||||
quant-os doctor
|
||||
quant-os architecture check
|
||||
quant-os standard validate
|
||||
quant-os standard evaluate
|
||||
```
|
||||
|
||||
预期:
|
||||
|
||||
- `doctor.ok = true`:目录、入口、profile 和新 namespace 边界有效;
|
||||
- `standard evaluate.ok = true`:评估过程有效;
|
||||
- `qualified = false`:当前没有达到 80,属于正确结果。
|
||||
|
||||
`doctor.architecture.scope = new_namespace_only` 表示 AST checker 只覆盖
|
||||
`src/quant_os/`;legacy `quant60` 和根级 adapter/platform/tool 仍在迁移。
|
||||
它证明工程和标准可验证,不证明全部旧代码已经分层、策略有效或平台可用。
|
||||
|
||||
没有 editable install 时可用:
|
||||
|
||||
```bash
|
||||
PYTHONPATH=src:. python -m quant_os doctor
|
||||
```
|
||||
|
||||
## 4. 无账号本地 demo
|
||||
|
||||
状态:`[已实现|无需账号]`
|
||||
|
||||
完整验证:
|
||||
|
||||
```bash
|
||||
make local
|
||||
```
|
||||
|
||||
较短路径:
|
||||
|
||||
```bash
|
||||
quant-os smoke \
|
||||
--config configs/baseline.json \
|
||||
--output artifacts/local-smoke
|
||||
|
||||
quant-os verify-manifest artifacts/local-smoke/manifest.json
|
||||
|
||||
quant-os research-smoke --output artifacts/research-smoke
|
||||
quant-os verify-research-manifest artifacts/research-smoke/manifest.json
|
||||
```
|
||||
|
||||
`quant-os` 在这些命令上调用兼容的 `quant60` V1 内核。产物仍使用 V1 identity,
|
||||
因此历史 verifier 不会失效。
|
||||
|
||||
本地 demo 证明:
|
||||
|
||||
- 核心规则、ledger、manifest 和 hash 可重放;
|
||||
- synthetic 五层链能够生成 ModelBundle 和 TargetPackage;
|
||||
- 平台 bundle 可生成并通过 fake contract。
|
||||
|
||||
它不证明:
|
||||
|
||||
- 真实市场收益;
|
||||
- 真实 JQData 完整性;
|
||||
- 真实 QMT 或券商执行;
|
||||
- 60/80 达标。
|
||||
|
||||
## 5. Tushare 数据与 Qlib
|
||||
|
||||
状态:`[已实现到技术 smoke|真实镜像仍不完整]`
|
||||
|
||||
先阅读 [`../TUSHARE_LOCAL_DATA.md`](../TUSHARE_LOCAL_DATA.md)。当前盘点表明
|
||||
completed Parquet 能校验并转换为 managed Qlib provider,但缺复权、真实
|
||||
benchmark、历史成分、ST、停牌和涨跌停,不能进入生产 decision。
|
||||
|
||||
典型流程:
|
||||
|
||||
```bash
|
||||
PYTHONPATH=src:. python tools/tushare_qlib.py inventory --help
|
||||
PYTHONPATH=src:. python tools/tushare_qlib.py build --help
|
||||
PYTHONPATH=src:. python tools/tushare_qlib.py verify --help
|
||||
PYTHONPATH=src:. python -m platforms.qlib_runner --help
|
||||
```
|
||||
|
||||
具体参数以 Tushare 文档和本机 inventory 为准。不要为了跑通而静默补缺失字段。
|
||||
|
||||
这条路径证明数据桥和 Qlib runtime 可用,不证明 Ridge Baseline、G1/G2 或
|
||||
投资价值。
|
||||
|
||||
## 6. JQData → 本地 PIT
|
||||
|
||||
状态:`[已实现 adapter|需要授权账号]`
|
||||
|
||||
```bash
|
||||
python3.12 -m venv .venv-jqdata
|
||||
source .venv-jqdata/bin/activate
|
||||
python -m pip install -r requirements/jqdata.txt
|
||||
|
||||
PYTHONPATH=src:. python tools/jqdata_snapshot.py \
|
||||
--index 000905.XSHG \
|
||||
--start 2024-01-02 \
|
||||
--end 2024-12-31 \
|
||||
--output data/snapshots/csi500-2024
|
||||
|
||||
quant-os snapshot-backtest \
|
||||
--snapshot data/snapshots/csi500-2024/manifest.json \
|
||||
--config configs/baseline.json \
|
||||
--output artifacts/csi500-2024-backtest
|
||||
```
|
||||
|
||||
凭据只通过无回显登录或本地 secret source 注入,不进入命令历史、Git、OB、
|
||||
notebook 或 artifact。
|
||||
|
||||
重要限制:当前 snapshot backtest/decision 仍走
|
||||
`portable-momentum-v1` compatibility path;它尚未接入真实 Ridge 五层权威。
|
||||
|
||||
## 7. Colab
|
||||
|
||||
状态:`[默认流程已实现|JQData/Qlib cell 需对应授权/运行时]`
|
||||
|
||||
打开 `notebooks/quant_os_colab.ipynb`。独立仓 clone 目标为:
|
||||
|
||||
```text
|
||||
https://git.gomars.fun/boat/quant-os.git
|
||||
```
|
||||
|
||||
默认 cell 不需要账号。`RUN_JQDATA=True` 和 `RUN_QLIB=True` 是显式可选。
|
||||
Colab 禁止上传券商凭据、账户号、持仓/成交、QMT userdata 或未获许可的行情。
|
||||
|
||||
本地先验证 notebook:
|
||||
|
||||
```bash
|
||||
python tools/run_colab_notebook.py notebooks/quant_os_colab.ipynb
|
||||
python tools/run_colab_notebook.py notebooks/quant_os_colab.ipynb --execute
|
||||
```
|
||||
|
||||
## 8. 聚宽
|
||||
|
||||
状态:`[bundle 已实现|上传/导出需要登录聚宽]`
|
||||
|
||||
动量 smoke:
|
||||
|
||||
```bash
|
||||
python tools/bundle_platforms.py
|
||||
```
|
||||
|
||||
TargetPackage consumer:
|
||||
|
||||
```bash
|
||||
python tools/bundle_platforms.py \
|
||||
--target-package artifacts/research-smoke/target_package.json \
|
||||
--output-dir dist/target-package
|
||||
```
|
||||
|
||||
上传 `dist/target-package/joinquant_strategy.py` 后,必须保存平台运行参数、日志、
|
||||
订单、成交和同输入 hash。当前已有一次 synthetic-research package 的真实
|
||||
execution smoke,只证明 execution consumer,不计 G9/60/80。
|
||||
|
||||
## 9. QMT
|
||||
|
||||
状态:`[bundle/fake/read-only 框架已实现|真运行需要 Windows QMT 账号]`
|
||||
|
||||
当前可做:
|
||||
|
||||
```bash
|
||||
python tools/bundle_platforms.py
|
||||
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
|
||||
```
|
||||
|
||||
取得 QMT 后按顺序启用:
|
||||
|
||||
1. built-in 历史回测;
|
||||
2. qmttools 回测导出;
|
||||
3. XtTrader 只读 contract probe;
|
||||
4. broker snapshot 与 shadow plan;
|
||||
5. callback/restart/reconcile;
|
||||
6. 完成券商程序化交易报告确认后,才讨论 TradeBrokerPort。
|
||||
|
||||
账号、userdata 路径和账户标识不放进 Git/OB。当前没有任何 operator 可运行的
|
||||
实盘下单命令,这是有意的安全边界。
|
||||
|
||||
## 10. 如何读 60/80 状态
|
||||
|
||||
```bash
|
||||
quant-os standard evaluate
|
||||
```
|
||||
|
||||
重点字段:
|
||||
|
||||
- `raw_score`:只有达到最低 evidence tier 且 hash 有效的控制项得分;
|
||||
- `effective_score`:应用 60/canary cap 后的分数;
|
||||
- `hard_gate_failures`:任何一项都阻断上线;
|
||||
- `domain_floor_failures`:防止用文档/单一领域堆分;
|
||||
- `prerequisites`:60、QMT、券商确认、shadow、canary、P0/P1;
|
||||
- `standard_canonical_sha256`:本次评估解释所使用的精确标准版本;
|
||||
- `pre_canary_authorization`:授权 ID/hash、有效期和 verifier/issuer provenance;
|
||||
- `pre_canary_authorization_valid_as_of_assessment`:授权在评估日是否仍可开始或继续
|
||||
canary;过期后为 false,但不会否定期限内已完成的历史 canary chain;
|
||||
- `canary_authorization_chain_verified`:E4 canary 是否引用同一有效授权、处于合法
|
||||
时间窗,并以实际最大资本/notional、标的、流量和 breach/stop 记录证明未突破
|
||||
hard caps;
|
||||
- `verification_environment` / `test_only`:测试 verifier 永远不能冒充生产信任;
|
||||
- `pre_canary_authorized`:只有生产 issuer registry 核验后才可能为真;
|
||||
- `trusted_attestation_verified`:最终 promotion attestation 是否由信任边界核验;
|
||||
- `qualified`:唯一可用于 promotion policy 的布尔值。
|
||||
|
||||
当前 CLI 没有注入 production trust provider / issuer registry,所以它只能
|
||||
诚实评估当前全负状态,不能接受手工伪造的 `passed` 或自行颁发 60/80。
|
||||
单元测试即使构造满分证据,也只能得到 `TEST_ONLY_STRUCTURAL_PASS`;
|
||||
production verifier 是明确 backlog。
|
||||
|
||||
公开页只展示脱敏静态快照,不是实时健康页:
|
||||
|
||||
```text
|
||||
https://gomars.fun/quant-os/status/
|
||||
```
|
||||
|
||||
## 11. 新策略如何接入
|
||||
|
||||
状态:`[目标 API|本轮只定义边界,尚未完成 Strategy SDK]`
|
||||
|
||||
策略最终应留在 `quants-strategies`,通过版本化 StrategySpec/port 进入 Quant OS。
|
||||
策略只提供特征、预测或目标权重逻辑,不能自行:
|
||||
|
||||
- 读取券商 secret;
|
||||
- 调用 TradeBrokerPort;
|
||||
- 修改 evidence;
|
||||
- 绕过独立风险;
|
||||
- 把 hosted 平台变成第二个 Alpha 权威。
|
||||
|
||||
当前新增策略仍按现有 `quant60` 实验接口工作,待 V2 StrategySpec 和 golden
|
||||
vectors 完成后迁移。
|
||||
|
||||
## 12. 建议的学习顺序
|
||||
|
||||
1. 跑 `doctor` 和 `standard evaluate`;
|
||||
2. 跑 `make local`,打开 manifest 看 hash/identity;
|
||||
3. 对照 60/80 文档解释为什么 synthetic 不得分;
|
||||
4. 阅读三平面架构和 ReadBroker/TradeBroker 拆分;
|
||||
5. 盘点 Tushare provider,再跑 Qlib technical smoke;
|
||||
6. 获得 JQData 后生成真实 snapshot;
|
||||
7. 先做真实聚宽多期导出,再接真实 QMT;
|
||||
8. 最后才累计 shadow/canary。
|
||||
|
||||
任何时候发现“代码可以下单,但证据/规则/券商确认还没到”,正确行为都是让
|
||||
系统保持停机。
|
||||
@@ -0,0 +1,57 @@
|
||||
# Quant OS 60 分与 80 分的边界
|
||||
|
||||
## 一句话区别
|
||||
|
||||
- 60 分回答:同一策略研究链是否**可运行、可重放、可审计,并可进入只读 shadow**?
|
||||
- 80 分回答:同一已报告版本是否已经在真实券商和受限资金 canary 下,满足
|
||||
**本 profile 明示的账户、报告、风控、恢复与审计控制项**?
|
||||
|
||||
80 不是替代 60;60 的 G1—G10 全部通过是 80 的前置。
|
||||
|
||||
## 对照表
|
||||
|
||||
| 责任 | 60 分 Baseline | 80 分受限生产 | 当前状态 |
|
||||
| --- | --- | --- | --- |
|
||||
| 系统定位 | research-to-shadow baseline;无资金发送权 | limited-production/canary-ready | 未达到 60 |
|
||||
| 数据 | 有 PIT 合同、不可变快照和真实抽样重放 | 完整生产 PIT、许可、SLO、质量告警、恢复/回填 | fake JQData + 不完整 Tushare |
|
||||
| 研究 | purge/embargo walk-forward、冻结 OOS 和成本后结果 | 再加真实容量、选择治理、registry、drift、champion/challenger | Ridge 只接 synthetic |
|
||||
| 策略收益 | 可报告冻结 OOS,但不能用 synthetic 冒充 | 仍按预注册 mandate 判断,不设全局 Sharpe 门槛 | 无投资价值声明 |
|
||||
| 风险 | A 股规则和事前/事后风险合同 | 独立且不可绕过的真实账户风控,失效即拒单 | 组件存在,未接真实券商 |
|
||||
| 执行 | 本地 fill model、平台薄 wrapper、订单状态机合同 | 有效期规则包、真实 OMS、幂等/重连/重启、真实 TCA | 聚宽仅窄执行 smoke |
|
||||
| 跨引擎 | local/JQ/QMT 同输入层级 parity,未知差异为零 | local/JQ/QMT 做 L1—L5 strict peer;Qlib 只在声明层级比较,另有订单/成交 difference ledger | mock parity,无真 QMT |
|
||||
| QMT | bundle、qmttools、只读 shadow 入口 | 真账号 query/callback/reject/partial/cancel/reconnect/cross-day | fake contract |
|
||||
| 对账 | 影子计划与 broker snapshot 有可重放合同 | 每个交易日现金、持仓、可卖、订单、成交 100% 对账 | 无真实连续序列 |
|
||||
| Shadow | 至少 20 个交易日,验证研究到券商只读链 | 同一冻结候选至少 60 个交易日;重大变更重置 | 0 日真实 QMT |
|
||||
| 实盘 | 明确禁止宣称可实盘 | 先以 E3 独立授权冻结资本/标的/流量/停机 hard caps;只有授权仍有效才能开始或继续 canary。E4 canary claim 反向引用授权,并逐项证明实际资本/notional、标的、流量未超限以及 breach 已触发 fail-closed stop,完成至少 10 个交易日;禁止自动扩容。已在期限内完成的历史 canary 可于到期后晋级复核 | 未授权 |
|
||||
| 运维 | runbook、停机条件、恢复设计 | 监控告警、P0/P1、RPO=0、交易窗口 RTO≤15 分钟及故障演练 | 无生产演练 |
|
||||
| 发布 | 可复现 manifest、schema 和 bundle hash | 原子晋级、审批、签名、依赖清单、回滚演练 | 独立 CI 正在建立 |
|
||||
| 安全 | 凭据不入 Git/OB/artifact,默认关闭券商 mutation | secret store、最小权限、轮换/撤销、日志脱敏、供应链治理 | 仅仓库级边界 |
|
||||
| 合规 | 明确程序化交易义务和需券商确认 | 已完成首次报告,实际版本与报告一致,重大变更受控 | 无券商确认 |
|
||||
| 资本权限 | `NONE` | 小额、单策略、单账户、有限标的且有 hard cap | `NONE` |
|
||||
| 达标标签 | `BASELINE_60_VERIFIED` | `PRE_CANARY_AUTHORIZED` → `LIMITED_LIVE_VERIFIED` | `NOT_BASELINE_60` |
|
||||
|
||||
80 分的 21 个生产 Hard Gate 权重合计正好 80,缺一项都不能用软项补偿。额外
|
||||
20 分描述数据冗余、模型选择治理、真实 TCA 和未来监管模式等成熟度余量。
|
||||
所有正向 evidence 都必须绑定同一 subject、standard/profile canonical hash
|
||||
并经过外部可信 verifier;60 分也不能靠 scorecard 手填。当前 production
|
||||
issuer registry 尚未实现,所以当前只能得到负向、fail-closed 结论;测试
|
||||
verifier 最多得到 `TEST_ONLY_*`,不会得到正式 60/80。
|
||||
|
||||
## 不能混淆的三类分数
|
||||
|
||||
1. **系统成熟度**:本标准 0—100,关心数据、执行、运维、合规。
|
||||
2. **策略证据**:按预注册 mandate 判断 OOS、容量、风险和 canary,不用基础设施
|
||||
分数掩盖没有 Alpha。
|
||||
3. **实际收益**:只来自真实账户或具备适用授权、版本冻结的研究报告,不是系统
|
||||
成熟度分数。
|
||||
|
||||
一个工程完美但没有有效策略的系统,可以有很高的系统成熟度,但不能发布一个
|
||||
没有通过策略 mandate 的 model;一个回测收益很好但缺少风控、券商确认和恢复
|
||||
能力的策略,同样不能上线。
|
||||
|
||||
## 为什么 80 仍不是“机构满分”
|
||||
|
||||
80 面向个人自有资金的受限生产。完整机构化还需要组织隔离、独立复核、多账户/
|
||||
多策略资本分配、法务与合规职能、供应商管理、审计留存、业务连续性、人员权限
|
||||
和可能的持牌/私募义务。未来进入外部资金模式时必须切换独立的
|
||||
`REGULATED_MODE`,不能沿用个人 profile。
|
||||
@@ -0,0 +1,220 @@
|
||||
# Quant OS 80 分受限生产验证标准
|
||||
|
||||
基准日:2026-07-30
|
||||
机器权威:[`profiles/production-80/standard.json`](../../profiles/production-80/standard.json)
|
||||
当前评估:[`profiles/production-80/current-assessment.json`](../../profiles/production-80/current-assessment.json)
|
||||
|
||||
## 结论先说
|
||||
|
||||
80 分不是“代码功能比 60 分多一点”,而是系统责任发生了变化:
|
||||
|
||||
> 60 分证明系统能在受控数据和研究/影子环境中被复现;80 分证明同一个已报告、
|
||||
> 已版本化、可审计的系统,已经在真实券商链路下完成受限资金验证,并且故障时
|
||||
> 会自动停,而不是继续错。
|
||||
|
||||
当前 Quant OS 为 `NOT_BASELINE_60`,80 分机器评估是
|
||||
`BLOCKED_FROM_LIMITED_LIVE`,已验证得分为 0/100。这不等于“没有代码”,而是
|
||||
目前的代码、synthetic、mock 和单次聚宽执行 smoke 没有达到本标准规定的
|
||||
E2—E4 外部证据等级。当前 production trust provider / issuer registry 尚未
|
||||
实现,因此 evaluator 能验证 profile、subject、claim envelope 与 hash 结构,
|
||||
但不能自行颁发正向 80 分资格。
|
||||
|
||||
80 分代表个人自有资金系统的**受限生产已验证**。它不代表:
|
||||
|
||||
- 有超额收益或任何投资回报保证;
|
||||
- 获得机构牌照;
|
||||
- 可以管理第三方资金,或接受他人委托并代其决策、执行交易;
|
||||
- 券商必然开放程序化交易权限;
|
||||
- 法律意见。
|
||||
|
||||
## 1. 判定公式
|
||||
|
||||
状态分两步,避免“尚未实盘,却要先拿实盘证据”的循环:
|
||||
|
||||
1. `PRE_CANARY_AUTHORIZED`:除 `D6-CANARY` 外的 20 个 Hard Gate 全部通过,
|
||||
得分至少 77,60 分、真实 QMT、券商确认、60 日 shadow、P0/P1 和同一冻结
|
||||
subject 全部满足,并取得独立的 E3 可信 canary 授权。该不可变授权绑定标准
|
||||
hash、pre-canary projection、账户/策略、有效期、资本/notional、标的白名单、
|
||||
“申报+撤单”流量和停机条件;只有评估日仍在授权有效期内,状态才能保持
|
||||
`PRE_CANARY_AUTHORIZED` 或 `CANARY_RUNNING`。它不是 E4 实盘结果,也不是
|
||||
常规实盘许可。
|
||||
2. `LIMITED_LIVE_VERIFIED`:完成至少 10 个交易日 canary 后,21 个 Hard Gate
|
||||
全部通过,七个维度**合计**证据得分不低于 80/100,并取得绑定整个 assessment
|
||||
hash 的可信 E4 promotion attestation。D6-CANARY 的 E4 claim 必须反向引用
|
||||
前述授权 ID/hash,并证明 `authorized_at < canary window <= expires_on`;
|
||||
claim 还必须记录 canary 实际最大资本、gross notional、标的、每秒/每日
|
||||
“申报+撤单”流量、是否自动扩容,以及 breach 与 fail-closed stop event 的
|
||||
逐项关联。任一实际值超过授权即不得通过。若 canary 已在授权到期前合法完成,
|
||||
后续 E4 promotion 可以在授权到期后复核这段历史链;到期不能倒推抹除已经发生
|
||||
的受控观察,但也不能继续开放新的 canary 交易。
|
||||
|
||||
完整 80 分必须同时满足:
|
||||
|
||||
1. 七个维度合计证据得分不低于 80/100;
|
||||
2. 60 分 G1—G10 全部通过;
|
||||
3. 所有 Hard Gate 通过;
|
||||
4. 每个维度至少获得该维度 60% 的分数;
|
||||
5. 同一冻结候选累计至少 60 个交易日真实 QMT shadow;
|
||||
6. 受限资金 canary 至少 10 个交易日;
|
||||
7. 没有未关闭 P0/P1;
|
||||
8. 已取得券商程序化交易报告/核查确认,部署版本与报告事实一致;
|
||||
9. 已有真实 QMT 运行证据;
|
||||
10. assessment、每份 evidence claim 和最终 attestation 都绑定相同的
|
||||
candidate、release、model、dataset、账户 pseudonym 与 material-change
|
||||
epoch,以及同一个 canonical `standard.json` SHA-256;Baseline 60 还必须
|
||||
引用可信 evaluator 的签名结果和 canonical profile hash。
|
||||
|
||||
机器 profile 中 21 个 Hard Gate 的权重恰好合计 80;其余 20 分是数据冗余、
|
||||
champion/challenger、真实 TCA 与未来监管模式等成熟度余量。换句话说,80
|
||||
不能靠软项补偿任何生产核心门;81—100 表示更高的冗余和治理深度。
|
||||
|
||||
不可补偿规则:
|
||||
|
||||
- 60 分未通过,最终分封顶 59;
|
||||
- 没有 canary,最终分封顶 79;
|
||||
- 任一 Hard Gate 失败,即使原始分超过 80,也不得上线;
|
||||
- 代码、文档、mock、fixture 和单元测试不能替代真实数据、真实平台、真实券商
|
||||
或持续运行证据;
|
||||
- 不允许人工把 `not_passed` 改成 `passed`。通过项必须提供仓库相对路径、
|
||||
证据等级、SHA-256、绑定同一 subject 的 claim envelope,并由配置在外部
|
||||
trust provider 中的 verifier 核验;最终 attestation 还必须绑定整个
|
||||
assessment 的 canonical hash。
|
||||
- canary 不能跳过 77 分授权:授权使用独立 E3 authority,canary observation
|
||||
使用 E4,并以授权 hash、评估日有效期、时间窗和“授权上限 vs 实际观测”
|
||||
hard caps 形成不可断开的链;风险、数据、规则或对账 breach 必须引用同窗
|
||||
stop event,且该 stop 明确停止交易、禁止自动恢复。
|
||||
- 当前仓库没有生产 verifier 或信任根;单独编辑 JSON、复用无关文件或拿
|
||||
`standard.json` 冒充 E4 都不能产生正向资格。测试 verifier 只能产生
|
||||
`TEST_ONLY_PRE_CANARY_PASS` / `TEST_ONLY_STRUCTURAL_PASS`,`qualified`
|
||||
仍为 false。
|
||||
|
||||
cap 用于分数呈现,Hard Gate/prerequisite 用于资格阻断,二者是纵深防御。
|
||||
这些时长、维度和内部限额是 Quant OS 的工程标准,不是监管机关原文。
|
||||
|
||||
## 2. 证据等级
|
||||
|
||||
| 等级 | 定义 | 可证明什么 | 不能证明什么 |
|
||||
| --- | --- | --- | --- |
|
||||
| E0 | 口头结论、设计文档、源文件存在 | 意图和设计边界 | 代码可运行、市场有效、平台有效 |
|
||||
| E1 | 确定性本地测试、synthetic、fixture、mock | 算法/合同/故障路径在受控环境成立 | 真实数据、平台、券商和收益 |
|
||||
| E2 | 授权真实数据、冻结 OOS,或外部权威原始证据 | 数据/研究可重放,或规则/权属来源可核验 | 券商执行、持续运行 |
|
||||
| E3 | 真实平台/券商 shadow,或绑定生产环境的运维、数据、安全、合规控制观察/独立 canary 授权 | 真实平台、账户状态、生产控制链路或受限实盘授权边界 | 已发生的资金实盘行为 |
|
||||
| E4 | 受限资金、受控 live canary | 小范围真实生产行为 | 自动扩容、机构级规模或投资收益 |
|
||||
|
||||
每个控制项声明最低证据等级。更高等级可以覆盖更低等级,但 claim 的 control、
|
||||
subject、evidence type、authority、时点和来源 hash 必须满足该项 acceptance;
|
||||
不能拿一份无关 E4 文件替代数据或研究证据。当前实现已经强制 control/subject/
|
||||
standard/profile hash、pre-authorization chain 和带 verifier/issuer/key/policy
|
||||
provenance 的验证决定;生产 issuer registry、允许的 evidence type/
|
||||
authority/freshness policy 仍是下一阶段交付,因此当前不能颁发正向 60/80。
|
||||
|
||||
## 3. 七个评分维度
|
||||
|
||||
| 维度 | 分值 | 核心问题 | 关键 Hard Gate |
|
||||
| --- | ---: | --- | --- |
|
||||
| D1 数据与 PIT 真值 | 15 | 决策日看到的是否真是当时可得数据,数据是否有许可、时效和恢复能力 | 完整 PIT、许可/lineage/数据 SLO |
|
||||
| D2 研究与模型治理 | 15 | 是否在真实长样本 OOS 下冻结,模型是否可追责、过期和回滚 | OOS、成本/容量、Model Registry |
|
||||
| D3 跨引擎一致性 | 10 | 本地、聚宽、QMT strict peer 是否一致;Qlib 是否只在声明层级比较 | local/JQ/QMT L1—L5 identity、difference ledger |
|
||||
| D4 执行与独立风控 | 15 | 策略能否绕过验资验券、规则、OMS 和 kill-switch | 独立风控、2026 规则包、幂等 OMS |
|
||||
| D5 真实 QMT 与券商真值 | 15 | 查询、回报、重连、持仓、可卖、订单和成交是否由券商事实驱动 | 真 QMT、逐日 100% 对账、可重放证据 |
|
||||
| D6 运维、灾备与审计 | 15 | 是否经历足够长的 shadow/canary,故障能否恢复 | 60 日 shadow、10 日 canary、RPO/RTO |
|
||||
| D7 合规与安全 | 15 | 是否先报告后交易、账户和业务符合本 profile 边界、系统与报告一致、凭据受控 | 个人自营范围、券商确认、异常交易防线、安全 |
|
||||
|
||||
25 个控制项、权重和 acceptance 以机器 profile 为准。文档只解释标准,不替代
|
||||
profile。
|
||||
|
||||
## 4. 当前差距
|
||||
|
||||
当前评估全部保持 `not_passed`,主要原因如下:
|
||||
|
||||
- G1—G10 仍是 0/10,未满足 80 分前置;
|
||||
- Ridge 五层链的上游研究输入仍是 synthetic;
|
||||
- JQData 真实完整 PIT 长样本未进入冻结模型和 TargetPackage tape;
|
||||
- 聚宽只有一次 synthetic-research TargetPackage execution consumer 观察;
|
||||
- QMT 只有语法、fake contract 与只读 shadow 框架,没有真实账号证据;
|
||||
- 没有 60 个交易日真实 shadow;
|
||||
- 没有受限资金 canary;
|
||||
- 没有券商程序化交易报告/核查确认;
|
||||
- 2026-07-06 生效(含部分继续暂缓实施条款)的交易规则尚未形成状态化规则包;
|
||||
- 没有生产数据 SLO、真实 TCA、持续监控、RPO/RTO 演练、签名发布和凭据轮换证据。
|
||||
- 没有 production trust provider、issuer registry 和可验证的 promotion
|
||||
attestation,因此 evaluator 即使未来看到手工 `passed` 也不会自行授予资格。
|
||||
|
||||
因此,本轮交付只建立“能客观判断什么时候达到 80”的标准和架构,不声称已经
|
||||
达到 80。
|
||||
|
||||
## 5. 监管硬事实与内部工程门槛
|
||||
|
||||
下面只概括与系统设计直接相关的规则事实。券商协议和后续有效规则可能更严格,
|
||||
实盘以券商书面确认及当时有效规则为准。
|
||||
|
||||
### 5.1 规则事实
|
||||
|
||||
1. 《证券法》第 45 条将程序自动生成或下达交易指令纳入程序化交易,要求符合
|
||||
证监会规定并向交易所报告,不得影响交易系统安全或正常交易秩序。
|
||||
2. 《证券市场程序化交易管理规定(试行)》自 2024-10-08 实施。客户必须向
|
||||
券商报告账户、资金/杠杆、策略、指令执行、最高申报速率、日最高申报笔数、
|
||||
软件名称/版本/开发主体和联系人等;券商核查确认后才能开始程序化交易。
|
||||
3. 沪、深、北程序化交易实施细则自 2025-07-07 施行,个人投资者明确纳入。
|
||||
资金、杠杆、联系人、策略、速率、软件或停止交易等重大变化需要按规定变更
|
||||
报告,实际交易行为会与报告事实比对。
|
||||
4. 交易所重点监控瞬时申报速率异常、频繁瞬时撤单、频繁拉抬打压和短时间大额
|
||||
成交。低于高频定义不等于监管安全。
|
||||
5. 单账户申报加撤单最高达到 300 笔/秒,或单日达到 20,000 笔,属于高频交易。
|
||||
这是高频分类阈值,不是低于阈值即可免责的安全港。
|
||||
6. 沪深《交易规则(2026 年修订)》自 2026-07-06 施行,但上交所通知同时
|
||||
明确部分既有条文继续暂缓实施。规则生效不等于每一条款均已启用;系统不能
|
||||
继续依赖旧硬编码,也必须建模 `effective/suspended/resumed/repealed` 状态。
|
||||
7. 对实施细则列明情形,会员应当按照委托协议等约定采取拒绝程序化交易委托、
|
||||
撤销相关申报等措施;具体适用仍以券商协议和当时有效规则为准。
|
||||
8. 突发故障或重大差错时,系统必须具备暂停、撤单、错误处理和应急处置能力。
|
||||
|
||||
### 5.2 Quant OS 内部门槛
|
||||
|
||||
- 账户模式固定为 `PERSONAL_PROPRIETARY`。出现第三方资金、接受他人委托并代其
|
||||
决策或执行交易、向他人提供收费投资顾问服务,或与他人共享交易控制权时,
|
||||
系统 fail closed,转入单独的法律/合规适用性评估。仅购买或阅读第三方研究
|
||||
信息本身不等于代客指令。
|
||||
- 没有券商确认、实际委托协议和版本一致性证据,TradeBrokerPort 保持禁用。
|
||||
- machine profile 的日频 Baseline 默认上限按“Quant OS 发出的全部委托申报
|
||||
尝试 + 撤单请求”合计,每账户及实际控制账户组均不超过 10 条/秒、
|
||||
1,000 条/交易日。日撤单率 = 策略主动撤单请求 / 已接受委托申报;拒单不进
|
||||
分母,部分成交后主动撤单进分子,交易所自动撤单不进分子。达到 30% 预警并
|
||||
人工复核;更严规则优先。这是内部限额,不是监管安全港。
|
||||
- 当前交易能力范围明确为沪深;北交所为 `excluded/fail_closed`。规则包按市场、
|
||||
板块、证券类型及发布、生效、暂缓、恢复、废止日期版本化。规则未知、过期、
|
||||
暂缓或不完整时拒单。
|
||||
- Colab 只允许上传供应商许可范围内的脱敏研究数据,绝不上传券商凭据、账户号、
|
||||
持仓、成交或个人敏感信息。
|
||||
|
||||
## 6. 官方来源
|
||||
|
||||
- [中华人民共和国证券法(全国人大)](https://www.npc.gov.cn/c2/c30834/201912/t20191231_304436.html)
|
||||
- [证券市场程序化交易管理规定(试行)(证监会 PDF)](https://www.csrc.gov.cn/csrc/c101954/c7480579/7480579/files/%E9%99%84%E4%BB%B61%EF%BC%9A%E8%AF%81%E5%88%B8%E5%B8%82%E5%9C%BA%E7%A8%8B%E5%BA%8F%E5%8C%96%E4%BA%A4%E6%98%93%E7%AE%A1%E7%90%86%E8%A7%84%E5%AE%9A%EF%BC%88%E8%AF%95%E8%A1%8C%EF%BC%89.pdf)
|
||||
- [上交所程序化交易管理实施细则](https://www.sse.com.cn/lawandrules/sselawsrules2025/trade/universal/c/c_20250612_10781696.shtml)
|
||||
- [深交所程序化交易管理实施细则通知](https://www.szse.cn/lawrules/rule/allrules/bussiness/t20250403_612770.html)
|
||||
- [北交所程序化交易管理实施细则](https://www.bse.cn/jygl_list/200025383.html)
|
||||
- [上交所交易规则(2026 年修订)](https://www.sse.com.cn/lawandrules/sselawsrules2025/stocks/exchange/c/c_20260424_10816482.shtml)
|
||||
- [深交所交易规则(2026 年修订)](https://www.szse.cn/lawrules/rule/trade/current/t20260424_620190.html)
|
||||
- [程序化交易委托协议示范文本(中证协)](https://www.sac.net.cn/zlgl/zlgz/202512/t20251231_69956.html)
|
||||
- [JR/T 0303—2024 投资研究时序数据参考模型](https://www.csrc.gov.cn/csrc/c100028/c7483073/content.shtml)
|
||||
- [个人信息保护法(全国人大)](https://www.npc.gov.cn/npc/c2/c30834/202108/t20210820_313088.html)
|
||||
|
||||
如果未来管理外部资金,个人 profile 立即失效。量化私募还涉及系统/数据安全、
|
||||
策略上线、防自成交/反向交易、超限控制以及长期保存等要求。参见
|
||||
[私募证券投资基金运作指引(中基协 PDF)](https://www.amac.org.cn/xwfb/xhyw/202304/P020231126355219356728.pdf)。
|
||||
|
||||
## 7. 机器验收
|
||||
|
||||
```bash
|
||||
PYTHONPATH=src:. python -m quant_os standard validate
|
||||
PYTHONPATH=src:. python -m quant_os standard evaluate
|
||||
```
|
||||
|
||||
第一条验证标准本身权重、控制项和证据等级是否自洽;第二条重新评估当前证据。
|
||||
评估成功运行只表示“当前结构与负向结论有效”,不表示达到 80。必须看
|
||||
`qualification_state`、`qualified`、`pre_canary_authorization`、
|
||||
`canary_authorization_chain_verified`、`standard_canonical_sha256`、
|
||||
`verification_environment`、`trusted_attestation_verified`、`hard_gate_failures`、
|
||||
`domain_floor_failures` 和 `prerequisites`。当前 CLI 不注入生产 verifier,
|
||||
这是明确的未完成项,而不是允许手工绕过的入口。
|
||||
@@ -0,0 +1,5 @@
|
||||
# portable-momentum smoke
|
||||
|
||||
`portable-momentum-v1` is a connectivity and contract example. It must never be
|
||||
promoted as the Ridge Baseline or as investment evidence. The default bundler
|
||||
compatibility mode will move here after golden-vector parity is frozen.
|
||||
+5
-5
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"system": "Quant OS / quant60 baseline kernel",
|
||||
"audit_as_of": "2026-07-26",
|
||||
"audit_as_of": "2026-07-30",
|
||||
"claim_policy": {
|
||||
"baseline_threshold": 60,
|
||||
"all_gates_required": true,
|
||||
@@ -35,7 +35,7 @@
|
||||
"status": "not_scored",
|
||||
"reason": "Quant OS 已接通本地 synthetic 五层候选链,能冻结 Ridge ModelBundle、逐层 trace 和无股数 TargetPackageV1;一个 synthetic-research TargetPackage 已在真实 JoinQuant hosted 环境完成消费、账户/开盘价绑定、3 笔委托和 3 笔成交,因此 execution 层已有真实平台观察。但真实 provider-data 长样本/OOS 尚未接入,JoinQuant 没有重跑上游 Universe/Alpha/Portfolio/Risk,QMT peer 与逐层真实数据 parity 仍缺失,factor risk、CVXPY 和 guarded TWAP/POV 也尚未进入当前主链。因此它仍是 NOT_BASELINE_60,G9 未通过。",
|
||||
"verified_supporting_facts": [
|
||||
"The current standard-library suite completed 285 tests with OK and 6 optional-runtime skips; successful local tests do not prove real-platform, broker or compliance gates.",
|
||||
"The independent-repository standard-library suite completed 315 tests with OK and 6 optional-runtime skips; successful local tests do not prove real-platform, broker or compliance gates.",
|
||||
"The local synthetic research entrypoint now executes one causally linked Universe → Ridge Alpha → cost/risk-aware Portfolio → post-risk gate → reference Execution path, freezes a replayable ModelBundle, and publishes a hash-linked TargetPackageV1 and five-layer trace. This is structural synthetic evidence only.",
|
||||
"The connected candidate currently uses 60-session diagonal realized variance, explicit spread/fee/square-root-impact cost forecasts, the deterministic constrained optimizer, a post-target risk gate, and reference lot/T+1 order deltas. factor_risk.py, the optional CVXPY solver mode, and guarded TWAP/POV are not selected by the current main slice.",
|
||||
"JoinQuant and QMT target-package modes verify an exact embedded package tape, match the declared dual clock, bind platform equity/positions/sellable quantities/prices, and reject missing, duplicate or tampered packages without falling back to momentum. Both modes have local harness/contract evidence; JoinQuant execution alone now also has one narrow real hosted smoke.",
|
||||
@@ -58,8 +58,8 @@
|
||||
"authorized JQData market snapshot, licence/lineage record and a frozen provider-data candidate tape",
|
||||
"authorized real-data multi-period JoinQuant TargetPackage run plus same-input local/JoinQuant layer-by-layer reconciliation",
|
||||
"real QMT TargetPackage built-in and qmttools backtest exports",
|
||||
"real QMT historical-ST positive control, terminal/xtquant build record, broker query contract and trade-amount semantic probes, callback/restart and at least 20 trading days of shadow evidence",
|
||||
"continuous reconciliation, recovery drills and broker compliance confirmation"
|
||||
"real QMT historical-ST positive control, terminal/xtquant build record, broker query contract and trade-amount semantic probes, and callback/restart evidence; Baseline 60 requires at least 20 trading days of shadow, while the current Production 80 gap requires at least 60 trading days for the same frozen candidate",
|
||||
"continuous reconciliation, recovery drills and broker programmatic-trading report/verification confirmation"
|
||||
]
|
||||
},
|
||||
"gates": [
|
||||
@@ -167,7 +167,7 @@
|
||||
"required_evidence": [
|
||||
"真实平台跨引擎 manifest",
|
||||
"逐层 signal/target/order 差异",
|
||||
"QMT 至少 20 个交易日影子记录"
|
||||
"Baseline 60 所需的 QMT 至少 20 个交易日影子记录;Production 80 另要求同一冻结候选至少 60 个交易日"
|
||||
],
|
||||
"evidence": []
|
||||
},
|
||||
|
||||
@@ -45,8 +45,9 @@
|
||||
"import shutil\n",
|
||||
"import subprocess\n",
|
||||
"import sys\n",
|
||||
"import tempfile\n",
|
||||
"\n",
|
||||
"REPO_URL = \"https://git.gomars.fun/boat/quants-strategies.git\"\n",
|
||||
"REPO_URL = \"https://git.gomars.fun/boat/quant-os.git\"\n",
|
||||
"REPO_BRANCH = \"master\"\n",
|
||||
"\n",
|
||||
"def run(command, *, env=None):\n",
|
||||
@@ -54,19 +55,23 @@
|
||||
" subprocess.run(command, check=True, env=env)\n",
|
||||
"\n",
|
||||
"cwd = Path.cwd().resolve()\n",
|
||||
"if cwd.name == \"quant-os\" and (cwd / \"pyproject.toml\").is_file():\n",
|
||||
"if (cwd / \"pyproject.toml\").is_file() and (cwd / \"src\" / \"quant60\").is_dir():\n",
|
||||
" project = cwd\n",
|
||||
"elif (cwd / \"quant-os\" / \"pyproject.toml\").is_file():\n",
|
||||
" project = cwd / \"quant-os\"\n",
|
||||
"else:\n",
|
||||
" checkout = Path(\"/content/quants-strategies\")\n",
|
||||
" checkout = Path(\"/content/quant-os\")\n",
|
||||
" if not checkout.exists():\n",
|
||||
" run([\"git\", \"clone\", \"--depth\", \"1\", \"--branch\", REPO_BRANCH, REPO_URL, str(checkout)])\n",
|
||||
" project = checkout / \"quant-os\"\n",
|
||||
" project = checkout\n",
|
||||
"\n",
|
||||
"if not (project / \"pyproject.toml\").is_file():\n",
|
||||
" raise RuntimeError(f\"Quant OS project not found at {project}\")\n",
|
||||
"os.chdir(project)\n",
|
||||
"cache_root = Path(tempfile.gettempdir()) / \"quant-os-notebook-cache\"\n",
|
||||
"cache_root.mkdir(parents=True, exist_ok=True)\n",
|
||||
"os.environ.setdefault(\"MPLCONFIGDIR\", str(cache_root / \"matplotlib\"))\n",
|
||||
"os.environ.setdefault(\"XDG_CACHE_HOME\", str(cache_root / \"xdg\"))\n",
|
||||
"print({\"project\": str(project), \"python\": sys.version})"
|
||||
]
|
||||
},
|
||||
@@ -114,7 +119,11 @@
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"metadata": {
|
||||
"tags": [
|
||||
"optional"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"equity = json.loads(Path(\"artifacts/colab-smoke/equity.json\").read_text(encoding=\"utf-8\"))\n",
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# Quant OS public status operations
|
||||
|
||||
The repository is the source of truth for the public status page. A deployment
|
||||
contains exactly:
|
||||
|
||||
```text
|
||||
/srv/www/quant-os/
|
||||
releases/<git-sha>/
|
||||
index.html
|
||||
status-data.js
|
||||
status -> releases/<git-sha>
|
||||
```
|
||||
|
||||
Deployment invariants:
|
||||
|
||||
1. run `python3 tools/build_public_status.py --check` and the full local suite;
|
||||
2. deploy from a clean, pushed commit and name the release with that full SHA;
|
||||
3. acquire the web-root deployment lock and copy into a same-filesystem
|
||||
`.incoming-<sha>` directory;
|
||||
4. require exactly two regular, non-symlink payload files and compare local and
|
||||
staged SHA-256 for both;
|
||||
5. atomically rename the verified incoming directory to the immutable release;
|
||||
6. create a temporary relative symlink and activate it with one atomic rename;
|
||||
7. install [`nginx/quant-os-static.conf`](nginx/quant-os-static.conf), run
|
||||
`nginx -t`, then reload;
|
||||
8. verify the canonical page, generated JavaScript, cache header and all legacy
|
||||
redirects from a public client;
|
||||
9. keep the previous release for rollback.
|
||||
|
||||
The server-side deploy and activation are executable and fail closed:
|
||||
|
||||
```bash
|
||||
ops/deploy_static_status.sh <full-git-sha> <staged-status-dir>
|
||||
ops/activate_static_status.sh <previous-full-git-sha>
|
||||
```
|
||||
|
||||
The first command rejects a symlinked source root, symlink payloads,
|
||||
directories and any third source entry,
|
||||
holds `.status-deploy.lock` across staging and activation, verifies both
|
||||
SHA-256 values, atomically promotes `.incoming-<sha>`, and refuses to overwrite
|
||||
an existing release. Promoted payload files and their release directory are
|
||||
owner read-only. The second command is the rollback/activation primitive;
|
||||
when called directly it takes the same lock and switches only a relative
|
||||
symlink. A stale lock or incoming directory means a prior process may have
|
||||
crashed: inspect it and confirm no deploy is running before removing it.
|
||||
|
||||
Nginx is deliberately two-stage:
|
||||
|
||||
1. include `nginx/quant-os-static.conf` in the existing HTTPS server block,
|
||||
run `nginx -t`, reload, and verify `/quant-os/status/`;
|
||||
2. only then include `nginx/quant-os-legacy-redirects-302.conf`, preserving the
|
||||
existing broad `/quants-strategies/` route for other projects;
|
||||
3. after an observation period and a separate production change, replace the
|
||||
302 include with `quant-os-legacy-redirects-308.conf`.
|
||||
|
||||
Rollback only changes the `status` symlink to the previously verified release
|
||||
and reloads no application state. The page is a static, redacted build-time
|
||||
snapshot; it must never contain credentials, local paths, market data, account
|
||||
identifiers, positions or fills.
|
||||
Executable
+109
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "$#" -lt 1 || "$#" -gt 2 ]]; then
|
||||
echo "usage: $0 <40-char-git-sha> [web-root]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
release_sha="$1"
|
||||
web_root="${2:-/srv/www/quant-os}"
|
||||
|
||||
if [[ ! "${release_sha}" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
echo "release SHA must be a 40-character lowercase Git SHA" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [[ "${web_root}" != /* || "${web_root}" == "/" ]]; then
|
||||
echo "web root must be an explicit absolute non-root path" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [[ ! -d "${web_root}" ]]; then
|
||||
echo "web root does not exist: ${web_root}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
lock_dir="${web_root}/.status-deploy.lock"
|
||||
lock_acquired=false
|
||||
next_link=""
|
||||
next_link_created=false
|
||||
|
||||
cleanup() {
|
||||
exit_status="$?"
|
||||
if [[ "${next_link_created}" == "true" && -L "${next_link}" ]]; then
|
||||
rm -f "${next_link}"
|
||||
fi
|
||||
if [[ "${lock_acquired}" == "true" ]]; then
|
||||
rmdir "${lock_dir}" 2>/dev/null || true
|
||||
fi
|
||||
exit "${exit_status}"
|
||||
}
|
||||
trap cleanup EXIT HUP INT TERM
|
||||
|
||||
if [[ "${QUANT_OS_STATUS_DEPLOY_LOCK_HELD:-0}" == "1" ]]; then
|
||||
if [[ ! -d "${lock_dir}" || -L "${lock_dir}" ]]; then
|
||||
echo "caller asserted a deployment lock that is not held" >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
if ! mkdir "${lock_dir}" 2>/dev/null; then
|
||||
echo "another status deployment or activation holds ${lock_dir}" >&2
|
||||
exit 1
|
||||
fi
|
||||
lock_acquired=true
|
||||
fi
|
||||
|
||||
release_dir="${web_root}/releases/${release_sha}"
|
||||
if [[ ! -d "${release_dir}" || -L "${release_dir}" ]]; then
|
||||
echo "release must be a regular directory: ${release_dir}" >&2
|
||||
exit 1
|
||||
fi
|
||||
release_entries=()
|
||||
while IFS= read -r -d '' entry; do
|
||||
release_entries+=("${entry}")
|
||||
done < <(find "${release_dir}" -mindepth 1 -maxdepth 1 -print0 2>/dev/null)
|
||||
if [[ "${#release_entries[@]}" != "2" ]]; then
|
||||
echo "release must contain exactly index.html and status-data.js" >&2
|
||||
exit 1
|
||||
fi
|
||||
for required in index.html status-data.js; do
|
||||
if [[ ! -f "${release_dir}/${required}" || -L "${release_dir}/${required}" ]]; then
|
||||
echo "release must contain a regular non-symlink ${required}" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
for entry in "${release_entries[@]}"; do
|
||||
case "$(basename "${entry}")" in
|
||||
index.html|status-data.js)
|
||||
;;
|
||||
*)
|
||||
echo "unexpected release entry: $(basename "${entry}")" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
next_link="${web_root}/.status-next-${release_sha}"
|
||||
if [[ -e "${next_link}" || -L "${next_link}" ]]; then
|
||||
echo "temporary activation link already exists: ${next_link}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ln -s "releases/${release_sha}" "${next_link}"
|
||||
next_link_created=true
|
||||
if [[ -e "${web_root}/status" && ! -L "${web_root}/status" ]]; then
|
||||
echo "active status path exists and is not a symlink" >&2
|
||||
exit 1
|
||||
fi
|
||||
if mv --version >/dev/null 2>&1; then
|
||||
mv -Tf "${next_link}" "${web_root}/status"
|
||||
else
|
||||
mv -fh "${next_link}" "${web_root}/status"
|
||||
fi
|
||||
next_link_created=false
|
||||
|
||||
active_target="$(readlink "${web_root}/status")"
|
||||
if [[ "${active_target}" != "releases/${release_sha}" ]]; then
|
||||
echo "active release verification failed" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "activated ${release_sha}"
|
||||
Executable
+164
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "$#" -lt 2 || "$#" -gt 3 ]]; then
|
||||
echo "usage: $0 <40-char-git-sha> <source-dir> [web-root]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
release_sha="$1"
|
||||
source_dir="$2"
|
||||
web_root="${3:-/srv/www/quant-os}"
|
||||
|
||||
if [[ ! "${release_sha}" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
echo "release SHA must be a 40-character lowercase Git SHA" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [[ ! -d "${source_dir}" ]]; then
|
||||
echo "source directory does not exist" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [[ -L "${source_dir}" ]]; then
|
||||
echo "source directory must not be a symlink" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [[ "${web_root}" != /* || "${web_root}" == "/" ]]; then
|
||||
echo "web root must be an explicit absolute non-root path" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
source_entries=()
|
||||
while IFS= read -r -d '' entry; do
|
||||
source_entries+=("${entry}")
|
||||
done < <(find "${source_dir}" -mindepth 1 -maxdepth 1 -print0)
|
||||
if [[ "${#source_entries[@]}" != "2" ]]; then
|
||||
echo "source must contain exactly index.html and status-data.js" >&2
|
||||
exit 1
|
||||
fi
|
||||
for required in index.html status-data.js; do
|
||||
if [[ ! -f "${source_dir}/${required}" || -L "${source_dir}/${required}" ]]; then
|
||||
echo "source must contain a regular non-symlink ${required}" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
for entry in "${source_entries[@]}"; do
|
||||
case "$(basename "${entry}")" in
|
||||
index.html|status-data.js)
|
||||
;;
|
||||
*)
|
||||
echo "unexpected source entry: $(basename "${entry}")" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
if [[ ! -f "${entry}" || -L "${entry}" ]]; then
|
||||
echo "source entries must be regular non-symlink files" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
release_dir="${web_root}/releases/${release_sha}"
|
||||
incoming_dir="${web_root}/.incoming-${release_sha}"
|
||||
lock_dir="${web_root}/.status-deploy.lock"
|
||||
incoming_created=false
|
||||
lock_acquired=false
|
||||
|
||||
cleanup() {
|
||||
exit_status="$?"
|
||||
if [[ "${incoming_created}" == "true" ]]; then
|
||||
chmod 0755 "${incoming_dir}" 2>/dev/null || true
|
||||
rm -f \
|
||||
"${incoming_dir}/index.html" \
|
||||
"${incoming_dir}/status-data.js"
|
||||
rmdir "${incoming_dir}" 2>/dev/null || true
|
||||
fi
|
||||
if [[ "${lock_acquired}" == "true" ]]; then
|
||||
rmdir "${lock_dir}" 2>/dev/null || true
|
||||
fi
|
||||
exit "${exit_status}"
|
||||
}
|
||||
trap cleanup EXIT HUP INT TERM
|
||||
|
||||
install -d -m 0755 "${web_root}"
|
||||
if ! mkdir "${lock_dir}" 2>/dev/null; then
|
||||
echo "another status deployment or activation holds ${lock_dir}" >&2
|
||||
exit 1
|
||||
fi
|
||||
lock_acquired=true
|
||||
|
||||
if [[ -L "${web_root}/releases" ]]; then
|
||||
echo "releases directory must not be a symlink" >&2
|
||||
exit 1
|
||||
fi
|
||||
install -d -m 0755 "${web_root}/releases"
|
||||
if [[ -L "${web_root}/releases" ]]; then
|
||||
echo "releases directory must not be a symlink" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -e "${release_dir}" || -L "${release_dir}" ]]; then
|
||||
echo "refusing to overwrite existing release: ${release_dir}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -e "${incoming_dir}" || -L "${incoming_dir}" ]]; then
|
||||
echo "stale incoming path must be inspected and removed: ${incoming_dir}" >&2
|
||||
exit 1
|
||||
fi
|
||||
install -d -m 0755 "${incoming_dir}"
|
||||
incoming_created=true
|
||||
install -m 0644 "${source_dir}/index.html" "${incoming_dir}/index.html"
|
||||
install -m 0644 \
|
||||
"${source_dir}/status-data.js" \
|
||||
"${incoming_dir}/status-data.js"
|
||||
|
||||
hash_command=()
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
hash_command=(sha256sum)
|
||||
elif command -v shasum >/dev/null 2>&1; then
|
||||
hash_command=(shasum -a 256)
|
||||
else
|
||||
echo "sha256sum or shasum is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for required in index.html status-data.js; do
|
||||
source_hash="$("${hash_command[@]}" "${source_dir}/${required}" | awk '{print $1}')"
|
||||
incoming_hash="$("${hash_command[@]}" "${incoming_dir}/${required}" | awk '{print $1}')"
|
||||
if [[ "${source_hash}" != "${incoming_hash}" ]]; then
|
||||
echo "release hash mismatch: ${required}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "${required} ${incoming_hash}"
|
||||
done
|
||||
|
||||
incoming_entries=()
|
||||
while IFS= read -r -d '' entry; do
|
||||
incoming_entries+=("${entry}")
|
||||
done < <(find "${incoming_dir}" -mindepth 1 -maxdepth 1 -print0)
|
||||
if [[ "${#incoming_entries[@]}" != "2" ]]; then
|
||||
echo "incoming release must contain exactly two files" >&2
|
||||
exit 1
|
||||
fi
|
||||
for required in index.html status-data.js; do
|
||||
if [[ ! -f "${incoming_dir}/${required}" || -L "${incoming_dir}/${required}" ]]; then
|
||||
echo "incoming release is not a regular immutable payload: ${required}" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
chmod 0444 \
|
||||
"${incoming_dir}/index.html" \
|
||||
"${incoming_dir}/status-data.js"
|
||||
|
||||
if mv --version >/dev/null 2>&1; then
|
||||
mv -T "${incoming_dir}" "${release_dir}"
|
||||
else
|
||||
if [[ -e "${release_dir}" || -L "${release_dir}" ]]; then
|
||||
echo "refusing to overwrite concurrently created release" >&2
|
||||
exit 1
|
||||
fi
|
||||
mv "${incoming_dir}" "${release_dir}"
|
||||
fi
|
||||
incoming_created=false
|
||||
chmod 0555 "${release_dir}"
|
||||
|
||||
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
export QUANT_OS_STATUS_DEPLOY_LOCK_HELD=1
|
||||
"${script_dir}/activate_static_status.sh" "${release_sha}" "${web_root}"
|
||||
@@ -0,0 +1,26 @@
|
||||
# Temporary compatibility redirects. Install only after the new canonical
|
||||
# route has passed public verification. Keep these as 302 during observation.
|
||||
|
||||
location = /quants-strategies/quant-os {
|
||||
return 302 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quants-strategies/quant-os/ {
|
||||
return 302 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quants-strategies/quant-os/status {
|
||||
return 302 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quants-strategies/quant-os/status/ {
|
||||
return 302 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quants-strategies/quant-os/status/index.html {
|
||||
return 302 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quants-strategies/quant-os/status/status-data.js {
|
||||
return 302 https://$host/quant-os/status/status-data.js;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
# Stable compatibility redirects. Replace the temporary 302 include with this
|
||||
# file only after an observation period and an explicit production change.
|
||||
|
||||
location = /quants-strategies/quant-os {
|
||||
return 308 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quants-strategies/quant-os/ {
|
||||
return 308 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quants-strategies/quant-os/status {
|
||||
return 308 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quants-strategies/quant-os/status/ {
|
||||
return 308 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quants-strategies/quant-os/status/index.html {
|
||||
return 308 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quants-strategies/quant-os/status/status-data.js {
|
||||
return 308 https://$host/quant-os/status/status-data.js;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
# Quant OS public status routes.
|
||||
#
|
||||
# This snippet belongs inside the gomars.fun HTTPS server block. The release
|
||||
# payload is published under /srv/www/quant-os/releases/<git-sha>/ and the
|
||||
# /srv/www/quant-os/status symlink is switched atomically after verification.
|
||||
|
||||
location = /quant-os {
|
||||
return 308 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quant-os/ {
|
||||
return 308 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quant-os/status {
|
||||
return 308 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location = /quant-os/status/index.html {
|
||||
return 308 https://$host/quant-os/status/;
|
||||
}
|
||||
|
||||
location ^~ /quant-os/status/ {
|
||||
root /srv/www;
|
||||
index index.html;
|
||||
try_files $uri $uri/ =404;
|
||||
add_header Cache-Control "no-cache" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
# Quant OS maturity profiles
|
||||
|
||||
`baseline-60` and `production-80` are evidence profiles. They are not package
|
||||
names and they do not change the trading strategy.
|
||||
|
||||
- `baseline-60`: the existing G1–G10 research-to-shadow baseline.
|
||||
- `production-80`: the limited-production standard and fail-closed evaluator.
|
||||
|
||||
The score is never inferred from source-file count. A control earns points only
|
||||
when its required evidence tier, immutable file hash, frozen subject and
|
||||
canonical standard/profile hash are present and a verifier returns provenance.
|
||||
The repository has no production issuer registry; test verification can never
|
||||
emit a production qualification.
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"profile_id": "quant-os-baseline-60",
|
||||
"claim": "RESEARCH_TO_SHADOW_BASELINE",
|
||||
"source_of_truth": "gate_scorecard.json",
|
||||
"required_gates": [
|
||||
"G1",
|
||||
"G2",
|
||||
"G3",
|
||||
"G4",
|
||||
"G5",
|
||||
"G6",
|
||||
"G7",
|
||||
"G8",
|
||||
"G9",
|
||||
"G10"
|
||||
],
|
||||
"all_gates_required": true,
|
||||
"minimum_score": 60,
|
||||
"minimum_shadow_trading_days": 20,
|
||||
"trusted_verification_required": true,
|
||||
"verification_claim": {
|
||||
"schema": "quant-os-evidence-claim/1.0",
|
||||
"control_id": "BASELINE_60",
|
||||
"evidence_type": "baseline60_qualification_attestation",
|
||||
"minimum_evidence_tier": "E3",
|
||||
"profile_binding": "canonical_json_sha256"
|
||||
},
|
||||
"capital_authority": "NONE"
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"standard_id": "quant-os-production-80-v1",
|
||||
"standard_canonical_sha256": "44fc6c168ade4c6de15569214d88b96ce47b22b4120b10c80eb6f212fad5e662",
|
||||
"assessed_as_of": "2026-07-30",
|
||||
"claim": "CURRENT_CANDIDATE_IS_NOT_80",
|
||||
"subject": {
|
||||
"candidate_id": "UNASSIGNED",
|
||||
"release_sha256": null,
|
||||
"model_sha256": null,
|
||||
"dataset_sha256": null,
|
||||
"account_scope_id": null,
|
||||
"material_change_epoch": null
|
||||
},
|
||||
"pre_canary_attestation": null,
|
||||
"qualification_attestation": null,
|
||||
"baseline_60": {
|
||||
"all_gates_passed": false,
|
||||
"gates_passed": 0,
|
||||
"gates_total": 10,
|
||||
"profile_id": "quant-os-baseline-60",
|
||||
"profile_canonical_sha256": "34adf09a824299ddb07c17f56751527ddf96ff5e494e792be0c65da4f8acdeb3",
|
||||
"verification": null
|
||||
},
|
||||
"observations": {
|
||||
"real_qmt_observed": false,
|
||||
"broker_programmatic_confirmation": false,
|
||||
"shadow_trading_days": 0,
|
||||
"canary_trading_days": 0,
|
||||
"open_p0": null,
|
||||
"open_p1": null
|
||||
},
|
||||
"controls": [
|
||||
{
|
||||
"id": "D1-PIT",
|
||||
"status": "not_passed",
|
||||
"evidence": [],
|
||||
"note": "No authorized complete production PIT dataset has reached the frozen Ridge path."
|
||||
},
|
||||
{
|
||||
"id": "D1-OPS",
|
||||
"status": "not_passed",
|
||||
"evidence": [],
|
||||
"note": "No production freshness SLO, entitlement record and restore history."
|
||||
},
|
||||
{
|
||||
"id": "D1-REDUNDANCY",
|
||||
"status": "not_passed",
|
||||
"evidence": [],
|
||||
"note": "No independent production source or rehearsed fallback."
|
||||
},
|
||||
{
|
||||
"id": "D2-OOS",
|
||||
"status": "not_passed",
|
||||
"evidence": [],
|
||||
"note": "The connected Ridge candidate still uses synthetic research data."
|
||||
},
|
||||
{
|
||||
"id": "D2-MODEL",
|
||||
"status": "not_passed",
|
||||
"evidence": [],
|
||||
"note": "ModelBundle exists, but production ownership, expiry, approval and rollback evidence do not."
|
||||
},
|
||||
{
|
||||
"id": "D2-CHALLENGER",
|
||||
"status": "not_passed",
|
||||
"evidence": [],
|
||||
"note": "No real-data champion/challenger or production drift history."
|
||||
},
|
||||
{
|
||||
"id": "D3-LAYERS",
|
||||
"status": "not_passed",
|
||||
"evidence": [],
|
||||
"note": "No real-data local/JoinQuant/QMT L1-L5 strict-peer identity; Qlib has only a separate declared-scope research fixture."
|
||||
},
|
||||
{
|
||||
"id": "D3-DIFF",
|
||||
"status": "not_passed",
|
||||
"evidence": [],
|
||||
"note": "Mock parity and one JoinQuant execution smoke do not close a real QMT difference ledger."
|
||||
},
|
||||
{
|
||||
"id": "D4-RISK",
|
||||
"status": "not_passed",
|
||||
"evidence": [],
|
||||
"note": "Risk components exist, but there is no independent real-broker fail-closed service evidence."
|
||||
},
|
||||
{
|
||||
"id": "D4-RULES",
|
||||
"status": "not_passed",
|
||||
"evidence": [],
|
||||
"note": "The effective-dated 2026-07-06 SSE/SZSE rule packs are not implemented; BSE remains explicitly excluded and fail-closed."
|
||||
},
|
||||
{
|
||||
"id": "D4-OMS",
|
||||
"status": "not_passed",
|
||||
"evidence": [],
|
||||
"note": "No real QMT callback, reconnect and restart lifecycle evidence."
|
||||
},
|
||||
{
|
||||
"id": "D4-TCA",
|
||||
"status": "not_passed",
|
||||
"evidence": [],
|
||||
"note": "No controlled-live fills or governed TCA feedback."
|
||||
},
|
||||
{
|
||||
"id": "D5-QMT",
|
||||
"status": "not_passed",
|
||||
"evidence": [],
|
||||
"note": "QMT has fake-contract and syntax evidence only."
|
||||
},
|
||||
{
|
||||
"id": "D5-RECON",
|
||||
"status": "not_passed",
|
||||
"evidence": [],
|
||||
"note": "No real daily broker reconciliation series."
|
||||
},
|
||||
{
|
||||
"id": "D5-EVIDENCE",
|
||||
"status": "not_passed",
|
||||
"evidence": [],
|
||||
"note": "Local HMAC evidence is not broker attestation or real callback history."
|
||||
},
|
||||
{
|
||||
"id": "D5-FALLBACK",
|
||||
"status": "not_passed",
|
||||
"evidence": [],
|
||||
"note": "No real-account manual handoff drill."
|
||||
},
|
||||
{
|
||||
"id": "D6-SHADOW",
|
||||
"status": "not_passed",
|
||||
"evidence": [],
|
||||
"note": "Zero of the required 60 real QMT shadow trading days are recorded."
|
||||
},
|
||||
{
|
||||
"id": "D6-CANARY",
|
||||
"status": "not_passed",
|
||||
"evidence": [],
|
||||
"note": "No limited-capital canary is authorized or observed."
|
||||
},
|
||||
{
|
||||
"id": "D6-DR",
|
||||
"status": "not_passed",
|
||||
"evidence": [],
|
||||
"note": "No measured RPO/RTO and broker failure-drill evidence."
|
||||
},
|
||||
{
|
||||
"id": "D6-RELEASE",
|
||||
"status": "not_passed",
|
||||
"evidence": [],
|
||||
"note": "Independent CI, signed release inventory and rollback drill are not yet complete."
|
||||
},
|
||||
{
|
||||
"id": "D7-SCOPE",
|
||||
"status": "not_passed",
|
||||
"evidence": [],
|
||||
"note": "The intended personal scope is documented but has no private attestation reference."
|
||||
},
|
||||
{
|
||||
"id": "D7-REPORT",
|
||||
"status": "not_passed",
|
||||
"evidence": [],
|
||||
"note": "No broker programmatic-trading confirmation exists yet."
|
||||
},
|
||||
{
|
||||
"id": "D7-FLOW",
|
||||
"status": "not_passed",
|
||||
"evidence": [],
|
||||
"note": "No real-account abnormal-trading monitoring history."
|
||||
},
|
||||
{
|
||||
"id": "D7-SECURITY",
|
||||
"status": "not_passed",
|
||||
"evidence": [],
|
||||
"note": "Repository secret hygiene exists, but production access, rotation and supply-chain evidence do not."
|
||||
},
|
||||
{
|
||||
"id": "D7-REGULATED",
|
||||
"status": "not_passed",
|
||||
"evidence": [],
|
||||
"note": "The regulated-mode activation calendar is documentation-only."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"standard_id": "quant-os-production-80-v1",
|
||||
"name": "Quant OS 80-point controlled-production standard",
|
||||
"as_of": "2026-07-30",
|
||||
"maximum_score": 100,
|
||||
"qualification_threshold": 80,
|
||||
"claim_boundary": "Passing is an engineering qualification for limited personal-account production. It is not an investment-return claim, institutional licence or legal opinion.",
|
||||
"evidence_tiers": {
|
||||
"E0": {
|
||||
"rank": 0,
|
||||
"name": "assertion_or_document_only"
|
||||
},
|
||||
"E1": {
|
||||
"rank": 1,
|
||||
"name": "deterministic_local_test_or_synthetic"
|
||||
},
|
||||
"E2": {
|
||||
"rank": 2,
|
||||
"name": "authorized_real_data_frozen_oos_or_external_authority"
|
||||
},
|
||||
"E3": {
|
||||
"rank": 3,
|
||||
"name": "production_operational_control_or_real_platform_shadow"
|
||||
},
|
||||
"E4": {
|
||||
"rank": 4,
|
||||
"name": "controlled_live_observation"
|
||||
}
|
||||
},
|
||||
"qualification": {
|
||||
"baseline_60_gates": 10,
|
||||
"minimum_shadow_trading_days": 60,
|
||||
"minimum_canary_trading_days": 10,
|
||||
"pre_canary_threshold": 77,
|
||||
"pre_canary_exempt_hard_gates": [
|
||||
"D6-CANARY"
|
||||
],
|
||||
"maximum_open_p0": 0,
|
||||
"maximum_open_p1": 0,
|
||||
"score_cap_without_baseline_60": 59,
|
||||
"score_cap_without_canary": 79,
|
||||
"all_hard_gates_required": true,
|
||||
"all_domain_floors_required": true,
|
||||
"broker_programmatic_confirmation_required": true,
|
||||
"real_qmt_observation_required": true,
|
||||
"trusted_pre_canary_authorization_required": true,
|
||||
"trusted_promotion_attestation_required": true
|
||||
},
|
||||
"baseline_prerequisite": {
|
||||
"profile_path": "profiles/baseline-60/profile.json",
|
||||
"profile_id": "quant-os-baseline-60",
|
||||
"trusted_attestation_required": true,
|
||||
"minimum_evidence_tier": "E3",
|
||||
"claim_control_id": "BASELINE_60",
|
||||
"claim_evidence_type": "baseline60_qualification_attestation"
|
||||
},
|
||||
"attestation_policy": {
|
||||
"claim_schema": "quant-os-evidence-claim/1.0",
|
||||
"standard_binding": "canonical_json_sha256",
|
||||
"assessment_binding": "canonical_json_sha256",
|
||||
"issuer_registry_required": true,
|
||||
"production_trust_registry": "external_runtime_configuration_required",
|
||||
"repository_self_issuance_allowed": false,
|
||||
"pre_canary": {
|
||||
"control_id": "PRE_CANARY_AUTHORIZATION",
|
||||
"evidence_type": "pre_canary_authorization_attestation",
|
||||
"minimum_evidence_tier": "E3",
|
||||
"required_authorization_fields": [
|
||||
"authorization_id",
|
||||
"authorized_at",
|
||||
"expires_on",
|
||||
"account_scope_id",
|
||||
"strategy_ids",
|
||||
"maximum_capital_cny",
|
||||
"maximum_gross_notional_cny",
|
||||
"maximum_symbols",
|
||||
"symbol_allowlist",
|
||||
"flow_limits",
|
||||
"stop_conditions",
|
||||
"no_automatic_scale_up"
|
||||
],
|
||||
"required_stop_conditions": [
|
||||
"any_risk_data_rule_or_reconciliation_breach",
|
||||
"authorization_expired",
|
||||
"operator_kill_switch"
|
||||
]
|
||||
},
|
||||
"canary_observation": {
|
||||
"control_id": "D6-CANARY",
|
||||
"evidence_type": "controlled_live_canary_observation",
|
||||
"minimum_evidence_tier": "E4",
|
||||
"required_link_fields": [
|
||||
"pre_canary_authorization_claim_id",
|
||||
"pre_canary_authorization_sha256",
|
||||
"canary_started_on",
|
||||
"canary_ended_on",
|
||||
"trading_days_observed"
|
||||
],
|
||||
"required_observation_fields": [
|
||||
"maximum_capital_cny_observed",
|
||||
"maximum_gross_notional_cny_observed",
|
||||
"symbols_observed",
|
||||
"maximum_submission_and_cancel_requests_per_second_observed",
|
||||
"maximum_submission_and_cancel_requests_per_day_observed",
|
||||
"automatic_scale_up_observed",
|
||||
"breach_events",
|
||||
"stop_events"
|
||||
]
|
||||
},
|
||||
"promotion": {
|
||||
"control_id": "QUALIFICATION",
|
||||
"evidence_type": "production80_promotion_attestation",
|
||||
"minimum_evidence_tier": "E4"
|
||||
}
|
||||
},
|
||||
"operating_defaults": {
|
||||
"declared_market_scope": {
|
||||
"included": [
|
||||
"SSE",
|
||||
"SZSE"
|
||||
],
|
||||
"excluded_fail_closed": [
|
||||
"BSE"
|
||||
],
|
||||
"expansion_policy": "requires_effective_dated_rule_pack_and_regression_evidence_before_enablement"
|
||||
},
|
||||
"rule_pack_contract": {
|
||||
"required_fields": [
|
||||
"published_at",
|
||||
"effective_at",
|
||||
"implementation_status",
|
||||
"suspended_at",
|
||||
"resumed_at",
|
||||
"source_url",
|
||||
"source_sha256"
|
||||
],
|
||||
"implementation_status_values": [
|
||||
"effective",
|
||||
"suspended",
|
||||
"partially_suspended",
|
||||
"repealed"
|
||||
],
|
||||
"unknown_or_suspended_behavior": "fail_closed"
|
||||
},
|
||||
"flow_limits": {
|
||||
"maximum_submission_and_cancel_requests_per_second": 10,
|
||||
"maximum_submission_and_cancel_requests_per_day": 1000,
|
||||
"aggregation_scope": "per_account_and_actual_control_group",
|
||||
"message_count_semantics": "all_order_submission_attempts_plus_all_cancel_requests_emitted_by_quant_os",
|
||||
"cancellation_rate": {
|
||||
"review_threshold": 0.3,
|
||||
"numerator": "strategy_initiated_cancel_requests",
|
||||
"denominator": "accepted_order_submissions",
|
||||
"window": "trading_day_per_account_and_actual_control_group",
|
||||
"rejected_submissions": "excluded_from_denominator",
|
||||
"partial_fill_then_cancel": "included_in_numerator",
|
||||
"exchange_automatic_cancel": "excluded_from_numerator",
|
||||
"zero_denominator": "zero_rate"
|
||||
},
|
||||
"semantics": "internal_conservative_default_not_regulatory_safe_harbor"
|
||||
}
|
||||
},
|
||||
"domains": [
|
||||
{
|
||||
"id": "D1",
|
||||
"name": "Data and point-in-time truth",
|
||||
"weight": 15,
|
||||
"minimum_score_ratio": 0.6,
|
||||
"controls": [
|
||||
{
|
||||
"id": "D1-PIT",
|
||||
"name": "Production PIT dataset is complete, immutable and replayable",
|
||||
"weight": 4,
|
||||
"hard_gate": true,
|
||||
"minimum_evidence_tier": "E2",
|
||||
"acceptance": "The declared universe has effective-dated security master, membership, ST, suspension, limits, corporate actions, announcement availability and fail-closed completeness tests."
|
||||
},
|
||||
{
|
||||
"id": "D1-OPS",
|
||||
"name": "Data licence, lineage, freshness and recovery are operational",
|
||||
"weight": 5,
|
||||
"hard_gate": true,
|
||||
"minimum_evidence_tier": "E3",
|
||||
"acceptance": "Every source has a permitted-use record, immutable lineage, freshness SLO, quality alarms, backfill and restore evidence."
|
||||
},
|
||||
{
|
||||
"id": "D1-REDUNDANCY",
|
||||
"name": "Critical inputs have independent validation or a tested fallback",
|
||||
"weight": 6,
|
||||
"hard_gate": false,
|
||||
"minimum_evidence_tier": "E3",
|
||||
"acceptance": "A second source or independent control total detects material provider drift and the fallback path is rehearsed."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "D2",
|
||||
"name": "Research and model governance",
|
||||
"weight": 15,
|
||||
"minimum_score_ratio": 0.6,
|
||||
"controls": [
|
||||
{
|
||||
"id": "D2-OOS",
|
||||
"name": "Frozen real-data walk-forward and OOS evidence is valid",
|
||||
"weight": 4,
|
||||
"hard_gate": true,
|
||||
"minimum_evidence_tier": "E2",
|
||||
"acceptance": "Purge and embargo, untouched test data, A-share constraints, dated fees, impact, capacity and pre-registered mandate thresholds are all frozen and reproducible."
|
||||
},
|
||||
{
|
||||
"id": "D2-MODEL",
|
||||
"name": "Model, feature and release identities are governed",
|
||||
"weight": 5,
|
||||
"hard_gate": true,
|
||||
"minimum_evidence_tier": "E2",
|
||||
"acceptance": "Registry records dataset, feature schema, code, config, training cutoff, model hash, owner, approval, rollback and expiry."
|
||||
},
|
||||
{
|
||||
"id": "D2-CHALLENGER",
|
||||
"name": "Champion, challenger, drift and experiment selection are controlled",
|
||||
"weight": 6,
|
||||
"hard_gate": false,
|
||||
"minimum_evidence_tier": "E3",
|
||||
"acceptance": "Selection policy is pre-registered; drift and degradation alerts can demote or roll back without touching a hidden test set."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "D3",
|
||||
"name": "Cross-engine consistency",
|
||||
"weight": 10,
|
||||
"minimum_score_ratio": 0.6,
|
||||
"controls": [
|
||||
{
|
||||
"id": "D3-LAYERS",
|
||||
"name": "Local, JoinQuant and QMT share frozen L1-L5 identities; Qlib follows its declared comparison scope",
|
||||
"weight": 6,
|
||||
"hard_gate": true,
|
||||
"minimum_evidence_tier": "E3",
|
||||
"acceptance": "Local, JoinQuant and QMT strict peers produce identical signals and post-risk target weights under one dual-clock and versioned rule contract. Qlib compares L1/L2 only unless the same frozen model and portfolio implementation are explicitly exported; an unrelated Alpha158 workflow is never claimed as an L3/L4 peer."
|
||||
},
|
||||
{
|
||||
"id": "D3-DIFF",
|
||||
"name": "Order and fill differences are completely explained",
|
||||
"weight": 4,
|
||||
"hard_gate": true,
|
||||
"minimum_evidence_tier": "E3",
|
||||
"acceptance": "Multi-period difference ledgers account for price source, lot, fee, limit, fill and broker-state effects with zero unexplained residual."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "D4",
|
||||
"name": "Execution and independent risk",
|
||||
"weight": 15,
|
||||
"minimum_score_ratio": 0.6,
|
||||
"controls": [
|
||||
{
|
||||
"id": "D4-RISK",
|
||||
"name": "Independent pre-trade risk fails closed",
|
||||
"weight": 3,
|
||||
"hard_gate": true,
|
||||
"minimum_evidence_tier": "E3",
|
||||
"acceptance": "No strategy can bypass cash, sellable, permission, price, quantity, duplicate-order, concentration, turnover, loss and kill-switch checks."
|
||||
},
|
||||
{
|
||||
"id": "D4-RULES",
|
||||
"name": "Effective-dated SSE/SZSE rule packs are current; BSE remains fail-closed",
|
||||
"weight": 4,
|
||||
"hard_gate": true,
|
||||
"minimum_evidence_tier": "E3",
|
||||
"acceptance": "The declared scope is SSE and SZSE; BSE is explicitly excluded and fails closed until its own rule pack is qualified. Every market, board and security-type rule records publication, effective, suspension, resumption and repeal state plus source hash. Regressions cover the 2026-07-06 rules and SSE provisions that remain suspended; unknown, expired or suspended behavior blocks submission."
|
||||
},
|
||||
{
|
||||
"id": "D4-OMS",
|
||||
"name": "Order lifecycle is idempotent and restart-safe",
|
||||
"weight": 3,
|
||||
"hard_gate": true,
|
||||
"minimum_evidence_tier": "E3",
|
||||
"acceptance": "New, accepted, partial, rejected, cancelled and duplicate callbacks reconcile across disconnect, retry, restart and session rollover."
|
||||
},
|
||||
{
|
||||
"id": "D4-TCA",
|
||||
"name": "Real fills drive TCA and capacity feedback",
|
||||
"weight": 5,
|
||||
"hard_gate": false,
|
||||
"minimum_evidence_tier": "E4",
|
||||
"acceptance": "Arrival, implementation shortfall, spread, impact and participation are measured against actual fills and feed governed limit changes."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "D5",
|
||||
"name": "Real QMT and broker truth",
|
||||
"weight": 15,
|
||||
"minimum_score_ratio": 0.6,
|
||||
"controls": [
|
||||
{
|
||||
"id": "D5-QMT",
|
||||
"name": "Authorized QMT account contracts are observed end to end",
|
||||
"weight": 5,
|
||||
"hard_gate": true,
|
||||
"minimum_evidence_tier": "E3",
|
||||
"acceptance": "Read-only, simulation and explicitly enabled mutation phases cover query fields, callbacks, reject, partial fill, cancel, reconnect and cross-day recovery."
|
||||
},
|
||||
{
|
||||
"id": "D5-RECON",
|
||||
"name": "Cash, positions, sellable, orders and fills reconcile daily",
|
||||
"weight": 5,
|
||||
"hard_gate": true,
|
||||
"minimum_evidence_tier": "E3",
|
||||
"acceptance": "Every trading day has 100% identity reconciliation and zero unexplained difference; a difference or stale snapshot blocks new risk."
|
||||
},
|
||||
{
|
||||
"id": "D5-EVIDENCE",
|
||||
"name": "Broker facts and callbacks have authenticated replay evidence",
|
||||
"weight": 2,
|
||||
"hard_gate": true,
|
||||
"minimum_evidence_tier": "E3",
|
||||
"acceptance": "Append-only envelopes bind run, account pseudonym, source time, sequence, data, model, config, code, order and callback identities."
|
||||
},
|
||||
{
|
||||
"id": "D5-FALLBACK",
|
||||
"name": "Manual broker fallback and operator handoff are rehearsed",
|
||||
"weight": 3,
|
||||
"hard_gate": true,
|
||||
"minimum_evidence_tier": "E3",
|
||||
"acceptance": "The owner can freeze automation, inspect broker truth and complete or cancel risk safely without hidden automation."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "D6",
|
||||
"name": "Operations, disaster recovery and audit",
|
||||
"weight": 15,
|
||||
"minimum_score_ratio": 0.6,
|
||||
"controls": [
|
||||
{
|
||||
"id": "D6-SHADOW",
|
||||
"name": "The frozen candidate completes 60 trading days of shadow",
|
||||
"weight": 4,
|
||||
"hard_gate": true,
|
||||
"minimum_evidence_tier": "E3",
|
||||
"acceptance": "The same core strategy, risk and adapter release runs for at least 60 trading days with zero open P0/P1 and daily reconciliation; a material change resets the clock."
|
||||
},
|
||||
{
|
||||
"id": "D6-CANARY",
|
||||
"name": "Limited capital canary completes at least 10 trading days",
|
||||
"weight": 3,
|
||||
"hard_gate": true,
|
||||
"minimum_evidence_tier": "E4",
|
||||
"acceptance": "Capital, gross notional, symbols, account, strategy and submission/cancel flow remain within the cited pre-canary authorization; there is no automatic scale-up, and every risk, data, rule or reconciliation breach is linked to a fail-closed stop event."
|
||||
},
|
||||
{
|
||||
"id": "D6-DR",
|
||||
"name": "Failure recovery meets RPO and RTO targets",
|
||||
"weight": 5,
|
||||
"hard_gate": true,
|
||||
"minimum_evidence_tier": "E3",
|
||||
"acceptance": "Order and fill RPO is zero, trading-window RTO is at most 15 minutes, and network, crash, stale market, corrupt state and duplicate-message drills pass."
|
||||
},
|
||||
{
|
||||
"id": "D6-RELEASE",
|
||||
"name": "Build, promotion, rollback and audit are reproducible",
|
||||
"weight": 3,
|
||||
"hard_gate": true,
|
||||
"minimum_evidence_tier": "E3",
|
||||
"acceptance": "A clean build emits signed manifests and dependency inventory; promotion is approved, atomic, observable and rollback-tested."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "D7",
|
||||
"name": "Compliance and security",
|
||||
"weight": 15,
|
||||
"minimum_score_ratio": 0.6,
|
||||
"controls": [
|
||||
{
|
||||
"id": "D7-SCOPE",
|
||||
"name": "Account and business scope remains personal proprietary",
|
||||
"weight": 3,
|
||||
"hard_gate": true,
|
||||
"minimum_evidence_tier": "E3",
|
||||
"acceptance": "A private attestation is bound to broker KYC or account-ownership evidence and an access review. Third-party money, accepting a mandate to decide or execute trades for another person, providing paid investment advice to others, or sharing trade-control authority blocks this profile and triggers a documented legal/compliance applicability review. Purchasing or reading third-party research does not by itself constitute a third-party trading mandate."
|
||||
},
|
||||
{
|
||||
"id": "D7-REPORT",
|
||||
"name": "Broker programmatic-trading report matches the deployed release",
|
||||
"weight": 4,
|
||||
"hard_gate": true,
|
||||
"minimum_evidence_tier": "E3",
|
||||
"acceptance": "The broker has confirmed first reporting and the deployed strategy, software version, developer, capital, leverage and declared rates match the reported facts; material changes are registered before activation."
|
||||
},
|
||||
{
|
||||
"id": "D7-FLOW",
|
||||
"name": "Abnormal-trading and conservative flow controls are enforced",
|
||||
"weight": 3,
|
||||
"hard_gate": true,
|
||||
"minimum_evidence_tier": "E3",
|
||||
"acceptance": "Per account and actual-control account group, monitoring covers order-submission attempts, cancel requests, one-second cancellation, price impact, trade share, self-trade and reverse trade. The versioned daily-baseline policy counts every Quant OS submission attempt plus cancel request and permits no more than 10 such messages per second or 1000 per trading day. A strategy-initiated cancel-request count divided by accepted order submissions at or above 30% triggers review; rejected submissions are excluded from that denominator, partial-fill-then-cancel is included in the numerator, and exchange automatic cancellation is excluded. Stricter broker or exchange limits prevail and no internal limit claims regulatory safety."
|
||||
},
|
||||
{
|
||||
"id": "D7-SECURITY",
|
||||
"name": "Secrets, access, personal data and supply chain are controlled",
|
||||
"weight": 2,
|
||||
"hard_gate": true,
|
||||
"minimum_evidence_tier": "E3",
|
||||
"acceptance": "Credentials remain in an approved secret store, access is least-privilege and revocable, logs are redacted, dependencies are inventoried, and Colab/public artifacts contain no account facts."
|
||||
},
|
||||
{
|
||||
"id": "D7-REGULATED",
|
||||
"name": "A future regulated-mode rule calendar is maintained",
|
||||
"weight": 3,
|
||||
"hard_gate": false,
|
||||
"minimum_evidence_tier": "E2",
|
||||
"acceptance": "An effective-dated calendar records official URLs, publication and effective dates, retrieval date, content hash and review cadence. External capital or clients always block PERSONAL_PROPRIETARY and trigger a documented legal/compliance applicability determination; private-fund, disclosure, retention or licensed-operation obligations are activated only when that determination finds them applicable."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+4
-3
@@ -3,13 +3,14 @@ requires = ["setuptools>=68"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "quant60"
|
||||
version = "0.1.0"
|
||||
description = "Portable, auditable A-share quantitative trading baseline"
|
||||
name = "quant-os"
|
||||
version = "0.2.0"
|
||||
description = "Evidence-driven A-share research, backtest and controlled-production operating system"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = []
|
||||
|
||||
[project.scripts]
|
||||
quant-os = "quant_os.cli:main"
|
||||
quant60 = "quant60.cli:main"
|
||||
|
||||
[tool.setuptools]
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
从仓库中的 `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
|
||||
当前结论是 `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 smoke,QMT 与 broker 尚无
|
||||
真实运行证据;聚宽仍缺完整导出与同输入 peer。
|
||||
|
||||
@@ -435,7 +436,8 @@ QMT 账号还要先探测官方 query API 在“失败”与“空列表”时
|
||||
terminal/xtquant build 记录以及 `XtTrade.traded_amount` 对
|
||||
`price × quantity` 的真实容差探针。当前 amount 是被认证的 informational
|
||||
fact,不参与 target/delta。之后还需脱敏 callback replay、restart recovery、
|
||||
完整对账、至少 20 trading days shadow 和 G10。当前 `.BJ` mutation 不支持。
|
||||
完整对账和 G10;Baseline 60 至少需要 20 trading days shadow,Production 80
|
||||
当前要求同一冻结候选至少 60 trading days。当前 `.BJ` mutation 不支持。
|
||||
|
||||
## 8. Qlib 0.9.7 research
|
||||
|
||||
@@ -527,8 +529,9 @@ real_platform_pass = false
|
||||
gate_credit = []
|
||||
```
|
||||
|
||||
因此 mock parity 不计 G9。真实 G9 仍要求真实 JoinQuant/QMT export、同输入
|
||||
的 L1—L5 difference report 和至少 20 trading days QMT/broker shadow。
|
||||
因此 mock parity 不计 G9。Baseline 60 的真实 G9 仍要求真实 JoinQuant/QMT
|
||||
export、同输入的 L1—L5 difference report 和至少 20 trading days QMT/broker
|
||||
shadow;Production 80 当前要求同一冻结候选至少 60 trading days。
|
||||
|
||||
## 10. Evidence checklist
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Proprietary runtime boundaries
|
||||
|
||||
These directories document deployment boundaries. They do not vendor platform
|
||||
SDKs or credentials.
|
||||
|
||||
- `portable36`: generated dependency-free consumer for hosted Python 3.6.
|
||||
- `joinquant`: JoinQuant upload/runtime contract.
|
||||
- `qmt_builtin`: QMT built-in strategy runtime.
|
||||
- `colab`: notebook runtime and data-upload boundary.
|
||||
|
||||
Current executable compatibility files remain under `platforms/`, `dist/` and
|
||||
`notebooks/` until their golden-vector migrations complete.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Colab runtime
|
||||
|
||||
The canonical notebook remains `notebooks/quant_os_colab.ipynb`. Colab may
|
||||
receive only data whose licence permits the use and which contains no broker
|
||||
credential, account, holding, fill or other sensitive account fact.
|
||||
@@ -0,0 +1,5 @@
|
||||
# JoinQuant runtime
|
||||
|
||||
The hosted file is a thin execution consumer. Current source and generated
|
||||
artifacts remain at `platforms/joinquant_strategy.py` and
|
||||
`dist/joinquant_strategy.py` during compatibility migration.
|
||||
@@ -0,0 +1,5 @@
|
||||
# portable36
|
||||
|
||||
Target boundary for the generated Python 3.6-compatible TargetPackage consumer.
|
||||
It may consume versioned schemas and golden vectors, but cannot import the
|
||||
modern local core or calculate Alpha.
|
||||
@@ -0,0 +1,4 @@
|
||||
# QMT built-in runtime
|
||||
|
||||
The built-in runtime remains backtest-only until real QMT contract, callback,
|
||||
restart, reconciliation and broker programmatic-trading evidence pass.
|
||||
@@ -8,6 +8,9 @@ python_bin="${PYTHON_BIN:-python3}"
|
||||
|
||||
"${python_bin}" -c \
|
||||
'import sys; assert sys.version_info >= (3, 10), sys.version'
|
||||
PYTHONPATH=src:. "${python_bin}" -m quant_os architecture check
|
||||
PYTHONPATH=src:. "${python_bin}" -m quant_os standard validate
|
||||
PYTHONPATH=src:. "${python_bin}" -m quant_os standard evaluate
|
||||
PYTHONPATH=src:. "${python_bin}" -m unittest discover -s tests -p '*.py' -q
|
||||
"${python_bin}" tools/bundle_platforms.py
|
||||
PYTHONPATH=src:. "${python_bin}" -m quant60 smoke \
|
||||
@@ -26,6 +29,8 @@ PYTHONPATH=src:. "${python_bin}" tools/export_mock_parity.py \
|
||||
verify \
|
||||
artifacts/mock-parity/report.json
|
||||
PYTHONPATH=src:. "${python_bin}" tools/capability_probe.py
|
||||
PYTHONPATH=src:. "${python_bin}" -m quant_os doctor
|
||||
"${python_bin}" tools/check_tracked_secrets.py
|
||||
"${python_bin}" tools/build_public_status.py --check
|
||||
"${python_bin}" tools/run_colab_notebook.py notebooks/quant_os_colab.ipynb
|
||||
"${python_bin}" tools/run_colab_notebook.py \
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
"""Long-lived product namespace for Quant OS.
|
||||
|
||||
`quant60` remains the compatibility namespace for the first baseline protocol.
|
||||
Maturity scores belong to profiles, not to the package name.
|
||||
"""
|
||||
|
||||
__version__ = "0.2.0"
|
||||
@@ -0,0 +1,5 @@
|
||||
from .cli import main
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,6 @@
|
||||
"""Concrete provider, research-engine, platform and broker port adapters.
|
||||
|
||||
The existing top-level `adapters/` modules are the compatibility
|
||||
implementations. They move here only after golden-vector equivalence is
|
||||
established.
|
||||
"""
|
||||
@@ -0,0 +1 @@
|
||||
"""Use cases that compose domain ports without importing concrete SDKs."""
|
||||
@@ -0,0 +1,268 @@
|
||||
"""Machine-checkable dependency rules for the new Quant OS namespace."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
from pathlib import Path
|
||||
from typing import Dict, Iterable, List, Mapping, Set
|
||||
|
||||
|
||||
LAYER_DEPENDENCIES: Mapping[str, Set[str]] = {
|
||||
"foundation": {"foundation"},
|
||||
"contracts": {"foundation", "contracts"},
|
||||
"ports": {"foundation", "contracts", "ports"},
|
||||
"data": {"foundation", "contracts", "ports", "data"},
|
||||
"research": {"foundation", "contracts", "ports", "research"},
|
||||
"portfolio": {"foundation", "contracts", "ports", "portfolio"},
|
||||
"execution": {"foundation", "contracts", "ports", "execution"},
|
||||
"application": {
|
||||
"foundation",
|
||||
"contracts",
|
||||
"ports",
|
||||
"data",
|
||||
"research",
|
||||
"portfolio",
|
||||
"execution",
|
||||
"application",
|
||||
},
|
||||
"evidence": {"foundation", "contracts", "ports", "evidence"},
|
||||
"adapters": {
|
||||
"foundation",
|
||||
"contracts",
|
||||
"ports",
|
||||
"adapters",
|
||||
},
|
||||
"control": {
|
||||
"foundation",
|
||||
"contracts",
|
||||
"ports",
|
||||
"application",
|
||||
"evidence",
|
||||
"control",
|
||||
},
|
||||
"composition": {
|
||||
"foundation",
|
||||
"contracts",
|
||||
"ports",
|
||||
"data",
|
||||
"research",
|
||||
"portfolio",
|
||||
"execution",
|
||||
"application",
|
||||
"evidence",
|
||||
"adapters",
|
||||
"control",
|
||||
"composition",
|
||||
},
|
||||
}
|
||||
|
||||
PLANES: Mapping[str, tuple[str, ...]] = {
|
||||
"data": ("data", "research", "portfolio", "execution"),
|
||||
"control": ("application", "control"),
|
||||
"evidence": ("evidence",),
|
||||
"boundary": ("contracts", "ports", "adapters"),
|
||||
"foundation": ("foundation",),
|
||||
"composition": ("composition",),
|
||||
}
|
||||
|
||||
VENDOR_MODULES = frozenset({"jqdatasdk", "qlib", "xtquant"})
|
||||
LEGACY_FIRST_PARTY_MODULES = frozenset(
|
||||
{"quant60", "adapters", "platforms", "tools"}
|
||||
)
|
||||
LEGACY_COMPATIBILITY_LAYERS = frozenset({"composition"})
|
||||
ROOT_MODULE_LAYERS: Mapping[str, str] = {
|
||||
"__init__": "foundation",
|
||||
"__main__": "composition",
|
||||
"architecture": "control",
|
||||
"cli": "composition",
|
||||
}
|
||||
|
||||
|
||||
def _imports(
|
||||
tree: ast.AST,
|
||||
*,
|
||||
current_layer: str,
|
||||
root_module: bool,
|
||||
) -> Iterable[tuple[str, str]]:
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.Import):
|
||||
for alias in node.names:
|
||||
parts = alias.name.split(".")
|
||||
if len(parts) >= 2 and parts[0] == "quant_os":
|
||||
yield ("quant_os_layer", parts[1])
|
||||
yield ("module", parts[0])
|
||||
elif isinstance(node, ast.ImportFrom):
|
||||
module = node.module or ""
|
||||
parts = module.split(".") if module else []
|
||||
if node.level >= 2:
|
||||
if parts:
|
||||
yield ("quant_os_layer", parts[0])
|
||||
else:
|
||||
for alias in node.names:
|
||||
yield ("quant_os_layer", alias.name.split(".")[0])
|
||||
elif node.level == 1:
|
||||
if root_module and parts and parts[0] in LAYER_DEPENDENCIES:
|
||||
yield ("quant_os_layer", parts[0])
|
||||
elif root_module and parts and parts[0] in ROOT_MODULE_LAYERS:
|
||||
yield ("quant_os_layer", ROOT_MODULE_LAYERS[parts[0]])
|
||||
elif root_module and not parts:
|
||||
for alias in node.names:
|
||||
imported = alias.name.split(".")[0]
|
||||
if imported in LAYER_DEPENDENCIES:
|
||||
yield ("quant_os_layer", imported)
|
||||
else:
|
||||
yield ("quant_os_layer", current_layer)
|
||||
elif parts and parts[0] == "quant_os":
|
||||
if len(parts) >= 2:
|
||||
yield ("quant_os_layer", parts[1])
|
||||
else:
|
||||
for alias in node.names:
|
||||
yield ("quant_os_layer", alias.name.split(".")[0])
|
||||
if parts:
|
||||
yield ("module", parts[0])
|
||||
|
||||
|
||||
def _check_python_file(
|
||||
*,
|
||||
path: Path,
|
||||
root: Path,
|
||||
layer: str,
|
||||
allowed: Set[str],
|
||||
) -> List[Dict[str, object]]:
|
||||
violations: List[Dict[str, object]] = []
|
||||
try:
|
||||
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
|
||||
except (OSError, SyntaxError) as exc:
|
||||
return [
|
||||
{
|
||||
"layer": layer,
|
||||
"path": path.relative_to(root).as_posix(),
|
||||
"reason": f"cannot_parse:{type(exc).__name__}",
|
||||
}
|
||||
]
|
||||
imports = set(
|
||||
_imports(
|
||||
tree,
|
||||
current_layer=layer,
|
||||
root_module=path.parent == root,
|
||||
)
|
||||
)
|
||||
for kind, imported in sorted(imports):
|
||||
if (
|
||||
kind == "quant_os_layer"
|
||||
and imported in LAYER_DEPENDENCIES
|
||||
and imported not in allowed
|
||||
):
|
||||
violations.append(
|
||||
{
|
||||
"layer": layer,
|
||||
"path": path.relative_to(root).as_posix(),
|
||||
"reason": "forbidden_dependency",
|
||||
"imports": imported,
|
||||
"allowed": sorted(allowed),
|
||||
}
|
||||
)
|
||||
if kind == "module" and imported in VENDOR_MODULES and layer != "adapters":
|
||||
violations.append(
|
||||
{
|
||||
"layer": layer,
|
||||
"path": path.relative_to(root).as_posix(),
|
||||
"reason": "vendor_sdk_outside_adapter",
|
||||
"imports": imported,
|
||||
}
|
||||
)
|
||||
if (
|
||||
kind == "module"
|
||||
and imported in LEGACY_FIRST_PARTY_MODULES
|
||||
and layer not in LEGACY_COMPATIBILITY_LAYERS
|
||||
):
|
||||
violations.append(
|
||||
{
|
||||
"layer": layer,
|
||||
"path": path.relative_to(root).as_posix(),
|
||||
"reason": "legacy_dependency_escape",
|
||||
"imports": imported,
|
||||
"allowed_layers": sorted(
|
||||
LEGACY_COMPATIBILITY_LAYERS
|
||||
),
|
||||
}
|
||||
)
|
||||
return violations
|
||||
|
||||
|
||||
def validate_architecture(package_root: str | Path) -> Dict[str, object]:
|
||||
"""Return violations without importing project modules.
|
||||
|
||||
The legacy `quant60` namespace is intentionally outside this check. New
|
||||
code must enter through these boundaries; the compatibility kernel will be
|
||||
migrated one vertical slice at a time.
|
||||
"""
|
||||
|
||||
root = Path(package_root)
|
||||
violations: List[Dict[str, object]] = []
|
||||
checked_files = 0
|
||||
for child in sorted(root.iterdir()) if root.is_dir() else []:
|
||||
if (
|
||||
child.is_dir()
|
||||
and child.name not in LAYER_DEPENDENCIES
|
||||
and child.name != "__pycache__"
|
||||
and any(child.rglob("*.py"))
|
||||
):
|
||||
violations.append(
|
||||
{
|
||||
"layer": child.name,
|
||||
"path": child.relative_to(root).as_posix(),
|
||||
"reason": "unregistered_layer_package",
|
||||
}
|
||||
)
|
||||
for layer, allowed in sorted(LAYER_DEPENDENCIES.items()):
|
||||
layer_root = root / layer
|
||||
if not layer_root.is_dir():
|
||||
violations.append(
|
||||
{
|
||||
"layer": layer,
|
||||
"path": layer_root.as_posix(),
|
||||
"reason": "missing_layer_package",
|
||||
}
|
||||
)
|
||||
continue
|
||||
for path in sorted(layer_root.rglob("*.py")):
|
||||
checked_files += 1
|
||||
violations.extend(
|
||||
_check_python_file(
|
||||
path=path,
|
||||
root=root,
|
||||
layer=layer,
|
||||
allowed=allowed,
|
||||
)
|
||||
)
|
||||
for path in sorted(root.glob("*.py")):
|
||||
root_layer = ROOT_MODULE_LAYERS.get(path.stem)
|
||||
if root_layer is None:
|
||||
violations.append(
|
||||
{
|
||||
"layer": "root",
|
||||
"path": path.relative_to(root).as_posix(),
|
||||
"reason": "unregistered_root_module",
|
||||
}
|
||||
)
|
||||
continue
|
||||
checked_files += 1
|
||||
violations.extend(
|
||||
_check_python_file(
|
||||
path=path,
|
||||
root=root,
|
||||
layer=root_layer,
|
||||
allowed=set(LAYER_DEPENDENCIES[root_layer]),
|
||||
)
|
||||
)
|
||||
return {
|
||||
"ok": not violations,
|
||||
"scope": "new_namespace_only",
|
||||
"package_root": "src/quant_os",
|
||||
"migration_state": "transitioning_from_quant60_legacy",
|
||||
"checked_files": checked_files,
|
||||
"layers": sorted(LAYER_DEPENDENCIES),
|
||||
"planes": {name: list(layers) for name, layers in PLANES.items()},
|
||||
"violations": violations,
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
"""Stable Quant OS CLI with `quant60` kept as a compatibility protocol."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Sequence
|
||||
|
||||
from quant60.cli import main as legacy_main
|
||||
|
||||
from . import __version__
|
||||
from .architecture import validate_architecture
|
||||
from .evidence import evaluate_assessment, load_json_object, validate_standard
|
||||
|
||||
|
||||
LEGACY_COMMANDS = frozenset(
|
||||
{
|
||||
"validate-config",
|
||||
"smoke",
|
||||
"backtest",
|
||||
"verify-ledger",
|
||||
"verify-manifest",
|
||||
"research-smoke",
|
||||
"verify-research-manifest",
|
||||
"snapshot-decision",
|
||||
"snapshot-backtest",
|
||||
"verify-snapshot-decision",
|
||||
"preflight",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _looks_like_project_root(path: Path) -> bool:
|
||||
return (
|
||||
(path / "pyproject.toml").is_file()
|
||||
and (path / "profiles/production-80/standard.json").is_file()
|
||||
and (path / "src/quant_os").is_dir()
|
||||
)
|
||||
|
||||
|
||||
def _project_root(explicit: str | Path | None = None) -> Path:
|
||||
if explicit is not None:
|
||||
candidate = Path(explicit).expanduser().resolve()
|
||||
if not _looks_like_project_root(candidate):
|
||||
raise FileNotFoundError(
|
||||
f"not a Quant OS checkout: {candidate}"
|
||||
)
|
||||
return candidate
|
||||
candidates = [Path.cwd().resolve()]
|
||||
candidates.extend(Path.cwd().resolve().parents)
|
||||
source_checkout = Path(__file__).resolve().parents[2]
|
||||
candidates.append(source_checkout)
|
||||
seen = set()
|
||||
for candidate in candidates:
|
||||
if candidate in seen:
|
||||
continue
|
||||
seen.add(candidate)
|
||||
if _looks_like_project_root(candidate):
|
||||
return candidate
|
||||
raise FileNotFoundError(
|
||||
"Quant OS checkout not found; run inside the repository or pass "
|
||||
"--project-root"
|
||||
)
|
||||
|
||||
|
||||
def _profile_paths(project_root: Path) -> tuple[Path, Path]:
|
||||
root = project_root / "profiles" / "production-80"
|
||||
return root / "standard.json", root / "current-assessment.json"
|
||||
|
||||
|
||||
def _emit(payload: Dict[str, Any]) -> None:
|
||||
print(
|
||||
json.dumps(
|
||||
payload,
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
sort_keys=True,
|
||||
allow_nan=False,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _doctor(project: Path) -> Dict[str, Any]:
|
||||
profile_path, assessment_path = _profile_paths(project)
|
||||
architecture = validate_architecture(project / "src" / "quant_os")
|
||||
standard = load_json_object(profile_path)
|
||||
assessment = load_json_object(assessment_path)
|
||||
profile = validate_standard(standard)
|
||||
evaluation = evaluate_assessment(
|
||||
standard,
|
||||
assessment,
|
||||
repository_root=project,
|
||||
)
|
||||
required_paths = {
|
||||
"baseline_config": project / "configs" / "baseline.json",
|
||||
"joinquant_bundle": project / "dist" / "joinquant_strategy.py",
|
||||
"qmt_bundle": project / "dist" / "qmt_builtin_strategy.py",
|
||||
"colab_notebook": project / "notebooks" / "quant_os_colab.ipynb",
|
||||
"baseline_60_scorecard": project / "gate_scorecard.json",
|
||||
}
|
||||
legacy_roots = (
|
||||
project / "src" / "quant60",
|
||||
project / "adapters",
|
||||
project / "platforms",
|
||||
project / "tools",
|
||||
)
|
||||
legacy_python_files = sum(
|
||||
1
|
||||
for root in legacy_roots
|
||||
if root.is_dir()
|
||||
for _path in root.rglob("*.py")
|
||||
)
|
||||
return {
|
||||
"ok": bool(architecture["ok"]) and all(
|
||||
path.is_file() for path in required_paths.values()
|
||||
),
|
||||
"product": "Quant OS",
|
||||
"version": __version__,
|
||||
"architecture": architecture,
|
||||
"migration": {
|
||||
"state": "transitioning",
|
||||
"checked_scope": "src/quant_os",
|
||||
"legacy_python_files_outside_checker": legacy_python_files,
|
||||
"legacy_roots": [
|
||||
path.relative_to(project).as_posix()
|
||||
for path in legacy_roots
|
||||
if path.is_dir()
|
||||
],
|
||||
},
|
||||
"profile": profile,
|
||||
"current_maturity": {
|
||||
"state": evaluation["qualification_state"],
|
||||
"effective_score": evaluation["effective_score"],
|
||||
"maximum_score": evaluation["maximum_score"],
|
||||
"baseline_60_passed": evaluation["baseline_60_passed"],
|
||||
"qualified": evaluation["qualified"],
|
||||
"standard_canonical_sha256": evaluation[
|
||||
"standard_canonical_sha256"
|
||||
],
|
||||
"production_trust_registry_available": evaluation[
|
||||
"production_trust_registry_available"
|
||||
],
|
||||
"verification_environment": evaluation[
|
||||
"verification_environment"
|
||||
],
|
||||
},
|
||||
"compatibility": {
|
||||
"quant60_cli": True,
|
||||
"quant60_import": True,
|
||||
"writer_protocol": "V1",
|
||||
},
|
||||
"required_paths": {
|
||||
name: {"available": path.is_file(), "path": path.relative_to(project).as_posix()}
|
||||
for name, path in required_paths.items()
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(prog="quant-os", description=__doc__)
|
||||
parser.add_argument("--version", action="version", version=__version__)
|
||||
parser.add_argument(
|
||||
"--project-root",
|
||||
help="path to a Quant OS repository checkout",
|
||||
)
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
subparsers.add_parser(
|
||||
"doctor",
|
||||
help="validate project boundaries, profiles and local entrypoint files",
|
||||
)
|
||||
architecture = subparsers.add_parser(
|
||||
"architecture", help="check new-namespace dependency boundaries"
|
||||
)
|
||||
architecture.add_argument("action", choices=("check",))
|
||||
standard = subparsers.add_parser(
|
||||
"standard", help="validate or evaluate a maturity profile"
|
||||
)
|
||||
standard.add_argument("action", choices=("validate", "evaluate"))
|
||||
standard.add_argument("--profile")
|
||||
standard.add_argument("--assessment")
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
arguments = list(sys.argv[1:] if argv is None else argv)
|
||||
if arguments and arguments[0] in LEGACY_COMMANDS:
|
||||
return legacy_main(arguments)
|
||||
args = _build_parser().parse_args(arguments)
|
||||
try:
|
||||
project = _project_root(args.project_root)
|
||||
if args.command == "doctor":
|
||||
payload = _doctor(project)
|
||||
elif args.command == "architecture":
|
||||
payload = validate_architecture(project / "src" / "quant_os")
|
||||
elif args.command == "standard":
|
||||
default_profile, default_assessment = _profile_paths(project)
|
||||
profile = load_json_object(args.profile or default_profile)
|
||||
if args.action == "validate":
|
||||
payload = validate_standard(profile)
|
||||
else:
|
||||
payload = evaluate_assessment(
|
||||
profile,
|
||||
load_json_object(args.assessment or default_assessment),
|
||||
repository_root=project,
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"unsupported command {args.command}")
|
||||
except Exception as exc:
|
||||
_emit(
|
||||
{
|
||||
"ok": False,
|
||||
"error": type(exc).__name__,
|
||||
"message": str(exc),
|
||||
}
|
||||
)
|
||||
return 2
|
||||
_emit(payload)
|
||||
return 0 if payload.get("ok") else 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,5 @@
|
||||
"""Composition root for wiring application use cases to runtime adapters.
|
||||
|
||||
Only this layer may depend on both application/control modules and adapters.
|
||||
Vendor SDK imports still belong inside adapter implementations.
|
||||
"""
|
||||
@@ -0,0 +1 @@
|
||||
"""Versioned DTOs, schemas and migrations; no provider or broker SDK imports."""
|
||||
@@ -0,0 +1 @@
|
||||
"""Run specifications, orchestration and release policy."""
|
||||
@@ -0,0 +1 @@
|
||||
"""Canonical point-in-time datasets, immutable snapshots and quality checks."""
|
||||
@@ -0,0 +1,15 @@
|
||||
"""Evidence manifests, claims, parity and maturity evaluation."""
|
||||
|
||||
from .maturity import (
|
||||
MaturityStandardError,
|
||||
evaluate_assessment,
|
||||
load_json_object,
|
||||
validate_standard,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"MaturityStandardError",
|
||||
"evaluate_assessment",
|
||||
"load_json_object",
|
||||
"validate_standard",
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
"""Execution intents, OMS state, reconciliation and fail-closed kill switches."""
|
||||
@@ -0,0 +1 @@
|
||||
"""Pure IDs, clocks, symbols, money and other dependency-free primitives."""
|
||||
@@ -0,0 +1 @@
|
||||
"""Risk, cost, capacity, optimization and post-risk target weights."""
|
||||
@@ -0,0 +1,5 @@
|
||||
"""Inward-facing ports implemented by external adapters."""
|
||||
|
||||
from .broker import EvidenceSinkPort, ReadBrokerPort, TradeBrokerPort
|
||||
|
||||
__all__ = ["EvidenceSinkPort", "ReadBrokerPort", "TradeBrokerPort"]
|
||||
@@ -0,0 +1,38 @@
|
||||
"""Broker and evidence ports with read and mutation authority kept separate."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Mapping, Protocol, Sequence
|
||||
|
||||
|
||||
class ReadBrokerPort(Protocol):
|
||||
"""Read-only broker facts. Implementations must not expose mutation here."""
|
||||
|
||||
def query_asset(self) -> Mapping[str, Any]:
|
||||
...
|
||||
|
||||
def query_positions(self) -> Sequence[Mapping[str, Any]]:
|
||||
...
|
||||
|
||||
def query_orders(self) -> Sequence[Mapping[str, Any]]:
|
||||
...
|
||||
|
||||
def query_trades(self) -> Sequence[Mapping[str, Any]]:
|
||||
...
|
||||
|
||||
|
||||
class TradeBrokerPort(Protocol):
|
||||
"""Explicit mutation boundary enabled only by a promoted release policy."""
|
||||
|
||||
def submit_order(self, intent: Mapping[str, Any]) -> Mapping[str, Any]:
|
||||
...
|
||||
|
||||
def cancel_order(self, broker_order_id: str) -> Mapping[str, Any]:
|
||||
...
|
||||
|
||||
|
||||
class EvidenceSinkPort(Protocol):
|
||||
"""Append immutable facts without granting decision or trading authority."""
|
||||
|
||||
def append(self, envelope: Mapping[str, Any]) -> str:
|
||||
...
|
||||
@@ -0,0 +1 @@
|
||||
"""Features, splits, models and evaluation; this layer never knows a broker."""
|
||||
+173
-23
@@ -1457,7 +1457,7 @@
|
||||
<div class="masthead__brand">
|
||||
<p class="eyebrow">Release control / public snapshot</p>
|
||||
<h1 class="brand-title" id="page-title">Quant OS</h1>
|
||||
<p class="brand-subtitle">个人 A 股研究到执行的证据审计台</p>
|
||||
<p class="brand-subtitle">个人沪深 A 股研究到执行的证据审计台</p>
|
||||
</div>
|
||||
<div class="masthead__claim">
|
||||
<div class="claim-line">
|
||||
@@ -1479,15 +1479,15 @@
|
||||
|
||||
<div class="safety-banner" role="note" aria-label="资金安全边界">
|
||||
<svg class="icon" aria-hidden="true"><use href="#icon-shield-alert"></use></svg>
|
||||
<p><strong>可运行框架 ≠ 可投入资金。</strong> 本页只呈现构建时证据快照,不是实时服务健康监控,也不构成投资收益声明。</p>
|
||||
<p><strong>可运行框架 ≠ 可投入资金。</strong> 本页只呈现构建时证据快照,不是实时服务健康监控,也不构成投资收益声明。<span id="market-scope-copy">当前交易执行范围仅覆盖沪深;北交所尚未接入,所有北交所委托必须 fail closed。</span></p>
|
||||
<span class="snapshot-label">LIVE READY = FALSE</span>
|
||||
</div>
|
||||
|
||||
<section class="metrics" aria-label="核心状态指标">
|
||||
<div class="metric">
|
||||
<span class="metric__label">Delivery score</span>
|
||||
<strong class="metric__value" id="metric-score">NOT SCORED</strong>
|
||||
<span class="metric__note">不把文章估分冒充系统得分</span>
|
||||
<span class="metric__label">Production 80</span>
|
||||
<strong class="metric__value" id="metric-score">0 / 100</strong>
|
||||
<span class="metric__note">证据得分;Hard Gate 不可补偿</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="metric__label">Gate evidence</span>
|
||||
@@ -1513,9 +1513,10 @@
|
||||
<a href="#layers"><span class="rail__index">01</span><span>系统分层</span></a>
|
||||
<a href="#models"><span class="rail__index">02</span><span>模型边界</span></a>
|
||||
<a href="#platforms"><span class="rail__index">03</span><span>平台接入</span></a>
|
||||
<a href="#gates"><span class="rail__index">04</span><span>G1–G10</span></a>
|
||||
<a href="#bundles"><span class="rail__index">05</span><span>发布包</span></a>
|
||||
<a href="#provenance"><span class="rail__index">06</span><span>证据来源</span></a>
|
||||
<a href="#maturity80"><span class="rail__index">04</span><span>80 分标准</span></a>
|
||||
<a href="#gates"><span class="rail__index">05</span><span>G1–G10</span></a>
|
||||
<a href="#bundles"><span class="rail__index">06</span><span>发布包</span></a>
|
||||
<a href="#provenance"><span class="rail__index">07</span><span>证据来源</span></a>
|
||||
</nav>
|
||||
|
||||
<main class="main-content" id="main-content" tabindex="-1">
|
||||
@@ -1626,9 +1627,48 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="audit-section" id="gates" aria-labelledby="gates-title">
|
||||
<section class="audit-section" id="maturity80" aria-labelledby="maturity80-title">
|
||||
<div class="section-header">
|
||||
<span class="section-code">SEC.04</span>
|
||||
<div>
|
||||
<h2 class="section-title" id="maturity80-title">Production 80 · 受限生产标准</h2>
|
||||
<p class="section-intro">80 分以 Baseline 60 全门通过为前置,并要求真实 QMT、60 个交易日 shadow、10 个交易日受限资金 canary、持续对账、恢复演练和券商程序化交易确认。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overview-board">
|
||||
<div class="overview-copy">
|
||||
<h3>当前 80 分判定</h3>
|
||||
<p><strong id="maturity80-score">0 / 100 · BLOCKED</strong></p>
|
||||
<p id="maturity80-policy">等待 machine profile。缺失数据时不得获得分数。</p>
|
||||
</div>
|
||||
<div class="evidence-summary">
|
||||
<h3>不可补偿前置</h3>
|
||||
<ul class="runtime-list" id="maturity80-prerequisites">
|
||||
<li>等待状态数据</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="matrix-wrap">
|
||||
<table class="bundle-table">
|
||||
<caption class="visually-hidden">Quant OS 80 分七个维度的已验证得分和最低分</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Domain</th>
|
||||
<th scope="col">Verified</th>
|
||||
<th scope="col">Weight</th>
|
||||
<th scope="col">Required floor</th>
|
||||
<th scope="col">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="maturity80-domain-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="gate-stack" id="maturity80-control-list"></div>
|
||||
</section>
|
||||
|
||||
<section class="audit-section" id="gates" aria-labelledby="gates-title">
|
||||
<div class="section-header">
|
||||
<span class="section-code">SEC.05</span>
|
||||
<div>
|
||||
<h2 class="section-title" id="gates-title">Baseline 60 · G1–G10</h2>
|
||||
<p class="section-intro">Gate 是发布闸门,不是代码清单。展开每项查看验收定义和缺失证据;当前 10 项均未通过。</p>
|
||||
@@ -1639,7 +1679,7 @@
|
||||
|
||||
<section class="audit-section" id="bundles" aria-labelledby="bundles-title">
|
||||
<div class="section-header">
|
||||
<span class="section-code">SEC.05</span>
|
||||
<span class="section-code">SEC.06</span>
|
||||
<div>
|
||||
<h2 class="section-title" id="bundles-title">跨平台发布包完整性</h2>
|
||||
<p class="section-intro">构建器核对共享核心、wrapper、配置与生成单文件的 SHA-256。哈希一致只证明代码发布包完整,不证明目标平台通过。</p>
|
||||
@@ -1664,7 +1704,7 @@
|
||||
|
||||
<section class="audit-section" id="provenance" aria-labelledby="provenance-title">
|
||||
<div class="section-header">
|
||||
<span class="section-code">SEC.06</span>
|
||||
<span class="section-code">SEC.07</span>
|
||||
<div>
|
||||
<h2 class="section-title" id="provenance-title">证据来源与快照血缘</h2>
|
||||
<p class="section-intro">所有公开字段由仓库中的权威记录生成并脱敏。链接指向私有 Gitea 源码,访问时可能要求登录。</p>
|
||||
@@ -1738,7 +1778,7 @@
|
||||
|
||||
<footer class="page-footer">
|
||||
<p>本页是公开、脱敏的构建时快照。未发布本地行情、运行 artifacts、券商状态或可复用凭据。任何实盘决策必须回到 G1–G10 的发布级证据。</p>
|
||||
<a href="https://git.gomars.fun/boat/quants-strategies/src/branch/master/quant-os" target="_blank" rel="noopener noreferrer">
|
||||
<a href="https://git.gomars.fun/boat/quant-os/src/branch/master" target="_blank" rel="noopener noreferrer">
|
||||
源码(需登录)
|
||||
<svg class="icon" aria-hidden="true"><use href="#icon-external"></use></svg>
|
||||
</a>
|
||||
@@ -1759,7 +1799,7 @@
|
||||
(() => {
|
||||
"use strict";
|
||||
|
||||
const repoBase = "https://git.gomars.fun/boat/quants-strategies/src/branch/master/quant-os/";
|
||||
const repoBase = "https://git.gomars.fun/boat/quant-os/src/branch/master/";
|
||||
const statusLabels = {
|
||||
implemented: { label: "已实现", tone: "implemented" },
|
||||
tested: { label: "合同验证", tone: "tested" },
|
||||
@@ -1830,13 +1870,14 @@
|
||||
const renderHeader = (data) => {
|
||||
const claim = data.claim || {};
|
||||
const summary = data.summary || {};
|
||||
const maturity80 = data.maturity80 || {};
|
||||
byId("claim-status").textContent = claim.status || "NOT_BASELINE_60";
|
||||
byId("claim-gates").textContent = `${claim.gatesPassed ?? 0} / ${claim.gatesTotal ?? 10} GATES`;
|
||||
byId("claim-reason").textContent = claim.reason || "缺少发布级证据。";
|
||||
byId("overview-reason").textContent = claim.reason || "缺少发布级证据。";
|
||||
byId("metric-score").textContent = claim.score == null
|
||||
? String(claim.scoreStatus || "not_scored").replaceAll("_", " ").toUpperCase()
|
||||
: String(claim.score);
|
||||
byId("market-scope-copy").textContent = data.marketScope?.statement ||
|
||||
"当前交易执行范围仅覆盖沪深;北交所尚未接入,所有北交所委托必须 fail closed。";
|
||||
byId("metric-score").textContent = `${maturity80.effectiveScore ?? 0} / ${maturity80.maximumScore ?? 100}`;
|
||||
byId("metric-gates").textContent = `${claim.gatesPassed ?? 0} / ${claim.gatesTotal ?? 10}`;
|
||||
byId("metric-tests").textContent = summary.testsRecorded == null ? "—" : String(summary.testsRecorded);
|
||||
byId("metric-tests-note").textContent = summary.optionalSkipsRecorded == null
|
||||
@@ -1853,6 +1894,63 @@
|
||||
: "<li>无真实运行证据记录</li>";
|
||||
};
|
||||
|
||||
const renderMaturity80 = (data) => {
|
||||
const maturity = data.maturity80 || {};
|
||||
byId("maturity80-score").textContent =
|
||||
`${maturity.effectiveScore ?? 0} / ${maturity.maximumScore ?? 100} · ${maturity.state || "BLOCKED"}`;
|
||||
byId("maturity80-policy").textContent = maturity.evidencePolicy ||
|
||||
"缺少 hash-linked evidence 时不得计分。";
|
||||
const prerequisiteLabels = {
|
||||
baseline_60_all_gates: "Baseline 60 · G1–G10 全通过",
|
||||
real_qmt_observed: "真实 QMT 已观察",
|
||||
broker_programmatic_confirmation: "券商程序化交易确认",
|
||||
shadow_trading_days: "同一候选 60 个交易日 shadow",
|
||||
canary_trading_days: "受限资金 10 个交易日 canary",
|
||||
open_p0_within_limit: "未关闭 P0 = 0",
|
||||
open_p1_within_limit: "未关闭 P1 = 0"
|
||||
};
|
||||
const prerequisites = maturity.prerequisites || {};
|
||||
const prerequisiteRows = Object.entries(prerequisiteLabels)
|
||||
.map(([key, label]) => `<li>${escapeHtml(label)} · ${prerequisites[key] === true ? "PASS" : "BLOCKED"}</li>`)
|
||||
prerequisiteRows.push(
|
||||
`<li>Pre-canary 授权记录已核验 · ${maturity.preCanaryAuthorizationVerified === true ? "PASS" : "BLOCKED"}</li>`,
|
||||
`<li>Canary 证据反向绑定授权 · ${maturity.canaryAuthorizationChainVerified === true ? "PASS" : "BLOCKED"}</li>`,
|
||||
`<li>最终 promotion 可信 attestation · ${maturity.trustedAttestationVerified === true ? "PASS" : "BLOCKED"}</li>`
|
||||
);
|
||||
byId("maturity80-prerequisites").innerHTML = prerequisiteRows.join("");
|
||||
const domains = Array.isArray(maturity.domains) ? maturity.domains : [];
|
||||
byId("maturity80-domain-body").innerHTML = domains.map((domain) => `
|
||||
<tr>
|
||||
<td><strong>${escapeHtml(domain.id)} · ${escapeHtml(domain.name)}</strong></td>
|
||||
<td>${escapeHtml(domain.score)}</td>
|
||||
<td>${escapeHtml(domain.weight)}</td>
|
||||
<td>${escapeHtml(domain.minimumScore)}</td>
|
||||
<td>${statusTag(domain.floorPassed ? "tested" : "blocked")}</td>
|
||||
</tr>
|
||||
`).join("");
|
||||
const controls = Array.isArray(maturity.controls) ? maturity.controls : [];
|
||||
byId("maturity80-control-list").innerHTML = controls.map((control) => `
|
||||
<details class="gate-item">
|
||||
<summary>
|
||||
<span class="gate-id">${escapeHtml(control.id)}</span>
|
||||
<span class="gate-name">${escapeHtml(control.name)}</span>
|
||||
<span class="gate-acceptance">${control.hardGate ? "HARD GATE" : "SCORED CONTROL"} · ${escapeHtml(control.weight)} points · min ${escapeHtml(control.minimumEvidenceTier)}</span>
|
||||
${statusTag(control.status)}
|
||||
</summary>
|
||||
<div class="gate-detail">
|
||||
<div>
|
||||
<h4>Current gap</h4>
|
||||
<p>${escapeHtml(control.note || "No qualifying evidence recorded.")}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Evidence rule</h4>
|
||||
<p>只有绑定同一候选身份、达到最低 tier、SHA-256 可重算且通过可信 verifier 的 claim 才能通过。</p>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
`).join("");
|
||||
};
|
||||
|
||||
const renderLayers = (data) => {
|
||||
const layers = Array.isArray(data.layers) ? data.layers : [];
|
||||
byId("layer-list").innerHTML = layers.map((layer, index) => `
|
||||
@@ -2071,15 +2169,66 @@
|
||||
|
||||
const validatePublicData = (data) => {
|
||||
if (!data || typeof data !== "object") throw new Error("missing public status object");
|
||||
if (data.claim?.status !== "NOT_BASELINE_60") throw new Error("unsafe claim status");
|
||||
if (data.claim?.releaseVerified !== false || data.claim?.liveReady !== false) {
|
||||
throw new Error("unsafe release claim");
|
||||
const claim = data.claim || {};
|
||||
if (!["NOT_BASELINE_60", "BASELINE_60_VERIFIED"].includes(claim.status)) {
|
||||
throw new Error("unsupported claim status");
|
||||
}
|
||||
if (data.claim?.gatesPassed !== 0 || data.claim?.gatesTotal !== 10) {
|
||||
throw new Error("unexpected gate score");
|
||||
if (!Array.isArray(data.gates) || data.gates.length !== claim.gatesTotal) {
|
||||
throw new Error("gate set mismatch");
|
||||
}
|
||||
if (!Array.isArray(data.gates) || data.gates.some((gate) => gate.status !== "not_passed")) {
|
||||
throw new Error("all gates must remain explicitly not_passed");
|
||||
if (data.gates.some((gate) => !["passed", "not_passed"].includes(gate.status))) {
|
||||
throw new Error("unsupported gate status");
|
||||
}
|
||||
const gatesPassed = data.gates.filter((gate) => gate.status === "passed").length;
|
||||
if (gatesPassed !== claim.gatesPassed) throw new Error("gate count mismatch");
|
||||
const baselineVerified = claim.status === "BASELINE_60_VERIFIED";
|
||||
if (baselineVerified !== Boolean(claim.releaseVerified)) {
|
||||
throw new Error("baseline release claim mismatch");
|
||||
}
|
||||
if (baselineVerified && gatesPassed !== claim.gatesTotal) {
|
||||
throw new Error("baseline claim requires every gate");
|
||||
}
|
||||
const maturity = data.maturity80 || {};
|
||||
const marketScope = data.marketScope || {};
|
||||
if (
|
||||
JSON.stringify(marketScope.included) !== JSON.stringify(["SSE", "SZSE"])
|
||||
|| JSON.stringify(marketScope.excludedFailClosed) !== JSON.stringify(["BSE"])
|
||||
|| marketScope.bseTradingEnabled !== false
|
||||
) {
|
||||
throw new Error("unsafe or ambiguous market scope");
|
||||
}
|
||||
if (Boolean(maturity.baseline60Passed) !== baselineVerified) {
|
||||
throw new Error("60/80 assessment mismatch");
|
||||
}
|
||||
if (Boolean(maturity.qualified) !== Boolean(claim.liveReady)) {
|
||||
throw new Error("limited-live claim mismatch");
|
||||
}
|
||||
if (maturity.qualified && (
|
||||
maturity.state !== "LIMITED_LIVE_VERIFIED"
|
||||
|| !maturity.trustedAttestationVerified
|
||||
|| !maturity.preCanaryAuthorized
|
||||
|| !maturity.canaryAuthorizationChainVerified
|
||||
)) {
|
||||
throw new Error("unattested production-80 claim");
|
||||
}
|
||||
if (
|
||||
["PRE_CANARY_AUTHORIZED", "CANARY_RUNNING"].includes(maturity.state)
|
||||
&& !maturity.preCanaryAuthorized
|
||||
) {
|
||||
throw new Error("current canary state lacks authorization");
|
||||
}
|
||||
if (maturity.preCanaryAuthorized
|
||||
&& !maturity.preCanaryAuthorizationVerified) {
|
||||
throw new Error("unattested pre-canary authorization");
|
||||
}
|
||||
if (
|
||||
String(maturity.state || "").startsWith("TEST_ONLY_")
|
||||
&& (!maturity.testOnly || maturity.qualified)
|
||||
) {
|
||||
throw new Error("test-only assessment overclaim");
|
||||
}
|
||||
if (!baselineVerified && maturity.effectiveScore > 59) {
|
||||
throw new Error("production-80 score violates baseline-60 cap");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2095,6 +2244,7 @@
|
||||
renderLayers(data);
|
||||
renderModels(data);
|
||||
renderPlatforms();
|
||||
renderMaturity80(data);
|
||||
renderGates(data);
|
||||
renderBundles(data);
|
||||
renderActions(data);
|
||||
|
||||
+506
-28
@@ -1,6 +1,6 @@
|
||||
// Generated by tools/build_public_status.py; do not edit by hand.
|
||||
window.QUANT_OS_PUBLIC_STATUS = {
|
||||
"auditAsOf": "2026-07-26",
|
||||
"auditAsOf": "2026-07-30",
|
||||
"bundles": {
|
||||
"artifacts": [
|
||||
{
|
||||
@@ -116,8 +116,8 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
||||
"authorized JQData market snapshot, licence/lineage record and a frozen provider-data candidate tape",
|
||||
"authorized real-data multi-period JoinQuant TargetPackage run plus same-input local/JoinQuant layer-by-layer reconciliation",
|
||||
"real QMT TargetPackage built-in and qmttools backtest exports",
|
||||
"real QMT historical-ST positive control, terminal/xtquant build record, broker query contract and trade-amount semantic probes, callback/restart and at least 20 trading days of shadow evidence",
|
||||
"continuous reconciliation, recovery drills and broker compliance confirmation"
|
||||
"real QMT historical-ST positive control, terminal/xtquant build record, broker query contract and trade-amount semantic probes, and callback/restart evidence; Baseline 60 requires at least 20 trading days of shadow, while the current Production 80 gap requires at least 60 trading days for the same frozen candidate",
|
||||
"continuous reconciliation, recovery drills and broker programmatic-trading report/verification confirmation"
|
||||
],
|
||||
"internal": [
|
||||
"The authorized provider-data path has not yet been connected end to end to the frozen Ridge ModelBundle, calibrated risk/cost inputs, five-layer Baseline candidate and TargetPackage tape; the current connected candidate remains synthetic.",
|
||||
@@ -203,11 +203,17 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
||||
"kind": "runbook",
|
||||
"label": "Platform deployment runbook",
|
||||
"path": "runbooks/PLATFORM_DEPLOYMENT.md"
|
||||
},
|
||||
{
|
||||
"id": "public-status-deployment",
|
||||
"kind": "runbook",
|
||||
"label": "Atomic public status deployment",
|
||||
"path": "ops/README.md"
|
||||
}
|
||||
],
|
||||
"freshness": [
|
||||
{
|
||||
"asOf": "2026-07-26",
|
||||
"asOf": "2026-07-30",
|
||||
"evidenceLinks": [
|
||||
"scorecard",
|
||||
"audit"
|
||||
@@ -216,7 +222,7 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
||||
"label": "项目证据审计",
|
||||
"semantics": "证据盘点日期;不是部署时间,也不是行情截止日。",
|
||||
"status": "recorded",
|
||||
"value": "2026-07-26"
|
||||
"value": "2026-07-30"
|
||||
},
|
||||
{
|
||||
"evidenceLinks": [
|
||||
@@ -292,7 +298,7 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
||||
"label": "QMT / broker 真实运行",
|
||||
"lastRealRuntimeAt": null,
|
||||
"productionReady": false,
|
||||
"semantics": "QMT 只有 bundle/harness,XtTrader 只有 fake-broker 只读合同;尚无真实运行或 20 日影子证据。",
|
||||
"semantics": "QMT 只有 bundle/harness,XtTrader 只有 fake-broker 只读合同;尚无真实运行或影子证据。Baseline 60 至少需要 20 个交易日,Production 80 当前缺口为同一冻结候选至少 60 个交易日。",
|
||||
"status": "blocked",
|
||||
"value": "N/A"
|
||||
}
|
||||
@@ -486,13 +492,13 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
||||
"limitations": [
|
||||
"真实平台跨引擎 manifest",
|
||||
"逐层 signal/target/order 差异",
|
||||
"QMT 至少 20 个交易日影子记录"
|
||||
"Baseline 60 所需的 QMT 至少 20 个交易日影子记录;Production 80 另要求同一冻结候选至少 60 个交易日"
|
||||
],
|
||||
"name": "跨引擎一致性",
|
||||
"requiredEvidence": [
|
||||
"真实平台跨引擎 manifest",
|
||||
"逐层 signal/target/order 差异",
|
||||
"QMT 至少 20 个交易日影子记录"
|
||||
"Baseline 60 所需的 QMT 至少 20 个交易日影子记录;Production 80 另要求同一冻结候选至少 60 个交易日"
|
||||
],
|
||||
"status": "not_passed"
|
||||
},
|
||||
@@ -519,7 +525,7 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
||||
"status": "not_passed"
|
||||
}
|
||||
],
|
||||
"generatedAt": "2026-07-26T00:00:00Z",
|
||||
"generatedAt": "2026-07-30T00:00:00Z",
|
||||
"generatedAtSource": "audit_as_of",
|
||||
"layers": [
|
||||
{
|
||||
@@ -646,7 +652,7 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
||||
],
|
||||
"id": "broker-oms",
|
||||
"limitations": [
|
||||
"无真实券商 attestation、回调/重启证明和 20 个交易日影子记录;没有实盘下单入口。"
|
||||
"无真实券商 attestation、回调/重启证明和影子记录;Baseline 60 至少需要 20 个交易日,Production 80 当前缺口为同一冻结候选至少 60 个交易日;没有实盘下单入口。"
|
||||
],
|
||||
"name": "券商、订单与影子盘",
|
||||
"releaseVerified": false,
|
||||
@@ -673,6 +679,412 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
||||
"status": "blocked"
|
||||
}
|
||||
],
|
||||
"marketScope": {
|
||||
"bseTradingEnabled": false,
|
||||
"excludedFailClosed": [
|
||||
"BSE"
|
||||
],
|
||||
"executionScopeLabel": "沪深(上交所、深交所)",
|
||||
"included": [
|
||||
"SSE",
|
||||
"SZSE"
|
||||
],
|
||||
"statement": "当前交易执行范围仅覆盖沪深;北交所尚未接入,所有北交所委托必须 fail closed。"
|
||||
},
|
||||
"maturity80": {
|
||||
"assessedAsOf": "2026-07-30",
|
||||
"baseline60AttestationVerified": false,
|
||||
"baseline60Passed": false,
|
||||
"baselineProfileCanonicalSha256": "34adf09a824299ddb07c17f56751527ddf96ff5e494e792be0c65da4f8acdeb3",
|
||||
"canaryAuthorizationChainVerified": false,
|
||||
"canaryObservation": null,
|
||||
"canaryRunning": false,
|
||||
"controls": [
|
||||
{
|
||||
"domainId": "D1",
|
||||
"hardGate": true,
|
||||
"id": "D1-PIT",
|
||||
"minimumEvidenceTier": "E2",
|
||||
"name": "Production PIT dataset is complete, immutable and replayable",
|
||||
"note": "No authorized complete production PIT dataset has reached the frozen Ridge path.",
|
||||
"status": "not_passed",
|
||||
"weight": 4
|
||||
},
|
||||
{
|
||||
"domainId": "D1",
|
||||
"hardGate": true,
|
||||
"id": "D1-OPS",
|
||||
"minimumEvidenceTier": "E3",
|
||||
"name": "Data licence, lineage, freshness and recovery are operational",
|
||||
"note": "No production freshness SLO, entitlement record and restore history.",
|
||||
"status": "not_passed",
|
||||
"weight": 5
|
||||
},
|
||||
{
|
||||
"domainId": "D1",
|
||||
"hardGate": false,
|
||||
"id": "D1-REDUNDANCY",
|
||||
"minimumEvidenceTier": "E3",
|
||||
"name": "Critical inputs have independent validation or a tested fallback",
|
||||
"note": "No independent production source or rehearsed fallback.",
|
||||
"status": "not_passed",
|
||||
"weight": 6
|
||||
},
|
||||
{
|
||||
"domainId": "D2",
|
||||
"hardGate": true,
|
||||
"id": "D2-OOS",
|
||||
"minimumEvidenceTier": "E2",
|
||||
"name": "Frozen real-data walk-forward and OOS evidence is valid",
|
||||
"note": "The connected Ridge candidate still uses synthetic research data.",
|
||||
"status": "not_passed",
|
||||
"weight": 4
|
||||
},
|
||||
{
|
||||
"domainId": "D2",
|
||||
"hardGate": true,
|
||||
"id": "D2-MODEL",
|
||||
"minimumEvidenceTier": "E2",
|
||||
"name": "Model, feature and release identities are governed",
|
||||
"note": "ModelBundle exists, but production ownership, expiry, approval and rollback evidence do not.",
|
||||
"status": "not_passed",
|
||||
"weight": 5
|
||||
},
|
||||
{
|
||||
"domainId": "D2",
|
||||
"hardGate": false,
|
||||
"id": "D2-CHALLENGER",
|
||||
"minimumEvidenceTier": "E3",
|
||||
"name": "Champion, challenger, drift and experiment selection are controlled",
|
||||
"note": "No real-data champion/challenger or production drift history.",
|
||||
"status": "not_passed",
|
||||
"weight": 6
|
||||
},
|
||||
{
|
||||
"domainId": "D3",
|
||||
"hardGate": true,
|
||||
"id": "D3-LAYERS",
|
||||
"minimumEvidenceTier": "E3",
|
||||
"name": "Local, JoinQuant and QMT share frozen L1-L5 identities; Qlib follows its declared comparison scope",
|
||||
"note": "No real-data local/JoinQuant/QMT L1-L5 strict-peer identity; Qlib has only a separate declared-scope research fixture.",
|
||||
"status": "not_passed",
|
||||
"weight": 6
|
||||
},
|
||||
{
|
||||
"domainId": "D3",
|
||||
"hardGate": true,
|
||||
"id": "D3-DIFF",
|
||||
"minimumEvidenceTier": "E3",
|
||||
"name": "Order and fill differences are completely explained",
|
||||
"note": "Mock parity and one JoinQuant execution smoke do not close a real QMT difference ledger.",
|
||||
"status": "not_passed",
|
||||
"weight": 4
|
||||
},
|
||||
{
|
||||
"domainId": "D4",
|
||||
"hardGate": true,
|
||||
"id": "D4-RISK",
|
||||
"minimumEvidenceTier": "E3",
|
||||
"name": "Independent pre-trade risk fails closed",
|
||||
"note": "Risk components exist, but there is no independent real-broker fail-closed service evidence.",
|
||||
"status": "not_passed",
|
||||
"weight": 3
|
||||
},
|
||||
{
|
||||
"domainId": "D4",
|
||||
"hardGate": true,
|
||||
"id": "D4-RULES",
|
||||
"minimumEvidenceTier": "E3",
|
||||
"name": "Effective-dated SSE/SZSE rule packs are current; BSE remains fail-closed",
|
||||
"note": "The effective-dated 2026-07-06 SSE/SZSE rule packs are not implemented; BSE remains explicitly excluded and fail-closed.",
|
||||
"status": "not_passed",
|
||||
"weight": 4
|
||||
},
|
||||
{
|
||||
"domainId": "D4",
|
||||
"hardGate": true,
|
||||
"id": "D4-OMS",
|
||||
"minimumEvidenceTier": "E3",
|
||||
"name": "Order lifecycle is idempotent and restart-safe",
|
||||
"note": "No real QMT callback, reconnect and restart lifecycle evidence.",
|
||||
"status": "not_passed",
|
||||
"weight": 3
|
||||
},
|
||||
{
|
||||
"domainId": "D4",
|
||||
"hardGate": false,
|
||||
"id": "D4-TCA",
|
||||
"minimumEvidenceTier": "E4",
|
||||
"name": "Real fills drive TCA and capacity feedback",
|
||||
"note": "No controlled-live fills or governed TCA feedback.",
|
||||
"status": "not_passed",
|
||||
"weight": 5
|
||||
},
|
||||
{
|
||||
"domainId": "D5",
|
||||
"hardGate": true,
|
||||
"id": "D5-QMT",
|
||||
"minimumEvidenceTier": "E3",
|
||||
"name": "Authorized QMT account contracts are observed end to end",
|
||||
"note": "QMT has fake-contract and syntax evidence only.",
|
||||
"status": "not_passed",
|
||||
"weight": 5
|
||||
},
|
||||
{
|
||||
"domainId": "D5",
|
||||
"hardGate": true,
|
||||
"id": "D5-RECON",
|
||||
"minimumEvidenceTier": "E3",
|
||||
"name": "Cash, positions, sellable, orders and fills reconcile daily",
|
||||
"note": "No real daily broker reconciliation series.",
|
||||
"status": "not_passed",
|
||||
"weight": 5
|
||||
},
|
||||
{
|
||||
"domainId": "D5",
|
||||
"hardGate": true,
|
||||
"id": "D5-EVIDENCE",
|
||||
"minimumEvidenceTier": "E3",
|
||||
"name": "Broker facts and callbacks have authenticated replay evidence",
|
||||
"note": "Local HMAC evidence is not broker attestation or real callback history.",
|
||||
"status": "not_passed",
|
||||
"weight": 2
|
||||
},
|
||||
{
|
||||
"domainId": "D5",
|
||||
"hardGate": true,
|
||||
"id": "D5-FALLBACK",
|
||||
"minimumEvidenceTier": "E3",
|
||||
"name": "Manual broker fallback and operator handoff are rehearsed",
|
||||
"note": "No real-account manual handoff drill.",
|
||||
"status": "not_passed",
|
||||
"weight": 3
|
||||
},
|
||||
{
|
||||
"domainId": "D6",
|
||||
"hardGate": true,
|
||||
"id": "D6-SHADOW",
|
||||
"minimumEvidenceTier": "E3",
|
||||
"name": "The frozen candidate completes 60 trading days of shadow",
|
||||
"note": "Zero of the required 60 real QMT shadow trading days are recorded.",
|
||||
"status": "not_passed",
|
||||
"weight": 4
|
||||
},
|
||||
{
|
||||
"domainId": "D6",
|
||||
"hardGate": true,
|
||||
"id": "D6-CANARY",
|
||||
"minimumEvidenceTier": "E4",
|
||||
"name": "Limited capital canary completes at least 10 trading days",
|
||||
"note": "No limited-capital canary is authorized or observed.",
|
||||
"status": "not_passed",
|
||||
"weight": 3
|
||||
},
|
||||
{
|
||||
"domainId": "D6",
|
||||
"hardGate": true,
|
||||
"id": "D6-DR",
|
||||
"minimumEvidenceTier": "E3",
|
||||
"name": "Failure recovery meets RPO and RTO targets",
|
||||
"note": "No measured RPO/RTO and broker failure-drill evidence.",
|
||||
"status": "not_passed",
|
||||
"weight": 5
|
||||
},
|
||||
{
|
||||
"domainId": "D6",
|
||||
"hardGate": true,
|
||||
"id": "D6-RELEASE",
|
||||
"minimumEvidenceTier": "E3",
|
||||
"name": "Build, promotion, rollback and audit are reproducible",
|
||||
"note": "Independent CI, signed release inventory and rollback drill are not yet complete.",
|
||||
"status": "not_passed",
|
||||
"weight": 3
|
||||
},
|
||||
{
|
||||
"domainId": "D7",
|
||||
"hardGate": true,
|
||||
"id": "D7-SCOPE",
|
||||
"minimumEvidenceTier": "E3",
|
||||
"name": "Account and business scope remains personal proprietary",
|
||||
"note": "The intended personal scope is documented but has no private attestation reference.",
|
||||
"status": "not_passed",
|
||||
"weight": 3
|
||||
},
|
||||
{
|
||||
"domainId": "D7",
|
||||
"hardGate": true,
|
||||
"id": "D7-REPORT",
|
||||
"minimumEvidenceTier": "E3",
|
||||
"name": "Broker programmatic-trading report matches the deployed release",
|
||||
"note": "No broker programmatic-trading confirmation exists yet.",
|
||||
"status": "not_passed",
|
||||
"weight": 4
|
||||
},
|
||||
{
|
||||
"domainId": "D7",
|
||||
"hardGate": true,
|
||||
"id": "D7-FLOW",
|
||||
"minimumEvidenceTier": "E3",
|
||||
"name": "Abnormal-trading and conservative flow controls are enforced",
|
||||
"note": "No real-account abnormal-trading monitoring history.",
|
||||
"status": "not_passed",
|
||||
"weight": 3
|
||||
},
|
||||
{
|
||||
"domainId": "D7",
|
||||
"hardGate": true,
|
||||
"id": "D7-SECURITY",
|
||||
"minimumEvidenceTier": "E3",
|
||||
"name": "Secrets, access, personal data and supply chain are controlled",
|
||||
"note": "Repository secret hygiene exists, but production access, rotation and supply-chain evidence do not.",
|
||||
"status": "not_passed",
|
||||
"weight": 2
|
||||
},
|
||||
{
|
||||
"domainId": "D7",
|
||||
"hardGate": false,
|
||||
"id": "D7-REGULATED",
|
||||
"minimumEvidenceTier": "E2",
|
||||
"name": "A future regulated-mode rule calendar is maintained",
|
||||
"note": "The regulated-mode activation calendar is documentation-only.",
|
||||
"status": "not_passed",
|
||||
"weight": 3
|
||||
}
|
||||
],
|
||||
"controlsPassed": 0,
|
||||
"controlsTotal": 25,
|
||||
"domainFloorFailures": [
|
||||
"D1",
|
||||
"D2",
|
||||
"D3",
|
||||
"D4",
|
||||
"D5",
|
||||
"D6",
|
||||
"D7"
|
||||
],
|
||||
"domains": [
|
||||
{
|
||||
"floorPassed": false,
|
||||
"id": "D1",
|
||||
"minimumScore": 9,
|
||||
"name": "Data and point-in-time truth",
|
||||
"score": 0,
|
||||
"weight": 15
|
||||
},
|
||||
{
|
||||
"floorPassed": false,
|
||||
"id": "D2",
|
||||
"minimumScore": 9,
|
||||
"name": "Research and model governance",
|
||||
"score": 0,
|
||||
"weight": 15
|
||||
},
|
||||
{
|
||||
"floorPassed": false,
|
||||
"id": "D3",
|
||||
"minimumScore": 6,
|
||||
"name": "Cross-engine consistency",
|
||||
"score": 0,
|
||||
"weight": 10
|
||||
},
|
||||
{
|
||||
"floorPassed": false,
|
||||
"id": "D4",
|
||||
"minimumScore": 9,
|
||||
"name": "Execution and independent risk",
|
||||
"score": 0,
|
||||
"weight": 15
|
||||
},
|
||||
{
|
||||
"floorPassed": false,
|
||||
"id": "D5",
|
||||
"minimumScore": 9,
|
||||
"name": "Real QMT and broker truth",
|
||||
"score": 0,
|
||||
"weight": 15
|
||||
},
|
||||
{
|
||||
"floorPassed": false,
|
||||
"id": "D6",
|
||||
"minimumScore": 9,
|
||||
"name": "Operations, disaster recovery and audit",
|
||||
"score": 0,
|
||||
"weight": 15
|
||||
},
|
||||
{
|
||||
"floorPassed": false,
|
||||
"id": "D7",
|
||||
"minimumScore": 9,
|
||||
"name": "Compliance and security",
|
||||
"score": 0,
|
||||
"weight": 15
|
||||
}
|
||||
],
|
||||
"effectiveScore": 0,
|
||||
"evidencePolicy": "Only subject-bound evidence claims verified by a configured trust provider, at or above each control's minimum tier, earn points. The current repository has no production trust provider and therefore cannot self-issue a positive qualification.",
|
||||
"finalAttestationVerified": false,
|
||||
"hardGateFailures": [
|
||||
"D1-OPS",
|
||||
"D1-PIT",
|
||||
"D2-MODEL",
|
||||
"D2-OOS",
|
||||
"D3-DIFF",
|
||||
"D3-LAYERS",
|
||||
"D4-OMS",
|
||||
"D4-RISK",
|
||||
"D4-RULES",
|
||||
"D5-EVIDENCE",
|
||||
"D5-FALLBACK",
|
||||
"D5-QMT",
|
||||
"D5-RECON",
|
||||
"D6-CANARY",
|
||||
"D6-DR",
|
||||
"D6-RELEASE",
|
||||
"D6-SHADOW",
|
||||
"D7-FLOW",
|
||||
"D7-REPORT",
|
||||
"D7-SCOPE",
|
||||
"D7-SECURITY"
|
||||
],
|
||||
"hardGatesPassed": 0,
|
||||
"hardGatesTotal": 21,
|
||||
"maximumScore": 100,
|
||||
"preCanaryAttestationVerified": false,
|
||||
"preCanaryAuthorization": null,
|
||||
"preCanaryAuthorizationVerified": false,
|
||||
"preCanaryAuthorized": false,
|
||||
"preCanaryStructurallyEligible": false,
|
||||
"prerequisites": {
|
||||
"baseline_60_all_gates": false,
|
||||
"broker_programmatic_confirmation": false,
|
||||
"canary_trading_days": false,
|
||||
"open_p0_within_limit": false,
|
||||
"open_p1_within_limit": false,
|
||||
"real_qmt_observed": false,
|
||||
"shadow_trading_days": false
|
||||
},
|
||||
"productionTrustRegistryAvailable": false,
|
||||
"qualificationThreshold": 80,
|
||||
"qualified": false,
|
||||
"rawScore": 0,
|
||||
"scoreCaps": [
|
||||
{
|
||||
"maximum": 59,
|
||||
"reason": "baseline_60_not_passed"
|
||||
},
|
||||
{
|
||||
"maximum": 79,
|
||||
"reason": "canary_not_observed"
|
||||
}
|
||||
],
|
||||
"standardCanonicalSha256": "44fc6c168ade4c6de15569214d88b96ce47b22b4120b10c80eb6f212fad5e662",
|
||||
"standardId": "quant-os-production-80-v1",
|
||||
"state": "BLOCKED_FROM_LIMITED_LIVE",
|
||||
"structurallyEligible": false,
|
||||
"testOnly": false,
|
||||
"trustedAttestationVerified": false,
|
||||
"verificationEnvironment": "none"
|
||||
},
|
||||
"models": [
|
||||
{
|
||||
"claim": "共享 symbol normalization、momentum、target weight/quantity 与 order-delta 计算;2024 JoinQuant hosted run 只证明这一 smoke 路径。",
|
||||
@@ -777,8 +1189,8 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
||||
"G9"
|
||||
],
|
||||
"priority": 5,
|
||||
"title": "完成 QMT 券商合同探针与至少 20 日影子盘",
|
||||
"why": "real QMT historical-ST positive control, terminal/xtquant build record, broker query contract and trade-amount semantic probes, callback/restart and at least 20 trading days of shadow evidence"
|
||||
"title": "完成 QMT 券商合同探针与 Production 80 的 60 日影子盘(Baseline 60 门槛 20 日)",
|
||||
"why": "real QMT historical-ST positive control, terminal/xtquant build record, broker query contract and trade-amount semantic probes, and callback/restart evidence; Baseline 60 requires at least 20 trading days of shadow, while the current Production 80 gap requires at least 60 trading days for the same frozen candidate"
|
||||
},
|
||||
{
|
||||
"gateIds": [
|
||||
@@ -786,8 +1198,8 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
||||
"G10"
|
||||
],
|
||||
"priority": 6,
|
||||
"title": "完成连续对账、恢复演练与券商合规确认",
|
||||
"why": "continuous reconciliation, recovery drills and broker compliance confirmation"
|
||||
"title": "完成连续对账、恢复演练与券商程序化交易报告/核查确认",
|
||||
"why": "continuous reconciliation, recovery drills and broker programmatic-trading report/verification confirmation"
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
@@ -984,7 +1396,7 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
||||
"id": "xttrader-read-only-shadow",
|
||||
"intendedUse": "账户绑定 target-diff 与 broker observation",
|
||||
"limitations": [
|
||||
"HMAC 不是 broker attestation;仍缺官方 query 失败/空结果合同、callback/restart、20 日真实 shadow;not live-ready"
|
||||
"HMAC 不是 broker attestation;仍缺官方 query 失败/空结果合同及 callback/restart;Baseline 60 至少 20 个交易日,Production 80 当前缺口为同一冻结候选至少 60 个交易日;not live-ready"
|
||||
],
|
||||
"liveReady": false,
|
||||
"name": "XtTrader read-only shadow",
|
||||
@@ -1063,16 +1475,30 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
||||
"provenance": {
|
||||
"baselineConfig": "configs/baseline.json",
|
||||
"bundleManifest": "dist/bundle_manifest.json",
|
||||
"payloadSha256": "76aad7a50088b9949b9c2549a4da568d14c6bc2ff4acc7df6419989637386bc3",
|
||||
"payloadSha256": "9aae5da8e1d92fd28cfd7d6bef50774c4981b2baac1728ccd3153729234a88f0",
|
||||
"payloadSha256Semantics": "SHA-256 of canonical payload before payloadSha256 is added.",
|
||||
"platformMatrix": "docs/PLATFORM_MATRIX.md",
|
||||
"production80Assessment": "profiles/production-80/current-assessment.json",
|
||||
"production80Standard": "profiles/production-80/standard.json",
|
||||
"releaseReachability": "releases/quant60-research-candidate-v1/reachability.json",
|
||||
"scorecard": "gate_scorecard.json",
|
||||
"sourceSetSha256": "e1b8bb8827c6c051a73872f2b0ac46f18dcf705fb50768eb98090bbbe08f863c",
|
||||
"sourceSetSha256": "6d906cde8df07f8fae2f2faf05679a476ceae8f5173533b3b67beecc710b2f61",
|
||||
"sources": [
|
||||
{
|
||||
"path": "gate_scorecard.json",
|
||||
"sha256": "db9ed17d1b56452f973db23461e8298b4f43f319b04be373adb383133500c2b6"
|
||||
"sha256": "2cc29f8d3670cd2e1e90ebe10a7d4d5a1ab9fc6a6b08612df2bbad485cd19655"
|
||||
},
|
||||
{
|
||||
"path": "profiles/baseline-60/profile.json",
|
||||
"sha256": "fbfb71356bd6a711510dd6ef0d84a4f76c47cc8244e90120142fa6bf36780fb9"
|
||||
},
|
||||
{
|
||||
"path": "profiles/production-80/standard.json",
|
||||
"sha256": "60ca7de08b337b75ca7ed53b7c01b6a7c3d621d788e60a15f26f2adaf2844064"
|
||||
},
|
||||
{
|
||||
"path": "profiles/production-80/current-assessment.json",
|
||||
"sha256": "f6439d548c4bf176dfcd734ade2e3343b2673e9e11f8233d7c642d4ee3436369"
|
||||
},
|
||||
{
|
||||
"path": "dist/bundle_manifest.json",
|
||||
@@ -1092,27 +1518,43 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
||||
},
|
||||
{
|
||||
"path": "README.md",
|
||||
"sha256": "d5593bdc4e535502ba817636053dc8ac38f8594be6f9e61f0fe2d11cf883a985"
|
||||
"sha256": "0bf0562ae9247a88a5b0248f66b9b314d7ab0ea435bc8f8e5efa78f7d2f785bb"
|
||||
},
|
||||
{
|
||||
"path": "docs/ARCHITECTURE.md",
|
||||
"sha256": "93b1981168084f40c6d127dc534ff2e60eb4cd0703c8ef446c6df58b351dfbaa"
|
||||
"sha256": "8aae46f1b371651ac6f72905b7bcb3d72fdfda2a793e5505ab890352921c3219"
|
||||
},
|
||||
{
|
||||
"path": "docs/architecture/ARCHITECTURE_80.md",
|
||||
"sha256": "bb57df9d7eb5858741dde282ae0853a5e4ec9f4f4eb290bdb5954228fcb8956a"
|
||||
},
|
||||
{
|
||||
"path": "docs/AUDIT_2026-07-25.md",
|
||||
"sha256": "c070b49dfc2805c338400697369d886a6dc47b53af4a1834d0dfd383431e74d0"
|
||||
},
|
||||
{
|
||||
"path": "docs/standards/QUANT_OS_80_STANDARD.md",
|
||||
"sha256": "90dc9e7f1c5be17f5e6672598aac4afca3d26cf0099ae794c1be85bdafd0b65b"
|
||||
},
|
||||
{
|
||||
"path": "docs/standards/BASELINE_60_VS_PRODUCTION_80.md",
|
||||
"sha256": "c99e858dc186c100ff9d9cd673b1644bdcb81fecd6fe945166bdd7966b9cfe8a"
|
||||
},
|
||||
{
|
||||
"path": "docs/getting-started/GETTING_STARTED_80.md",
|
||||
"sha256": "5c18a558bd2f9e1640144a1729309bf38ada3185188c985476bd609b28c4479e"
|
||||
},
|
||||
{
|
||||
"path": "docs/PLATFORM_MATRIX.md",
|
||||
"sha256": "4880b17e54257b794398bf734d80ea40d1180e52845762987443a9c61d562a23"
|
||||
"sha256": "9c7952b019c92efd1055488b58838045ee30979ccc84008cd1218fab2a6d6717"
|
||||
},
|
||||
{
|
||||
"path": "docs/CROSS_ENGINE_CONSISTENCY.md",
|
||||
"sha256": "358e4798bf853a96cb288ac379f916938415b86e86dbe4fa0e3fb4a86a612b6f"
|
||||
"sha256": "bdb493e84201f51c5b91bacf8c894858b0c2dbceaaf0522c68d42d040ed45057"
|
||||
},
|
||||
{
|
||||
"path": "docs/JOINQUANT_HOSTED_EVIDENCE_2026-07-26.md",
|
||||
"sha256": "03df249829b3ad9b4fe33c5d93afbd842561458f4ff2fe10e80e4432934eb2d2"
|
||||
"sha256": "71d017a7e009331413f378fc847203d19eaa1c24317f364e9dcebe645df3d13b"
|
||||
},
|
||||
{
|
||||
"path": "docs/JOINQUANT_TARGET_PACKAGE_EVIDENCE_2026-07-26.md",
|
||||
@@ -1120,11 +1562,39 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
||||
},
|
||||
{
|
||||
"path": "docs/TUSHARE_LOCAL_DATA.md",
|
||||
"sha256": "fd6cfe6cf45dbdd82e6aa88e80b119e05a888c560b79907370c6e023b5e00e82"
|
||||
"sha256": "611b120ba59b59b443b5cd9237356c509f2f2699063784a4685506e88a95adea"
|
||||
},
|
||||
{
|
||||
"path": "MIGRATION.md",
|
||||
"sha256": "1d86ef7d854dc7c2eed80d2c5e5de0ee30bf87035be9be27a2eb8cb098277a5c"
|
||||
},
|
||||
{
|
||||
"path": "ops/README.md",
|
||||
"sha256": "eda75a106ea961faa22ada2c95c503ee2a5437030e238d065d7f112550dcac0d"
|
||||
},
|
||||
{
|
||||
"path": "ops/activate_static_status.sh",
|
||||
"sha256": "26e026da5ab13d228696de4a1d9a811bc80e2adf3220859b868129852655a93f"
|
||||
},
|
||||
{
|
||||
"path": "ops/deploy_static_status.sh",
|
||||
"sha256": "2b86edbd76e42832aba661d98cbadfc34607fb626d36324f7933e89c66a89c6e"
|
||||
},
|
||||
{
|
||||
"path": "ops/nginx/quant-os-static.conf",
|
||||
"sha256": "10b1c338c22caa0d4c4f68c1213d6c297a367995601dece7dd19df561b0c002e"
|
||||
},
|
||||
{
|
||||
"path": "ops/nginx/quant-os-legacy-redirects-302.conf",
|
||||
"sha256": "5206a0ed69ee01b8596fb3c5be9fdab713c14a512968c0cd24839e65495508a4"
|
||||
},
|
||||
{
|
||||
"path": "ops/nginx/quant-os-legacy-redirects-308.conf",
|
||||
"sha256": "ce8fc27e36b1c07dad445d76199b31a0113b42d759db8af7a60a3442548d4f94"
|
||||
},
|
||||
{
|
||||
"path": "runbooks/PLATFORM_DEPLOYMENT.md",
|
||||
"sha256": "f6bb1eda335c0d05f96d39b4d4167ee623fe6cffeef75015ba7985638fa8f635"
|
||||
"sha256": "001bc09f6865c6ee90fbbc2363b94bed7a282d039790af024457a5b1390b9840"
|
||||
},
|
||||
{
|
||||
"path": "tools/build_release_reachability.py",
|
||||
@@ -1132,11 +1602,19 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
||||
},
|
||||
{
|
||||
"path": "tools/build_public_status.py",
|
||||
"sha256": "38347c2238a38fc603703c7265fd1ca54480b3f3f8c453cde9f21056f207a4ec"
|
||||
"sha256": "ec24dd12a6383ab1210a475c6f982b911f6ce45a7c205b9474bb2bb70665bc2a"
|
||||
},
|
||||
{
|
||||
"path": "tools/check_tracked_secrets.py",
|
||||
"sha256": "359eddb5fb009d3790bd81ce9c939ceb9aea7868fde4ccbc22ffe01099c62304"
|
||||
},
|
||||
{
|
||||
"path": "tools/inventory_local_assets.py",
|
||||
"sha256": "f52a89b2e42d0f3aed7e3c79e0110c37cab779794f550658116e07b9e338160b"
|
||||
}
|
||||
]
|
||||
},
|
||||
"schemaVersion": 1,
|
||||
"schemaVersion": 2,
|
||||
"summary": {
|
||||
"allFiveLayersRealPlatformObserved": false,
|
||||
"candidateRealPlatformObservationScope": "target_package_execution_consumer_only",
|
||||
@@ -1153,7 +1631,7 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
||||
"Tushare completed-partition Qlib momentum smoke"
|
||||
],
|
||||
"testEvidenceQualifier": "Scorecard-recorded run; tests do not prove real-platform or broker gates.",
|
||||
"testsRecorded": 285
|
||||
"testsRecorded": 315
|
||||
},
|
||||
"system": "Quant OS / quant60 baseline kernel"
|
||||
};
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import json
|
||||
import os
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
@@ -23,26 +26,355 @@ class ProjectScaffoldTests(unittest.TestCase):
|
||||
)
|
||||
payload = json.loads(result.stdout.strip().splitlines()[-1])
|
||||
self.assertTrue(payload["ok"])
|
||||
self.assertGreaterEqual(payload["code_cells"], 5)
|
||||
self.assertGreaterEqual(payload["code_cells"], 4)
|
||||
self.assertFalse(payload["executed"])
|
||||
optional_result = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
"tools/run_colab_notebook.py",
|
||||
"notebooks/quant_os_colab.ipynb",
|
||||
"--include-optional",
|
||||
],
|
||||
cwd=PROJECT,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
optional_payload = json.loads(
|
||||
optional_result.stdout.strip().splitlines()[-1]
|
||||
)
|
||||
self.assertTrue(optional_payload["optional_included"])
|
||||
self.assertGreater(
|
||||
optional_payload["code_cells"],
|
||||
payload["code_cells"],
|
||||
)
|
||||
|
||||
def test_secret_files_are_ignored(self):
|
||||
ignore = (PROJECT / ".gitignore").read_text(encoding="utf-8")
|
||||
for entry in (".env", "secrets/", "credentials/", "userdata_mini/"):
|
||||
for entry in (
|
||||
".env",
|
||||
"secrets/",
|
||||
"credentials/",
|
||||
"userdata_mini/",
|
||||
"data/",
|
||||
"build/",
|
||||
):
|
||||
self.assertIn(entry, ignore)
|
||||
|
||||
def test_tracked_secret_scan_passes(self):
|
||||
result = subprocess.run(
|
||||
[sys.executable, "tools/check_tracked_secrets.py"],
|
||||
cwd=PROJECT,
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertIn("tracked secret scan: OK", result.stdout)
|
||||
|
||||
def test_claude_project_contract_exists(self):
|
||||
content = (PROJECT / "CLAUDE.md").read_text(encoding="utf-8")
|
||||
self.assertIn("executable source of truth for Quant OS", content)
|
||||
self.assertIn("make local", content)
|
||||
|
||||
def test_gitea_workflow_is_at_monorepo_root(self):
|
||||
workflow = PROJECT.parent / ".gitea/workflows/quant-os-ci.yml"
|
||||
def test_gitea_workflow_is_at_independent_repo_root(self):
|
||||
workflow = PROJECT / ".gitea/workflows/ci.yml"
|
||||
self.assertTrue(workflow.is_file())
|
||||
content = workflow.read_text(encoding="utf-8")
|
||||
self.assertIn("working-directory: quant-os", content)
|
||||
self.assertIn("bash scripts/validate_all.sh", content)
|
||||
self.assertFalse((PROJECT / ".gitea/workflows/ci.yml").exists())
|
||||
self.assertNotIn("working-directory: quant-os", content)
|
||||
self.assertNotIn('"quant-os/**"', content)
|
||||
self.assertIn(r"\.env(\.[^/]+)?", content)
|
||||
self.assertIn(r"\.env\.example", content)
|
||||
|
||||
def test_long_lived_product_namespace_is_installable(self):
|
||||
pyproject = (PROJECT / "pyproject.toml").read_text(encoding="utf-8")
|
||||
self.assertIn('name = "quant-os"', pyproject)
|
||||
self.assertIn('quant-os = "quant_os.cli:main"', pyproject)
|
||||
self.assertIn('quant60 = "quant60.cli:main"', pyproject)
|
||||
self.assertTrue((PROJECT / "src/quant_os/__init__.py").is_file())
|
||||
|
||||
def test_cli_can_resolve_an_explicit_checkout_from_elsewhere(self):
|
||||
environment = {**os.environ, "PYTHONPATH": str(PROJECT / "src")}
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
result = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
"-m",
|
||||
"quant_os",
|
||||
"--project-root",
|
||||
str(PROJECT),
|
||||
"doctor",
|
||||
],
|
||||
cwd=temporary,
|
||||
env=environment,
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
payload = json.loads(result.stdout)
|
||||
self.assertTrue(payload["ok"])
|
||||
self.assertEqual(
|
||||
payload["architecture"]["scope"],
|
||||
"new_namespace_only",
|
||||
)
|
||||
self.assertGreater(
|
||||
payload["migration"]["legacy_python_files_outside_checker"],
|
||||
0,
|
||||
)
|
||||
|
||||
def test_nginx_source_of_truth_has_atomic_release_routes(self):
|
||||
config = (
|
||||
PROJECT / "ops/nginx/quant-os-static.conf"
|
||||
).read_text(encoding="utf-8")
|
||||
self.assertIn("location ^~ /quant-os/status/", config)
|
||||
self.assertIn("root /srv/www;", config)
|
||||
self.assertIn(
|
||||
"https://$host/quant-os/status/",
|
||||
config,
|
||||
)
|
||||
self.assertNotIn("/quants-strategies/", config)
|
||||
self.assertNotIn(":8443", config)
|
||||
temporary = (
|
||||
PROJECT
|
||||
/ "ops/nginx/quant-os-legacy-redirects-302.conf"
|
||||
).read_text(encoding="utf-8")
|
||||
stable = (
|
||||
PROJECT
|
||||
/ "ops/nginx/quant-os-legacy-redirects-308.conf"
|
||||
).read_text(encoding="utf-8")
|
||||
self.assertIn(
|
||||
"location = /quants-strategies/quant-os/status",
|
||||
temporary,
|
||||
)
|
||||
self.assertIn("return 302 ", temporary)
|
||||
self.assertNotIn("return 308 ", temporary)
|
||||
self.assertIn("return 308 ", stable)
|
||||
|
||||
def test_static_status_deployer_is_atomic_and_non_overwriting(self):
|
||||
sha = "a" * 40
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
web_root = Path(temporary) / "web"
|
||||
result = subprocess.run(
|
||||
[
|
||||
"bash",
|
||||
"ops/deploy_static_status.sh",
|
||||
sha,
|
||||
"status",
|
||||
str(web_root),
|
||||
],
|
||||
cwd=PROJECT,
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertEqual(
|
||||
(web_root / "status").readlink(),
|
||||
Path("releases") / sha,
|
||||
)
|
||||
release = web_root / "releases" / sha
|
||||
self.assertEqual(
|
||||
{path.name for path in release.iterdir()},
|
||||
{"index.html", "status-data.js"},
|
||||
)
|
||||
self.assertFalse(release.stat().st_mode & stat.S_IWUSR)
|
||||
self.assertFalse(
|
||||
release.joinpath("index.html").stat().st_mode & stat.S_IWUSR
|
||||
)
|
||||
self.assertFalse(web_root.joinpath(f".incoming-{sha}").exists())
|
||||
self.assertFalse(web_root.joinpath(".status-deploy.lock").exists())
|
||||
second_sha = "b" * 40
|
||||
switched = subprocess.run(
|
||||
[
|
||||
"bash",
|
||||
"ops/deploy_static_status.sh",
|
||||
second_sha,
|
||||
"status",
|
||||
str(web_root),
|
||||
],
|
||||
cwd=PROJECT,
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
self.assertEqual(switched.returncode, 0, switched.stderr)
|
||||
self.assertEqual(
|
||||
(web_root / "status").readlink(),
|
||||
Path("releases") / second_sha,
|
||||
)
|
||||
second = subprocess.run(
|
||||
[
|
||||
"bash",
|
||||
"ops/deploy_static_status.sh",
|
||||
second_sha,
|
||||
"status",
|
||||
str(web_root),
|
||||
],
|
||||
cwd=PROJECT,
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
self.assertNotEqual(second.returncode, 0)
|
||||
self.assertIn("refusing to overwrite", second.stderr)
|
||||
|
||||
def test_static_status_deployer_rejects_non_regular_or_extra_source(self):
|
||||
cases = ("symlink", "extra-directory")
|
||||
for case in cases:
|
||||
with self.subTest(case=case):
|
||||
temporary_context = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(temporary_context.cleanup)
|
||||
temporary = temporary_context.name
|
||||
root = Path(temporary)
|
||||
source = root / "source"
|
||||
source.mkdir()
|
||||
source.joinpath("status-data.js").write_text(
|
||||
"window.STATUS = {};\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
if case == "symlink":
|
||||
source.joinpath("index.html").symlink_to(
|
||||
PROJECT / "status/index.html"
|
||||
)
|
||||
else:
|
||||
source.joinpath("index.html").write_text(
|
||||
"<!doctype html>\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
source.joinpath("extra").mkdir()
|
||||
web_root = root / "web"
|
||||
result = subprocess.run(
|
||||
[
|
||||
"bash",
|
||||
"ops/deploy_static_status.sh",
|
||||
"c" * 40,
|
||||
str(source),
|
||||
str(web_root),
|
||||
],
|
||||
cwd=PROJECT,
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertFalse(web_root.joinpath("releases").exists())
|
||||
|
||||
def test_static_status_deploy_and_activation_share_concurrency_lock(self):
|
||||
sha = "d" * 40
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
web_root = Path(temporary) / "web"
|
||||
web_root.mkdir()
|
||||
web_root.joinpath(".status-deploy.lock").mkdir()
|
||||
deploy = subprocess.run(
|
||||
[
|
||||
"bash",
|
||||
"ops/deploy_static_status.sh",
|
||||
sha,
|
||||
"status",
|
||||
str(web_root),
|
||||
],
|
||||
cwd=PROJECT,
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
self.assertNotEqual(deploy.returncode, 0)
|
||||
self.assertIn("another status deployment", deploy.stderr)
|
||||
|
||||
release = web_root / "releases" / sha
|
||||
release.mkdir(parents=True)
|
||||
for name in ("index.html", "status-data.js"):
|
||||
release.joinpath(name).write_text(name, encoding="utf-8")
|
||||
activate = subprocess.run(
|
||||
[
|
||||
"bash",
|
||||
"ops/activate_static_status.sh",
|
||||
sha,
|
||||
str(web_root),
|
||||
],
|
||||
cwd=PROJECT,
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
self.assertNotEqual(activate.returncode, 0)
|
||||
self.assertIn("another status deployment", activate.stderr)
|
||||
self.assertFalse(web_root.joinpath("status").exists())
|
||||
|
||||
def test_static_status_activation_rejects_extra_release_entry(self):
|
||||
sha = "e" * 40
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
web_root = Path(temporary) / "web"
|
||||
release = web_root / "releases" / sha
|
||||
release.mkdir(parents=True)
|
||||
for name in ("index.html", "status-data.js"):
|
||||
release.joinpath(name).write_text(name, encoding="utf-8")
|
||||
release.joinpath("extra").mkdir()
|
||||
result = subprocess.run(
|
||||
[
|
||||
"bash",
|
||||
"ops/activate_static_status.sh",
|
||||
sha,
|
||||
str(web_root),
|
||||
],
|
||||
cwd=PROJECT,
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn("exactly index.html and status-data.js", result.stderr)
|
||||
self.assertFalse(web_root.joinpath("status").exists())
|
||||
self.assertFalse(web_root.joinpath(".status-deploy.lock").exists())
|
||||
|
||||
def test_local_asset_inventory_is_deterministic_and_path_safe(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
(root / "data").mkdir()
|
||||
(root / "artifacts").mkdir()
|
||||
(root / "data" / "one.bin").write_bytes(b"one")
|
||||
(root / "artifacts" / "two.json").write_text(
|
||||
"{}\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
first = root / "first.json"
|
||||
result = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
str(PROJECT / "tools/inventory_local_assets.py"),
|
||||
str(root),
|
||||
"--output",
|
||||
str(first),
|
||||
],
|
||||
cwd=PROJECT,
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
payload = json.loads(first.read_text(encoding="utf-8"))
|
||||
self.assertEqual(
|
||||
[entry["path"] for entry in payload["entries"]],
|
||||
["data/one.bin", "artifacts/two.json"],
|
||||
)
|
||||
encoded = json.dumps(payload)
|
||||
self.assertNotIn(str(root), encoded)
|
||||
compare = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
str(PROJECT / "tools/inventory_local_assets.py"),
|
||||
str(root),
|
||||
"--compare",
|
||||
str(first),
|
||||
],
|
||||
cwd=PROJECT,
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
self.assertEqual(compare.returncode, 0, compare.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -54,6 +54,28 @@ class PublicStatusTests(unittest.TestCase):
|
||||
self.assertEqual(payload["claim"]["gatesPassed"], 0)
|
||||
self.assertEqual(payload["claim"]["gatesTotal"], 10)
|
||||
self.assertTrue(all(gate["status"] == "not_passed" for gate in payload["gates"]))
|
||||
self.assertEqual(payload["schemaVersion"], 2)
|
||||
self.assertFalse(payload["maturity80"]["qualified"])
|
||||
self.assertFalse(payload["maturity80"]["baseline60Passed"])
|
||||
self.assertEqual(payload["maturity80"]["effectiveScore"], 0)
|
||||
self.assertFalse(payload["maturity80"]["preCanaryAuthorized"])
|
||||
self.assertFalse(
|
||||
payload["maturity80"]["preCanaryAuthorizationVerified"]
|
||||
)
|
||||
self.assertFalse(
|
||||
payload["maturity80"]["canaryAuthorizationChainVerified"]
|
||||
)
|
||||
self.assertFalse(
|
||||
payload["maturity80"]["trustedAttestationVerified"]
|
||||
)
|
||||
self.assertEqual(payload["marketScope"]["included"], ["SSE", "SZSE"])
|
||||
self.assertEqual(
|
||||
payload["marketScope"]["excludedFailClosed"],
|
||||
["BSE"],
|
||||
)
|
||||
self.assertFalse(payload["marketScope"]["bseTradingEnabled"])
|
||||
self.assertIn("北交所", payload["marketScope"]["statement"])
|
||||
self.assertIn("fail closed", payload["marketScope"]["statement"])
|
||||
|
||||
def test_public_payload_is_deliberately_non_secret_and_non_local(self):
|
||||
payload = _read_payload()
|
||||
@@ -84,8 +106,23 @@ class PublicStatusTests(unittest.TestCase):
|
||||
self.assertIn("NOT_BASELINE_60", page)
|
||||
self.assertIn("0 / 10", page)
|
||||
self.assertIn("validatePublicData", page)
|
||||
self.assertIn("current canary state lacks authorization", page)
|
||||
self.assertNotIn("all gates must remain", page)
|
||||
self.assertIn('id="maturity80"', page)
|
||||
self.assertIn("renderMaturity80", page)
|
||||
self.assertIn('id="market-scope-copy"', page)
|
||||
self.assertIn("个人沪深 A 股", page)
|
||||
self.assertIn("北交所", page)
|
||||
self.assertIn("platform.limitations", page)
|
||||
self.assertNotIn("platform.missingReleaseEvidence", page)
|
||||
self.assertIn(
|
||||
"https://git.gomars.fun/boat/quant-os/src/branch/master/",
|
||||
page,
|
||||
)
|
||||
self.assertNotIn(
|
||||
"quants-strategies/src/branch/master/quant-os",
|
||||
page,
|
||||
)
|
||||
self.assertNotRegex(
|
||||
page,
|
||||
re.compile(
|
||||
@@ -98,6 +135,37 @@ class PublicStatusTests(unittest.TestCase):
|
||||
for tag in re.findall(r"<a\\b[^>]*target=\"_blank\"[^>]*>", page):
|
||||
self.assertIn('rel="noopener noreferrer"', tag)
|
||||
|
||||
def test_production80_is_machine_evaluated_and_fail_closed(self):
|
||||
payload = _read_payload()
|
||||
maturity = payload["maturity80"]
|
||||
self.assertEqual(maturity["standardId"], "quant-os-production-80-v1")
|
||||
self.assertEqual(maturity["maximumScore"], 100)
|
||||
self.assertEqual(maturity["qualificationThreshold"], 80)
|
||||
self.assertEqual((maturity["rawScore"], maturity["effectiveScore"]), (0, 0))
|
||||
self.assertEqual(
|
||||
maturity["state"],
|
||||
"BLOCKED_FROM_LIMITED_LIVE",
|
||||
)
|
||||
self.assertFalse(maturity["qualified"])
|
||||
self.assertEqual(maturity["controlsPassed"], 0)
|
||||
self.assertEqual(maturity["controlsTotal"], 25)
|
||||
self.assertGreater(maturity["hardGatesTotal"], 0)
|
||||
self.assertEqual(len(maturity["domains"]), 7)
|
||||
self.assertTrue(
|
||||
all(not domain["floorPassed"] for domain in maturity["domains"])
|
||||
)
|
||||
self.assertTrue(
|
||||
all(value is False for value in maturity["prerequisites"].values())
|
||||
)
|
||||
self.assertEqual(
|
||||
payload["provenance"]["production80Standard"],
|
||||
"profiles/production-80/standard.json",
|
||||
)
|
||||
self.assertEqual(
|
||||
payload["provenance"]["production80Assessment"],
|
||||
"profiles/production-80/current-assessment.json",
|
||||
)
|
||||
|
||||
def test_models_and_platforms_preserve_evidence_boundaries(self):
|
||||
payload = _read_payload()
|
||||
models = {model["id"]: model for model in payload["models"]}
|
||||
@@ -124,6 +192,11 @@ class PublicStatusTests(unittest.TestCase):
|
||||
self.assertEqual(platforms["QMT built-in bundle"]["status"], "implemented")
|
||||
self.assertFalse(platforms["XtTrader read-only shadow"]["liveReady"])
|
||||
self.assertIn("fake", platforms["XtTrader read-only shadow"]["claim"].lower())
|
||||
shadow_limit = platforms["XtTrader read-only shadow"]["limitations"][0]
|
||||
self.assertIn("Baseline 60", shadow_limit)
|
||||
self.assertIn("20", shadow_limit)
|
||||
self.assertIn("Production 80", shadow_limit)
|
||||
self.assertIn("60", shadow_limit)
|
||||
|
||||
def test_candidate_records_only_real_joinquant_execution_observation(self):
|
||||
payload = _read_payload()
|
||||
@@ -301,7 +374,7 @@ class PublicStatusTests(unittest.TestCase):
|
||||
def test_freshness_uses_daily_bars_not_the_later_trade_calendar(self):
|
||||
payload = _read_payload()
|
||||
freshness = {item["id"]: item for item in payload["freshness"]}
|
||||
self.assertEqual(freshness["project-audit"]["value"], "2026-07-26")
|
||||
self.assertEqual(freshness["project-audit"]["value"], "2026-07-30")
|
||||
self.assertEqual(
|
||||
freshness["joinquant-hosted-market-window"]["marketDataThrough"],
|
||||
"2024-12-31",
|
||||
@@ -399,7 +472,7 @@ class PublicStatusTests(unittest.TestCase):
|
||||
scorecard = {
|
||||
"claim_policy": {
|
||||
"baseline_threshold": 60,
|
||||
"current_claim": "BASELINE_60",
|
||||
"current_claim": "BASELINE_60_VERIFIED",
|
||||
},
|
||||
"current_delivery": {"score": 59},
|
||||
"gates": [{"id": "G1", "status": "not_passed"}],
|
||||
|
||||
@@ -0,0 +1,954 @@
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
PROJECT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(PROJECT / "src"))
|
||||
|
||||
from quant_os.architecture import validate_architecture
|
||||
from quant_os.evidence.maturity import (
|
||||
MaturityStandardError,
|
||||
_final_assessment_digest,
|
||||
_pre_canary_projection_digest,
|
||||
evaluate_assessment,
|
||||
load_json_object,
|
||||
validate_standard,
|
||||
)
|
||||
|
||||
|
||||
PROFILE = PROJECT / "profiles/production-80/standard.json"
|
||||
ASSESSMENT = PROJECT / "profiles/production-80/current-assessment.json"
|
||||
|
||||
|
||||
class QuantOsMaturityTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.standard = load_json_object(PROFILE)
|
||||
self.assessment = load_json_object(ASSESSMENT)
|
||||
|
||||
@staticmethod
|
||||
def _frozen_subject():
|
||||
return {
|
||||
"candidate_id": "candidate-test-v1",
|
||||
"release_sha256": "1" * 64,
|
||||
"model_sha256": "2" * 64,
|
||||
"dataset_sha256": "3" * 64,
|
||||
"account_scope_id": "broker-account-pseudonym",
|
||||
"material_change_epoch": 0,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _trusted_test_verifier(claim, control, source):
|
||||
trusted = (
|
||||
claim.get("attestation")
|
||||
== {"method": "unit-test-trust-boundary"}
|
||||
and source.is_file()
|
||||
and claim.get("control_id") == control["id"]
|
||||
)
|
||||
return {
|
||||
"trusted": trusted,
|
||||
"trust_environment": "test",
|
||||
"verifier_id": "unit-test-verifier",
|
||||
"issuer_id": "unit-test-issuer",
|
||||
"key_fingerprint_sha256": "9" * 64,
|
||||
"policy_version": "unit-test-policy-v1",
|
||||
}
|
||||
|
||||
def _write_claim(
|
||||
self,
|
||||
root,
|
||||
*,
|
||||
control_id,
|
||||
tier,
|
||||
subject,
|
||||
evidence_type="control_acceptance_evidence",
|
||||
assessment_sha256=None,
|
||||
observed_as_of="2026-06-30",
|
||||
extra=None,
|
||||
):
|
||||
claims = root / "claims"
|
||||
claims.mkdir(exist_ok=True)
|
||||
claim = {
|
||||
"schema_version": "quant-os-evidence-claim/1.0",
|
||||
"claim_id": f"claim-{control_id.lower()}",
|
||||
"control_id": control_id,
|
||||
"tier": tier,
|
||||
"subject": subject,
|
||||
"standard_id": self.standard["standard_id"],
|
||||
"standard_canonical_sha256": self.assessment[
|
||||
"standard_canonical_sha256"
|
||||
],
|
||||
"evidence_type": evidence_type,
|
||||
"authority_class": "unit_test_only",
|
||||
"observed_as_of": observed_as_of,
|
||||
"source_hashes": ["4" * 64],
|
||||
"attestation": {"method": "unit-test-trust-boundary"},
|
||||
}
|
||||
if assessment_sha256 is not None:
|
||||
claim["assessment_sha256"] = assessment_sha256
|
||||
if extra is not None:
|
||||
claim.update(extra)
|
||||
path = claims / f"{control_id.lower()}.json"
|
||||
path.write_text(
|
||||
json.dumps(claim, sort_keys=True),
|
||||
encoding="utf-8",
|
||||
)
|
||||
return {
|
||||
"path": path.relative_to(root).as_posix(),
|
||||
"tier": tier,
|
||||
"sha256": hashlib.sha256(path.read_bytes()).hexdigest(),
|
||||
}
|
||||
|
||||
def _prepare_qualified_assessment(
|
||||
self,
|
||||
root,
|
||||
passing_controls,
|
||||
*,
|
||||
canary_days=10,
|
||||
attestation_kind="qualification",
|
||||
):
|
||||
assessment = copy.deepcopy(self.assessment)
|
||||
subject = self._frozen_subject()
|
||||
assessment["subject"] = subject
|
||||
baseline_profile = load_json_object(
|
||||
PROJECT / "profiles/baseline-60/profile.json"
|
||||
)
|
||||
baseline_root = root / "profiles" / "baseline-60"
|
||||
baseline_root.mkdir(parents=True)
|
||||
(baseline_root / "profile.json").write_text(
|
||||
json.dumps(baseline_profile, sort_keys=True),
|
||||
encoding="utf-8",
|
||||
)
|
||||
baseline_verification = self._write_claim(
|
||||
root,
|
||||
control_id="BASELINE_60",
|
||||
tier="E3",
|
||||
subject=subject,
|
||||
evidence_type="baseline60_qualification_attestation",
|
||||
extra={
|
||||
"baseline_profile_id": "quant-os-baseline-60",
|
||||
"baseline_profile_canonical_sha256": self.assessment[
|
||||
"baseline_60"
|
||||
]["profile_canonical_sha256"],
|
||||
"gates_passed": 10,
|
||||
"gates_total": 10,
|
||||
"score": 60,
|
||||
},
|
||||
)
|
||||
assessment["baseline_60"] = {
|
||||
"all_gates_passed": True,
|
||||
"gates_passed": 10,
|
||||
"gates_total": 10,
|
||||
"profile_id": "quant-os-baseline-60",
|
||||
"profile_canonical_sha256": self.assessment["baseline_60"][
|
||||
"profile_canonical_sha256"
|
||||
],
|
||||
"verification": baseline_verification,
|
||||
}
|
||||
assessment["observations"] = {
|
||||
"real_qmt_observed": True,
|
||||
"broker_programmatic_confirmation": True,
|
||||
"shadow_trading_days": 60,
|
||||
"canary_trading_days": canary_days,
|
||||
"open_p0": 0,
|
||||
"open_p1": 0,
|
||||
}
|
||||
for control in assessment["controls"]:
|
||||
if (
|
||||
control["id"] in passing_controls
|
||||
and control["id"] != "D6-CANARY"
|
||||
):
|
||||
standard_control = next(
|
||||
item
|
||||
for domain in self.standard["domains"]
|
||||
for item in domain["controls"]
|
||||
if item["id"] == control["id"]
|
||||
)
|
||||
control["status"] = "passed"
|
||||
control["evidence"] = [
|
||||
self._write_claim(
|
||||
root,
|
||||
control_id=control["id"],
|
||||
tier=standard_control["minimum_evidence_tier"],
|
||||
subject=subject,
|
||||
)
|
||||
]
|
||||
if attestation_kind not in {"qualification", "pre_canary"}:
|
||||
raise AssertionError(f"unknown attestation kind {attestation_kind}")
|
||||
assessment["pre_canary_attestation"] = self._write_claim(
|
||||
root,
|
||||
control_id="PRE_CANARY_AUTHORIZATION",
|
||||
tier="E3",
|
||||
subject=subject,
|
||||
evidence_type="pre_canary_authorization_attestation",
|
||||
assessment_sha256=_pre_canary_projection_digest(assessment),
|
||||
observed_as_of="2026-07-01",
|
||||
extra={
|
||||
"authorization_id": (
|
||||
"claim-pre_canary_authorization"
|
||||
),
|
||||
"authorized_at": "2026-07-01",
|
||||
"expires_on": "2026-08-31",
|
||||
"account_scope_id": subject["account_scope_id"],
|
||||
"strategy_ids": [subject["candidate_id"]],
|
||||
"maximum_capital_cny": 100000,
|
||||
"maximum_gross_notional_cny": 80000,
|
||||
"maximum_symbols": 2,
|
||||
"symbol_allowlist": [
|
||||
"000001.XSHE",
|
||||
"600000.XSHG",
|
||||
],
|
||||
"flow_limits": {
|
||||
"maximum_submission_and_cancel_requests_per_second": 2,
|
||||
"maximum_submission_and_cancel_requests_per_day": 50,
|
||||
},
|
||||
"stop_conditions": [
|
||||
"any_risk_data_rule_or_reconciliation_breach",
|
||||
"authorization_expired",
|
||||
"operator_kill_switch",
|
||||
],
|
||||
"no_automatic_scale_up": True,
|
||||
},
|
||||
)
|
||||
if "D6-CANARY" in passing_controls:
|
||||
canary_control = next(
|
||||
control
|
||||
for control in assessment["controls"]
|
||||
if control["id"] == "D6-CANARY"
|
||||
)
|
||||
canary_control["status"] = "passed"
|
||||
canary_control["evidence"] = [
|
||||
self._write_claim(
|
||||
root,
|
||||
control_id="D6-CANARY",
|
||||
tier="E4",
|
||||
subject=subject,
|
||||
evidence_type="controlled_live_canary_observation",
|
||||
observed_as_of="2026-07-15",
|
||||
extra={
|
||||
"pre_canary_authorization_claim_id": (
|
||||
"claim-pre_canary_authorization"
|
||||
),
|
||||
"pre_canary_authorization_sha256": assessment[
|
||||
"pre_canary_attestation"
|
||||
]["sha256"],
|
||||
"canary_started_on": "2026-07-02",
|
||||
"canary_ended_on": "2026-07-15",
|
||||
"trading_days_observed": canary_days,
|
||||
"maximum_capital_cny_observed": 75000,
|
||||
"maximum_gross_notional_cny_observed": 60000,
|
||||
"symbols_observed": [
|
||||
"000001.XSHE",
|
||||
"600000.XSHG",
|
||||
],
|
||||
"maximum_submission_and_cancel_requests_per_second_observed": 2,
|
||||
"maximum_submission_and_cancel_requests_per_day_observed": 40,
|
||||
"automatic_scale_up_observed": False,
|
||||
"breach_events": [],
|
||||
"stop_events": [],
|
||||
},
|
||||
)
|
||||
]
|
||||
if attestation_kind == "qualification":
|
||||
assessment["qualification_attestation"] = self._write_claim(
|
||||
root,
|
||||
control_id="QUALIFICATION",
|
||||
tier="E4",
|
||||
subject=subject,
|
||||
evidence_type="production80_promotion_attestation",
|
||||
assessment_sha256=_final_assessment_digest(assessment),
|
||||
observed_as_of="2026-07-16",
|
||||
)
|
||||
return assessment
|
||||
|
||||
def test_profile_is_exactly_weighted_and_current_claim_is_blocked(self):
|
||||
summary = validate_standard(self.standard)
|
||||
self.assertEqual(summary["maximum_score"], 100)
|
||||
self.assertEqual(summary["domain_weight_total"], 100)
|
||||
self.assertEqual(summary["qualification_threshold"], 80)
|
||||
self.assertEqual(summary["domain_count"], 7)
|
||||
self.assertEqual(summary["hard_gate_count"], 21)
|
||||
self.assertEqual(summary["hard_gate_weight_total"], 80)
|
||||
result = evaluate_assessment(
|
||||
self.standard,
|
||||
self.assessment,
|
||||
repository_root=PROJECT,
|
||||
)
|
||||
self.assertEqual(result["raw_score"], 0)
|
||||
self.assertEqual(result["effective_score"], 0)
|
||||
self.assertFalse(result["baseline_60_passed"])
|
||||
self.assertFalse(result["qualified"])
|
||||
self.assertEqual(
|
||||
result["qualification_state"],
|
||||
"BLOCKED_FROM_LIMITED_LIVE",
|
||||
)
|
||||
self.assertTrue(result["hard_gate_failures"])
|
||||
self.assertEqual(
|
||||
set(result["domain_floor_failures"]),
|
||||
{f"D{index}" for index in range(1, 8)},
|
||||
)
|
||||
|
||||
def test_a_pass_without_hashed_evidence_is_rejected(self):
|
||||
assessment = copy.deepcopy(self.assessment)
|
||||
assessment["controls"][0]["status"] = "passed"
|
||||
with self.assertRaisesRegex(
|
||||
MaturityStandardError,
|
||||
"without immutable evidence",
|
||||
):
|
||||
evaluate_assessment(
|
||||
self.standard,
|
||||
assessment,
|
||||
repository_root=PROJECT,
|
||||
)
|
||||
|
||||
def test_all_controls_form_a_test_only_structural_path(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
passing_controls = {
|
||||
control["id"]
|
||||
for domain in self.standard["domains"]
|
||||
for control in domain["controls"]
|
||||
}
|
||||
assessment = self._prepare_qualified_assessment(
|
||||
root,
|
||||
passing_controls,
|
||||
)
|
||||
result = evaluate_assessment(
|
||||
self.standard,
|
||||
assessment,
|
||||
repository_root=root,
|
||||
trusted_evidence_verifier=self._trusted_test_verifier,
|
||||
)
|
||||
self.assertEqual(result["effective_score"], 100)
|
||||
self.assertFalse(result["qualified"])
|
||||
self.assertTrue(result["structurally_eligible"])
|
||||
self.assertTrue(result["final_attestation_verified"])
|
||||
self.assertFalse(result["trusted_attestation_verified"])
|
||||
self.assertTrue(result["test_only"])
|
||||
self.assertTrue(result["canary_authorization_chain_verified"])
|
||||
self.assertEqual(
|
||||
result["qualification_state"],
|
||||
"TEST_ONLY_STRUCTURAL_PASS",
|
||||
)
|
||||
|
||||
def test_exactly_eighty_points_is_a_structural_path_only(self):
|
||||
hard_controls = {
|
||||
control["id"]
|
||||
for domain in self.standard["domains"]
|
||||
for control in domain["controls"]
|
||||
if control["hard_gate"]
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
assessment = self._prepare_qualified_assessment(
|
||||
root,
|
||||
hard_controls,
|
||||
)
|
||||
result = evaluate_assessment(
|
||||
self.standard,
|
||||
assessment,
|
||||
repository_root=root,
|
||||
trusted_evidence_verifier=self._trusted_test_verifier,
|
||||
)
|
||||
self.assertEqual(result["raw_score"], 80)
|
||||
self.assertEqual(result["effective_score"], 80)
|
||||
self.assertFalse(result["qualified"])
|
||||
self.assertEqual(
|
||||
result["qualification_state"],
|
||||
"TEST_ONLY_STRUCTURAL_PASS",
|
||||
)
|
||||
|
||||
def test_structural_score_cannot_self_attest_without_trusted_verifier(self):
|
||||
hard_controls = {
|
||||
control["id"]
|
||||
for domain in self.standard["domains"]
|
||||
for control in domain["controls"]
|
||||
if control["hard_gate"]
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
assessment = self._prepare_qualified_assessment(
|
||||
root,
|
||||
hard_controls,
|
||||
)
|
||||
with self.assertRaisesRegex(
|
||||
MaturityStandardError,
|
||||
"trusted evidence verifier",
|
||||
):
|
||||
evaluate_assessment(
|
||||
self.standard,
|
||||
assessment,
|
||||
repository_root=root,
|
||||
)
|
||||
|
||||
def test_pre_canary_requires_a_separate_trusted_authorization(self):
|
||||
pre_canary_controls = {
|
||||
control["id"]
|
||||
for domain in self.standard["domains"]
|
||||
for control in domain["controls"]
|
||||
if control["hard_gate"] and control["id"] != "D6-CANARY"
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
assessment = self._prepare_qualified_assessment(
|
||||
root,
|
||||
pre_canary_controls,
|
||||
canary_days=0,
|
||||
attestation_kind="pre_canary",
|
||||
)
|
||||
result = evaluate_assessment(
|
||||
self.standard,
|
||||
assessment,
|
||||
repository_root=root,
|
||||
trusted_evidence_verifier=self._trusted_test_verifier,
|
||||
)
|
||||
self.assertEqual(result["raw_score"], 77)
|
||||
self.assertFalse(result["qualified"])
|
||||
self.assertFalse(result["pre_canary_authorized"])
|
||||
self.assertTrue(result["pre_canary_attestation_verified"])
|
||||
self.assertEqual(
|
||||
result["pre_canary_authorization"]["claim_id"],
|
||||
"claim-pre_canary_authorization",
|
||||
)
|
||||
self.assertEqual(
|
||||
result["qualification_state"],
|
||||
"TEST_ONLY_PRE_CANARY_PASS",
|
||||
)
|
||||
|
||||
def test_pre_canary_authorization_survives_running_day_updates(self):
|
||||
pre_canary_controls = {
|
||||
control["id"]
|
||||
for domain in self.standard["domains"]
|
||||
for control in domain["controls"]
|
||||
if control["hard_gate"] and control["id"] != "D6-CANARY"
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
assessment = self._prepare_qualified_assessment(
|
||||
root,
|
||||
pre_canary_controls,
|
||||
canary_days=0,
|
||||
attestation_kind="pre_canary",
|
||||
)
|
||||
assessment["observations"]["canary_trading_days"] = 5
|
||||
result = evaluate_assessment(
|
||||
self.standard,
|
||||
assessment,
|
||||
repository_root=root,
|
||||
trusted_evidence_verifier=self._trusted_test_verifier,
|
||||
)
|
||||
self.assertTrue(result["pre_canary_attestation_verified"])
|
||||
self.assertEqual(
|
||||
result["qualification_state"],
|
||||
"TEST_ONLY_PRE_CANARY_PASS",
|
||||
)
|
||||
|
||||
def test_expired_pre_canary_authorization_cannot_open_or_continue_canary(self):
|
||||
pre_canary_controls = {
|
||||
control["id"]
|
||||
for domain in self.standard["domains"]
|
||||
for control in domain["controls"]
|
||||
if control["hard_gate"] and control["id"] != "D6-CANARY"
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
assessment = self._prepare_qualified_assessment(
|
||||
root,
|
||||
pre_canary_controls,
|
||||
canary_days=0,
|
||||
attestation_kind="pre_canary",
|
||||
)
|
||||
assessment["assessed_as_of"] = "2026-09-01"
|
||||
result = evaluate_assessment(
|
||||
self.standard,
|
||||
assessment,
|
||||
repository_root=root,
|
||||
trusted_evidence_verifier=self._trusted_test_verifier,
|
||||
)
|
||||
self.assertFalse(
|
||||
result["pre_canary_authorization_valid_as_of_assessment"]
|
||||
)
|
||||
self.assertEqual(
|
||||
result["pre_canary_authorization"]["validity_status"],
|
||||
"expired",
|
||||
)
|
||||
self.assertFalse(result["canary_running"])
|
||||
self.assertEqual(
|
||||
result["qualification_state"],
|
||||
"TEST_ONLY_PRE_CANARY_EXPIRED",
|
||||
)
|
||||
|
||||
def test_completed_canary_chain_survives_authorization_expiry(self):
|
||||
hard_controls = {
|
||||
control["id"]
|
||||
for domain in self.standard["domains"]
|
||||
for control in domain["controls"]
|
||||
if control["hard_gate"]
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
assessment = self._prepare_qualified_assessment(
|
||||
root,
|
||||
hard_controls,
|
||||
)
|
||||
assessment["assessed_as_of"] = "2026-09-01"
|
||||
final_link = assessment["qualification_attestation"]
|
||||
final_path = root / final_link["path"]
|
||||
final_claim = json.loads(final_path.read_text(encoding="utf-8"))
|
||||
final_claim["assessment_sha256"] = _final_assessment_digest(
|
||||
assessment
|
||||
)
|
||||
final_path.write_text(
|
||||
json.dumps(final_claim, sort_keys=True),
|
||||
encoding="utf-8",
|
||||
)
|
||||
final_link["sha256"] = hashlib.sha256(
|
||||
final_path.read_bytes()
|
||||
).hexdigest()
|
||||
result = evaluate_assessment(
|
||||
self.standard,
|
||||
assessment,
|
||||
repository_root=root,
|
||||
trusted_evidence_verifier=self._trusted_test_verifier,
|
||||
)
|
||||
self.assertFalse(
|
||||
result["pre_canary_authorization_valid_as_of_assessment"]
|
||||
)
|
||||
self.assertTrue(result["canary_authorization_chain_verified"])
|
||||
self.assertTrue(result["structurally_eligible"])
|
||||
self.assertEqual(
|
||||
result["qualification_state"],
|
||||
"TEST_ONLY_STRUCTURAL_PASS",
|
||||
)
|
||||
|
||||
def test_canary_observation_must_stay_within_authorized_hard_caps(self):
|
||||
hard_controls = {
|
||||
control["id"]
|
||||
for domain in self.standard["domains"]
|
||||
for control in domain["controls"]
|
||||
if control["hard_gate"]
|
||||
}
|
||||
mutations = (
|
||||
(
|
||||
"capital",
|
||||
{"maximum_capital_cny_observed": 100001},
|
||||
"authorized capital caps",
|
||||
),
|
||||
(
|
||||
"gross_notional",
|
||||
{"maximum_gross_notional_cny_observed": 80001},
|
||||
"authorized capital caps",
|
||||
),
|
||||
(
|
||||
"symbol",
|
||||
{"symbols_observed": ["300001.XSHE"]},
|
||||
"authorized symbol caps",
|
||||
),
|
||||
(
|
||||
"flow",
|
||||
{
|
||||
"maximum_submission_and_cancel_requests_per_second_observed": 3
|
||||
},
|
||||
"authorized flow caps",
|
||||
),
|
||||
(
|
||||
"automatic_scale_up",
|
||||
{"automatic_scale_up_observed": True},
|
||||
"automatic scale-up",
|
||||
),
|
||||
)
|
||||
for name, mutation, expected in mutations:
|
||||
with self.subTest(name=name), tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
assessment = self._prepare_qualified_assessment(
|
||||
root,
|
||||
hard_controls,
|
||||
)
|
||||
canary = next(
|
||||
control
|
||||
for control in assessment["controls"]
|
||||
if control["id"] == "D6-CANARY"
|
||||
)
|
||||
evidence = canary["evidence"][0]
|
||||
path = root / evidence["path"]
|
||||
claim = json.loads(path.read_text(encoding="utf-8"))
|
||||
claim.update(mutation)
|
||||
path.write_text(
|
||||
json.dumps(claim, sort_keys=True),
|
||||
encoding="utf-8",
|
||||
)
|
||||
evidence["sha256"] = hashlib.sha256(
|
||||
path.read_bytes()
|
||||
).hexdigest()
|
||||
with self.assertRaisesRegex(
|
||||
MaturityStandardError,
|
||||
expected,
|
||||
):
|
||||
evaluate_assessment(
|
||||
self.standard,
|
||||
assessment,
|
||||
repository_root=root,
|
||||
trusted_evidence_verifier=(
|
||||
self._trusted_test_verifier
|
||||
),
|
||||
)
|
||||
|
||||
def test_canary_breach_must_link_to_fail_closed_stop_event(self):
|
||||
hard_controls = {
|
||||
control["id"]
|
||||
for domain in self.standard["domains"]
|
||||
for control in domain["controls"]
|
||||
if control["hard_gate"]
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
assessment = self._prepare_qualified_assessment(
|
||||
root,
|
||||
hard_controls,
|
||||
)
|
||||
canary = next(
|
||||
control
|
||||
for control in assessment["controls"]
|
||||
if control["id"] == "D6-CANARY"
|
||||
)
|
||||
evidence = canary["evidence"][0]
|
||||
path = root / evidence["path"]
|
||||
claim = json.loads(path.read_text(encoding="utf-8"))
|
||||
claim["breach_events"] = [
|
||||
{
|
||||
"breach_id": "breach-1",
|
||||
"condition": (
|
||||
"any_risk_data_rule_or_reconciliation_breach"
|
||||
),
|
||||
"observed_on": "2026-07-08",
|
||||
"stop_event_id": "missing-stop",
|
||||
}
|
||||
]
|
||||
path.write_text(
|
||||
json.dumps(claim, sort_keys=True),
|
||||
encoding="utf-8",
|
||||
)
|
||||
evidence["sha256"] = hashlib.sha256(
|
||||
path.read_bytes()
|
||||
).hexdigest()
|
||||
with self.assertRaisesRegex(
|
||||
MaturityStandardError,
|
||||
"not linked to an authorized stop",
|
||||
):
|
||||
evaluate_assessment(
|
||||
self.standard,
|
||||
assessment,
|
||||
repository_root=root,
|
||||
trusted_evidence_verifier=self._trusted_test_verifier,
|
||||
)
|
||||
|
||||
def test_canary_breach_with_fail_closed_stop_event_is_accepted(self):
|
||||
hard_controls = {
|
||||
control["id"]
|
||||
for domain in self.standard["domains"]
|
||||
for control in domain["controls"]
|
||||
if control["hard_gate"]
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
assessment = self._prepare_qualified_assessment(
|
||||
root,
|
||||
hard_controls,
|
||||
)
|
||||
canary = next(
|
||||
control
|
||||
for control in assessment["controls"]
|
||||
if control["id"] == "D6-CANARY"
|
||||
)
|
||||
evidence = canary["evidence"][0]
|
||||
path = root / evidence["path"]
|
||||
claim = json.loads(path.read_text(encoding="utf-8"))
|
||||
claim["breach_events"] = [
|
||||
{
|
||||
"breach_id": "breach-1",
|
||||
"condition": (
|
||||
"any_risk_data_rule_or_reconciliation_breach"
|
||||
),
|
||||
"observed_on": "2026-07-08",
|
||||
"stop_event_id": "stop-1",
|
||||
}
|
||||
]
|
||||
claim["stop_events"] = [
|
||||
{
|
||||
"stop_event_id": "stop-1",
|
||||
"condition": (
|
||||
"any_risk_data_rule_or_reconciliation_breach"
|
||||
),
|
||||
"triggered_on": "2026-07-08",
|
||||
"trading_stopped": True,
|
||||
"automatic_resume": False,
|
||||
}
|
||||
]
|
||||
path.write_text(
|
||||
json.dumps(claim, sort_keys=True),
|
||||
encoding="utf-8",
|
||||
)
|
||||
evidence["sha256"] = hashlib.sha256(
|
||||
path.read_bytes()
|
||||
).hexdigest()
|
||||
final_link = assessment["qualification_attestation"]
|
||||
final_path = root / final_link["path"]
|
||||
final_claim = json.loads(final_path.read_text(encoding="utf-8"))
|
||||
final_claim["assessment_sha256"] = _final_assessment_digest(
|
||||
assessment
|
||||
)
|
||||
final_path.write_text(
|
||||
json.dumps(final_claim, sort_keys=True),
|
||||
encoding="utf-8",
|
||||
)
|
||||
final_link["sha256"] = hashlib.sha256(
|
||||
final_path.read_bytes()
|
||||
).hexdigest()
|
||||
result = evaluate_assessment(
|
||||
self.standard,
|
||||
assessment,
|
||||
repository_root=root,
|
||||
trusted_evidence_verifier=self._trusted_test_verifier,
|
||||
)
|
||||
self.assertTrue(result["canary_authorization_chain_verified"])
|
||||
self.assertEqual(
|
||||
result["canary_observation"]["breach_event_count"],
|
||||
1,
|
||||
)
|
||||
self.assertEqual(
|
||||
result["canary_observation"]["stop_event_count"],
|
||||
1,
|
||||
)
|
||||
self.assertEqual(
|
||||
result["qualification_state"],
|
||||
"TEST_ONLY_STRUCTURAL_PASS",
|
||||
)
|
||||
|
||||
def test_final_path_cannot_skip_pre_canary_history(self):
|
||||
hard_controls = {
|
||||
control["id"]
|
||||
for domain in self.standard["domains"]
|
||||
for control in domain["controls"]
|
||||
if control["hard_gate"]
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
assessment = self._prepare_qualified_assessment(
|
||||
root,
|
||||
hard_controls,
|
||||
)
|
||||
assessment["pre_canary_attestation"] = None
|
||||
result = evaluate_assessment(
|
||||
self.standard,
|
||||
assessment,
|
||||
repository_root=root,
|
||||
trusted_evidence_verifier=self._trusted_test_verifier,
|
||||
)
|
||||
self.assertFalse(result["qualified"])
|
||||
self.assertFalse(result["canary_authorization_chain_verified"])
|
||||
self.assertEqual(
|
||||
result["qualification_state"],
|
||||
"PENDING_PRE_CANARY_HISTORY",
|
||||
)
|
||||
|
||||
def test_canary_claim_must_reference_pre_authorization_hash(self):
|
||||
hard_controls = {
|
||||
control["id"]
|
||||
for domain in self.standard["domains"]
|
||||
for control in domain["controls"]
|
||||
if control["hard_gate"]
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
assessment = self._prepare_qualified_assessment(
|
||||
root,
|
||||
hard_controls,
|
||||
)
|
||||
canary = next(
|
||||
control
|
||||
for control in assessment["controls"]
|
||||
if control["id"] == "D6-CANARY"
|
||||
)
|
||||
evidence = canary["evidence"][0]
|
||||
path = root / evidence["path"]
|
||||
claim = json.loads(path.read_text(encoding="utf-8"))
|
||||
claim["pre_canary_authorization_sha256"] = "8" * 64
|
||||
path.write_text(json.dumps(claim, sort_keys=True), encoding="utf-8")
|
||||
evidence["sha256"] = hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
with self.assertRaisesRegex(
|
||||
MaturityStandardError,
|
||||
"does not reference the verified authorization",
|
||||
):
|
||||
evaluate_assessment(
|
||||
self.standard,
|
||||
assessment,
|
||||
repository_root=root,
|
||||
trusted_evidence_verifier=self._trusted_test_verifier,
|
||||
)
|
||||
|
||||
def test_standard_hash_drift_rejects_old_assessment(self):
|
||||
standard = copy.deepcopy(self.standard)
|
||||
standard["domains"][0]["controls"][0]["acceptance"] += " changed"
|
||||
with self.assertRaisesRegex(
|
||||
MaturityStandardError,
|
||||
"canonical hash does not match",
|
||||
):
|
||||
evaluate_assessment(
|
||||
standard,
|
||||
self.assessment,
|
||||
repository_root=PROJECT,
|
||||
)
|
||||
|
||||
def test_boolean_verifier_cannot_issue_a_qualification(self):
|
||||
hard_controls = {
|
||||
control["id"]
|
||||
for domain in self.standard["domains"]
|
||||
for control in domain["controls"]
|
||||
if control["hard_gate"]
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
assessment = self._prepare_qualified_assessment(
|
||||
root,
|
||||
hard_controls,
|
||||
)
|
||||
with self.assertRaisesRegex(
|
||||
MaturityStandardError,
|
||||
"attestation is not trusted",
|
||||
):
|
||||
evaluate_assessment(
|
||||
self.standard,
|
||||
assessment,
|
||||
repository_root=root,
|
||||
trusted_evidence_verifier=lambda *_args: True,
|
||||
)
|
||||
|
||||
def test_repository_cannot_accept_a_fake_production_registry(self):
|
||||
hard_controls = {
|
||||
control["id"]
|
||||
for domain in self.standard["domains"]
|
||||
for control in domain["controls"]
|
||||
if control["hard_gate"]
|
||||
}
|
||||
|
||||
def fake_production_verifier(*_args):
|
||||
return {
|
||||
"trusted": True,
|
||||
"trust_environment": "production",
|
||||
"verifier_id": "forged-local-verifier",
|
||||
"issuer_id": "forged-local-issuer",
|
||||
"key_fingerprint_sha256": "7" * 64,
|
||||
"policy_version": "forged-v1",
|
||||
}
|
||||
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
assessment = self._prepare_qualified_assessment(
|
||||
root,
|
||||
hard_controls,
|
||||
)
|
||||
with self.assertRaisesRegex(
|
||||
MaturityStandardError,
|
||||
"production verifier registry is not implemented",
|
||||
):
|
||||
evaluate_assessment(
|
||||
self.standard,
|
||||
assessment,
|
||||
repository_root=root,
|
||||
trusted_evidence_verifier=fake_production_verifier,
|
||||
)
|
||||
|
||||
def test_hard_gates_cannot_make_the_published_threshold_unattainable(self):
|
||||
standard = copy.deepcopy(self.standard)
|
||||
soft_control = next(
|
||||
control
|
||||
for domain in standard["domains"]
|
||||
for control in domain["controls"]
|
||||
if control["id"] == "D1-REDUNDANCY"
|
||||
)
|
||||
soft_control["hard_gate"] = True
|
||||
with self.assertRaisesRegex(
|
||||
MaturityStandardError,
|
||||
"hard-gate weights must exactly equal",
|
||||
):
|
||||
validate_standard(standard)
|
||||
|
||||
def test_hash_drift_is_rejected(self):
|
||||
assessment = copy.deepcopy(self.assessment)
|
||||
assessment["controls"][0]["status"] = "passed"
|
||||
assessment["controls"][0]["evidence"] = [
|
||||
{
|
||||
"path": "profiles/production-80/standard.json",
|
||||
"tier": "E4",
|
||||
"sha256": "0" * 64,
|
||||
}
|
||||
]
|
||||
with self.assertRaisesRegex(MaturityStandardError, "hash drift"):
|
||||
evaluate_assessment(
|
||||
self.standard,
|
||||
assessment,
|
||||
repository_root=PROJECT,
|
||||
)
|
||||
|
||||
def test_architecture_is_valid_and_forbidden_import_is_detected(self):
|
||||
current = validate_architecture(PROJECT / "src/quant_os")
|
||||
self.assertTrue(current["ok"], current["violations"])
|
||||
self.assertEqual(current["scope"], "new_namespace_only")
|
||||
self.assertEqual(current["package_root"], "src/quant_os")
|
||||
self.assertEqual(
|
||||
current["migration_state"],
|
||||
"transitioning_from_quant60_legacy",
|
||||
)
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
for layer in current["layers"]:
|
||||
(root / layer).mkdir()
|
||||
(root / layer / "__init__.py").write_text("", encoding="utf-8")
|
||||
(root / "research" / "bad.py").write_text(
|
||||
"from quant_os.execution import submit\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(root / "research" / "bad_relative.py").write_text(
|
||||
"from ..execution import submit\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(root / "research" / "bad_from_root.py").write_text(
|
||||
"from quant_os import execution\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(root / "control" / "bad_vendor.py").write_text(
|
||||
"from xtquant import xttrader\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(root / "research" / "bad_legacy.py").write_text(
|
||||
"from quant60.experiment import run\n"
|
||||
"from adapters.jqdata_local import load\n"
|
||||
"from platforms.qlib_runner import main\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(root / "architecture.py").write_text(
|
||||
"from .research import train\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(root / "bad_root.py").write_text("", encoding="utf-8")
|
||||
(root / "unregistered").mkdir()
|
||||
(root / "unregistered" / "__init__.py").write_text(
|
||||
"",
|
||||
encoding="utf-8",
|
||||
)
|
||||
invalid = validate_architecture(root)
|
||||
self.assertFalse(invalid["ok"])
|
||||
reasons = [item["reason"] for item in invalid["violations"]]
|
||||
self.assertEqual(reasons.count("forbidden_dependency"), 4)
|
||||
self.assertIn("vendor_sdk_outside_adapter", reasons)
|
||||
self.assertIn("legacy_dependency_escape", reasons)
|
||||
self.assertIn("unregistered_layer_package", reasons)
|
||||
self.assertIn("unregistered_root_module", reasons)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+278
-20
@@ -17,7 +17,12 @@ from typing import Any, Dict, Iterable, List, Mapping, Sequence, Tuple
|
||||
PROJECT = Path(__file__).resolve().parents[1]
|
||||
DEFAULT_OUTPUT = PROJECT / "status" / "status-data.js"
|
||||
GLOBAL_NAME = "QUANT_OS_PUBLIC_STATUS"
|
||||
SCHEMA_VERSION = 1
|
||||
SCHEMA_VERSION = 2
|
||||
PRODUCTION_80_STANDARD_PATH = "profiles/production-80/standard.json"
|
||||
PRODUCTION_80_ASSESSMENT_PATH = (
|
||||
"profiles/production-80/current-assessment.json"
|
||||
)
|
||||
BASELINE_60_PROFILE_PATH = "profiles/baseline-60/profile.json"
|
||||
REACHABILITY_PATH = (
|
||||
"releases/quant60-research-candidate-v1/reachability.json"
|
||||
)
|
||||
@@ -36,21 +41,37 @@ JOINQUANT_TARGET_PACKAGE_BUNDLE_SHA256 = (
|
||||
|
||||
SOURCE_PATHS: Tuple[str, ...] = (
|
||||
"gate_scorecard.json",
|
||||
BASELINE_60_PROFILE_PATH,
|
||||
PRODUCTION_80_STANDARD_PATH,
|
||||
PRODUCTION_80_ASSESSMENT_PATH,
|
||||
"dist/bundle_manifest.json",
|
||||
REACHABILITY_PATH,
|
||||
JOINQUANT_TARGET_PACKAGE_RUN_PATH,
|
||||
"configs/baseline.json",
|
||||
"README.md",
|
||||
"docs/ARCHITECTURE.md",
|
||||
"docs/architecture/ARCHITECTURE_80.md",
|
||||
"docs/AUDIT_2026-07-25.md",
|
||||
"docs/standards/QUANT_OS_80_STANDARD.md",
|
||||
"docs/standards/BASELINE_60_VS_PRODUCTION_80.md",
|
||||
"docs/getting-started/GETTING_STARTED_80.md",
|
||||
"docs/PLATFORM_MATRIX.md",
|
||||
"docs/CROSS_ENGINE_CONSISTENCY.md",
|
||||
"docs/JOINQUANT_HOSTED_EVIDENCE_2026-07-26.md",
|
||||
JOINQUANT_TARGET_PACKAGE_DOC_PATH,
|
||||
"docs/TUSHARE_LOCAL_DATA.md",
|
||||
"MIGRATION.md",
|
||||
"ops/README.md",
|
||||
"ops/activate_static_status.sh",
|
||||
"ops/deploy_static_status.sh",
|
||||
"ops/nginx/quant-os-static.conf",
|
||||
"ops/nginx/quant-os-legacy-redirects-302.conf",
|
||||
"ops/nginx/quant-os-legacy-redirects-308.conf",
|
||||
"runbooks/PLATFORM_DEPLOYMENT.md",
|
||||
"tools/build_release_reachability.py",
|
||||
"tools/build_public_status.py",
|
||||
"tools/check_tracked_secrets.py",
|
||||
"tools/inventory_local_assets.py",
|
||||
)
|
||||
|
||||
ALLOWED_PROGRESS_STATUSES = frozenset(
|
||||
@@ -704,6 +725,12 @@ def _build_evidence(project: Path) -> List[Dict[str, Any]]:
|
||||
"runbooks/PLATFORM_DEPLOYMENT.md",
|
||||
"runbook",
|
||||
),
|
||||
(
|
||||
"public-status-deployment",
|
||||
"Atomic public status deployment",
|
||||
"ops/README.md",
|
||||
"runbook",
|
||||
),
|
||||
)
|
||||
result: List[Dict[str, Any]] = []
|
||||
for evidence_id, label, raw_path, kind in records:
|
||||
@@ -721,7 +748,38 @@ def _build_evidence(project: Path) -> List[Dict[str, Any]]:
|
||||
return result
|
||||
|
||||
|
||||
def _build_layers() -> List[Dict[str, Any]]:
|
||||
def _build_market_scope(standard: Mapping[str, Any]) -> Dict[str, Any]:
|
||||
operating_defaults = standard.get("operating_defaults")
|
||||
if not isinstance(operating_defaults, dict):
|
||||
raise PublicStatusError("production-80 operating_defaults must be an object")
|
||||
declared_scope = operating_defaults.get("declared_market_scope")
|
||||
if not isinstance(declared_scope, dict):
|
||||
raise PublicStatusError(
|
||||
"production-80 declared_market_scope must be an object"
|
||||
)
|
||||
included = declared_scope.get("included")
|
||||
excluded = declared_scope.get("excluded_fail_closed")
|
||||
if included != ["SSE", "SZSE"] or excluded != ["BSE"]:
|
||||
raise PublicStatusError(
|
||||
"public market scope must explicitly include SSE/SZSE and fail-close BSE"
|
||||
)
|
||||
return {
|
||||
"included": list(included),
|
||||
"excludedFailClosed": list(excluded),
|
||||
"executionScopeLabel": "沪深(上交所、深交所)",
|
||||
"bseTradingEnabled": False,
|
||||
"statement": (
|
||||
"当前交易执行范围仅覆盖沪深;北交所尚未接入,"
|
||||
"所有北交所委托必须 fail closed。"
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _build_layers(
|
||||
*,
|
||||
baseline_shadow_days: int,
|
||||
production_shadow_days: int,
|
||||
) -> List[Dict[str, Any]]:
|
||||
return [
|
||||
{
|
||||
"id": "data",
|
||||
@@ -840,7 +898,10 @@ def _build_layers() -> List[Dict[str, Any]]:
|
||||
"authenticated evidence envelope and exact replay",
|
||||
],
|
||||
"limitations": [
|
||||
"无真实券商 attestation、回调/重启证明和 20 个交易日影子记录;没有实盘下单入口。"
|
||||
"无真实券商 attestation、回调/重启证明和影子记录;"
|
||||
f"Baseline 60 至少需要 {baseline_shadow_days} 个交易日,"
|
||||
f"Production 80 当前缺口为同一冻结候选至少 {production_shadow_days} "
|
||||
"个交易日;没有实盘下单入口。"
|
||||
],
|
||||
"evidenceLinks": ["architecture", "platform-matrix", "scorecard"],
|
||||
"releaseVerified": False,
|
||||
@@ -917,7 +978,12 @@ def _build_models() -> List[Dict[str, Any]]:
|
||||
]
|
||||
|
||||
|
||||
def _build_next_actions(scorecard: Mapping[str, Any]) -> List[Dict[str, Any]]:
|
||||
def _build_next_actions(
|
||||
scorecard: Mapping[str, Any],
|
||||
*,
|
||||
baseline_shadow_days: int,
|
||||
production_shadow_days: int,
|
||||
) -> List[Dict[str, Any]]:
|
||||
delivery = scorecard.get("current_delivery", {})
|
||||
if not isinstance(delivery, dict):
|
||||
raise PublicStatusError("scorecard current_delivery must be an object")
|
||||
@@ -958,13 +1024,17 @@ def _build_next_actions(scorecard: Mapping[str, Any]) -> List[Dict[str, Any]]:
|
||||
},
|
||||
{
|
||||
"priority": 5,
|
||||
"title": "完成 QMT 券商合同探针与至少 20 日影子盘",
|
||||
"title": (
|
||||
"完成 QMT 券商合同探针与 "
|
||||
f"Production 80 的 {production_shadow_days} 日影子盘"
|
||||
f"(Baseline 60 门槛 {baseline_shadow_days} 日)"
|
||||
),
|
||||
"why": str(missing[3]),
|
||||
"gateIds": ["G6", "G8", "G9"],
|
||||
},
|
||||
{
|
||||
"priority": 6,
|
||||
"title": "完成连续对账、恢复演练与券商合规确认",
|
||||
"title": "完成连续对账、恢复演练与券商程序化交易报告/核查确认",
|
||||
"why": str(missing[4]),
|
||||
"gateIds": ["G8", "G10"],
|
||||
},
|
||||
@@ -977,6 +1047,8 @@ def _build_freshness(
|
||||
joinquant_markdown: str,
|
||||
joinquant_target_package_run: Mapping[str, Any],
|
||||
tushare_markdown: str,
|
||||
baseline_shadow_days: int,
|
||||
production_shadow_days: int,
|
||||
) -> List[Dict[str, Any]]:
|
||||
period_match = re.search(
|
||||
r"(?m)^-\s+Period:\s+"
|
||||
@@ -1123,7 +1195,12 @@ def _build_freshness(
|
||||
"lastRealRuntimeAt": None,
|
||||
"value": "N/A",
|
||||
"productionReady": False,
|
||||
"semantics": "QMT 只有 bundle/harness,XtTrader 只有 fake-broker 只读合同;尚无真实运行或 20 日影子证据。",
|
||||
"semantics": (
|
||||
"QMT 只有 bundle/harness,XtTrader 只有 fake-broker 只读合同;"
|
||||
f"尚无真实运行或影子证据。Baseline 60 至少需要 "
|
||||
f"{baseline_shadow_days} 个交易日,Production 80 当前缺口为同一"
|
||||
f"冻结候选至少 {production_shadow_days} 个交易日。"
|
||||
),
|
||||
"evidenceLinks": ["platform-matrix", "platform-deployment", "scorecard"],
|
||||
},
|
||||
]
|
||||
@@ -1136,21 +1213,21 @@ def _validate_claim(scorecard: Mapping[str, Any]) -> None:
|
||||
claim = str(policy.get("current_claim", ""))
|
||||
if claim in FORBIDDEN_PUBLIC_CLAIMS:
|
||||
raise PublicStatusError(f"unsafe public release claim: {claim}")
|
||||
if claim not in {"NOT_BASELINE_60", "BASELINE_60_VERIFIED"}:
|
||||
raise PublicStatusError(f"unsupported baseline claim: {claim}")
|
||||
if claim == "BASELINE_60_VERIFIED":
|
||||
raise PublicStatusError(
|
||||
"positive Baseline-60 publication is disabled until the "
|
||||
"production issuer registry and signed baseline evaluator result "
|
||||
"are implemented"
|
||||
)
|
||||
|
||||
gates = scorecard.get("gates")
|
||||
if not isinstance(gates, list) or not gates:
|
||||
raise PublicStatusError("scorecard gates must be a non-empty list")
|
||||
passed = sum(gate.get("status") == "passed" for gate in gates if isinstance(gate, dict))
|
||||
threshold = int(policy.get("baseline_threshold", 60))
|
||||
delivery = scorecard.get("current_delivery", {})
|
||||
score = delivery.get("score") if isinstance(delivery, dict) else None
|
||||
all_passed = passed == len(gates)
|
||||
|
||||
if claim != "NOT_BASELINE_60":
|
||||
if not all_passed or not isinstance(score, (int, float)) or score < threshold:
|
||||
raise PublicStatusError(
|
||||
"a baseline claim requires every gate passed and a threshold score"
|
||||
)
|
||||
if claim == "NOT_BASELINE_60" and all_passed:
|
||||
raise PublicStatusError(
|
||||
"scorecard is internally inconsistent: every gate passed but claim is negative"
|
||||
@@ -1187,6 +1264,135 @@ def _validate_public_payload(payload: Mapping[str, Any]) -> None:
|
||||
)
|
||||
|
||||
|
||||
def _build_maturity80(
|
||||
project: Path,
|
||||
standard: Mapping[str, Any],
|
||||
assessment: Mapping[str, Any],
|
||||
) -> Dict[str, Any]:
|
||||
source_root = project / "src"
|
||||
inserted = str(source_root) not in sys.path
|
||||
if inserted:
|
||||
sys.path.insert(0, str(source_root))
|
||||
try:
|
||||
from quant_os.evidence import ( # pylint: disable=import-outside-toplevel
|
||||
MaturityStandardError,
|
||||
evaluate_assessment,
|
||||
)
|
||||
|
||||
try:
|
||||
evaluation = evaluate_assessment(
|
||||
standard,
|
||||
assessment,
|
||||
repository_root=project,
|
||||
)
|
||||
except MaturityStandardError as exc:
|
||||
raise PublicStatusError(
|
||||
f"production-80 assessment failed canonical verification: {exc}"
|
||||
) from exc
|
||||
finally:
|
||||
if inserted:
|
||||
sys.path.remove(str(source_root))
|
||||
|
||||
controls = evaluation["controls"]
|
||||
return {
|
||||
"standardId": evaluation["standard_id"],
|
||||
"standardCanonicalSha256": evaluation[
|
||||
"standard_canonical_sha256"
|
||||
],
|
||||
"baselineProfileCanonicalSha256": evaluation[
|
||||
"baseline_profile_canonical_sha256"
|
||||
],
|
||||
"assessedAsOf": evaluation["assessed_as_of"],
|
||||
"rawScore": evaluation["raw_score"],
|
||||
"effectiveScore": evaluation["effective_score"],
|
||||
"maximumScore": evaluation["maximum_score"],
|
||||
"qualificationThreshold": evaluation["qualification_threshold"],
|
||||
"qualified": evaluation["qualified"],
|
||||
"state": evaluation["qualification_state"],
|
||||
"baseline60Passed": evaluation["baseline_60_passed"],
|
||||
"structurallyEligible": evaluation["structurally_eligible"],
|
||||
"trustedAttestationVerified": evaluation[
|
||||
"trusted_attestation_verified"
|
||||
],
|
||||
"finalAttestationVerified": evaluation[
|
||||
"final_attestation_verified"
|
||||
],
|
||||
"productionTrustRegistryAvailable": evaluation[
|
||||
"production_trust_registry_available"
|
||||
],
|
||||
"verificationEnvironment": evaluation[
|
||||
"verification_environment"
|
||||
],
|
||||
"testOnly": evaluation["test_only"],
|
||||
"preCanaryStructurallyEligible": evaluation[
|
||||
"pre_canary_structurally_eligible"
|
||||
],
|
||||
"preCanaryAttestationVerified": evaluation[
|
||||
"pre_canary_attestation_verified"
|
||||
],
|
||||
"preCanaryAuthorized": evaluation["pre_canary_authorized"],
|
||||
"preCanaryAuthorizationVerified": evaluation[
|
||||
"pre_canary_attestation_verified"
|
||||
],
|
||||
"preCanaryAuthorization": evaluation[
|
||||
"pre_canary_authorization"
|
||||
],
|
||||
"canaryAuthorizationChainVerified": evaluation[
|
||||
"canary_authorization_chain_verified"
|
||||
],
|
||||
"canaryObservation": evaluation["canary_observation"],
|
||||
"canaryRunning": evaluation["canary_running"],
|
||||
"baseline60AttestationVerified": evaluation[
|
||||
"baseline_60_attestation_verified"
|
||||
],
|
||||
"hardGateFailures": evaluation["hard_gate_failures"],
|
||||
"domainFloorFailures": evaluation["domain_floor_failures"],
|
||||
"prerequisites": evaluation["prerequisites"],
|
||||
"scoreCaps": evaluation["score_caps"],
|
||||
"domains": [
|
||||
{
|
||||
"id": domain["id"],
|
||||
"name": domain["name"],
|
||||
"score": domain["score"],
|
||||
"weight": domain["weight"],
|
||||
"minimumScore": domain["minimum_score"],
|
||||
"floorPassed": domain["floor_passed"],
|
||||
}
|
||||
for domain in evaluation["domains"]
|
||||
],
|
||||
"controls": [
|
||||
{
|
||||
"id": control["id"],
|
||||
"domainId": control["domain_id"],
|
||||
"name": control["name"],
|
||||
"weight": control["weight"],
|
||||
"hardGate": control["hard_gate"],
|
||||
"status": control["status"],
|
||||
"minimumEvidenceTier": control["minimum_evidence_tier"],
|
||||
"note": control["note"],
|
||||
}
|
||||
for control in controls
|
||||
],
|
||||
"controlsPassed": sum(
|
||||
control["status"] == "passed" for control in controls
|
||||
),
|
||||
"controlsTotal": len(controls),
|
||||
"hardGatesPassed": (
|
||||
sum(
|
||||
control["hard_gate"] and control["status"] == "passed"
|
||||
for control in controls
|
||||
)
|
||||
),
|
||||
"hardGatesTotal": sum(control["hard_gate"] for control in controls),
|
||||
"evidencePolicy": (
|
||||
"Only subject-bound evidence claims verified by a configured "
|
||||
"trust provider, at or above each control's minimum tier, earn "
|
||||
"points. The current repository has no production trust provider "
|
||||
"and therefore cannot self-issue a positive qualification."
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def build_public_status(
|
||||
project: Path,
|
||||
*,
|
||||
@@ -1195,6 +1401,13 @@ def build_public_status(
|
||||
) -> Dict[str, Any]:
|
||||
project = project.resolve()
|
||||
scorecard = _load_json(project / "gate_scorecard.json")
|
||||
production_80_standard = _load_json(
|
||||
project / PRODUCTION_80_STANDARD_PATH
|
||||
)
|
||||
production_80_assessment = _load_json(
|
||||
project / PRODUCTION_80_ASSESSMENT_PATH
|
||||
)
|
||||
baseline_60_profile = _load_json(project / BASELINE_60_PROFILE_PATH)
|
||||
manifest = _load_json(project / "dist" / "bundle_manifest.json")
|
||||
reachability_manifest = _load_json(project / REACHABILITY_PATH)
|
||||
joinquant_target_package_run_raw = _load_json(
|
||||
@@ -1212,6 +1425,23 @@ def build_public_status(
|
||||
)
|
||||
|
||||
_validate_claim(scorecard)
|
||||
try:
|
||||
baseline_shadow_days = int(
|
||||
baseline_60_profile["minimum_shadow_trading_days"]
|
||||
)
|
||||
production_shadow_days = int(
|
||||
production_80_standard["qualification"][
|
||||
"minimum_shadow_trading_days"
|
||||
]
|
||||
)
|
||||
except (KeyError, TypeError, ValueError) as exc:
|
||||
raise PublicStatusError(
|
||||
"baseline-60 and production-80 shadow requirements must be integers"
|
||||
) from exc
|
||||
if baseline_shadow_days <= 0 or production_shadow_days <= baseline_shadow_days:
|
||||
raise PublicStatusError(
|
||||
"production-80 shadow requirement must exceed the positive baseline-60 requirement"
|
||||
)
|
||||
audit_as_of = str(scorecard.get("audit_as_of", ""))
|
||||
try:
|
||||
datetime.strptime(audit_as_of, "%Y-%m-%d")
|
||||
@@ -1271,6 +1501,11 @@ def build_public_status(
|
||||
project,
|
||||
joinquant_target_package_run_raw,
|
||||
)
|
||||
maturity80 = _build_maturity80(
|
||||
project,
|
||||
production_80_standard,
|
||||
production_80_assessment,
|
||||
)
|
||||
source_records: List[Dict[str, str]] = []
|
||||
for relative in SOURCE_PATHS:
|
||||
safe_path = _safe_repo_path(relative, label="provenance source")
|
||||
@@ -1284,6 +1519,16 @@ def build_public_status(
|
||||
|
||||
claim_policy = scorecard["claim_policy"]
|
||||
delivery = scorecard["current_delivery"]
|
||||
baseline_verified = (
|
||||
claim_policy["current_claim"] == "BASELINE_60_VERIFIED"
|
||||
and passed == len(gates)
|
||||
and isinstance(delivery.get("score"), (int, float))
|
||||
and delivery["score"] >= claim_policy["baseline_threshold"]
|
||||
)
|
||||
if bool(maturity80["baseline60Passed"]) != baseline_verified:
|
||||
raise PublicStatusError(
|
||||
"Baseline-60 scorecard and production-80 assessment disagree"
|
||||
)
|
||||
internal_delivery_gaps = delivery.get("internal_delivery_gaps", [])
|
||||
external_evidence_missing = delivery.get("external_evidence_missing", [])
|
||||
if not isinstance(internal_delivery_gaps, list) or len(internal_delivery_gaps) < 3:
|
||||
@@ -1312,9 +1557,11 @@ def build_public_status(
|
||||
"score": delivery.get("score"),
|
||||
"scoreStatus": str(delivery.get("status")),
|
||||
"reason": str(delivery.get("reason")),
|
||||
"releaseVerified": False,
|
||||
"liveReady": False,
|
||||
"releaseVerified": baseline_verified,
|
||||
"liveReady": bool(maturity80["qualified"]),
|
||||
},
|
||||
"marketScope": _build_market_scope(production_80_standard),
|
||||
"maturity80": maturity80,
|
||||
"summary": {
|
||||
"testsRecorded": test_count,
|
||||
"optionalSkipsRecorded": optional_skips,
|
||||
@@ -1338,7 +1585,7 @@ def build_public_status(
|
||||
"Tushare completed-partition Qlib momentum smoke",
|
||||
],
|
||||
"gateCoverageLabel": f"{passed}/{len(gates)} passed",
|
||||
"productionReady": False,
|
||||
"productionReady": bool(maturity80["qualified"]),
|
||||
"investmentValueClaim": False,
|
||||
},
|
||||
"candidateBoundary": {
|
||||
@@ -1373,7 +1620,10 @@ def build_public_status(
|
||||
"internal": [str(item) for item in internal_delivery_gaps],
|
||||
"externalEvidence": [str(item) for item in external_evidence_missing],
|
||||
},
|
||||
"layers": _build_layers(),
|
||||
"layers": _build_layers(
|
||||
baseline_shadow_days=baseline_shadow_days,
|
||||
production_shadow_days=production_shadow_days,
|
||||
),
|
||||
"models": _build_models(),
|
||||
"platforms": _build_platforms(platform_markdown),
|
||||
"freshness": _build_freshness(
|
||||
@@ -1381,6 +1631,8 @@ def build_public_status(
|
||||
joinquant_markdown=joinquant_markdown,
|
||||
joinquant_target_package_run=joinquant_target_package_run,
|
||||
tushare_markdown=tushare_markdown,
|
||||
baseline_shadow_days=baseline_shadow_days,
|
||||
production_shadow_days=production_shadow_days,
|
||||
),
|
||||
"gates": gates,
|
||||
"bundles": {
|
||||
@@ -1409,9 +1661,15 @@ def build_public_status(
|
||||
},
|
||||
},
|
||||
"evidence": _build_evidence(project),
|
||||
"nextActions": _build_next_actions(scorecard),
|
||||
"nextActions": _build_next_actions(
|
||||
scorecard,
|
||||
baseline_shadow_days=baseline_shadow_days,
|
||||
production_shadow_days=production_shadow_days,
|
||||
),
|
||||
"provenance": {
|
||||
"scorecard": "gate_scorecard.json",
|
||||
"production80Standard": PRODUCTION_80_STANDARD_PATH,
|
||||
"production80Assessment": PRODUCTION_80_ASSESSMENT_PATH,
|
||||
"bundleManifest": "dist/bundle_manifest.json",
|
||||
"releaseReachability": REACHABILITY_PATH,
|
||||
"platformMatrix": "docs/PLATFORM_MATRIX.md",
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Reject obvious reusable credentials without printing their values."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
PROJECT = Path(__file__).resolve().parents[1]
|
||||
PATTERNS = (
|
||||
re.compile(r"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----"),
|
||||
re.compile(r"\b(?:ghp|github_pat|glpat|gitea)_[A-Za-z0-9_-]{16,}\b"),
|
||||
re.compile(r"https?://[^/\s:@]+:[^/\s@]{8,}@"),
|
||||
re.compile(
|
||||
r"(?i)\b(?:password|passwd|api[_-]?key|access[_-]?token|secret)"
|
||||
r"\s*[:=]\s*[\"'][A-Za-z0-9+/=_-]{12,}[\"']"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def tracked_files() -> list[Path]:
|
||||
result = subprocess.run(
|
||||
["git", "ls-files", "-z"],
|
||||
cwd=PROJECT,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
return [
|
||||
PROJECT / item.decode("utf-8")
|
||||
for item in result.stdout.split(b"\0")
|
||||
if item
|
||||
]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
findings: list[tuple[str, int, int]] = []
|
||||
for path in tracked_files():
|
||||
try:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
except (OSError, UnicodeDecodeError):
|
||||
continue
|
||||
for line_number, line in enumerate(text.splitlines(), start=1):
|
||||
for pattern_number, pattern in enumerate(PATTERNS, start=1):
|
||||
if pattern.search(line):
|
||||
findings.append(
|
||||
(
|
||||
path.relative_to(PROJECT).as_posix(),
|
||||
line_number,
|
||||
pattern_number,
|
||||
)
|
||||
)
|
||||
if findings:
|
||||
for path, line_number, pattern_number in findings:
|
||||
print(
|
||||
f"credential-like content: {path}:{line_number} "
|
||||
f"(rule {pattern_number})",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
print("tracked secret scan: OK")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build a deterministic, content-hashed inventory of ignored local assets."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
DEFAULT_INCLUDES = ("data", "artifacts", "mlruns")
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as handle:
|
||||
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def build_inventory(root: Path, includes: tuple[str, ...]) -> dict:
|
||||
root = root.resolve()
|
||||
entries: list[dict] = []
|
||||
summaries: dict[str, dict] = {}
|
||||
for include in includes:
|
||||
relative = Path(include)
|
||||
if (
|
||||
relative.is_absolute()
|
||||
or ".." in relative.parts
|
||||
or len(relative.parts) != 1
|
||||
):
|
||||
raise ValueError(f"unsafe include: {include}")
|
||||
directory = root / relative
|
||||
file_count = 0
|
||||
total_bytes = 0
|
||||
if directory.exists() and not directory.is_dir():
|
||||
raise ValueError(f"include is not a directory: {include}")
|
||||
if directory.is_dir():
|
||||
for path in sorted(directory.rglob("*")):
|
||||
if path.is_symlink():
|
||||
raise ValueError(
|
||||
f"symlink is not allowed in inventory: "
|
||||
f"{path.relative_to(root)}"
|
||||
)
|
||||
if not path.is_file():
|
||||
continue
|
||||
size = path.stat().st_size
|
||||
entries.append(
|
||||
{
|
||||
"path": path.relative_to(root).as_posix(),
|
||||
"size": size,
|
||||
"sha256": _sha256(path),
|
||||
}
|
||||
)
|
||||
file_count += 1
|
||||
total_bytes += size
|
||||
summaries[include] = {
|
||||
"available": directory.is_dir(),
|
||||
"files": file_count,
|
||||
"bytes": total_bytes,
|
||||
}
|
||||
body = {
|
||||
"schema_version": "quant-os-local-assets-inventory/1.0",
|
||||
"includes": list(includes),
|
||||
"summaries": summaries,
|
||||
"entries": entries,
|
||||
}
|
||||
body["inventory_sha256"] = hashlib.sha256(
|
||||
json.dumps(
|
||||
body,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
allow_nan=False,
|
||||
).encode("utf-8")
|
||||
).hexdigest()
|
||||
return body
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("root", type=Path)
|
||||
parser.add_argument(
|
||||
"--include",
|
||||
action="append",
|
||||
dest="includes",
|
||||
help="one top-level ignored directory; repeat as needed",
|
||||
)
|
||||
parser.add_argument("--output", type=Path)
|
||||
parser.add_argument("--compare", type=Path)
|
||||
args = parser.parse_args()
|
||||
includes = tuple(args.includes or DEFAULT_INCLUDES)
|
||||
try:
|
||||
inventory = build_inventory(args.root, includes)
|
||||
except (OSError, ValueError) as exc:
|
||||
print(f"inventory failed: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
encoded = (
|
||||
json.dumps(
|
||||
inventory,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
indent=2,
|
||||
allow_nan=False,
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
if args.compare is not None:
|
||||
try:
|
||||
expected = json.loads(args.compare.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
print(f"cannot read comparison inventory: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
if inventory != expected:
|
||||
print("local asset inventory differs", file=sys.stderr)
|
||||
return 1
|
||||
if args.output is None:
|
||||
print(encoded, end="")
|
||||
else:
|
||||
args.output.write_text(encoded, encoding="utf-8")
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"ok": True,
|
||||
"files": len(inventory["entries"]),
|
||||
"inventory_sha256": inventory["inventory_sha256"],
|
||||
"output": args.output.name,
|
||||
},
|
||||
sort_keys=True,
|
||||
)
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user