Python中最大回撤的开始、结束和持续时间

29 投票
6 回答
48912 浏览
提问于 2025-04-17 23:38

给定一个时间序列,我想计算出最大的回撤(也就是资产价值的最大下降幅度),同时我还想找到这个最大回撤的开始和结束点,这样我就可以计算出它持续了多久。我想在时间序列的图上标记出回撤的开始和结束,就像这样:

一只忙碌的猫

到目前为止,我已经写好了生成随机时间序列的代码,也有计算最大回撤的代码。如果有人知道怎么找出回撤开始和结束的地方,我会非常感激!

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

# create random walk which I want to calculate maximum drawdown for:

T = 50
mu = 0.05
sigma = 0.2
S0 = 20
dt = 0.01
N = round(T/dt)
t = np.linspace(0, T, N)
W = np.random.standard_normal(size = N) 
W = np.cumsum(W)*np.sqrt(dt) ### standard brownian motion ###
X = (mu-0.5*sigma**2)*t + sigma*W 

S = S0*np.exp(X) ### geometric brownian motion ###
plt.plot(S)

# Max drawdown function      

def max_drawdown(X):
    mdd = 0
    peak = X[0]
    for x in X:
        if x > peak: 
            peak = x
        dd = (peak - x) / peak
        if dd > mdd:
            mdd = dd
    return mdd    

drawSeries = max_drawdown(S)
MaxDD = abs(drawSeries.min()*100)
print MaxDD


plt.show()

6 个回答

1

我同意k0rnik的观点。

这里有一个简单的例子,证明behzad.nouri给出的公式可能会产生错误的结果。

xs = [1, 50, 10, 180, 40, 200]

pos_min1 = np.argmax(np.maximum.accumulate(xs) - xs) # end of the period
pos_peak1 = np.argmax(xs[:pos_min1]) # start of period

pos_min2 = np.argmax((np.maximum.accumulate(xs) - 
xs)/np.maximum.accumulate(xs)) # end of the period
pos_peak2 = np.argmax(xs[:pos_min2]) # start of period

plt.plot(xs)
plt.plot([pos_min1, pos_peak1], [xs[pos_min1], xs[pos_peak1]], 'o', 
label="mdd 1", color='Red', markersize=10)
plt.plot([pos_min2, pos_peak2], [xs[pos_min2], xs[pos_peak2]], 'o', 
label="mdd 2", color='Green', markersize=10)
plt.legend()

mdd1 = 100 * (xs[pos_min1] - xs[pos_peak1]) / xs[pos_peak1]
mdd2 = 100 * (xs[pos_min2] - xs[pos_peak2]) / xs[pos_peak2]

print(f"solution 1: peak {xs[pos_peak1]}, min {xs[pos_min1]}\n rate : 
{mdd1}\n")
print(f"solution 2: peak {xs[pos_peak2]}, min {xs[pos_min2]}\n rate : 
{mdd2}")

另外,资产的价格不能是负数,所以

xs = np.random.randn(n).cumsum()

这个说法是不正确的。可以考虑加上:

xs -= (np.min(xs) - 10)
3

你的最大回撤(max_drawdown)已经在记录最高点的位置了。你只需要修改一下这个if语句,让它在保存最大回撤(mdd)的同时,也把结束位置mdd_end存起来,然后把mdd、最高点(peak)和mdd_end一起返回就可以了。

6

基于这个,我添加了水下分析,如果对谁有帮助的话...

def drawdowns(equity_curve):
    i = np.argmax(np.maximum.accumulate(equity_curve.values) - equity_curve.values) # end of the period
    j = np.argmax(equity_curve.values[:i]) # start of period

    drawdown=abs(100.0*(equity_curve[i]-equity_curve[j]))

    DT=equity_curve.index.values

    start_dt=pd.to_datetime(str(DT[j]))
    MDD_start=start_dt.strftime ("%Y-%m-%d") 

    end_dt=pd.to_datetime(str(DT[i]))
    MDD_end=end_dt.strftime ("%Y-%m-%d") 

    NOW=pd.to_datetime(str(DT[-1]))
    NOW=NOW.strftime ("%Y-%m-%d")

    MDD_duration=np.busday_count(MDD_start, MDD_end)

    try:
        UW_dt=equity_curve[i:].loc[equity_curve[i:].values>=equity_curve[j]].index.values[0]
        UW_dt=pd.to_datetime(str(UW_dt))
        UW_dt=UW_dt.strftime ("%Y-%m-%d")
        UW_duration=np.busday_count(MDD_end, UW_dt)
    except:
        UW_dt="0000-00-00"
        UW_duration=np.busday_count(MDD_end, NOW)

    return MDD_start, MDD_end, MDD_duration, drawdown, UW_dt, UW_duration
11

behzad.nouri 的解决方案很简洁,但它并不是最大回撤(我刚开了账户,暂时没有足够的声望,无法评论)。

你最终得到的是名义价值的最大下降,而不是相对价值的下降(百分比下降)。举个例子,如果你把这个方法应用到长期上升的时间序列(比如股票市场指数 S&P 500),那么最近的价值下降(名义值下降更多)会被优先考虑,而不是之前的价值下降,只要名义值的下降幅度更大。

比如 S&P 500 的情况:

  • 2007-08 年的金融危机,下降了 56.7%,下降了 888.62 点
  • 最近的冠状病毒危机,下降了 33.9%,下降了 1,1148.75 点

如果你把这个方法应用到 2000 年后的时期,你会看到冠状病毒危机的影响,而不是 2007-08 年的金融危机。

下面是相关的代码(来自 behzad.nouri):

n = 1000
xs = np.random.randn(n).cumsum()
i = np.argmax(np.maximum.accumulate(xs) - xs) # end of the period
j = np.argmax(xs[:i]) # start of period

plt.plot(xs)
plt.plot([i, j], [xs[i], xs[j]], 'o', color='Red', markersize=10)

你只需要把这个名义价值的下降除以最大累计金额,就能得到相对的(%)回撤。

( np.maximum.accumulate(xs) - xs ) / np.maximum.accumulate(xs)
79

只需要找出“当前值”与“运行中的最大值”之间的差值最大的地方:

n = 1000
xs = np.random.randn(n).cumsum()
i = np.argmax(np.maximum.accumulate(xs) - xs) # end of the period
j = np.argmax(xs[:i]) # start of period

plt.plot(xs)
plt.plot([i, j], [xs[i], xs[j]], 'o', color='Red', markersize=10)

drawdown

撰写回答