小市值策略(股票)
(以下部分内容参考《因子投资:方法与实践》一书)
1. 原理
1.1 因子投资
提到量化策略,总是绕不开因子投资。自CAPM模型提出以来,因子投资不断发展壮大,成为市场广泛关注的领域。市面上的策略很大一部分都是基于各种各样的因子创造的,因子在股票和债券市场上的应用也取得了不小的成就。
到底什么是因子投资?
20世纪60年代,资本资产定价模型(Capital Asset Pricing Model)提出,揭示了资产的预期收益率(预期超额收益率)与风险之间的关系,第一次给出了资本资产定价的直观表达式。
其中Rm表示市场预期收益率,β表示市场的风险暴露。
该公式指出资产的预期超额收益率由资产对市场风险的暴露大小决定,也就是说,资产的超额预期收益率可以完全由市场因子解释。
随着市场异象不断出现,人们发现资产的收益率并非只由市场因子决定,还受到其他因子的影响。1976年,Ross提出了无套利定价理论,构建了多因子定价模型,表达式为:
其中λ是因子预期收益率,β是对应的因子暴露,α表示误差项。
该公式指出资产的预期收益率是由一系列因子及其因子暴露加上误差项决定的。而因子投资的目的就是寻找到能够解释资产收益率的因子。
既然资产预期收益率是由众多因子决定的,什么是因子?怎么寻找因子?
根据《因子投资方法与实践》一书中的定义,“一个因子描述了众多资产共同暴露的某种系统性风险,该风险是资产收益率背后的驱动力,因子收益率正式这种系统性风险的风险溢价或风险补偿,它是这些资产的共性收益。”翻译过来就是,想要作为因子,必须能够解释多个资产的收益情况,并且能够带来正收益。
再来看式2,可以发现,资产预期收益率是由两部分组成的,除了λ以外,还有一个α项,称之为误差项,表示资产预期收益率中λ无法解释的部分。α的出现可能有以下两个原因,一种是因为模型设定错误,右侧遗漏了重要的因子;一种是由于选用样本数据可能存在一定偏差,导致在该样本数据下出现了α项。
为了确定α的出现是哪种原因,需要利用统计检验进行判断。
如果α显著为零,说明α的出现只是偶然,并不能说明定价模型存在错误;
如果α显著不为零,说明资产预期收益里尚存在定价模型未能完全解释的部分。这种情况,就成为异象。
因此,因子大体上可分为两种,一种是定价因子,也就是λ部分;一种是异象因子,也就是α部分。
1.2 规模因子
1981年Banz基于纽交所长达40年的数据发现,小市值股票月均收益率比其他股票高0.4%。其背后的原因可能是投资者普遍不愿意持有小公司股票,使得这些小公司价格普遍偏低,甚至低于成本价,因此会有较高的预期收益率。由此产生了小市值策略,即投资于市值较小的股票。市值因子也被纳入进大名鼎鼎的Fama三因子模型和五因子模型之中。
A股市场上规模因子是否有效?研究发现,2016年以前,A股市场上规模因子的显著性甚至超过了欧美等发达国家市场。但到了2017-2018年期间,大市值股票的表现明显优于小市值股票,使得规模因子在A股市场上的有效性存疑。
2. 策略逻辑
第一步:确定调仓频率,以每月第一天调仓为例
第二步:确定股票池股票数量,这里假设有30支
第三步:调仓日当天获取前一个月的历史数据,并按照市值由小到大排序
第四步:买入前30支股票
回测期:2005-01-01 到 2020-10-01
股票池:所有A股股票
回测初始资金:100万
3. 策略代码
# coding=utf-8
from __future__ import print_function, absolute_import, unicode_literals
from gm.api import *
from datetime import timedelta
"""
小市值策略
本策略每个月触发一次,计算当前沪深市场上市值最小的前30只股票,并且等权重方式进行买入。
对于不在前30的有持仓的股票直接平仓。
回测时间为:2005-01-01 08:00:00 到 2020-10-01 16:00:00
"""
def init(context):
# 每月第一个交易日的09:40 定时执行algo任务(仿真和实盘时不支持该频率)
schedule(schedule_func=algo, date_rule='1m', time_rule='09:40:00')
# 使用多少的资金来进行开仓。
context.ratio = 0.8
# 定义股票池数量
context.num = 30
# 通过get_instruments获取所有的上市股票代码
context.all_stock = get_instruments(exchanges='SHSE, SZSE', sec_types=[1], skip_suspended=False,
skip_st=False, fields='symbol, listed_date, delisted_date',
df=True)
def algo(context):
# 获取筛选时间:date1表示当前日期之前的100天,date2表示当前时间
date1 = (context.now - timedelta(days=100)).strftime("%Y-%m-%d %H:%M:%S")
date2 = context.now.strftime("%Y-%m-%d %H:%M:%S")
# 上市不足100日的新股和退市股和B股
code = context.all_stock[(context.all_stock['listed_date'] < date1) & (context.all_stock['delisted_date'] > date2) &
(context.all_stock['symbol'].str[5] != '9') & (context.all_stock['symbol'].str[5] != '2')]
# 剔除停牌和st股
df_code = get_history_instruments(symbols=code['symbol'].to_list(), start_date=date2, end_date=date2, df=True)
df_code = df_code[(df_code['is_suspended'] == 0) & (df_code['sec_level'] == 1)]
# 获取所有股票市值
fundamental = get_fundamentals_n('trading_derivative_indicator', df_code['symbol'].to_list(),
context.now, fields='TOTMKTCAP', order_by='TOTMKTCAP', count=1, df=True)
# 对市值进行排序(升序),并且获取前30个。 最后将这个series 转化成为一个list即为标的池
trade_symbols = fundamental.reset_index(
drop=True).loc[:context.num - 1, 'symbol'].to_list()
print('本次股票池有股票数目: ', len(trade_symbols))
# 计算每个个股应该在持仓中的权重
percent = 1.0 / len(trade_symbols) * context.ratio
# 获取当前所有仓位
positions = context.account().positions()
# 平不在标的池的仓位
for position in positions:
symbol = position['symbol']
if symbol not in trade_symbols:
order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market,
position_side=PositionSide_Long)
print('市价单平不在标的池的', symbol)
# 将标中已有持仓的和还没有持仓的都调整到计算出来的比例。
for symbol in trade_symbols:
order_target_percent(symbol=symbol, percent=percent, order_type=OrderType_Market,
position_side=PositionSide_Long)
print(symbol, '以市价单调整至权重', percent)
if __name__ == '__main__':
'''
strategy_id策略ID,由系统生成
filename文件名,请与本文件名保持一致
mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
token绑定计算机的ID,可在系统设置-密钥管理中生成
backtest_start_time回测开始时间
backtest_end_time回测结束时间
backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
backtest_initial_cash回测初始资金
backtest_commission_ratio回测佣金比例
backtest_slippage_ratio回测滑点比例
'''
run(strategy_id='13a64e72-e900-11eb-b05f-309c2322ba62',
filename='main.py',
mode=MODE_BACKTEST,
token='2b62e7651c9897d0cdd4a6cd818a7ba8488af710',
backtest_start_time='2005-01-01 08:00:00',
backtest_end_time='2020-10-01 16:00:00',
backtest_adjust=ADJUST_PREV,
backtest_initial_cash=1000000,
backtest_commission_ratio=0.0001,
backtest_slippage_ratio=0.0001)
4. 回测结果和稳健性分析
设定初始资金100万,手续费率为0.01%,滑点比率为0.01%。回测结果如图所示:
回测期累计收益率为447.16%,年化收益率为28.38%,沪深300指数收益率为366.77%,策略整体跑赢指数。
为了检验策略的稳健性,改变回测期和标的股票数量,得到结果如下:
n | 回测期 | 回测期长度 | 年化收益率 | 最大回撤 |
---|---|---|---|---|
30 | 2005.01.01-2020.10.01 | 15年零9个月 | 28.38% | 59.51% |
30 | 2005.01.01-2015.01.01 | 10年零9个月 | 0.99% | 0.30% |
30 | 2015.01.01-2020.10.01 | 5年零9个月 | 0.85% | 0.31% |
20 | 2005.01.01-2020.10.01 | 15年零9个月 | 20.19% | 60.53% |
10 | 2005.01.01-2020.10.01 | 15年零9个月 | 23.43% | 60.75% |
从长期来看,小市值策略能够带来一定的收益,但同时也伴随着较大的回撤水平。
从短期来看,策略收益跑输大盘。收益率低,回撤小,整体效益较差。
注:此策略只用于学习、交流、演示,不构成任何投资建议。