本文主要有两个观点:
1、A股是基本面的良好反馈
2、对长期利润的准确预测可以获得高额回报
A股投资者,段子手偏多,伪财经偏多,都是韭菜生长的大量肥料。因为伪财经偏多,所以在韭菜学习后难免将自己的亏损归咎于A股并不反应基本面为理由。
然而,实际上,A股一直对基本面有良好定价。与其怪罪证监会主席,不如反求诸己。
本文利用量化从历史检验的方法,带领大家看看从2006年开始至2017年6月的基本面分析对投资的作用。
具体方法,则是使用量化中所说的未来函数,也就是利用在过去某一时点并不知晓的未来一段时间的数据。举个栗子,在2006年5月我选择买入的股票就是基于2007年5月才公布的利润数据。这个数据在2006年5月是无法准确获取的,但是我们假设某些人可以通过基本面分析知晓未来的利润。
我们总共测试了4个样本组,分别利用了:
1.未来第1年的利润增速的前50只股票
2.未来第2年的利润增速的前50只股票
3.未来第3年的利润增速的前50只股票
4.未来第3年的利润增速的前100只股票
样本组中的利润增速,是指归母净利润同比。样本中,还对利润小于当年平均利润总额的公司进行了剔除,为了尽可能避免小盘股利润调节的影响。
对照组为沪深300。
从上图可以看出,对于长期利润的准确预估有利于获得超额收益。未来第3年利润增速的准确预测获得了最高的收益率,且存在非常明显的随预测年限递减的趋势。对照组沪深300的增速明年落后于4个样本组。
上表可以看出,对未来3年利润增速的准确预测,可以获得接近200%的年化收益率(复利后的算数平均),这个收益率还是相当惊人的。
在同样因子下,选择100支股票不仅获得了更高的年化收益率,还同时提升了IC表现。但是对100支股票的预测往往不可能是个人或者小团队可以实现的,这也就充分说明未来有研究实力的大机构作为投资主力具有巨大潜力。
有的朋友可能还有疑问,A股即便是反应企业利润的,那A股反应了中国经济吗?A股是中国经济的晴雨表吗?答案是肯定的。
从上图其实可以看出,上市公司利润同比波动与PMI的变化非常贴近,这可能与大家常用的GDP有很大的出入。由于PMI数据是月频,所以我们对其进行了变频处理为年度。数据上,2005-2006、2012-2014年的轻微不匹配,可能就是政策对经济的影响,对应2005年出台的国企改革、2013年的债务放水。但是大体上的宏观波段并没有差异,所以,未来宏观环境的不乐观依旧是当前低估值市场的抑制因素。
因为作者时间有限,公号以每周发布些思考为主,作为研究还需更加深入,后续研究可改进之处:
1.对因子进行市值行业中性化处理
2.选择连续N年的利润平均增速为因子
3.其他基本面因子的效果,评价出最值得研究的因子
4.对于DDM模型进行量化解释
5. ……
附录:(文中数据基于聚宽研究终端提取,具体实现代码如下)
import datetime
from dateutil import rrule
from dateutil.parser import parse
from dateutil.relativedelta import relativedelta
import pandas as pd
import numpy as np
# 设置起始时间
begT = '2006-1-1'
endT = '2016-1-1'
date_list = list(rrule.rrule(rrule.MONTHLY,dtstart=parse(begT),until=parse(endT))) #获取隔月数据
# 日期变为
quarter_list=[]
for i in date_list:
if i.month in [5,9,11]:
quarter_list.append(i)
quarter_df = pd.DataFrame(quarter_list,columns=['buy_day'])
for num,i in enumerate(quarter_df.buy_day):
# 1季报:每年4月1日——4月30日。
# 2季报(中报):每年7月1日——8月30日。
# 3季报: 每年10月1日——10月31日。
# 4季报 (年报):每年1月1日——4月30日。
if i.month == 5:
add_list = [4,6,12,24,36]
if i.month == 9:
add_list = [2,8,12,24,36]
if i.month == 11:
add_list = [6,10,12,24,36]
quarter_df.loc[num,'next_1_quarter'] = i + relativedelta(months=add_list[0])
quarter_df.loc[num,'next_2_quarter'] = i + relativedelta(months=add_list[1])
quarter_df.loc[num,'next_3_quarter'] = i + relativedelta(months=add_list[2])
quarter_df.loc[num,'next_6_quarter'] = i + relativedelta(months=add_list[3])
quarter_df.loc[num,'next_9_quarter'] = i + relativedelta(months=add_list[4])
from jqdata import finance
# 获取整体上市信息
q=query(finance.STK_LIST).filter(finance.STK_LIST.start_date<='2015-01-01')
df2=finance.run_query(q)
q=query(finance.STK_LIST).filter(finance.STK_LIST.start_date>'2015-01-01')
df3=finance.run_query(q)
stock_info = pd.concat([df2,df3]) # 整体上市信息,用于剔除未上市股票
def get_stock(quarter_df,column_name,N):
buy_table = pd.DataFrame([])
for num,i in enumerate(quarter_df.buy_day):
print('Running:',i)
# 修改此处的返回值可以获取多种因子
df = get_fundamentals(query(
indicator.code, indicator.pubDate, indicator.inc_net_profit_to_shareholders_year_on_year,income.np_parent_company_owners
), date=quarter_df.loc[num,column_name])
# 在当期平均利润之上的企业的利润增速前N名
# N = 50
x_mean = np.mean(df.np_parent_company_owners)
df = df[df.np_parent_company_owners>x_mean]
df.sort_values(by=['inc_net_profit_to_shareholders_year_on_year'],ascending=False,inplace=True)
# 检查是否存在前50有未上市情况
check_stocks = df[:N+900] # 空余10个位置备选
tof_list = [] # 判断是否未上市
for num,j in enumerate(check_stocks.code):
if (stock_info[stock_info.code == j].start_date>i.strftime("%Y-%m-%d")).values[0]:
tof_list.append(False)
else:
tof_list.append(True)
check_stocks=check_stocks[tof_list]
print('Finish check publish day!')
#print(check_stocks)
# 假如可筛选股票小于N
len_check_stocks = len(check_stocks)
print(len_check_stocks)
if len_check_stocks > N:
buy_stock = pd.DataFrame([check_stocks[:N].code.values,ones(N),ones(N)*100/N]).T
else:
buy_stock = pd.DataFrame([check_stocks.code.values,ones(len_check_stocks),ones(len_check_stocks)*100/len_check_stocks]).T
buy_stock.columns = ['证券代码','调整日期','持仓权重']
buy_stock['调整日期'] = i
# 合并至大表
buy_table = pd.concat([buy_table,buy_stock])
return buy_table
test = get_stock(quarter_df,'next_9_quarter',200)
# 可以构建自己的时间表格,匹配任意股票数量
test.index=range(0,len(test))
for num,i in enumerate(test['证券代码']):
if i[-4:]=='XSHE':
test.loc[num,'证券代码'] = i[:7]+'SZ'
if i[-4:]=='XSHG':
test.loc[num,'证券代码'] = i[:7]+'SH'
test.to_csv(r'D:\test_profit9_200.csv',encoding="utf_8_sig") # 请修改为自己的保存地址