量化選股-多因子模型
總體分為基本面選股、市場行為選股?;久孢x股包括:多因子模型,風格輪動模型,行業(yè)輪動模型。市場行為選股包括:資金流選股,動量反轉(zhuǎn)模型,一致預期模型,趨勢追蹤模型和籌碼選股。
今天要講的是多因子模型。
AQF資料丨多因子選股模型是廣泛應用的一種方法。采用一系列的因子作為選股標準,滿足則買入,不滿足則賣出。不同的市場時期總有一些因子在發(fā)揮作用,該模型相對來說比較穩(wěn)定。
模型的優(yōu)點是可以綜合很多信息后給出一個選股結(jié)果。選取的因子不同以及如何綜合各個因子得到最終判斷的方法不同會產(chǎn)生不同的模型。一般來說,綜合因子的方法有打分法和回歸法兩種,打分法較為常見。
模型構(gòu)建實例
選取09-15年做樣本期,進行因子檢驗。
benchmark = 000001.XSHG
一.備選因子選取
根據(jù)市場經(jīng)驗和經(jīng)濟邏輯選取。選擇更多和更有效的因子能增強模型信息捕獲能力。 如一些基本面指標(PB、PE、EPS、增長率),技術(shù)面指標(動量、換手率、波動),或其他指標(預期收益增長、分析師一致預期變化、宏觀經(jīng)濟變量)。
結(jié)合JQ能提供的數(shù)據(jù),具體選取以下三個方面的因子:
(1)估值:賬面市值比(B/M)、盈利收益率(EPS)、動態(tài)市盈(PEG)
(2)成長性:ROE、ROA、主營毛利率(GP/R)、凈利率(P/R)
(3)資本結(jié)構(gòu):資產(chǎn)負債(L/A)、固定資產(chǎn)比例(FAP)、流通市值(CMV)
下面就上述10個因子的有效性進行驗證。
二.因子有效性檢驗
采用排序的方法檢驗備選因子的有效性。
對任一個因子,從第一個月月初計算市場每只股票該因子的大小,從小到大對樣本股票池排序,平均分為n個組合,一直持有到月末。每月初用同樣的方法調(diào)整股票池。運用一定樣本時期的數(shù)據(jù)來建立模型。
0.導入所需庫
In [1]:
import pandas as pd
from pandas import Series, DataFrame
import numpy as np
import statsmodels.api as sm
import scipy.stats as scs
import matplotlib.pyplot as plt
1.每月初取所有因子數(shù)值(以2015-01-01為例)
(1)估值:賬面市值比(B/M)、盈利收益率(EPS)、動態(tài)市盈(PEG)
(2)成長性:ROE、ROA、主營毛利率(GP/R)、凈利率(P/R)
(3)資本結(jié)構(gòu):資產(chǎn)負債(L/A)、固定資產(chǎn)比例(FAP)、流通市值(CMV)
In [2]:
factors = ['B/M','EPS','PEG','ROE','ROA','GP/R','P/R','L/A','FAP','CMV']
#月初取出因子數(shù)值
def get_factors(fdate,factors):
stock_set = get_index_stocks('000001.XSHG',fdate)
q = query(
valuation.code,
balance.total_owner_equities/valuation.market_cap/100000000,
income.basic_eps,
valuation.pe_ratio,
income.net_profit/balance.total_owner_equities,
income.net_profit/balance.total_assets,
income.total_profit/income.operating_revenue,
income.net_profit/income.operating_revenue,
balance.total_liability/balance.total_assets,
balance.fixed_assets/balance.total_assets,
valuation.circulating_market_cap
).filter(
valuation.code.in_(stock_set),
valuation.circulating_market_cap
)
fdf = get_fundamentals(q, date=fdate)
fdf.index = fdf['code']
fdf.columns = ['code'] + factors
return fdf.iloc[:,-10:]
fdf = get_factors('2015-01-01',factors)
fdf.head()
Out[2]:

2.對每個因子按大小排序(以'B/M'為例)
In [3]:
score = fdf['B/M'].order()
score.head()
Out[3]:
code600301.XSHG -0.045989600444.XSHG -0.029723600228.XSHG -0.026231600217.XSHG -0.026090600876.XSHG -0.010862Name: B/M, dtype: float64
股票池中股票數(shù)目
In [4]:
len(score)
Out[4]:
966
3.按分值將股票池五等分構(gòu)造組合port1-5
In [5]:
startdate = '2015-01-01'
enddate = '2015-02-01'
nextdate = '2015-03-01'
df = {}
CMV = fdf['CMV']
port1 = list(score.index)[: len(score)/5]
port2 = list(score.index)[ len(score)/5: 2*len(score)/5]
port3 = list(score.index)[ 2*len(score)/5: -2*len(score)/5]
port4 = list(score.index)[ -2*len(score)/5: -len(score)/5]
port5 = list(score.index)[ -len(score)/5: ]
Out[5]:
15066.599999999999
4.函數(shù)-計算組合月收益(按流通市值加權(quán))
In [6]:
def caculate_port_monthly_return(port,startdate,enddate,nextdate,CMV):
close1 = get_price(port, startdate, enddate, 'daily', ['close'])
close2 = get_price(port, enddate, nextdate, 'daily',['close'])
weighted_m_return = ((close2['close'].ix[0,:]/close1['close'].ix[0,:]-1)*CMV).sum()/(CMV.ix[port].sum())
return weighted_m_return
caculate_port_monthly_return(port1,'2015-01-01','2015-02-01','2015-03-01',fdf['CMV'])
Out[6]:
0.042660461430416276
5.函數(shù)-計算benchmark月收益
In [7]:
def caculate_benchmark_monthly_return(startdate,enddate,nextdate):
close1 = get_price(['000001.XSHG'],startdate,enddate,'daily',['close'])['close']
close2 = get_price(['000001.XSHG'],enddate, nextdate, 'daily',['close'])['close']
benchmark_return = (close2.ix[0,:]/close1.ix[0,:]-1).sum()
return benchmark_return
caculate_benchmark_monthly_return('2015-01-01','2015-02-01','2015-03-01')
Out[7]:
-0.06632375461831419
6.觀察5個組合在2015-01-01日構(gòu)建起一個月內(nèi)的收益情況
In [8]:
benchmark_return =caculate_benchmark_monthly_return(startdate,enddate,nextdate)
df['port1'] = caculate_port_monthly_return(port1,startdate,enddate,nextdate,CMV)
df['port2'] = caculate_port_monthly_return(port2,startdate,enddate,nextdate,CMV)
df['port3'] = caculate_port_monthly_return(port3,startdate,enddate,nextdate,CMV)
df['port4'] = caculate_port_monthly_return(port4,startdate,enddate,nextdate,CMV)
df['port5'] = caculate_port_monthly_return(port5,startdate,enddate,nextdate,CMV)
print Series(df)
print 'benchmark_return %s'%benchmark_returnOut[8]:
port1 0.042660port2 -0.047200port3 0.012783port4 -0.063027port5 -0.117817dtype: float64benchmark_return -0.0663237546183
7.構(gòu)建因子組合并計算每月?lián)Q倉時不同組合的月收益率
數(shù)據(jù)范圍:2009-2015共7年
得到結(jié)果monthly_return為panel數(shù)據(jù),儲存所有因子,在7×12個月內(nèi)5個組合及benchmark的月收益率
In [9]:
factors = ['B/M','EPS','PEG','ROE','ROA','GP/R','P/R','L/A','FAP','CMV']
#因為研究模塊取fundmental數(shù)據(jù)默認date為研究日期的前一天。所以要自備時間序列。按月取
year = ['2009','2010','2011','2012','2013','2014','2015']
month = ['01','02','03','04','05','06','07','08','09','10','11','12']
result = {}
for i in range(7*12):
startdate = year[i/12] + '-' + month[i%12] + '-01'
try:
enddate = year[(i+1)/12] + '-' + month[(i+1)%12] + '-01'
except IndexError:
enddate = '2016-01-01'
try:
nextdate = year[(i+2)/12] + '-' + month[(i+2)%12] + '-01'
except IndexError:
if enddate == '2016-01-01':
nextdate = '2016-02-01'
else:
nextdate = '2016-01-01'
print 'time %s'%startdate
fdf = get_factors(startdate,factors)
CMV = fdf['CMV']
#5個組合,10個因子
df = DataFrame(np.zeros(6*10).reshape(6,10),index = ['port1','port2','port3','port4','port5','benchmark'],columns = factors)
for fac in factors:
score = fdf[fac].order()
port1 = list(score.index)[: len(score)/5]
port2 = list(score.index)[ len(score)/5+1: 2*len(score)/5]
port3 = list(score.index)[ 2*len(score)/5+1: -2*len(score)/5]
port4 = list(score.index)[ -2*len(score)/5+1: -len(score)/5]
port5 = list(score.index)[ -len(score)/5+1: ]
df.ix['port1',fac] = caculate_port_monthly_return(port1,startdate,enddate,nextdate,CMV)
df.ix['port2',fac] = caculate_port_monthly_return(port2,startdate,enddate,nextdate,CMV)
df.ix['port3',fac] = caculate_port_monthly_return(port3,startdate,enddate,nextdate,CMV)
df.ix['port4',fac] = caculate_port_monthly_return(port4,startdate,enddate,nextdate,CMV)
df.ix['port5',fac] = caculate_port_monthly_return(port5,startdate,enddate,nextdate,CMV)
df.ix['benchmark',fac] = caculate_benchmark_monthly_return(startdate,enddate,nextdate)
print 'factor %s'%fac
result[i+1]=df
monthly_return = pd.Panel(result)
8.取某個因子的5個組合收益情況('L/A'為例)
In [10]:
monthly_return[:,:,'L/A']
Out [10]:

In [11]:
(monthly_return[:,:,'L/A'].T+1).cumprod().tail()
Out [11]:

9.因子檢驗量化指標
模型建立后,計算n個組合的年化復合收益、超額收益、不同市場情況下高收益組合跑贏benchmark和低收益組合跑輸benchmark的概率。
檢驗有效性的量化標準:
(1)序列1-n的組合,年化復合收益應滿足一定排序關(guān)系,即組合因子大小與收益具有較大相關(guān)關(guān)系。假定序列i的組合年化收益為Xi,則Xi與i的相關(guān)性絕對值A(chǔ)bs(Corr(Xi,i))>MinCorr。此處MinCorr為給定的最小相關(guān)閥值。
(2)序列1和n表示的兩個極端組合超額收益分別為AR1、ARn。MinARtop、MinARbottom表示最小超額收益閥值。
if AR1 > ARn #因子越小,收益越大
則應滿足AR1 > MinARtop >0 and ARn < MinARbottom < 0
if AR1 < ARn #因子越小,收益越大
則應滿足ARn > MinARtop >0 and AR1 < MinARbottom < 0
以上條件保證因子最大和最小的兩個組合,一個明顯跑贏市場,一個明顯跑輸市場。
(3) 在任何市場行情下,1和n兩個極端組合,都以較高概率跑贏or跑輸市場。
以上三個條件,可以選出過去一段時間有較好選股能力的因子。
In [12]:
total_return = {}
annual_return = {}
excess_return = {}
win_prob = {}
loss_prob = {}
effect_test = {}
MinCorr = 0.3
Minbottom = -0.05
Mintop = 0.05
for fac in factors:
effect_test[fac] = {}
monthly = monthly_return[:,:,fac]
total_return[fac] = (monthly+1).T.cumprod().iloc[-1,:]-1
annual_return[fac] = (total_return[fac]+1)**(1./6)-1
excess_return[fac] = annual_return[fac]- annual_return[fac][-1]
#判斷因子有效性
#1.年化收益與組合序列的相關(guān)性 大于 閥值
effect_test[fac][1] = annual_return[fac][0:5].corr(Series([1,2,3,4,5],index = annual_return[fac][0:5].index))
#2.高收益組合跑贏概率
#因子小,收益小,port1是輸家組合,port5是贏家組合
if total_return[fac][0] < total_return[fac][-2]:
loss_excess = monthly.iloc[0,:]-monthly.iloc[-1,:]
loss_prob[fac] = loss_excess[loss_excess<0].count()/float(len(loss_excess))
win_excess = monthly.iloc[-2,:]-monthly.iloc[-1,:]
win_prob[fac] = win_excess[win_excess>0].count()/float(len(win_excess))
effect_test[fac][3] = [win_prob[fac],loss_prob[fac]]
#超額收益
effect_test[fac][2] = [excess_return[fac][-2]*100,excess_return[fac][0]*100]
#因子小,收益大,port1是贏家組合,port5是輸家組合
else:
loss_excess = monthly.iloc[-2,:]-monthly.iloc[-1,:]
loss_prob[fac] = loss_excess[loss_excess<0].count()/float(len(loss_excess))
win_excess = monthly.iloc[0,:]-monthly.iloc[-1,:]
win_prob[fac] = win_excess[win_excess>0].count()/float(len(win_excess))
effect_test[fac][3] = [win_prob[fac],loss_prob[fac]]
#超額收益
effect_test[fac][2] = [excess_return[fac][0]*100,excess_return[fac][-2]*100]
#effect_test[1]記錄因子相關(guān)性,>0.5或<-0.5合格
#effect_test[2]記錄【贏家組合超額收益,輸家組合超額收益】
#effect_test[3]記錄贏家組合跑贏概率和輸家組合跑輸概率?!?0.5,>0.4】合格(因?qū)嶋H情況,跑輸概率暫時不考慮)
DataFrame(effect_test)
Out[12]:


檢驗結(jié)果,同時滿足上述三個條件的5個有效因子(粗體):
(1)估值:賬面市值比(B/M)、盈利收益率(EPS)、動態(tài)市盈(PEG)
(2)成長性:ROE、ROA、主營毛利率(GP/R)、凈利率(P/R)
(3)資本結(jié)構(gòu):資產(chǎn)負債(L/A)、固定資產(chǎn)比例(FAP)、流通市值(CMV)
其中:CMV,F(xiàn)AP,PEG三個因子越小收益越大;B/M,P/R越大收益越大
(1)有效因子的總收益和年化收益
小市值妖孽!!按CMV因子排序時,CMV小的組合總收益14.6倍,年化58%!總收益第二名是FAP的port2,達到2.71倍。(這也是造成FAP組合收益相關(guān)性稍低的原因)
In [13]:
effective_factors = ['B/M','PEG','P/R','FAP','CMV']
DataFrame(total_return).ix[:,effective_factors]
Out[13]:
aqf1719
In [14]:
DataFrame(annual_return).ix[:,effective_factors]
Out[14]:

(2)有效因子組合和benchmark收益率展示
In [15]:
def draw_return_picture(df):
plt.figure(figsize =(10,4))
plt.plot((df.T+1).cumprod().ix[:,0], label = 'port1')
plt.plot((df.T+1).cumprod().ix[:,1], label = 'port2')
plt.plot((df.T+1).cumprod().ix[:,2], label = 'port3')
plt.plot((df.T+1).cumprod().ix[:,3], label = 'port4')
plt.plot((df.T+1).cumprod().ix[:,4], label = 'port5')
plt.plot((df.T+1).cumprod().ix[:,5], label = 'benchmark')
plt.xlabel('return of factor %s'%fac)
plt.legend(loc=0)
for fac in effective_factors:
draw_return_picture(monthly_return[:,:,fac])
Out [15]:





3.冗余因子的剔除
(僅給出思路,此處因子較少不做這一步)
有些因子,因為內(nèi)在的邏輯比較相近等原因,選出來的組合在個股構(gòu)成和收益等方面相關(guān)性較高。所以要對這些因子做冗余剔除,保留同類因子中收益較好、區(qū)分度較高的因子。具體步驟:
(1)對不同因子的n個組合打分。收益越大分值越大。分值達到好將分值賦給每月該組合內(nèi)的所有個股。
if AR1 > ARn #因子越小,收益越大
則組合i的分值為(n-i+1)
if AR1 < ARn #因子越小,收益越小
則組合i的分值為i
(2)按月計算個股不同因子得分的相關(guān)性矩陣。得到第t月個股的因子得分相關(guān)性矩陣Score_Corrt,u,v。u,v為因子序號。
(3)計算樣本期內(nèi)相關(guān)性矩陣的平均值。即樣本期共m個月,加總矩陣后取1/m。
(4)設(shè)定得分相關(guān)性閥值MinScoreCorr。只保留與其他因子相關(guān)性較小的因子。
4.模型建立和選股
根據(jù)選好的有效因子,每月初對市場個股計算因子得分,按一定權(quán)重求得所有因子的平均分。如遇因子當月無取值時,按剩下的因子分值求加權(quán)平均。通過對個股的加權(quán)平均得分進行排序,選擇排名靠前的股票交易。
以下代碼段等權(quán)重對因子分值求和,選出分值較高的股票進行交易。
(1)模型構(gòu)建
In [16]:
def score_stock(fdate):
#CMV,F(xiàn)AP,PEG三個因子越小收益越大,分值越大,應降序排;B/M,P/R越大收益越大應順序排
effective_factors = {'B/M':True,'PEG':False,'P/R':True,'FAP':False,'CMV':False}
fdf = get_factors(fdate)
score = {}
for fac,value in effective_factors.items():
score[fac] = fdf[fac].rank(ascending = value,method = 'first')
print DataFrame(score).T.sum().order(ascending = False).head(5)
score_stock = list(DataFrame(score).T.sum().order(ascending = False).index)
return score_stock,fdf['CMV']
def get_factors(fdate):
factors = ['B/M','PEG','P/R','FAP','CMV']
stock_set = get_index_stocks('000001.XSHG',fdate)
q = query(
valuation.code,
balance.total_owner_equities/valuation.market_cap/100000000,
valuation.pe_ratio,
income.net_profit/income.operating_revenue,
balance.fixed_assets/balance.total_assets,
valuation.circulating_market_cap
).filter(
valuation.code.in_(stock_set)
)
fdf = get_fundamentals(q,date = fdate)
fdf.index = fdf['code']
fdf.columns = ['code'] + factors
return fdf.iloc[:,-5:]
[score_result,CMV] = score_stock('2016-01-01')
Out [16]:
code600382.XSHG 4274600638.XSHG 4224600291.XSHG 4092600791.XSHG 4078600284.XSHG 4031dtype: float64
In [17]:
year = ['2009','2010','2011','2012','2013','2014','2015']
month = ['01','02','03','04','05','06','07','08','09','10','11','12']
factors = ['B/M','PEG','P/R','FAP','CMV']
result = {}
for i in range(7*12):
startdate = year[i/12] + '-' + month[i%12] + '-01'
try:
enddate = year[(i+1)/12] + '-' + month[(i+1)%12] + '-01'
except IndexError:
enddate = '2016-01-01'
try:
nextdate = year[(i+2)/12] + '-' + month[(i+2)%12] + '-01'
except IndexError:
if enddate == '2016-01-01':
nextdate = '2016-02-01'
else:
nextdate = '2016-01-01'
print 'time %s'%startdate
#綜合5個因子打分后,劃分幾個組合
df = DataFrame(np.zeros(7),index = ['Top20','port1','port2','port3','port4','port5','benchmark'])
[score,CMV] = score_stock(startdate)
port0 = score[:20]
port1 = score[: len(score)/5]
port2 = score[ len(score)/5+1: 2*len(score)/5]
port3 = score[ 2*len(score)/5+1: -2*len(score)/5]
port4 = score[ -2*len(score)/5+1: -len(score)/5]
port5 = score[ -len(score)/5+1: ]
print len(score)
df.ix['Top20'] = caculate_port_monthly_return(port1,startdate,enddate,nextdate,CMV)
df.ix['port1'] = caculate_port_monthly_return(port1,startdate,enddate,nextdate,CMV)
df.ix['port2'] = caculate_port_monthly_return(port2,startdate,enddate,nextdate,CMV)
df.ix['port3'] = caculate_port_monthly_return(port3,startdate,enddate,nextdate,CMV)
df.ix['port4'] = caculate_port_monthly_return(port4,startdate,enddate,nextdate,CMV)
df.ix['port5'] = caculate_port_monthly_return(port5,startdate,enddate,nextdate,CMV)
df.ix['benchmark'] = caculate_benchmark_monthly_return(startdate,enddate,nextdate)
result[i+1]=df
backtest_results = pd.DataFrame(result)
(哈哈,此處結(jié)果下一次再公布~)
5.不足和改進
隨著模型使用人數(shù)的增加,有的因子會逐漸失效,也可能出現(xiàn)一些新的因素需要加入到因子庫中。同時,各因子的權(quán)重設(shè)計有進一步改進空間。模型本身需要做持續(xù)的再評價,并不斷改進來適應市場的變化。
.png)
完善下表,48小時內(nèi)查收全套AQF備考資料
.jpg)
金融寬客交流群: 801860357
聲明▎更多內(nèi)容請關(guān)注微信號量化金融分析師。




