python 中的最大主动回撤

Maximum Active Drawdown in python

我最近问了一个关于 where Alexander 的问题,在 pandas.

中给出了一种使用 DataFrame 方法计算它的非常简洁有效的方法

我想跟进询问其他人如何计算最大活跃缩减?

这会计算最大回撤。不是!最大主动回撤

这是我根据 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

它采用 return 系列并返回 max_drawdown 以及发生回撤的指数。

我们首先生成一系列累积 return 作为 return 索引。

r = returns.add(1).cumprod()

在每个时间点,通过比较 return 指数的当前水平与之前所有时期的最大 return 指数来计算当前回撤。

dd = r.div(r.cummax()).sub(1)

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

我的问题:

I wanted to follow up by asking how others are calculating maximum active drawdown?

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

从一系列投资组合 return 和基准 return 开始,我们为两者构建累积 return。假设下面的变量已经累积 return space.

活跃期return从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

说明

与绝对情况类似,在每个时间点,我们想知道截至该时间点的最大累计活跃度return。我们用 p - b 得到这一系列累积活跃 returns。不同之处在于我们要跟踪此时的 p 和 b 是什么,而不是差异本身。

因此,我们生成了一系列在 cam 中捕获的 'whens' (cumulative argmax) 以及后续系列的投资组合和基准值在那些 '何时'.

    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)

示范[​​=34=]
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')

您可能已经注意到,无论是相加方式还是几何方式,您的各个组成部分并不等于整体:

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

这总是一个令人不安的情况,因为它表明您的模型中可能发生了某种泄漏。

混合单一时期和多时期归因始终是一个挑战。部分问题在于分析的目标,即你试图解释什么。

如果您像上述情况一样查看累积 returns,那么执行分析的一种方法如下:

  1. 确保投资组合return和基准return都是超额return,即减去适当的现金return各自的时期(例如每天、每月等)。

  2. 假设您有一位富有的叔叔借给您 1 亿美元来启动您的基金。现在您可以将您的投资组合视为三笔交易,一笔现金和两笔衍生品交易: a) 将您的 1 亿美元投资于现金账户,轻松赚取优惠利率。 b) 以名义价值 1 亿美元进行股权互换 c) 与零贝塔对冲基金进行互换交易,名义金额同样为 1 亿美元。

我们将方便地假设两次掉期交易都由现金账户抵押,并且没有交易成本(如果只有......!)。

第一天,股指上涨了 1% 以上(扣除当天的现金支出后,涨幅正好 return 1.00%)。然而,不相关的对冲基金交付了 -5% 的超额 return。我们的基金现在是 9600 万美元。

第二天,我们如何重新平衡?你的计算表​​明我们永远不会这样做。每个都是一个独立的投资组合,永远漂移......然而,出于归因的目的,我认为每天重新平衡是完全有意义的,即 100% 到两种策略中的每一种。

由于这些只是具有充足现金抵押品的名义风险敞口,我们可以调整金额。因此,我们不会在第二天有 1.01 亿美元的股票指数敞口和 9500 万美元的对冲基金敞口,而是重新平衡(零成本),以便我们对每个都有 9600 万美元的敞口。

您可能会问,在 Pandas 中这是如何工作的?您已经计算了 cum['Portfolio'],这是投资组合的累计超额增长因子(即扣除现金后 returns)。如果我们将当天的超额基准和活跃 returns 应用于前一天的投资​​组合增长因子,我们计算每日重新平衡的 returns.

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()

现在我们看到主动 return 加上基准 return 加上初始现金等于投资组合的当前价值。

   >>> 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!)和理解(大多数人不会得到名义风险),行业惯例通常将主动return定义为[=66=的累积差异]s 在一段时间内。例如,如果一只基金在一个月内上涨了 5.0%,而市场下跌了 1.0%,那么该月的超额 return 通常定义为 +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 return 贡献与您计算的金额进行比较,您会发现它们一开始很相似,然后随着时间的推移逐渐分开(我的 return 计算结果为绿色):

我便宜的两便士纯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

piRSquared回答中我建议修改

pmb = p - b 

pmb = p / b 

找到 rel。最大DD。 df3 使用 pmb = p-b 标识一个 rel。 MaxDD 为 851 美元 (-48.9%)。 df2 使用 pmb = p/b 标识 rel。 MaxDD 为 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   

   

PS: 我没有足够的声望来发表评论。