资产配置
把「我有多少钱、最多能亏多少」变成「买哪几只 ETF、各占多少」,并且用历史数据证明那个「最多能亏多少」的承诺站得住。
三条不能破的规矩
1. 不预测涨跌。 组合由风险约束决定,不由对后市的看法决定。搜到的市场资讯只用来决定建仓节奏和写风险提示,不用来调整权重。用户问「现在该买什么方向」时,如实说这个 skill 不做方向判断,然后给他被动方案。
2. 回撤守不住就直说。 allocate.py 抛 InfeasibleConstraint 时,原样把「最保守的组合历史回撤也有 X%」告诉用户,让他选放宽上限还是接受更低收益。绝不自己放宽约束凑一个组合出来。
3. 报告里的回撤要报 p95,不要只报实现值。 实现值是单次抽样,系统性乐观。正确说法是 「中位情形回撤 X%,二十分之一的概率超过 Y%」,不是「最大回撤 X%」。
回测窗口从 2014 年起,不含 2008 年。但自助在这里帮了忙:全仓沪深300 的自助 p95 是 -72.0%,而它 2008 年的真实回撤是 -72.3%——样本外那次崩盘的量级被恢复出来了。这说明 区块自助确实能外推一部分尾部,但它抓不到危机中相关性集体冲向 1 的情形。
流程
第 1 步:风险画像
必须问两组问题,不能只问一组。详见 references/risk-profiling.md。
- 意愿(心理):账户浮亏多少会让你睡不着 / 想割肉?
- 能力(财务):这笔钱多久不用?收入稳不稳?除这笔钱外有没有 6 个月的应急金?这笔钱占你总资产多大比例?
两者冲突时取低的那个,并在报告里写明按哪个来、为什么。一个说能扛 50% 但三年后要买房的人,实际上限远低于 50%。
同时问到:本金金额、投资期限、是否已有持仓。
第 2 步:确定资产菜单
默认核心四类,代码和理由见 references/asset-menu.md:
| 代码 | 名称 | 类别 | 建议上限 |
|---|---|---|---|
| 510300 | 沪深300ETF | equity | 50% |
| 510500 | 中证500ETF | equity | 30% |
| 510880 | 红利ETF | equity | 30% |
| 511010 | 国债ETF | bond | 80% |
| 518880 | 黄金ETF | gold | 20% |
| 513100 | 纳指ETF | overseas | 25% |
行业/主题 ETF 不进核心优化。 它们最早的(512880 证券、512660 军工)也是 2016-08 才上市,2015 年那波暴跌不在数据里,回撤约束覆盖不到。要配就单独走「卫星仓」,合计 ≤ 20%,并在报告里写明这部分不受回撤约束保护。
第 3 步:拉数据
python3 scripts/fetch_history.py \
--codes 510300,510500,510880,511010,518880,513100 \
--start 2014-01-01 --out data/core_prices.csv
纯标准库,不需要 venv。数据源是腾讯(免 key、不封 IP)。东财的历史接口在部分网络下被 DNS 劫持,所以不用它。
第 4 步:算权重
.venv/bin/python scripts/allocate.py \
--prices data/core_prices.csv \
--specs data/core_specs.json \
--max-dd 0.20
--max-dd 0.20 就是第 1 步问出来的那个数(取能力和意愿的低者)。
默认按自助 p95 口径判定,不是历史实现值。 历史上实现的那一个最大回撤是单条路径的
一次抽样,有效样本量为 1,拿它当上限系统性乐观——实测在这套资产上乐观约 6 个百分点。
所以 allocate.py 会跑平稳区块自助,用回撤分布的 95 分位来卡约束。代价是慢一些
(几秒到十几秒)。想要旧行为传 --risk-metric realized,但要知道你拿到的数字偏乐观。
输出 JSON,含 weights、metrics、bootstrap、robustness、holding_period、frontier。
必看 frontier。 它是各回撤档位上能拿到的最高年化,直接回答「多承受 10% 回撤能多赚多少」。实测这条曲线在 35-40% 处见顶后下降——多数情况下把上限放到 20% 以上收益增量很小,这个结论要主动告诉用户。
必看 robustness.breached_in_subperiod。 为 true 说明全窗口达标只是被某一段的大涨稀释了,某个子区间其实破了上限。这时必须在报告里明确警告。
期限 5 年以内的用户,必须加 --horizon-years N(N = 他的期限)。 最大回撤衡量的是
「中途最难看的时候有多难看」,适合能一直拿着的人。期限短的人真正面对的是到期那天的盈亏
——中途跌多深无所谓,到期是亏是赚才决定他能不能拿回本金。加了这个参数会多输出
holding_period(持有这么久的收益分布:最差、5 分位、中位、亏钱概率),报告里要放进去。
第 5 步:搜当前市况
用 WebSearch 查两件事:
- 主要宽基(沪深300、中证500)当前估值分位
- 近期宏观风险事件
只影响两个输出,不改权重:
- 建仓节奏:估值分位高 → 建议分 6-12 期建仓;分位低 → 可一次性建仓。
- 风险提示段落。
第 6 步:出报告 + 存档案
报告模板见 references/reporting.md。存档到 portfolio-profile.json。
档案含个人财务数据,绝不能进任何 git 仓库。 写档案前先确认目标目录不在版本控制下。
何时不适用
- 这笔钱一年内要用 → 先说清楚:一年不够从下跌里恢复,正常做法是货币基金或国债逆回购。
用户坚持要配的话,必须带
--horizon-years 1并把持有期收益分布放进报告。 - 用户想选个股 → 这个 skill 只做 ETF 大类配置
- 用户想知道现在该买什么方向 → 如实说不做方向判断
- 用户要短线交易或追热点 → 不适用,说明理由
- 本金太小(几千元)→ 场内 ETF 有最小 100 份的买入单位,六只标的分不开,建议先做两三只
参考文件
references/risk-profiling.md— 两维问卷、打分、取低规则references/asset-menu.md— ETF 清单,含各自的坑(QDII 溢价、流动性)references/reporting.md— 报告模板
免责
这是研究和规划工具,不是投资建议。历史回测不预示未来。每份报告结尾都要写这句。