在Python中计算复合收益系列

10 投票
3 回答
14038 浏览
提问于 2025-04-16 14:53

大家好,我有两组数据:每天的原始股票价格收益(可能是正数也可能是负数)和交易信号(买入=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 个回答

1

假设我有一个数据矩阵,里面包含了收盘价格、一些指标值,还有一个交易信号,像这样:

 >>> 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格式。到目前为止,看起来还不错。

3

这个问题中关于累计收益的部分在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
9

有一个很棒的模块叫做 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

撰写回答