深300指数 PB&PE 处于历史最低10% 时买入持有2年的年化收益率

python 量化投资


条件:

  1. 市盈率(P/E)处于历史最低的10%范围
  2. 市净率(P/B)处于历史最低的10%范围
  3. 市盈率(P/E)& 市净率(P/B)同时处于历史最低的10%范围

追加条件: 成交量低于2年平均值

处理:

(选取日收盘价两年后的市值 / 选取日收盘价市值)* 100%

import pandas as pd

Ricequant 策略API

index_indicator - 获取指数每日估值指标

文档地址:index_indicator - 获取指数每日估值指标

数据从 2016-01-04 开始. 因为时间太靠近,结束日期放在2017-08-28, 总计404天。正好是一个熊市的起点,所以结果会比较好看.

ii = index_indicator('000300.XSHG', end_date=20170828)
  • pb: 市净率
  • pe_ttm: 市盈率
  • trade_date: 交易日
ii.head()
order_book_id pb pe_ttm trade_date
0 000300.XSHG 1.4884 12.7512 2016-01-04
1 000300.XSHG 1.4919 12.7809 2016-01-05
2 000300.XSHG 1.5200 13.0217 2016-01-06
3 000300.XSHG 1.4261 12.2178 2016-01-07
4 000300.XSHG 1.4562 12.4754 2016-01-08
  • count: 统计数量
  • mean: 平均值
  • std: 标准差
  • min: 最小值
  • 25%: 25%位数
  • 50%: 中位数
  • 75%: 3/4位数
  • max: 最大值
ii.describe()
pb pe_ttm
count 404.000000 404.000000
mean 1.338338 12.753184
std 0.054754 0.874503
min 1.222900 10.645600
25% 1.301325 11.952375
50% 1.336650 12.945000
75% 1.375225 13.455100
max 1.520000 14.518500
# s_pb: Series类型, trade_date作为索引,pb 作为值,累积历史数据用来统计
s_pb = pd.Series()
# s_pe: Series类型, trade_date作为索引,pe_ttm 作为值,累积历史数据用来统计
s_pe = pd.Series()
# selected_pb_date: 数组类型, 存放用 pb洗出来的 trade_date 时间戳
selected_pb_date = []
#  selected_pe_date: 数组类型, 存放用 pe洗出来的 trade_date 时间戳
selected_pe_date = []

# pandas.Series.quantile: pandas API 返回给定分位数
# 文档地址: http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.quantile.html
for index, row in ii.iterrows():
    if s_pb.any() and row['pb'] < s_pb.quantile(0.1):
        selected_pb_date.append(row['trade_date'])
    if s_pe.any() and row['pe_ttm'] < s_pe.quantile(0.1):
        selected_pe_date.append(row['trade_date'])
    s_pb = s_pb.append(pd.Series({row['trade_date']: row['pb']}), ignore_index=False)
    s_pe = s_pe.append(pd.Series({row['trade_date']: row['pe_ttm']}), ignore_index=False)
selected_pb_date
[Timestamp('2016-01-07 00:00:00'),
 Timestamp('2016-01-11 00:00:00'),
 Timestamp('2016-01-12 00:00:00'),
 Timestamp('2016-01-13 00:00:00'),
 Timestamp('2016-01-15 00:00:00'),
 Timestamp('2016-01-18 00:00:00'),
 Timestamp('2016-01-21 00:00:00'),
 Timestamp('2016-01-22 00:00:00'),
 Timestamp('2016-01-26 00:00:00'),
 Timestamp('2016-01-27 00:00:00'),
 Timestamp('2016-01-28 00:00:00'),
 Timestamp('2016-02-01 00:00:00'),
 Timestamp('2016-02-03 00:00:00'),
 Timestamp('2016-02-15 00:00:00'),
 Timestamp('2016-02-25 00:00:00'),
 Timestamp('2016-02-29 00:00:00'),
 Timestamp('2016-05-06 00:00:00'),
 Timestamp('2016-05-09 00:00:00'),
 Timestamp('2016-05-10 00:00:00'),
 Timestamp('2016-05-11 00:00:00'),
 Timestamp('2016-05-12 00:00:00'),
 Timestamp('2016-05-13 00:00:00'),
 Timestamp('2016-05-16 00:00:00'),
 Timestamp('2016-05-17 00:00:00'),
 Timestamp('2016-05-18 00:00:00'),
 Timestamp('2016-05-19 00:00:00'),
 Timestamp('2016-05-20 00:00:00'),
 Timestamp('2016-05-23 00:00:00'),
 Timestamp('2016-05-24 00:00:00'),
 Timestamp('2016-05-25 00:00:00'),
 Timestamp('2016-05-26 00:00:00'),
 Timestamp('2016-05-27 00:00:00'),
 Timestamp('2016-05-30 00:00:00')]

404个交易日, 用pb洗出33个值。

len(selected_pb_date)
33
selected_pe_date
[Timestamp('2016-01-07 00:00:00'),
 Timestamp('2016-01-11 00:00:00'),
 Timestamp('2016-01-12 00:00:00'),
 Timestamp('2016-01-13 00:00:00'),
 Timestamp('2016-01-15 00:00:00'),
 Timestamp('2016-01-18 00:00:00'),
 Timestamp('2016-01-21 00:00:00'),
 Timestamp('2016-01-22 00:00:00'),
 Timestamp('2016-01-26 00:00:00'),
 Timestamp('2016-01-27 00:00:00'),
 Timestamp('2016-01-28 00:00:00'),
 Timestamp('2016-02-01 00:00:00'),
 Timestamp('2016-02-03 00:00:00'),
 Timestamp('2016-02-15 00:00:00'),
 Timestamp('2016-02-25 00:00:00'),
 Timestamp('2016-02-29 00:00:00')]

404个交易日, 用pe洗出16个值。

len(selected_pe_date)
16

交易日和它的 pb 及 pe 值

# pb
ii_pb = ii.loc[ii['trade_date'].isin(selected_pb_date)];ii_pb
order_book_id pb pe_ttm trade_date
3 000300.XSHG 1.4261 12.2178 2016-01-07
5 000300.XSHG 1.3884 11.8948 2016-01-11
6 000300.XSHG 1.3972 11.9698 2016-01-12
7 000300.XSHG 1.3723 11.7569 2016-01-13
9 000300.XSHG 1.3496 11.5620 2016-01-15
10 000300.XSHG 1.3506 11.5709 2016-01-18
13 000300.XSHG 1.3357 11.4432 2016-01-21
14 000300.XSHG 1.3488 11.5549 2016-01-22
16 000300.XSHG 1.2748 10.9212 2016-01-26
17 000300.XSHG 1.2718 10.8956 2016-01-27
18 000300.XSHG 1.2426 10.6456 2016-01-28
20 000300.XSHG 1.2558 10.7581 2016-02-01
22 000300.XSHG 1.2700 10.8805 2016-02-03
25 000300.XSHG 1.2664 10.8501 2016-02-15
33 000300.XSHG 1.2581 10.7789 2016-02-25
35 000300.XSHG 1.2479 10.6908 2016-02-29
82 000300.XSHG 1.2488 11.7747 2016-05-06
83 000300.XSHG 1.2231 11.5321 2016-05-09
84 000300.XSHG 1.2245 11.5460 2016-05-10
85 000300.XSHG 1.2306 11.6028 2016-05-11
86 000300.XSHG 1.2321 11.6173 2016-05-12
87 000300.XSHG 1.2276 11.5745 2016-05-13
88 000300.XSHG 1.2342 11.6369 2016-05-16
89 000300.XSHG 1.2320 11.6164 2016-05-17
90 000300.XSHG 1.2254 11.5540 2016-05-18
91 000300.XSHG 1.2229 11.5307 2016-05-19
92 000300.XSHG 1.2285 11.5835 2016-05-20
93 000300.XSHG 1.2325 11.6206 2016-05-23
94 000300.XSHG 1.2253 11.5534 2016-05-24
95 000300.XSHG 1.2234 11.5356 2016-05-25
96 000300.XSHG 1.2260 11.5599 2016-05-26
97 000300.XSHG 1.2254 11.5543 2016-05-27
98 000300.XSHG 1.2295 11.5928 2016-05-30
ii_pb.count()
order_book_id    33
pb               33
pe_ttm           33
trade_date       33
dtype: int64
# pe
ii_pe = ii.loc[ii['trade_date'].isin(selected_pe_date)];ii_pe
order_book_id pb pe_ttm trade_date
3 000300.XSHG 1.4261 12.2178 2016-01-07
5 000300.XSHG 1.3884 11.8948 2016-01-11
6 000300.XSHG 1.3972 11.9698 2016-01-12
7 000300.XSHG 1.3723 11.7569 2016-01-13
9 000300.XSHG 1.3496 11.5620 2016-01-15
10 000300.XSHG 1.3506 11.5709 2016-01-18
13 000300.XSHG 1.3357 11.4432 2016-01-21
14 000300.XSHG 1.3488 11.5549 2016-01-22
16 000300.XSHG 1.2748 10.9212 2016-01-26
17 000300.XSHG 1.2718 10.8956 2016-01-27
18 000300.XSHG 1.2426 10.6456 2016-01-28
20 000300.XSHG 1.2558 10.7581 2016-02-01
22 000300.XSHG 1.2700 10.8805 2016-02-03
25 000300.XSHG 1.2664 10.8501 2016-02-15
33 000300.XSHG 1.2581 10.7789 2016-02-25
35 000300.XSHG 1.2479 10.6908 2016-02-29
ii_pe.count()
order_book_id    16
pb               16
pe_ttm           16
trade_date       16
dtype: int64

Ricequant 策略API

get_price - 获取合约历史数据

文档地址:获取合约历史数据

数据从 2005-01-04 开始. 用来拿到指定日期和该日期之前2年成交量的平均值

  • open: 开盘价
  • close: 收盘价
  • high: 当日最高价
  • low: 当日最低价
  • total_turnover: 总成交额
  • volume: 总成交量
get_price('000300.XSHG', start_date='2005-01-01', end_date='2016-02-12').describe()
WARN: start_date is earlier than 2005-01-04, adjusted
open close high low total_turnover volume
count 2696.000000 2696.000000 2696.000000 2696.000000 2.696000e+03 2.696000e+03
mean 2693.010190 2695.792178 2724.412325 2660.751158 9.753338e+10 8.191792e+09
std 1066.528197 1066.182710 1081.392945 1046.870550 1.232879e+11 9.020222e+09
min 816.546000 818.033000 823.860000 807.784000 2.901827e+09 5.014525e+08
25% 2172.842000 2175.877250 2190.866750 2159.729925 3.644046e+10 3.568496e+09
50% 2597.382000 2596.751000 2618.367500 2573.390500 6.382059e+10 5.697665e+09
75% 3297.854500 3295.771500 3330.435000 3251.278000 1.071529e+11 8.693812e+09
max 5862.377900 5877.202100 5891.723100 5815.608900 9.494980e+11 6.864391e+10
'''

闰年2月有29天

pandas.Timestamp 相关

文档地址: [Timestamp](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Timestamp.html#pandas.Timestamp)

'''

def leap_month_day(date, modify_year_number = 0, modify_day_number = 0):
    if date.is_leap_year and date.is_month_end and date.month == 2 :
        nday = date.day - 1
    else:
        nday = date.day
    if modify_day_number != 0 and (nday + modify_day_number) > date.days_in_month:
        nmonth = date.month + 1
        nday = nday + modify_day_number - date.days_in_month
    return pd.Timestamp(date.year + modify_year_number, date.month, nday + modify_day_number)
# 利用 成交量低于2年平均值 这个条件筛选

# final_pb_date: 存放  pb 相关的 日期
final_pb_date = []
for date in selected_pb_date:
    volumes = get_price('000300.XSHG', start_date=leap_month_day(date, -2), end_date=date, fields='volume')
    if volumes[-1] < volumes.mean():
        final_pb_date.append(date)

#print(final_pb_date)

# rates_in_pb: 保存 pb 条件下得到的利润率结果
rates_in_pb = []

for day in final_pb_date:
    price_then = get_price('000300.XSHG', start_date=day, end_date=day, fields='close').values[-1]

    price_late = get_price('000300.XSHG', start_date=leap_month_day(day, 2), end_date=leap_month_day(day, 2), fields='close')
    day_time = 1
    while(price_late.empty):
        price_late = get_price('000300.XSHG', start_date=leap_month_day(day, 2), end_date=leap_month_day(day, 2, day_time), fields='close')
        day_time += 1
    price_late = price_late.values[-1]

    #print('price_then:', price_then)
    #print('price_late:', price_late)

    rates_in_pb.append(price_late / price_then - 1)

#print('rates_in_pb:', rates_in_pb)

将结果转换为Series类型,方便操作。

s_pb_rates = pd.Series(rates_in_pb)
s_pb_rates
0     0.262803
1     0.317354
2     0.313863
3     0.338847
4     0.354795
5     0.364352
6     0.407371
7     0.392854
8     0.489980
9     0.468089
10    0.507493
11    0.463574
12    0.449532
13    0.375343
14    0.411021
15    0.398328
16    0.224841
17    0.262916
18    0.268463
19    0.256269
20    0.265087
21    0.271342
22    0.257660
23    0.252113
24    0.272170
25    0.280406
26    0.273867
27    0.248560
28    0.249273
29    0.247537
30    0.250978
31    0.251677
32    0.214126
dtype: float64
s_pb_rates.describe()
count    33.000000
mean      0.323118
std       0.085735
min       0.214126
25%       0.256269
50%       0.273867
75%       0.392854
max       0.507493
dtype: float64

两年32.3%的平均收益率. 最大收益率50%, 最小收益率21%

下面对pe数据做同样的处理

final_pe_date = []
for date in selected_pe_date:
    if date.is_leap_year and date.is_month_end and date.month == 2 :
        nday = date.day - 1
    else:
        nday = date.day
    volumes = get_price('000300.XSHG', start_date=pd.Timestamp(date.year - 2, date.month, nday), end_date=date, fields='volume')
    if volumes[-1] < volumes.mean():
        final_pe_date.append(date)
#print(len(final_pe_date))

rates_in_pe = []

for day in final_pe_date:
    price_then = get_price('000300.XSHG', start_date=day, end_date=day, fields='close').values[-1]

    price_late = get_price('000300.XSHG', start_date=leap_month_day(day, 2), end_date=leap_month_day(day, 2), fields='close')
    day_time = 1
    while(price_late.empty):
        price_late = get_price('000300.XSHG', start_date=leap_month_day(day, 2), end_date=leap_month_day(day, 2, day_time), fields='close')
        day_time += 1
    price_late = price_late.values[-1]

    #print('price_then:', price_then)
    #print('price_late:', price_late)
    rates_in_pe.append(price_late / price_then - 1)

#print('rates_in_pb:', rates_in_pb)

s_pe_rates = pd.Series(rates_in_pe)
s_pe_rates
0     0.262803
1     0.317354
2     0.313863
3     0.338847
4     0.354795
5     0.364352
6     0.407371
7     0.392854
8     0.489980
9     0.468089
10    0.507493
11    0.463574
12    0.449532
13    0.375343
14    0.411021
15    0.398328
dtype: float64
s_pe_rates.describe()
count    16.000000
mean      0.394725
std       0.068945
min       0.262803
25%       0.350808
50%       0.395591
75%       0.453043
max       0.507493
dtype: float64

两年39.4%的平均收益率. 最大收益率50.7%, 最小收益率26.2%

这个收益率挺不错的。操作简单,又不花时间。

2016年到现在刚好是一个低谷到低谷的时间段。中间经历了一次峰值。以两年为期,有很多机会可以得到高于21%的收益。而且就是错过了,还有下一个两年。我觉得以长期资产的眼光来看待股票市场, 超过10%的年化收益率也已经高于市面上能接触到的理财产品了。 通过在指数成分股里面进一步缩小股票池,提高风险,也会进一步的提高潜在收益率。