导语:作为策略集锦压轴篇,向大家介绍完整的多因子选股策略,结合了光大证券多因子选股研报,希望能在多因子研究模块,给大家打好坚实基础。
动量因子
动量与反转效应是市场上经常出现的一种情况。所谓动量效应就是在前一段时间强势的股票,在未来一段时间继续保持强势;反转效应就是前一段时间弱的股票,未来一段时间会变强。但问题的关健是这个强势和弱势会保持多长时间和多大幅度,这是动量、反转策略需要考虑的关健问题。
衡量市场、个股的动量效应,需要运用动量因子。以下是光大证券-多因子研究系列(五):动量类因子测试研报中对于动量类因子的定义。
本篇内容选取PM_1M、PM_3M、PM_6M、PM_9M、PM_12M、PM_12MC6M、PM_12MC1M、PM_6MC1M这8个动量因子,向大家讲述动量因子选股策略。
动量多因子选股
第一步:以沪深300指数成分股为股票池,计算股票池内个股上月的8个动量因子。
第二步:计算上个月8个动量因子的超额收益数值,作为本期因子权重值。公式:上月因子超额收益=因子值前10%的个股上月收益率-因子值后10%的个股上月收益率。
第三步:删除上月无超额收益的动量因子,如果数量超过4个,则本月不再选股,定义为市场当前无明显动量,空仓避险。(择时模块)
第四步:计算出本月个股本月的8个动量因子,进行因子标准化处理,每个因子乘以上月因子超额收益值,并求和,最终值即为动量alpha值。
第五步:按alpha值挑选出前20只股票作为持仓。
注:标准化公式:因子标准值=(因子值-所有个股的因子平均值)/所有个股的因子标准差
动量多因子选股注意点
1.A股市场历来以反转市著称,因此简单的通过动量选股在很大程度上是违背市场主流的,因此我们在第三步中,特意加入了动量因子择时模块,事先对市场的动量效应进行简单检测,随后选股操作。
2.考虑到动量类因子中,各因子的值差异严重,因子需要做标准化处理,更好的体现出每个因子的权重。
3.计算出因子超额收益后,需剔除无超额收益的上月动量因子,以更好体现有效因子的价值。
以下为策略实现的基本信息:
策略实现难度:5
实现过程中所需要用到的API函数,ps:通过SuperMind量化交易平台API文档快速掌握:
需要用到的API函数 | 功能 |
---|---|
init() | 初始化函数 |
handle_bar() | 定时运行函数,按交易日或按分钟运行 |
set_benchmark() | 设置基准指数 |
history() | 获取多只股票多属性的历史行情数据 |
动量类多因子选股的核心部分是计算因子超额收益,标准化后求出最终alpha值,建议初学者前往研究环境操作。
当期因子值和当期因子权重计算完毕后需整理成一个DataFrame格式,该部分操作需要同学们熟悉DataFrame的常用操作,以下是作者实现动量类因子选股策略过程,研究环境的代码草稿,分享给同学们。
#===============以沪深300为股票池,除去ST和停牌=====
def stock(date):
stk=list(get_index_stocks('000300.SH',date))
price=get_price(stk, None, date, '1d', ['is_paused', 'is_st'], False, None, 1, is_panel=1)
stopstk=price['is_paused'].iloc[-1]
ststk=price['is_st'].iloc[-1]
startstk=(stopstk[stopstk==0].index)
okstk=(ststk[ststk==0].index)
tradestk=list(set(startstk)&set(okstk))
return tradestk
date='20180102'
stock_list=stock(date)
quote_rate_all=get_price(stock_list, None, date, '1d', ['quote_rate'], False, 'pre', 273, is_panel=1)['quote_rate']
nowmonth=quote_rate_all.iloc[-21:]
quote_rate=quote_rate_all.iloc[:-21]
PM_1M=quote_rate.iloc[-21:].sum()
PM_3M=quote_rate.iloc[-63:].sum()
PM_6M=quote_rate.iloc[-126:].sum()
PM_9M=quote_rate.iloc[-189:].sum()
PM_12M=quote_rate.iloc[:].sum()
PM_12MC6M=quote_rate.iloc[:].sum()-quote_rate.iloc[-126:].sum()
PM_12MC1M=quote_rate.iloc[:].sum()-quote_rate.iloc[-21:].sum()
PM_6MC1M=quote_rate.iloc[-126:].sum()-quote_rate.iloc[-21:].sum()
quote_rate=quote_rate.T
quote_rate['PM_1M']=PM_1M
quote_rate['PM_3M']=PM_3M
quote_rate['PM_6M']=PM_6M
quote_rate['PM_9M']=PM_9M
quote_rate['PM_12M']=PM_12M
quote_rate['PM_12MC6M']=PM_12MC6M
quote_rate['PM_12MC1M']=PM_12MC1M
quote_rate['PM_6MC1M']=PM_6MC1M
yinzi_list=['PM_1M','PM_3M','PM_6M','PM_9M','PM_12M','PM_12MC6M','PM_12MC1M','PM_6MC1M']
for i in quote_rate.columns:
if i in yinzi_list:
pass
else:
del quote_rate[i]
nowmonth=nowmonth.sum()
quote_rate['nowmonth']=nowmonth
stocknum=int(len(quote_rate.index)*0.1)
yinzi_IClist=[]
zeshi=0
for i in quote_rate.columns:
if i in yinzi_list:
quote_rate = pd.DataFrame(quote_rate).sort_values(by =i, ascending=False)
d=quote_rate['nowmonth'].iloc[:stocknum].mean()-quote_rate['nowmonth'].iloc[-stocknum:].mean()
if d <0:
zeshi=zeshi+1
yinzi_IClist.append(d)
else:
yinzi_IClist.append(d)
quote_rate_all=quote_rate_all.iloc[-252:]
PM_1M=quote_rate_all.iloc[-21:].sum()
PM_3M=quote_rate_all.iloc[-63:].sum()
PM_6M=quote_rate_all.iloc[-126:].sum()
PM_9M=quote_rate_all.iloc[-189:].sum()
PM_12M=quote_rate_all.iloc[:].sum()
PM_12MC6M=quote_rate_all.iloc[:].sum()-quote_rate_all.iloc[-126:].sum()
PM_12MC1M=quote_rate_all.iloc[:].sum()-quote_rate_all.iloc[-21:].sum()
PM_6MC1M=quote_rate_all.iloc[-126:].sum()-quote_rate_all.iloc[-21:].sum()
df=pd.DataFrame(columns=yinzi_list)
df['PM_1M']=PM_1M
df['PM_3M']=PM_3M
df['PM_6M']=PM_6M
df['PM_9M']=PM_9M
df['PM_12M']=PM_12M
df['PM_12MC6M']=PM_12MC6M
df['PM_12MC1M']=PM_12MC1M
df['PM_6MC1M']=PM_6MC1M
for i in yinzi_list:
df[i] = (df[i] - df[i].mean())/df[i].std()
df=df.T
df['IC']=yinzi_IClist
df=df[df['IC']>0]
for i in df.columns:
if i =='IC':
pass
else:
df[i]=df[i]*df['IC']
del df['IC']
alpha=df.sum()
df=df.T
df['alpha']=alpha
df['syboml']=df.index
df=pd.DataFrame(df).sort_values(by ='alpha', ascending=False)
df.head()
策略回测区间:2021.01.01-2022.12.31
回测资金:1000000
回测频率:日级
回测结果:红色曲线为策略收益率曲线,蓝色曲线为对应的基准指数收益率曲线
策略源代码:
import pandas as pd
import numpy as np
# 初始化函数,全局只运行一次
def init(context):
g.n = 20 #设置最大持仓数量为20只股票
run_monthly(handle_bar,date_rule=1)
#将trade交易函数设置为定时运行:每个月第一个交易日
context.stock = []#储存上期的股票池
g.stock='000300.SH'#设置指数获取其成分股
g.kc=False #因子择时结果是够为空仓
## 开盘时运行函数
def handle_bar(context, bar_dict):
date=get_last_datetime().strftime('%Y%m%d')
#执行选股函数:alphastock(date),并将结果导入,该股票列表是需要买入的个股。
needstock_list = alphastock(date)
if g.kc ==True:
context.stock=[]
for i in list(context.portfolio.positions):
order_target(i,0)
pass
else:
#获取上期持仓个股
holdstock_list = list(context.stock)
#确定本期需要卖出的个股
sell_list = list(set(holdstock_list)-set(needstock_list))
#执行卖出操作,运用for循环,逐个操作。
for s in sell_list:
order_target(s,0)
#确定本期需要买入的个股,其余即为继续持仓的个股
buy_list=[]
for i in needstock_list:
if i in holdstock_list:
pass
else:
buy_list.append(i)
#确定可用资金,平分分配至需买入的个股
n=len(buy_list)
cash=context.portfolio.available_cash/n
#执行买入操作
for s in range(0,n,1):
stock=list(buy_list)[s]
order_value(stock,cash)
#操作完毕,将选股结果放到上期股票池储存变量中,以备下次使用。
context.stock = frozenset(needstock_list)
#===============以沪深300为股票池,除去ST和停牌=====
def stock(date):
stk=list(get_index_stocks(g.stock,date))
#2015年前上市的
stk2=list(get_all_securities('stock','20150105').index)
price=get_price(stk, None, date, '1d', ['is_paused', 'is_st'], False, None, 1, is_panel=1)
stopstk=price['is_paused'].iloc[-1]
ststk=price['is_st'].iloc[-1]
startstk=(stopstk[stopstk==0].index)
okstk=(ststk[ststk==0].index)
tradestk=list(set(startstk)&set(okstk)&set(stk2))
return tradestk
def alphastock(date):
stock_list=stock(date)
quote_rate_all=get_price(stock_list, None, date, '1d', ['quote_rate'], False, 'pre', 273, is_panel=1)['quote_rate']
nowmonth=quote_rate_all.iloc[-21:]
quote_rate=quote_rate_all.iloc[:-21]
PM_1M=quote_rate.iloc[-21:].sum()
PM_3M=quote_rate.iloc[-63:].sum()
PM_6M=quote_rate.iloc[-126:].sum()
PM_9M=quote_rate.iloc[-189:].sum()
PM_12M=quote_rate.iloc[:].sum()
PM_12MC6M=quote_rate.iloc[:].sum()-quote_rate.iloc[-126:].sum()
PM_12MC1M=quote_rate.iloc[:].sum()-quote_rate.iloc[-21:].sum()
PM_6MC1M=quote_rate.iloc[-126:].sum()-quote_rate.iloc[-21:].sum()
quote_rate=quote_rate.T
quote_rate['PM_1M']=PM_1M
quote_rate['PM_3M']=PM_3M
quote_rate['PM_6M']=PM_6M
quote_rate['PM_9M']=PM_9M
quote_rate['PM_12M']=PM_12M
quote_rate['PM_12MC6M']=PM_12MC6M
quote_rate['PM_12MC1M']=PM_12MC1M
quote_rate['PM_6MC1M']=PM_6MC1M
yinzi_list=['PM_1M','PM_3M','PM_6M','PM_9M','PM_12M','PM_12MC6M','PM_12MC1M','PM_6MC1M']
for i in quote_rate.columns:
if i in yinzi_list:
pass
else:
del quote_rate[i]
nowmonth=nowmonth.sum()
quote_rate['nowmonth']=nowmonth
stocknum=int(len(quote_rate.index)*0.1)
yinzi_IClist=[]
zeshi=0
for i in quote_rate.columns:
if i in yinzi_list:
quote_rate = pd.DataFrame(quote_rate).sort_values(by =i, ascending=False)
d=quote_rate['nowmonth'].iloc[:stocknum].mean()-quote_rate['nowmonth'].iloc[-stocknum:].mean()
if d <0:
zeshi=zeshi+1
yinzi_IClist.append(d)
else:
yinzi_IClist.append(d)
if zeshi > 4:
g.kc = True
else:
g.kc = False
quote_rate_all=quote_rate_all.iloc[-252:]
PM_1M=quote_rate_all.iloc[-21:].sum()
PM_3M=quote_rate_all.iloc[-63:].sum()
PM_6M=quote_rate_all.iloc[-126:].sum()
PM_9M=quote_rate_all.iloc[-189:].sum()
PM_12M=quote_rate_all.iloc[:].sum()
PM_12MC6M=quote_rate_all.iloc[:].sum()-quote_rate_all.iloc[-126:].sum()
PM_12MC1M=quote_rate_all.iloc[:].sum()-quote_rate_all.iloc[-21:].sum()
PM_6MC1M=quote_rate_all.iloc[-126:].sum()-quote_rate_all.iloc[-21:].sum()
df=pd.DataFrame(columns=yinzi_list)
df['PM_1M']=PM_1M
df['PM_3M']=PM_3M
df['PM_6M']=PM_6M
df['PM_9M']=PM_9M
df['PM_12M']=PM_12M
df['PM_12MC6M']=PM_12MC6M
df['PM_12MC1M']=PM_12MC1M
df['PM_6MC1M']=PM_6MC1M
for i in yinzi_list:
df[i] = (df[i] - df[i].mean())/df[i].std()
df=df.T
df['IC']=yinzi_IClist
df=df[df['IC']>0]
for i in df.columns:
if i =='IC':
pass
else:
df[i]=df[i]*df['IC']
del df['IC']
alpha=df.sum()
df=df.T
df['alpha']=alpha
df['syboml']=df.index
df=pd.DataFrame(df).sort_values(by ='alpha', ascending=False)
#获取前20只股票的代码
needstock_list=[]
for s in range(0,g.n,1):
needstock_list.append(df.iloc[s]['syboml'])
#完成函数,输入即为10只股票代码,作为策略的股票池
return needstock_list