feat: add Quant OS public status console
This commit is contained in:
@@ -6,7 +6,7 @@ Quant OS 是项目名;`quant60` 是第一版 A 股 Baseline 内核、CLI names
|
||||
回测/影子验证,并且所有 G1—G10 都由版本绑定的真实证据支持。
|
||||
|
||||
当前结论仍为 `NOT_BASELINE_60`。最近一次标准库全量测试记录为
|
||||
`Ran 216 tests`、`OK (skipped=6)`;六个 skip 是可选依赖/运行时路径。
|
||||
`Ran 224 tests`、`OK (skipped=6)`;六个 skip 是可选依赖/运行时路径。
|
||||
该结果证明代码合同,不证明真实平台、券商或合规 Gate。
|
||||
|
||||
## Stage 1: 独立仓库与确定性执行
|
||||
|
||||
@@ -4,7 +4,7 @@ SMOKE_DIR := artifacts/local-smoke
|
||||
RESEARCH_DIR := artifacts/research-smoke
|
||||
PARITY_REPORT := artifacts/mock-parity/report.json
|
||||
|
||||
.PHONY: test bundle smoke verify research research-verify parity parity-verify doctor notebook-check notebook-smoke local
|
||||
.PHONY: test bundle smoke verify research research-verify parity parity-verify doctor status status-check notebook-check notebook-smoke local
|
||||
|
||||
test:
|
||||
PYTHONPATH=$(PROJECT_PYTHONPATH) $(PYTHON) -m unittest discover -s tests -p '*.py' -q
|
||||
@@ -33,10 +33,16 @@ parity-verify:
|
||||
doctor:
|
||||
PYTHONPATH=$(PROJECT_PYTHONPATH) $(PYTHON) tools/capability_probe.py
|
||||
|
||||
status:
|
||||
$(PYTHON) tools/build_public_status.py
|
||||
|
||||
status-check:
|
||||
$(PYTHON) tools/build_public_status.py --check
|
||||
|
||||
notebook-check:
|
||||
$(PYTHON) tools/run_colab_notebook.py notebooks/quant_os_colab.ipynb
|
||||
|
||||
notebook-smoke:
|
||||
$(PYTHON) tools/run_colab_notebook.py notebooks/quant_os_colab.ipynb --execute
|
||||
|
||||
local: test bundle smoke verify research research-verify parity parity-verify doctor notebook-check notebook-smoke
|
||||
local: test bundle smoke verify research research-verify parity parity-verify doctor status-check notebook-check notebook-smoke
|
||||
|
||||
@@ -15,6 +15,26 @@ Quant OS 是项目名;`quant60` 是第一版 A 股 Baseline 的 Python
|
||||
> [`docs/AUDIT_2026-07-25.md`](docs/AUDIT_2026-07-25.md) 与
|
||||
> [`gate_scorecard.json`](gate_scorecard.json)。
|
||||
|
||||
## 在线公开验收面板
|
||||
|
||||
公开只读入口:
|
||||
|
||||
[`https://gomars.fun/quants-strategies/quant-os/status/index.html`](https://gomars.fun/quants-strategies/quant-os/status/index.html)
|
||||
|
||||
页面从 `gate_scorecard.json`、平台矩阵、bundle manifest 和证据文档生成
|
||||
脱敏的构建时快照,逐层展示数据、模型、回测平台、券商影子盘与 G1—G10
|
||||
缺口。它不是实时服务健康页,不发布本地行情、账户状态或运行 artifacts。
|
||||
|
||||
更新权威记录后必须重新生成并检查页面数据:
|
||||
|
||||
```bash
|
||||
python3 tools/build_public_status.py
|
||||
python3 tools/build_public_status.py --check
|
||||
```
|
||||
|
||||
网页源码位于 [`status/`](status/),生成器位于
|
||||
[`tools/build_public_status.py`](tools/build_public_status.py)。
|
||||
|
||||
## 当前交付边界
|
||||
|
||||
| 路径 | 当前能做什么 | 已有证据 | 仍缺什么 |
|
||||
|
||||
@@ -228,7 +228,7 @@ saved/current source aggregate、saved/current schema aggregate 和 payload
|
||||
schema。`verify-snapshot-decision` 与 data snapshot semantic verifier 承担
|
||||
各自边界的同类职责。
|
||||
|
||||
这些合同已有自动测试;最近一次标准库套件为 216 tests、OK、6 个可选路径
|
||||
这些合同已有自动测试;最近一次标准库套件为 224 tests、OK、6 个可选路径
|
||||
skip。测试结果不能替代 schema 迁移演练、真实平台导出或券商回调认证。
|
||||
|
||||
## Clocks and reconciliation
|
||||
|
||||
@@ -25,7 +25,7 @@ A 股 Baseline 内核。它的新增得分必须在授权数据、真实平台
|
||||
|
||||
截至 2026-07-26 的实现事实快照:
|
||||
|
||||
- 截至 2026-07-26 的最新标准库全量测试为 `Ran 216 tests`、`OK (skipped=6)`;
|
||||
- 截至 2026-07-26 的最新标准库全量测试为 `Ran 224 tests`、`OK (skipped=6)`;
|
||||
- JQData raw/factor/pre-close/limits/paused + 每日 PIT `is_st`/指数成员
|
||||
已连接到 semantic snapshot verifier、`snapshot-backtest` 和
|
||||
`snapshot-decision`,并拒绝把抓取当日未到 24:00 的日线标成完整收盘,
|
||||
@@ -152,7 +152,7 @@ A 股 Baseline 内核。它的新增得分必须在授权数据、真实平台
|
||||
T 日 Signal 与 T+1 broker fact 绑定、完整 artifact verifier;
|
||||
- Colab 默认无账号流程,以及显式可选的 JQData/Qlib cell。
|
||||
|
||||
最近一次 216-test suite、两条 Qlib fixture smoke 和 Tushare
|
||||
最近一次 224-test suite、两条 Qlib fixture smoke 和 Tushare
|
||||
managed-provider 技术 smoke 证明上述代码路径能执行,
|
||||
但仍明确不证明:
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
| Path | Intended use | Runtime / prerequisite | Current verified fact | Missing release evidence |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Local synthetic execution | 确定性事件回测、ledger/replay | Python ≥3.10,无账号 | 标准库套件最近记录为 216 tests、OK、6 skip;smoke + exact manifest verifier 可运行 | synthetic 不代表真实市场/收益 |
|
||||
| Local synthetic execution | 确定性事件回测、ledger/replay | Python ≥3.10,无账号 | 标准库套件最近记录为 224 tests、OK、6 skip;smoke + exact manifest verifier 可运行 | synthetic 不代表真实市场/收益 |
|
||||
| Local synthetic research | PIT/features/walk-forward/Ridge/risk/cost/target | Python ≥3.10,无账号 | research smoke + manifest verifier 可运行 | Ridge 未冻结部署,未做授权长样本 OOS |
|
||||
| JQData ingestion | 授权数据到 canonical immutable snapshot | `jqdatasdk==1.9.8` + 授权账号 | fake-provider 测试覆盖 raw/factor/pre-close/limits/paused/PIT `is_st`/membership、24:00 完整收盘约束和语义 verifier | 尚未登录真实账号保存快照、许可与 lineage |
|
||||
| Snapshot local backtest | PIT membership 的 provider-data 本地回测 | semantic verifier 通过的 snapshot | `snapshot-backtest` 复用 production decision path,输出普通 run manifest | 尚无授权真实 snapshot;仍是本地 fill model |
|
||||
|
||||
+2
-2
@@ -33,9 +33,9 @@
|
||||
"current_delivery": {
|
||||
"score": null,
|
||||
"status": "not_scored",
|
||||
"reason": "Quant OS now has executable local pipelines, real Qlib fixture smokes, a checksummed incomplete Tushare-to-Qlib technical smoke and one completed real JoinQuant hosted smoke, but it has not produced complete adjusted PIT data, the same-input JoinQuant comparison, real QMT, broker-shadow, continuous reconciliation or compliance evidence required to score Baseline 60.",
|
||||
"reason": "Quant OS 已有可执行本地流水线、真实 Qlib fixture smoke、带校验但不完整的 Tushare→Qlib 技术 smoke,以及一次完成的真实聚宽 hosted smoke;但仍缺完整复权 PIT 数据、同输入聚宽比较、真实 QMT、券商影子盘、连续对账与合规证据,因此不能计入 Baseline 60。",
|
||||
"verified_supporting_facts": [
|
||||
"The latest standard-library suite completed 216 tests with OK and 6 optional-runtime skips; the CPython 3.12 pyqlib environment completed the same 216 tests with the native Qlib smoke enabled and no skips.",
|
||||
"The latest standard-library suite completed 224 tests with OK and 6 optional-runtime skips; the CPython 3.12 pyqlib environment completed the same 224 tests with the native Qlib smoke enabled and no skips.",
|
||||
"The JQData adapter maps raw bars, adjustment factor, previous close, daily limits, pause state, PIT is_st and daily PIT index membership into a semantically revalidated snapshot; it rejects an end date that has not reached the provider's complete daily-bar boundary, and only fake-provider tests have been run so far.",
|
||||
"The local snapshot can drive snapshot-backtest and snapshot-decision with manifest verification; it freezes a cross-checked provider trading calendar, and a T-close signal can bind only to a broker observation in the unique next session's Asia/Shanghai [09:00,09:30) window without collapsing the two clocks.",
|
||||
"CPython 3.12 with pyqlib 0.9.7 completed a native momentum fixture smoke with 24 signals and saved audited portfolio-table hashes, bounds and recomputed metrics.",
|
||||
|
||||
@@ -26,6 +26,7 @@ PYTHONPATH=src:. "${python_bin}" tools/export_mock_parity.py \
|
||||
verify \
|
||||
artifacts/mock-parity/report.json
|
||||
PYTHONPATH=src:. "${python_bin}" tools/capability_probe.py
|
||||
"${python_bin}" tools/build_public_status.py --check
|
||||
"${python_bin}" tools/run_colab_notebook.py notebooks/quant_os_colab.ipynb
|
||||
"${python_bin}" tools/run_colab_notebook.py \
|
||||
notebooks/quant_os_colab.ipynb \
|
||||
|
||||
+2117
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,217 @@
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
PROJECT = Path(__file__).resolve().parents[1]
|
||||
BUILDER_PATH = PROJECT / "tools" / "build_public_status.py"
|
||||
OUTPUT_PATH = PROJECT / "status" / "status-data.js"
|
||||
INDEX_PATH = PROJECT / "status" / "index.html"
|
||||
PREFIX = "window.QUANT_OS_PUBLIC_STATUS = "
|
||||
|
||||
|
||||
def _load_builder():
|
||||
spec = importlib.util.spec_from_file_location("build_public_status", BUILDER_PATH)
|
||||
if spec is None or spec.loader is None:
|
||||
raise RuntimeError("cannot import public status builder")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def _read_payload(path=OUTPUT_PATH):
|
||||
content = path.read_text(encoding="utf-8")
|
||||
lines = content.splitlines()
|
||||
if not lines or not lines[0].startswith("// Generated by"):
|
||||
raise AssertionError("generated header is missing")
|
||||
assignment = "\n".join(lines[1:])
|
||||
if not assignment.startswith(PREFIX) or not assignment.endswith(";"):
|
||||
raise AssertionError("unexpected JavaScript wrapper")
|
||||
return json.loads(assignment[len(PREFIX) : -1])
|
||||
|
||||
|
||||
class PublicStatusTests(unittest.TestCase):
|
||||
def test_committed_status_data_has_no_drift(self):
|
||||
result = subprocess.run(
|
||||
[sys.executable, str(BUILDER_PATH), "--check"],
|
||||
cwd=PROJECT,
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
payload = _read_payload()
|
||||
self.assertEqual(payload["claim"]["status"], "NOT_BASELINE_60")
|
||||
self.assertFalse(payload["claim"]["releaseVerified"])
|
||||
self.assertFalse(payload["claim"]["liveReady"])
|
||||
self.assertEqual(payload["claim"]["gatesPassed"], 0)
|
||||
self.assertEqual(payload["claim"]["gatesTotal"], 10)
|
||||
self.assertTrue(all(gate["status"] == "not_passed" for gate in payload["gates"]))
|
||||
|
||||
def test_public_payload_is_deliberately_non_secret_and_non_local(self):
|
||||
payload = _read_payload()
|
||||
encoded = json.dumps(payload, ensure_ascii=False, sort_keys=True)
|
||||
self.assertNotRegex(
|
||||
encoded,
|
||||
re.compile(r"/(?:Users|home|root|private|var|tmp)/|[A-Za-z]:[\\/]"),
|
||||
)
|
||||
self.assertNotIn("RELEASE_VERIFIED", encoded)
|
||||
self.assertNotIn("LIVE_READY", encoded)
|
||||
lowered = encoded.lower()
|
||||
for marker in (
|
||||
"password",
|
||||
"passwd",
|
||||
"api_key",
|
||||
"private_key",
|
||||
"userdata_path",
|
||||
):
|
||||
self.assertNotIn(marker, lowered)
|
||||
for evidence in payload["evidence"]:
|
||||
self.assertFalse(Path(evidence["path"]).is_absolute())
|
||||
self.assertNotIn("..", Path(evidence["path"]).parts)
|
||||
|
||||
def test_static_page_has_fail_closed_fallback_and_no_local_paths(self):
|
||||
page = INDEX_PATH.read_text(encoding="utf-8")
|
||||
self.assertIn('src="./status-data.js"', page)
|
||||
self.assertGreaterEqual(page.count("STATUS DATA UNAVAILABLE"), 2)
|
||||
self.assertIn("NOT_BASELINE_60", page)
|
||||
self.assertIn("0 / 10", page)
|
||||
self.assertIn("validatePublicData", page)
|
||||
self.assertIn("platform.limitations", page)
|
||||
self.assertNotIn("platform.missingReleaseEvidence", page)
|
||||
self.assertNotRegex(
|
||||
page,
|
||||
re.compile(
|
||||
r"(?:(?<=^)|(?<=[\s\"'=:(]))"
|
||||
r"(?:/(?:Users|home|root|private|var|tmp)/|[A-Za-z]:[\\/])"
|
||||
),
|
||||
)
|
||||
self.assertNotIn("quant-os/artifacts", page)
|
||||
self.assertNotIn("quant-os/data", page)
|
||||
for tag in re.findall(r"<a\\b[^>]*target=\"_blank\"[^>]*>", page):
|
||||
self.assertIn('rel="noopener noreferrer"', tag)
|
||||
|
||||
def test_models_and_platforms_preserve_evidence_boundaries(self):
|
||||
payload = _read_payload()
|
||||
models = {model["id"]: model for model in payload["models"]}
|
||||
self.assertEqual(
|
||||
set(models),
|
||||
{"portable-momentum-v1", "ridge-challenger", "alpha158-lightgbm"},
|
||||
)
|
||||
self.assertFalse(models["portable-momentum-v1"]["releaseVerified"])
|
||||
self.assertFalse(models["ridge-challenger"]["investmentValueClaim"])
|
||||
self.assertFalse(models["alpha158-lightgbm"]["investmentValueClaim"])
|
||||
self.assertIn("synthetic", models["ridge-challenger"]["role"])
|
||||
self.assertIn("synthetic", models["alpha158-lightgbm"]["limitations"][0])
|
||||
|
||||
platforms = {platform["name"]: platform for platform in payload["platforms"]}
|
||||
self.assertEqual(platforms["JoinQuant hosted bundle"]["status"], "real_runtime")
|
||||
self.assertEqual(platforms["QMT built-in bundle"]["status"], "implemented")
|
||||
self.assertFalse(platforms["XtTrader read-only shadow"]["liveReady"])
|
||||
self.assertIn("fake", platforms["XtTrader read-only shadow"]["claim"].lower())
|
||||
|
||||
def test_freshness_uses_daily_bars_not_the_later_trade_calendar(self):
|
||||
payload = _read_payload()
|
||||
freshness = {item["id"]: item for item in payload["freshness"]}
|
||||
self.assertEqual(freshness["project-audit"]["value"], "2026-07-26")
|
||||
self.assertEqual(
|
||||
freshness["joinquant-hosted-market-window"]["marketDataThrough"],
|
||||
"2024-12-31",
|
||||
)
|
||||
|
||||
tushare = freshness["tushare-daily-market-data"]
|
||||
self.assertEqual(tushare["completedPartitions"], 35)
|
||||
self.assertEqual(tushare["plannedPartitions"], 428)
|
||||
self.assertAlmostEqual(tushare["completionRatio"], 35 / 428, places=6)
|
||||
self.assertEqual(tushare["marketDataThrough"], "1993-10-29")
|
||||
self.assertFalse(tushare["productionReady"])
|
||||
source = (PROJECT / "docs" / "TUSHARE_LOCAL_DATA.md").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
self.assertRegex(source, r"`trade_cal`.*2026")
|
||||
self.assertNotIn("2026", tushare["marketDataThrough"])
|
||||
self.assertIn("daily", tushare["semantics"])
|
||||
|
||||
self.assertEqual(freshness["jqdata-real-snapshot"]["value"], "N/A")
|
||||
self.assertEqual(freshness["jqdata-real-snapshot"]["status"], "blocked")
|
||||
self.assertIsNone(
|
||||
freshness["qmt-broker-real-runtime"]["lastRealRuntimeAt"]
|
||||
)
|
||||
self.assertEqual(
|
||||
freshness["qmt-broker-real-runtime"]["status"], "blocked"
|
||||
)
|
||||
|
||||
def test_explicit_timestamp_build_is_deterministic(self):
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
first = Path(temp_dir) / "first.js"
|
||||
second = Path(temp_dir) / "second.js"
|
||||
command = [
|
||||
sys.executable,
|
||||
str(BUILDER_PATH),
|
||||
"--generated-at",
|
||||
"2026-07-26T08:09:10+08:00",
|
||||
]
|
||||
subprocess.run(
|
||||
[*command, "--output", str(first)],
|
||||
cwd=PROJECT,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
subprocess.run(
|
||||
[*command, "--output", str(second)],
|
||||
cwd=PROJECT,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
self.assertEqual(first.read_bytes(), second.read_bytes())
|
||||
payload = _read_payload(first)
|
||||
self.assertEqual(payload["generatedAt"], "2026-07-26T00:09:10Z")
|
||||
self.assertEqual(payload["generatedAtSource"], "argument")
|
||||
|
||||
def test_source_date_epoch_controls_timestamp(self):
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
output = Path(temp_dir) / "epoch.js"
|
||||
environment = dict(os.environ)
|
||||
environment["SOURCE_DATE_EPOCH"] = "0"
|
||||
result = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
str(BUILDER_PATH),
|
||||
"--output",
|
||||
str(output),
|
||||
],
|
||||
cwd=PROJECT,
|
||||
env=environment,
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
payload = _read_payload(output)
|
||||
self.assertEqual(payload["generatedAt"], "1970-01-01T00:00:00Z")
|
||||
self.assertEqual(payload["generatedAtSource"], "source_date_epoch")
|
||||
|
||||
def test_builder_rejects_unearned_positive_claim(self):
|
||||
module = _load_builder()
|
||||
scorecard = {
|
||||
"claim_policy": {
|
||||
"baseline_threshold": 60,
|
||||
"current_claim": "BASELINE_60",
|
||||
},
|
||||
"current_delivery": {"score": 59},
|
||||
"gates": [{"id": "G1", "status": "not_passed"}],
|
||||
}
|
||||
with self.assertRaises(module.PublicStatusError):
|
||||
module._validate_claim(scorecard)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user