Python中的最大活动回撤

12

最近我在StackOverflow上提问关于计算最大回撤的问题,Alexander给了一个使用pandas中数据框方法非常简洁高效的计算方式。

我想跟进一下,询问其他人如何计算最大活动回撤

这个是计算最大回撤,不是最大活动回撤

以下是我根据Alexander在之前链接中的答案所实现的最大回撤计算:

def max_drawdown_absolute(returns):
    r = returns.add(1).cumprod()
    dd = r.div(r.cummax()).sub(1)
    mdd = dd.min()
    end = dd.argmin()
    start = r.loc[:end].argmax()
    return mdd, start, end

它接受一个回报序列,并返回最大回撤以及回撤发生的指数。

我们首先生成一系列累积回报作为回报指数。

r = returns.add(1).cumprod()
每个时间点的当前回撤是通过将回报指数的当前水平与所有先前期间的最大回报指数进行比较来计算的。
dd = r.div(r.cummax()).sub(1)

最大回撤就是所有计算出的回撤中的最小值。

我的问题:

我想跟进一下,问其他人是如何计算活动最大回撤的?

假设解决方案将在上述解决方案的基础上进行扩展。

4个回答

11

我们从一系列投资组合回报和基准回报开始,为两者建立累计回报。以下变量假定已经处于累计回报空间。

从期间 j 到期间 i 的主动回报为:

主动回报公式

解决方案

这是我们如何扩展绝对解决方案的方法:

def max_draw_down_relative(p, b):
    p = p.add(1).cumprod()
    b = b.add(1).cumprod()
    pmb = p - b
    cam = pmb.expanding(min_periods=1).apply(lambda x: x.argmax())
    p0 = pd.Series(p.iloc[cam.values.astype(int)].values, index=p.index)
    b0 = pd.Series(b.iloc[cam.values.astype(int)].values, index=b.index)
    dd = (p * b0 - b * p0) / (p0 * b0)
    mdd = dd.min()
    end = dd.argmin()
    start = cam.ix[end]
    return mdd, start, end

说明

与绝对情况类似,我们希望在每个时间点上知道到该时间点为止最大的累计主动回报是多少。我们使用p - b获得这个累计主动回报序列。不同之处在于,我们想要跟踪此时p和b是什么,而不是它们之间的差异本身。

因此,我们生成了一系列'whens'并将其捕获在camcumulative argmax)中,以及在这些'whens'时的投资组合和基准值的后续系列。

    p0 = pd.Series(p.ix[cam.values.astype(int)].values, index=p.index)
    b0 = pd.Series(b.ix[cam.values.astype(int)].values, index=b.index)

使用上述公式,可以类比地进行回撤计算:

    dd = (p * b0 - b * p0) / (p0 * b0)

演示

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

np.random.seed(314)
p = pd.Series(np.random.randn(200) / 100 + 0.001)
b = pd.Series(np.random.randn(200) / 100 + 0.001)

keys = ['Portfolio', 'Benchmark']
cum = pd.concat([p, b], axis=1, keys=keys).add(1).cumprod()
cum['Active'] = cum.Portfolio - cum.Benchmark


mdd, sd, ed = max_draw_down_relative(p, b)

f, a = plt.subplots(2, 1, figsize=[8, 10])

cum[['Portfolio', 'Benchmark']].plot(title='Cumulative Absolute', ax=a[0])
a[0].axvspan(sd, ed, alpha=0.1, color='r')

cum[['Active']].plot(title='Cumulative Active', ax=a[1])
a[1].axvspan(sd, ed, alpha=0.1, color='r')

输入图片说明


为了在所有情况下保持准确性,该函数需要自动将零添加为组合和基准的第一项返回。p = pd.Series([-0.1, -0.1, -0.1], name='p') b = pd.Series([-0.05, -0.05, -0.05], name='b') p = p.add(1).cumprod() b = b.add(1).cumprod() results = pd.concat([p, b], axis=1) results['cum diff'] = results['p'] - results['b'] results 结果应为-0.128375。但是您的函数返回-0.0925。 - Pilgrim

4

你可能已经注意到,你的单个组件无论是以加法还是几何方式都无法等于整体:

>>> cum.tail(1)
     Portfolio  Benchmark    Active
199   1.342179   1.280958  1.025144

这总是一个令人困扰的情况,因为它表明您的模型可能发生了某种泄漏。
单期和多期归因混合始终是一个挑战。问题的一部分在于分析的目标,即您想要解释什么。
如果您正在查看累计回报,如上例所示,则可以按以下方式执行分析:
1.确保组合回报和基准回报都是超额回报,即减去相应期间的现金回报(例如,每日、每月等)。
2.假设您有一个富裕的叔叔借给您1亿美元来启动您的基金。现在您可以将您的投资组合视为三笔交易,其中包括一笔现金交易和两笔衍生品交易: a) 将100百万美元投资在现金账户中,方便地赚取报价利率。 b) 进入100百万名义金额的股票掉期 c) 再次进入与零贝塔对冲基金的掉期交易,名义金额也为100百万。
我们方便地假设两笔掉期交易都由现金账户担保,并且没有交易成本(如果只有...!)。

第一天,股票指数上涨了略高于1%(扣除当日现金费用后的超额收益率为确切的1.00%)。然而,不相关的对冲基金却实现了-5%的超额回报。我们的基金现在是9600万美元。

第二天,我们如何再平衡?您的计算表明我们永远不会这样做。每个都是单独的组合,永远漂移...但是出于归因目的,我认为每天重新平衡是完全有道理的,即对两种策略分别分配100%。

由于这些只是名义敞口,拥有充足的现金抵押品,因此我们可以调整金额。因此,我们将重新平衡(零成本)而不是在第二天对股票指数暴露101m美元,对冲基金暴露95m美元,我们将调整为对每个策略暴露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

enter image description here

构造时,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)

enter image description here

你可以像以前一样确定回撤的起点和终点。
将我的累计主动回报贡献与你计算的金额进行比较,你会发现它们一开始很相似,但随着时间的推移会有所偏差(我的回报计算在绿色区域)。

enter image description here


1
你发现我计算累积主动回报的方式有误。我的本意是在取差值之前累加“组合”和“基准”的回报。但实际上,我是先计算期间回报的差异,然后再进行累加。正如你所指出的那样,这是一个错误。我已经更正了这个计算方法。幸运的是,这不会影响最大主动回撤的计算,因为这只是演示中的错误。感谢你的指出。 - piRSquared

2

以下是我对纯Python的一些简单想法:

def find_drawdown(lista):
    peak = 0
    trough = 0
    drawdown = 0
    for n in lista:
        if n > peak:
            peak = n
            trough = peak
        if n < trough:
            trough = n
        temp_dd = peak - trough
        if temp_dd > drawdown:
            drawdown = temp_dd
    return -drawdown

0
piRSquared的回答中,我建议修改。
pmb = p - b 

pmb = p / b 

使用 p-b 确定 df3 中的 pmb,以查找相对最大回撤。相对最大回撤为 US$851(-48.9%)。使用 p/b 确定 df2 中的 pmb,相对最大回撤为 US$544.6(-57.9%)。

import pandas as pd
import datetime
import pandas_datareader.data as pdr
import matplotlib.pyplot as plt
import yfinance as yfin
yfin.pdr_override()
stocks = ["AMZN", "SPY"]
df = pdr.get_data_yahoo(stocks, start="2020-01-01", end="2022-02-18")
df = df[['Adj Close']]
df.columns = df.columns.droplevel(0)
df.reset_index(inplace=True)
df.Date=df.Date.dt.date

df2 = df[df.Date.isin([datetime.date(2020,7,9), datetime.date(2022,2,3)])].copy()
df2['AMZN/SPY'] = df2.AMZN / df2.SPY
df2['AMZN-SPY'] = df2.AMZN - df2.SPY
df2['USDdiff'] = df2['AMZN-SPY'].diff().round(1)
df2[["p", "b"]] = df2[['AMZN','SPY']].pct_change(1).round(4)
df2['p-b'] = df2.p - df2.b
df2.replace(np. nan,'',regex=True, inplace=True)
df2 = df2.round(2)
print(df2)


      Date     AMZN    SPY  AMZN/SPY  AMZN-SPY USDdiff       p       b      p-b
2020-07-09  3182.63  307.7     10.34   2874.93                           
2022-02-03  2776.91  446.6      6.22   2330.31  -544.6 -0.1275  0.4514  -0.5789 

   
df3 = df[df.Date.isin([datetime.date(2020,9,2), datetime.date(2022,2,3)])].copy()
df3['AMZN/SPY'] = df3.AMZN / df3.SPY
df3['AMZN-SPY'] = df3.AMZN - df3.SPY
df3['USDdiff'] = df3['AMZN-SPY'].diff().round(1)
df3[["p", "b"]] = df3[['AMZN','SPY']].pct_change(1).round(4)
df3['p-b'] = df3.p - df3.b
df3.replace(np. nan,'',regex=True, inplace=True)
df3 = df3.round(2)
print(df3)
    
      Date     AMZN     SPY  AMZN/SPY  AMZN-SPY USDdiff       p       b      p-b 
2020-09-02  3531.45  350.09     10.09   3181.36                           
2022-02-03  2776.91  446.60      6.22   2330.31  -851.0 -0.2137  0.2757  -0.4894   

   

顺便说一下:我没有足够的声望来评论。


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