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,那么执行分析的一种方法如下:
确保投资组合return和基准return都是超额return,即减去适当的现金return各自的时期(例如每天、每月等)。
假设您有一位富有的叔叔借给您 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: 我没有足够的声望来发表评论。
我最近问了一个关于
我想跟进询问其他人如何计算最大活跃缩减?
这会计算最大回撤。不是!最大主动回撤
这是我根据 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')
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,那么执行分析的一种方法如下:
确保投资组合return和基准return都是超额return,即减去适当的现金return各自的时期(例如每天、每月等)。
假设您有一位富有的叔叔借给您 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: 我没有足够的声望来发表评论。