参考资料:
https://bbs.pinggu.org/thread-4745852-1-1.html
【量化小讲堂-Python、Pandas系列15】完整策略框架:以均线策略为例
这个是邢不行老师的量化系列课程,有源码分享,很赞!强烈推荐!
https://www.jianshu.com/p/363aa2dd3441
Python计算量化策略评估指标
微信公众号:Python金融量化(id:tkfy920),专注于分享python在金融领域的应用。推荐推荐!
下图是 JoinQuant 聚宽量化平台上的回测界面,这个学习笔记的内容就是如何做出这样一个图!
1、导入原始数据
from __future__ import division #导入精确除法
import pandas as pd
import warnings # warning通常用于提示用户一些错误或者过时的用法。
warnings.filterwarnings("ignore")
import numpy as np
import tushare as ts
import matplotlib.pyplot as plt
pro = ts.pro_api()
api = ts.pro_api('29acf1ff24d0c5539a992821efac9b2037d42610f4cb0724ea93e084')
stock_data = ts.pro_bar(pro_api=api, ts_code='002916.SZ', adj='qfq', start_date='20171213', end_date='20190128')[['ts_code','trade_date','open','close','pct_chg']]
stock_data['pct_chg'] = stock_data['pct_chg'].div(100)
stock_data.axes
stock_data.index = pd.DatetimeIndex(stock_data.index)
stock_data.sort_values(by='trade_date', inplace=True)
def simple_ma(stock_data, window_short=5, window_long=60):
"""
:param stock_data: 股票数据集
:param window_short: 较短的窗口期
:param window_long: 较长的窗口期
:return: 当天收盘时持有该股票的仓位数据
最简单的均线策略逻辑:
短期均线上穿长期均线,且第二天开盘没有涨停,则以第二天开盘价全仓买入;
短期均线下穿长期均线,且第二天开盘没有跌停,则以第二天开盘价全仓卖出。
"""
# 计算短期和长期的移动平均线
stock_data['ma_short'] = pd.Series.rolling(stock_data['close'], window=window_short, min_periods=5).mean()
stock_data['ma_long'] = pd.Series.rolling(stock_data['close'], window=window_long, min_periods=60).mean()
# 出现买入信号而且第二天开盘没有涨停
stock_data.ix[(stock_data['ma_short'].shift(1) > stock_data['ma_long'].shift(1)) &
(stock_data['open'] < stock_data['close'].shift(1) * 1.097), 'position'] = 1
# 出现卖出信号而且第二天开盘没有跌停
stock_data.ix[(stock_data['ma_short'].shift(1) < stock_data['ma_long'].shift(1)) &
(stock_data['open'] > stock_data['close'].shift(1) * 0.903), 'position'] = 0
stock_data['position'].fillna(method='ffill', inplace=True)
stock_data['position'].fillna(0, inplace=True)
return stock_data
stock_data = simple_ma(stock_data, window_short=5, window_long=60)
1、根据每日仓位计算策略的日收益率
def account(df, slippage=1.0 / 1000, commision_rate=2.0 / 1000):
"""
:param df: 股票账户数据集
:param slippage: 买卖滑点 默认为1.0 / 1000 #滑点是指下单的点位和最后成交的点位有差距。
:param commision_rate: 手续费 默认为2.0 / 1000
:return: 返回账户资产的日收益率和日累计收益率的数据集
"""
# 第一天没有交易信号产生
df.ix[0, 'capital_rtn'] = 0
# 当加仓时,计算当天资金曲线涨幅 capital_rtn.capital_rtn = 今天开盘新买入的 position 在今天的涨幅(扣除手续费) + 昨天的position在今天涨幅
df.ix[df['position'] > df['position'].shift(1), 'capital_rtn'] = (df['close'] / df['open'] - 1) * (1 - slippage - commision_rate) * (df['position'] - df['position'].shift(1))
+ df['pct_chg'] * df['position'].shift(1)
# 当减仓时,计算当天资金曲线涨幅 capital_rtn.capital_rtn = 今天开盘卖出的 position 在今天的涨幅(扣除手续费) + 还剩的position在今天的涨幅
df.ix[df['position'] < df['position'].shift(1), 'capital_rtn'] = (df['open'] / df['close'].shift(1) - 1) * (1 - slippage - commision_rate) * (df['position'].shift(1) - df['position'])
+ df['pct_chg'] * df['position']
# 当仓位不变时,当天的 capital_rtn 是当天的 pct_chg * position
df.ix[df['position'] == df['position'].shift(1), 'capital_rtn'] = df['pct_chg'] * df['position']
return df
stock_data = account(stock_data, slippage=1.0 / 1000, commision_rate=2.0 / 1000)
def get_stock_data(stock_data, index_code='000300.SH', start_date='20171213', end_date='20190128'):
"""
:param stock_data: 股票账户数据集
:param index_code: 指数代码,e.g. 沪深300指数:'000300.SH'
:param start_date: 回测开始日期,e.g. '20171213'
:param end_date: 回测结束日期, e.g. '20190128'
:return: 函数返回股票和基准的数据集
"""
# 选取在日期范围内的股票数据序列并按日期排序:
stock_data_subset = stock_data[['ts_code', 'trade_date', 'close', 'pct_chg', 'capital_rtn']]
# 获取基准指数数据序列并按日期排序:
benchmark = pro.index_daily(ts_code=index_code, start_date=start_date, end_date=end_date)
benchmark['pct_chg'] = benchmark['pct_chg'].div(100)
benchmark = benchmark[['trade_date', 'pct_chg']]
benchmark.sort_values(by='trade_date', inplace=True)
benchmark['trade_date'] = pd.DatetimeIndex(benchmark['trade_date'])
benchmark.set_index('trade_date', inplace=True)
return stock_data_subset, benchmark
data = get_stock_data(stock_data, index_code='000300.SH', start_date='20171213', end_date='20190128')
stock_data_subset = data[0]
benchmark = data[1]
code_list = list(stock_data_subset['ts_code'].unique())
3、计算策略和基准在回测期间的累计收益率并画图:
def cumulative_return(stock_data_subset, benchmark):
"""
:param stock_data_subset: 股票数据集
:param benchmark: 基准的数据集
:return: 输出策略和基准的累计收益率曲线
"""
df = stock_data_subset['capital_rtn']
df1 = benchmark['pct_chg']
stock_data_subset['strategy_rtn_line'] = (df + 1).cumprod() - 1
benchmark['benchmark_rtn_line'] = (df1 + 1).cumprod() - 1
SR = stock_data_subset[['strategy_rtn_line']]
BR = benchmark[['benchmark_rtn_line']]
df_concat = pd.concat([SR,BR], axis=1, join='outer')
df_concat.fillna(method = 'ffill', inplace=True)
df_concat.plot(kind='line', figsize=(12,5), title='return_line')
return plt.show()
1、计算策略累计收益率、年化收益率函数、基准累计收益率
def all_return(stock_data_subset, benchmark):
"""
:param stock_data_subset:策略日收益率数据
:param benchmark:基准日收益率数据
:return: 输出在回测期间的策略累计收益率、年化收益率,基准累计收益率
"""
# 计算策略累计收益率:
strategy_ret = stock_data_subset[['capital_rtn']].apply(lambda x: (x + 1.0).prod() - 1.0)
SR = pd.DataFrame(float(strategy_ret), columns=['策略收益'], index=code_list)
# 计算策略年化收益率:
annual_ret = pow(1+float(strategy_ret), 250/len(stock_data_subset))-1
AR = pd.DataFrame(float(annual_ret), columns=['策略年化收益'], index=code_list)
# 计算基准累计收益率
benchmark_ret = benchmark[['pct_chg']].apply(lambda x: (x + 1.0).prod() - 1.0)
BR = pd.DataFrame(float(benchmark_ret), columns=['基准收益'], index=code_list)
return SR, AR, BR
all_return = all_return(stock_data_subset, benchmark)
strategy_ret = all_return[0]
annual_ret = all_return[1]
benchmark_ret = all_return[2]
2、计算 Alpha 和 Beta
# 计算贝塔的函数:
def beta(stock_data_subset, benchmark):
"""
:param stock_data_subset:策略日收益率数据
:param benchmark:基准日收益率数据
:return: 输出在回测期间的策略的贝塔值
"""
# 计算协方差、方差:
cov = stock_data_subset['capital_rtn'].cov(benchmark['pct_chg'])
var = benchmark['pct_chg'].var()
# 计算贝塔:
beta = cov / var
beta = pd.DataFrame(beta, columns=['贝塔系数'], index=code_list)
return beta
beta = beta(stock_data_subset, benchmark)
# 计算 Alpha的函数:
def alpha(stock_data_subset, benchmark):
"""
:param stock_data_subset:策略日收益率数据
:param benchmark:基准日收益率数据
:return: 输出在回测期间的策略的 Alpha
"""
# 计算贝塔值:
cov = stock_data_subset['capital_rtn'].cov(benchmark['pct_chg'])
var = benchmark['pct_chg'].var()
beta = cov / var
# 计算策略的年化收益率:
strategy_ret = stock_data_subset[['capital_rtn']].apply(lambda x: (x + 1.0).prod() - 1.0)
annual_stock = pow(1+float(strategy_ret), 250/len(stock_data_subset))-1
# 计算基准的年化收益率:
index_ret = benchmark[['pct_chg']].apply(lambda x: (x + 1.0).prod() - 1.0)
annual_index = pow(1+float(index_ret), 250 / len(benchmark)) - 1
# 无风险利率:取10年期国债的到期年化收益率,中国债券信息网2月15日更新为:3.08%
rf = 0.0308
# 计算 Alpha值:
alpha = annual_stock - (rf + beta * (annual_index - rf))
alpha = pd.DataFrame(alpha, columns=['Alpha系数'], index=code_list)
return alpha
alpha = alpha(stock_data_subset, benchmark)
3、计算夏普比率和信息比率
# 计算夏普比率函数:
def sharpe_ratio(stock_data_subset):
"""
:param stock_data_subset:策略日收益率数据
:return: 输出夏普比率
"""
# 无风险利率:取10年期国债的到期年化收益率,中国债券信息网2月15日更新为:3.08%
rf = 0.0308
# 计算策略和无风险利率之间的日超额收益率:
ex_return = stock_data_subset['capital_rtn'] - rf/250
# 计算夏普比率(年化):
sharpe = np.sqrt(len(ex_return)) * ex_return.mean()/ex_return.std()
sharpe = pd.DataFrame(sharpe, columns=['Sharpe'], index=code_list)
return sharpe
sharpe = sharpe_ratio(stock_data_subset)
def info_ratio(stock_data_subset, benchmark):
"""
:param stock_data_subset:策略日收益率数据
:param benchmark:基准日收益率数据
:return: 输出在回测期间的策略的信息比率
"""
# 计算策略和无风险利率之间的日超额收益率:
ex_return = stock_data_subset['capital_rtn'] - benchmark['pct_chg']
# 计算信息比率(年化):
info = np.sqrt(len(ex_return)) * ex_return.mean()/ex_return.std()
info = pd.DataFrame(info, columns=['Info'], index=code_list)
return info
info = info_ratio(stock_data_subset, benchmark)
4、计算胜率
# 计算策略在回测期间的胜率函数:
def win_rate(stock_data_subset):
"""
:param stock_data_subset:策略日收益率数据
:return: 输出策略在回测期间的胜率
"""
df = stock_data_subset[['capital_rtn']]
# 计算策略胜率
win_rate = len(df[df['capital_rtn'] > 0]) / len(df[df['capital_rtn'] != 0])
win_rate = pd.DataFrame(float(win_rate), columns=['胜率'], index=code_list)
return win_rate
win_rate = win_rate(stock_data_subset)
5、计算最大回撤
# 计算最大回撤函数:
def max_drawdown(stock_data_subset):
"""
:param stock_data_subset:策略日收益率数据
:return: 输出最大回撤及开始日期和结束日期
"""
# 取出数据集中股票的qfq收盘价
df = stock_data_subset[['trade_date', 'close']]
# 计算当日之前的账户最大价值
df['max2here'] = df['close'].cummax()
# 计算当日的回撤
df['dd2here'] = (df['max2here'] - df['close'])/df['max2here']
# 计算最大回撤和结束时间
temp = df.sort_values(by='dd2here').iloc[-1][['trade_date', 'dd2here']]
max_dd = temp['dd2here']
max_dd = "%.2f%%" % (max_dd * 100)
end_date = temp['trade_date']
# 计算开始时间
df = df[df['trade_date'] <= end_date]
start_date = df.sort_values(by='close').iloc[-1]['trade_date']
md = pd.DataFrame(max_dd, columns=['最大回撤'], index=code_list)
print ('最大回撤为:%s, 开始日期:%s, 结束日期:%s' % (max_dd, start_date, end_date))
return md
md = max_drawdown(stock_data_subset)
6、所有指标合并成一个表格
indicators = pd.concat([strategy_ret, annual_ret, benchmark_ret, beta, alpha, sharpe, info, win_rate, md],
axis=1, join='outer')
# 结果保留三位小数:
indicators = indicators.round(3)
cumulative_return(stock_data_subset, benchmark)
print (indicators)