Python双均线策略回测(2021-10-12)

Posted EricWangP

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python双均线策略回测(2021-10-12)相关的知识,希望对你有一定的参考价值。

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):
    #df_price:价格数据;
    #window_short:短均线周期,默认为5;
    #window_long:长均线周期,默认为10;
    #lossratio:止损率,默认为1%,即开仓后下跌超过1%止损。

    ##2.1绘制K线和均线
    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]#里面的单个数组也必须转换成list
    kline.add_yaxis( 'K线',y )
    #kline.extend_axis(yaxis=opts.AxisOpts( axislabel_opts=opts.LabelOpts(formatter="{value}") ))
    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")

    ##2.2均线策略的交易记录
    Buy=[]#保存买入记录
    Sell=[]#保存卖出记录
    price_in=1#初始买入价设置为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]

    ##2.3返回交易记录和全过程数据
    return transactions,data_price

#=3.评价函数=

def performance( transactions,strategy ):
    ##3.1策略评价指标
    #年化收益率
    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)
    
    ##3.2策略逐年表现
    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('------------------------------')
    
    ##3.3策略净值可视化
    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 ) 

以上是关于Python双均线策略回测(2021-10-12)的主要内容,如果未能解决你的问题,请参考以下文章

用 Pandas 分析均线交叉策略收益率

r语言多均线股票价格量化策略回测

python均线周期转换(日线->周线)

Python量化交易策略--双均线策略及代码

WeQuant交易策略—5日均线

Python零基础该如何学习