跳转至


课程  因子投资  机器学习  Python  Poetry  ppw  tools  programming  Numpy  Pandas  pandas  算法  hdbscan  聚类  选股  Algo  minimum  numpy  algo  FFT  模式识别  配对交易  GBDT  LightGBM  XGBoost  statistics  CDF  KS-Test  monte-carlo  VaR  回测  过拟合  algorithms  machine learning  strategy  python  sklearn  pdf  概率  数学  面试题  量化交易  策略分类  风险管理  Info  interview  career  xgboost  PCA  wavelet  时序事件归因  SHAP  Figures  Behavioral Economics  graduate  arma  garch  人物  职场  Quantopian  figure  Banz  金融行业  买方  卖方  story  量化传奇  rsi  zigzag  穹顶压力  因子  ESG  因子策略  投资  策略  pe  ORB  Xgboost  Alligator  Indicator  factor  alpha101  alpha  技术指标  wave  quant  algorithm  pearson  spearman  tushare  因子分析  Alphalens  涨停板  herd-behaviour  momentum  因子评估  review  SMC  聪明钱  trade  history  indicators  zscore  波动率  强化学习  顶背离  freshman  resources  others  AI  DeepSeek  network  量子计算  金融交易  IBM  weekly  LLT  backtest  backtrader  研报  papers  UBL  quantlib  jupyter-notebook  scikit-learn  pypinyin  qmt  xtquant  blog  static-site  duckdb  工具  colors  free resources  barra  world quant  Alpha  openbb  数据  risk-management  llm  prompt  CANSLIM  Augment  arsenal  copilot  vscode  code  量化数据存储  hdf5  h5py  cursor  augment  trae  Jupyter  jupysql  pyarrow  parquet  数据源  quantstats  实盘  clickhouse  notebook  redis  remote-agent  AI-tools  Moonshot  回测,研报,tushare 

factor&strategy »

不看懂这篇文章,不要在量化中使用市盈率!


在9月16日的公众号文章(《节前迎来揪心一幕!谁来告诉我,A股现在有没有低估?》),我们发表了这样一个数据,就是A股的市盈率已经处于历史低位(10%分位)。两天之后,从9月18日起,沪指迎来一轮长达两个月的小牛市。

当沪指涨回到3452时,现在的市盈率又处在哪个位置?这是我们今天要回答的第一个问题。

不过,今天的重点是研究市盈率作为量化因子的有效性。我们将提出一个暂新的视角,分别在个股及沪指上,得出有趣的结论。

市盈率因子

市盈率在量化中一般被称为估值因子。它是价值因子的一个子类。价值因子包含了账面市值比,盈利因子(ROE),投资因子等。

市盈率的计算公式如下:

\[\text{PE} = \frac{\text{股票价格}}{\text{每股收益}}\]

一般认为,低估值的公司有可能股价上涨,高估值的公司有可能股价下跌,从而完成价格对价值的回归。

但实际上,PE作为因子,坑很多。

  1. PE值依赖每股收益这一财务指标,它的发布周期是季度,因此在两次数据发布之间,它只携带了收盘价这样的噪声信息(相对因子而言)。
  2. 一般而言,因子分析是横截面分析,即是一种同一时间点上、不同资产的相同属性放在一起比较排序的分析方法。但是,资产的PE差别主要由行业决定,而不是资产自身决定。所以,把PE当成因子来求资产的 alpha, 必须进行行业中性化。
  3. 周期性行业,往往是高PE值(甚至为负数时)是买入时机,因为往后公司盈利会好转,筹码后面才有派发的机会;低PE值时则是卖出时机,因为往后公司盈利会下降,坏消息不断,这时筹码派发就只能降价派发。

不过,眼见为实。我们还是拿它来回测一下。在回测时,我们要使用PE_TTM指标而不是PE,PE_TTM是滚动市盈利。PE是以年为单位发布的,这使得它的数据响应不够及时。PE_TTM在时间周期上,它虽然仍是以一年为单位进行计算,但是它是在 4 个季度的滑动窗口上计算和发布的,所以,更能及时反映公司的盈利变化。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
start = datetime.date(2018, 1, 1)
end = datetime.date(2023, 12, 31)
universe = get_stock_list(start, code_only=True)
barss = load_bars(start, end, tuple(universe))

pe = get_daily_basic(["pe_ttm"], start, end, universe) * -1
prices = barss.price.unstack(level="asset")

merged = get_clean_factor_and_forward_returns(pe, prices)
create_returns_tear_sheet(merged)

这里的get_daily_basic是我们封装的tushare函数,对应着tushare的daily_basic函数,但它可以一次把某个指标的数据取全。

注意第6行,我们给PE乘上了-1。如果你不明白这是为什么,也许可以考虑学习《因子分析与机器学习策略》这门课。

这是回测的年化Alpha。在我们的公众号上,曾出现过好多次年化Alpha超过15%的因子 -- 它们都是从无数次失败中选出来的 -- 实际上很多因子都跟PE一样:

PE因子的年化Alpha

分层均值收益图也表明该因子不具有与收益的良好线性关系。

碰壁之后,我们再回过头来思考为什么。

首先,PE是充满噪声的数据。它实际上在一年的时间里,在一个长达250个数据点的样本中,只有4次携带了信息。当我们使用PE作为因子时,实际上是在使用CLOSE作为因子。

降噪之后,发现周期股交易信号

所以,我们下面来给PE去噪声。去噪声的方法就是将PE除以收盘价。这样得到的实际上是每股收益的倒数。

生猪行业是一个强周期的行业。我们选其中一个看看:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
def deepinsight_fundamental(ticker: str, field, start, end, inverse = False):
    df = get_daily_basic([field, "close"], start, end, (ticker,))
    df = df.xs(ticker, level="asset")

    if inverse:
        df[field] = df["close"] / df[field]
    else:
        df[field] = df[field] / df["close"]

    df["d1"] = df[field].diff()
    df.ffill(inplace=True)
    _, ax = plt.subplots(figsize=(15, 5))
    ax.plot(df.index, df["d1"], label=f"{field}_denoise")
    ax2 = ax.twinx()
    ax2.plot(df.index, df["close"], label="close", color="r")
    ax.legend(loc=2)
    ax2.legend(loc=1)
    plt.show()

start = datetime.date(2005, 1, 1)
end = datetime.date(2014, 10,31)
deepinsight_fundamental("002714.XSHE", "pe_ttm", start, end)
某生🐖企业周期图 2005-2014

这是2005年到2014年间的结果。我们看到,大约在2014年4月前后,出现一个明显的信号,随后该资产价格上涨了60%(由于tushare数据在此处未复权,实际上涨更多)。

我们再来看2015到2022年的结果:

1
2
3
start = datetime.date(2014, 1, 1)
end = datetime.date(2022, 12, 31)
deepinsight_fundamental("002714.XSHE", "pe_ttm", start, end)
某生🐖企业周期图 2014-2022

在一连串的大于零的信号中,我们关注第一个,它是买入的起涨点;在一连串的于小零的信号中,我们也关注第一个,它是下跌起点(因为季度发布时间关系,有可能滞后一点)

在上图中,我们看到,2018年9月左右,出现比较明显的买入时机,随后上涨约4倍,直到2019三季度出现强烈的卖出信号(小于-3的那根线)。此后也随即出现下跌,下跌一直持续到2020年初。此后虽然股份有过上涨,也有过波动,但信号上以弱卖出居多。

Tip

这个方法是为强周期股准备的。不适用于弱周期股。

我们已经揭示了非常有趣的一个谜底。现在,我们来回答第一个问题,沪指上涨到3450之后,现在的市盈率是高还是低?

3450点,现在沪指高了吗?

我们总是通过数据来说话。先取沪指数据:

1
2
3
4
5
6
7
import akshare as ak

pe = ak.stock_market_pe_lg(symbol="上证")
pe.set_index("日期", inplace=True)
pe.index.name = "date"
pe.rename(columns={"平均市盈率": "pe", "指数": "price"}, inplace=True)
pe.tail(15)

表1 市盈率与指数

这样我们就得到了1999年以来沪指全部市盈率数据。我们用同样的方法来查看一下全市场的盈利能力。

1
2
3
4
a = pe.copy()
a["adj"] = a.price/a.pe
a = a[["price", "adj"]]
a.plot(secondary_y='price', figsize=(15,5))
去掉噪声后的PE与指数走势

从图中可以看出,这么多年以来,A股的盈利能力(以下称指标)一直是在上涨的。但是速度略有变化。在2004年到2012年间,上涨速度较快,最终带动了指数的回归,由跌转涨,不过在6000点的高位还是有点过度反应。2016年之后,指标走势变平缓,股价也受此因素影响,这也是万年三千点的真实来由。

从2016年初到2024年初,这一指标上涨了25%,当时是2737点,上涨25%就是3421点左右。这是大的趋势来看,它不会很精确,只是一种猜想。

最后,我们来回答3450点的沪指,现在处在2016年以来的哪个分位数上。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
start = datetime.date(2016, 1, 1)
pe2016 = pe.loc[start:]

rank = pe2016.rank().loc[:, "pe"]
percentile = rank / len(pe2016)
percentile.plot()

p30 = 0.3
p70 = 0.7

# 30和70分位
plt.axhline(y=p30, color='green', linestyle='--', label='30th Percentile')
plt.axhline(y=p70, color='red', linestyle='--', label='70th Percentile')

# 获取最后一期的PE值及其日期
last_pe = pe2016['pe'].iloc[-1]
last_date = pe2016.index[-1]

# 在图上标注最后一期的PE值
plt.annotate(f'PE/分位: {last_pe:.2f}/{percentile.iloc[-1]:.1%}', 
             xy=(last_date, percentile.iloc[-1]), 
             xytext=(last_date, percentile.iloc[-1] + 0.05), 
             arrowprops=dict(facecolor='red', shrink=0.05))

# 添加图例
plt.legend()

# 显示图形
plt.show()

数据表明,当前处在2016年以来的54%分位,现在处在进可攻、退可守的区间,没有明显的指标压制,也没有提供进攻的势能。在这个点上怎么操作,交给别的指标吧!