在Python中计算复合收益系列
大家好,我有两组数据:每天的原始股票价格收益(可能是正数也可能是负数)和交易信号(买入=1,卖出=-1,不交易=0)。
原始价格收益的计算方法很简单,就是今天的价格和昨天的价格的对数比:
log(p_today / p_yesterday)
举个例子:
raw_return_series = [ 0.0063 -0.0031 0.0024 ..., -0.0221 0.0097 -0.0015]
交易信号系列看起来是这样的:
signal_series = [-1. 0. -1. -1. 0. 0. -1. 0. 0. 0.]
根据交易信号来计算每日收益:
daily_returns = [raw_return_series[i] * signal_series[i+1] for i in range(0, len(signal_series)-1)]
这些每日收益可能会是这样的:
[0.0, 0.00316, -0.0024, 0.0, 0.0, 0.0023, 0.0, 0.0, 0.0] # results in daily_returns; notice the 0s
我需要用每日收益系列来计算一个复合收益系列。不过,由于每日收益系列中有0值,我需要把最后一个非零的复合收益“传递”到下一个非零的复合收益上。
比如,我是这样计算复合收益的(注意我是在“倒着”计算):
compound_returns = [(((1 + compounded[i + 1]) * (1 + daily_returns[i])) - 1) for i in range(len(compounded) - 2, -1, -1)]
最后得到的列表是:
[0.0, 0.0, 0.0023, 0.0, 0.0, -0.0024, 0.0031, 0.0] # (notice the 0s)
我的目标是把最后一个非零的收益传递过来,以便累积这些复合收益。也就是说,因为索引i的收益依赖于索引i+1的收益,所以索引i+1的收益应该是非零的。每当列表推导遇到每日收益系列中的零时,它基本上就会重新开始计算。
3 个回答
假设我有一个数据矩阵,里面包含了收盘价格、一些指标值,还有一个交易信号,像这样:
>>> data_matrix
close dvi signal
2008-01-02 00:00:00 144.9 0.6504 -1
2008-01-03 00:00:00 144.9 0.6603 -1
2008-01-04 00:00:00 141.3 0.7528 -1
2008-01-07 00:00:00 141.2 0.8226 -1
2008-01-08 00:00:00 138.9 0.8548 -1
2008-01-09 00:00:00 140.4 0.8552 -1
2008-01-10 00:00:00 141.3 0.846 -1
2008-01-11 00:00:00 140.2 0.7988 -1
2008-01-14 00:00:00 141.3 0.6151 -1
2008-01-15 00:00:00 138.2 0.3714 1
我用这个信号来创建一个基于交易信号的收益数据矩阵:
>>> get_indicator_returns()
indicator_returns
2008-01-02 00:00:00 NaN
2008-01-03 00:00:00 0.000483
2008-01-04 00:00:00 0.02451
2008-01-07 00:00:00 0.0008492
2008-01-08 00:00:00 0.01615
2008-01-09 00:00:00 -0.01051
2008-01-10 00:00:00 -0.006554
2008-01-11 00:00:00 0.008069
2008-01-14 00:00:00 -0.008063
2008-01-15 00:00:00 0.02201
最后我做成了这样:
def get_compounded_indicator_cumulative(self):
indicator_dm = self.get_indicator_returns()
dates = indicator_dm.index
indicator_returns = indicator_dm['indicator_returns']
compounded = array(zeros(size(indicator_returns)))
compounded[1] = indicator_returns[1]
for i in range(2, len(indicator_returns)):
compounded[i] = (1 + compounded[i-1]) * (1 + indicator_returns[i]) - 1
data = {
'compounded_returns': compounded
}
return DataMatrix(data, index=dates)
不知道为什么,我在这个问题上真的很挣扎……
我正在把我所有的价格序列转换成PyTables格式。到目前为止,看起来还不错。
这个问题中关于累计收益的部分在Wes McKinney的优秀书籍《Python数据分析》的第339页有详细讲解,书中使用了Pandas库里的cumprod()函数来根据计算出的价格变化,创建一个重新基准化的累计收益。
书中的例子:
import pandas.io.data as web
price = web.get_data_yahoo('AAPL', '2011-01-01')['Adj Close']
returns = price.pct_change()
ret_index = (1 + returns).cumprod()
ret_index[0] = 1 # Set first value to 1
有一个很棒的模块叫做 pandas,是一个在对冲基金AQR工作的家伙写的,它在处理这种计算方面特别出色……你需要的是一种处理“缺失数据”的方法……就像上面有人提到的,基本的方法是使用scipy或numpy中的nan(不是数字)功能;不过,即使是这些库,在金融计算上也没那么简单……如果你使用pandas,你可以把不想考虑的数据标记为 nan
,这样以后的计算就会忽略这些数据,同时对其他数据进行正常操作。
我在我的交易平台上使用 pandas 已经大约8个月了……我真希望我能早点开始用它。
作者Wes在2010年的pyCon上做过一次关于这个模块功能的演讲……可以在 pyCon 2010网页上查看幻灯片和视频。在那个视频中,他演示了如何获取每日收益,如何在收益矩阵上运行成千上万的线性回归(只需几分之一秒),以及如何时间戳/绘制数据……这一切都是用这个模块完成的。结合psyco,这真是一个强大的金融分析工具。
它处理的另一个很棒的功能是横截面数据……所以你可以抓取每日收盘价、它们的滚动均值等等……然后为每一个计算加上时间戳,并将所有这些存储在类似于python字典的结构中(见 pandas.DataFrame
类)……然后你可以像这样简单地访问数据的切片:
close_prices['stdev_5d']
有关如何计算滚动标准差的更多信息,请查看 pandas滚动时刻文档(这只需要一行代码)。
Wes还特别努力地用cython加速这个模块,尽管我承认,由于我的分析需求,我正在考虑升级我的服务器(一个较旧的Xeon)。
编辑以回应STRIMP的问题:
在你把代码转换为使用pandas数据结构后,我仍然不太清楚你是如何在pandas dataframe中索引你的数据,以及复合函数处理缺失数据的要求(或者说0.0收益的条件……或者你是否在pandas中使用 NaN
)。我将演示我的数据索引……随机选了一天…… df
是一个包含ES期货报价的数据框……按秒索引……缺失的报价用 numpy.nan
填充。DataFrame的索引是 datetime
对象,由 pytz
模块的时区对象偏移。
>>> df.info
<bound method DataFrame.info of <class 'pandas.core.frame.DataFrame'>
Index: 86400 entries , 2011-03-21 00:00:00-04:00 to 2011-03-21 23:59:59-04:00
etf 18390 non-null values
etfvol 18390 non-null values
fut 29446 non-null values
futvol 23446 non-null values
...
>>> # ET is a pytz object...
>>> et
<DstTzInfo 'US/Eastern' EST-1 day, 19:00:00 STD>
>>> # To get the futures quote at 9:45, eastern time...
>>> df.xs(et.localize(dt.datetime(2011,3,21,9,45,0)))['fut']
1291.75
>>>
为了给出一个简单的例子,说明如何计算一列连续收益(在 pandas.TimeSeries
中),它参考10分钟前的报价(并填补缺失的报价),我会这样做:
>>> df['fut'].fill(method='pad')/df['fut'].fill(method='pad').shift(600)
在这种情况下不需要lambda,只需将当前值列除以600秒前的值。那部分 .shift(600)
是因为我的数据是按秒索引的。
希望这对你有帮助, \mike