您可能已经注意到,您的各个组成部分并不等于整体,无论是以相加方式还是几何方式:
>>> cum.tail(1)
Portfolio Benchmark Active
199 1.342179 1.280958 1.025144
这总是一个令人不安的情况,因为它表明您的模型中可能发生某种泄漏。
混合使用单期和多期归因始终是一项挑战。部分问题在于分析的目标,即您要解释什么。
如果您按照上述情况查看累积回报,那么您执行分析的一种方法如下:
确保投资组合回报和基准回报都是超额回报,即减去相应时期(例如每日、每月等)的适当现金回报。
假设您有一个富有的叔叔借给您 1 亿美元来启动您的基金。现在您可以将您的投资组合视为三笔交易,一笔现金和两笔衍生品交易:
a) 将您的 1 亿美元投资于现金账户,方便地赚取报价。
b) 以 1 亿美元名义进行股权互换
c) 以 1 亿美元名义与零贝塔对冲基金进行掉期交易。
我们将方便地假设两个掉期交易都由现金账户担保,并且没有交易成本(如果只是......!)。
在第一天,股票指数上涨了 1% 以上(扣除当天的现金费用后,超额回报正好是 1.00%)。然而,不相关的对冲基金提供了 -5% 的超额回报。我们的基金现在是 9600 万美元。
第二天,我们如何重新平衡?你的计算表明我们从不这样做。每个都是一个独立的投资组合,永远漂移......然而,出于归因的目的,我认为每天重新平衡是完全有意义的,即 100% 用于两种策略中的每一个。
由于这些只是具有充足现金抵押品的名义风险敞口,我们可以调整金额。因此,我们将不再在第二天拥有 1.01 亿美元的股票指数敞口和 9500 万美元的对冲基金敞口,而是重新平衡(以零成本),这样我们每个都有 9600 万美元的敞口。
您可能会问,这在 Pandas 中是如何工作的?您已经计算了cum['Portfolio'],这是投资组合的累积超额增长因子(即扣除现金回报后)。如果我们将当天的超额基准和主动收益应用于前一天的投资组合增长因子,我们会计算每日重新平衡收益。
import numpy as np
import pandas as pd
np.random.seed(314)
df_returns = pd.DataFrame({
'Portfolio': np.random.randn(200) / 100 + 0.001,
'Benchmark': np.random.randn(200) / 100 + 0.001})
df_returns['Active'] = df.Portfolio - df.Benchmark
# Copy return dataframe shape and fill with NaNs.
df_cum = pd.DataFrame()
# Calculate cumulative portfolio growth
df_cum['Portfolio'] = (1 + df_returns.Portfolio).cumprod()
# Calculate shifted portfolio growth factors.
portfolio_return_factors = pd.Series([1] + df_cum['Portfolio'].shift()[1:].tolist(), name='Portfolio_return_factor')
# Use portfolio return factors to calculate daily rebalanced returns.
df_cum['Benchmark'] = (df_returns.Benchmark * portfolio_return_factors).cumsum()
df_cum['Active'] = (df_returns.Active * portfolio_return_factors).cumsum()
现在我们看到主动回报加上基准回报加上初始现金等于投资组合的当前价值。
>>> df_cum.tail(3)[['Benchmark', 'Active', 'Portfolio']]
Benchmark Active Portfolio
197 0.303995 0.024725 1.328720
198 0.287709 0.051606 1.339315
199 0.292082 0.050098 1.342179
通过构造,df_cum['Portfolio'] = 1 + df_cum['Benchmark'] + df_cum['Active']。
由于这种方法难以计算(没有 Pandas!)和理解(大多数人不会得到名义风险敞口),行业惯例通常将主动收益定义为一段时间内收益的累积差异。例如,如果一只基金在一个月内上涨了 5.0%,而市场下跌了 1.0%,那么该月的超额收益通常被定义为 +6.0%。然而,这种简单方法的问题在于,由于计算中没有正确考虑到复合和再平衡问题,您的结果会随着时间的推移而偏离。
因此,鉴于我们的df_cum.Active 列,我们可以将回撤定义为:
drawdown = pd.Series(1 - (1 + df_cum.Active)/(1 + df_cum.Active.cummax()), name='Active Drawdown')
>>> df_cum.Active.plot(legend=True);drawdown.plot(legend=True)
然后您可以像以前一样确定回撤的起点和终点。
将我的累积 Active 回报贡献与您计算的金额进行比较,您会发现它们起初相似,然后随着时间的推移逐渐偏离(我的回报计算为绿色):