股市要反弹?Python 帮你选出好股票

机器学习与数学

共 15880字,需浏览 32分钟

 ·

2021-03-15 03:21

首先,看一眼今日的大盘各板块的涨跌云图[1]。一片大红,股市是要反弹了吗?

股市接下来怎么走?不好说,看月线的话,说不定接下来会走熊,当然也可能反弹起来继续走牛。

但不管怎么说,个股总有机会,问题在于我们怎么找出好股呢?本篇试图结合一定的策略来从数据中选择一些所谓的好股票。

1股票数据的获取

首先,我们要获取股票数据,可以调用第三方程序包的 API,如 baostock[2]tushare[3] 等。但如果要获取当天的数据,也可以直接从相关网站上爬取。

这里我们选择雪球网,找一只股票来看一下对应的网页。

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

%matplotlib inline

我们要批量读取 A 股上市的股票数据,因此可以抓取下面这一页上的内容。

因为任务较简单,写一个函数就够了。

import requests,re,json,time,os
from bs4 import BeautifulSoup
def get_stock():
    headers = {'User-Agent''User-Agent:Mozilla/5.0'}
    c = ['股票代码','股票名称','净利润增长率','今年涨幅','滚动市净率','滚动市盈率','滚动净资产收益率']
    df = pd.DataFrame(columns=c)
    url = 'https://xueqiu.com/service/v5/stock/screener/quote/list?page='+str(1)+'&size=5000&order=desc&orderby=percent&order_by=percent&market=CN&type=sh_sz'
    response = requests.get(url,headers=headers)
    res_dict = json.loads(response.text)
    list_lsit = res_dict['data']
    db ={}
    for item in list_lsit['list']:
        db['股票代码'] = item['symbol']
        db['股票名称'] = item['name']
        db['净利润增长率'] = item['net_profit_cagr']
        db['今年涨幅'] = item['current_year_percent']
        db['滚动市净率'] = item['pb_ttm']
        db['滚动市盈率'] = item['pe_ttm']
        db['滚动净资产收益率'] = item['roe_ttm']

        df = df.append(db,ignore_index=True)
    return df
df = get_stock()
df.shape
(4304, 7)
# 将数据保存起来
df.to_csv('./沪深.csv', encoding='utf-8-sig')


# 去除带缺失值的股票
dfd = df.dropna()

# 取出 ROE_TTM PB_TTM
data = dfd[['滚动净资产收益率','滚动市净率']].astype(np.float64)
data.columns = ['ROE','PB']
# 补充几栏
data['ROE/PB'] = data['ROE']/data['PB']

data['CODE'] = dfd['股票代码']
data['NAME']   = dfd['股票名称']
data.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 2944 entries, 0 to 4286
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 ROE 2944 non-null float64
1 PB 2944 non-null float64
2 ROE/PB 2944 non-null float64
3 CODE 2944 non-null object
4 NAME 2944 non-null object
dtypes: float64(3), object(2)
memory usage: 138.0+ KB
# 看一下相关系数
corr = data.corr()

plt.subplots(figsize=(6,5))
sns.heatmap(corr, cmap='coolwarm', annot=True);

先不管三七二十一画出来总体看一下,发现有些离群值。

plt.subplots(figsize=(95))
sns.scatterplot(x='ROE', y='PB', data=data);
ax.set_xlabel('ROE')
ax.set_ylabel('PB')
Text(17.199999999999996, 0.5, 'PB')

那么就不用客气删除它们,以方便观察正常数据。

# 剔除异常值
data_inliers = data[(data['PB']<50)&(-10<data['ROE'])&(data['ROE']<100)]
data_inliers.head()

ROEPBROE/PBCODENAME
027.1554223.10998.731928SH605122N四方
214.8693509.26341.605172SZ300566激智科技
37.0975752.03843.481934SZ300428四通新材
646.44809120.27302.291131SZ300782卓胜微
70.6116500.98840.618828SH600192长城电工
# 去掉异常值后重新绘制
plt.subplots(figsize=(9,5))

ax = plt.axes()
plt.scatter(data_inliers['ROE'],data_inliers['PB'],marker='o',c='#55bbff',s=40,alpha=0.9,edgecolors='k')  
ax.set_xlabel('ROE')
ax.set_ylabel('PB')
ax.set_facecolor('#fff')


2价值投资之 ROE-PB 策略

一个投资者其实只需要学习两门功课:如何理解市场和如何估值。

—— 沃伦·巴菲特

可见,投资的一个重要内容是估值。那么用什么指标来对股票进行估值呢?

  • ROE(净资产收益率,是税后利润和所有者权益的比值),ROE 是衡量企业盈利能力的重要指标。

  • PB(市净率,是股票价格与每股净资产的比值),用来衡量估值的指标。

估值和盈利是反映和预测股票表现的两个重要维度,而市净率 PB 和净资产收益率 ROE 则是衡量估值水平和盈利能力的两个重要指标。

市净率 PB 衡量的是单位净资产的价格,PB 越低表明当前估值水平越低;净资产收益率 ROE 衡量的是单位净资产的收益率,ROE 越高表明企业利用净资产产生利润的能力越强。

股票价值投资旨在寻找当前价格低于内在价值的标的进行投资,ROE-PB 策略能够为价值投资提供良好的参考依据。中长期来看,投资者对股票的估值水平会随着公司盈利能力的增强而上升,即 PB 与 ROE 之间在理论上应当呈现正相关性。但实际中,往往存在错位的情况,即一些股票的 PB 值会高于或低于其 ROE 对应的理论水平,ROE-PB 策略可以帮助我们找出目前被市场低估的股票并进行价值投资。

ROE-PB 两个指标结合起来提供了一种投资策略。但实际上很难找到同时满足 PB 最小、ROE 最大的股票。但可以假设他们存在一个线性关系,回归线附近的点可以视为 PB、ROE 均衡水平。位于回归线右下方的股票都是 PB 被低估的,估价预期有一定的上升空间,而位于回归线上方的股票都是当前 PB 被高估的,未来很可能会下跌,因此投资可以选择位于回归线下方的股票。

鲁棒线性回归

下面我试图来把 ROE-PB 所在的回归线给找出来,简单说就是把它看成一个线性回归问题。但是这里往往并不是刚好形成一条直线,而且有很多点偏离主线。因此,我们使用鲁棒回归方法。所谓鲁棒,就是不受那些离群值的影响,能把数据中的主要部分的线性关系找出来,甚至可以抵御高达一半的离群值。

这里相当于要找一个主方向,那么使用 PCA 就能实现。我们这里是想找到 ROE-PB 潜在的回归直线,但它似乎并不是一条干净的直线,需要排除一些离群值。因此,我们使用最小协方差行列式(Minimum Covariance Determinent)来估计主方向。该方法可以更加鲁棒地计算出潜藏在大部分数据中的主方向。

from sklearn.covariance import MinCovDet
X = data_inliers[['ROE''PB']]
# 用最小协方差行列式(MCD)鲁棒估计来拟合数据
robust_cov = MinCovDet().fit(X)
robust_center = robust_cov.location_
w,v = np.linalg.eig(robust_cov.covariance_)
def mahadist(X, robust_cov):
    D_maha_dist = robust_cov.mahalanobis(X)
    return D_maha_dist

def drawmahadist(X_2d):   
    robust_cov = MinCovDet().fit(X_2d)
    robus_center = robust_cov.location_
    x_m = robus_center[0]
    y_m = robus_center[1]

    x = np.linspace(X_2d['ROE'].min(), X_2d['ROE'].max(), 100)
    y = np.linspace(X_2d['PB'].min(), X_2d['PB'].max(), 100)
    xx, yy = np.meshgrid(x, y)
    X_grid = np.c_[xx.ravel(), yy.ravel()]
    
    fig, ax = plt.subplots(figsize=(9,5))
    
    # contour
    zz = np.sqrt(mahadist(X_grid, robust_cov))
    cnt=ax.contourf(x, y, zz.reshape(100,100), levels=30, alpha=0.8, cmap='coolwarm')

    # point
    ax.scatter(data_inliers['ROE'], data_inliers['PB'], marker='o',c='#ff5500',s=40,alpha=0.25,edgecolors='k'

    # line
    t = np.linspace(0,80)[:,None]
    line_points = t*v[:,0:1].T + robust_center
    sns.lineplot(x=line_points[:,0], y=line_points[:,1], color='b', ax=ax);
    ax.grid(axis='both',linestyle=' ',color='#ffffff')
    ax.set_xlabel('ROE')
    ax.set_ylabel('PB')
drawmahadist(X)

上面图像中的一圈圈椭圆展示了距离场,只是这里的距离实际上是指马氏距离,而椭圆的长轴就是我们要找的主方向(如上图蓝色线段标示)。下面我们把位于这个主方向下面的股票挑出来。

def find_stock(data, c=None, v=None, roe_ttm=10):

    a = v[1]/v[0]
    X_c = data[['ROE','PB']]-c
    s = X_c['PB']/X_c['ROE']
    res = data[(0<s)&(s<a)]
    return res[res['ROE']>roe_ttm]
igotu = find_stock(data_inliers,robust_center,v[:,0])
# 根据 ROE 从高到低排列
roepb = igotu.sort_values(by='ROE', ascending=False)
roepb

ROEPBROE/PBCODENAME
226690.9126363.624525.082807SH603301振德医疗
190184.0926393.851921.831470SZ300552万集科技
192267.1475353.050522.011977SZ300418昆仑万维
198562.2823114.567113.637168SZ002605姚记科技
336857.2316864.927411.614987SZ002838道恩股份
286954.7448522.171925.205973SZ002869金溢科技
201254.2725662.433422.303183SZ002124天邦股份
62550.2874784.190711.999780SZ300951博硕科技
14848.0436004.489210.702041SZ002681*ST奋达
205045.4781924.175310.892197SZ002950奥美医疗
212645.4623673.854611.794315SZ300246宝莱特
200242.6481442.547416.741832SZ003021兆威机电
199942.5359692.476917.173067SZ000672上峰水泥
91738.9214432.530715.379714SH601155新城控股
404138.4022402.461515.601154SZ300498温氏股份
306834.4838223.127611.025650SZ300658延江股份
70933.6420092.398514.026270SH600685中船防务
252432.1905342.522312.762373SZ000897津滨发展
91832.1365063.33529.635556SZ300432富临精工
157530.6952752.490612.324450SZ002768国恩股份
353828.9869022.575411.255301SH600053九鼎投资
027.1554223.10998.731928SH605122N四方
389626.0321172.143212.146378SZ000011深物业A
381525.1925302.97028.481762SZ300417南华仪器
299924.4799293.03728.060032SH600167联美控股
102423.5437852.219810.606264SZ002459晶澳科技
153523.4921502.62208.959630SZ300016北陆药业
163623.2613632.42969.574154SZ000876新希望
220122.8049342.32709.800144SZ300497富祥药业
163522.4508812.56478.753804SZ002182云海金属
373722.3035742.204910.115458SH600477杭萧钢构
160621.7773402.19279.931746SZ002382蓝帆医疗
75321.0440912.43868.629579SZ000785居然之家
2320.7837772.80567.407961SH605298必得科技
277720.2725162.14759.440054SZ002641永高股份
45919.6612272.36498.313767SH605060联德股份
116319.5817382.27278.616068SH600663陆家嘴
62419.4236662.52057.706275SZ300800力合科技
193219.4152252.11289.189334SZ000935四川双马
282518.9954492.71876.986960SH603587地素时尚
184218.8631632.17298.681100SH603808歌力思
108218.6242102.48137.505828SH605368蓝天燃气
277018.6225962.74186.792106SH603156养元饮品
337418.2292222.32277.848289SZ300569天能重工
215118.1389502.34867.723303SZ300511雪榕生物
140518.0337832.63386.847059SH600731湖南海利
325617.7814382.31647.676324SH603871嘉友国际
175317.6386952.67886.584551SH603886元祖股份
11417.5528052.32057.564234SZ002487大金重工
313817.4797192.16598.070418SZ002035华帝股份
410117.4032952.58336.736846SH603319湘油泵
275917.0822542.25407.578640SZ000703恒逸石化
144716.9583512.58436.562067SZ002737葵花药业
114116.6744372.19507.596554SH688178万德斯
225416.5644962.18437.583435SZ000921海信家电
149216.5194282.52196.550390SH600783鲁信创投
171616.3163962.40126.795101SZ002697红旗连锁
302516.1681302.24287.208904SZ300771智莱科技
368016.1566742.39166.755592SZ300564筑博设计
276116.1410692.11237.641466SZ300259新天科技
41916.0628032.45056.554909SH601615明阳智能
202515.7455942.49386.313896SZ002014永新股份
67315.6907472.28466.868050SH603639海利尔
197515.6895572.49256.294707SZ002429兆驰股份
141215.6328322.14057.303355SZ000157中联重科
380115.6125172.42746.431786SZ300686智动力
309115.6054052.27956.845977SZ000628高新发展
123715.5461722.47906.271146SZ300801泰和科技
205415.4555642.48716.214292SZ002845同兴达
232315.4330542.29556.723178SH603393新天然气
417515.3982002.15067.159955SH603013亚普股份
154015.2765722.12607.185594SH601330绿色动力
136515.2746892.51496.073676SH603995甬金股份
265015.1859822.21826.846083SH603797联泰环保
100415.0429422.47616.075256SZ002940昂利康
3415.0025902.43106.171366SH605208永茂泰
89014.9884072.49825.999682SZ002911佛燃能源
248014.7070962.15296.831296SZ002616长青集团
348114.6762742.35976.219551SZ002853皮阿诺
62614.6333172.26096.472342SZ002003伟星股份
309914.5941972.31316.309367SH600511国药股份
375414.2541312.33906.094113SH600398海澜之家
267414.1715872.35096.028154SZ002300太阳电缆
373414.1627712.46295.750445SH603657春光科技
256314.0265022.21716.326508SZ300031宝通科技
4713.9907482.43495.745923SH603810丰山集团
334213.8692172.35815.881522SZ002293罗莱生活
118813.8615022.19826.305842SH603903中持股份
273513.6024892.13706.365226SZ002724海洋王
83913.4905052.28165.912739SZ002757南兴股份
331613.4656572.14066.290599SH600285羚锐制药
153713.4233152.22546.031866SZ002142宁波银行
383913.4192362.26175.933252SZ300200高盟新材
128413.3366302.14766.210016SZ300599雄塑科技
371113.2932172.16716.134104SZ300735光弘科技
96613.2396402.23335.928286SH603303得邦照明
78112.9696422.25475.752269SH603041美思德
187512.8891602.18535.898119SZ300947德必集团
170712.8871152.30775.584398SZ002130沃尔核材
117112.7897842.32255.506904SH603681永冠新材
130012.6710412.15235.887209SH603081大丰实业
155412.5610612.31425.427820SZ300082奥克股份
323312.4559092.20475.649707SZ300547川环科技
334912.4434612.26425.495743SH603109神驰机电
339912.3786632.11115.863608SH603867新化股份
340712.2316002.26455.401457SH688228开普云
399212.1278702.24325.406504SZ002399海普瑞
152811.8234042.32285.090151SH603662柯力传感
392211.8232922.13935.526711SH601628中国人寿
37311.7971432.21605.323620SH601388怡球资源
294211.7043672.25575.188796SZ300427红相股份
386811.6551372.17255.364850SZ000062深圳华强
163311.5281882.21585.202720SZ002373千方科技
356911.4893552.11435.434118SZ300692中环环保
99411.4730762.14115.358496SH600233圆通速递
72011.3312092.17505.209751SZ300580贝斯特
371511.1690452.13345.235326SH603898好莱客
314410.9643342.13655.131914SZ002498汉缆股份
212810.7804942.19494.911610SH600300维维股份
177710.6337792.21794.794526SH600295鄂尔多斯
12710.5316282.12584.954195SZ300435中泰股份
174010.2848952.22484.622840SZ300440运达科技
368410.2215162.21974.604909SZ300594朗进科技

说明

1、这里需要知道未来的 ROE,但实际上只有过去的数据,那么如何得到未来的值呢?根据已知的数据推测,如知道第一季度的数据,由它推测出整一年的数据。另外也需要弄清楚 ROE 是短期高呢还是能够长期保持高 ROE。这里简单起见,使用了滚动 ROE。

2、这里把所有股票放在一起了,实际上相同行业的股票才有可比性。可以收集感兴趣的同一行业或板块的股票,然后用 ROE-PB 分析、选择价值股。

3、另外,这里我们只使用了干巴巴的数字,实际上数字背后还有很多名堂需要去考虑。比如,这个 ROE 未来是否能保持呢?比如这里第一个股票,大家知道由于疫情的关系,这个股票去年的业绩是非亮眼,但是以后还能保持吗?或者说能保持多久?或者企业会不会趁机拓展相关业务呢?等等,值得去调查分析。如果对某些股票感兴趣,不妨去找找各家券商的研报。

聚类分析法

除了回归分析外,也可以使用聚类分析。该方法企图打破只选 PB 低估程度最大的选股方式,转而寻找受市场青睐的股票风格进行投资。

回归法假定 ROE-PB 坐标系中右下角的股票是最理想的投资标的,但实际上这部分股票的表现并非总是最优的。

不同区域代表了不同的股票风格,例如高 ROE、低 PB 区域以银行和周期股为主,高 ROE、高 PB 区域以消费板块为主,低 ROE、高 PB 区域以成长股为主等,每种区域的股票都有可能在一段时间内被市场青睐。

聚类法可以帮助我们将坐标系中的散点进行聚类,从而找出当前投资收益率最高的市场风格。

聚类法的原理示例如下图,这里代码略过,有兴趣可以在上面代码的基础上修改实现。

3PEG 策略

除了上面的 ROE-PB 策略,还有从市盈率和净利润增长率来估值的策略,即 PEG 策略。

这个策略也有来头,PEG(市盈率相对盈利增长比率)是 Jim Slater 发明的一个股票估值指标,在 PE(市盈率)估值的基础上发展起来的,它弥补了 PE 对企业动态成长性估计的不足。当时他在选股的时候就是选那些市盈率较低,同时它们的增长速度比较高的公司,这些公司有一个典型特点就是 PEG 会非常低。我们来看一下公式,

其中, 是指盈利增长速度,具体指为年净利润的增长率乘以 100。

PE 着眼于股票的低风险,而 PEG 在考虎低风险的同时,又加入了未来成长性的因素,因此这个策略相比来说更全面地对新兴市场的股票进行估值,可以认为是对个股价值研判的一个有指导意义的策略。

由于 PEG 指标是以公司的市盈率比较公司的成长速度来衡量投资价值,所以我们在选股的时候就是要选那些市盈率与公司成长速度匹配的公司。简单说,低市盈率,如果成长性不足,也不能称之为好公司,反之,高市盈率,如果成长性充分,也可以是个好标的。

一般而言,PEG < 1 意味着企业进入投资价值区域(如果负值意味着亏损,则不考虑),简单说,PEG 越小企业投资价值越大。

PEG 策略的优点是将企业的当前价值和未来成长预期进行了结合,不用考虑单独的 PE 值的意义,而是需要对其未来的盈利增长率作出预测。

dfd = df.dropna()
data_peg = dfd[['净利润增长率','滚动市盈率']].astype(np.float64)
data_peg.columns = ['NPCAGR','PE']

data_peg['CODE'] = dfd['股票代码']
data_peg['NAME']   = dfd['股票名称']
data_peg['ROE'] = dfd['滚动净资产收益率']
data_peg['今年涨幅'] = dfd['今年涨幅']
def screen(data):
    peg = data[data['NPCAGR']>0].copy()
    peg['PEG'] = peg['PE']/peg['NPCAGR']
    return peg
peg = screen(data_peg)
# 按 PEG 从大到小排列
peg_sort = peg.sort_values(by='PEG', ascending=True)
peg_sort

NPCAGRPECODENAMEROE今年涨幅PEG
2505597.0592017.047SZ002157正邦科技61.383860-6.220.011803
859677.6791908.388SH600685中船防务33.642009-11.960.012378
1205213.7673322.654SZ002582好想你51.480165-8.780.012415
1413334.5003976.375SZ300552万集科技84.092639-15.290.019058
2542349.1252876.995SZ002124天邦股份54.2725666.230.020036
2699205.1589055.923SH603301振德医疗90.912636-9.960.028870
........................
167617.11318610.733SH601318中国平安19.934096-3.400.627177
170315.56297847.415SZ002253川大智胜4.398328-8.153.046653

我们可以看到中国平安的净利润增长率为 17.113186,这个值其实我们也可以自己计算,前提就是得知道它前几年的净利润数据。

我们按 2020 年、2019 年、2018 年、 2017 年四年的净利润来算,可以得出 G 值。

np.power(1431.0/890.9,1/3)-1
0.1711258260190378

当然,这个算法是拿过去的数据来简单预测将来,当然也可以多用几年,如我们按 2020 年、2019 年、2018 年、2017 年以及 2016 年五年的净利润来算,也可以得出一个 G 值。

np.power(1431.0/623.9,1/4)-1
0.23064038253781982

那么这么算得出的这个 G 值靠谱吗?有没有更好地近似计算方法呢。你想,这件事本身挺复杂的,一个公司往往在发展成长中的,怎么变化我们并没有考虑进来,例如期间净资产发生了变化,对增长率的计算肯定是有很大影响的。所以说,上面这样计算可以说是一种相当简化的办法了。

上面公式中的动态市盈率怎么计算呢?

市盈率是静态、动态和滚动三种计算方式。静态不考虑未来,动态纯粹是预测,而滾动结合了静态和动态的利弊,但是预测准不准谁也不知道。上面代码里我们使用了滚动市盈率。也许动态的市盈率更符合对未来的预测,但预测一个公司的净利润恐怕不是我们普通投资者能把握的,那我们可以去看各个证券公司的研报。

此外由于行业的区别,不同行业的市盈率一般差异较大,而 PEG 指标可以消除这一影响,对不同行业的个股价值进行相对对比。不过需要注意的是,这个策略最关键的还是对公司业绩的预期。

另外,也可以直接打开东方财富网中的下面这个网页查看各个股票的 PEG 估值。

注意

本文纯属娱乐,不作投资参考。

参考文献

[1]

涨跌云图: http://summary.jrj.com.cn/dpyt/

[2]

baostock: http://baostock.com/baostock/index.php/Python_API%E6%96%87%E6%A1%A3

[3]

tushare: http://tushare.org/

[4]

https://zhuanlan.zhihu.com/p/350900240

[5]

https://www.ko123.com/gupiaojingyan/f12862.html

[6]

https://xueqiu.com/6882170213/149115035



浏览 44
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报