目录
概念
计算方法
1. 将收益率做成时间序列
2. 计算财富指数(也就是净值)【PS:初始净值为1】
3. 计算上一个最高点
4. 计算回撤率
5. 找出最大回撤
日期 | 收益率 | 财富指数(净值) | 上一个高点 | 回撤率 |
02-19 | 0.014483 | 1*(1+0.014483) =1.014483 | 1.014483 | 0 |
02-20 | -0.010259 | 1.014483*(1-0.010259) =1.004075 | 1.014483 | -0.010259 |
02-21 | -0.022635 | 1.004075*(1-0.022635) =0.981348 | 1.014483 | -0.032662 |
02-24 | -0.047500 | 0.981348*(1-0.047500) =0.934734 | 1.014483 | -0.078611 |
02-25 | -0.033872 | 0.934734*(1-0.033872) =0.903072 | 1.014483 | -0.109820 |
02-26 | 0.015864 | 0.903072*(1+0.015864) =0.917398 | 1.014483 | -0.095699 |
一、数据为收盘价,计算最大回撤
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
df_aapl = pd.read_csv('AAPL.csv',encoding='utf-8')
df_aapl['ret'] = df_aapl['Close'].pct_change()
df_aapl['Date'] = pd.to_datetime(df_aapl['Date'])
df_aapl.set_index('Date',inplace=True)
df = df_aapl.iloc[-504:]
df.head()
# 构建财富指数
wealth = 1*(1+df['ret']).cumprod()
wealth.head()
wealth.plot()
# 计算上一个最高点
previos_max = wealth.cummax()
previos_max.plot()
# 计算回撤率
drawdowns = (wealth-previos_max)/previos_max
drawdowns.plot()
# 找出最大回撤
drawdowns.min()
# out: -0.30668685958066383
# 最大回撤对应的日期
drawdowns.idxmin()
# out: Timestamp('2020-03-23 00:00:00')
将上面的计算过程组合成一个函数
def drawdown(return_series:pd.Series):
'''
把一个时间序列做成最大回撤的表格
表格字段为:
财富指数
上一个最大值
回撤率
'''
wealth = 1*(1+return_series).cumprod()
previos_max = wealth.cummax()
drawdowns = (wealth-previos_max)/previos_max
return pd.DataFrame({'wealth':wealth,
'previos_max':previos_max,
'drawdowns':drawdowns})
res_df = drawdown(df['ret'])
res_df.head()
res_df.plot(y=['wealth','previos_max'],figsize=(8,4))
res_df.plot(y='drawdowns',figsize=(8,4),color='k')
二、数据为净值,计算最大回撤
前置:
文章中用到的数据
链接:https://pan.baidu.com/s/1rKLM45dq_xIKxcI54Nq0qg
提取码:c298
最终效果图:
计算过程(jupyter notebook):
import matplotlib.pyplot as plt
import pylab as pl
import pandas as pd
import math
df = pd.read_csv('./temptemp.csv',encoding='utf-8')
df.head()
# 绘制折线图,标记回撤区域
def draw_trend_and_withdraw(xs,ys,title,res_points):
plt.figure(figsize=(20,10))
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
xs00 = range(len(xs))
plt.plot(xs00,ys)
# 只显示10个x轴刻度
xs00_=[]
xs_ =[]
for i in range(0,len(xs),math.floor(len(xs)/10)):
xs00_.append(i)
xs_.append(xs[i])
plt.xticks(xs00_,xs_,rotation=30)
plt.title(title)
for item in res_points:
min_x = item['min_x']
min_y = item['min_y']
max_x = item['max_x']
max_y = item['max_y']
show_min_str = item['show_min_str']
show_max_str = item['show_max_str']
withdraw = item['withdraw']
plt.scatter(min_x, min_y, color='r') # 标记最低点
plt.scatter(max_x, max_y, color='r') # 标记最高点
plt.annotate(show_min_str, xytext=(min_x, min_y), xy=(min_x, min_y)) # 标记提示
plt.annotate(show_max_str, xytext=(max_x, max_y), xy=(max_x, max_y)) # 标记提示
plt.plot([min_x, max_x], [min_y, max_y], color='b', linestyle='--') # 连接最低净值点和最高净值点
plt.annotate(withdraw, xytext=((max_x + min_x) / 2, (max_y + min_y) / 2), xy=((max_x + min_x) / 2, (max_y + min_y) / 2)) # 标记提示
pass
plt.show()
df['o_date'] = df['date']
df['o_date'] = pd.to_datetime(df['o_date'])
df.dropna(inplace=True)
df.sort_values(by='o_date',ascending=True,inplace=True)
df['count'] = range(len(df)) # count用于标识折线图x轴的位置
res_list = []
temp_hv = None # 记录当前最大值
temp_hv_date = None # 记录当前最大值对应的日期
temp_hv_loc = None # 记录当前最大值所在的位置
temp_lv = None # 记录当前最小值
temp_lv_date = None # 记录当前最小值对应的日期
temp_lv_loc = None # 记录当前最小值所在的位置
temp_duration = None # 最大值与最小值之间的差
for i,row in df.iterrows():
if temp_hv is None:
temp_hv = row['value']
temp_hv_date = row['date']
temp_hv_loc = row['count']
temp_lv = row['value']
temp_lv_date = row['date']
temp_lv_loc = row['count']
temp_duration = 0
else:
if row['value'] > temp_hv:
# 如果值大于此前的最大值,说明要进行新一轮的回撤,记录当前这一轮回撤的信息
# 计算回撤百分比
temp_pct = (temp_duration/temp_hv)*100
res_list.append([temp_hv_loc,temp_hv_date,temp_hv,temp_lv_loc,temp_lv_date,temp_lv,temp_pct,temp_duration])
temp_hv = row['value']
temp_hv_date = row['date']
temp_hv_loc = row['count']
temp_lv = row['value']
temp_lv_date = row['date']
temp_lv_loc = row['count']
temp_duration = 0
pass
else:
if row['value'] <= temp_lv:
# 当前值小于此前的最小值,说明数值还在往下走
temp_lv = row['value']
temp_lv_date = row['date']
temp_lv_loc= row['count']
temp_duration = temp_hv-temp_lv
# 最后一次的回撤:最后的价格一直没有超过此前一次的最高价,最后一次的回撤会无法触发记录的条件
temp_pct = (temp_duration/temp_hv)*100
res_list.append([temp_hv_loc,temp_hv_date,temp_hv,temp_lv_loc,temp_lv_date,temp_lv,temp_pct,temp_duration])
# 抽取出回撤大于等于10%的区间,并在图中标出
final_list = []
for item in res_list:
if item[-2]>=10:
pct_ = round(item[-2],2)
final_list.append({
"min_x":item[0],
"min_y":item[2],
"max_x":item[3],
"max_y":item[5],
"show_min_str":item[1],
"show_max_str":item[4],
"withdraw":str(pct_)+'%'
})
xs = df['date'].values.tolist()
ys = df['value'].values.tolist()
title_str = '回撤大于等于10%'
draw_trend_and_withdraw(xs,ys,title_str,final_list)