【问题标题】:Maximum Active Drawdown in pythonpython中的最大活动回撤
【发布时间】:2022-02-21 02:01:20
【问题描述】:

我最近问了一个关于calculating maximum drawdown 的问题,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

它需要一个返回序列并返回 max_drawdown 以及出现回撤的索引。

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

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

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

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

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

我的问题:

我想通过询问其他人如何计算最大值来跟进 有效回撤

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

【问题讨论】:

    标签: python pandas numpy quantitative-finance


    【解决方案1】:

    从一系列投资组合回报和基准回报开始,我们为两者建立累积回报。假设下面的变量已经在累积返回空间中。

    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 是什么,而不是差异本身。

    因此,我们生成了一系列在cam 中捕获的“whens”(c累积性a rgmax) 和后续系列的投资组合和基准值在那些'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([ -.1,-.1,-.1], name='p') b = pd.Series([-.05,-.05,-.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。
    【解决方案2】:

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

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

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

    混合使用单期和多期归因始终是一项挑战。部分问题在于分析的目标,即您要解释什么。

    如果您按照上述情况查看累积回报,那么您执行分析的一种方法如下:

    1. 确保投资组合回报和基准回报都是超额回报,即减去相应时期(例如每日、每月等)的适当现金回报。

    2. 假设您有一个富有的叔叔借给您 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 回报贡献与您计算的金额进行比较,您会发现它们起初相似,然后随着时间的推移逐渐偏离(我的回报计算为绿色):

    【讨论】:

    • 您发现我计算的累积主动回报不正确。我打算在获取差价之前累积“投资组合”和“基准”回报。相反,我采用了期间收益的差异并将它们累积起来。正如您所强调的,这是一个错误。我已经更正了这个计算。幸运的是,它不会影响 max_active_drawdown 的计算,因为它是演示中的错误。感谢您了解这一点。
    【解决方案3】:

    我在纯 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
    

    【讨论】:

      【解决方案4】:

      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:我没有足够的声誉来发表评论。

      【讨论】:

        猜你喜欢
        • 2020-01-28
        • 1970-01-01
        • 2014-01-30
        • 1970-01-01
        • 2014-05-01
        • 1970-01-01
        • 2019-11-12
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多