加速计算回报率

3
我正在使用Python 2.7。我想要计算复合收益率,从每日收益率中计算出来,但是我的代码计算速度相当缓慢,因此我正在寻找可以提高效率的地方。
我想要做的是将两个日期和一个证券传递到价格表中,并使用给定的证券计算这些日期之间的复合收益率。
我有一个价格表(prices_df):
security_id px_last    asof
    1       3.055   2015-01-05
    1       3.360   2015-01-06
    1       3.315   2015-01-07
    1       3.245   2015-01-08
    1       3.185   2015-01-09

我也有一个包含两个日期和安全性的表格(events_df):
asof            disclosed_on    security_ref_id
2015-01-05  2015-01-09 16:31:00     1
2018-03-22  2018-03-27 16:33:00     3616
2017-08-03  2018-03-27 12:13:00     2591
2018-03-22  2018-03-27 11:33:00     3615
2018-03-22  2018-03-27 10:51:00     3615

使用这个表中的两个日期,我想使用价格表来计算收益。

我正在使用的两个函数:

import pandas as pd
# compounds returns
def cum_rtrn(df):
    df_out = df.add(1).cumprod()
    df_out['return'].iat[0] = 1
    return df_out

# calculates compound returns from prices between two dates
def calc_comp_returns(price_df, start_date=None, end_date=None, security=None):
    df = price_df[price_df.security_id == security]
    df = df.set_index(['asof'])
    df = df.loc[start_date:end_date]
    df['return'] = df.px_last.pct_change()
    df = df[['return']]
    df = cum_rtrn(df)
    return df.iloc[-1][0]

我随后使用 .iterrows 迭代遍历 events_df ,每次传递 calc_comp_returns 函数。然而,这是一个非常缓慢的过程,因为我有10K+次迭代,所以我正在寻找改进方法。解决方案不需要基于 pandas

# example of how function is called
start = datetime.datetime.strptime('2015-01-05', '%Y-%m-%d').date()
end = datetime.datetime.strptime('2015-01-09', '%Y-%m-%d').date()
calc_comp_returns(prices_df, start_date=start, end_date=end, security=1)
3个回答

1

这里有一个解决方案(在我的电脑上使用一些虚拟数据可以提高100倍的速度)。

import numpy as np

price_df = price_df.set_index('asof')

def calc_comp_returns_fast(price_df, start_date, end_date, security):
    rows = price_df[price_df.security_id == security].loc[start_date:end_date]
    changes = rows.px_last.pct_change()
    comp_rtrn = np.prod(changes + 1)
    return comp_rtrn

或者,作为一行代码:
 def calc_comp_returns_fast(price_df, start_date, end_date, security):
    return np.prod(price_df[price_df.security_id == security].loc[start_date:end_date].px_last.pct_change() + 1)

不需要在整个price_df数据帧上每次重建数据帧,因此无需事先调用set_index方法。

这样做更快,因为它不会在每个步骤中重新创建DataFrames。在您的代码中,df几乎在每一行被一个新的数据帧覆盖。初始化过程和垃圾回收(从内存中擦除未使用的数据)都需要很长时间。

在我的代码中,rows是原始数据的一个切片或“视图”,它不需要复制或重新初始化任何对象。此外,我直接使用了numpy的product函数,它与取最后一个cumprod元素相同(pandas在内部使用np.cumprod)。

建议:如果您正在使用IPython、Jupyter或Spyder,则可以使用魔术命令%prun calc_comp_returns(...)查看哪个部分花费了最多的时间。我在您的代码上运行了它,发现垃圾回收器使用了超过50%的总运行时间!


以前从未听说过 %prun 魔术函数,这真的很有用。谢谢! - roarkz

0

我对pandas不是很熟悉,但我会尝试一下。

你的解决方案存在问题

你的解决方案目前进行了大量不必要的计算。这主要是由于以下代码行导致的:

    df['return'] = df.px_last.pct_change()

这行代码实际上是在计算开始到结束之间每个日期的百分比变化。解决这个问题应该可以大大提高速度。您只需要获取起始价格和结束价格,然后将两者进行比较即可。这两个价格之间的价格对于您的计算来说完全无关紧要。再次说明,我对pandas并不熟悉,但您应该这样做:

def calc_comp_returns(price_df, start_date=None, end_date=None, security=None):
    df = price_df[price_df.security_id == security]
    df = df.set_index(['asof'])
    df = df.loc[start_date:end_date]
    return 1 + (df['px_last'].iloc(-1) - df['px_last'].iloc(0)

请记得,此代码依赖于price_df按日期排序的事实,因此请注意确保您只向calc_comp_returns传递按日期排序的price_df。


0
我们将使用 pd.merge_asofprices_df 中获取价格。但是,在这样做时,我们需要按照我们正在使用的日期列对相关数据帧进行排序。此外,为了方便起见,我将一些 pd.merge_asof 参数聚合到字典中,以用作关键字参数。
prices_df = prices_df.sort_values(['asof'])

aed = events_df.sort_values('asof')
ded = events_df.sort_values('disclosed_on')

aokw = dict(
    left_on='asof', right_on='asof',
    left_by='security_ref_id', right_by='security_id'
)

start_price = pd.merge_asof(aed, prices_df, **aokw).px_last

dokw = dict(
    left_on='disclosed_on', right_on='asof',
    left_by='security_ref_id', right_by='security_id'
)

end_price = pd.merge_asof(ded, prices_df, **dokw).px_last

returns = end_price.div(start_price).sub(1).rename('return')
events_df.join(returns)

        asof        disclosed_on  security_ref_id    return
0 2015-01-05 2015-01-09 16:31:00                1  0.040816
1 2018-03-22 2018-03-27 16:33:00             3616       NaN
2 2017-08-03 2018-03-27 12:13:00             2591       NaN
3 2018-03-22 2018-03-27 11:33:00             3615       NaN
4 2018-03-22 2018-03-27 10:51:00             3615       NaN

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接