除了市场广泛的牛市和熊市之外,A股的股市在较多的时间段都是结构性行情(其实熊牛市中也有结构性区别),而大盘股和小盘股的风格切换一直是众多投资者十分关心的问题。能把握好这一风格的切换能够在市场中取得十分可观的收益(小盘股策略回测实例)。本文将从趋势把握的角度构造小市值风格倾向指数,小市值风格倾向指数是用于识别市场整体是否倾向于配置小市值股票的指数,使用这一指数能够帮助我们把握小盘股的趋势行情。
目录
1. 指数介绍
1.1 构造方法
(1)基于涨跌幅的指数序列(mode1):将申万一级行业中市值前50%和市值后50%的股票分别分为两组,计算各组的算数平均涨跌幅,所需计算指数值为一级行业中后50%市值企业算术平均涨跌幅高于另一组的数量占总行业数的比例;
(2)基于涨跌家数的指数序列(mode2):将申万一级行业中市值前50%和市值后50%的股票分别分为两组,统计各组的上涨家数,所需计算指数值为一级行业中后50%市值企业上涨家数多于另一组的数量占总行业数的比例;
(3)基于上述两组指数序列,根据阈值法、极值法和类似布林通道的方法得出信号值,1为倾向于小市值,0为不倾向于小市值。
1.2 使用方法
*信号1、2指单独由序列mode1或序列mode2按照信号产生方法计算的信号
(1)阈值信号
根据指数值序列与定下的两个阈值(买入阈值:即市场倾向于小市值股票的阈值;卖出阈值:即市场不倾向于小市值股票的阈值),每日输出信号,1为买入,0为卖出。
阈值综合信号为结合了信号1、2的信号,由于信号1、2单独输出信号时不太稳定,因此做如下处理,当有其中一个信号输出为1时,综合信号输出为1,否则为0。
(2)极值信号
根据指数值序列与定下的两个倍数阈值(买入阈值:指数从最近N日的底部上升倍数超过该倍数阈值时输出买入信号;卖出阈值:指数从最近N日的顶部下降倍数超过该倍数阈值时输出卖出信号),每日输出信号,1为买入,0为卖出。
极值综合信号为结合了信号1、2的信号,由于信号1、2单独输出信号时不太稳定,因此做如下处理,当有其中一个信号输出为1时,综合信号输出为1,否则为0。
(3)布林信号
结合布林通道思想,将指数序列看做股票价格序列,进行布林通道处理。
当采用指数值从下穿越下轨时输出买入信号,从下穿越上柜时输出卖出信号。
1.3 示例
2 结果展示
2.1 描述性统计分析+指标序列与小市值指数超额收益走势
指标序列描述性统计:
可以看到标准差在进行了移动平均处理后明显下降,各分位数据也更加稳定,这有利于我们使用序列数据进行进一步分析。
指标序列与小市值指数超额收益走势:
可以看到指标序列与股价走势有明显关联,并且指标序列相对提前涨跌幅做出响应,因此使用指标序列按照一定的方法输出看空看多信号存在潜在的可能潜力。
2.2 阈值信号
阈值设置:
信号1 | 信号2 | 信号1 | 信号2 | 信号1 | 信号2 | |
MA10 | MA10 | MA20 | MA20 | MA30 | MA30 | |
买入阈值 | 0.481 | 0.477 | 0.483 | 0.479 | 0.485 | 0.479 |
卖出阈值 | 0.546 | 0.536 | 0.538 | 0.529 | 0.535 | 0.522 |
当指标序列向上突破买入阈值则发出买入信号,若向下突破卖出阈值则发出卖出信号。综合信号为综合了信号1和信号2的信号,处理方法是:当信号1、2中有一个信号输出为1时,综合信号输出为1,否则为0。
小盘指数累计超额涨跌幅-MODE1阈值信号MA10
小盘指数累计超额涨跌幅-MODE1阈值信号MA20
小盘指数累计超额涨跌幅-MODE1阈值信号MA30
小盘指数累计超额涨跌幅-MODE2阈值信号MA10
小盘指数累计超额涨跌幅-MODE2阈值信号MA20
小盘指数累计超额涨跌幅-MODE2阈值信号MA30
小盘指数累计超额涨跌幅-综合阈值信号MA10
小盘指数累计超额涨跌幅-综合阈值信号MA20
小盘指数累计超额涨跌幅-综合阈值信号MA30
2.3 极值信号
极值判断标准:
信号1 | 信号2 | 信号1 | 信号2 | 信号1 | 信号2 | |
MA10 | MA10 | MA20 | MA20 | MA30 | MA30 | |
买入极值判断标准 | 0.08 | 0.1 | 0.08 | 0.09 | 0.06 | 0.05 |
卖出极值判断标准 | 0.1 | 0.12 | 0.07 | 0.1 | 0.05 | 0.07 |
根据指数值序列与定下的两个倍数阈值(买入阈值:指数从最近N日的底部上升倍数超过该倍数阈值时输出买入信号;卖出阈值:指数从最近N日的顶部下降倍数超过该倍数阈值时输出卖出信号),每日输出信号,1为买入,0为卖出。N分别为10,20,30。
小盘指数累计超额涨跌幅-极值信号1MA10
小盘指数累计超额涨跌幅-极值信号1MA20
小盘指数累计超额涨跌幅-极值信号1MA30
小盘指数累计超额涨跌幅-极值信号2MA10
小盘指数累计超额涨跌幅-极值信号2MA20
小盘指数累计超额涨跌幅-极值信号2MA20
小盘指数累计超额涨跌幅-综合极值信号MA10
小盘指数累计超额涨跌幅-综合极值信号MA20
小盘指数累计超额涨跌幅-综合极值信号MA30
2.4 布林信号
结合布林通道思想,将指数序列看做股票价格序列,进行布林通道处理。
当采用指数值从下穿越下轨时输出买入信号,从下穿越上柜时输出卖出信号。
布林上下轨均与中轨相差1.9个std。
小盘指数累计超额涨跌幅-布林信号1MA10
小盘指数累计超额涨跌幅-布林信号1MA20
小盘指数累计超额涨跌幅-布林信号1MA30
小盘指数累计超额涨跌幅-布林信号2MA10
小盘指数累计超额涨跌幅-布林信号2MA20
小盘指数累计超额涨跌幅-布林信号2MA30
3. 一些结论
(1)阈值信号和极值信号都能以十分高的准确度输出信号,布林信号效果较差,主要是当指数值在轨道中运行时无有效信号产生,但实际行情却已经发生变化。
(2)原始指数序列波动非常大,故在实际使用时使用了MA10\MA20\MA30处理后的指数序列,其中MA10可以指示短期小市值倾向、MA20指示中期小市值倾向、MA30指示长期小市值倾向。
(3)2020年后单次出现信号后持续该信号时间小于以前。
(4)信号显示,自今年七月中旬后当前市场更倾向于小市值股票,部分短期信号显示上月底以来短期小市值倾向消失,但中长期信号任然显示有小市值倾向,接近月底中长期信号也小时。
4. 代码分享
麻烦感兴趣的同学把下面的研究克隆克隆,想要积分555~,代码也在里面,这个代码由于调用了聚宽的API,只能在聚宽平台的研究环境中运行~
市场小市值倾向研究--小市值风格倾向指数构造 - 金融界懒羊羊 - JoinQuant
import pandas as pd
import numpy as np
from jqdata import *
from tqdm import tqdm
import datetime as dt
import traceback
def filter_recent_start_stock(date,stock_list,days=30):
return [stock for stock in stock_list
if (date - get_security_info(stock).start_date).days > days]
#这里的mode3与博客中的mode2相同,代码懒得改了~
modes=[1,3]
for mode in modes:
start_date = dt.date(2010,1,1)
end_date = dt.date(2022,8,19)
dates = get_trade_days(start_date=start_date, end_date=end_date, count=None)
industries = get_industries("sw_l1")
half_half_return_for_sw = pd.DataFrame()
for date in tqdm(dates):
half_half_return_for_sw_temp = pd.DataFrame(index = [date])
for ind_code in industries.index:
ind_name = industries.loc[ind_code,'name']
try:
codes = get_industry_stocks(ind_code, date=date)
codes = filter_recent_start_stock(date,codes,days=30)
codes = list(get_fundamentals(query(
valuation.code
).filter(
valuation.code.in_(codes)
).order_by(
# 按市值降序排列
valuation.circulating_market_cap.desc()
), date=date)['code'])
codes_upperhalf = codes[0:int(len(codes)/2)]
codes_lowwerhalf = codes[int(len(codes)/2):]
close_today_yesterday_upperhalf = get_price(security = codes_upperhalf, start_date=None, end_date=date, frequency='daily', fields=['close','pre_close'], skip_paused=True, fq='pre', count=1, panel=False, fill_paused=False).dropna(axis=0)
close_today_yesterday_lowwererhalf = get_price(security = codes_lowwerhalf, start_date=None, end_date=date, frequency='daily', fields=['close','pre_close'], skip_paused=True, fq='pre', count=1, panel=False, fill_paused=False).dropna(axis=0)
upper_returns = [a/b-1 for a,b in zip(list(close_today_yesterday_upperhalf.iloc[:,2]),list(close_today_yesterday_upperhalf.iloc[:,3]))]
lowwer_returns = [a/b-1 for a,b in zip(list(close_today_yesterday_lowwererhalf.iloc[:,2]),list(close_today_yesterday_lowwererhalf.iloc[:,3]))]
if(mode==1):
#算数平均涨幅
upper_avg_return = np.mean(upper_returns)
lowwer_avg_return = np.mean(lowwer_returns)
#print(upper_avg_return,lowwer_avg_return)
half_half_return_for_sw_temp_temp = pd.DataFrame([[upper_avg_return,lowwer_avg_return]],index = [date],columns=[ind_name+"upper",ind_name+"lowwer"])
elif(mode==2):
# 市值加权平均涨幅
pass
elif(mode==3):
#上涨家数占比
upper_returns_rise_pct = len([i for i in upper_returns if i>0])/len(upper_returns)
lowwer_returns_rise_pct = len([i for i in lowwer_returns if i>0])/len(lowwer_returns)
half_half_return_for_sw_temp_temp = pd.DataFrame([[upper_returns_rise_pct,lowwer_returns_rise_pct]],index = [date],columns=[ind_name+"upper",ind_name+"lowwer"])
except:
#traceback.print_exc()
half_half_return_for_sw_temp_temp = pd.DataFrame([[0,0]],index = [date],columns=[ind_name+"upper",ind_name+"lowwer"])
half_half_return_for_sw_temp = pd.concat([half_half_return_for_sw_temp,half_half_return_for_sw_temp_temp],axis = 1)
half_half_return_for_sw = pd.concat([half_half_return_for_sw,half_half_return_for_sw_temp],axis=0)
half_half_return_for_sw.to_excel("sw_upper_lowwer_mode_circulation"+str(mode)+".xlsx")
def delet_continious_two0(list_use):
list_to_return = []
for i in range(0,len(list_use),2):
if(list_use[i]==0 and list_use[i+1]==0):
continue
else:
list_to_return.append(list_use[i])
list_to_return.append(list_use[i+1])
return list_to_return
sw_upper_lowwer_mode1 = pd.read_excel("sw_upper_lowwer_mode_circulation1.xlsx")
sw_upper_lowwer_mode3 = pd.read_excel("sw_upper_lowwer_mode_circulation3.xlsx")
samll_capital_index = pd.DataFrame(index=sw_upper_lowwer_mode1.index)
samll_capital_index_mode1_list = []
samll_capital_index_mode3_list = []
for date in tqdm(sw_upper_lowwer_mode1.index):
data_temp=list(sw_upper_lowwer_mode1.loc[date,:])
data_temp=delet_continious_two0(data_temp)
n = 0
for i in range(0,len(data_temp),2):
if(data_temp[i]<data_temp[i+1]):
n=n+1
pct_temp = n/(len(data_temp)/2)
samll_capital_index_mode1_list.append(pct_temp)
samll_capital_index['小市值风格倾向指数mode1']=samll_capital_index_mode1_list
for date in tqdm(sw_upper_lowwer_mode3.index):
data_temp=list(sw_upper_lowwer_mode3.loc[date,:])
data_temp=delet_continious_two0(data_temp)
#print(len(data_temp))
n = 0
for i in range(0,len(data_temp),2):
if(data_temp[i]<data_temp[i+1]):
n=n+1
pct_temp = n/(len(data_temp)/2)
samll_capital_index_mode3_list.append(pct_temp)
samll_capital_index['小市值风格倾向指数mode3']=samll_capital_index_mode3_list
samll_capital_index.to_excel('小市值风格倾向指数_流通股.xlsx')