因子投资-实验01-从价格到收益率
给定一列按交易日排序的价格,怎样得到简单收益、对数收益、累计净值和年化波动率?这个实验只用 6 个确定性价格,不依赖网络或数据库,目的是把第 01、02 讲的公式变成一条最小可审计的数据管道。
问题
给定一列按交易日排序的价格,怎样得到简单收益、对数收益、累计净值和年化波动率?这个实验只用 6 个确定性价格,不依赖网络或数据库,目的是把第 01、02 讲的公式变成一条最小可审计的数据管道。
完成后你应当能解释每一行为什么出现缺失值、为什么最终净值是 1.06、为什么简单收益与对数收益不能逐项相等,以及为什么 5 个日收益估出的年化波动率几乎没有决策价值。
输入数据
假设价格已经按日期升序、口径一致并代表总回报价格:
| date | price |
|---|---|
| 2026-01-05 | 100.0 |
| 2026-01-06 | 102.0 |
| 2026-01-07 | 101.0 |
| 2026-01-08 | 104.0 |
| 2026-01-09 | 103.0 |
| 2026-01-12 | 106.0 |
第一行没有前一期价格,所以一期收益必须是缺失,而不是 0。只有在计算“从 1 开始的净值”时,才把这一行的增长因子设为 1。
逐步实现
第一步:建立有日期索引的表
1 | |
日期不是装饰。真实研究必须先排序、去重、校验交易日,再计算相邻变化;否则 pct_change 会忠实地计算错误顺序。
第二步:计算两类收益
简单收益直接用当前价除以前价减一;对数收益用价格对数的一阶差分:
1 | |
对每个非缺失观测,都应满足 exp(log_return)-1=simple。这个恒等式是检查收益实现是否一致的好断言。
第三步:累计净值与年化波动
1 | |
净值使用简单收益连乘。日波动乘根号 252 只是在日收益独立、分布相对稳定时的尺度近似;本实验只有 5 个收益观测,数值只用于验证公式。
完整代码
复制下面代码即可独立运行。完整程序不读取或写入本地文件。
1 | |
预期输出
核心数值应为:
| date | simple | log_return | wealth |
|---|---|---|---|
| 2026-01-05 | 缺失 | 缺失 | 1.00 |
| 2026-01-06 | 0.020000 | 0.019803 | 1.02 |
| 2026-01-07 | -0.009804 | -0.009852 | 1.01 |
| 2026-01-08 | 0.029703 | 0.029270 | 1.04 |
| 2026-01-09 | -0.009615 | -0.009662 | 1.03 |
| 2026-01-12 | 0.029126 | 0.028710 | 1.06 |
年化波动率约为 0.318804,即 31.88%。它很高的一部分原因是样本只有 5 个日收益;不要把教学输出写成对未来波动的估计。
断言
实验用四类断言保护研究口径:
- 财富恒等式: 最终价格 106 相对初始 100,因此最终净值必须为 1.06。
- 收益变换恒等式: 每一期 exp(r)-1 必须等于简单收益 R。
- 时间顺序: 日期索引必须单调递增。
- 唯一性: 每个日期只能出现一次;真实个股面板还应断言 date、asset 联合唯一。
断言不是为了证明研究结论,而是尽早阻止数据管道静默地产生错误结果。真实项目还应检查价格为正、复权因子可用、异常跳变对应公司行动,以及股票代码没有被错误复用。
结果解释
价格从 100 到 106,所以累计总回报是 6%。中间有两次下跌,净值路径不是直线。简单收益与对数收益在幅度小时接近,但正收益时对数值略小,负收益时对数值更负;经过 exp(r)-1 后两者完全一致。
第一行收益保留缺失,是因为“没有上期数据”和“收益为零”具有不同含义。计算净值时将第一行缺失填 0,只是人为规定初始增长因子为 1,不应反向污染用于估计均值、波动或回归的收益样本。
年化波动使用样本标准差和根号 252。它没有考虑自相关、周末跨期、涨跌停或条件波动,也没有置信区间。正确的结论是“代码按这个近似得到 31.88%”,而不是“资产真实年风险为 31.88%”。
常见故障
- pct_change 警告或隐式填充: 显式指定 fill_method=None,避免缺失价格被自动前向填充后制造零收益。
- 日期未排序: 先排序再计算;排序后检查是否有重复日期,而不是让后出现的行覆盖前一行。
- 复权与分红重复: 若 price 已是总回报复权价,不再单独加入分红。
- 对数价格非正: 正常股价应大于零;出现零或负数通常是缺失编码、单位或数据错误,应停止而非强行取对数。
- 首行填零过早: 只在净值连乘时填零;统计估计必须丢弃没有定义的第一期收益。
- 根号年化滥用: 交易日数量、收益自相关和样本长度需要单独报告。
- 浮点精度: 不用 simple == exact 做严格相等判断,应使用 np.isclose 或 np.allclose。
练习
- 把最后价格改为 95,重新计算累计收益、最大回撤和年化波动。
- 在输入中复制一个日期,编写断言让程序立刻失败。
- 插入一个缺失价格,比较 fill_method=None 与先前向填充的结果,并解释哪一步可以用于持仓估值、哪一步不能代表成交。
- 新增一列每股现金分红,使用未复权价格手工构造总回报;再与供应商复权价收益交叉核对。
- 将频率改为月度,把年化因子从 252 改为 12;解释为什么这只是单位变换而非信息增加。
完成练习后,把数据表扩展为 date、asset、price 三列的长表。后续实验都将沿用“日期与资产联合唯一、信号先于未来收益”的结构。