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 )

轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/310631.html
標籤:區塊鏈
