概要:
本章内容讲解如何用Python语言编写一个简单的市净率因子策略,希望给有需要的同学提供一些帮助。内容主要分为:市净率因子介绍、构建简单市净率因子策略、运用Python编写出策略、策略回测结果分析。
正文:
一、市净率因子介绍
市净率是分析上市公司最基本的一项指标,该指标定义:市净率=每股市价/每股净资产。净资产是用来衡量股东权益大小的,当上市公司持续盈利时,公司净资产增大,公司股东权益增加。那么从指标的定义看,投资市净率较低的上市公司,是比较稳健的。市价代表着投资者买入公司所需要的付出的价格,而净资产代表着上市公司的股东权益,该值越小,那么投资者能以较低的价格买到相应的股东权益,该值越大,那么投资者需要花费更多的成本购买到公司的股东权益。
二、构建简单市净率因子策略
根据市净率因子的特征,可以明确的是:在公司尚可盈利的前提下,股票的理论价格应当高于其每股净资产。但目前中国A股市场,还存在着大量的非理性的散户,也正是他们非理性的投资,导致市场出现破净个股,而不合理的价格必然会慢慢趋于合理,毕竟A股市场存在一部分的机构投资者,当他们发现其中的价格差后,会持续买入,直到价格回归合理。综上所述,投资市净率较低的股票、甚至破净的个股是非常理性且稳健的。
由此我们构建出简单的市净率因子策略,每个月月初等权重买入当前市场上市净率最低的15只股票,持到月末进行换仓,也就是一直等权重持市场上市净率最低的15只个股。当然持股数量可以时5只、10只、甚至30只,但持股数不能过大,不然就无法体现股票组合是根据该因子选择的。
三、运用Python编写策略
第一步:导入编写过程中需要用到的代码包
1.from datetime import timedelta, date
导入时间数据包
2.import pandas as pd
导入pandas数据包,快捷使用为pd
第二步:创建一个选股函数,用于选择出当前市场上市净率最低的15只个股。
1.def stocks_pb(account,data)
这是一个自定义函数,用来选股,将其取名为stock_pb,后缀是参数。
2.last_date = get_last_datetime().strftime('%Y%m%d')
这行代码用来获取昨日时间。
3.pb = get_fundamentals(query(valuation.symbol,valuation.pb
).filter(valuation.pb > 0,
).order_by(valuation.pb.asc()
),date = last_date)
这行长长的代码其实是一个获取财务数据的函数:get_fundamentals(),其中query是获取对象,这里分别是symbol(股票代码)和pb(市净率),filter是过滤条件,:市净率大于0,order_by是排序:从小到大排序,data就是我们上一行代码获取的昨日日期。
4.return list(pb['valuation_symbol'][:15])
这行代码用来输出函数运行结果,输出项为list(列表),后缀是股票代码以及1-15只,运行完该函数,就得到了所需要交易的标的股票。
#详细的财务数据函数获取可以参考MINDGO的API文档http://quant.10jqka.com.cn/platform/html/help-api.html?t=data#4/0
第三步:设置交易的初始条件
1.def initialize(account):
这行代码是初始函数代码,在这下面可以设置一些交易条件
2.account.n = 15
这行代码是设置最大持股数量15个
3.account.trade_date = range(1,13,1)
这行代码是用来调仓频率,range(1,13,1)用来确定一列以1位单位并包含1而不包含13的数列,代表12个月份。
4.run_monthly(trade,date_rule=-1)
这行代码是用来控制运行周期,策略需要每个月运行,因此需要定义trade函数,使得其每个月月末调仓。
第四步:执行交易
1.def trade(account, data):
这行代码用来自定义交易函数,与选股函数同理。
2.date = get_datetime()
这行代码用来获取当前时间
3.months = get_datetime().month
这行代码用来获取当前时间的月份
4.if months in account.trade_date:
这行代码用来判断当前月份是否符合调仓月份要求,后半段 account.trade_date是初始设置条件之一。如果不满足直接跳到19行。
5.pb_list = stocks_pb(account,data)
这行代码用来获取股票列表,将选股函数的结果输出到列表上
6.stock_list = list(set(pb_list))
这行代码用来将列表的股票转移到新的列表,用来交易,其中list()是列表形式,set()用来创建集合。
7.if len(account.positions) > 0:
这行代码用来判断目前持仓股票数量,如果有数量,则进行下一步。
8. for stock in list(account.positions):
for in 是一个循环函数,将持仓股票逐一选出,并逐一进行下一步
9.if stock not in stock_list:
if 判断函数,如果选出的股票不在股票列表,则表明,个股经过一个月后不在是市盈率最低的15只了。需要进行下一步卖出。
10.order_target(stock, 0)
order_target是下单函数,用于买卖股票,参数stock是交易对象,参数0代表将股票清仓。具体下单函数可以阅读MINDGO的API文档,进行学习。http://quant.10jqka.com.cn/platform/html/help-api.html#7/145
11.if len(stock_list) > 0:
if判断函数,用来判断股票列表中个股数量是否大于0,符合则进行下一步。
12.for stock in stock_list:
for in 循环函数,将股票列表中的个股逐一选股,并逐一进行下一步。
13.if stock not in list(account.positions):
if函数,逐一拿出来的股票是否在当前持仓中,如果不在当前持仓则进到下一步。
14. if len(account.positions) < account.n :
if函数,判断当前持仓数量是否小于最大持股数,如果小于则下一步。如果不满足,则跳到17行。
15.number = account.n - len(account.positions)
计算出最大持股数与当前持仓数量的差值。
16.order_value(stock,account.cash/number)
order_valuse是下单函数,参数stock是交易对象,也就是逐一选的股票,参数account.cash是当前可用资金,number是15行计算的结果,整合就是买入的资金,即当前可用资金平均分配到每个个股。
17.else:
用来接收14行中,if函数不满足的个股
18.order_value(stock,account.cash)
order_valuse是下单函数,参数stock是交易对象,参数accunt.cash是买入金额,即当然可用资金。
19.else:
用于接收第4行代码中不符合的情况发生,进行下一步。
20pass
当出现第4行代码不符合的情况,则pass,跳过。
至此已经编写完整段代码(文末查看整段代码),随后将编写的策略进行历史数据回测。
四、策略回测结果分析
历史回测结果图
根据2014-01-01—2017-07-19的历史回测结果看,单个市净率因子策略,在最近2年半竟然能获得269.47%的收益率,而其回撤为30.62%。回测结果在确的表明,市净率因子在过去一段时间内是有效的超额收益因子,并且从17年的回测情况看,目前该因子依然有效。
根据本策略代码,同学们还可以在MINDGO平台上测试别的因子,比如:市值因子,市盈率因子等等,下图是本人所做的单因子策略之市值因子。从回测结果角度看,效果优于市净率因子,有兴趣的同学可以开始动手编写单因子策略啦!