概要
這兩個概念在剛接觸量化分析時,實在時折騰了許久,故在此作下總結。
內容
最大回撤(Max Drawdown)
簡單來說最大回撤就是從一個高點到一個低點最大的下跌幅度,用來描述一個策略可能出現的最糟糕的情況,衡量了最極端可能的虧損。如果一個策略的最大回撤是 50%,那么它的意思是,運氣不好的話,我們的持有價值可能要攔腰截斷了。計算公式為
\begin{align} \label{e6}
MaxDrawDown_t = \max(1-\frac{P_j}{P_i} )
\end{align}
其中
- \(MaxDrawDown_t\) 為 \(t\) 日的最大回撤
- \(P_i\) 和 \(P_j\) 分別為 \(i\) 日和 \(j\) 日的策略總資產,且需要滿足 \(t \geqslant j >i\)
有的教程給出的計算公式是剛好反過來的,即取負值,實際計算公式是一樣的,了解下即可。公式可能看起來為容易理解,用語言描述就是
\begin{align} \label{e7}
MaxDrawDown_t = 1-\frac{t \text{日持有價值}}{t \text{日之前持有最高價值}}
\end{align}
這樣是不是容易理解多了。
最大回撤恢復時間(Max Drawdown)
這個我的理解是:從最大回撤結束時間開始,需要多久才能達到回撤前的頂峰狀態。這個可以參考圖示。
最大回撤持續期(MaxDrawDownDuration)
最大回撤持續期描述的是持有價值從回撤開始到再創新高所經歷的時間,如果說最大回撤是在資產空間維度上描述資產的波動風險,那么最大回撤持續期則是在資產的時間維度上給出一個風險描述。直觀地說就是資產創新高的頻率是怎樣的。
圖示
下邊一張圖展示了我們介紹的三個概念,直觀地表示了三個概念范圍。
圖 1:回撤效果圖!
源代碼
本篇用的代碼(python)如下,該代碼也包含了 python 繪折線圖的大部分設置,可以作為參考。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sun Jun 2 15:32:13 2019
@author: zk
"""
import datetime
import matplotlib.pyplot as plt
import matplotlib.dates as mdate
import matplotlib.ticker as ticker
import numpy as np
import random
DAYS = 365
def Init():
timedelta = datetime.timedelta(days = 1)
startdate = datetime.date.today()
xdate = [startdate+i*timedelta for i in range(DAYS)]
ycapital = [3000]
for _ in range(DAYS-1):
ycapital.append(ycapital[-1]+random.uniform(-1, 1.1))
return xdate, ycapital
def max_drawdown(xdate, ycapital):
# 計算每日的回撤
drawdown = []
tmp_max_capital = ycapital[0]
for c in ycapital:
tmp_max_capital = max(c, tmp_max_capital)
drawdown.append(1 - c / tmp_max_capital)
# 最大回撤
MaxDrawdown = max(drawdown)
# 計算最大回撤日期范圍
endidx = np.argmax(drawdown)
#enddate = xdate[endidx]
startidx = np.argmax(ycapital[:endidx])
#startdate = xdate[startidx]
#僅僅畫圖的話,我們只要索引值更加方便
return MaxDrawdown, startidx, endidx
def max_drawdown_duration(xdate, ycapital):
duration = []
tmp_max_capital = ycapital[0]
for c in ycapital:
if c >= tmp_max_capital:
duration.append(0)
else:
duration.append(duration[-1]+1)
tmp_max_capital = max(c, tmp_max_capital)
MaxDDD = max(duration)
#fig, ax = plt.subplots(figsize = (21, 9))
#plt.plot(xdate, duration)
#ax.xaxis.set_major_formatter(mdate.DateFormatter('%Y-%m-%d'))
endidx = np.argmax(duration)
startidx = endidx - MaxDDD
return MaxDDD, startidx, endidx
def max_drawdown_restore_time(startidx, endidx, xdate, ycapital):
"""
startidx:表示最大回撤的開始時間在 xdate 中的索引,由 max_drawdown 方法返回
endidx:表示最大回撤的結束時間在 xdate 中的索引,由 max_drawdown 方法返回
"""
maxdd_resore_time = 0
restore_endidx = np.inf
for t in range(endidx, len(xdate)):
if ycapital[t] >= ycapital[startidx]:
restore_endidx = t
break
else:
maxdd_resore_time += 1
restore_endidx = min(restore_endidx, len(xdate)-1)
return maxdd_resore_time, restore_endidx
def plot(xdate, ycapital):
# 指定畫布大小
fig, ax = plt.subplots(figsize = (21, 9))
# 繪圖並設置顏色,圖例標簽,線寬
plt.plot(xdate, ycapital, 'red', label = 'My Strategy', linewidth = 2)
# 繪制最大回撤日期范圍標識 marker = 'v'
MaxDrawdown, startidx, endidx = max_drawdown(xdate, ycapital)
print("最大回撤為:", MaxDrawdown)
plt.scatter([xdate[startidx], xdate[endidx]], [ycapital[startidx], ycapital[endidx]],
s = 100, c = 'b', marker = 's', label = 'MaxDrawdown')
# 繪制最大回撤恢復時間
maxdd_resore_time, restore_endidx = max_drawdown_restore_time(startidx, endidx, xdate, ycapital)
print("最大回撤恢復時間為(天):", maxdd_resore_time)
plt.scatter([xdate[endidx], xdate[restore_endidx]], [ycapital[endidx], ycapital[restore_endidx]],
s = 100, c = 'cyan', marker = 'D', label = 'MaxDrawdown Restore Time')
# 繪制最大回撤持續期標識 marker = 'D'
MaxDDD, startidx, endidx = max_drawdown_duration(xdate, ycapital)
plt.scatter([xdate[startidx], xdate[endidx]], [ycapital[startidx], ycapital[endidx]],
s = 80, c = 'g', marker = 'v', label = 'MaxDrawdown Duration')
print("最大回撤持續期為(天):", MaxDDD)
# 設置刻度值顏色
plt.yticks(color = 'gray')
# 設置 y 軸百分比顯示,注意將 y 軸數據乘以 100
#ax.yaxis.set_major_formatter(ticker.FormatStrFormatter('%.2f%%'))
# 顏色,旋轉刻度
plt.xticks(color = 'gray',rotation = 15)
# 指定字體,大小,顏色
fontdict = {"family":"Times New Roman", 'size':12, 'color':'gray'} #Times New Roman, Arial
plt.title("random account value", fontdict = fontdict)
plt.xlabel("date(day)", fontdict = fontdict)
plt.ylabel("account value", fontdict = fontdict)
# 去掉邊框 top left right bottom
ax.spines['top'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['right'].set_visible(False)
# 設置 x 軸顏色
ax.spines['bottom'].set_color('lightgray')
#設置時間標簽顯示格式
ax.xaxis.set_major_formatter(mdate.DateFormatter('%Y-%m-%d'))
#設置時間刻度間隔
#timedelta = (xdate[-1] - xdate[0]) / 10 # 這種方式不能保證顯示最后一個日期
#plt.xticks(mdate.drange(xdate[0], xdate[-1], timedelta))
# 分成 10 份
delta = round(len(xdate) / 9)
plt.xticks([xdate[i*delta] for i in range(9)] + [xdate[-1]])
#通過修改tick_spacing的值可以修改x軸的密度
#ax.xaxis.set_major_locator(ticker.MultipleLocator(10))
# 去掉 y 軸刻度線,四個方向均可設置
plt.tick_params(left = 'off')
# 設置刻度的朝向,寬,長度
plt.tick_params(which = 'major', direction = 'out', width = 0.2, length = 5) # in, out or inout
# 設置刻度顯示在哪個方向上
#tick_params(labeltop='on',labelbottom='off',labelleft='off',labelright='off')
# 設置 y 軸方向的網絡線
plt.grid(axis = 'y', color = 'lightgray', linestyle = '-', linewidth = 0.5)
# 設置圖例 列寬:columnspacing=float (upper left)
plt.legend(loc = 'best', fontsize = 12, frameon=False, ncol = 1)
# 設置圖例字體顏色
#leg = 上一行 plt.legend 的返回值
#for line,text in zip(leg.legendHandles, leg.get_texts()):
# text.set_color(line.get_color())
fig.show()
# fig.savefig("test.png") # dpi = 150
if __name__ == '__main__':
# 構造繪圖數據
xdate, ycapital = Init()
plot(xdate, ycapital)
#max_drawdown_duration(xdate, ycapital)