机器学习多因子策略
标签(空格分隔): 量化交易 机器学习
前言
- 在二级市场的量化策略中,多因子策略称得上是最早被创造但是同时也是变化最多的投资策略之一,好的因子意味着长期稳定的收入,多因子策略可以通过不同的渠道来实现,从而带来不同的市场表现
- 传统使用的多元线性回归模型能够获得多因子与股价之间的一定的对应关系,但是在有的时候不够稳定
- 机器学习在预测和分类中具有良好的表现,传统的多因子线性回归模型也证明了多个因子确实和股价有关系,这种关系天然的适合应用机器学习去对股票进行选股和择时,从而使得获取超额收益的可能性更高
多因子策略简介
因子可以分为择时因子和选股因子以及衍生出来的市值解释因子,择时和选股可以说是量化策略中的核心部分,而通过市值解释因子则可以帮助我们进行择时选股操作,量化策略的所有工作几乎都是围绕着这两个核心进行的,量化系统的组成可以看下图:
st=>start: 行情数据,财务数据,自定义数据,投资经验
op=>operation: 选股,择时,仓位管理,止盈止损
io=>inputoutput: 买入信号,卖出信号,交易费用,收益
e=>end
st->op->io->e
io->e
最为经典的多因子模型是Fama和French提出的三因子模型,而其基本的 股票的超额收益率可以由市场风险、市值风险、账面市值三个因素决定,因此我们将股票一个时期内的超额收益率对这三个因素进行回归,再讲回归得到的参数本时期内的超额收益率进行预测,比较真实值和预测值,如果预测值大于真实值,也就是说理论上的超额收益率应该大于当前的超额收益率,那么根据有效市场假说,未来的超额收益率应该上升,反之亦然
市值解释因子简介
2005年,在哈佛大学Matthew Rhodes-Kropf教授以及杜克大学S. Viwanathan和David T. Robinson教授合著的论文《Valuation Waves and Merger Activity: The Empirical Evidence》中,股票市值被分解为如下的三因子模型:
$$m=\alpha_0IND+\alpha_1b+\alpha_3I\ln(NI)^+\alpha_4LEV+\epsilon$$
其中,$IND$为行业虚拟变量矩阵(若该股属于某行业,则将这一行业虚拟变量的值设为1,其他行业的虚拟变量值设为0),$m$为个股的对数净资产,$NI$为公司净利润,当净利润为负的时候$I$的值为1,并且我们取净利润绝对值的对数放到回归运算中;$LEV$为公司的财务杠杆(负债除以资产),当该模型被用于拟合特定美股的时候,此三因子模型平均拟合优度超过80%,这意味着这三因子模型是有效的
2012年,马里兰大学的Charles R. Hulten教授及其博士生Janet X. Hao发表的论文《The Role Of Intangible Capital in the Transformation and Growth of the Chinese Economy》中,发现上市公司的市值与其净资产、开发支出、组织资本以及市盈率密不可分,选取开发支出力度大的公司进行下述回归,拟合优度可以达到94%:
$$m=\alpha_0+\alpha_1b+\alpha_2RD+\alpha_3O+\alpha_4PE+\epsilon$$
其中$\alpha_0$为年度虚拟变量矩阵,$m$和$b$的含义与上文相同,$RD$为对数开发支出,$O$为对数组织资本,PE为市盈率
上述的多因子模型说明,股票在某个时间点的市值可以被多个因子解释,而通过市值我们就可以对股票的价格进行预测,从而根据市值的高低来指导我们的择时策略
模型搭建和因子选择
- 基本思路:多因子策略的原理是:股票的市值可以被多个因素解释,通过对多个因素做回归计算得到理论股票值,股票的真实市值减去理论市值,得到残差,残差越小表明股价越被低估,意味着该股票未来上涨的可能性越大
- 因子选择:对数净资产,对数净利润,公司财务杠杆,营业收入增长率,对数开发支出,以及行业虚拟变量
多元线性回归模型:
$$m=\alpha_0IND+\alpha_1b+\alpha_3I\ln(NI)^+\alpha_4LEV+\alpha_5g+alpha_6RD+\epsilon$$
支持向量机回归
import pandas as pd
import numpy as np
import math
from sklearn.svm import SVR
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import learning_curve
import jqdata
def initialize(context):
set_params()
set_backtest()
run_daily(trade,'every_bar')
def set_params():
g.days = 0
g.refresh_rate = 10
g.stocknum = 10
def set_backtest():
set_benchmark('000985.XSHG')
set_option('use_real_price',True)
log.set_level('order','error')
def trade(context):
if g.days%10 == 0:
sample = get_index_stock('000985.XSHG',date=None)
q = query(valuation.code, valuation.market_cap, balance.total_assets - balance.total_liability,
balance.total_assets / balance.total_liability, income.net_profit, income.net_profit + 1,
indicator.inc_revenue_year_on_year, balance.development_expenditure).filter(valuation.code.in_(sample))
df = get_fundamentals(q,date=None)
df.columns = ['code', 'log_mcap', 'log_NC', 'LEV', 'NI_p', 'NI_n', 'g', 'log_RD']
df['log_mcap'] = np.log(df['log_mcap'])
df['log_NC'] = np.log(df['log_NC'])
df['NI_p'] = np.log(np.abs(df['NI_p']))
df['NI_n'] = np.log(np.abs(df['NI_n'][df['NI_n']<0]))
df['log_RD'] = np.log(df['log_RD'])
df.index = df.code.values
del df['code']
df = df.fillna(0)
df[df>10000] = 10000
df[df<-10000] = -10000
industry_set = ['801010', '801020', '801030', '801040', '801050', '801080', '801110', '801120', '801130',
'801140', '801150', '801160', '801170', '801180', '801200', '801210', '801230', '801710',
'801720', '801730', '801740', '801750', '801760', '801770', '801780', '801790', '801880','801890']
for i in range(len(industry_set)):
industry = get_industry_stocks(industry_set[i], date = None)
s = pd.Series([0]*len(df), index=df.index)
s[set(industry) & set(df.index)]=1
df[industry_set[i]] = s
X = df[['log_NC', 'LEV', 'NI_p', 'NI_n', 'g', 'log_RD','801010', '801020', '801030', '801040', '801050',
'801080', '801110', '801120', '801130', '801140', '801150', '801160', '801170', '801180', '801200',
'801210', '801230', '801710', '801720', '801730', '801740', '801750', '801760', '801770', '801780',
'801790', '801880', '801890']]
Y = df[['log_mcap']]
X = X.fillna(0)
Y = Y.fillna(0)
svr = SVR(kernel='rbf', gamma=0.1)
model = svr.fit(X, Y)
factor = Y - pd.DataFrame(svr.predict(X), index = Y.index, columns = ['log_mcap'])
factor = factor.sort_index(by = 'log_mcap')
stockset = list(factor.index[:10])
sell_list = list(context.portfolio.positions.keys())
for stock in sell_list:
if stock not in stockset[:g.stocknum]:
stock_sell = stock
order_target_value(stock_sell, 0)
if len(context.portfolio.positions) < g.stocknum:
num = g.stocknum - len(context.portfolio.positions)
cash = context.portfolio.cash/num
else:
cash = 0
num = 0
for stock in stockset[:g.stocknum]:
if stock in sell_list:
pass
else:
stock_buy = stock
order_target_value(stock_buy, cash)
num = num - 1
if num == 0:
break
g.days += 1
else:
g.days = g.days + 1