복붙노트

[PYTHON] Python에서 벡터화 된 솔루션으로 최대 계산

PYTHON

Python에서 벡터화 된 솔루션으로 최대 계산

최대 삭도는 경험 한 가장 큰 부정적인 반환을 사정하기 위하여 양이 많은 회계에서 사용 된 일반적인 위험 측정 규정입니다.

최근에, 나는 루프 방식을 사용하여 최대 삭감을 계산할 시간에 참을성이 없어졌습니다.

def max_dd_loop(returns):
    """returns is assumed to be a pandas series"""
    max_so_far = None
    start, end = None, None
    r = returns.add(1).cumprod()
    for r_start in r.index:
        for r_end in r.index:
            if r_start < r_end:
                current = r.ix[r_end] / r.ix[r_start] - 1
                if (max_so_far is None) or (current < max_so_far):
                    max_so_far = current
                    start, end = r_start, r_end
    return max_so_far, start, end

저는 벡터화 된 솔루션이 더 좋을 것이라는 일반적인 인식에 익숙합니다.

질문은 다음과 같습니다.

알렉산더의 대답을 다음과 같이 수정했습니다.

def max_dd(returns):
    """Assumes returns is a pandas Series"""
    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

해결법

  1. ==============================

    1.df_returns는 수익의 데이터 프레임으로 간주되며 각 열은 별도의 전략 / 관리자 / 보안이며 각 행은 새로운 날짜 (예 : 월별 또는 매일)입니다.

    df_returns는 수익의 데이터 프레임으로 간주되며 각 열은 별도의 전략 / 관리자 / 보안이며 각 행은 새로운 날짜 (예 : 월별 또는 매일)입니다.

    cum_returns = (1 + df_returns).cumprod()
    drawdown =  1 - cum_returns.div(cum_returns.cummax())
    
  2. ==============================

    2.먼저 .expanding () 창을 사용하여 제안했지만 .cumprod () 및 .cummax () 함수를 사용하여 특정 지점까지 최대 drawdown을 계산할 필요는 없습니다.

    먼저 .expanding () 창을 사용하여 제안했지만 .cumprod () 및 .cummax () 함수를 사용하여 특정 지점까지 최대 drawdown을 계산할 필요는 없습니다.

    df = pd.DataFrame(data={'returns': np.random.normal(0.001, 0.05, 1000)}, index=pd.date_range(start=date(2016,1,1), periods=1000, freq='D'))
    
    df = pd.DataFrame(data={'returns': np.random.normal(0.001, 0.05, 1000)},
                      index=pd.date_range(start=date(2016, 1, 1), periods=1000, freq='D'))
    df['cumulative_return'] = df.returns.add(1).cumprod().subtract(1)
    df['max_drawdown'] = df.cumulative_return.add(1).div(df.cumulative_return.cummax().add(1)).subtract(1)
    

                returns  cumulative_return  max_drawdown
    2016-01-01 -0.014522          -0.014522      0.000000
    2016-01-02 -0.022769          -0.036960     -0.022769
    2016-01-03  0.026735          -0.011214      0.000000
    2016-01-04  0.054129           0.042308      0.000000
    2016-01-05 -0.017562           0.024004     -0.017562
    2016-01-06  0.055254           0.080584      0.000000
    2016-01-07  0.023135           0.105583      0.000000
    2016-01-08 -0.072624           0.025291     -0.072624
    2016-01-09 -0.055799          -0.031919     -0.124371
    2016-01-10  0.129059           0.093020     -0.011363
    2016-01-11  0.056123           0.154364      0.000000
    2016-01-12  0.028213           0.186932      0.000000
    2016-01-13  0.026914           0.218878      0.000000
    2016-01-14 -0.009160           0.207713     -0.009160
    2016-01-15 -0.017245           0.186886     -0.026247
    2016-01-16  0.003357           0.190869     -0.022979
    2016-01-17 -0.009284           0.179813     -0.032050
    2016-01-18 -0.027361           0.147533     -0.058533
    2016-01-19 -0.058118           0.080841     -0.113250
    2016-01-20 -0.049893           0.026914     -0.157492
    2016-01-21 -0.013382           0.013173     -0.168766
    2016-01-22 -0.020350          -0.007445     -0.185681
    2016-01-23 -0.085842          -0.092648     -0.255584
    2016-01-24  0.022406          -0.072318     -0.238905
    2016-01-25  0.044079          -0.031426     -0.205356
    2016-01-26  0.045782           0.012917     -0.168976
    2016-01-27 -0.018443          -0.005764     -0.184302
    2016-01-28  0.021461           0.015573     -0.166797
    2016-01-29 -0.062436          -0.047836     -0.218819
    2016-01-30 -0.013274          -0.060475     -0.229189
    ...              ...                ...           ...
    2018-08-28  0.002124           0.559122     -0.478738
    2018-08-29 -0.080303           0.433921     -0.520597
    2018-08-30 -0.009798           0.419871     -0.525294
    2018-08-31 -0.050365           0.348359     -0.549203
    2018-09-01  0.080299           0.456631     -0.513004
    2018-09-02  0.013601           0.476443     -0.506381
    2018-09-03 -0.009678           0.462153     -0.511158
    2018-09-04 -0.026805           0.422960     -0.524262
    2018-09-05  0.040832           0.481062     -0.504836
    2018-09-06 -0.035492           0.428496     -0.522411
    2018-09-07 -0.011206           0.412489     -0.527762
    2018-09-08  0.069765           0.511031     -0.494817
    2018-09-09  0.049546           0.585896     -0.469787
    2018-09-10 -0.060201           0.490423     -0.501707
    2018-09-11 -0.018913           0.462235     -0.511131
    2018-09-12 -0.094803           0.323611     -0.557477
    2018-09-13  0.025736           0.357675     -0.546088
    2018-09-14 -0.049468           0.290514     -0.568542
    2018-09-15  0.018146           0.313932     -0.560713
    2018-09-16 -0.034118           0.269104     -0.575700
    2018-09-17  0.012191           0.284576     -0.570527
    2018-09-18 -0.014888           0.265451     -0.576921
    2018-09-19  0.041180           0.317562     -0.559499
    2018-09-20  0.001988           0.320182     -0.558623
    2018-09-21 -0.092268           0.198372     -0.599348
    2018-09-22 -0.015386           0.179933     -0.605513
    2018-09-23 -0.021231           0.154883     -0.613888
    2018-09-24 -0.023536           0.127701     -0.622976
    2018-09-25  0.030160           0.161712     -0.611605
    2018-09-26  0.025528           0.191368     -0.601690
    
  3. ==============================

    3.일련의 수익률을 감안할 때 시작 지점과 종료 지점의 모든 조합에 대한 총 수익을 평가해야합니다.

    일련의 수익률을 감안할 때 시작 지점과 종료 지점의 모든 조합에 대한 총 수익을 평가해야합니다.

    첫 번째 트릭은 일련의 반환 값을 일련의 반환 인덱스로 변환하는 것입니다. 일련의 수익률 지수가 주어지면, 임의의 하위 기간에 걸쳐 수익률을 시작 지표 ri_0과 끝점 ri_1에서 계산할 수 있습니다. 계산은 ri_1 / ri_0 - 1입니다.

    두 번째 트릭은 리턴 인덱스의 반전에 대한 두 번째 계열을 생성하는 것입니다. 만약 r이 나의 일련의 반환 지수라면, 1 / r은 나의 역행렬입니다.

    세 번째 트릭은 r * (1 / r) .Transpose의 행렬 곱을 취하는 것입니다.

    r은 nx1 행렬입니다. (1 / r) .Transpose는 1 x n 행렬입니다. 결과 제품은 ri_j / ri_k의 모든 조합을 포함합니다. 그냥 1을 빼면 실제로 수익을 얻었습니다.

    네 번째 요령은 내가 분자로 표현되기 이전의 마침표를 나타내도록 내 분모를 제한하는 것입니다.

    아래는 내 벡터화 된 함수입니다.

    import numpy as np
    import pandas as pd
    
    def max_dd(returns):
        # make into a DataFrame so that it is a 2-dimensional
        # matrix such that I can perform an nx1 by 1xn matrix
        # multiplication and end up with an nxn matrix
        r = pd.DataFrame(returns).add(1).cumprod()
    
        # I copy r.T to ensure r's index is not the same
        # object as 1 / r.T's columns object
        x = r.dot(1 / r.T.copy()) - 1
        x.columns.name, x.index.name = 'start', 'end'
    
        # let's make sure we only calculate a return when start
        # is less than end.
        y = x.stack().reset_index()
        y = y[y.start < y.end]
    
        # my choice is to return the periods and the actual max
        # draw down
        z = y.set_index(['start', 'end']).iloc[:, 0]
        return z.min(), z.argmin()[0], z.argmin()[1]
    

    이 기능은 어떻게 수행됩니까?

    벡터화 된 솔루션의 경우 길이 10, 50, 100, 150, 200의 시계열에서 10 번의 반복을 실행했습니다. 시간은 아래와 같습니다.

    10:   0.032 seconds
    50:   0.044 seconds
    100:  0.055 seconds
    150:  0.082 seconds
    200:  0.047 seconds
    

    루프 된 솔루션에 대한 동일한 테스트가 아래와 같습니다.

    10:   0.153 seconds
    50:   3.169 seconds
    100: 12.355 seconds
    150: 27.756 seconds
    200: 49.726 seconds
    

    알렉산더의 대답은 뛰어난 결과를 제공합니다. 수정 된 코드를 사용하여 동일한 테스트

    10:   0.000 seconds
    50:   0.000 seconds
    100:  0.004 seconds
    150:  0.007 seconds
    200:  0.008 seconds
    

    코드를 다음 함수로 수정했습니다.

    def max_dd(returns):
        r = returns.add(1).cumprod()
        dd = r.div(r.cummax()).sub(1)
        mdd = drawdown.min()
        end = drawdown.argmin()
        start = r.loc[:end].argmax()
        return mdd, start, end
    
  4. from https://stackoverflow.com/questions/36750571/calculate-max-draw-down-with-a-vectorized-solution-in-python by cc-by-sa and MIT license