Максимальная активная просадка в python

недавно я задал вопрос о расчет максимальной просадки здесь Александр дал очень краткий и эффективный способ вычисления его с помощью методов фрейма данных в панд.

Я хотел продолжить, спросив, как другие вычисляют максимум активный просадка?

это вычисляет максимальную просадку. Нет! Максимальная Активная Просадка

Это то, что я реализовал для максимальной просадки на основе Ответ Александра на вопрос, связанный выше:

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)

максимальная просадка - это минимум всех рассчитанных просадок.

мой вопрос:

Я хотел продолжить, спросив, как другие вычисляют максимум активный просадка?

предполагает, что решение будет распространяться на решение выше.

3 ответов


начиная с серии возвратов портфеля и контрольных возвратов, мы строим кумулятивные доходы для обоих. предполагается, что переменные ниже уже находятся в кумулятивном пространстве возврата.

активный возврат из периода j на срок я - это:

formula for active return

решение

вот как мы можем расширить Абсолют решение:

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

caclulation просадки теперь можно сделать аналогично используя формулу выше:

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

enter image description here


возможно, вы заметили, что ваши отдельные компоненты не равны целому, ни аддитивным, ни геометрическим образом:

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

это всегда тревожная ситуация, так как это указывает на то, что в вашей модели может произойти какая-то утечка.

смешивание одного периода и многопериодной атрибуции всегда является проблемой. Часть проблемы заключается в цели анализа, то есть в том, что вы пытаетесь объяснить.

если вы смотрите на кумулятивные возвраты как и в случае выше, то один из способов выполнения анализа заключается в следующем:

  1. убедитесь, что доходность портфеля и контрольная доходность являются избыточной доходностью, т. е. вычитают соответствующую денежную доходность за соответствующий период (например, ежедневно, ежемесячно и т. д.).

  2. Предположим, у вас есть богатый дядя, который одалживает вам $ 100m, чтобы начать свой фонд. Теперь вы можете думать о своем портфеле как о трех транзакциях, одной денежной и двух производных операции: a) инвестируйте свои $100m в денежный счет, удобно зарабатывая ставку предложения. b) ввести в капитал своп на $ 100м условный c) заключите сделку свопа с нулевым бета-хедж-фондом, опять же за $100m условно.

мы удобно предположим, что обе своп-транзакции обеспечиваются денежным счетом и что нет никаких транзакционных издержек (если только...!).

в первый день фондовый индекс вырос чуть более чем на 1% (an сверхприбыль ровно 1,00% после вычета кассового расхода за день). Некоррелированный хедж-фонд, однако, предоставил избыточную доходность -5%. Наш фонд сейчас составляет $ 96m.

день второй, как мы перебалансируем? Ваши расчеты подразумевают, что мы никогда этого не делаем. Каждый представляет собой отдельный портфель, который дрейфует вечно... Однако для целей атрибуции я считаю, что имеет смысл ежедневно перебалансировать, то есть 100% для каждой из двух стратегий.

как это просто условные экспозиции с достаточным денежным обеспечением, мы можем просто скорректировать суммы. Таким образом, вместо того, чтобы иметь $101m воздействия индекса акций на второй день и $95m воздействия хедж-фонда, мы вместо этого перебалансировка (при нулевой стоимости), так что у нас есть $96m воздействия каждого.

как это работает в панд, спросите вы? Вы уже рассчитали 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']. Потому что этот метод трудно вычислить (без панд!) и понять (большинство людей не получат условный exposures), отраслевая практика обычно определяет активную доходность как совокупную разницу в доходности за определенный период времени. Например, если фонд вырос на 5,0% в месяц, а рынок упал на 1,0%, то избыточная доходность за этот месяц обычно определяется как +6,0%. Проблема с этим упрощенным подходом, однако, заключается в том, что ваши результаты будут дрейфовать друг от друга с течением времени из-за усложнения и перебалансировки проблем, которые должным образом не учитываются в расчетах.

так дали наши!--6--> столбец, мы могли бы определить просадку как:

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


мои дешевые два Пенни в чистом 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