请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  量化平台 帖子:3364736 新帖:0

量化视角下的A股市场信息总览

牛市来了发表于:5 月 10 日 07:32回复(1)

近段时间A股可以说是内忧外患,内有经济转型面临的短期困难,外有贸易战以及全球股市表现拖累等影响,一再突破前期低点,让人不禁感慨“敢问底在何方?”。刚好最近在学习数据可视化,于是就整理了一些目前A股市场的信息帮助大家参考、判断。
(注:若无特别说明,以下数据均选取自2007年1月1日至2018年11月30日,单日数据来自2018年11月30日。若需要其他日期数据,请克隆代码自行调整日期参数。)

一、A股上市公司市值分布情况

3993f2862f17b173e8310a3497d25b3b

  • A股一半以上的股票市值小于50亿元,将近80%的股票市值在200亿以下,这一方面说明A股大部分上市公司市值偏低,实力相对不强;另一方面也是近期市场人气低迷,投资者对上市公司的估值下调,导致股价持续下跌拖累市值。实际上,根据著名的Fama-Franch三因子理论,投资小市值公司比大市值公司更有机会取得超额收益,因此目前投资A股的机会其实是越来越大的。大市值公司方面,只有4.43%的股票市值在400亿元以上,其中有6支股票市值更是超过了10000亿元,而在5000至10000亿元市值的股票仅有4支。但同时,这10支市值在5000亿元以上的股票,大约占去了A股总市值的20%。(具体前几名可运行代码得到,在这里就不贴出来了。)

二、行业总市值、流通市值占比

行业市值分布.png
行业流通市值分布.png

  • 上面第一幅图是行业总市值占比,第二幅图是行业流通市值占比。根据申万一级行业分类统计,市值占比前三的行业依次是银行、非银金融和医药生物,其中银行占有A股将近20%的市值。而流通市值占比排名没有显著变化,但在百分比上银行和非银金融下降了约1%—2%。

三、市场整体基础财务指标走势

04f5ba515969f4159931d20a2afb8e2d

  • 从A股市场历史平均市盈率数据来看,75%的时间内,平均市盈率在19倍以下。平均市盈率在12.32倍以下的时间只占25%,而目前11-12倍的市场平均市盈率,单从这一个数据来看,已经可以被称为市场底部。

a332c591f6f898b40413c23020a735a2

  • 市净率方面,A股市场历史市净率走势与市盈率几乎相同,最大的不同点是目前1.4倍左右的市净率低于全部数据的1%分位数,处于历史最低区间。市场都快破净了,底部大概也快到了吧。

(这里我们只选取了市盈率和市净率两个指标,需要其他指标改改代码就可以了。)

四、分行业财务指标值

先上图

1.历史分行业平均市盈率变动情况

  • A)2014年以前

8e708b3aff3d16379bc093a2e7e167466035784101668cc61558db6f45db6426

  • B)2014年以后

3cb9a69a33c7a6f7253890c35f8ff915a9c5f481bb524aec3acd8eb6869a103b

2.历史分行业平均市净率变动情况

  • A)2014年以前

81b85b875095eb57f3abb627d7de84d36667a9b4a7692191cd7c41417c334dc2

  • B)2014年以后

24d27ca275fd8ced5d26d4db9948cdc532c21acba0017479f6eaa457fb466a8e

3.综合分析

  • 从市盈率角度来看,在2014年调整以前的17个申万一级行业中,尽管波动率不尽相同,但相对集中,即波动率相差不大,波动率相对最低的五个行业其绝对数值也不是很小。而在2014年调整以后的28个申万行业中,分化相对明显,新加入的国防军工行业平均市盈率绝对值甚至一度超过200倍,而新加入的银行行业平均市盈率基本上始终保持在5-10倍之间,在图中明显远低于其他行业。另外,无论在调整前后,始终保持高波动率的是有色金属行业,始终保持低波动率的是家用电器和公用事业行业。
  • 从市净率角度来看,情况和市盈率类似,即2014年以前相对集中,2014年以后分化明显,特别在2014年以后,行业平均市净率波动最低的前五行业市净率从未超过4倍,其中银行更是从未超过1.5倍。始终保持行业平均市净率高波动的是休闲服务行业,低波动的是钢铁行业,而采掘行业在调整前后从波动最大第4名转为波动最小第2名。

注释:

  1. 行业分类标准采用的是申万一级行业分类,且由于申万行业分类标准在2014年2月21日作出改动,因此以此改动日为界限分别求出行业波动率,图例按其排名顺序依次排列。
  2. 在计算过程中,由于新股只有市值数据而缺省净利润数据,在市值极大的新股加入行业后,用行业净利润均值填充缺省值并不合适,会造成行业平均市盈率虚高(市净率同理)。为防止此情况,计算时剔除行业内新股,而在其上市一年后再加入计算。

五、成交量、成交总额的逐年变化

ba20e1fbc2c734436be11b74cbfbc188

  • 选取近12年A股成交总额和成交量数据绘图研究。从图中容易发现大部分时间内,成交金额数量级约为千亿,最高接近2万亿;成交量数量级约为百亿,最高在1千亿左右,且两个数据历史走势几乎完全一致。除了2015年出现高峰之外,两个数据波动几乎不大。另外成交量和成交金额在2015年高峰期之后均比2015年之前有小幅上涨,但是若考虑到通货膨胀的影响,实际增幅其实并不大。

ef06922167a24a143c3645eacd75e227

  • 从第二幅箱线图中可以看出,成交金额中位数在1000亿左右,成交量中位数在100亿左右,且上方有来自15年牛市的大量异常值。

六、市场换手率占比、平均换手率

54ee18bff955ab2ff408524ce779569cc4ab1aac33fb6cc211a8b6ebcef9dfaa

  • 市场平均换手率在短期来看波动较大,经常在短时间内出现高低频繁变换,而从长期来看,平均换手率波动也很剧烈,除两次牛市曾上探到接近9%以外,大部分时间保持在4%以下。

ac4761be77a24d8300ae3de5943563808721699f2b262e668cce628c6144f3fe

  • 分析一天内各股票换手率分布情况,以近期2018-11-30这一天换手率情况为例,将近85%的个股换手率在3%以下,只有15%左右的股票表现较为活跃,说明市场比较沉闷,投资者交易意愿不高。但是在2015年出现牛市,大盘大幅上涨期间,换手率在1%以下的股票仅有2.93%,3%以下仅占四分之一,换手率在10%以上的股票占了将近20%,说明市场非常活跃。

七、总结

根据以上统计汇总得到的结果可以看出,市场底部的特征还是比较明显的,但是具体底部会持续多久,又是否会继续下探,对于同样的信息每个人也都有自己不同的解读,不好一概而论。本文旨在达到科普、抛砖引玉的效果,欢迎大家以此为框架深入研究,有兴趣可以留言多多交流建议和思路。

#导入各种包,建议使用聚宽研究平台运行。
#若使用其他方式,请自行导入numpy和matplotlib
import pandas as pd
from jqdata import *
from jqfactor import get_factor_values


def main():
    #定义全局变量
    global date, all_stocks, industries, indu_name
    #取交易日信息
    date = get_trade_days(start_date = '2007-01-01', end_date = '2018-11-30')
    #取全部股票代码
    all_stocks = list(get_all_securities(['stock']).index)       
    #获取申万一级行业
    industries = get_industries(name='sw_l1').index
    industries = industries.tolist()
    #获取行业名
    indu_name = get_industries(name='sw_l1')['name'].tolist()
    
    #1
    Stock_MCD()
    #2
    Industry_MCD('market_cap')
    Industry_MCD('circulating_market_cap')
    #3
    Market_Fundamentals('pe_ratio')
    Market_Fundamentals('pb_ratio')
    #4
    Industry_Fundamentals('pe_ratio')
    Industry_Fundamentals('pb_ratio')
    #5
    Volume_Money()
    #6
    Turnover_Ratio()
    TR_Distribution('2018-11-30')
    TR_Distribution('2015-03-30')


#1
#股票市值分布
def Stock_MCD():
    #获取全部股票市值
    q = query(valuation.market_cap, valuation.code).filter(valuation.code.in_(all_stocks))
    dfmc = get_fundamentals(q,'2018-11-30')
    #分层处理数据
    counta = 0
    countb = 0
    countc = 0
    countd = 0
    counte = 0
    for i in range(len(dfmc)):
        try:
            if dfmc['market_cap'][i] <= 50:
                counta += 1
            elif dfmc['market_cap'][i] <= 100:
                countb += 1
            elif dfmc['market_cap'][i] <= 200:
                countc += 1
            elif dfmc['market_cap'][i] <= 400:
                countd += 1
            else:
                counte += 1
        except:
            continue
    
    #设置绘图参数        
    a = '市值小于50亿元'
    b = '市值在50至100亿元之间'
    c = '市值在100至200亿元之间'
    d = '市值在200至400亿元之间'
    e = '市值大于400亿元'
    res = [counta, countb,countc,countd,counte]
    l = [a,b,c,d,e]
    explode = [0.1] * 5
    
    #画饼状图
    fig, ax = subplots()
    ax.pie(x = res, labels = l, autopct='%.2f%%', shadow=True, explode = explode, counterclock = False)
    ax.set_title('A股市值分布情况')
    #ax.text(x = 1, y = -1, s = '单位:亿元')
    
    #找出市值最大的几支股票
    dfmc2 = dfmc.sort_values(by = 'market_cap', ascending = False)
    #对大市值股票计数
    count_m1 = 0
    count_m2 = 0
    for i in range(len(dfmc2)):
        if dfmc['market_cap'][i] > 10000:
            count_m1 += 1
            count_m2 += 1
        elif dfmc['market_cap'][i] > 5000:
            count_m2 += 1
    #求市值占比
    s = sum(dfmc2['market_cap'])
    p1 = sum(dfmc2['market_cap'][0:count_m1])/s
    p2 = sum(dfmc2['market_cap'][0:count_m2])/s
    print('市值在10000亿元以上的股票有{0}支,占A股总市值的{1:.2%}'.format(count_m1, p1))
    print('市值在5000亿元以上的股票有{0}支,占A股总市值的{1:.2%}'.format(count_m2, p2))        
    #输出市值前五只股票信息
    print('其中市值最大的前五支股票分别为:')
    stock_name = []
    market_cap = []
    for i in range(5):
        stock_name.append(get_security_info(dfmc2['code'].tolist()[i]).display_name)
        market_cap.append(dfmc2['market_cap'].tolist()[i])
    print('{0:8}{1:10}'.format('股票名称','市值(亿元)'))
    for i in range(5):
        print('{0:6}{1:10.2f}'.format(stock_name[i],market_cap[i]))

#2
#行业市值分布
def Industry_MCD(m):
    #新建一个空列表
    df = list([0] * len(industries))
    #获取全部行业内的股票
    for i in range(len(industries)):
        df[i] = get_industry_stocks(industries[i])
    #转换为DataFrame
    df = pd.DataFrame(df)
    df.index = industries
    #数据清洗
    df.dropna(how = 'all', inplace = True)
    #保留原数据备用
    df2 = df.copy()
    if m == 'market_cap':  
        #获取个股市值
        for i in range(len(df2)):
            for j in range(len(df2.columns)):
                try:
                    q = query(valuation.market_cap).filter(valuation.code.in_([df2.iloc[i,j]]))
                    df2.iloc[i,j] = get_fundamentals(q, "2018-11-30").iloc[0,0]
                except:
                    df2.iloc[i,j] = 0
    elif m == 'circulating_market_cap':
        #获取个股市值
        for i in range(len(df2)):
            for j in range(len(df2.columns)):
                try:
                    q = query(valuation.circulating_market_cap).filter(valuation.code.in_([df2.iloc[i,j]]))
                    df2.iloc[i,j] = get_fundamentals(q, "2018-11-30").iloc[0,0]
                except:
                    df2.iloc[i,j] = 0
    else:
        print('所需指标暂不存在,请输入其他指标')
        return None

    df2.fillna(0, inplace = True)            
    
    #加总得到各行业市值
    res = df2.apply(sum, axis = 'columns')
    #做饼状图
    df3 = pd.DataFrame(res)
    df3.index = indu_name
    df3.columns = ['market_cap']
    explode = [0.2] * len(df3)
    fig, ax = subplots()
    ax.pie(df3, labels = df3.index, autopct='%.2f%%', shadow=False, radius = 3, explode = explode)


#3
#市场财务指标值,f为指标名称,目前只可选'pe_ratio', 'pb_ratio'
def Market_Fundamentals(f):
    div = ''
    #市场平均市盈率
    if f == 'pe_ratio':
        div = 'net_profit_ttm'
        q = query(valuation.market_cap, valuation.code).filter(valuation.code.in_(all_stocks))
        #数据获取、清洗、整合、计算处理
        res = []
        for i in range(len(date)):
            df = get_fundamentals(q, date[i])
            df2 = get_factor_values(all_stocks, [div], end_date = date[i], count = 1)[div].T 
            df2.columns = [div]
            df_temp = pd.DataFrame({"code" : df2.index}, index = df2.index)
            df2 = df2.join(df_temp, how = "outer")
            df2.index = range(len(df2))
            df = pd.merge(df,df2, on = "code", how = "left")
            df.index = range(len(df))
            for j in range(len(df)):
                if df[div][j] <= 0 :
                    df.drop([j], inplace = True)
            df.dropna(axis = 'index', inplace = True)
            res.append(sum(df['market_cap'])*1e8/sum(df[div]))
        
        res = pd.DataFrame(res, index = date, columns = ['市场平均市盈率'])
        #作图
        figsize(15,10)
        fig, ax = subplots()
        tick_params(labelsize=15)
        plot(res)
        ax.set_title('历史12年内市场平均市盈率走势', fontsize = 20)
        ax.set_xlim('2007', '2018-11-30')
    
    #市场平均市净率
    elif f == 'pb_ratio':
        div = 'equities_parent_company_owners'
        #求历史平均市净率
        q = query(valuation.market_cap, valuation.code, balance.equities_parent_company_owners).filter(valuation.code.in_(all_stocks))
        res = []
        for i in range(len(date)):
            df = get_fundamentals(q, date[i])
            df.index = range(len(df))
            for j in range(len(df)):
                if df[div][j] <= 0 :
                    df.drop([j], inplace = True)
            df.dropna(axis = 'index', inplace = True)
            res.append(sum(df['market_cap'])*1e8/sum(df[div]))
        
        res = pd.DataFrame(res, index = date, columns = ['市场平均市净率'])
        #作图
        figsize(15,10)
        fig, ax = subplots()
        tick_params(labelsize=15)
        plot(res)
        ax.set_title('历史12年内市场平均市净率走势', fontsize = 20)
        ax.set_xlim('2007', '2018-11-30')        
    else:
        print('所需指标暂不存在,请输入其他指标')
        return None


#4
#行业财务指标值,f为指标名称,目前只可选'pe_ratio', 'pb_ratio'
def Industry_Fundamentals(f):
    div = ''
    #行业平均市盈率
    if f == 'pe_ratio':
        div = 'net_profit_ttm'
        #建立空DataFrame存储数据
        res = pd.DataFrame([[0]*len(industries)]*len(date), index = date, columns = industries)
        #数据获取、清洗、处理(会跑两个小时)
        for i in range(len(date)):
            #分别获取市值和净利润数据,并按个股代码合并为一个DataFrame
            for industry in industries:
                indu_stocks = get_industry_stocks(industry, date[i])
                if indu_stocks == []:
                    continue
                q = query(valuation.market_cap, valuation.code).filter(valuation.code.in_(indu_stocks))
                df = get_fundamentals(q, date[i])
                df2 = get_factor_values(indu_stocks, [div], end_date = date[i], count = 1)[div].T
                df2.columns = [div]
                df_temp = pd.DataFrame({"code" : df2.index}, index = df2.index)
                df2 = df2.join(df_temp, how = "outer")
                df2.index = range(len(df2))
                df = pd.merge(df,df2, on = "code", how = "left")
                df.index = range(len(df))
                #清洗亏损个股
                for j in range(len(df)):
                    if df[div][j] <= 0 :
                        df.drop([j], inplace = True)
                #去掉缺省值(缺省主要因为新股,若加入市值远大于原行业的新股,而净利润缺省,对PE计算影响极大,因此去掉缺省值)
                df.dropna(axis = 'index', inplace = True)
                #结果填充
                res.loc[date[i], industry] = sum(df['market_cap'])*1e8/sum(df[div])
                
    #行业平均市净率
    elif f == 'pb_ratio':
        div = 'equities_parent_company_owners'
        #建立空DataFrame存储数据
        res = pd.DataFrame([[0]*len(industries)]*len(date), index = date, columns = industries)
        #数据获取、清洗、处理(会跑两个小时)
        for i in range(len(date)):
            #分别获取市值和净利润数据,并按个股代码合并为一个DataFrame
            for industry in industries:
                indu_stocks = get_industry_stocks(industry, date[i])
                if indu_stocks == []:
                    continue
                q = query(valuation.market_cap, valuation.code, balance.equities_parent_company_owners).filter(valuation.code.in_(indu_stocks))
                df = get_fundamentals(q, date[i])
                df.index = range(len(df))
                #清洗亏损个股
                for j in range(len(df)):
                    if df[div][j] <= 0 :
                        df.drop([j], inplace = True)
                #去掉缺省值(缺省主要因为新股,若加入市值远大于原行业的新股,而净利润缺省,对PE计算影响极大,因此去掉缺省值)
                df.dropna(axis = 'index', inplace = True)
                #结果填充
                res.loc[date[i], industry] = sum(df['market_cap'])*1e8/sum(df[div])
    else:
        print('所需指标暂不存在,请输入其他指标')
        return None
    
    IFplot(res, f)
    
def IFplot(res, f):
    #按申万调整期划分
    res1 = res.loc[:datetime.date(2014, 2, 20)]
    res2 = res.loc[datetime.date(2014, 2, 20):]
    #2014年调整以前
    res1.columns = indu_name
    #求行业标准差代表波动率
    a = res1.apply(std, axis = 0)
    #排序
    a.sort_values(ascending = False, inplace = True)
    #去除0值行业
    i = 0
    while i in range(len(a)):
        if a[i] == 0:
            a = a.iloc[:i]
            break
        else:
            i += 1
           
    #取得波动率最大的五名        
    b = []
    for i in range(5):
        b.append(a.index.tolist()[i])
    #作图  
    fig1, ax1 = subplots()
    for ind in b:
        res1[ind].plot.line(figsize = (20,15), fontsize = 20)
    
    ax1.legend(b, fontsize = 20)
    if f == 'pe_ratio':
        ax1.set_title('2014年调整以前行业平均市盈率波动幅度最大的前五行业', fontsize = 20)
    elif f == 'pb_ratio':
        ax1.set_title('2014年调整以前行业平均市净率波动幅度最大的前五行业', fontsize = 20)
    else:
        return None
    #取得波动率最小的五名        
    c = []
    for i in range(1, 6):
        c.append(a.index.tolist()[-i])
    #作图  
    fig2, ax2 = subplots()
    for ind in c:
        res1[ind].plot.line(figsize = (20,15), fontsize = 20)
    
    ax2.legend(c, fontsize = 20)
    if f == 'pe_ratio':
        ax2.set_title('2014年调整以前行业平均市盈率波动幅度最小的前五行业', fontsize = 20)
    elif f == 'pb_ratio':
        ax2.set_title('2014年调整以前行业平均市净率波动幅度最小的前五行业', fontsize = 20)
    else:
        return None
    #2014年调整以后
    res2.columns = indu_name
    #求行业标准差代表波动率
    d = res2.apply(std, axis = 0)
    #排序
    d.sort_values(ascending = False, inplace = True)
    
    #取得波动率最大的五名        
    e = []
    
    for i in range(5):
        e.append(d.index.tolist()[i])
    #作图  
    fig3, ax3 = subplots()
    for ind in e:
        res2[ind].plot.line(figsize = (20,15), fontsize = 20)
    
    ax3.legend(e, fontsize = 20)
    if f == 'pe_ratio':
        ax3.set_title('2014年调整以后行业平均市盈率波动幅度最大的前五行业', fontsize = 20)
    elif f == 'pb_ratio':
        ax3.set_title('2014年调整以后行业平均市净率波动幅度最大的前五行业', fontsize = 20)
    else:
        return None
    #取得波动率最小的五名        
    f = []
    
    for i in range(1, 6):
        f.append(d.index.tolist()[-i])
    #作图  
    fig4, ax4 = subplots()
    for ind in f:
        res2[ind].plot.line(figsize = (20,15), fontsize = 20)
    
    ax4.legend(f, fontsize = 20)
    if f == 'pe_ratio':
        ax4.set_title('2014年调整以后行业平均市盈率波动幅度最小的前五行业', fontsize = 20)
    elif f == 'pb_ratio':
        ax4.set_title('2014年调整以后行业平均市净率波动幅度最小的前五行业', fontsize = 20)
    else:
        return None


#5
#市场成交量和成交总额变化情况
def Volume_Money():
    #获取近12年沪市和深市的成交量和成交总额数据
    df = get_price(['000001.XSHG','399001.XSHE'], start_date = '2007-01-01', end_date = '2018-11-30', 
                    fields = ['volume','money'])
    sh = df.minor_xs('000001.XSHG')
    sz = df.minor_xs('399001.XSHE')
    combine = sh.copy()
    #求和
    combine['money'] = sh['money'] + sz['money']
    combine['volume'] = sh['volume'] + sz['volume']
    #作图
    figsize(15,10)
    fig, ax = subplots(2,1)
    tick_params(labelsize=15)
    ax[0].set_title('12年内市场成交总额和成交量的变化情况', fontsize = 20)
    ax[0].plot(combine['money'])
    ax[0].legend(['成交总额'], fontsize = 15)
    ax[0].set_xlim('2007', '2018-11-30')
    ax[1].plot(combine['volume'], color = 'orange')
    ax[1].legend(['成交量'], fontsize = 15)
    ax[1].set_xlim('2007', '2018-11-30')
    #作箱线图
    combine.columns = ['成交总额', '成交量']
    combine.plot.box(subplots = True, figsize = (10,12), fontsize = 16)


#6
#市场平均换手率走势和占比
def Turnover_Ratio():
    #查询字段换手率和股票代码
    q = query(valuation.turnover_ratio,valuation.code).filter(valuation.code.in_(all_stocks))
    #按日期求得平均换手率
    res = []
    for i in range(len(date)):
        c = get_fundamentals(q, date[i])
        res.append(mean(c['turnover_ratio']))
    #转换数据格式并作图    
    res = pd.DataFrame(res)
    res.index = date
    res.columns = ['市场平均换手率']
    #作图
    figsize(30,20)
    fig, ax = subplots()
    tick_params(labelsize=30)
    plot(res)
    ax.set_title('历史12年内市场平均换手率走势', fontsize = 40)
    ax.set_xlim('2007', '2018-11-30')
    #按日期求得平均换手率
    date_temp = get_trade_days(end_date = '2018-11-30', count = 100)
    res2 = []
    for i in range(len(date_temp)):
        c = get_fundamentals(q, date_temp[i])
        tm.append(mean(c['turnover_ratio']))
    #转换数据格式并作图    
    res2 = pd.DataFrame(res2)
    res2.index = date_temp
    res2.columns = ['市场平均换手率']
    figsize(15,10)
    fig, ax = subplots()
    tick_params(labelsize=15)
    plot(res2)
    ax.set_title('历史100天内市场平均换手率走势', fontsize = 20)
#换手率数据分布统计并作图
def TR_Distribution(date):
    q = query(valuation.turnover_ratio,valuation.code).filter(valuation.code.in_(all_stocks))
    trs = get_fundamentals(q, date)['turnover_ratio']
    trs = trs.tolist()
    counta = 0
    countb = 0
    countc = 0
    countd = 0
    counte = 0
    for tr in trs:
        if tr < 1:
            counta += 1
        elif tr < 3:
            countb += 1
        elif tr < 5:
            countc += 1
        elif tr < 10:
            countd += 1
        else :
            counte += 1
    result = [counta, countb, countc, countd, counte]
    a = '换手率小于1%'
    b = '换手率在1%-3%之间'
    c = '换手率在3%-5%之间'
    d = '换手率在5%-10%之间'
    e = '换手率大于10%'
    l = [a,b,c,d,e]
    explode = [0.1] * 5
    fig, ax = subplots(figsize = (8,7))
    ax.set_title('换手率分布情况--' + date, fontsize = 15)
    ax.pie(result, labels = l, shadow = True, counterclock = False, explode = explode, autopct = '%.2f%%')


    
#调用主函数,得到结果
main()
    
    
    

全部回复

0/140

量化课程

    移动端课程