用Python分析指数: 11月16日热门指数Z值表

用Python分析指数: 11月16日热门指数Z值表

王勇王勇

衡量市场,指数高低是一个难题!


价值投资者很难知道,现在是高估,还是低估? 买的是便宜还是,贵了? 应该现在买/卖,还是再等等?


针对这个问题,我在网上看到了一些量化的处理方法。例如:平均数法,中位数法,比例法等等。这种方法往往过于简单,只能衡量集中度。不能衡量离散度和概率。


也许统计方法中的标准差Z值法更加适合。既可以衡量某个指数的指标的集中度,还可以衡量离散度,和风险情况。尽管指数的数据也不是完美的正态分布,但Z值法依然存在较大参考意义。


我的观点

- Z值越大,越高估。因为大数定理认为:Z>1, Z>2,意味着继续变大的可能性小于16%, 5%。

- Z值越小,越低估。因为大数定理认为:Z<-1, Z<-2,意味着继续变小的可能性小于16%, 5%

- 综观550多只指数的历史数据。绝大部分指数的Z值都在-2,3之间。

- 注:少数的能源,金属类指数曾经出现过短暂疯狂的。Z值法就不太适用


我使用Python的Pandas 和 Matplotlib 等工具,加上一些渠道获得的指数数据(尤其是市盈率),做了这个工具。主要目的是:


- 方便自己定投使用。知道何时开始定投,何时停止定投,何时止盈。 (目前还没有止盈过)

- 结合统计学,熟悉Python的基本数据分析方法。

- 网上分享给愿意参考的人,交流和学习


分享是对自己最好的投资!


欢迎指正。


上个月,文章发表后在雪球和知乎上得到的不少朋友的赞和关注。非常感谢支持。 这个月指数上上下下,貌似熊市来了,又貌似牛市来了。 不同的时间跨度和评估角度看,就有不同的结果。

本月增加了热门指数的Z值高低表,并附上了该指数数据年份跨度的时间

首先,献上截至2017年11月16日的热门指数Z指高低表。

1 Python 基础模块初始化

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
!free -h

# 以下代码是为了显示正文正常
import matplotlib as mpl
import matplotlib.font_manager as font_manager
path_eng = '/usr/share/fonts/chinese/REFSAN.TTF'
path_CHN = "/usr/share/fonts/chinese/simhei.ttf"
prop = font_manager.FontProperties(fname=path_CHN)  #Set the microsoft sans serief as default font family. if show chinese test, set path_CHN instead.
#prop.set_weight = 'light'
mpl.rcParams['font.family'] = prop.get_name()
Today = "2017年11月17日"

2 数据库导入

#import data 
#数据源:UQer. 
#数据采集是另外一套程序,由于UQer中文支持,图形支持不太好,下载数据到本地来进一步处理。
#8月份开始,UQ停止了数据下载功能。现在恢复了,不知道能用多长时间。先用着再说。
sec_map = pd.read_hdf("uqer/sec_map.h5","map") # sec_map 包含了约2800多个指数,实际指数约550只
#sec_map = sec_map.set_index("ticker")
history = pd.read_hdf("uqer/uq_history.h5","history") #hisotry 包含了从2004年到2017年11月16日的指数数据( 大约86万条数据).
print(sec_map.columns)
print(history.columns)
history.info()

3 定义指标-画图函数

  • 根据大数定律,在正态分布情况下,Z值=0,左右概率是50% Z值在(-1,+1)左右的概率合计是68%, Z值在(-2,+2)左右区间的概率合计是95%。例
    • 例:Z值=1, 其他数据大于1的概率<=84%,小于1的概率>=16%。 可近似认为:目前已经高估了
    • 例:Z值=-1, 其他数据小于-1的概率<=84%,大于-1的概率<=16% 可近似认为:目前已经低估了
def show_KPI(history,ticker ="000001",source="uq",KPI="Close"):
   
    #get the ticker shortName for easy understanding
    shortname =sec_map.loc[ticker]["secShortName"] #get the security index chinese name
    
    #check if the request KPI is compatible with database
    if source =="uq":
        if KPI not in ["Close","TurnoverValue","PE1","PE2"]:
            return
    elif source =="csi":
        if KPI not in ["Close","Turnover","P/E1","D/P1"]:
            return
    # setup two time span,  short for this year(2017),long for full history
    timespan={7:"2017",
              6:"2016",
              5:"2015",
              4:"2014",
              3:"2013",
              2:"2012",
              1:"2011",
              0:"2007:2016"}               #define the time span for each year. 0 is for full range(2007~2017)
    
    history= history.loc[ticker][KPI]  #show the selected year KPI performances
    
    history_thisyear =history.loc[timespan[7]]
    
    history_compare =[history,history_thisyear]
    
    #his=history.swaplevel().sort_index().loc["2014"]
    
    #initialize the matplot, chinese font and size and qty of subplots
   
    fig, axes = plt.subplots(1,2, figsize=(10,5),sharey=True)


    for i,v_history in enumerate(history_compare): #0 for full histry, 1 for 2017
        mean=v_history.mean()
        std =v_history.std()
        last=v_history.tail(1)
        
        #print(last.index.strftime('%Y-%m-%d'),last.values[0])
       
        fig.axes[i].plot(v_history,"b")
        #xticks = v_history.date_range(start=dStart, end=dEnd, freq='W-Tue')
        #fig.axes[i].set_xticklabels(rotation=30)      
        
        fig.axes[i].legend(loc='best') 
        if np.isnan(mean) or  np.isnan(std):
            pass
        else:
            
            fig.axes[i].axhline(mean,color="y",label ="mean") 
            fig.axes[i].axhline(mean + std,color="r",label ="+Z1") 
            fig.axes[i].axhline(mean - std,color="g",label ="-Z1") 
            #fig.axes[i].set_title(today)
            
            fig.axes[i].axhline(round(mean +2*std,2), linestyle ="dashed",c="k",label="+Z2",lw=0.5)
            fig.axes[i].axhline(round(mean -2*std,2), linestyle ="dashed",c="k",label="-Z2",lw=0.5) 
            
    today="{2} {0}:{1:,.2f}".format(KPI,last.values[0],last.index.strftime('%Y-%m-%d'))
    fig.suptitle("({0} _{1}) KPI performance \n {2}  ".format(shortname, ticker,today))    
    fig.autofmt_xdate(rotation=45, ha='right')
    
    plt.subplots_adjust(wspace=0, hspace=0.5)
    plt.show()

4 历史数据分组正态化处理 -获得Z值

infolist = ["Close","PE1","PE2","PB1","PB2","TurnoverValue","TurnoverVol"] 
UQ_Stat=pd.DataFrame()


def COEV(x):
    return( x.std()/x.mean())
        
def init(data_grp,column):
    #print(df.head(2))
    #df_grp =df.groupby(level="ticker")
    df_Stat = data_grp[column].agg(['count',np.mean, np.std,COEV,'last'])
                                   
    df_Stat["Zscore"]=(df_Stat["last"]-df_Stat["mean"])/df_Stat["std"]
    df_Stat["Group_Type"]=column
    #print(df_Stat.columns)
    return df_Stat.sort_values(by="Zscore")
    
History_grp=history.groupby(level="ticker")

for info in infolist:
    UQ_Stat=UQ_Stat.append(init(History_grp,info))


print("{0} 只指数将被分析".format(UQ_Stat.groupby(level=0).count().shape[0]))

5 热门指数当日和历史图

如该名单有侵权,我受到通知后,将立刻删除。热门指数主要参考ETF拯救世界和银行螺丝钉两位雪球大V的讨论文章

BigVlist=["000015","000922","CSPSADRP","500SNLV","000170","399975","000905","399989","000827","399006","000991","000300"]
mylist=["399417","399971","399814","000852"]
skiplist = ["CSPSADRP","500SNLV","399975","000170"]
hotlist=(BigVlist + mylist)
for i in skiplist:    hotlist.remove(i)


"""
Hotlist 中是E大和钉大关注的指数,里面有些是重合的。
个人感觉E大比较注重资产配置,投资节奏;钉大比较注重现金流,即每个投资品种的股息率,回报率。

上证红利指数(000015)、
中证红利指数(000922)、
标普A股红利机会指数(CSPSADRP)、#缺数据,略
中证500行业中性低波指数(500SNLV),#缺数据,略
50AH优选 (000170) #缺数据,略
证券指数(399975) #缺数据,略
中证500(000905)
中证医疗(399989) 
中证环保(000827)
创业板(399006)
全指医药(000991)

"""
mask1 = UQ_Stat.index.isin(hotlist)
mask2 = UQ_Stat["Group_Type"]=="Close"
mask = mask1 & mask2
tmp_count=UQ_Stat.loc[mask,"count"]/250

tmp =UQ_Stat.pivot(columns="Group_Type",values="Zscore").loc[hotlist]
tmp=pd.concat([tmp,tmp_count],axis=1)
tmp=tmp.join(sec_map[["secShortName"]])
columns_E2Cmap = {"secShortName":"名称",
                        "Close": "收盘价_Z值", 
                        "PB1": "市净率_Z值",
                       "PE1": "市盈率Z指",
                        "TurnoverValue":"成交额_Z值",
                        "TurnoverVol":"成交量_Z指",
                        "count":"交易记录(年)",
                        #"Group_Type":"指标类型",                       
                       }

tmp=tmp[["secShortName","PE1","Close","PB1","TurnoverValue","TurnoverVol","count"]].rename(columns=columns_E2Cmap
                       )
tmp.index.name="代码"
print("2017年11月16日热门指数估值高低表")
print("交易记录年数约长越可靠")
tmp.sort_values(by=["交易记录(年)","市盈率Z指","收盘价_Z值"],ascending=False).round(2).style.bar(align="zero", color=[ '#5fba7d','#d65f5f',],width=100/2)


6 全市场概览 - (价格,市盈率,市净率)

  • 查看和比较目前所有指数的Z值平均数
    • [-0.5,0.5] 常态
    • 小于-0.5,市场低估
    • 大于0.5, 市场高估活跃
#print(UQ_Stat.shape)
#shortname =sec_map.loc[ticker]["secShortName"]
columns =["PE1","TurnoverValue","Close",] 

fig,axes=plt.subplots(1,3,figsize=(15,5), sharex=True, sharey=True)
fig.suptitle("{0}指数Z值频数图(2007-2017) ".format(Today))
for i in range(len(columns)):   
    mask=UQ_Stat["Group_Type"]==columns[i]
    UQ_Result=UQ_Stat.loc[mask]
    #print(UQ_Result.shape,fig.axes[i])
    mean = UQ_Result["Zscore"].mean() 
    skew = UQ_Result["Zscore"].skew() 
   
    mean_limit=0.5
    #print(columns[i],mean,skew)
    if (mean>=(0-mean_limit) )and (mean<=mean_limit):
        color_mean = "blue"
    elif mean>mean_limit:
        color_mean ="Y"
    elif mean<(0-mean_limit):
        color_mean ="g"
    fig.axes[i].hist(UQ_Result["Zscore"],bins=50,color=color_mean)
    fig.axes[i].set_title("{0}".format(columns[i]))
    fig.axes[i].axvline(mean,color="k",linestyle='--')
    fig.axes[i].set_xlabel("{0}".format(columns[i]))
    #fig.axes[i].set_ylabel("指数频数")
  
    #UQ_Result["Zscore"].hist(bins=50)
    #fig.axes[i].legend()

    #mask2=UQ_Result["Zscore"]<=0
    print("综合指数之-{0}\t\t Z值{1:.3f}".format( columns[i],mean))
    
space =0.2
plt.subplots_adjust(wspace=0, hspace=space)



Z值频数图说明:

  • 横轴是从-3到+3的Z值范围,Y轴是指数的数量。 Z值=0时平均情况,不高不低。


  • PE1图(指数18日市盈率Z值):参考度:####
      • 当日约550只指数市盈率的Z值频数图。
      • 正常波动:绝大多数指数市盈率在[-1,1]之间。
      • 高估: 右侧,其中约有50只指数市盈率Z值>1,存在较大风险。约10只指数市盈率>2,异常高估(风险非常大)
      • 低估: 左侧,其中约有10只指数市盈率Z值<-1。 长期定投买入,是不错的产品。
  • TurnoverValue(指数的18日市盈率成交量Z值): 参考度:###
    • 当日约550只指数市盈率的Z值频数图。 正常,高估,低估分析方法参考市盈率图。
  • Close图(指数的18日收盘价Z值图) 参考度:##
    • 当日约550只指数市盈率的Z值频数图。 正常,高估,低估分析方法参考市盈率图。
    • 收盘价是绝对值,不像市盈率是相对值。因此中间线不是常规的0,而是大约1.3左右。
    • 考虑到过去10年GDP每年增长8%,10年后经济增量,企业业绩增长,指数的绝对值也在增长。


7 3年时间以上的指数Z值

7.1 市盈率Z值

  • 最高5个指数。 某指数与自己过去历史的市盈率相比,现在所处的位置。
  • 最低5个指数。 某指数与自己过去历史的市盈率相比,现在所处的位置。 我的观点Z值越大,越高估。因为大数定理认为:Z>1, Z>2,意味着继续变大的可能性小于16%, 5%。我的观点Z值越小,越低估。因为大数定理认为:Z<-1, Z<-2,意味着继续变小的可能性小于16%, 5% 综观550多只指数的历史数据。绝大部分指数的Z值都在-2,3之间。 注:少数的能源,金属类指数曾经出现过短暂疯狂的。Z值法就不太适用

7 3年时间以上的指数Z值

7.1 市盈率Z值

  • 最高5个指数。 某指数与自己过去历史的市盈率相比,现在所处的位置。
  • 最低10个指数。 某指数与自己过去历史的市盈率相比,现在所处的位置。 我的观点Z值越大,越高估。因为大数定理认为:Z>1, Z>2,意味着继续变大的可能性小于16%, 5%。我的观点Z值越小,越低估。因为大数定理认为:Z<-1, Z<-2,意味着继续变小的可能性小于16%, 5% 综观550多只指数的历史数据。
  • 注:在过去的1个月,10月到11月,许多指数出现了大幅下跌。太多的指数可以选,故本月发布最低的10个指数Z值
ndays = 750
Zscorelimit = -0.1
Type ="PE1"

mask1 = (UQ_Stat["Zscore"]<Zscorelimit)
mask3 = (UQ_Stat["count"] >=ndays)
mask2 = UQ_Stat["Group_Type"] == Type

mask =   mask3 & mask2
UQ_Z1M =UQ_Stat.loc[mask,["Zscore","last","Group_Type"]].drop_duplicates()
UQ_Z1M=UQ_Z1M.join(sec_map[["secShortName"]]).sort_values(by="Zscore")

UQ_Z1M=UQ_Z1M.rename(columns={"Zscore": "Z值", 
                       "last": "最新数据",
                       "Group_Type":"指标类型",
                       "secShortName":"名称"
                       }
                       )
UQ_Z1M.index.name="代码"
#idx = pd.IndexSlice
#UQ_Z1M=UQ_Z1M.set_index("指标类型",append=True)
#mask = UQ_Z1M["名称"].str.contains("餐")

#UQ_Z1M[mask]
print(" 550指数市盈率Z值 最高5个和最低10个指数")
UQ_Z1M.iloc[np.arange(-5,10)].round(2).sort_values(by="Z值").style.bar(subset=["Z值"],align="zero", color=[ '#5fba7d','#d65f5f',],width=100/2)

7.2 指数市盈率Z值和指数收盘价Z值加权表

mask1 =(UQ_Stat["count"]>750) 
#mask2 =(UQ_Stat["Zscore"]<0.5) 
mask = mask1
Weight_Close = 0.2
Weight_PE1  =  0.8

tmp =UQ_Stat[mask].pivot(columns="Group_Type",values="Zscore")
tmp["OverallScore"] =(tmp["Close"]* Weight_Close +tmp["PE1"]* Weight_PE1)

tmp = tmp[~tmp.OverallScore.isnull()].sort_values(by="OverallScore",ascending=True)
tmp =tmp.join(sec_map.secShortName)
tmp = tmp[[u'secShortName',u'OverallScore',u'Close', u'PE1',u'TurnoverValue' ]]
tmp.iloc[np.arange(-5,10)].sort_values(by="OverallScore").style.bar(subset=["OverallScore"],align="zero", color=[ '#5fba7d','#d65f5f',],width=100/2)

7.3 最高和最低的指数市盈率,和收盘价例子

  • 指数: 中证1000,新能源车,中证传媒,和 食品饮料。 排除中证电信,和某些没有对应基金产品的指数。
  • 红色的线表示,Z值=1
  • 绿色的线表示,Z值=-1
  • 注:
    • 左图:(以过去10年所有数据为基础计算Z值),最后一个点2017年11月16日
    • 右图:(以2017年的所有数据为基础计算Z值),最后一个点2017年11月16日
KPIs=["PE1","Close"]
secCodes =["000852","399417" ,"399971","000807"] 
for secCode in secCodes:
    for KPI in KPIs:
        show_KPI(history,secCode,KPI=KPI)


本文同时在雪球(仅结果)和知乎(包含程序和结果)发表,并发布到Python中文社区。

分享是对自己最好的投资!

祝大家周末快乐

2017年11月17日下午

文章被以下专栏收录
1 条评论
推荐阅读