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/
|
venv/
|
||||||
|
|
||||||
# Generated evidence and local data
|
# Generated evidence and local data
|
||||||
artifacts/
|
/artifacts/
|
||||||
reports/
|
reports/
|
||||||
runs/
|
runs/
|
||||||
data/raw/
|
/build/
|
||||||
data/canonical/
|
/data/
|
||||||
data/snapshots/
|
/data/raw/
|
||||||
data/qlib/
|
/data/canonical/
|
||||||
|
/data/snapshots/
|
||||||
|
/data/qlib/
|
||||||
mlruns/
|
mlruns/
|
||||||
*.parquet
|
*.parquet
|
||||||
*.duckdb
|
*.duckdb
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
# CLAUDE.md
|
# CLAUDE.md
|
||||||
|
|
||||||
This directory is the executable source of truth for Quant OS. `quant60` is
|
This independent repository is the executable source of truth for Quant OS.
|
||||||
its first A-share Baseline kernel/package; the project itself also owns data,
|
`quant_os` is the long-lived product namespace. `quant60` is the compatibility
|
||||||
research, portfolio, execution, evidence, and platform adapters. Obsidian
|
namespace for the first A-share Baseline protocol and V1 artifacts; maturity
|
||||||
contains research and status documentation only.
|
scores belong to profiles, never to package names. Obsidian contains research
|
||||||
|
and status documentation only.
|
||||||
|
|
||||||
## Development commands
|
## Development commands
|
||||||
|
|
||||||
Run all commands from this directory:
|
Run all commands from this directory:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
make architecture
|
||||||
|
make standard
|
||||||
make test
|
make test
|
||||||
make bundle
|
make bundle
|
||||||
make smoke
|
make smoke
|
||||||
@@ -50,5 +53,8 @@ A change is complete only when:
|
|||||||
1. unit/integration tests pass;
|
1. unit/integration tests pass;
|
||||||
2. hosted bundles rebuild and pass Python 3.6 syntax checks;
|
2. hosted bundles rebuild and pass Python 3.6 syntax checks;
|
||||||
3. the deterministic smoke and manifest verifier pass;
|
3. the deterministic smoke and manifest verifier pass;
|
||||||
4. docs state the exact evidence class and remaining external gate;
|
4. architecture and maturity profile checks pass;
|
||||||
5. no secret or machine-specific absolute path is added to tracked artifacts.
|
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 的聚宽完整 package/plan/orders/fills 导出和本地 peer;
|
||||||
- 同一个 TargetPackage 的授权 QMT built-in 与 qmttools 回测导出;
|
- 同一个 TargetPackage 的授权 QMT built-in 与 qmttools 回测导出;
|
||||||
- 至少 20 个交易日的真实 QMT/broker shadow;
|
- Baseline 60 至少 20 个交易日、Production 80 同一冻结候选至少 60 个交易日
|
||||||
|
的真实 QMT/broker shadow;
|
||||||
- 同一 canonical 输入的 L1—L5 差异报告。
|
- 同一 canonical 输入的 L1—L5 差异报告。
|
||||||
|
|
||||||
**状态**:部分完成;JoinQuant runtime smoke 已执行,但 strict comparison、
|
**状态**:部分完成;JoinQuant runtime smoke 已执行,但 strict comparison、
|
||||||
@@ -130,7 +131,8 @@ guarded TWAP/POV 和影子证据。
|
|||||||
- 券商实际状态码映射与脱敏回调;
|
- 券商实际状态码映射与脱敏回调;
|
||||||
- 实际 QMT query API 的失败/空列表合同探针;
|
- 实际 QMT query API 的失败/空列表合同探针;
|
||||||
- durable scheduler/recovery daemon;
|
- durable scheduler/recovery daemon;
|
||||||
- 20 个交易日 shadow、日终零未知账差和故障恢复演练;
|
- Baseline 60 的 20 个交易日 shadow(Production 80 为同一冻结候选 60 日)、
|
||||||
|
日终零未知账差和故障恢复演练;
|
||||||
- 券商权限、频率、软件和程序化交易报告确认。
|
- 券商权限、频率、软件和程序化交易报告确认。
|
||||||
|
|
||||||
**状态**:仅有未认证的安全边界;不可视为 live-ready。
|
**状态**:仅有未认证的安全边界;不可视为 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
|
RESEARCH_DIR := artifacts/research-smoke
|
||||||
PARITY_REPORT := artifacts/mock-parity/report.json
|
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:
|
test:
|
||||||
PYTHONPATH=$(PROJECT_PYTHONPATH) $(PYTHON) -m unittest discover -s tests -p '*.py' -q
|
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:
|
bundle:
|
||||||
$(PYTHON) tools/bundle_platforms.py
|
$(PYTHON) tools/bundle_platforms.py
|
||||||
|
|
||||||
@@ -31,8 +38,12 @@ parity-verify:
|
|||||||
PYTHONPATH=$(PROJECT_PYTHONPATH) $(PYTHON) tools/export_mock_parity.py verify $(PARITY_REPORT)
|
PYTHONPATH=$(PROJECT_PYTHONPATH) $(PYTHON) tools/export_mock_parity.py verify $(PARITY_REPORT)
|
||||||
|
|
||||||
doctor:
|
doctor:
|
||||||
|
PYTHONPATH=$(PROJECT_PYTHONPATH) $(PYTHON) -m quant_os doctor
|
||||||
PYTHONPATH=$(PROJECT_PYTHONPATH) $(PYTHON) tools/capability_probe.py
|
PYTHONPATH=$(PROJECT_PYTHONPATH) $(PYTHON) tools/capability_probe.py
|
||||||
|
|
||||||
|
secret-scan:
|
||||||
|
$(PYTHON) tools/check_tracked_secrets.py
|
||||||
|
|
||||||
status:
|
status:
|
||||||
$(PYTHON) tools/build_public_status.py
|
$(PYTHON) tools/build_public_status.py
|
||||||
|
|
||||||
@@ -45,4 +56,4 @@ notebook-check:
|
|||||||
notebook-smoke:
|
notebook-smoke:
|
||||||
$(PYTHON) tools/run_colab_notebook.py notebooks/quant_os_colab.ipynb --execute
|
$(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
|
Quant OS 已从 `quants-strategies` 拆成独立项目。`quant-os` / `quant_os`
|
||||||
包、CLI namespace 与 artifact 协议前缀。
|
是长期 CLI/import;`quant60` 是第一版 A 股 Baseline 的兼容包、CLI namespace
|
||||||
|
与 V1 artifact 协议前缀。60/80 是证据 profile,不再写进产品名。
|
||||||
|
|
||||||
本项目把 2026-07-22 的技术选型文章落成一个可执行、可审计的模块化单体:
|
> 当前交易执行范围仅覆盖上交所、深交所(沪深)。北交所尚未接入,
|
||||||
本地与 Colab 可零账号运行工程 smoke;JQData 登录后可生成不可变快照并直接
|
> 所有北交所委托必须 `fail closed`;代码映射支持 `.BJ` / `.XBSE`
|
||||||
运行 PIT 本地回测/目标决策;聚宽、QMT 与 Qlib 有各自明确的运行入口。
|
> 不代表具备北交所回测或实盘交易能力。
|
||||||
|
|
||||||
> 当前状态仍是 `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、fake harness、Qlib fixture、真实聚宽 hosted 动量 smoke,以及
|
||||||
> 单个 synthetic-research TargetPackage 的真实聚宽 **execution smoke**,
|
> 单个 synthetic-research TargetPackage 的真实聚宽 **execution smoke**,
|
||||||
> 都不能代替授权真实数据 OOS、逐层同输入 parity、真实 QMT、券商影子盘、
|
> 都不能代替授权真实数据 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) 与
|
> [`docs/AUDIT_2026-07-25.md`](docs/AUDIT_2026-07-25.md) 与
|
||||||
> [`gate_scorecard.json`](gate_scorecard.json)。
|
> [`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 和证据文档生成
|
页面从 Production 80 machine profile、`gate_scorecard.json`、平台矩阵、
|
||||||
脱敏的构建时快照,逐层展示数据、模型、回测平台、券商影子盘与 G1—G10
|
bundle manifest 和证据文档生成脱敏的构建时快照,逐层展示数据、模型、
|
||||||
缺口。它不是实时服务健康页,不发布本地行情、账户状态或运行 artifacts。
|
回测平台、券商影子盘与证据缺口。它不是实时服务健康页,不发布本地行情、
|
||||||
|
账户状态或运行 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 实跑导出 |
|
| 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 |
|
| 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 |
|
| 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,不是
|
`portable-momentum-v1` 只是当前真实跑过聚宽的跨引擎连通性 smoke,不是
|
||||||
60 分 Baseline。Baseline 的权威决策留在本地:冻结 Ridge model bundle,
|
60 分 Baseline。Baseline 的权威决策留在本地:冻结 Ridge model bundle,
|
||||||
@@ -68,13 +90,14 @@ make local
|
|||||||
|
|
||||||
它依次执行:
|
它依次执行:
|
||||||
|
|
||||||
1. 全量单元/合约测试;
|
1. 新 namespace 依赖边界和 80 分 profile 评估;
|
||||||
2. 聚宽与 QMT 单文件 bundle;
|
2. 全量单元/合约测试;
|
||||||
3. synthetic execution smoke 与完整 manifest verifier;
|
3. 聚宽与 QMT 单文件 bundle;
|
||||||
4. synthetic research-to-target 与 research manifest verifier;
|
4. synthetic execution smoke 与完整 manifest verifier;
|
||||||
5. JoinQuant/QMT mock parity 报告及校验;
|
5. synthetic research-to-target 与 research manifest verifier;
|
||||||
6. capability probe;
|
6. JoinQuant/QMT mock parity 报告及校验;
|
||||||
7. Colab notebook 语法检查和逐 cell 本地执行。
|
7. capability probe;
|
||||||
|
8. Colab notebook 语法检查和逐 cell 本地执行。
|
||||||
|
|
||||||
等价的脚本入口:
|
等价的脚本入口:
|
||||||
|
|
||||||
@@ -437,7 +460,9 @@ key。它必须稳定备份、限制访问且绝不
|
|||||||
官方 query API 对“失败”和“空列表”的返回合同,并记录实际
|
官方 query API 对“失败”和“空列表”的返回合同,并记录实际
|
||||||
terminal/xtquant build、确认 `XtTrade.traded_amount` 成交金额语义与允许
|
terminal/xtquant build、确认 `XtTrade.traded_amount` 成交金额语义与允许
|
||||||
误差。当前 amount 只是被认证的 informational fact,不参与 target quantity
|
误差。当前 amount 只是被认证的 informational fact,不参与 target quantity
|
||||||
或 proposed delta。之后还要累计至少 20 个交易日、恢复演练和零未知账差。
|
或 proposed delta。之后还要先满足 Baseline 60 的至少 20 个交易日 shadow、
|
||||||
|
恢复演练和零未知账差;Production 80 当前要求同一冻结候选累计至少 60 个
|
||||||
|
交易日。
|
||||||
|
|
||||||
## Qlib 0.9.7
|
## Qlib 0.9.7
|
||||||
|
|
||||||
@@ -553,7 +578,8 @@ real_platform_pass = false
|
|||||||
gate_credit = []
|
gate_credit = []
|
||||||
```
|
```
|
||||||
|
|
||||||
真实 G9 仍要求聚宽/QMT 导出与至少 20 个交易日 QMT 影子记录。完整层级、
|
Baseline 60 的真实 G9 仍要求聚宽/QMT 导出与至少 20 个交易日 QMT 影子记录;
|
||||||
|
Production 80 当前要求同一冻结候选至少 60 个交易日。完整层级、
|
||||||
容差和 reason code 见
|
容差和 reason code 见
|
||||||
[`docs/CROSS_ENGINE_CONSISTENCY.md`](docs/CROSS_ENGINE_CONSISTENCY.md)。
|
[`docs/CROSS_ENGINE_CONSISTENCY.md`](docs/CROSS_ENGINE_CONSISTENCY.md)。
|
||||||
|
|
||||||
@@ -593,7 +619,8 @@ factor risk、CVXPY、TWAP/POV 或 Ridge 上游四层在 hosted 环境被调用
|
|||||||
- 聚宽已有单包 TargetPackage execution 导出,但仍缺授权真实数据的多期
|
- 聚宽已有单包 TargetPackage execution 导出,但仍缺授权真实数据的多期
|
||||||
TargetPackage、本地/聚宽逐层同输入对账;QMT 尚无真实 peer 导出,QMT
|
TargetPackage、本地/聚宽逐层同输入对账;QMT 尚无真实 peer 导出,QMT
|
||||||
历史 ST 权限正例、账户 query 合同、
|
历史 ST 权限正例、账户 query 合同、
|
||||||
20 日只读影子、回调/恢复/对账尚未形成真实证据;
|
Baseline 60 所需 20 日只读影子、回调/恢复/对账尚未形成真实证据;
|
||||||
|
Production 80 还要求同一冻结候选累计至少 60 个交易日 shadow;
|
||||||
- 自动 scheduler、日终对账服务、监控告警与 kill-switch 演练未形成连续证据;
|
- 自动 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 证据;
|
- 授权真实数据训练的冻结模型包和真实 OOS 证据;
|
||||||
- 真实数据校准的风险、冲击、容量和 TCA;
|
- 真实数据校准的风险、冲击、容量和 TCA;
|
||||||
- 授权真实数据的多期 JoinQuant 导出、逐层本地对账与真实 QMT peer;
|
- 授权真实数据的多期 JoinQuant 导出、逐层本地对账与真实 QMT peer;
|
||||||
- 券商状态映射、restart recovery daemon、20 日 shadow 与连续对账;
|
- 券商状态映射、restart recovery daemon、连续对账,以及 Production 80
|
||||||
- 运维监控、kill-switch 演练和程序化交易合规确认。
|
要求的同一冻结候选 60 日 shadow(Baseline 60 的前置门槛为 20 日);
|
||||||
|
- 运维监控、kill-switch 演练和券商程序化交易报告/核查确认。
|
||||||
|
|
||||||
所以 Quant OS 已具备本地五层 vertical slice 与 TargetPackage 平台边界,
|
所以 Quant OS 已具备本地五层 vertical slice 与 TargetPackage 平台边界,
|
||||||
但仍只是可继续填证据的候选骨架,不是已经达到整体 60 分的实盘系统。证据政策见
|
但仍只是可继续填证据的候选骨架,不是已经达到整体 60 分的实盘系统。证据政策见
|
||||||
|
|||||||
@@ -117,7 +117,9 @@ true;Universe、Alpha、Portfolio、Risk 仍为 false,因此
|
|||||||
smoke 作为线路先验,但仍要导出完整多期 identity/plan/orders/fills。
|
smoke 作为线路先验,但仍要导出完整多期 identity/plan/orders/fills。
|
||||||
5. 做 local/JoinQuant/QMT 逐层差异;任何 `UNEXPLAINED` 必须为零。当前无
|
5. 做 local/JoinQuant/QMT 逐层差异;任何 `UNEXPLAINED` 必须为零。当前无
|
||||||
QMT peer,所以 G9 仍未通过。
|
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 |
|
| real JoinQuant export vs local canonical run | Yes,覆盖的 layers/dates |
|
||||||
| licensed qmttools run with saved version/result | Yes,覆盖的 layers |
|
| licensed qmttools run with saved version/result | Yes,覆盖的 layers |
|
||||||
| XtTrader shadow using real broker snapshot | Yes,pre-trade/order semantics |
|
| 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 smoke 已运行;QMT 和 broker shadow 尚未运行,而且
|
||||||
JoinQuant 仍缺同输入导出与 local peer,所以 G9 继续为 `not_passed`。
|
JoinQuant 仍缺同输入导出与 local peer,所以 G9 继续为 `not_passed`。
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ still missing:
|
|||||||
portable plan, orders and fills;
|
portable plan, orders and fills;
|
||||||
- a local canonical run over the exact same frozen inputs;
|
- a local canonical run over the exact same frozen inputs;
|
||||||
- layer-by-layer L1—L4 comparison with enumerated difference reasons;
|
- 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`.
|
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 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 配置和执行时钟待取证 |
|
| 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 合同已测 | 尚无专有运行时实跑 |
|
| 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 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 和冻结模型缺失 |
|
| 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` |
|
| 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:
|
从 Quant OS 根目录执行。镜像路径只放在当前 shell 环境变量中,不写入 Git:
|
||||||
|
|
||||||
```bash
|
```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
|
export TUSHARE_MIRROR_ROOT=/path/to/tushare-mirror
|
||||||
cd "$QUANT_OS_ROOT"
|
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",
|
"schema_version": "1.0",
|
||||||
"system": "Quant OS / quant60 baseline kernel",
|
"system": "Quant OS / quant60 baseline kernel",
|
||||||
"audit_as_of": "2026-07-26",
|
"audit_as_of": "2026-07-30",
|
||||||
"claim_policy": {
|
"claim_policy": {
|
||||||
"baseline_threshold": 60,
|
"baseline_threshold": 60,
|
||||||
"all_gates_required": true,
|
"all_gates_required": true,
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
"status": "not_scored",
|
"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 未通过。",
|
"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": [
|
"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 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.",
|
"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.",
|
"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 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",
|
"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 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",
|
"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 compliance confirmation"
|
"continuous reconciliation, recovery drills and broker programmatic-trading report/verification confirmation"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"gates": [
|
"gates": [
|
||||||
@@ -167,7 +167,7 @@
|
|||||||
"required_evidence": [
|
"required_evidence": [
|
||||||
"真实平台跨引擎 manifest",
|
"真实平台跨引擎 manifest",
|
||||||
"逐层 signal/target/order 差异",
|
"逐层 signal/target/order 差异",
|
||||||
"QMT 至少 20 个交易日影子记录"
|
"Baseline 60 所需的 QMT 至少 20 个交易日影子记录;Production 80 另要求同一冻结候选至少 60 个交易日"
|
||||||
],
|
],
|
||||||
"evidence": []
|
"evidence": []
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -45,8 +45,9 @@
|
|||||||
"import shutil\n",
|
"import shutil\n",
|
||||||
"import subprocess\n",
|
"import subprocess\n",
|
||||||
"import sys\n",
|
"import sys\n",
|
||||||
|
"import tempfile\n",
|
||||||
"\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",
|
"REPO_BRANCH = \"master\"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"def run(command, *, env=None):\n",
|
"def run(command, *, env=None):\n",
|
||||||
@@ -54,19 +55,23 @@
|
|||||||
" subprocess.run(command, check=True, env=env)\n",
|
" subprocess.run(command, check=True, env=env)\n",
|
||||||
"\n",
|
"\n",
|
||||||
"cwd = Path.cwd().resolve()\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",
|
" project = cwd\n",
|
||||||
"elif (cwd / \"quant-os\" / \"pyproject.toml\").is_file():\n",
|
"elif (cwd / \"quant-os\" / \"pyproject.toml\").is_file():\n",
|
||||||
" project = cwd / \"quant-os\"\n",
|
" project = cwd / \"quant-os\"\n",
|
||||||
"else:\n",
|
"else:\n",
|
||||||
" checkout = Path(\"/content/quants-strategies\")\n",
|
" checkout = Path(\"/content/quant-os\")\n",
|
||||||
" if not checkout.exists():\n",
|
" if not checkout.exists():\n",
|
||||||
" run([\"git\", \"clone\", \"--depth\", \"1\", \"--branch\", REPO_BRANCH, REPO_URL, str(checkout)])\n",
|
" run([\"git\", \"clone\", \"--depth\", \"1\", \"--branch\", REPO_BRANCH, REPO_URL, str(checkout)])\n",
|
||||||
" project = checkout / \"quant-os\"\n",
|
" project = checkout\n",
|
||||||
"\n",
|
"\n",
|
||||||
"if not (project / \"pyproject.toml\").is_file():\n",
|
"if not (project / \"pyproject.toml\").is_file():\n",
|
||||||
" raise RuntimeError(f\"Quant OS project not found at {project}\")\n",
|
" raise RuntimeError(f\"Quant OS project not found at {project}\")\n",
|
||||||
"os.chdir(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})"
|
"print({\"project\": str(project), \"python\": sys.version})"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -114,7 +119,11 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": null,
|
"execution_count": null,
|
||||||
"metadata": {},
|
"metadata": {
|
||||||
|
"tags": [
|
||||||
|
"optional"
|
||||||
|
]
|
||||||
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"equity = json.loads(Path(\"artifacts/colab-smoke/equity.json\").read_text(encoding=\"utf-8\"))\n",
|
"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"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "quant60"
|
name = "quant-os"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
description = "Portable, auditable A-share quantitative trading baseline"
|
description = "Evidence-driven A-share research, backtest and controlled-production operating system"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = []
|
dependencies = []
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
quant-os = "quant_os.cli:main"
|
||||||
quant60 = "quant60.cli:main"
|
quant60 = "quant60.cli:main"
|
||||||
|
|
||||||
[tool.setuptools]
|
[tool.setuptools]
|
||||||
|
|||||||
@@ -3,9 +3,10 @@
|
|||||||
从仓库中的 `quant-os` 项目根目录运行命令。本文没有任何命令会启用真实订单。
|
从仓库中的 `quant-os` 项目根目录运行命令。本文没有任何命令会启用真实订单。
|
||||||
Quant OS 是项目名;`quant60` 是第一版 A 股 Baseline 内核和 CLI namespace。
|
Quant OS 是项目名;`quant60` 是第一版 A 股 Baseline 内核和 CLI namespace。
|
||||||
|
|
||||||
当前结论是 `NOT_BASELINE_60`。最近一次标准库套件为 211 tests、OK、
|
当前结论是 `NOT_BASELINE_60`。最近一次完整套件的精确计数以
|
||||||
3 个可选路径 skip。CPython 3.12 + `pyqlib==0.9.7` 的 native momentum
|
`gate_scorecard.json` 和 CI 运行记录为准,测试通过只属于本地/fixture
|
||||||
和 Alpha158/LightGBM/Recorder fixture 已真实跑通;JoinQuant、QMT 与 broker
|
证据。CPython 3.12 + `pyqlib==0.9.7` 的 native momentum 和
|
||||||
|
Alpha158/LightGBM/Recorder fixture 已真实跑通;JoinQuant、QMT 与 broker
|
||||||
中,JoinQuant 已有一次 2024 全年真实 hosted smoke,QMT 与 broker 尚无
|
中,JoinQuant 已有一次 2024 全年真实 hosted smoke,QMT 与 broker 尚无
|
||||||
真实运行证据;聚宽仍缺完整导出与同输入 peer。
|
真实运行证据;聚宽仍缺完整导出与同输入 peer。
|
||||||
|
|
||||||
@@ -435,7 +436,8 @@ QMT 账号还要先探测官方 query API 在“失败”与“空列表”时
|
|||||||
terminal/xtquant build 记录以及 `XtTrade.traded_amount` 对
|
terminal/xtquant build 记录以及 `XtTrade.traded_amount` 对
|
||||||
`price × quantity` 的真实容差探针。当前 amount 是被认证的 informational
|
`price × quantity` 的真实容差探针。当前 amount 是被认证的 informational
|
||||||
fact,不参与 target/delta。之后还需脱敏 callback replay、restart recovery、
|
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
|
## 8. Qlib 0.9.7 research
|
||||||
|
|
||||||
@@ -527,8 +529,9 @@ real_platform_pass = false
|
|||||||
gate_credit = []
|
gate_credit = []
|
||||||
```
|
```
|
||||||
|
|
||||||
因此 mock parity 不计 G9。真实 G9 仍要求真实 JoinQuant/QMT export、同输入
|
因此 mock parity 不计 G9。Baseline 60 的真实 G9 仍要求真实 JoinQuant/QMT
|
||||||
的 L1—L5 difference report 和至少 20 trading days QMT/broker shadow。
|
export、同输入的 L1—L5 difference report 和至少 20 trading days QMT/broker
|
||||||
|
shadow;Production 80 当前要求同一冻结候选至少 60 trading days。
|
||||||
|
|
||||||
## 10. Evidence checklist
|
## 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 \
|
"${python_bin}" -c \
|
||||||
'import sys; assert sys.version_info >= (3, 10), sys.version'
|
'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
|
PYTHONPATH=src:. "${python_bin}" -m unittest discover -s tests -p '*.py' -q
|
||||||
"${python_bin}" tools/bundle_platforms.py
|
"${python_bin}" tools/bundle_platforms.py
|
||||||
PYTHONPATH=src:. "${python_bin}" -m quant60 smoke \
|
PYTHONPATH=src:. "${python_bin}" -m quant60 smoke \
|
||||||
@@ -26,6 +29,8 @@ PYTHONPATH=src:. "${python_bin}" tools/export_mock_parity.py \
|
|||||||
verify \
|
verify \
|
||||||
artifacts/mock-parity/report.json
|
artifacts/mock-parity/report.json
|
||||||
PYTHONPATH=src:. "${python_bin}" tools/capability_probe.py
|
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/build_public_status.py --check
|
||||||
"${python_bin}" tools/run_colab_notebook.py notebooks/quant_os_colab.ipynb
|
"${python_bin}" tools/run_colab_notebook.py notebooks/quant_os_colab.ipynb
|
||||||
"${python_bin}" tools/run_colab_notebook.py \
|
"${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">
|
<div class="masthead__brand">
|
||||||
<p class="eyebrow">Release control / public snapshot</p>
|
<p class="eyebrow">Release control / public snapshot</p>
|
||||||
<h1 class="brand-title" id="page-title">Quant OS</h1>
|
<h1 class="brand-title" id="page-title">Quant OS</h1>
|
||||||
<p class="brand-subtitle">个人 A 股研究到执行的证据审计台</p>
|
<p class="brand-subtitle">个人沪深 A 股研究到执行的证据审计台</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="masthead__claim">
|
<div class="masthead__claim">
|
||||||
<div class="claim-line">
|
<div class="claim-line">
|
||||||
@@ -1479,15 +1479,15 @@
|
|||||||
|
|
||||||
<div class="safety-banner" role="note" aria-label="资金安全边界">
|
<div class="safety-banner" role="note" aria-label="资金安全边界">
|
||||||
<svg class="icon" aria-hidden="true"><use href="#icon-shield-alert"></use></svg>
|
<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>
|
<span class="snapshot-label">LIVE READY = FALSE</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="metrics" aria-label="核心状态指标">
|
<section class="metrics" aria-label="核心状态指标">
|
||||||
<div class="metric">
|
<div class="metric">
|
||||||
<span class="metric__label">Delivery score</span>
|
<span class="metric__label">Production 80</span>
|
||||||
<strong class="metric__value" id="metric-score">NOT SCORED</strong>
|
<strong class="metric__value" id="metric-score">0 / 100</strong>
|
||||||
<span class="metric__note">不把文章估分冒充系统得分</span>
|
<span class="metric__note">证据得分;Hard Gate 不可补偿</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="metric">
|
<div class="metric">
|
||||||
<span class="metric__label">Gate evidence</span>
|
<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="#layers"><span class="rail__index">01</span><span>系统分层</span></a>
|
||||||
<a href="#models"><span class="rail__index">02</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="#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="#maturity80"><span class="rail__index">04</span><span>80 分标准</span></a>
|
||||||
<a href="#bundles"><span class="rail__index">05</span><span>发布包</span></a>
|
<a href="#gates"><span class="rail__index">05</span><span>G1–G10</span></a>
|
||||||
<a href="#provenance"><span class="rail__index">06</span><span>证据来源</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>
|
</nav>
|
||||||
|
|
||||||
<main class="main-content" id="main-content" tabindex="-1">
|
<main class="main-content" id="main-content" tabindex="-1">
|
||||||
@@ -1626,9 +1627,48 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</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">
|
<div class="section-header">
|
||||||
<span class="section-code">SEC.04</span>
|
<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>
|
<div>
|
||||||
<h2 class="section-title" id="gates-title">Baseline 60 · G1–G10</h2>
|
<h2 class="section-title" id="gates-title">Baseline 60 · G1–G10</h2>
|
||||||
<p class="section-intro">Gate 是发布闸门,不是代码清单。展开每项查看验收定义和缺失证据;当前 10 项均未通过。</p>
|
<p class="section-intro">Gate 是发布闸门,不是代码清单。展开每项查看验收定义和缺失证据;当前 10 项均未通过。</p>
|
||||||
@@ -1639,7 +1679,7 @@
|
|||||||
|
|
||||||
<section class="audit-section" id="bundles" aria-labelledby="bundles-title">
|
<section class="audit-section" id="bundles" aria-labelledby="bundles-title">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<span class="section-code">SEC.05</span>
|
<span class="section-code">SEC.06</span>
|
||||||
<div>
|
<div>
|
||||||
<h2 class="section-title" id="bundles-title">跨平台发布包完整性</h2>
|
<h2 class="section-title" id="bundles-title">跨平台发布包完整性</h2>
|
||||||
<p class="section-intro">构建器核对共享核心、wrapper、配置与生成单文件的 SHA-256。哈希一致只证明代码发布包完整,不证明目标平台通过。</p>
|
<p class="section-intro">构建器核对共享核心、wrapper、配置与生成单文件的 SHA-256。哈希一致只证明代码发布包完整,不证明目标平台通过。</p>
|
||||||
@@ -1664,7 +1704,7 @@
|
|||||||
|
|
||||||
<section class="audit-section" id="provenance" aria-labelledby="provenance-title">
|
<section class="audit-section" id="provenance" aria-labelledby="provenance-title">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<span class="section-code">SEC.06</span>
|
<span class="section-code">SEC.07</span>
|
||||||
<div>
|
<div>
|
||||||
<h2 class="section-title" id="provenance-title">证据来源与快照血缘</h2>
|
<h2 class="section-title" id="provenance-title">证据来源与快照血缘</h2>
|
||||||
<p class="section-intro">所有公开字段由仓库中的权威记录生成并脱敏。链接指向私有 Gitea 源码,访问时可能要求登录。</p>
|
<p class="section-intro">所有公开字段由仓库中的权威记录生成并脱敏。链接指向私有 Gitea 源码,访问时可能要求登录。</p>
|
||||||
@@ -1738,7 +1778,7 @@
|
|||||||
|
|
||||||
<footer class="page-footer">
|
<footer class="page-footer">
|
||||||
<p>本页是公开、脱敏的构建时快照。未发布本地行情、运行 artifacts、券商状态或可复用凭据。任何实盘决策必须回到 G1–G10 的发布级证据。</p>
|
<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>
|
<svg class="icon" aria-hidden="true"><use href="#icon-external"></use></svg>
|
||||||
</a>
|
</a>
|
||||||
@@ -1759,7 +1799,7 @@
|
|||||||
(() => {
|
(() => {
|
||||||
"use strict";
|
"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 = {
|
const statusLabels = {
|
||||||
implemented: { label: "已实现", tone: "implemented" },
|
implemented: { label: "已实现", tone: "implemented" },
|
||||||
tested: { label: "合同验证", tone: "tested" },
|
tested: { label: "合同验证", tone: "tested" },
|
||||||
@@ -1830,13 +1870,14 @@
|
|||||||
const renderHeader = (data) => {
|
const renderHeader = (data) => {
|
||||||
const claim = data.claim || {};
|
const claim = data.claim || {};
|
||||||
const summary = data.summary || {};
|
const summary = data.summary || {};
|
||||||
|
const maturity80 = data.maturity80 || {};
|
||||||
byId("claim-status").textContent = claim.status || "NOT_BASELINE_60";
|
byId("claim-status").textContent = claim.status || "NOT_BASELINE_60";
|
||||||
byId("claim-gates").textContent = `${claim.gatesPassed ?? 0} / ${claim.gatesTotal ?? 10} GATES`;
|
byId("claim-gates").textContent = `${claim.gatesPassed ?? 0} / ${claim.gatesTotal ?? 10} GATES`;
|
||||||
byId("claim-reason").textContent = claim.reason || "缺少发布级证据。";
|
byId("claim-reason").textContent = claim.reason || "缺少发布级证据。";
|
||||||
byId("overview-reason").textContent = claim.reason || "缺少发布级证据。";
|
byId("overview-reason").textContent = claim.reason || "缺少发布级证据。";
|
||||||
byId("metric-score").textContent = claim.score == null
|
byId("market-scope-copy").textContent = data.marketScope?.statement ||
|
||||||
? String(claim.scoreStatus || "not_scored").replaceAll("_", " ").toUpperCase()
|
"当前交易执行范围仅覆盖沪深;北交所尚未接入,所有北交所委托必须 fail closed。";
|
||||||
: String(claim.score);
|
byId("metric-score").textContent = `${maturity80.effectiveScore ?? 0} / ${maturity80.maximumScore ?? 100}`;
|
||||||
byId("metric-gates").textContent = `${claim.gatesPassed ?? 0} / ${claim.gatesTotal ?? 10}`;
|
byId("metric-gates").textContent = `${claim.gatesPassed ?? 0} / ${claim.gatesTotal ?? 10}`;
|
||||||
byId("metric-tests").textContent = summary.testsRecorded == null ? "—" : String(summary.testsRecorded);
|
byId("metric-tests").textContent = summary.testsRecorded == null ? "—" : String(summary.testsRecorded);
|
||||||
byId("metric-tests-note").textContent = summary.optionalSkipsRecorded == null
|
byId("metric-tests-note").textContent = summary.optionalSkipsRecorded == null
|
||||||
@@ -1853,6 +1894,63 @@
|
|||||||
: "<li>无真实运行证据记录</li>";
|
: "<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 renderLayers = (data) => {
|
||||||
const layers = Array.isArray(data.layers) ? data.layers : [];
|
const layers = Array.isArray(data.layers) ? data.layers : [];
|
||||||
byId("layer-list").innerHTML = layers.map((layer, index) => `
|
byId("layer-list").innerHTML = layers.map((layer, index) => `
|
||||||
@@ -2071,15 +2169,66 @@
|
|||||||
|
|
||||||
const validatePublicData = (data) => {
|
const validatePublicData = (data) => {
|
||||||
if (!data || typeof data !== "object") throw new Error("missing public status object");
|
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");
|
const claim = data.claim || {};
|
||||||
if (data.claim?.releaseVerified !== false || data.claim?.liveReady !== false) {
|
if (!["NOT_BASELINE_60", "BASELINE_60_VERIFIED"].includes(claim.status)) {
|
||||||
throw new Error("unsafe release claim");
|
throw new Error("unsupported claim status");
|
||||||
}
|
}
|
||||||
if (data.claim?.gatesPassed !== 0 || data.claim?.gatesTotal !== 10) {
|
if (!Array.isArray(data.gates) || data.gates.length !== claim.gatesTotal) {
|
||||||
throw new Error("unexpected gate score");
|
throw new Error("gate set mismatch");
|
||||||
}
|
}
|
||||||
if (!Array.isArray(data.gates) || data.gates.some((gate) => gate.status !== "not_passed")) {
|
if (data.gates.some((gate) => !["passed", "not_passed"].includes(gate.status))) {
|
||||||
throw new Error("all gates must remain explicitly not_passed");
|
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);
|
renderLayers(data);
|
||||||
renderModels(data);
|
renderModels(data);
|
||||||
renderPlatforms();
|
renderPlatforms();
|
||||||
|
renderMaturity80(data);
|
||||||
renderGates(data);
|
renderGates(data);
|
||||||
renderBundles(data);
|
renderBundles(data);
|
||||||
renderActions(data);
|
renderActions(data);
|
||||||
|
|||||||
+506
-28
@@ -1,6 +1,6 @@
|
|||||||
// Generated by tools/build_public_status.py; do not edit by hand.
|
// Generated by tools/build_public_status.py; do not edit by hand.
|
||||||
window.QUANT_OS_PUBLIC_STATUS = {
|
window.QUANT_OS_PUBLIC_STATUS = {
|
||||||
"auditAsOf": "2026-07-26",
|
"auditAsOf": "2026-07-30",
|
||||||
"bundles": {
|
"bundles": {
|
||||||
"artifacts": [
|
"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 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",
|
"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 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",
|
"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 compliance confirmation"
|
"continuous reconciliation, recovery drills and broker programmatic-trading report/verification confirmation"
|
||||||
],
|
],
|
||||||
"internal": [
|
"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.",
|
"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",
|
"kind": "runbook",
|
||||||
"label": "Platform deployment runbook",
|
"label": "Platform deployment runbook",
|
||||||
"path": "runbooks/PLATFORM_DEPLOYMENT.md"
|
"path": "runbooks/PLATFORM_DEPLOYMENT.md"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "public-status-deployment",
|
||||||
|
"kind": "runbook",
|
||||||
|
"label": "Atomic public status deployment",
|
||||||
|
"path": "ops/README.md"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"freshness": [
|
"freshness": [
|
||||||
{
|
{
|
||||||
"asOf": "2026-07-26",
|
"asOf": "2026-07-30",
|
||||||
"evidenceLinks": [
|
"evidenceLinks": [
|
||||||
"scorecard",
|
"scorecard",
|
||||||
"audit"
|
"audit"
|
||||||
@@ -216,7 +222,7 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
|||||||
"label": "项目证据审计",
|
"label": "项目证据审计",
|
||||||
"semantics": "证据盘点日期;不是部署时间,也不是行情截止日。",
|
"semantics": "证据盘点日期;不是部署时间,也不是行情截止日。",
|
||||||
"status": "recorded",
|
"status": "recorded",
|
||||||
"value": "2026-07-26"
|
"value": "2026-07-30"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"evidenceLinks": [
|
"evidenceLinks": [
|
||||||
@@ -292,7 +298,7 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
|||||||
"label": "QMT / broker 真实运行",
|
"label": "QMT / broker 真实运行",
|
||||||
"lastRealRuntimeAt": null,
|
"lastRealRuntimeAt": null,
|
||||||
"productionReady": false,
|
"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",
|
"status": "blocked",
|
||||||
"value": "N/A"
|
"value": "N/A"
|
||||||
}
|
}
|
||||||
@@ -486,13 +492,13 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
|||||||
"limitations": [
|
"limitations": [
|
||||||
"真实平台跨引擎 manifest",
|
"真实平台跨引擎 manifest",
|
||||||
"逐层 signal/target/order 差异",
|
"逐层 signal/target/order 差异",
|
||||||
"QMT 至少 20 个交易日影子记录"
|
"Baseline 60 所需的 QMT 至少 20 个交易日影子记录;Production 80 另要求同一冻结候选至少 60 个交易日"
|
||||||
],
|
],
|
||||||
"name": "跨引擎一致性",
|
"name": "跨引擎一致性",
|
||||||
"requiredEvidence": [
|
"requiredEvidence": [
|
||||||
"真实平台跨引擎 manifest",
|
"真实平台跨引擎 manifest",
|
||||||
"逐层 signal/target/order 差异",
|
"逐层 signal/target/order 差异",
|
||||||
"QMT 至少 20 个交易日影子记录"
|
"Baseline 60 所需的 QMT 至少 20 个交易日影子记录;Production 80 另要求同一冻结候选至少 60 个交易日"
|
||||||
],
|
],
|
||||||
"status": "not_passed"
|
"status": "not_passed"
|
||||||
},
|
},
|
||||||
@@ -519,7 +525,7 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
|||||||
"status": "not_passed"
|
"status": "not_passed"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"generatedAt": "2026-07-26T00:00:00Z",
|
"generatedAt": "2026-07-30T00:00:00Z",
|
||||||
"generatedAtSource": "audit_as_of",
|
"generatedAtSource": "audit_as_of",
|
||||||
"layers": [
|
"layers": [
|
||||||
{
|
{
|
||||||
@@ -646,7 +652,7 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
|||||||
],
|
],
|
||||||
"id": "broker-oms",
|
"id": "broker-oms",
|
||||||
"limitations": [
|
"limitations": [
|
||||||
"无真实券商 attestation、回调/重启证明和 20 个交易日影子记录;没有实盘下单入口。"
|
"无真实券商 attestation、回调/重启证明和影子记录;Baseline 60 至少需要 20 个交易日,Production 80 当前缺口为同一冻结候选至少 60 个交易日;没有实盘下单入口。"
|
||||||
],
|
],
|
||||||
"name": "券商、订单与影子盘",
|
"name": "券商、订单与影子盘",
|
||||||
"releaseVerified": false,
|
"releaseVerified": false,
|
||||||
@@ -673,6 +679,412 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
|||||||
"status": "blocked"
|
"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": [
|
"models": [
|
||||||
{
|
{
|
||||||
"claim": "共享 symbol normalization、momentum、target weight/quantity 与 order-delta 计算;2024 JoinQuant hosted run 只证明这一 smoke 路径。",
|
"claim": "共享 symbol normalization、momentum、target weight/quantity 与 order-delta 计算;2024 JoinQuant hosted run 只证明这一 smoke 路径。",
|
||||||
@@ -777,8 +1189,8 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
|||||||
"G9"
|
"G9"
|
||||||
],
|
],
|
||||||
"priority": 5,
|
"priority": 5,
|
||||||
"title": "完成 QMT 券商合同探针与至少 20 日影子盘",
|
"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, callback/restart and at least 20 trading days of shadow evidence"
|
"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": [
|
"gateIds": [
|
||||||
@@ -786,8 +1198,8 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
|||||||
"G10"
|
"G10"
|
||||||
],
|
],
|
||||||
"priority": 6,
|
"priority": 6,
|
||||||
"title": "完成连续对账、恢复演练与券商合规确认",
|
"title": "完成连续对账、恢复演练与券商程序化交易报告/核查确认",
|
||||||
"why": "continuous reconciliation, recovery drills and broker compliance confirmation"
|
"why": "continuous reconciliation, recovery drills and broker programmatic-trading report/verification confirmation"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"platforms": [
|
"platforms": [
|
||||||
@@ -984,7 +1396,7 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
|||||||
"id": "xttrader-read-only-shadow",
|
"id": "xttrader-read-only-shadow",
|
||||||
"intendedUse": "账户绑定 target-diff 与 broker observation",
|
"intendedUse": "账户绑定 target-diff 与 broker observation",
|
||||||
"limitations": [
|
"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,
|
"liveReady": false,
|
||||||
"name": "XtTrader read-only shadow",
|
"name": "XtTrader read-only shadow",
|
||||||
@@ -1063,16 +1475,30 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
|||||||
"provenance": {
|
"provenance": {
|
||||||
"baselineConfig": "configs/baseline.json",
|
"baselineConfig": "configs/baseline.json",
|
||||||
"bundleManifest": "dist/bundle_manifest.json",
|
"bundleManifest": "dist/bundle_manifest.json",
|
||||||
"payloadSha256": "76aad7a50088b9949b9c2549a4da568d14c6bc2ff4acc7df6419989637386bc3",
|
"payloadSha256": "9aae5da8e1d92fd28cfd7d6bef50774c4981b2baac1728ccd3153729234a88f0",
|
||||||
"payloadSha256Semantics": "SHA-256 of canonical payload before payloadSha256 is added.",
|
"payloadSha256Semantics": "SHA-256 of canonical payload before payloadSha256 is added.",
|
||||||
"platformMatrix": "docs/PLATFORM_MATRIX.md",
|
"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",
|
"releaseReachability": "releases/quant60-research-candidate-v1/reachability.json",
|
||||||
"scorecard": "gate_scorecard.json",
|
"scorecard": "gate_scorecard.json",
|
||||||
"sourceSetSha256": "e1b8bb8827c6c051a73872f2b0ac46f18dcf705fb50768eb98090bbbe08f863c",
|
"sourceSetSha256": "6d906cde8df07f8fae2f2faf05679a476ceae8f5173533b3b67beecc710b2f61",
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"path": "gate_scorecard.json",
|
"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",
|
"path": "dist/bundle_manifest.json",
|
||||||
@@ -1092,27 +1518,43 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "README.md",
|
"path": "README.md",
|
||||||
"sha256": "d5593bdc4e535502ba817636053dc8ac38f8594be6f9e61f0fe2d11cf883a985"
|
"sha256": "0bf0562ae9247a88a5b0248f66b9b314d7ab0ea435bc8f8e5efa78f7d2f785bb"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "docs/ARCHITECTURE.md",
|
"path": "docs/ARCHITECTURE.md",
|
||||||
"sha256": "93b1981168084f40c6d127dc534ff2e60eb4cd0703c8ef446c6df58b351dfbaa"
|
"sha256": "8aae46f1b371651ac6f72905b7bcb3d72fdfda2a793e5505ab890352921c3219"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "docs/architecture/ARCHITECTURE_80.md",
|
||||||
|
"sha256": "bb57df9d7eb5858741dde282ae0853a5e4ec9f4f4eb290bdb5954228fcb8956a"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "docs/AUDIT_2026-07-25.md",
|
"path": "docs/AUDIT_2026-07-25.md",
|
||||||
"sha256": "c070b49dfc2805c338400697369d886a6dc47b53af4a1834d0dfd383431e74d0"
|
"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",
|
"path": "docs/PLATFORM_MATRIX.md",
|
||||||
"sha256": "4880b17e54257b794398bf734d80ea40d1180e52845762987443a9c61d562a23"
|
"sha256": "9c7952b019c92efd1055488b58838045ee30979ccc84008cd1218fab2a6d6717"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "docs/CROSS_ENGINE_CONSISTENCY.md",
|
"path": "docs/CROSS_ENGINE_CONSISTENCY.md",
|
||||||
"sha256": "358e4798bf853a96cb288ac379f916938415b86e86dbe4fa0e3fb4a86a612b6f"
|
"sha256": "bdb493e84201f51c5b91bacf8c894858b0c2dbceaaf0522c68d42d040ed45057"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "docs/JOINQUANT_HOSTED_EVIDENCE_2026-07-26.md",
|
"path": "docs/JOINQUANT_HOSTED_EVIDENCE_2026-07-26.md",
|
||||||
"sha256": "03df249829b3ad9b4fe33c5d93afbd842561458f4ff2fe10e80e4432934eb2d2"
|
"sha256": "71d017a7e009331413f378fc847203d19eaa1c24317f364e9dcebe645df3d13b"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "docs/JOINQUANT_TARGET_PACKAGE_EVIDENCE_2026-07-26.md",
|
"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",
|
"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",
|
"path": "runbooks/PLATFORM_DEPLOYMENT.md",
|
||||||
"sha256": "f6bb1eda335c0d05f96d39b4d4167ee623fe6cffeef75015ba7985638fa8f635"
|
"sha256": "001bc09f6865c6ee90fbbc2363b94bed7a282d039790af024457a5b1390b9840"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "tools/build_release_reachability.py",
|
"path": "tools/build_release_reachability.py",
|
||||||
@@ -1132,11 +1602,19 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "tools/build_public_status.py",
|
"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": {
|
"summary": {
|
||||||
"allFiveLayersRealPlatformObserved": false,
|
"allFiveLayersRealPlatformObserved": false,
|
||||||
"candidateRealPlatformObservationScope": "target_package_execution_consumer_only",
|
"candidateRealPlatformObservationScope": "target_package_execution_consumer_only",
|
||||||
@@ -1153,7 +1631,7 @@ window.QUANT_OS_PUBLIC_STATUS = {
|
|||||||
"Tushare completed-partition Qlib momentum smoke"
|
"Tushare completed-partition Qlib momentum smoke"
|
||||||
],
|
],
|
||||||
"testEvidenceQualifier": "Scorecard-recorded run; tests do not prove real-platform or broker gates.",
|
"testEvidenceQualifier": "Scorecard-recorded run; tests do not prove real-platform or broker gates.",
|
||||||
"testsRecorded": 285
|
"testsRecorded": 315
|
||||||
},
|
},
|
||||||
"system": "Quant OS / quant60 baseline kernel"
|
"system": "Quant OS / quant60 baseline kernel"
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
import stat
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -23,26 +26,355 @@ class ProjectScaffoldTests(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
payload = json.loads(result.stdout.strip().splitlines()[-1])
|
payload = json.loads(result.stdout.strip().splitlines()[-1])
|
||||||
self.assertTrue(payload["ok"])
|
self.assertTrue(payload["ok"])
|
||||||
self.assertGreaterEqual(payload["code_cells"], 5)
|
self.assertGreaterEqual(payload["code_cells"], 4)
|
||||||
self.assertFalse(payload["executed"])
|
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):
|
def test_secret_files_are_ignored(self):
|
||||||
ignore = (PROJECT / ".gitignore").read_text(encoding="utf-8")
|
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)
|
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):
|
def test_claude_project_contract_exists(self):
|
||||||
content = (PROJECT / "CLAUDE.md").read_text(encoding="utf-8")
|
content = (PROJECT / "CLAUDE.md").read_text(encoding="utf-8")
|
||||||
self.assertIn("executable source of truth for Quant OS", content)
|
self.assertIn("executable source of truth for Quant OS", content)
|
||||||
self.assertIn("make local", content)
|
self.assertIn("make local", content)
|
||||||
|
|
||||||
def test_gitea_workflow_is_at_monorepo_root(self):
|
def test_gitea_workflow_is_at_independent_repo_root(self):
|
||||||
workflow = PROJECT.parent / ".gitea/workflows/quant-os-ci.yml"
|
workflow = PROJECT / ".gitea/workflows/ci.yml"
|
||||||
self.assertTrue(workflow.is_file())
|
self.assertTrue(workflow.is_file())
|
||||||
content = workflow.read_text(encoding="utf-8")
|
content = workflow.read_text(encoding="utf-8")
|
||||||
self.assertIn("working-directory: quant-os", content)
|
|
||||||
self.assertIn("bash scripts/validate_all.sh", 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__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -54,6 +54,28 @@ class PublicStatusTests(unittest.TestCase):
|
|||||||
self.assertEqual(payload["claim"]["gatesPassed"], 0)
|
self.assertEqual(payload["claim"]["gatesPassed"], 0)
|
||||||
self.assertEqual(payload["claim"]["gatesTotal"], 10)
|
self.assertEqual(payload["claim"]["gatesTotal"], 10)
|
||||||
self.assertTrue(all(gate["status"] == "not_passed" for gate in payload["gates"]))
|
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):
|
def test_public_payload_is_deliberately_non_secret_and_non_local(self):
|
||||||
payload = _read_payload()
|
payload = _read_payload()
|
||||||
@@ -84,8 +106,23 @@ class PublicStatusTests(unittest.TestCase):
|
|||||||
self.assertIn("NOT_BASELINE_60", page)
|
self.assertIn("NOT_BASELINE_60", page)
|
||||||
self.assertIn("0 / 10", page)
|
self.assertIn("0 / 10", page)
|
||||||
self.assertIn("validatePublicData", 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.assertIn("platform.limitations", page)
|
||||||
self.assertNotIn("platform.missingReleaseEvidence", 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(
|
self.assertNotRegex(
|
||||||
page,
|
page,
|
||||||
re.compile(
|
re.compile(
|
||||||
@@ -98,6 +135,37 @@ class PublicStatusTests(unittest.TestCase):
|
|||||||
for tag in re.findall(r"<a\\b[^>]*target=\"_blank\"[^>]*>", page):
|
for tag in re.findall(r"<a\\b[^>]*target=\"_blank\"[^>]*>", page):
|
||||||
self.assertIn('rel="noopener noreferrer"', tag)
|
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):
|
def test_models_and_platforms_preserve_evidence_boundaries(self):
|
||||||
payload = _read_payload()
|
payload = _read_payload()
|
||||||
models = {model["id"]: model for model in payload["models"]}
|
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.assertEqual(platforms["QMT built-in bundle"]["status"], "implemented")
|
||||||
self.assertFalse(platforms["XtTrader read-only shadow"]["liveReady"])
|
self.assertFalse(platforms["XtTrader read-only shadow"]["liveReady"])
|
||||||
self.assertIn("fake", platforms["XtTrader read-only shadow"]["claim"].lower())
|
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):
|
def test_candidate_records_only_real_joinquant_execution_observation(self):
|
||||||
payload = _read_payload()
|
payload = _read_payload()
|
||||||
@@ -301,7 +374,7 @@ class PublicStatusTests(unittest.TestCase):
|
|||||||
def test_freshness_uses_daily_bars_not_the_later_trade_calendar(self):
|
def test_freshness_uses_daily_bars_not_the_later_trade_calendar(self):
|
||||||
payload = _read_payload()
|
payload = _read_payload()
|
||||||
freshness = {item["id"]: item for item in payload["freshness"]}
|
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(
|
self.assertEqual(
|
||||||
freshness["joinquant-hosted-market-window"]["marketDataThrough"],
|
freshness["joinquant-hosted-market-window"]["marketDataThrough"],
|
||||||
"2024-12-31",
|
"2024-12-31",
|
||||||
@@ -399,7 +472,7 @@ class PublicStatusTests(unittest.TestCase):
|
|||||||
scorecard = {
|
scorecard = {
|
||||||
"claim_policy": {
|
"claim_policy": {
|
||||||
"baseline_threshold": 60,
|
"baseline_threshold": 60,
|
||||||
"current_claim": "BASELINE_60",
|
"current_claim": "BASELINE_60_VERIFIED",
|
||||||
},
|
},
|
||||||
"current_delivery": {"score": 59},
|
"current_delivery": {"score": 59},
|
||||||
"gates": [{"id": "G1", "status": "not_passed"}],
|
"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]
|
PROJECT = Path(__file__).resolve().parents[1]
|
||||||
DEFAULT_OUTPUT = PROJECT / "status" / "status-data.js"
|
DEFAULT_OUTPUT = PROJECT / "status" / "status-data.js"
|
||||||
GLOBAL_NAME = "QUANT_OS_PUBLIC_STATUS"
|
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 = (
|
REACHABILITY_PATH = (
|
||||||
"releases/quant60-research-candidate-v1/reachability.json"
|
"releases/quant60-research-candidate-v1/reachability.json"
|
||||||
)
|
)
|
||||||
@@ -36,21 +41,37 @@ JOINQUANT_TARGET_PACKAGE_BUNDLE_SHA256 = (
|
|||||||
|
|
||||||
SOURCE_PATHS: Tuple[str, ...] = (
|
SOURCE_PATHS: Tuple[str, ...] = (
|
||||||
"gate_scorecard.json",
|
"gate_scorecard.json",
|
||||||
|
BASELINE_60_PROFILE_PATH,
|
||||||
|
PRODUCTION_80_STANDARD_PATH,
|
||||||
|
PRODUCTION_80_ASSESSMENT_PATH,
|
||||||
"dist/bundle_manifest.json",
|
"dist/bundle_manifest.json",
|
||||||
REACHABILITY_PATH,
|
REACHABILITY_PATH,
|
||||||
JOINQUANT_TARGET_PACKAGE_RUN_PATH,
|
JOINQUANT_TARGET_PACKAGE_RUN_PATH,
|
||||||
"configs/baseline.json",
|
"configs/baseline.json",
|
||||||
"README.md",
|
"README.md",
|
||||||
"docs/ARCHITECTURE.md",
|
"docs/ARCHITECTURE.md",
|
||||||
|
"docs/architecture/ARCHITECTURE_80.md",
|
||||||
"docs/AUDIT_2026-07-25.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/PLATFORM_MATRIX.md",
|
||||||
"docs/CROSS_ENGINE_CONSISTENCY.md",
|
"docs/CROSS_ENGINE_CONSISTENCY.md",
|
||||||
"docs/JOINQUANT_HOSTED_EVIDENCE_2026-07-26.md",
|
"docs/JOINQUANT_HOSTED_EVIDENCE_2026-07-26.md",
|
||||||
JOINQUANT_TARGET_PACKAGE_DOC_PATH,
|
JOINQUANT_TARGET_PACKAGE_DOC_PATH,
|
||||||
"docs/TUSHARE_LOCAL_DATA.md",
|
"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",
|
"runbooks/PLATFORM_DEPLOYMENT.md",
|
||||||
"tools/build_release_reachability.py",
|
"tools/build_release_reachability.py",
|
||||||
"tools/build_public_status.py",
|
"tools/build_public_status.py",
|
||||||
|
"tools/check_tracked_secrets.py",
|
||||||
|
"tools/inventory_local_assets.py",
|
||||||
)
|
)
|
||||||
|
|
||||||
ALLOWED_PROGRESS_STATUSES = frozenset(
|
ALLOWED_PROGRESS_STATUSES = frozenset(
|
||||||
@@ -704,6 +725,12 @@ def _build_evidence(project: Path) -> List[Dict[str, Any]]:
|
|||||||
"runbooks/PLATFORM_DEPLOYMENT.md",
|
"runbooks/PLATFORM_DEPLOYMENT.md",
|
||||||
"runbook",
|
"runbook",
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"public-status-deployment",
|
||||||
|
"Atomic public status deployment",
|
||||||
|
"ops/README.md",
|
||||||
|
"runbook",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
result: List[Dict[str, Any]] = []
|
result: List[Dict[str, Any]] = []
|
||||||
for evidence_id, label, raw_path, kind in records:
|
for evidence_id, label, raw_path, kind in records:
|
||||||
@@ -721,7 +748,38 @@ def _build_evidence(project: Path) -> List[Dict[str, Any]]:
|
|||||||
return result
|
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 [
|
return [
|
||||||
{
|
{
|
||||||
"id": "data",
|
"id": "data",
|
||||||
@@ -840,7 +898,10 @@ def _build_layers() -> List[Dict[str, Any]]:
|
|||||||
"authenticated evidence envelope and exact replay",
|
"authenticated evidence envelope and exact replay",
|
||||||
],
|
],
|
||||||
"limitations": [
|
"limitations": [
|
||||||
"无真实券商 attestation、回调/重启证明和 20 个交易日影子记录;没有实盘下单入口。"
|
"无真实券商 attestation、回调/重启证明和影子记录;"
|
||||||
|
f"Baseline 60 至少需要 {baseline_shadow_days} 个交易日,"
|
||||||
|
f"Production 80 当前缺口为同一冻结候选至少 {production_shadow_days} "
|
||||||
|
"个交易日;没有实盘下单入口。"
|
||||||
],
|
],
|
||||||
"evidenceLinks": ["architecture", "platform-matrix", "scorecard"],
|
"evidenceLinks": ["architecture", "platform-matrix", "scorecard"],
|
||||||
"releaseVerified": False,
|
"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", {})
|
delivery = scorecard.get("current_delivery", {})
|
||||||
if not isinstance(delivery, dict):
|
if not isinstance(delivery, dict):
|
||||||
raise PublicStatusError("scorecard current_delivery must be an object")
|
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,
|
"priority": 5,
|
||||||
"title": "完成 QMT 券商合同探针与至少 20 日影子盘",
|
"title": (
|
||||||
|
"完成 QMT 券商合同探针与 "
|
||||||
|
f"Production 80 的 {production_shadow_days} 日影子盘"
|
||||||
|
f"(Baseline 60 门槛 {baseline_shadow_days} 日)"
|
||||||
|
),
|
||||||
"why": str(missing[3]),
|
"why": str(missing[3]),
|
||||||
"gateIds": ["G6", "G8", "G9"],
|
"gateIds": ["G6", "G8", "G9"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"priority": 6,
|
"priority": 6,
|
||||||
"title": "完成连续对账、恢复演练与券商合规确认",
|
"title": "完成连续对账、恢复演练与券商程序化交易报告/核查确认",
|
||||||
"why": str(missing[4]),
|
"why": str(missing[4]),
|
||||||
"gateIds": ["G8", "G10"],
|
"gateIds": ["G8", "G10"],
|
||||||
},
|
},
|
||||||
@@ -977,6 +1047,8 @@ def _build_freshness(
|
|||||||
joinquant_markdown: str,
|
joinquant_markdown: str,
|
||||||
joinquant_target_package_run: Mapping[str, Any],
|
joinquant_target_package_run: Mapping[str, Any],
|
||||||
tushare_markdown: str,
|
tushare_markdown: str,
|
||||||
|
baseline_shadow_days: int,
|
||||||
|
production_shadow_days: int,
|
||||||
) -> List[Dict[str, Any]]:
|
) -> List[Dict[str, Any]]:
|
||||||
period_match = re.search(
|
period_match = re.search(
|
||||||
r"(?m)^-\s+Period:\s+"
|
r"(?m)^-\s+Period:\s+"
|
||||||
@@ -1123,7 +1195,12 @@ def _build_freshness(
|
|||||||
"lastRealRuntimeAt": None,
|
"lastRealRuntimeAt": None,
|
||||||
"value": "N/A",
|
"value": "N/A",
|
||||||
"productionReady": False,
|
"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"],
|
"evidenceLinks": ["platform-matrix", "platform-deployment", "scorecard"],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -1136,21 +1213,21 @@ def _validate_claim(scorecard: Mapping[str, Any]) -> None:
|
|||||||
claim = str(policy.get("current_claim", ""))
|
claim = str(policy.get("current_claim", ""))
|
||||||
if claim in FORBIDDEN_PUBLIC_CLAIMS:
|
if claim in FORBIDDEN_PUBLIC_CLAIMS:
|
||||||
raise PublicStatusError(f"unsafe public release claim: {claim}")
|
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")
|
gates = scorecard.get("gates")
|
||||||
if not isinstance(gates, list) or not gates:
|
if not isinstance(gates, list) or not gates:
|
||||||
raise PublicStatusError("scorecard gates must be a non-empty list")
|
raise PublicStatusError("scorecard gates must be a non-empty list")
|
||||||
passed = sum(gate.get("status") == "passed" for gate in gates if isinstance(gate, dict))
|
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)
|
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:
|
if claim == "NOT_BASELINE_60" and all_passed:
|
||||||
raise PublicStatusError(
|
raise PublicStatusError(
|
||||||
"scorecard is internally inconsistent: every gate passed but claim is negative"
|
"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(
|
def build_public_status(
|
||||||
project: Path,
|
project: Path,
|
||||||
*,
|
*,
|
||||||
@@ -1195,6 +1401,13 @@ def build_public_status(
|
|||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
project = project.resolve()
|
project = project.resolve()
|
||||||
scorecard = _load_json(project / "gate_scorecard.json")
|
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")
|
manifest = _load_json(project / "dist" / "bundle_manifest.json")
|
||||||
reachability_manifest = _load_json(project / REACHABILITY_PATH)
|
reachability_manifest = _load_json(project / REACHABILITY_PATH)
|
||||||
joinquant_target_package_run_raw = _load_json(
|
joinquant_target_package_run_raw = _load_json(
|
||||||
@@ -1212,6 +1425,23 @@ def build_public_status(
|
|||||||
)
|
)
|
||||||
|
|
||||||
_validate_claim(scorecard)
|
_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", ""))
|
audit_as_of = str(scorecard.get("audit_as_of", ""))
|
||||||
try:
|
try:
|
||||||
datetime.strptime(audit_as_of, "%Y-%m-%d")
|
datetime.strptime(audit_as_of, "%Y-%m-%d")
|
||||||
@@ -1271,6 +1501,11 @@ def build_public_status(
|
|||||||
project,
|
project,
|
||||||
joinquant_target_package_run_raw,
|
joinquant_target_package_run_raw,
|
||||||
)
|
)
|
||||||
|
maturity80 = _build_maturity80(
|
||||||
|
project,
|
||||||
|
production_80_standard,
|
||||||
|
production_80_assessment,
|
||||||
|
)
|
||||||
source_records: List[Dict[str, str]] = []
|
source_records: List[Dict[str, str]] = []
|
||||||
for relative in SOURCE_PATHS:
|
for relative in SOURCE_PATHS:
|
||||||
safe_path = _safe_repo_path(relative, label="provenance source")
|
safe_path = _safe_repo_path(relative, label="provenance source")
|
||||||
@@ -1284,6 +1519,16 @@ def build_public_status(
|
|||||||
|
|
||||||
claim_policy = scorecard["claim_policy"]
|
claim_policy = scorecard["claim_policy"]
|
||||||
delivery = scorecard["current_delivery"]
|
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", [])
|
internal_delivery_gaps = delivery.get("internal_delivery_gaps", [])
|
||||||
external_evidence_missing = delivery.get("external_evidence_missing", [])
|
external_evidence_missing = delivery.get("external_evidence_missing", [])
|
||||||
if not isinstance(internal_delivery_gaps, list) or len(internal_delivery_gaps) < 3:
|
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"),
|
"score": delivery.get("score"),
|
||||||
"scoreStatus": str(delivery.get("status")),
|
"scoreStatus": str(delivery.get("status")),
|
||||||
"reason": str(delivery.get("reason")),
|
"reason": str(delivery.get("reason")),
|
||||||
"releaseVerified": False,
|
"releaseVerified": baseline_verified,
|
||||||
"liveReady": False,
|
"liveReady": bool(maturity80["qualified"]),
|
||||||
},
|
},
|
||||||
|
"marketScope": _build_market_scope(production_80_standard),
|
||||||
|
"maturity80": maturity80,
|
||||||
"summary": {
|
"summary": {
|
||||||
"testsRecorded": test_count,
|
"testsRecorded": test_count,
|
||||||
"optionalSkipsRecorded": optional_skips,
|
"optionalSkipsRecorded": optional_skips,
|
||||||
@@ -1338,7 +1585,7 @@ def build_public_status(
|
|||||||
"Tushare completed-partition Qlib momentum smoke",
|
"Tushare completed-partition Qlib momentum smoke",
|
||||||
],
|
],
|
||||||
"gateCoverageLabel": f"{passed}/{len(gates)} passed",
|
"gateCoverageLabel": f"{passed}/{len(gates)} passed",
|
||||||
"productionReady": False,
|
"productionReady": bool(maturity80["qualified"]),
|
||||||
"investmentValueClaim": False,
|
"investmentValueClaim": False,
|
||||||
},
|
},
|
||||||
"candidateBoundary": {
|
"candidateBoundary": {
|
||||||
@@ -1373,7 +1620,10 @@ def build_public_status(
|
|||||||
"internal": [str(item) for item in internal_delivery_gaps],
|
"internal": [str(item) for item in internal_delivery_gaps],
|
||||||
"externalEvidence": [str(item) for item in external_evidence_missing],
|
"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(),
|
"models": _build_models(),
|
||||||
"platforms": _build_platforms(platform_markdown),
|
"platforms": _build_platforms(platform_markdown),
|
||||||
"freshness": _build_freshness(
|
"freshness": _build_freshness(
|
||||||
@@ -1381,6 +1631,8 @@ def build_public_status(
|
|||||||
joinquant_markdown=joinquant_markdown,
|
joinquant_markdown=joinquant_markdown,
|
||||||
joinquant_target_package_run=joinquant_target_package_run,
|
joinquant_target_package_run=joinquant_target_package_run,
|
||||||
tushare_markdown=tushare_markdown,
|
tushare_markdown=tushare_markdown,
|
||||||
|
baseline_shadow_days=baseline_shadow_days,
|
||||||
|
production_shadow_days=production_shadow_days,
|
||||||
),
|
),
|
||||||
"gates": gates,
|
"gates": gates,
|
||||||
"bundles": {
|
"bundles": {
|
||||||
@@ -1409,9 +1661,15 @@ def build_public_status(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"evidence": _build_evidence(project),
|
"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": {
|
"provenance": {
|
||||||
"scorecard": "gate_scorecard.json",
|
"scorecard": "gate_scorecard.json",
|
||||||
|
"production80Standard": PRODUCTION_80_STANDARD_PATH,
|
||||||
|
"production80Assessment": PRODUCTION_80_ASSESSMENT_PATH,
|
||||||
"bundleManifest": "dist/bundle_manifest.json",
|
"bundleManifest": "dist/bundle_manifest.json",
|
||||||
"releaseReachability": REACHABILITY_PATH,
|
"releaseReachability": REACHABILITY_PATH,
|
||||||
"platformMatrix": "docs/PLATFORM_MATRIX.md",
|
"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