账户层:持仓、可用资金与那个必须写在明处的分红约定
5 只标的的分红贡献平均 96.6 个百分点——算两次,就是这个量级的错误
一只标的,同一段 16 年:
- 后复权累计 +97.12%
- 不复权累计 -56.21%
股价本身是跌的。全部回报来自分红。
这就是为什么账户层必须有一个写在明处的约定:价格用后复权 → 分红已隐含在价格里 → 账户不再单独入账现金分红。
如果你违反它——既用后复权价、又把分红当现金入账一次——同一笔钱会被算两次。实测 5 只标的,分红贡献平均 96.6 个百分点。
这不是小数点误差,是结论级错误。而且它不会报错,只会让你的回测好得很"合理"。
你的工资卡余额,已经是"税后 + 公司报销"之后的数字。
如果你在记账时,先记工资卡余额,再把报销单又记一遍收入——账面上你会变富,而银行卡不会。
后复权价格,就是那张已经算过分红的工资卡。
一、账户层单元测试:六条行为
账户要随时能回答三个问题:有多少现金、持有什么(其中多少今天买的)、现在清算值多少。
✓ 买入后现金减少 现金 89995.00
✓ 买入当日全部锁定(T+1) 持有 1000 / 可卖 0
✓ 持仓均价含费用 均价 10.0050
✓ 次日解锁 可卖 1000
✓ 清仓后持仓归零 持仓 0 / 成本 0.0
✓ 已实现盈亏 = (卖价-均价)×数量-费 1989.00(应为 1989.00)
6/6 条行为按预期工作
两个容易写错的细节:
- 持仓均价必须含买入费用(10.005 而不是 10.00),否则已实现盈亏会系统性偏高
- 清仓后成本必须归零,否则下一轮建仓的均价会被上一轮污染
二、盈亏分解:第三条对账路径
p4.6 用了两条路径。这里是第三条:
权益变化 = 已实现盈亏 + 未实现盈亏
| 标的 | 总盈亏 | 已实现 | 未实现 | 费用 | 残差 |
|---|---|---|---|---|---|
| sh.600000 | 585,944.13 | 506,941.91 | 79,002.22 | 159,485.27 | 0.0000 |
| sh.600009 | -146,995.31 | -167,350.13 | 20,354.82 | 130,285.14 | 0.0000 |
| sh.600010 | 195,514.68 | 180,866.90 | 14,647.79 | 161,592.56 | 0.0000 |
| sh.600011 | 463,745.49 | 449,317.56 | 14,427.93 | 157,542.59 | -0.0000 |
| sh.600015 | -89,008.26 | -126,221.88 | 37,213.62 | 108,524.03 | 0.0000 |
5/5 闭合。
顺带看一眼费用那一列:10.8 万到 16.2 万,而初始资金是 100 万。光是手续费就吃掉了 11%~16% 的本金——这与 p4.2 成本网格的结论一致。
三、分红口径:算两次到底多算多少
用 baostock 取同一批标的的两种口径真实价格(2010-01-04 ~ 2026-07-29):
| 标的 | 后复权累计% | 不复权累计% | 分红贡献(pp) | 占后复权收益 |
|---|---|---|---|---|
| sh.600000 | +97.12% | -56.21% | 153.33 | 157.9% |
| sh.600009 | +67.89% | +36.15% | 31.74 | 46.8% |
| sh.600010 | +37.91% | -51.97% | 89.87 | 237.1% |
| sh.600011 | +53.16% | -10.11% | 63.27 | 119.0% |
| sh.600015 | +102.54% | -42.26% | 144.80 | 141.2% |
平均分红贡献 96.60 个百分点,占后复权总收益的 140.4%。
"占比超过 100%"不是算错了。
它的意思是:不复权收益为负 —— 股价本身是跌的,全部回报来自分红,而且分红还得先填上股价的亏损。
5 只里有 4 只是这种情况。这些是银行、公用事业类的标的,长期股价不涨甚至阴跌,但持续分红。
如果你用不复权价格回测这类标的,你会得出"这只股票十六年亏了 56%"的结论——而实际上持有它的人是赚的。
这也解释了为什么 p1.3 要花整整一节讲复权:口径错了,方向都会反。
所以口径必须二选一,并写在明处
| 方案 | 价格 | 分红处理 |
|---|---|---|
| A(本课) | 后复权 | 不单独入账 |
| B | 不复权 | 分红当日入账现金 + 送转股当日增加股数 |
混用 = 同一笔钱算两次。而它不会报错——只会让你的回测多出 96 个百分点,而且看起来很合理。
🔧 动手做:查一查你(或任何人)的回测有没有"把钱算两次"(5 分钟)
本引擎的约定写在明处:价格用后复权 → 分红已隐含在价格里 → 账户不再单独入账现金分红。
这一节的分红对比要连 baostock 取原始价(联网),但那个坑你现在就能查:翻出任何一份回测代码(你自己的、GitHub 上的、教程里的),问两个问题——① 它用的是复权价吗?② 它有没有在账户里又单独加了一笔"现金分红"?
如果两个都是"是",它的收益被算了两次——而且净值曲线看着完全正常,不会报错。
想明白:这是回测最常见的静默错误之一。分红只能进一次:要么在价格里(后复权)、要么单独入账现金(不复权),混用 = 同一笔钱算两次。本节用真实数据量过——混用能让收益虚高几十个百分点。凡是不写明"价格口径 + 分红怎么处理"的回测,默认先怀疑它算重了。
四、现金曲线:钱到底在哪
以一只标的为例(3976 个交易日):
持仓天数 2956 (74.3%) | 空仓天数 1020 (25.7%)
现金占权益比例:持仓时中位 5.4%,空仓时中位 100.0%
最低现金 39,982 | 最高现金 1,955,781
目标仓位设 0.95,意味着持仓时约 5% 留作现金缓冲。
这个缓冲不是可有可无:
- 整手取整——买入向下取整会剩零头
- 最低佣金——每笔至少 5 元
- 次日开盘跳空——按昨收算出的股数,可能在开盘价下买不起
设成 1.00 的回测会频繁出现"资金不足"被拒。(别和 p4.1 那 131 笔混为一谈 —— 那一批是把本金从 100 万降到 5000 元逼出来的,与目标仓位无关;两者都会撞上「资金不足一手」, 但成因不同。)
📌 免责:本课为技术教学,标的为沪深300 成分股按代码序取样(非精选),所有回测为历史模拟,不构成投资建议。
四件事:
- 账户单元测试 6/6——两个易错点:持仓均价必须含买入费用、清仓后成本必须归零
- 盈亏分解 5/5 闭合(残差 0.0000)——这是 p4.6 之外的第三条对账路径;顺带看到费用吃掉了 11%~16% 的本金
- 分红口径:5 只标的分红贡献平均 96.60pp,其中 4 只的不复权收益为负——股价本身是跌的,全部回报来自分红。混用口径 = 同一笔钱算两次,而且不会报错
- 现金缓冲:目标仓位 0.95 留出的 5%,是给整手取整、最低佣金、开盘跳空用的——设成 1.00 会频繁被拒
下一节讲双路对账:回测里最该做、却几乎没人做的一件事——一条净值曲线,你凭什么信它本身算得对?用两条独立路径互相印证,5 只标的最大绝对差做到 0.000000;再注入三种 bug,看它能不能 3/3 全抓出来。
🔎 来源与核验· 4 条,点开核对
