1 pandas计算策略评估指标
本章节介绍关于金融量化分析的一些基本概念,如年华收益率、基准年化收益率、最大回撤等。在严格的量化策略回测中,这些概念都是需要掌握并熟练使用的,这样能够全面的评估量化策略。市面上,很多策略回测笼统地使用所谓的“胜率”来确定策略的好坏,这是一种非常业余而且不可靠的分析方法。在衡量基金业绩好坏的时候,大部分人也只是看基金的年化收益,忽略了基金的风险情况(波动率)。市场中充斥着大量类似的业余的分析方法,这些方法导致很多所谓的回测看起来很美好,其实在统计上根本站不住脚,即所谓的“统计骗局”。 因此在量化回测过程中,需要从收益、稳定性、胜率、风险四个方面来综合评估策略好坏。熟练掌握评估指标,还能够帮助大家识别一些经典“骗局”,如
只展示基金的年化收益,而不提基金的波动率或者最大回撤或使用周收益率来计算夏普比率,而不是使用日收益率来计算
1.1 数据准备
为了帮助大家深入了解指标计算逻辑和实现方式,本章节采用指标讲解和代码实现相结合的方式进行讲解。再具体计算过程中,选择的目标标的是贵州茅台(600519.SH)、工商银行(601398.SH)、中国平安(601318.SH),策略基准是沪深300指数(000300.XSHG),策略采用最简单的方式:买入持有。持有周期为20180101 - 20221231,共1826个自然日。数据获取如下所示
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import tushare as ts
%matplotlib inline
# 无视warning
import warnings
warnings.filterwarnings("ignore")
# 正常显示画图时出现的中文和负号
from pylab import mpl
mpl.rcParams['font.sans-serif']=['SimHei']
mpl.rcParams['axes.unicode_minus']=False
#起始和结束日期可以自行输入,否则使用默认
def get_data(code, start_date, end_date):
# 配置 tushare token
my_token = '***'
pro = ts.pro_api(my_token)
df = pro.daily(ts_code=code, start_date=start_date, end_date=end_date)
df.index = pd.to_datetime(df.trade_date)
return df.close
#以上证综指、贵州茅台、工商银行、中国平安为例
stocks={
'600519.SH':'贵州茅台',
'601398.SH':'工商银行',
'601318.SH':'中国平安'
}
df = pd.DataFrame()
for code,name in stocks.items():
df[name] = get_data(code, '20180101', '20221231')
# 按照日期正序
df = df.sort_index()
# 本地读入沪深300合并
df_base = pd.read_csv('000300.XSHG_2018_2022.csv')
df_base.index = pd.to_datetime(df_base.trade_date)
df['沪深300'] = df_base['close']
000300.XSHG_2018_2022.csv 获取
1.2 净值曲线
净值曲线是一组时间序列的曲线,其含义表示为股票或基金在不同时间的价值相对于期初的价值的倍数。 再具体分析中,我们可以将期初价格定位1,那么如果你在当前时间点手里的净值是1.4,就意味着当前时间的资产价值是期初的1.4倍。
# 以第一交易日2018年1月1日收盘价为基点,计算净值并绘制净值曲线
df_worth = df / df.iloc[0]
df_worth.plot(figsize=(15,6))
plt.title('股价净值走势', fontsize=10)
plt.xticks(pd.date_range('20180101','20221231',freq='Y'),fontsize=10)
plt.show()
1.3 收益率指标
# 区间累计收益率(绝对收益率)
total_return = df_worth.iloc[-1]-1
total_return = pd.DataFrame(total_return.values,columns=['累计收益率'],index=total_return.index)
total_return
# 年化收益率
annual_return = pd.DataFrame((1 + total_return.values) ** (252 / 1826) - 1,columns=['年化收益率'],index=total_return.index)
annual_return
1.4 波动率指标
df_return = df / df.shift(1) - 1
df_return = ((df_return.iloc[1:] - df_return.mean()) ** 2)
volatility = pd.DataFrame(np.sqrt(df_return.sum() * 252 / (1826-1)),columns=['波动率'],index=total_return.index)
volatility
1.5 最大回撤
def max_drawdown_cal(df):
md = ((df.cummax() - df)/df.cummax()).max()
return round(md, 4)
max_drawdown = {}
stocks={
'600519.SH':'贵州茅台',
'601398.SH':'工商银行',
'601318.SH':'中国平安',
'000300.XSHG': '沪深300'
}
for code,name in stocks.items():
max_drawdown[name]=max_drawdown_cal(df[name])
max_drawdown = pd.DataFrame(max_drawdown,index=['最大回撤']).T
max_drawdown
1.6 Alpha系数和Beta系数
from scipy import stats
#计算每日收益率 收盘价缺失值(停牌),使用前值代替
rets=(df.iloc[:,:4].fillna(method='pad')).apply(lambda x:x/x.shift(1)-1)[1:]
#市场指数为x,个股收益率为y
x = rets.iloc[:,3].values
y = rets.iloc[:,:3].values
capm = pd.DataFrame()
alpha = []
beta = []
for i in range(3):
b, a, r_value, p_value, std_err=stats.linregress(x,y[:,i])
#alpha转化为年化
alpha.append(round(a*250,3))
beta.append(round(b,3))
capm['alpha']=alpha
capm['beta']=beta
capm.index=rets.columns[:3]
#输出结果:
capm
1.7 夏普比率
# 超额收益率以无风险收益率为基准 假设无风险收益率为年化3%
ex_return=rets - 0.03/250
# 计算夏普比率
sharpe_ratio=np.sqrt(len(ex_return))*ex_return.mean()/ex_return.std()
sharpe_ratio=pd.DataFrame(sharpe_ratio,columns=['夏普比率'])
sharpe_ratio
1.8 信息比率
###信息比率
ex_return = pd.DataFrame()
ex_return['贵州茅台']=rets.iloc[:,0]-rets.iloc[:,3]
ex_return['工商银行']=rets.iloc[:,1]-rets.iloc[:,3]
ex_return['中国平安']=rets.iloc[:,2]-rets.iloc[:,3]
#计算信息比率
information_ratio = np.sqrt(len(ex_return))*ex_return.mean()/ex_return.std()
#信息比率的输出结果
information_ratio = pd.DataFrame(information_ratio,columns=['信息比率'])
information_ratio
2 聚宽平台量化回测实践
2.1 平台介绍
聚宽(https://www.joinquant.com/) 成立于2015年5月,是一家量化交易平台,为投资者提供做量化交易的工具与服务,帮助投资者更好地做量化交易。 整体来看,聚宽具有以下几点优势
*聚宽让做量化交易的成本极大降低
*提供多种优质的便于取用的数据
*提供投资研究功能,便于自由地统计、研究、学习等
*提供多种的策略评价指标与评价维度
*支持多种策略的编写、回测、模拟、实盘
*具备丰富且活跃的量化社区,可以发帖、学习、比赛等。
2.2 策略实现
本部分将介绍如何在聚宽平台实现一个双均线策略(具体参照ch05择时策略),并且在聚宽平台上进行回测, 来测试整体收益率。
策略代码如下,核心点有:
*选择标的为:002594.XSHE 比亚迪
*选择基准为:000300.XSHG 沪深300
*策略为:当5日线金叉10日线,全仓买入;当5日线死叉10日线全仓卖出。
# 导入函数库
from __future__ import (absolute_import, division, print_function, unicode_literals)
import datetime
import pymysql
import pandas as pd
import backtrader as bt
import tushare as ts
import numpy as np
# 数据获取(从Tushare中获取数据)
"""
数据获取一般都是通过连接数据库从数据库中读取,对于不了解数据库来源的新手可以从Tushare中直接获取数据
"""
def get_data(stock_code):
"""
stock_code:股票代码,类型: str
return: 股票日线数据,类型: DataFrame
"""
token = 'Tushare token' # 可通过进入个人主页-接口TOKEN获得
ts.set_token(token)
pro = ts.pro_api(token)
data_daily = pro.daily(ts_code = stock_code, start_date='20180101', end_date='20230101')
data_daily['trade_date'] = pd.to_datetime(data_daily['trade_date'])
data_daily = data_daily.rename(columns={'vol': 'volume'})
data_daily.set_index('trade_date', inplace=True)
data_daily = data_daily.sort_index(ascending=True)
dataframe = data_daily
data_daily['openinterest'] = 0
dataframe['openinterest'] = 0
data = bt.feeds.PandasData(dataname=dataframe,
fromdate=datetime.datetime(2018, 1, 1),
todate=datetime.datetime(2023, 1, 1)
)
return data
# 双均线策略实现
class DoubleAverages(bt.Strategy):
# 设置均线周期
params = (
('period_data5', 5),
('period_data10', 10)
)
# 日志输出
def log(self, txt, dt=None):
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
def __init__(self):
# 初始化数据参数
self.dataclose = self.datas[0].close # 定义变量dataclose,保存收盘价
self.order = None # 定义变量order,用于保存订单
self.buycomm = None # 定义变量buycomm,记录订单佣金
self.buyprice = None # 定义变量buyprice,记录订单价格
self.sma5 = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.period_data5) # 计算5日均线
self.sma10 = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.period_data10) # 计算10日均线
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]: # 若订单提交或者已经接受则返回
return
if order.status in [order.Completed]:
if order.isbuy():
self.log(
'Buy Executed, Price: %.2f, Cost: %.2f, Comm: %.2f' %
(order.executed.price, order.executed.value, order.executed.comm))
self.buyprice = order.executed.price
self.buycomm = order.executed.comm
else:
self.log('Sell Executed, Price: %.2f, Cost: %.2f, Comm: %.2f' %
(order.executed.price, order.executed.value, order.executed.comm))
self.bar_executed = len(self)
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('Order Canceled/Margin/Rejected')
self.order = None
def notify_trade(self, trade):
if not trade.isclosed: # 若交易未关闭则返回
return
self.log('Operation Profit, Total_Profit %.2f, Net_Profit: %.2f' %
(trade.pnl, trade.pnlcomm)) # pnl表示盈利, pnlcomm表示手续费
def next(self): # 双均线策略逻辑实现
self.log('Close: %.2f' % self.dataclose[0]) # 打印收盘价格
if self.order: # 检查是否有订单发送
return
if not self.position: # 检查是否有仓位
if self.sma5[0] > self.sma10[0]:
self.log('Buy: %.2f' % self.dataclose[0])
self.order = self.buy()
else:
if self.sma5[0] < self.sma10[0]:
self.log('Sell: %.2f' % self.dataclose[0])
self.order = self.sell()
if __name__ == '__main__':
cerebro = bt.Cerebro() # 创建策略容器
cerebro.addstrategy(DoubleAverages) # 添加双均线策略
data = get_data('000001.SZ')
cerebro.adddata(data) # 添加数据
cerebro.broker.setcash(10000.0) # 设置资金
cerebro.addsizer(bt.sizers.FixedSize, stake=100) # 设置每笔交易的股票数量
cerebro.broker.setcommission(commission=0.01) # 设置手续费
print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue()) # 打印初始资金
cerebro.run() # 运行策略
print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue()) # 打印最终资金
cerebro.plot()
)