目录
1 什么是净利润跳空策略?
净利润跳空是指上市公司发布超预期的业绩公告后,股价出现跳空上涨的现象。净利润断层本身包含两大要素,要素之一是上市公司发布的业绩超预期,要素之二是上市公司股价在发布业绩后的首个交易日跳空上涨。
跳空上涨的具体表现是当日最低价超越前一交易日的最高价,形成一个向上跳空的缺口,在K线图上形成类似断层的走势。由于这种缺口是由业绩超预期引发的,因此称之为净利润断层。
净利润断层本质是上市公司基本面在技术面上的体现,利用这一现象选股实际上是基本面和技术面的有机结合。如果我们认可市场是有效的,则上市公司公告超预期的业绩后,股价会立即做出反应——上涨。
业绩超预期的幅度越大,上涨的幅度越大,而且越有可能是跳空上涨。此外,当行业或上市公司进入景气度向上周期时,往往会连续多个季度业绩超预期,则相关标的可能多次出现净利润断层现象,即多次出现跳空上涨。
从行业配置视角看,净利润断层个股业绩超预期和行业业绩景气度超预期可以相互印证。当隶属同一行业的多只个股净利润断层走势,往往表明该行业多只个股业绩超预期,则该行业整体业绩也大概率是超预期的。
同理,如果某一行业整体业绩超预期,其中大概率会有多只个股业绩超预期。因此,可以通过净利润断层个股数量来作为行业业绩是否超预期的相互印证。
从个股选择视角看,净利润断层可降低投资者的搜寻成本。并非所有净利润断层的个股均能获得超额收益,但净利润断层个股可有效降低搜寻成本。
此外,随着信息披露和监管规范性越来越强,未来净利润断层策略的有效性有可能会增强。当市场在信息披露、信息保密性等方面不规范时,净利润断层策略的有效性会下降,因为总有少部分投资者能通过特殊渠道提前获知上市公司业绩,导致股价提前上涨,从而减弱公告日跳空上涨的可能性。
2聚宽量化环境简介
3本策略实现的逻辑
代码
from jqdata import *
## 初始化函数,设定要操作的股票、基准等等
def initialize(context):
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# True为开启动态复权模式,使用真实价格交易
set_option('use_real_price', True)
# 设定成交量比例
set_option('order_volume_ratio', 1)
# 股票类交易手续费是:买入时佣金万分之三,卖出时佣金万分之三加千分之一印花税, 每笔交易佣金最低扣5块钱
set_order_cost(OrderCost(open_tax=0, close_tax=0.001, \
open_commission=0.0003, close_commission=0.0003,\
close_today_commission=0, min_commission=5), type='stock')
# 持仓数量
g.stocknum = 5
# 交易日计时器
g.days = 0
# 调仓频率
g.refresh_rate = 30
# 选取行业数
g.indnum = 3
#根据大盘止损,如不想加入大盘止损,注释下句即可
#run_daily(dapan_stoploss, time='open')
# 运行函数
run_daily(trade, 'every_bar')
## 选出标的股票
def check_stocks(context):
#筛选净利润环比增长超过20%的股票
Stocks = get_fundamentals(query(
valuation.code,
indicator.inc_net_profit_annual,
).filter(
indicator.inc_net_profit_annual > 0.2, #净利润环比增长率(%)大于20%
))
#筛选跳空的股票
temp = get_bars(Stocks['code'].values.tolist(), 2, unit='1d',fields=['date','open','close'],include_now=False)
jump = []
for i in list(set(temp.keys())):
min1 = min(temp[i][0][1],temp[i][0][2])
max2 = max(temp[i][1][1],temp[i][1][2])
if(min1 > max2):
jump.append(1)
else:
jump.append(0)
Stocks['jump'] = pd.DataFrame(jump)
Stocks = Stocks[Stocks['jump'] > 0]
#筛选在前g.indnum个行业中的stock
Stocks_afterind = getstocklist_byindustry(list(Stocks['code']),g.indnum)
Stocks = Stocks[Stocks.code.isin(Stocks_afterind)]
Stocks.sort_values(by="inc_net_profit_annual" , inplace=True, ascending=False)
#返回筛选后的股票代码
Codes = list(Stocks['code'])
# 过滤停牌股票
buylist = filter_paused_stock(Codes)
return buylist[:g.stocknum]
## 交易函数
def trade(context):
if g.days%g.refresh_rate == 0:
## 获取持仓列表
sell_list = list(context.portfolio.positions.keys())
## 选股
stock_list = check_stocks(context)
# 如果有持仓,则卖出当前不在stock_list中的股票
if len(sell_list) > 0 :
for stock in sell_list:
if(stock not in stock_list):
order_target_value(stock, 0)
## 分配资金
if len(context.portfolio.positions) < g.stocknum :
Num = g.stocknum - len(context.portfolio.positions)
Cash = context.portfolio.cash/Num
else:
Cash = 0
## 买入股票
for stock in stock_list:
if len(context.portfolio.positions.keys()) < g.stocknum:
order_value(stock, Cash)
# 天计数加一
g.days = 1
else:
g.days += 1
# 过滤停牌股票
def filter_paused_stock(stock_list):
current_data = get_current_data()
return [stock for stock in stock_list if not current_data[stock].paused]
# 统计满足净利润跳空的股票的行业及对应数量,根据行业满足净利润跳空标的数量进行排序,并根据最终选取行业的数量返回要买入的stock_list
def getstocklist_byindustry(Codes,num):
sw_l1 = pd.DataFrame(np.zeros(len(list(set(get_industries(name="sw_l1")['name'])))).reshape(1,-1),columns = list(set(get_industries(name="sw_l1")['name'])))
for code in Codes:
try:
sw_l1[get_industry(security=code)[code]['sw_l1']['industry_name']] = sw_l1[get_industry(security=code)[code]['sw_l1']['industry_name']] + 1
except:
log.info(" %s 不存在申万行业数据" % (code))
sw_l1 = sw_l1.T
sw_l1.sort_values(by = 0 , inplace=True, ascending=False)
sw_l1 = sw_l1[0:num].T
industry_list = list(sw_l1.columns)
result_list = []
for code in Codes:
try:
if(get_industry(security=code)[code]['sw_l1']['industry_name'] in industry_list):
result_list.append(code)
except:
log.info(" %s 不存在申万行业数据" % (code))
return result_list
## 根据局大盘止损,具体用法详见dp_stoploss函数说明
def dapan_stoploss(context):
stoploss = dp_stoploss(kernel=2, n=3, zs=0.1)
if stoploss:
if len(context.portfolio.positions)>0:
for stock in list(context.portfolio.positions.keys()):
order_target(stock, 0)
## 大盘止损函数
def dp_stoploss(kernel=2, n=10, zs=0.03):
'''
方法1:当大盘N日均线(默认60日)与昨日收盘价构成“死叉”,则发出True信号
方法2:当大盘N日内跌幅超过zs,则发出True信号
方法3:个股亏损15%以上止损,发出True信号(暂未完善)
'''
# 止损方法1:根据大盘指数N日均线进行止损
if kernel == 1:
t = n+2
hist = attribute_history('000300.XSHG', t, '1d', 'close', df=False)
temp1 = sum(hist['close'][1:-1])/float(n)
temp2 = sum(hist['close'][0:-2])/float(n)
close1 = hist['close'][-1]
close2 = hist['close'][-2]
if (close2 > temp2) and (close1 < temp1):
return True
else:
return False
# 止损方法2:根据大盘指数跌幅进行止损
elif kernel == 2:
hist1 = attribute_history('000300.XSHG', n, '1d', 'close',df=False)
if ((1-float(hist1['close'][-1]/hist1['close'][0])) >= zs):
return True
else:
return False
一些说明
- 满足净利润跳空的条件是:当期净利润环比增长率大于20%, 当前交易日前一交易日股价出现跳空(用后一交易日min(开,收)>前一交易日max(开,收)做一个简化版的跳空)
- 本文中回测结果将大盘止损注释掉了,即回测时没有进行大盘止损操作
- 构建组合:选取净利润断层标的数量排名靠前的行业作为入选行业,对每一入选行业,然后将这些行业的所有满足净利润跳空的标的以净利润环比增长率为关键字排序,选取前五只进入股票池
- 最大持仓标的数量为5
- 初始资金为1000000
回测结果
回测时间:2019-01-02 ~ 2020-04-30
回测时间:2010-02-01 ~ 2022-04-29
尚未更新完全~