基于成交量脉冲的大盘择时策略¶
研究思路¶
成交量脉冲指的是成交量(金额)突然放大的现象。成交量脉冲出现,往往是一股集中的力量大量买入股票造成的,有时候是投资者者的一致预期突然改变,有时是“聪明的钱”的集中活动,有时候是外资大量增持A股。
这种脉冲式的买入,往往蕴涵着A股未来大概率向好。我们就捕捉这种脉冲,开发相应的交易策略。
根据研报定义,我们用脉冲指标比来衡量成交量脉冲的强度。具体定义脉冲比为当日成交额除以过去 5 日(不含当日)的平均成交金额。
脉冲比=当日成交额 / MA(过去 5 个交易日的成交额)
下图是 A 股历史上的总成交金额图,可以肉眼看到,2015 年初和2019年初有明显成交量放量。
get_price('000002.XSHG',start_date = '2009-12-23',end_date = '2019-07-12',fields=['money']).plot()
成交量脉冲与A股涨跌的关系¶
根据统计,成交量出现正的脉冲,当天股市行情上涨的概率较大。总体趋势是脉冲比越大,当天上涨概率越高,总体概率在74.1%。
from jqdata import *
import numpy as np
import pandas as pd
#数据获取
start='2009-12-23'
end='2019-7-12'
n=5
index = '000001.XSHG'
extend_days = get_trade_days(end_date=start,count=n)
if end in extend_days:
extend_days = extend_days
else:
extend_days = extend_days[:-1]
trade_days = get_trade_days(start_date=start, end_date=end, count=None)
extended_trade_days=np.concatenate((extend_days,trade_days),axis=0)
def cal_future_ret(close,n):
future_ret=[]
for i in range(len(close)-n):
future_ret.append(close[i+n]/close[i]-1)
return np.concatenate((future_ret,n*[np.nan]))
def get_data(index):
df = get_price(index,start_date=extended_trade_days[0],end_date=end,fields=['close','open','money'])
df['ma_vol']=df['money'].rolling(6).mean()
df['vol_shock']=df['money']/((df['ma_vol']-df['money']/6)*6/5)
ret = np.concatenate(([np.nan],np.array(df['close'][1:])/np.array(df['close'][:-1])))-1
df['当日涨幅'] = ret
groups = [1.2,1.3,1.4,1.5,1.6,1.7]
v = pd.cut(df['vol_shock'],bins=[1.2,1.3,1.4,1.5,1.6,1.7,max(df['vol_shock'])],labels=groups)
v = v.cat.add_categories(0)
v = v.fillna(0)
df['vol_shock_group'] = v
df['未来30日涨幅'] = cal_future_ret(df['close'],30)
df['未来20日涨幅'] = cal_future_ret(df['close'],20)
df['未来10日涨幅'] = cal_future_ret(df['close'],10)
return df
df = get_data(index)
def group_plot(df,y):
a = df[df['vol_shock_group']!=0]
b = a.groupby('vol_shock_group').mean()
plt.xlabel('脉冲量级')
plt.ylabel(y)
plt.plot(np.array(b.index),b[y])
sum_dict = {}
for group in groups:
b = df[df['vol_shock_group']==group]
freq_pos = len(b[b['当日涨幅']>=0])
freq_neg = len(b[b['当日涨幅']<0])
prob_pos = freq_pos/(freq_pos+freq_neg)
sum_dict[group] = [freq_pos,freq_pos+freq_neg,prob_pos]
b = df[df['vol_shock_group']!=0]
freq_pos = len(b[b['当日涨幅']>=0])
freq_neg = len(b[b['当日涨幅']<0])
prob_pos = freq_pos/(freq_pos+freq_neg)
sum_dict['汇总'] = [freq_pos,freq_pos+freq_neg,prob_pos]
pd.DataFrame(sum_dict,index=['上涨数','样本数','上涨占比'])
下图为上证综指的成交量脉冲比与平均收益的关系。可以看出,大致趋势是脉冲比越大,未来一段时间的预期收益会更高。
plt.figure(1,figsize=(12,6))
plt.title('当日涨幅')
group_plot(df,'当日涨幅')
plt.figure(2,figsize=(12,6))
plt.title('未来30日涨幅')
group_plot(df,'未来30日涨幅')
plt.figure(3,figsize=(12,6))
plt.title('未来20日涨幅')
group_plot(df,'未来20日涨幅')
plt.figure(4,figsize=(12,6))
plt.title('未来10日涨幅')
group_plot(df,'未来10日涨幅')
基于脉冲比指标构建择时策略¶
策略逻辑:成交量的脉冲出现,往往是一股集中的力量大量买入股票造成的,有时候是投资者的一致预期突然改变,有时是“聪明的钱”的集中活动,有时候是外资大量增持A股。
这种脉冲式的买入,往往蕴涵着A股未来大概率向好。我们就捕捉这种脉冲,开发相应的交易策略。
策略思路:当成交量出现脉冲时,计算脉冲比。根据脉冲比,决定是持有指数还是空仓。
参数 1:脉冲比阈值(例如:1.3)
参数 2:持有期限(例如:30 天)
如果持有期内,出现新的大于阈值的脉冲,则重现计算持有期限。
交易标的为上证指数和深证成指,基准用各自的价格指数。
上证综指择时效果¶
根据后面的敏感性测试,我们可以得出最优参数1为1.5,最优参数2为30。
策略满仓天数为1130,空仓天数为1196。各项指标均大幅优于基准指数,且完全避开了2015年下半年的大回撤。年化收益达到10.96%,夏普比率达到0.88。
#上证综指择时
threshold = 1.5
holding = 30
def back_test(df,threshold,holding,prt=False):
count=holding
position=[]
for i in range(len(df)):
vol_shock = df['vol_shock'][i]
if vol_shock>threshold:
position.append(1)
count = 1
else:
if count<holding:
count +=1
position.append(1)
else:
position.append(0)
df['position']=position
if prt:
position = np.array(position)
print('满仓天数:',len(position[position==1]))
print('空仓天数:',len(position[position==0]))
index_ret=np.concatenate(([np.nan],np.array(df['close'][1:])/np.array(df['close'][:-1])-1))
ret=[0]
for i in range(len(df)-1):
ret.append(index_ret[i+1]*position[i])
ret = np.array(ret)
df['ret']=ret
cum_ret=[]
for i in range(len(ret)):
if i==0:
cum_ret.append(1+ret[i])
else:
cum_ret.append(cum_ret[-1]*(1+ret[i]))
df['cum_ret']=cum_ret
return df
def summary(df):
#输出各项指标
cum_ret = df['cum_ret']
ret = df['ret']
annual_ret = cum_ret[-1]**(240/(len(ret)-5))-1
cum_ret_rate = cum_ret[-1]-1
max_nv = np.maximum.accumulate(cum_ret)
mdd = -np.min(cum_ret/max_nv-1)
print('年化收益率: {:.2%}'.format(annual_ret))
print('累计收益率: {:.2%}'.format(cum_ret_rate))
print('最大回撤: {:.2%}'.format(mdd))
print('夏普比率:{:.2}'.format(ret.mean()/ret.std()*np.sqrt(240)))
#作图
plt.figure(1,figsize=(20,10))
plt.title('净值曲线',fontsize=18)
plt.plot(df.index,cum_ret)
plt.plot(df.index,df['close']/df['close'][0])
plt.legend(['策略净值','基准净值'],fontsize=15)
plt.figure(2,figsize=(20,10))
plt.title('相对优势',fontsize=18)
plt.plot(df.index,cum_ret-df['close']/df['close'][0])
plt.show()
df = back_test(df,threshold,holding,prt=True)
summary(df)
上证综指择时的敏感性分析¶
由于本策略受到两个参数的影响,不同的参数可能会带来截然不同的效果。所以,通过敏感性分析找出最优参数是很有必要的。
敏感性分析的参数为触发阈值和持有时间。触发阈值的范围为1.2-1.7(步长0.1),而持有期的范围为10-30(步长10)。优化目标为夏普比率。
通过敏感性分析,我们得到最优阈值为1.5和30,此时夏普比率为0.88。
#上证综指择时敏感性分析
threshold = [1.2,1.3,1.4,1.5,1.6,1.7]
holding = [10,20,30]
sharpe=[]
for i in threshold:
for j in holding:
df = back_test(df,i,j)
ret = df['ret']
sharpe.append(ret.mean()/ret.std()*np.sqrt(240))
sharpe = np.array(sharpe).reshape((6,3))
sharpe_df = pd.DataFrame(sharpe,index=threshold,columns=holding)
sharpe_df
深证成指择时效果¶
根据后面的敏感性测试,我们可以得出最优参数1为1.6,最优参数2为10。
策略满仓天数为490,空仓天数为1836。各项指标同样优于基准指数,但由于2015年回撤的影响,不如上证综指的择时效果。年化收益达到10.96%,夏普比率达到0.56。
df2 = get_data('399001.XSHE')
df2 = back_test(df2,1.6,10,prt=True)
summary(df2)
深证成指择时的敏感性分析¶
与上文类似,我们对深证成指择时策略的两个参数进行敏感性分析,试图达到最优夏普比率,从而找出最优参数。经过敏感性分析,我们得到最优阈值为1.6,最优持有期为10。
#深证成指择时敏感性测试
threshold = [1.2,1.3,1.4,1.5,1.6,1.7]
holding = [10,20,30]
sharpe=[]
for i in threshold:
for j in holding:
df2 = back_test(df2,i,j)
ret = df2['ret']
sharpe.append(ret.mean()/ret.std()*np.sqrt(240))
sharpe = np.array(sharpe).reshape((6,3))
sharpe_df = pd.DataFrame(sharpe,index=threshold,columns=holding)
sharpe_df
研究结论¶
成交量脉冲大盘择时基于放量上涨的思想:成交量突然上升往往是由于集中的大量买入造成的。而这种大量的买入往往意味着A股未来大概率上升。基于这种思想,我们参考研报,复现了基于成交量脉冲的大盘择时策略。
策略在两种上证综指和深证成指上的回测效果都不错,可以大幅战胜指数,但策略对于上证综指的择时效果更胜一筹。总的来说,这个策略原理简单,容易实现,择时效果也十分不错,收益稳定且大幅跑赢市场,但对于参数的选择仍需注意,不同的参数可能会带来截然不同的结果。另外,策略的收益主要来自2015年的大牛市,在震荡市中收益较小。根据我们的研究结果,我们建议在上证综指出现1.5以上的脉冲比时,买入持有30天。