跳转至

第 11 课 收益率、复利和净值曲线

价格变化需要先放回原来的本金中比较,多天结果还要沿着不断变化的财富逐期连接。

适合谁会做四则运算并能阅读价格表的读者
前置知识第 10 课,理解价格口径与相邻日期
建议用时80 分钟
本课成果手算单日收益、四日复利和一条净值曲线

同样上涨一元,变化程度并不相同

小岚同时观察两只虚构资产。甲从 10 元涨到 11 元,乙从 100 元涨到 101 元。两者都上涨 1 元,如果只比较价格差,看起来完全一样。可是购买一份甲原本只需要 10 元,这 1 元相当于起点的十分之一;购买一份乙需要 100 元,同样的 1 元只占百分之一。

先不用公式,按照“变化金额除以原来金额”分别计算。甲的变化金额是 11 减 10,得到 1 元,再用 1 除以起点 10,得到小数 0.1,也就是 10%。乙的变化金额也是 1 元,用它除以起点 100,得到 0.01,也就是 1%。起点被放进计算以后,两次变化才有了可比较的尺度。

变化金额相对于起点金额的比例叫作简单收益率。前面的中文关系可以压缩成下面的公式。

\[ r_t=\frac{P_t-P_{t-1}}{P_{t-1}} \]

符号 \(P\) 表示价格,下标表示它属于哪个时点。\(P_{t-1}\) 是前一个时点的价格,\(P_t\) 是当前价格,\(r_t\) 表示从前一个时点走到当前时点的收益率。字母 \(t\) 只承担日期编号的工作,没有额外的数学含义。

把甲的数字放进去,分子是 11 减 10,分母是 10,结果为 0.1。收益率用小数参加程序计算,用百分数展示时再乘以 100。代码中的 0.1 表示 10%,代码中的 10 若被误当成收益率,则代表 1000%,两者相差一百倍。

上涨与下跌都使用最初那个价格作分母

价格从 25 元跌到 24 元时,变化金额为负 1 元。用负 1 除以前一个价格 25,得到负 0.04,也就是负 4%。负号表示财富变化方向向下,绝对值 4% 表示跌幅相对于起点的大小。

价格从 0 元开始、价格可以为负数的特殊资产,以及期货保证金收益,都需要更细的定义。当前股票教学数据保持正价格,简单收益率可以直接使用。遇到不同资产时,研究报告应说明分母代表价格、投入资金还是保证金,不能把同一公式机械套用。

为什么先涨十个百分点再跌十个百分点会亏钱

假设账户从 100 元开始。第一天上涨 10%,增加 10 元,日末变成 110 元。第二天下跌 10% 时,跌幅以新的 110 元为起点,减少 11 元,最终只剩 99 元。两个百分数一正一负,却作用在不同的金额上,所以不会互相抵消。

第一天结束 100 × 1.10 = 110 元
第二天结束 110 × 0.90 = 99 元
两天结果  99 ÷ 100 - 1 = -1%

一次上涨 10% 会把财富乘以 1.10,一次下跌 10% 会把财富乘以 0.90。多天结果要把这些变化倍数依次相乘,这就是复利。复利表示每一期都在上一期更新后的财富上继续变化。

如果用 \(W\) 表示财富,前一天财富写作 \(W_{t-1}\),当天收益率写作 \(r_t\),日末财富就可以写成下面的公式。

\[ W_t=W_{t-1}(1+r_t) \]

公式中的 \(1+r_t\) 是当天财富保留下来的倍数。收益为 2% 时,它等于 1.02;收益为负 20% 时,它等于 0.80。把每一天的倍数连续相乘,就得到从起点到当前的财富变化。

从四天收益手算一条净值曲线

不同账户的初始金额可能不同,为了只比较变化过程,研究报告常把起点统一设为 1。这种以共同起点表示的财富序列叫作净值。起点设为 100 或 100000 元也能画出相同形状,设为 1 更容易把净值 1.05 直接理解为相对起点增加 5%。

假设策略接下来三天的收益依次是 2%、负 1% 和 3%。先把百分数写成小数,再从 1 开始逐日相乘。

日期 当日收益率 当日变化倍数 日末净值 手算过程
起点 1.000000 统一比较起点
周一 2% 1.02 1.020000 1.000000 × 1.02
周二 -1% 0.99 1.009800 1.020000 × 0.99
周三 3% 1.03 1.040094 1.009800 × 1.03

周三结束净值约为 1.0401,表示相对于起点累计增加约 4.01%。把 2%、负 1% 和 3% 简单相加会得到 4%,差异在三天里很小,时间变长、波动变大以后会不断积累。累计收益率可以用最终净值除以起始净值再减 1 得到;本例起始净值为 1,所以直接用 1.040094 减 1 即可。

代码只把刚才的连乘交给 pandas

下面的代码使用与手算完全相同的三个输入。任务是先把每个收益率加 1,得到当天变化倍数,再按日期进行累计乘法。运行后应该依次看到 1.020000、1.009800 和 1.040094,与表格三行日末净值一致。

import pandas as pd

daily_return = pd.Series([0.02, -0.01, 0.03])
daily_factor = 1 + daily_return
wealth = daily_factor.cumprod()

print(wealth)
0    1.020000
1    1.009800
2    1.040094
dtype: float64

pd.Series 把三个按日期排列的收益率保存成一列。1 + daily_return 会逐项计算 1.02、0.99 与 1.03。cumprod 是累计乘积的缩写,它先保留第一个倍数,再计算前两个的乘积,最后计算全部三个的乘积。代码没有使用新的金融规则,只是稳定执行手算中的重复步骤。

若输出与手算不同,先打印 daily_factor,查看百分数是否正确写成小数;再确认日期顺序是否颠倒,缺失日期是否被悄悄删除。把计算拆成两行,正是为了让中间步骤能够被看见。

市场收益还要经过持仓才能变成策略收益

资产价格从今天到明天上涨 2%,只能说明市场收益为 2%。策略是否得到这段变化,要看收益开始以前账户持有多少。若今天收盘后持仓比例为 1,账户完整承受明天价格变化;持仓比例为 0,账户保持空仓;持仓比例为 0.5,忽略费用时只承受一半变化。

持仓比例表示有多少账户权益暴露在该资产上。教学中可以先把它理解为 0 到 1 之间的数,后面的做空和杠杆会让它出现负数或大于 1。最简单的单资产策略毛收益可以写成前一期持仓比例乘以本期市场收益。

\[ r^{\text{strategy}}_t=w_{t-1}r^{\text{market}}_t \]

\(w_{t-1}\) 是本期收益开始以前已经确定的持仓比例,\(r^{\text{market}}_t\) 是资产在本期的市场收益,左侧是策略因此获得的毛收益。持仓使用前一期下标非常重要。如果把本期结束后才计算出的信号与本期收益直接相乘,程序就可能让尚未出现的判断参与已经发生的价格变化。

假设周一收盘后策略持仓为 1,周二市场下跌 1%,策略周二毛收益也是负 1%。若周一收盘后持仓为 0,周二的市场变化不会进入策略账本。第 14 课会用逐日账本把信号、成交和持仓连接起来。

毛收益、成本和净收益要分开保存

毛收益表示尚未扣除交易成本的策略结果,净收益则是成本进入账本后的结果。把两者分开保存,可以看出策略优势来自市场变化,还是被频繁交易消耗。

假设账户用 10000 元买入,资产随后上涨 2%,毛盈利为 200 元。买卖佣金、税费与价差影响合计占初始资金 0.2%,成本为 20 元,净盈利便是 180 元,对应净收益率约 1.8%。若实际成交价格比参考价格再差 0.1%,净收益还会继续下降。

交易成本通常与持仓变化有关。账户一直持有而没有交易时,价格仍会产生收益,却不应每天重复收取买卖费用。程序因此常先计算相邻两日持仓差,再根据换手金额扣费。费用规则会在第 14 课详细展开,此处先保留“市场收益、持仓影响、交易成本”三层结构。

分红与拆股会改变收益口径

某只股票从 10 元除息后变成 9.80 元,同时每股向持有人支付 0.20 元现金。单看价格,收益率是负 2%;把现金收入加入以后,持有人财富接近没有变化。只计算价格变化得到的是价格收益,同时考虑现金分红等收入得到的是总收益。

拆股也会改变名义价格和持有数量。一股拆成两股时,每股价格可能大约减半,数量同时翻倍,账户总价值不会因为拆分自动减半。第 10 课介绍的复权序列把这些公司行动调整到连续口径,研究者仍要说明收益是否包含分红、采用哪种复权方式,以及订单使用哪一列真实价格。

比较策略与基准时,两边口径必须一致。含分红的策略总收益与不含分红的价格指数直接相比,会把口径差异误认为策略能力。币种、时间区间和交易日边界也需要统一。

净值曲线还要能回到账户账本

收益率便于计算和比较,现金账本则解释钱从哪里来。带交易的回测最好同时记录现金、持仓数量、成交价格、资产市值、费用和总权益。总权益等于现金加上持仓市值,每期损益可以拆成价格变动损益、交易费用、融资费用与现金收入。

假设初始现金 10000 元,买入资产后剩余现金 4000 元,持仓市值 6200 元,又扣除 5 元费用,总权益就是 4000 加 6200 再减去尚未扣除的 5 元。账本必须明确费用何时已经进入现金,避免同一笔费用减两次。

收益率累计得到的净值,与总权益除以初始权益得到的净值应当互相吻合。两者不一致时,常见原因包括成交日错位、费用重复、分红遗漏、持仓数量更新顺序错误或使用了不同价格口径。双重核对比只看最终曲线更容易定位第一天出错的位置。

年化数字回答的是换算问题

两个策略分别运行六个月和五年,只比较累计收益并不公平。年化会把一段观察期的增长换算成“一年尺度下相当于怎样的速度”,方便不同长度样本进行描述。它是一种数学换算,不会给短样本补上缺少的市场经历。

假设净值从 1 变成 \(W_T\),样本共有 \(N\) 个交易日,一年按 \(D\) 个交易日计算。若假定观察到的复合速度能够持续,年化收益可以写成下面的关系。

\[ r_{\text{annual}}=W_T^{D/N}-1 \]

中国股票和美国股票研究常近似使用一年 242 到 252 个交易日,具体数字要根据市场与样本说明。十个交易日赚 2% 后直接年化,会得到很高的数值,因为公式假定相似速度能重复许多次。十天数据没有提供这种稳定性的证据,所以年化结果必须与样本起止日期、交易日数量和市场环境一起展示。

阅读论文时可能遇到对数收益

本书的交易账本主要使用简单收益,它能直接回答本金增加或减少多少。统计研究中还会看到对数收益,也叫连续复合收益。它把相邻价格比取自然对数。

\[ g_t=\ln\left(\frac{P_t}{P_{t-1}}\right) \]

对数收益在时间上可以直接相加,这对某些统计推导很方便。价格变化较小时,它与简单收益很接近;波动变大以后,两者差异会扩大。对数收益不能直接解释成账户的实际百分比变化,也不适用于价格为零或负数的情形。初学回测继续使用简单收益即可,看到论文中的 log return 时再知道作者采用了另一种表达方式。

用互动实验观察复利与路径

收益与回撤练习允许你修改每天价格,并实时查看单日收益、累计净值和回撤。先让价格从 100 跌到 80 再回到 100,观察“回到原价”与“从低点上涨 25%”为什么可以同时成立。

打开收益与回撤练习

容易弄错的地方

容易混淆的做法 问题在哪里 应怎样检查
用上涨金额比较不同资产 忽略了各自起点大小 变化金额除以前一期价格
把多日收益率直接相加 每天变化作用在更新后的财富上 连乘每期的 1 + 收益率
用当天结束信号乘当天收益 判断可能在收益结束后才形成 使用收益开始前已经确定的持仓
把毛收益写成最终收益 费用、滑点和现金收入尚未进入 毛收益与成本分列保存
用十天结果强调高年化 换算假定增长速度能够反复持续 同时报告样本长度与换算方法

一分钟自测

100 元先下跌 20%,随后上涨 20%,最终会回到 100 元吗?

查看答案 不会。下跌后剩 80 元,第二次上涨以 80 元为起点,只增加 16 元,最终为 96 元。要从 80 元回到 100 元,需要上涨 20 除以 80,也就是 25%。

本课术语

词语 先这样理解
简单收益率 变化金额相对于前一期金额的比例
复利 每一期在更新后的财富基础上继续变化
净值 把共同起点设为 1 后的财富序列
累计收益 从起点到当前的总变化比例
持仓比例 账户权益中有多少承受某项资产变化
毛收益 尚未扣除交易成本的策略收益
净收益 扣除费用等成本后的策略收益
年化 把观察到的变化速度换算到一年尺度

课后练习

1 计算单日收益

价格从 25 元变为 24 元,简单收益率是多少?

提示先求负 1 元的变化,再除以前一期价格 25 元。
答案负 1 除以 25 得到负 0.04,也就是负 4%。

2 计算两期复利

净值从 1 开始,先上涨 5%,随后再上涨 5%,最终净值与累计收益是多少?

提示把 1.05 连续乘两次。
答案最终净值为 1.1025,累计收益为 10.25%,高于简单相加得到的 10%。

3 寻找时间错误

程序用周二收盘后生成的持仓比例乘以周一收盘到周二收盘的收益。哪里不合理?

提示周一到周二的变化开始时,周二收盘信号还存在吗?
答案不存在。周二收盘后才能形成的持仓不能获得此前已经发生的收益,应使用收益开始前已确定的持仓。

4 动手核对

把代码中的第二天收益从负 1% 改成负 10%,先手算三天净值,再运行代码比较。

提示依次计算 1 乘 1.02、再乘 0.90、最后乘 1.03。
答案最终净值约为 0.94554,累计收益约为负 5.446%。程序结果应与手算一致。

5 开放思考

为什么报告应同时展示净值曲线、累计收益和现金账本?

评价要点累计收益概括起点与终点,净值曲线保留变化路径,现金账本解释成交、持仓、费用和收入怎样形成结果。三者能够互相核对,回答的问题也不同。
离开本页前
两个价格先变成一段简单收益,多段收益再通过连乘形成净值。市场当天涨了多少,还要乘上前一期已经持有的仓位,并扣掉调仓产生的成本,才会成为账户收益。把这三层关系分开,后面讨论风险时就不会把市场曲线误当成策略账本。