唐奇安通道策略-python量化
这里简单的介绍关于唐奇安通道策略的相关理论以及python代码,抛砖引玉。
前言
唐奇安通道是海龟交易策略中需要应用到的一个指标。
简单而言唐奇安通道是由一条上轨线、中线和下线组成,上轨线由N1日内最高价构成,下轨线由N2日内最低价计算,当价格冲破上轨是可能的买入信号,反之,冲破下轨时是可能的卖出信号。
一、唐奇安通道计算
通道上界=过去20日内的最高价
通道下界=过去20日内的最低价
中轨道=0.5*(通道上界+通道下界)
本章需要使用的就是以上三个指标。接下来进行计算和回测。
二、python实现
1.获取数据进行指标计算
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import ta
from pylab import mpl
mpl.rcParams['font.sans-serif']=['SimHei']
mpl.rcParams['axes.unicode_minus']=False
# 获取数据
def get_daily_data(security,start_date,end_date):
df=get_price(security, start_date,end_date, frequency='daily', fields=['open','close','high','low','volume','money'])
df.index=pd.to_datetime(df.index)
# 计算收益率
df['ret']=df['close'].pct_change()
# 删除缺失值
df=df.dropna()
return df
security='000300.XSHG'
start_date='2018-01-01'
end_date='2019-12-31'
hs=get_daily_data(security,start_date,end_date)
hs
数据如下所示
# 上轨线和下轨线
close=hs.close
high=hs.high
low=hs.low
up=pd.Series(0.0,index=close.index)
down=pd.Series(0.0,index=close.index)
middle=pd.Series(0.0,index=close.index)
for i in range(20,len(close)):
up[i]=max(high[(i-20):i])
down[i]=min(low[(i-20):i])
middle[i]=(up[i]+down[i])/2
hs['up']=up
hs['down']=down
hs['middle']=middle
hs=hs[20:]
hs
各指标如下所示
ATR计算
#TR=Max(High−Low,abs(High−PreClose),abs(PreClose−Low))
hs1=hs.copy()
hs1 = ta.add_all_ta_features(hs1, "open", "high", "low", "close", "volume", fillna=True)
hs1=hs.copy()
hs1 = ta.add_all_ta_features(hs1, "open", "high", "low", "close", "volume", fillna=True)
atr=hs1.volatility_atr
atr
hs['ATR']=atr
hs=hs[20:]
hs
画出k线图和轨道线
```bash
def my_strategy(data):
x1=data.close>data.up
x2=data.close.shift(1)<data.up.shift(1)
x=x1&x2
y1=data.close<data.down
y2=data.close.shift(1)>data.down.shift(1)
y=y1&y2
data.loc[x,'signal']=1
data.loc[y,'signal']=-1
data=data.fillna(0)
return data
my_strategy(data=hs)
查看交易信号
def my_strategy(data):
x1=data.close>data.up
x2=data.close.shift(1)<data.up.shift(1)
x=x1&x2
y1=data.close<data.down
y2=data.close.shift(1)>data.down.shift(1)
y=y1&y2
data.loc[x,'signal']=1
data.loc[y,'signal']=-1
data=data.fillna(0)
return data
my_strategy(data=hs)
2.构建策略
from datetime import datetime,timedelta
def strategy(data,start,end):
df=data
x1=data.close>data.up
x2=data.close.shift(1)<data.up.shift(1)
x=x1&x2
y1=data.close<data.down
y2=data.close.shift(1)>data.down.shift(1)
y=y1&y2
data.loc[x,'收盘信号']=1
data.loc[y,'收盘信号']=0
data=data.fillna(0)
df['当天仓位']=df['收盘信号'].shift(1)
df['当天仓位'].fillna(method='ffill',inplace=True)
d=df[df['当天仓位']==1].index[0]-timedelta(days=1)
df1=df.loc[d:].copy()
df1['ret'][0]=0
df1['当天仓位'][0]=0
#当仓位为1时,买入持仓,当仓位为-1时,空仓,计算资金净值
df1['策略净值']=(df1.ret.values*df1['当天仓位'].values+1.0).cumprod()
df1['指数净值']=(df1.ret.values+1.0).cumprod()
df1['策略收益率']=df1['策略净值']/df1['策略净值'].shift(1)-1
df1['指数收益率']=df1.ret
total_ret=df1[['策略净值','指数净值']].iloc[-1]-1
annual_ret=pow(1+total_ret,250/len(df1))-1
dd=(df1[['策略净值','指数净值']].cummax()-df1[['策略净值','指数净值']])/df1[['策略净值','指数净值']].cummax()
d=dd.max()
beta=df1[['策略收益率','指数收益率']].cov().iat[0,1]/df1['指数收益率'].var()
alpha=(annual_ret['策略净值']-annual_ret['指数净值']*beta)
exReturn=df1['策略收益率']-0.03/250
sharper_atio=np.sqrt(len(exReturn))*exReturn.mean()/exReturn.std()
TA1=round(total_ret['策略净值']*100,2)
TA2=round(total_ret['指数净值']*100,2)
AR1=round(annual_ret['策略净值']*100,2)
AR2=round(annual_ret['指数净值']*100,2)
MD1=round(d['策略净值']*100,2)
MD2=round(d['指数净值']*100,2)
S=round(sharper_atio,2)
df1[['策略净值','指数净值']].plot(figsize=(15,7))
plt.title('海龟交易策略简单回测',size=15)
bbox = dict(boxstyle="round", fc="w", ec="0.5", alpha=0.9)
plt.text(df1.index[int(len(df1)/5)], df1['指数净值'].max()/1.5, f'累计收益率:\
策略{TA1}%,指数{TA2}%;\n年化收益率:策略{AR1}%,指数{AR2}%;\n最大回撤: 策略{MD1}%,指数{MD2}%;\n\
策略alpha: {round(alpha,2)},策略beta:{round(beta,2)}; \n夏普比率: {S}',size=13,bbox=bbox)
plt.xlabel('')
ax=plt.gca()
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
plt.show()
#
strategy(data=hs,start='2018-05-01',end='2019-12-31')
从上图来看,对沪深300指数使用该策略的收益情况不是很理想,接下来对个股使用该策略进行检验。
3.个股检验
这里的个股选择凤凰光学
对其进行了简要的基本面研究。
事件:2021年9月21日,凤凰光学股份有限公司就拟筹划以发行A股股份的方式购买中电科半导体材料有限公司等股东持有的南京国盛电子有限公司及河北普兴电子科技股份有限公司的部分股权或全部股权发布停牌公告。2021年9月29日,公司发布重大资产出售及发行股份购买资产并募集配套资金暨关联交易预案。2021年10月18日,公司回复上交所关注函,就本次重大资产重组的合理性进行说明。
市场表现:公司2021年股价涨幅达351.01%。2021年凤凰光学积极开拓海内外市场,订单增加,前三季度营业收入达11.35亿元,同比增长34.89%。此外,凤凰光学在9月发布重大资产重组预案,重点布局半导体外延材料,受市场认可度较高。
行业定位:重大重组后,公司战略性退出光学器材行业,未来业务将定位于半导体外延材料,主要业务包括半导体外延材料产品的研发、生产以及销售。
股价走势
交易数据:(2021年12月31日数据)
总股本(万股) 28157.3887
流通股本(万股) 23747.2461
流通市值(亿元) 122.2033
总市值(亿元) 144.8979
换手率(%) 4.9051
动态市盈率 615.181
市净率 27.8647
市销率 9.2517
from datetime import datetime
import backtrader as bt
# 通过数据库获取数据
def get_data(security,start_date,end_date):
df = get_price(security, start_date, end_date, frequency='daily')
df['ret']=df.close.pct_change()
close=df.close
high=df.high
low=df.low
up=pd.Series(0.0,index=close.index)
down=pd.Series(0.0,index=close.index)
middle=pd.Series(0.0,index=close.index)
for i in range(20,len(close)):
up[i]=max(high[(i-20):i])
down[i]=min(low[(i-20):i])
middle[i]=(up[i]+down[i])/2
df['up']=up
df['down']=down
df['middle']=middle
df=df[20:]
df1=df.copy()
df1 = ta.add_all_ta_features(df1, "open", "high", "low", "close", "volume", fillna=True)
atr=df1.volatility_atr
df['atr']=atr
df=df[20:]
df['openinterest']=0
df.index=pd.to_datetime(df.index)
df=df[['open','high','low','close','volume','openinterest','ret','up','down','middle','atr']]
return df
security='600071.XSHG'
start_date='2013-01-01'
end_date='2022-01-01'
stock_df=get_data(security,start_date,end_date)
stock_df
style = mpf.make_mpf_style(base_mpl_style="ggplot", marketcolors=mc)
add_plot=[
mpf.make_addplot(stock_df.up),
mpf.make_addplot(stock_df.middle),
mpf.make_addplot(stock_df.down)]
mpf.plot(data=stock_df,
type="candle",
title="Candlestick for fhgx",
addplot=add_plot,
ylabel="price",
style=style,
volume=True,
figratio=(20,14))
交易信号
strategy2=my_strategy(data=stock_df)
strategy2.signal.plot()
回测结果
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False
strategy(data=stock_df,start='2018-05-01',end='2019-12-31')
收益率不错,但是和指数收益率相比,有些差距。
三、backtrader回测
from backtrader.feeds import PandasData
class PandasData(PandasData):
# 新增两条数据线
lines = ('up', 'down',)
# 新增数据在dataframe中的位置
params = (('up',8), ('down',9), )
fromdate=datetime(2019,1,1)
todate=datetime(2019,12,31)
# data=bt.feeds.PandasData(dataname=stock_df,fromdate=fromdate,todate=todate)
data = PandasData(dataname=stock_df,datetime=-1,fromdate=fromdate,todate=todate)
class TestStrategy(bt.Strategy):
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
self.dataup = self.datas[0].up
self.datadown = self.datas[0].down
self.datatime=self.datas[0].datetime.date(0)
self.order=None #跟踪挂单
self.buyprice = None
self.buycomm = None #加入手续费
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 next(self):
self.log('Close,%.2f'%self.dataclose[0])
if self.order: ## 检查订单是否处于待处理状态...如果是,我们不能发送第二个
return
if not self.position: #检查我们是否在市场上
if self.dataclose[0]>self.dataup[0]:
if self.dataclose[-1]<self.dataup[-1]:
self.log('BUY CREATE,%.2f'%self.dataclose[0])
self.log('BUY CREATE,%.2f'%self.dataup[0])
self.order=self.buy() #跟踪创建的订单以避免第二个订单
else:
if self.dataclose[0]<self.datadown[0]:
if self.dataclose[-1]>self.datadown[-1]:
self.log('SELL CREATE,%.2f'%self.dataclose[0])
self.log('SELL CREATE,%.2f'%self.datadown[0])
self.order=self.sell()
import backtrader.analyzers as btanalyzers
import backtrader.feeds as btfeeds
import backtrader.strategies as btstrats
if __name__== '__main__':
cerebro=bt.Cerebro()
cerebro.addstrategy(TestStrategy)
cerebro.adddata(data)
cerebro.addanalyzer(btanalyzers.SharpeRatio, _name='mysharpe')
cerebro.broker.setcash(50000.0)
cerebro.broker.setcommission(commission=0.002)
print(f'组合初始价值:%.2f'%cerebro.broker.getvalue())
# 运行broker
thestrats = cerebro.run()
print(f'组合期末价值:%.2f'%cerebro.broker.getvalue())
cerebro.plot()
thestrat = thestrats[0]
print('Sharpe Ratio:', thestrat.analyzers.mysharpe.get_analysis())
回测结果如下所示
组合初始价值:50000.00
2019-01-02,Close,7.65
2019-01-03,Close,7.64
2019-01-04,Close,7.82
2019-01-07,Close,8.01
2019-01-08,Close,8.06
2019-01-09,Close,8.19
2019-01-10,Close,8.18
2019-01-11,Close,8.26
2019-01-14,Close,8.27
2019-01-15,Close,8.28
2019-01-16,Close,8.20
2019-01-17,Close,8.06
2019-01-18,Close,8.00
2019-01-21,Close,8.35
2019-01-22,Close,8.17
2019-01-23,Close,8.10
2019-01-24,Close,8.12
2019-01-25,Close,8.17
2019-01-28,Close,8.23
2019-01-29,Close,7.87
2019-01-30,Close,8.01
2019-01-31,Close,7.65
2019-02-01,Close,7.97
2019-02-11,Close,8.20
2019-02-12,Close,8.57
2019-02-13,Close,9.08
2019-02-14,Close,8.89
2019-02-15,Close,8.95
2019-02-18,Close,9.29
2019-02-19,Close,9.24
2019-02-20,Close,9.32
2019-02-21,Close,9.16
2019-02-22,Close,9.45
2019-02-25,Close,10.10
2019-02-26,Close,10.14
2019-02-27,Close,10.18
2019-02-28,Close,10.68
2019-03-01,Close,10.74
2019-03-04,Close,11.29
2019-03-05,Close,11.58
2019-03-06,Close,12.71
2019-03-07,Close,12.56
2019-03-08,Close,11.89
2019-03-11,Close,12.50
2019-03-12,Close,12.39
2019-03-13,Close,12.21
2019-03-14,Close,11.89
2019-03-15,Close,11.73
2019-03-18,Close,12.02
2019-03-19,Close,11.93
2019-03-20,Close,11.90
2019-03-21,Close,12.15
2019-03-22,Close,11.96
2019-03-25,Close,12.00
2019-03-26,Close,12.08
2019-03-27,Close,11.85
2019-03-28,Close,12.03
2019-03-29,Close,12.02
2019-04-01,Close,12.50
2019-04-02,Close,12.58
2019-04-03,Close,12.58
2019-04-04,Close,12.29
2019-04-08,Close,12.19
2019-04-09,Close,12.20
2019-04-10,Close,12.50
2019-04-11,Close,13.74
2019-04-12,Close,13.40
2019-04-15,Close,14.70
2019-04-16,Close,15.15
2019-04-17,Close,14.74
2019-04-18,Close,14.10
2019-04-19,Close,13.76
2019-04-22,Close,13.27
2019-04-23,Close,13.26
2019-04-24,Close,13.18
2019-04-25,Close,12.08
2019-04-26,Close,12.09
2019-04-29,Close,10.88
2019-04-30,Close,10.78
2019-05-06,Close,9.70
2019-05-07,Close,9.76
2019-05-07,BUY CREATE,9.76
2019-05-07,BUY CREATE,9.70
2019-05-08,BUY EXECUTED, Price: 9.59, Cost: 9.59, Comm 0.02
2019-05-08,Close,10.07
2019-05-09,Close,10.00
2019-05-10,Close,10.30
2019-05-13,Close,10.10
2019-05-14,Close,9.86
2019-05-15,Close,10.24
2019-05-16,Close,10.29
2019-05-17,Close,9.80
2019-05-20,Close,9.73
2019-05-21,Close,9.85
2019-05-22,Close,9.70
2019-05-23,Close,9.51
2019-05-24,Close,9.13
2019-05-27,Close,9.78
2019-05-28,Close,10.05
2019-05-29,Close,9.95
2019-05-30,Close,9.99
2019-05-31,Close,9.78
2019-06-03,Close,9.65
2019-06-04,Close,9.42
2019-06-05,Close,9.65
2019-06-06,Close,9.22
2019-06-10,Close,9.29
2019-06-11,Close,9.80
2019-06-12,Close,9.56
2019-06-13,Close,9.75
2019-06-14,Close,9.69
2019-06-17,Close,9.70
2019-06-18,Close,9.91
2019-06-19,Close,9.82
2019-06-20,Close,9.88
2019-06-21,Close,10.87
2019-06-24,Close,11.30
2019-06-25,Close,10.88
2019-06-26,Close,10.71
2019-06-27,Close,10.71
2019-06-28,Close,10.80
2019-07-01,Close,11.09
2019-07-02,Close,11.02
2019-07-03,Close,10.73
2019-07-04,Close,10.63
2019-07-05,Close,10.73
2019-07-08,Close,10.11
2019-07-08,SELL CREATE,10.11
2019-07-08,SELL CREATE,10.47
2019-07-09,SELL EXECUTED, Price: 10.08, Cost: 9.59, Comm 0.02
2019-07-09,Close,10.53
2019-07-10,Close,10.86
2019-07-11,Close,10.60
2019-07-12,Close,10.28
2019-07-15,Close,10.45
2019-07-16,Close,10.40
2019-07-17,Close,10.69
2019-07-18,Close,10.27
2019-07-19,Close,10.21
2019-07-22,Close,10.13
2019-07-23,Close,10.14
2019-07-24,Close,11.15
2019-07-25,Close,11.00
2019-07-26,Close,11.05
2019-07-29,Close,10.95
2019-07-30,Close,10.76
2019-07-31,Close,10.65
2019-08-01,Close,10.67
2019-08-02,Close,10.62
2019-08-05,Close,10.50
2019-08-06,Close,10.12
2019-08-07,Close,9.72
2019-08-08,Close,9.67
2019-08-09,Close,9.24
2019-08-12,Close,9.86
2019-08-12,BUY CREATE,9.86
2019-08-12,BUY CREATE,9.00
2019-08-13,BUY EXECUTED, Price: 9.75, Cost: 9.75, Comm 0.02
2019-08-13,Close,9.93
2019-08-14,Close,10.30
2019-08-15,Close,10.19
2019-08-15,SELL CREATE,10.19
2019-08-15,SELL CREATE,10.22
2019-08-16,SELL EXECUTED, Price: 10.07, Cost: 9.75, Comm 0.02
2019-08-16,Close,10.15
2019-08-19,Close,10.31
2019-08-20,Close,10.32
2019-08-21,Close,10.44
2019-08-22,Close,10.59
2019-08-23,Close,10.35
2019-08-26,Close,10.07
2019-08-27,Close,10.34
2019-08-28,Close,10.18
2019-08-29,Close,10.23
2019-08-30,Close,10.20
2019-09-02,Close,10.38
2019-09-03,Close,10.47
2019-09-04,Close,10.77
2019-09-05,Close,10.98
2019-09-06,Close,11.00
2019-09-09,Close,11.37
2019-09-10,Close,11.40
2019-09-11,Close,12.54
2019-09-12,Close,11.70
2019-09-16,Close,11.62
2019-09-17,Close,11.37
2019-09-18,Close,11.38
2019-09-19,Close,11.57
2019-09-20,Close,11.36
2019-09-23,Close,11.26
2019-09-24,Close,11.33
2019-09-25,Close,10.96
2019-09-26,Close,10.63
2019-09-27,Close,11.02
2019-09-30,Close,10.80
2019-10-08,Close,10.74
2019-10-09,Close,10.89
2019-10-10,Close,10.84
2019-10-11,Close,10.81
2019-10-14,Close,11.02
2019-10-15,Close,10.82
2019-10-16,Close,10.58
2019-10-17,Close,10.51
2019-10-18,Close,10.26
2019-10-21,Close,10.19
2019-10-22,Close,10.68
2019-10-23,Close,10.73
2019-10-24,Close,10.85
2019-10-25,Close,10.88
2019-10-28,Close,11.17
2019-10-29,Close,10.96
2019-10-30,Close,10.96
2019-10-31,Close,10.87
2019-11-01,Close,10.81
2019-11-04,Close,11.22
2019-11-05,Close,11.69
2019-11-06,Close,11.73
2019-11-07,Close,11.69
2019-11-08,Close,12.17
2019-11-11,Close,12.69
2019-11-12,Close,13.09
2019-11-13,Close,12.68
2019-11-14,Close,12.16
2019-11-15,Close,11.60
2019-11-18,Close,11.41
2019-11-19,Close,11.51
2019-11-20,Close,11.24
2019-11-21,Close,10.98
2019-11-22,Close,11.13
2019-11-25,Close,10.95
2019-11-26,Close,10.96
2019-11-27,Close,11.22
2019-11-28,Close,10.86
2019-11-29,Close,10.91
2019-12-02,Close,10.90
2019-12-03,Close,10.70
2019-12-04,Close,10.74
2019-12-05,Close,10.91
2019-12-06,Close,10.92
2019-12-09,Close,11.15
2019-12-10,Close,11.06
2019-12-11,Close,11.01
2019-12-12,Close,11.01
2019-12-13,Close,10.84
2019-12-16,Close,11.13
2019-12-17,Close,11.55
2019-12-18,Close,11.44
2019-12-19,Close,11.92
2019-12-20,Close,11.55
2019-12-23,Close,11.34
2019-12-24,Close,11.76
2019-12-25,Close,11.77
2019-12-26,Close,11.90
2019-12-27,Close,11.53
2019-12-30,Close,11.43
2019-12-31,Close,11.46
组合期末价值:50000.73
可以看出,使用backtrader回测时,效果很不理想,可能是代码设计环节有误,不过这一策略也太过于简单,单单以来动量思想进行股票交易,还是有较大风险的。
希望抛砖引玉吧。
总结
唐奇安通道策略的尝试。