Files
quant-os/notebooks/quant_os_colab.ipynb
T

245 lines
11 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Quant OS / quant60 BaselineColab 与本地可复现实验\n",
"\n",
"这份 notebook 不保存账号、密码或 token。先在下面设置可选运行开关;默认流程不需要账号。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"optional"
]
},
"outputs": [],
"source": [
"RUN_JQDATA = False\n",
"JQDATA_START = \"2024-01-02\"\n",
"JQDATA_AS_OF = \"2024-06-28\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 默认工程验证\n",
"\n",
"这份 notebook 不保存账号、密码或 token。它会定位已有源码,或在 Colab 的空运行时从 `git.gomars.fun` 克隆代码,然后运行与 CI 相同的测试、bundle、合成回测和证据校验。合成数据只验证工程语义,不证明投资收益。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from pathlib import Path\n",
"import json\n",
"import os\n",
"import shutil\n",
"import subprocess\n",
"import sys\n",
"import tempfile\n",
"\n",
"REPO_URL = \"https://git.gomars.fun/boat/quant-os.git\"\n",
"REPO_BRANCH = \"master\"\n",
"\n",
"def run(command, *, env=None):\n",
" print(\"+\", \" \".join(str(item) for item in command))\n",
" subprocess.run(command, check=True, env=env)\n",
"\n",
"cwd = Path.cwd().resolve()\n",
"if (cwd / \"pyproject.toml\").is_file() and (cwd / \"src\" / \"quant60\").is_dir():\n",
" project = cwd\n",
"elif (cwd / \"quant-os\" / \"pyproject.toml\").is_file():\n",
" project = cwd / \"quant-os\"\n",
"else:\n",
" checkout = Path(\"/content/quant-os\")\n",
" if not checkout.exists():\n",
" run([\"git\", \"clone\", \"--depth\", \"1\", \"--branch\", REPO_BRANCH, REPO_URL, str(checkout)])\n",
" project = checkout\n",
"\n",
"if not (project / \"pyproject.toml\").is_file():\n",
" raise RuntimeError(f\"Quant OS project not found at {project}\")\n",
"os.chdir(project)\n",
"cache_root = Path(tempfile.gettempdir()) / \"quant-os-notebook-cache\"\n",
"cache_root.mkdir(parents=True, exist_ok=True)\n",
"os.environ.setdefault(\"MPLCONFIGDIR\", str(cache_root / \"matplotlib\"))\n",
"os.environ.setdefault(\"XDG_CACHE_HOME\", str(cache_root / \"xdg\"))\n",
"print({\"project\": str(project), \"python\": sys.version})"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"pythonpath = os.pathsep.join([\"src\", \".\"])\n",
"child_env = dict(os.environ, PYTHONPATH=pythonpath)\n",
"smoke_dir = Path(\"artifacts/colab-smoke\")\n",
"research_dir = Path(\"artifacts/colab-research\")\n",
"for generated_dir in (smoke_dir, research_dir):\n",
" if generated_dir.is_symlink():\n",
" raise RuntimeError(f\"refusing to replace symlinked output: {generated_dir}\")\n",
" if generated_dir.exists():\n",
" shutil.rmtree(generated_dir)\n",
"run([sys.executable, \"-m\", \"unittest\", \"discover\", \"-s\", \"tests\", \"-p\", \"*.py\", \"-q\"], env=child_env)\n",
"run([sys.executable, \"tools/bundle_platforms.py\"])\n",
"run([sys.executable, \"-m\", \"quant60\", \"smoke\", \"--config\", \"configs/baseline.json\", \"--output\", str(smoke_dir)], env=child_env)\n",
"run([sys.executable, \"-m\", \"quant60\", \"verify-manifest\", str(smoke_dir / \"manifest.json\")], env=child_env)\n",
"run([sys.executable, \"-m\", \"quant60\", \"research-smoke\", \"--output\", str(research_dir)], env=child_env)\n",
"run([sys.executable, \"-m\", \"quant60\", \"verify-research-manifest\", str(research_dir / \"manifest.json\")], env=child_env)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"report = json.loads(Path(\"artifacts/colab-smoke/report.json\").read_text(encoding=\"utf-8\"))\n",
"summary = {key: report[key] for key in [\"run_id\", \"trading_days\", \"order_count\", \"fill_count\", \"total_return\", \"max_drawdown\", \"investment_value_claim\"]}\n",
"print(json.dumps(summary, ensure_ascii=False, indent=2))\n",
"assert summary[\"investment_value_claim\"] is False\n",
"assert summary[\"fill_count\"] > 0\n",
"research_report = json.loads(Path(\"artifacts/colab-research/report.json\").read_text(encoding=\"utf-8\"))\n",
"research_summary = {key: research_report[key] for key in [\"run_id\", \"sample_count\", \"fold_count\", \"mean_oos_rank_ic\", \"target_gross_weight\", \"investment_value_claim\"]}\n",
"print(json.dumps(research_summary, ensure_ascii=False, indent=2))\n",
"assert research_summary[\"investment_value_claim\"] is False\n",
"assert research_summary[\"fold_count\"] > 0"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"optional"
]
},
"outputs": [],
"source": [
"equity = json.loads(Path(\"artifacts/colab-smoke/equity.json\").read_text(encoding=\"utf-8\"))\n",
"try:\n",
" import matplotlib.pyplot as plt\n",
"except ImportError:\n",
" print(\"matplotlib is optional; equity artifact is still available\")\n",
"else:\n",
" plt.figure(figsize=(10, 4))\n",
" plt.plot([row[\"date\"] for row in equity], [row[\"equity\"] for row in equity])\n",
" plt.title(\"Quant60 synthetic engineering smoke (not an investment result)\")\n",
" plt.xticks(rotation=45)\n",
" plt.tight_layout()\n",
" chart_path = Path(\"artifacts/colab-smoke/equity.png\")\n",
" plt.savefig(chart_path, dpi=140)\n",
" plt.close()\n",
" print({\"equity_chart\": str(chart_path.resolve())})"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 可选:JQData 授权快照 → PIT 回测与决策\n",
"\n",
"把首个代码 cell 的 `RUN_JQDATA` 改为 `True` 后,账号和密码只通过无回显交互输入,不写入 cell、artifact 或 Git。`JQDATA_AS_OF` 必须是区间内的真实交易日。该路径生成 provider snapshot、同一 `portable-momentum-v1` 的本地历史回测和单日信号/目标,并逐层验证;它不是聚宽 hosted 回测。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"optional"
]
},
"outputs": [],
"source": [
"if RUN_JQDATA:\n",
" run([sys.executable, \"-m\", \"pip\", \"install\", \"-r\", \"requirements/jqdata.txt\"])\n",
" jq_snapshot = Path(\"artifacts/jqdata-csi500\")\n",
" jq_decision = Path(\"artifacts/jqdata-csi500-decision\")\n",
" jq_backtest = Path(\"artifacts/jqdata-csi500-backtest\")\n",
" run([sys.executable, \"tools/jqdata_snapshot.py\", \"--index\", \"000905.XSHG\", \"--start\", JQDATA_START, \"--end\", JQDATA_AS_OF, \"--output\", str(jq_snapshot)], env=child_env)\n",
" run([sys.executable, \"-m\", \"quant60\", \"snapshot-backtest\", \"--snapshot\", str(jq_snapshot / \"manifest.json\"), \"--config\", \"configs/baseline.json\", \"--output\", str(jq_backtest)], env=child_env)\n",
" run([sys.executable, \"-m\", \"quant60\", \"verify-manifest\", str(jq_backtest / \"manifest.json\")], env=child_env)\n",
" run([sys.executable, \"-m\", \"quant60\", \"snapshot-decision\", \"--snapshot\", str(jq_snapshot / \"manifest.json\"), \"--config\", \"configs/baseline.json\", \"--as-of\", JQDATA_AS_OF, \"--equity\", \"1000000\", \"--output\", str(jq_decision)], env=child_env)\n",
" run([sys.executable, \"-m\", \"quant60\", \"verify-snapshot-decision\", str(jq_decision / \"manifest.json\")], env=child_env)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 可选:Qlib 0.9.7 原生 smoke\n",
"\n",
"只在 Python 3.12 环境把下面的 `RUN_QLIB` 改为 `True`。安装和运行结果必须单独保存;未执行时不得写成已验证。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"tags": [
"optional"
]
},
"outputs": [],
"source": [
"RUN_QLIB = False\n",
"if RUN_QLIB:\n",
" if sys.version_info[:2] != (3, 12):\n",
" raise RuntimeError(\"Qlib adapter is pinned to CPython 3.12\")\n",
" run([sys.executable, \"-m\", \"pip\", \"install\", \"-r\", \"requirements/research-py312.txt\"])\n",
" fixture = Path(\"artifacts/qlib-tiny-provider\")\n",
" run([sys.executable, \"tools/build_qlib_tiny_fixture.py\", str(fixture), \"--days\", \"80\"])\n",
" run([sys.executable, \"-m\", \"platforms.qlib_runner\", \"--provider-uri\", str(fixture), \"--market\", \"csi300\", \"--benchmark\", \"SH000300\", \"--start\", \"2024-02-01\", \"--end\", \"2024-04-19\", \"--lookback\", \"20\", \"--topk\", \"1\", \"--n-drop\", \"1\", \"--rebalance\", \"weekly\", \"--output-json\", \"artifacts/qlib-native/result.json\"], env=child_env)\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"smoke_archive = shutil.make_archive(\"artifacts/quant-os-colab-smoke\", \"zip\", \"artifacts/colab-smoke\")\n",
"research_archive = shutil.make_archive(\"artifacts/quant-os-colab-research\", \"zip\", \"artifacts/colab-research\")\n",
"download_artifacts = [smoke_archive, research_archive]\n",
"if Path(\"artifacts/jqdata-csi500\").is_dir():\n",
" download_artifacts.append(shutil.make_archive(\"artifacts/quant-os-jqdata-snapshot\", \"zip\", \"artifacts/jqdata-csi500\"))\n",
"if Path(\"artifacts/jqdata-csi500-decision\").is_dir():\n",
" download_artifacts.append(shutil.make_archive(\"artifacts/quant-os-jqdata-decision\", \"zip\", \"artifacts/jqdata-csi500-decision\"))\n",
"if Path(\"artifacts/jqdata-csi500-backtest\").is_dir():\n",
" download_artifacts.append(shutil.make_archive(\"artifacts/quant-os-jqdata-backtest\", \"zip\", \"artifacts/jqdata-csi500-backtest\"))\n",
"if Path(\"artifacts/qlib-native\").is_dir():\n",
" download_artifacts.append(shutil.make_archive(\"artifacts/quant-os-qlib-native\", \"zip\", \"artifacts/qlib-native\"))\n",
"print({\"download_artifacts\": download_artifacts})"
]
}
],
"metadata": {
"colab": {
"name": "quant_os_colab.ipynb",
"provenance": []
},
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}