Python向量化回测中空头收益率计算错误修正
我持有某交易标的的DataFrame数据集,包含position_signal(持仓信号)和close(收盘价)两个字段,每一行对应一个固定时间步(分钟、小时等周期)的行情数据。我需要运行向量化回测,计算每个时间点的累计组合收益率,覆盖多头持仓、空头持仓、空仓三种状态,三种状态对应的position_signal取值分别为1、-1、0。
该问题看似实现简单,我已尝试过多种编写方式。目前网上有大量教程资料声称实现了正确的向量化回测逻辑,但这些方案普遍存在两类缺陷:要么错误设定持仓期间的收益累计规则(简单将各周期收益率连乘,不符合固定持仓的实际收益计算逻辑),要么空头收益率的计算逻辑错误。
这类教程的核心实现代码基本如下:
df['log_returns'] = np.log(df['close']) - np.log(df['close'].shift(1)) df['strategy_returns'] = df['position_signal'] * df['log_returns'] df['cumulative_ret'] = df['strategy_returns'].cumsum().apply(np.exp)
这套逻辑计算多头持仓收益时结果正确,但计算空头持仓收益时会出现明显偏差,核心错误在于其默认空头收益率是等规模多头收益率的相反数,该逻辑完全不符合实际交易的收益计算规则。
举一个简单的算例:假设回测中包含两笔交易,第一笔为多头持仓,入场价格1美元,出场价格2美元;第二笔为空头持仓,入场价格2美元,出场价格1美元。正确的收益计算结果为:第一笔多头交易收益率100%,第二笔空头交易收益率50%。若初始资金为100美元,第一笔交易后资金增长至200美元,第二笔交易后资金增长至300美元,最终累计收益率为300%。
但上述教程给出的代码可以正确计算多头收益,却会将第二笔空头交易的收益率误算为100%(直接取多头持仓-100%收益率的相反数),导致最终结果错误。
以下代码可复现该错误:需要说明的是,代码中在position_signal首次出现1/-1的下一个时间步才正式持仓,相当于对position_signal做了1位偏移以避免未来函数,该处理属于细节设定,不影响核心收益逻辑的正确性。
import pandas as pd import numpy as np df = pd.DataFrame([[0.0, 1.0], [0.0, 1.2], [0.0, 1.50], [0.0, 1], [1.0, 1.2], # 本时间步开盘入场做多,开盘价等于上一步收盘价1.0美元 [1.0, 1.3], [1.0, 2.0], # 本时间步收盘平仓,平仓价2.0美元 [0.0, 1.7], [0.0, 2], [-1.0, 1.798], # 本时间步开盘入场做空,开盘价等于上一步收盘价2.0美元 [-1.0, 0.50], [-1.0, 1.3], [-1.0, 1], # 本时间步收盘平仓,平仓价1.0美元 [0.0, 1.5]], columns=['position_signal','close']) df['log_returns'] = np.log(df.close) - np.log(df.close.shift(1)) df['strategy_returns'] = df.log_returns * df.position_signal df['cumulative_returns'] = df.strategy_returns.cumsum().apply(np.exp) print(df)
position_signal close log_returns strategy_returns cumulative_returns 0 0.0 1.000 NaN NaN NaN 1 0.0 1.200 0.182322 0.000000 1.000000 2 0.0 1.500 0.223144 0.000000 1.000000 3 0.0 1.000 -0.405465 -0.000000 1.000000 4 1.0 1.200 0.182322 0.182322 1.200000 5 1.0 1.300 0.080043 0.080043 1.300000 6 1.0 2.000 0.430783 0.430783 2.000000 7 0.0 1.700 -0.162519 -0.000000 2.000000 8 0.0 2.000 0.162519 0.000000 2.000000 9 -1.0 1.798 -0.106472 0.106472 2.224694 10 -1.0 0.500 -1.279822 1.279822 8.000000 11 -1.0 1.300 0.955511 -0.955511 3.076923 12 -1.0 1.000 -0.262364 0.262364 4.000000 13 0.0 1.500 0.405465 0.000000 4.000000
从输出结果可见,代码计算的最终累计收益为4.0(即400%),和正确结果3.0(300%)存在明显偏差。
其他方案的局限性
- 若忽略中间价格tick,仅保留开多、平多、开空、平空四个交易节点的行数据,通过持仓信号掩码计算单笔交易收益后再做
cumprod可以得到正确结果,但该方法无法输出持仓期间每个时间步的组合价值,无法满足逐时点净值观测的需求,因此不适用。 - 也有方案将多头和空头的损益分开计算,但该方法未考虑历史盈亏对后续交易购买力的影响(例如一笔多头交易亏损50%后,下一笔交易盈利50%并不能使净值回到初始水平),逻辑存在本质缺陷。
- 采用非向量化的逐行遍历方式实现回测虽然逻辑容易写对,但运行速度过慢,无法满足大规模回测的性能要求。
现寻求可正确计算逐时点多空组合收益的高效向量化实现方案。
内容的提问来源于stack exchange,提问作者Joe

