深300指数 PB&PE 处于历史最低10% 时买入持有2年的年化收益率
python
量化投资
条件:
- 市盈率(P/E)处于历史最低的10%范围
- 市净率(P/B)处于历史最低的10%范围
- 市盈率(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%的年化收益率也已经高于市面上能接触到的理财产品了。 通过在指数成分股里面进一步缩小股票池,提高风险,也会进一步的提高潜在收益率。