三体韭菜策略
一套「绝对收益」型大类资产轮动量化策略。核心思想:用比值因子衡量不同资产之间的相对强弱(比价),越弱的资产越买(左侧均值回归),形成"防御资产打底 + 进攻资产择时"的组合。
四个版本(思路相通,因子逐级叠加)
| 版本 | 因子 | 资产(轮动池) |
|---|---|---|
| v1 | BOD | 固收 / 银行 |
| v2 | BOD + AOB | 固收 / 银行 / 上证 |
| v3 | BOD + AOB + SOA | 固收 / 银行 / 上证 / 券商 |
| v4 | BOD + AOB + SOA + COA | 固收 / 银行 / 上证 / 券商 / 创业板 |
因子定义(无量纲比值):
BOD = 银行 / 固收— 决定 固收 ↔ 银行 的跷跷板AOB = 上证 / 银行— 决定是否叠加上证进攻仓SOA = 券商指数 / 上证综指— 决定是否叠加券商进攻仓COA = 创业板 / 上证— 决定是否叠加创业板进攻仓
归一化:每个因子用 1500 日滚动 IQR 分位(5%/95%)×0.95/1.05,clip 到 [0,1]。
仓位:链条式叠加(每个因子在上一档基础上加进攻仓),T-1 仓位配 T 日收益,杜绝未来函数。
目录结构
santijiucai/
├── SKILL.md # 本文件
├── assets/
│ └── echarts.min.js # 看板图表库(内嵌,离线可用)
└── scripts/
├── tencent_data.py # 腾讯财富数据源(K线分页拉取 + 实时,纯标准库)
├── data_loader.py # 数据加载(全量/增量拉取 + 结算复核 + 缓存)
├── strategy.py # 统一回测引擎(v1~v4 + ROC 保护层)
└── build_dashboard.py # HTML 结果看板生成器(主要产物,含实时行情)
运行时状态不在技能目录内。 缓存与回测产物写在:
~/.workbuddy/skill-data/santijiucai/
├── prices.csv # 行情缓存(自动增量更新)
├── roc_state_archive.json # 每日 ROC 判定存档(用于检测结论漂移)
└── output/ # strategy.py 的回测产物
> ⚠️ 不要把运行时文件写回技能目录。 技能目录会被整体打包发布(发布工具的排除清单
> 只有 .git/.idea/.vscode/node_modules/__pycache__,.csv 不在其中)。缓存留在技能
> 目录里,会把「本机某一时刻的行情快照」连同本地痕迹一起发给所有用户,新用户拿到的
> 是陈旧数据而非从源头拉取。历史版本曾因此出过事故(见「数据可信度」一节)。
> 可用环境变量 SANTIJIUCAI_DATA_DIR 覆盖运行时目录位置。
用法
1. 数据(自动,无需手动维护)
价格数据自动从腾讯财富拉取,起始日期 2017-08-03,缓存在运行时目录,每次运行自动增量更新到最新交易日。首次运行需全量拉取(约 10 秒),之后仅增量(约 1 秒)。无需任何 token,无需手动更新 Excel。
2. 跑回测
python scripts/strategy.py --list # 列出所有版本
python scripts/strategy.py --version v2 # 指定版本
python scripts/strategy.py # 默认 v4
3. 生成 HTML 结果看板(主要产物)
用户问"三体韭菜策略最近表现如何?"时,跑这个生成自包含 HTML 看板(内嵌 ECharts,离线可开):
python scripts/build_dashboard.py # 数据自动拉取
python scripts/build_dashboard.py --out 看板.html # 指定输出路径
看板内容:三个选项卡(四因子 / 两因子 / 两因子+ROC 优化),含 KPI 指标卡、净值、超额收益、净值回撤、超额回撤、仓位堆叠、因子图、ROC(20,10) 信号图;顶部「刷新实时行情」按钮可拉到最新盘中价(生成时已并入一份实时,普通浏览器双击打开可点按钮实时刷新;WorkBuddy 内置预览面板受安全策略限制,点按钮可能不生效)。
数据源约定
- 起始日期固定为 2017-08-03(ETF 口径起点),写死在
data_loader.py的START_DATE。 - 7 个标的全为公开数据,从腾讯财富自动拉取——
- ETF(前复权 qfq):银行 512800、上证 510210、券商 512000、创业板 159915
- 指数(不复权):国债 000012、上证综指 000001、证券公司 399975
- 停牌日自动前值填充。
- 行情来源:腾讯财富
web.ifzq.gtimg.cn(K 线,支持前/后复权,分页拉取)+qt.gtimg.cn(实时)。
> 复权说明:ETF 分红/拆分会让「不复权」价在除息日跳空(如 512800 银行ETF 在 2025-07-07 拆分后不复权单日 -49.69%,实为 +0.56%)。本 skill 对 ETF 一律用前复权 qfq,正确纳入分红。
运行环境
- Python 3.8+;
strategy.py/data_loader.py需pandasnumpy;tencent_data.py仅标准库。 - pandas 版本兼容:
strategy.py的分年度统计用groupby(年份)实现(而非resample('A')),在 pandas 2.x 与 3.x 下均可运行(已实测 2.0.3 / 3.0.5)。 - 无需 tushare、无需任何 token,需可访问腾讯行情接口的网络。
数据可信度(重要:读 ROC 信号前先看「稳健度」)
ROC(20,10) 是趋势保护层,但它频繁在粘连带附近摆动,只看「开仓/空仓」四个字会得出
脆弱结论。实测近 40 个交易日有 24 天处于擦线区(|diff| < 1.5 倍带宽),0 天超过 3 倍带宽。
判读规则 —— 先算 稳健度 = |diff| ÷ 带宽:
| 稳健度 | 含义 | 怎么用 |
|---|---|---|
| ≥ 3 倍 | 强确认 | 可作为趋势依据 |
| 1.5 ~ 3 倍 | 中性 | 建议等次日确认 |
| < 1.5 倍 | 擦线弱信号 | 不要据此加减趋势仓;收盘价的 0.1% 级别修订就能翻转结论 |
收盘价的结算时滞会改变判定。 上交所 ETF(512800/510210/512000)的收盘价由收盘集合竞价
产生,15:00 后还要等撮合与回报。15:10 抓到的可能是「最新成交价」而非最终结算价,偏差
0.1~0.5%;而 ROC 当日 diff 只有 ±0.3% 量级,这点偏差足以跨过粘连带。
> 真实事故(2026-11-02 → 11-04 修复):15:10 抓到的 512800 = 0.890(未结算),结算真值
> 0.885。ROC diff 因此从 +0.260%(带内 → 空仓)变成 +0.426%(越带 → 开仓)——同一个交易日
> 的判定被追溯改写,同一份数据只因抓取时点不同得出相反结论。根因是增量抓取只追加
> 「严格晚于末日」的行,污染行一旦写入就再也不会被修正。
>
> 现已修复:data_loader._reconcile_recent_rows() 每次加载都拿 K 线真值复核最近 5 行
> (不是只查末行——污染行会在下次追加后退居中间,只用末行会漏掉),不一致即以结算价覆盖。
> build_dashboard._audit_state_drift() 另把每日判定存档,同一天结论发生变化就打印告警。
给调用者的建议:报告 ROC 状态时,务必同时说明 diff 值、带宽、稳健度倍数。若稳健度 < 1.5,
明确写「擦线弱信号,不宜据此决策」,不要只抛一个「开仓做多」。
注意事项
- 本策略仅供研究交流,不构成投资建议。
- 首次运行需联网全量拉取历史;运行时缓存可随时删除(下次运行会自动重建)。
- 不要修改
start参数以外的数据源口径;ETF 必须保持前复权(qfq)。