Python双均线策略回测 1.择时策略简介 根据百度百科的解释,择时交易是指利用某种方法来判断大势的走势情况,是上涨还是下跌或者是盘整。如果判断是上涨,则买入持有;如果判断是下跌,则卖出清仓,如果是盘整,可以高抛低吸。 从量化角度来说,择时是通过资产的数据构造出买卖信号,按照买卖信号进行交易。回测就是实现这整个过程。 本文以最简单的双均线策略为例进行回测,具体规则如下: ·短均线上穿长均线(金叉),且当前无持仓:买入; ·短均线下穿长均线(死叉),且当前持仓,卖出; ·其他情况,保持之前仓位; ·可以考虑控制回撤,单次亏损超过一定幅度平仓。 2.回测评价 年化收益 回测起点到终点的累积收益年化,算复利或单利都可以,复利假设策略的盈利也会被用于投资,因此复利算出来结果会更好看一些。 夏普比 夏普比 = (策略期望收益率 - 无风险收益率)/策略波动率 夏普比综合衡量了收益和风险,是最广泛应用的指标。 胜率 统计胜率要先统计交易次数,然后计算所以交易中盈利次数占的比例 最大回撤率 回撤是策略从前期最高点到当前时点的亏损,最大回撤是所有回撤中的最大值,反映的是策略的最大可能损失。 单次最大亏损 所有单次交易中的最大亏损 策略阶段性表现 对策略时间段进行分割,统计每个时间段内上述指标的变化情况,本文按年进行分割,统计测年逐年的收益率和相对于基准的超额收益率。 其他 除此外,还有波动率、下行风险、索提诺比率等各种指标,python中有专门的模块可以计算各种指标,这里我们自己算出各种指标,供参考。
此外,还需要测试策略的稳定性,对策略中参数进行扰动,检验策略的敏感性情况,好的策略应该是参数不敏感的。 3.回测说明 回测标的:贵州茅台(600519.SH) 回测区间:2011.01.01-2021.10.08 代码说明:回测代码分成两块,一块是策略函数(Strategy),一块是评价函数(Performance),策略函数通过指数的收盘价构造信号,计算策略净值,统计策略的每笔交易的情况。评价函数根据策略净值和策略每笔交易的情况计算策略的上述各个指标。 数据说明:策略所用数据来源于“Tushare大数据社区”,https://waditu.com/,大家可自行注册学习(强烈推荐)!!! 4.策略代码 导入所需数据库
import tushare as ts
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from pyecharts.charts import *
from pyecharts import options as opts
from pyecharts.globals import ThemeType
#=1.以贵州茅台(600519.SH)为例获取数据=
token='你的token'
pro=ts.pro_api(token)
df_price=pro.daily(ts_code='600519.SH',start_date='20110101',end_date='20211008',fields='ts_code,trade_date,close,open,high,low')
df_price.index=pd.to_datetime(df_price.trade_date)
df_price['year']=df_price.index.year
df_price=df_price.sort_index()
print(df_price.iloc[0:5,:])
#=2.策略函数=
def Strategy(data_price,window_short=5,window_long=10,loss_ratio=0.20):
data_price=data_price.copy()
data_price.index=data_price.index.strftime('%Y%m%d')
data_price['sma']=data_price.close.rolling(window_short).mean()
data_price['lma']=data_price.close.rolling(window_long).mean()
data_price['position']=0
data_price['flag']=0
kline=Kline( init_opts=opts.InitOpts(width='1200px',height='600px',theme=ThemeType.DARK) )
kline.add_xaxis( data_price.index.tolist() )
y=list( data_price.loc[:,['open','close','low','high']].round(2).values )
y=[i.tolist() for i in y]
kline.add_yaxis( 'K线',y )
kline.set_series_opts(label_opts=opts.LabelOpts(is_show=False))
kline.set_global_opts(
xaxis_opts=opts.AxisOpts(is_scale=True,axislabel_opts=opts.LabelOpts(rotate=60)),
yaxis_opts=opts.AxisOpts( axislabel_opts=opts.LabelOpts(formatter="{value}") ),
datazoom_opts=[opts.DataZoomOpts(type_='inside')],
title_opts=opts.TitleOpts(title="贵州茅台(600519.SH)K线及均线",pos_left='45%'),
legend_opts=opts.LegendOpts(pos_right="35%",pos_top="5%"),
tooltip_opts=opts.TooltipOpts(trigger="axis", axis_pointer_type="cross")
)
line=Line()
line.add_xaxis( data_price.index.tolist() )
line.add_yaxis( 'MA5',data_price.sma.round(2).tolist(),is_smooth=True )
line.add_yaxis( 'MA10',data_price.lma.round(2).tolist(),is_smooth=True )
line.set_series_opts(label_opts=opts.LabelOpts(is_show=False))
line.set_global_opts(
datazoom_opts=[opts.DataZoomOpts(type_='inside')],
legend_opts=opts.LegendOpts(pos_right="20%",pos_top="5%"),
tooltip_opts=opts.TooltipOpts(trigger="axis", axis_pointer_type="cross")
)
kline.overlap(line)
kline.render("16.1 贵州茅台(600519.SH)K线及均线.html")
Buy=[]
Sell=[]
price_in=1
for i in range( max(1,window_long),data_price.shape[0]-1 ):
if (data_price['position'][i]==0)and(data_price['sma'][i-1]<data_price['lma'][i-1])and(data_price['sma'][i]>data_price['lma'][i]):
data_price.ix[i,'flag']=1
data_price.ix[i+1,'position']=1
date_in=data_price.index[i]
price_in=data_price.ix[i,'close']
Buy.append( [date_in,price_in,'金叉买入'] )
elif ( data_price['position'][i]==1 ) & ( 1-data_price['close'][i]/price_in>loss_ratio ):
data_price.ix[i,'flag']=-1
data_price.ix[i+1,'position']=0
date_out=data_price.index[i]
price_out=data_price.ix[i,'close']
Sell.append( [date_out,price_out,'止损平仓'] )
elif ( data_price['position'][i]==1 ) & ( data_price['sma'][i-1]>data_price['lma'][i-1] ) & ( data_price['sma'][i]<data_price['lma'][i] ):
data_price.ix[i,'flag']=-1
data_price.ix[i+1,'position']=0
date_out=data_price.index[i]
price_out=data_price.ix[i,'close']
Sell.append( [date_out,price_out,'死叉卖出'] )
else:
data_price.ix[i+1,'position']=data_price.ix[i,'position']
p1=pd.DataFrame( Buy,columns=['买入日期','买入价格','备注'] )
p2=pd.DataFrame( Sell,columns=['卖出日期','卖出价格','备注'] )
transactions=pd.concat( [p1,p2],axis=1 )
data_price = data_price.iloc[window_long:,:]
data_price['ret'] = data_price.close.pct_change(1).fillna(0)
data_price['nav'] = (1 + data_price.ret*data_price.position).cumprod()
data_price['benchmark'] = data_price.close/data_price.close[0]
return transactions,data_price
#=3.评价函数=
def performance( transactions,strategy ):
N = 250
rety = strategy.nav[strategy.shape[0] - 1]**(N/strategy.shape[0]) - 1
Sharp = (strategy.ret*strategy.position).mean()/(strategy.ret*strategy.position).std()*np.sqrt(N)
VictoryRatio = ( (transactions['卖出价格'] - transactions['买入价格'])>0 ).mean()
DD = 1 - strategy.nav/strategy.nav.cummax()
MDD = max(DD)
maxloss = min(transactions['卖出价格']/transactions['买入价格'] - 1)
trade_count=strategy.flag.abs().sum()/strategy.shape[0]*20
print('------------------------------')
print('夏普比率为:',round(Sharp,2))
print('年化收益率为:{}%'.format(round(rety*100,2)))
print('胜率为:{}%'.format(round(VictoryRatio*100,2)))
print('最大回撤率为:{}%'.format(round(MDD*100,2)))
print('单次最大亏损为:{}%'.format(round(-maxloss*100,2)))
print('月均交易次数为:{}(买卖合计)'.format(round(trade_count,2)))
print('------------------------------')
result = {'Sharp':Sharp,
'RetYearly':rety,
'WinRate':VictoryRatio,
'MDD':MDD,
'maxlossOnce':-maxloss,
'num':round(strategy.flag.abs().sum()/strategy.shape[0],1)}
result = pd.DataFrame.from_dict(result,orient='index').T
print(result)
nav_peryear = strategy.nav.groupby(strategy.year).last()/strategy.nav.groupby(strategy.year).first() - 1
benchmark_peryear = strategy.benchmark.groupby(strategy.year).last()/strategy.benchmark.groupby(strategy.year).first() - 1
excess_ret = nav_peryear - benchmark_peryear
result_peryear = pd.concat([nav_peryear,benchmark_peryear,excess_ret],axis = 1)
result_peryear.columns = ['strategy_ret','bench_ret','excess_ret']
result_peryear = result_peryear.T
print('------------------------------')
print(result_peryear)
print('------------------------------')
line1=Line( init_opts=opts.InitOpts(width='1200px',height='600px',theme=ThemeType.DARK) )
line1.add_xaxis( strategy.index.tolist() )
line1.add_yaxis( '策略净值',strategy.nav.round(2).to_list(),yaxis_index=0,is_smooth=True )
line1.add_yaxis( '基准净值',strategy.benchmark.round(2).to_list(),yaxis_index=0,is_smooth=True )
line1.extend_axis(yaxis=opts.AxisOpts( min_=0.8,axislabel_opts=opts.LabelOpts(formatter="{value}") ))
line1.set_series_opts(label_opts=opts.LabelOpts(is_show=True))
line1.set_global_opts(
xaxis_opts=opts.AxisOpts(is_scale=True,axislabel_opts=opts.LabelOpts(rotate=60)),
yaxis_opts=opts.AxisOpts( min_=0.75,axislabel_opts=opts.LabelOpts(formatter="{value}") ),
datazoom_opts=[opts.DataZoomOpts(type_='inside')],
title_opts=opts.TitleOpts(title="双均线择时策略回测",pos_left='45%'),
legend_opts=opts.LegendOpts(pos_right="35%",pos_top="5%"),
tooltip_opts=opts.TooltipOpts(trigger="axis", axis_pointer_type="cross")
)
line2=Line()
line2.add_xaxis( strategy.index.tolist() )
line2.add_yaxis( '净值之比',(strategy.nav/strategy.benchmark).round(2).tolist(),yaxis_index=1,is_smooth=True )
line2.set_global_opts(
datazoom_opts=[opts.DataZoomOpts(type_='inside')],
legend_opts=opts.LegendOpts(pos_right="20%",pos_top="5%"),
tooltip_opts=opts.TooltipOpts(trigger="axis", axis_pointer_type="cross")
)
line1.overlap(line2)
line1.render("16.2 双均线择时策略回测.html")
return result,result_peryear
#=4.结果展示=
trans,data=Strategy(df_price,window_short=25,window_long=50,loss_ratio=0.10)
print('交易记录:\n',trans)
print('结果展示:\n',data)
performance( trans,data )
|