上篇內容,我們對決策樹進行了介紹,探討了決策樹的回歸和分類方法,並列出了一些關鍵參數的說明,構建了超參數學習曲線,本篇內容,我們將基於決策樹進行分組回測,對結果進行展示。
開始之前,我們在來回顧一下決策樹,決策樹學習能根據數據的屬性采用樹狀結構建立決策模型,能夠用來解決分類和回歸問題,決策樹方法簡單自然,符合人腦的思維邏輯,除了構建單棵決策樹,我們還可以建立多棵決策樹並通過某種方式將它們結合在一起,綜合投票產生最後的預測值,也就是隨機森林。
本篇報告中我們將基於決策樹進行預測,並將這個模型應用於多因子選股。
關於模型的思路
在用決策樹構建模型的時候,我們先來考慮這個問題,樹模型與傳統的線性模型相比,優勢和缺點在哪里?
首先,它可以處理非數值類特征,如不同板塊風格股票漲跌分類問題,也就是說,我們可以將行業標簽、甚至地域標簽、這樣的非數值特征放入特征數據中。
其次,對於連續數值的非線性問題,就比如不做倒數處理的估值因子PE、PB。理論上拿這些數據應用在決策樹中時,可以不必考慮數據在0軸上下的不同處理方式。
樹模型同樣有其缺陷,單獨決策樹使用時,對特征是帶有一定隨機性的,並且,在對訓練樣本敏感,訓練過程如不加適當的限制,則結果基本上就會過擬合。
參數尋優的問題與方法
模型的參數優化部分可以參考上篇介紹決策樹文章內容,進行學習曲線或者網格搜索,進行剪枝處理。
模型構建過程
參考《人工智能選股之隨機森林模型》中模型構建過程,我們按下面步驟進行預測模型搭建
1.數據獲取:我們選擇股票池為中證500,並剔除ST股票,剔除上市3個月內新股,獲取的數據區間為20090101到20190101,回測區間為20140101到20190101。
2.特征和標簽提取:每個自然月的第一個交易日,計算統計期的 39個因子暴露 度,作為樣本的原始特征;計算下一整個自然月的個股超額收益(以中證500 指數為基準),作為樣本的標簽。
3.特征預處理:進行去極值、標準化、中性化操作。
a) 中位數去極值:我們取每個月第一個交易日的因子數據,截面上對各個因子值進行去極值操作;
b) 缺失值處理:將缺失的因子值設為申萬一級行業相同個股的平均值。
c) 行業市值中性化:將填充缺失值後的因子暴露度對行業啞變量和取對數後的市值 做線性回歸,取殘差作為新的因子暴露度。
d) 標準化:將因子值減均值除以標準差,得到均值為0,標準差為1的因子值序列
4.訓練集合成:
a)使用回歸方法時,在每個月初截面期,將下月收益作為標簽y值,將當前時間往前推 60 個月的樣本合並,隨機選取 70%的樣本作為訓練集,餘下 30%的樣本作為測試集。
b)使用分類方法時,在每個月初截面期,選取下月收益排名前 30%的股票作為正例(y = 1),後 30%的 股票作為負例(y = 0)。 將當前時間往前推60個月的樣本合並,隨機選取 70%的樣本作為訓練集,餘下 30%的樣本作為測試集。
5.樣本內訓練:使用決策樹模型對每期的訓練集進行訓練,並計算測試集得分。
6.樣本外測試:得出模型後,以 T 月月初截面期所有樣本(即個股)預處理後的特征作為模型的輸入,得到每個樣本的 T 1 月的預測值(可以根據該預測值構建策略組合,具體細節參考 下文)
應用於選股策略
進行分層回溯是常見的單因子分析方法,根據因子值對股票進行打分,構建投資組合回測,是最直觀的衡量因子優劣的手段。樹模型屬於分類器,最終在每個月底可以產生對全部個股下月上漲或下跌的預測值(即各決策樹分類結果的投票平均值),可以將預測結果轉換為一個因子合成模型,即在每個月底將因子池中所有因子合成為一個“因子”。接下來,我們對該模型合成的這個“因子”(即個股下期預測值)進行分層回測,從各方面考察該模型的效果。仿照華泰單因子測試系列報告中的思路,分層回測模型構建方法如下
分層回測分析
股票池:中證500股票池股票,剔除 ST 股票,剔除上市 3 個月以內的股票。
回測區間:2014-01-01 至 2019-01-01(5年)
換倉期:在每個自然月第一個交易日核算因子值,在下個自然月首個交易日按當日 收盤價換倉。
數據處理方法:將模型的預測值視作單因子,因子值為空的股票不參與分層。
分層方法:按因子值大小進行排序。
評價方法:回測年化收益率、夏普比率、信息比率、最大回撤、勝率等。
下面是該模型中因子值排名靠前的50只股票組合的超額收益淨值曲線
下面是進行分組回測的各分層組合收益情況,決策樹所呈現出的分層效果並不理想,未能有明顯的分層效果
模型樣本內(外)得分
與決策樹回歸模型相比,得分即是模型判斷的正確率,決策樹分類模型能更直觀的展示決策效果,下面就是對該模型樣本內與樣本外得分情況進行了統計,在每個月月初截面期,將當前時間往前推 60 個月樣本內模型得分記為score_p,將對下一期預測值實際得分記為score_r,統計得分如下
可以看到,樣本內與樣本外得分差異還是較為明顯的,將模型外推進行預測時,預測正確率波動較大。
模型因子特征重要性統計
在上篇決策樹介紹中,我們提及過模型有很多屬性和方法可以進行獲取查看,
在具體的決策樹模型構建中,我們將當期股票的各個因子作為輸入特征,按照股票下月收益情況分為不同類別,也就是以股票下期收益為標簽,以此進行模型訓練。對於決策樹這一非線性分類器,我們依然可以 通過特征劃分過程來計算評估各個因子特征的重要性,
下面我們給出 2014-2019 年間39個模型的特征重要性評分均值柱狀圖
綜合曆史的特征重要性信息來看,波動率、動量、Beta因子對股價的預測能力明顯強於財務類因子,這也印證了A股市場投機行為顯著的特性。
總結說明
這篇內容基本上是在展示機器學習中簡單模型決策樹的工具性,更多的是研究流程方法的說明,從如何構建模型,搭建回測的角度出發,到結果展示匯總。
就模型本身而言,雖然多頭部分有超額收益,但是不足以證明該模型的優越性;模型的準確率也有待提高;特征重要性統計部分能獲知哪些因子對股價影響較大。一定有很多比決策樹更好的方法可以用,比如已知的模型中,由決策樹作為基學習器的集成算法隨機森林模型,就會比決策樹模型要好,有一些認真好學的同學問我,為什麼不直接用隨機森林呢,那就試試吧, 核心的模型代碼替換部分不算多,可以克隆代碼進行操作,或者嘗試更多的模型。
決策樹在多因子模型中的應用¶
- 第一部分:數據獲取
- 第二部分:模型構建
- 第三部分:分層回溯
- 第四部分:特征重要性
#工具函數
#工具函數
import time
from datetime import datetime, timedelta
import jqdata
import numpy as np
import pandas as pd
import math
from statsmodels import regression
import statsmodels.api as sm
import matplotlib.pyplot as plt
from jqfactor import get_factor_values
import datetime
from jqlib.technical_analysis import *
from scipy import stats
#設置畫圖樣式
plt.style.use('ggplot')
#輸入起止日期,返回所有自然日日期
def get_date_list(begin_date, end_date):
dates = []
dt = datetime.strptime(begin_date,"%Y-%m-%d")
date = begin_date[:]
while date <= end_date:
dates.append(date)
dt += timedelta(days=1)
date = dt.strftime("%Y-%m-%d")
return dates
#獲取日期列表
def get_tradeday_list(start,end,frequency=None,count=None):
if count != None:
df = get_price('000001.XSHG',end_date=end,count=count)
else:
df = get_price('000001.XSHG',start_date=start,end_date=end)
if frequency == None or frequency =='day':
return df.index
else:
df['year-month'] = [str(i)[0:7] for i in df.index]
if frequency == 'month':
return df.drop_duplicates('year-month').index
elif frequency == 'quarter':
df['month'] = [str(i)[5:7] for i in df.index]
df = df[(df['month']=='01') | (df['month']=='04') | (df['month']=='07') | (df['month']=='10') ]
return df.drop_duplicates('year-month').index
elif frequency =='halfyear':
df['month'] = [str(i)[5:7] for i in df.index]
df = df[(df['month']=='01') | (df['month']=='06')]
return df.drop_duplicates('year-month').index
def ret_se(start_date='2018-6-1',end_date='2018-7-1',stock_pool=None,weight=0):
pool = stock_pool
if len(pool) != 0:
#得到股票的曆史價格數據
df = get_price(list(pool),start_date=start_date,end_date=end_date,fields=['close']).close
df = df.dropna(axis=1)
#獲取列表中的股票流通市值對數值
df_mkt = get_fundamentals(query(valuation.code,valuation.circulating_market_cap).filter(valuation.code.in_(df.columns)))
df_mkt.index = df_mkt['code'].values
fact_se =pd.Series(df_mkt['circulating_market_cap'].values,index = df_mkt['code'].values)
fact_se = np.log(fact_se)
else:
df = get_price('000001.XSHG',start_date=start_date,end_date=end_date,fields=['close'])
df['v'] = [1]*len(df)
del df['close']
#相當於昨天的百分比變化
pct = df.pct_change()+1
pct.iloc[0,:] = 1
if weight == 0:
#等權重平均收益結果
se = pct.cumsum(axis=1).iloc[:,-1]/pct.shape[1]
return se
else:
#按權重的方式計算
se = (pct*fact_se).cumsum(axis=1).iloc[:,-1]/sum(fact_se)
return se
#獲取所有分組pct
def get_all_pct(pool_dict,trade_list,groups=5):
num = 1
for s,e in zip(trade_list[:-1],trade_list[1:]):
stock_list = pool_dict[s]
stock_num = len(stock_list)//groups
if num == 0:
pct_se_list = []
for i in range(groups):
pct_se_list.append(ret_se(start_date=s,end_date=e,stock_pool=stock_list[i*stock_num:(i+1)*stock_num]))
pct_df1 = pd.concat(pct_se_list,axis=1)
pct_df = pd.concat([pct_df,pct_df1],axis=0)
else:
pct_se_list = []
for i in range(groups):
pct_se_list.append(ret_se(start_date=s,end_date=e,stock_pool=stock_list[i*stock_num:(i+1)*stock_num]))
pct_df = pd.concat(pct_se_list,axis=1)
num = 0
return pct_df
def tradedays_before(date,count):#獲取指定交易日往前推count天交易日
date = get_price('000001.XSHG',end_date=date,count=count+1).index[0]
return date
#進行新股、St股過濾,返回篩選後的股票
def filter_stock(stockList,date,days=21*3):
#去除上市距beginDate不足3個月的股票
def delect_stop(stocks,beginDate,n=days):
stockList=[]
beginDate = datetime.datetime.strptime(beginDate, "%Y-%m-%d")
for stock in stocks:
start_date=get_security_info(stock).start_date
if start_date<(beginDate-datetime.timedelta(days=n)).date():
stockList.append(stock)
return stockList
#剔除ST股
st_data=get_extras('is_st',stockList, count = 1,end_date=date)
stockList = [stock for stock in stockList if not st_data[stock][0]]
#剔除停牌、新股及退市股票
stockList=delect_stop(stockList,date)
'''
#剔除開盤漲跌停
#如果需要收盤漲跌停可以改字段即可
df = get_price(stockList,end_date=date,fields=['open','high_limit','low_limit'],count=1).iloc[:,0,:]
df['h_limit']=(df['open']==df['high_limit'])
df['l_limit']=(df['open']==df['low_limit'])
stockList = [df.index[i] for i in range(len(df)) if not (df.h_limit[i] or df.l_limit[i])] #過濾漲跌停股票
'''
return stockList
第一部分 數據獲取部分
#第一部分:數據獲取,X,Y
#參數及工具函數部分
from jqfactor import *
import warnings
warnings.filterwarnings('ignore')
#因子數據獲取函數
#獲取時間為date的全部因子數據
def get_factor_data(stock,date):
data=pd.DataFrame(index=stock)
q = query(valuation,balance,cash_flow,income,indicator).filter(valuation.code.in_(stock))
df = get_fundamentals(q, date)
df['market_cap']=df['market_cap']*100000000
factor_data=get_factor_values(stock,['roe_ttm','roa_ttm','total_asset_turnover_rate',\
'net_operate_cash_flow_ttm','net_profit_ttm','net_profit_ratio',\
'cash_to_current_liability','current_ratio',\
'gross_income_ratio','non_recurring_gain_loss',\
'operating_revenue_ttm','net_profit_growth_rate'],end_date=date,count=1)
factor=pd.DataFrame(index=stock)
for i in factor_data.keys():
factor[i]=factor_data[i].iloc[0,:]
df.index = df['code']
data['code'] = df['code']
del df['code'],df['id']
#合並得大表
df=pd.concat([df,factor],axis=1)
#總市值取對數
data['size_lg']=np.log(df['market_cap'])
#淨利潤(TTM)/總市值
data['EP']=df['net_profit_ttm']/df['market_cap']
#淨資產/總市值
data['BP']=1/df['pb_ratio']
#營業收入(TTM)/總市值
data['SP']=1/df['ps_ratio']
#淨現金流(TTM)/總市值
data['NCFP']=1/df['pcf_ratio']
#經營性現金流(TTM)/總市值
data['OCFP']=df['net_operate_cash_flow_ttm']/df['market_cap']
#淨利潤同比增長率
data['net_g'] = df['net_profit_growth_rate']
#淨利潤(TTM)同比增長率/PE_TTM
data['G/PE']=df['net_profit_growth_rate']/df['pe_ratio']
#ROE_ttm
data['roe_ttm']=df['roe_ttm']
#ROE_YTD
data['roe_q']=df['roe']
#ROA_ttm
data['roa_ttm']=df['roa_ttm']
#ROA_YTD
data['roa_q']=df['roa']
#淨利率
data['netprofitratio_ttm'] = df['net_profit_ratio']
#毛利率TTM
data['grossprofitmargin_ttm']=df['gross_income_ratio']
#毛利率YTD
data['grossprofitmargin_q']=df['gross_profit_margin']
#扣除非經常性損益後淨利潤率YTD
data['profitmargin_q']=df['adjusted_profit']/df['operating_revenue']
#資產周轉率TTM
data['assetturnover_ttm']=df['total_asset_turnover_rate']
#總資產周轉率YTD 營業收入/總資產
data['assetturnover_q']=df['operating_revenue']/df['total_assets']
#經營性現金流/淨利潤TTM
data['operationcashflowratio_ttm']=df['net_operate_cash_flow_ttm']/df['net_profit_ttm']
#經營性現金流/淨利潤YTD
data['operationcashflowratio_q']=df['net_operate_cash_flow']/df['net_profit']
#淨資產
df['net_assets']=df['total_assets']-df['total_liability']
#總資產/淨資產
data['financial_leverage']=df['total_assets']/df['net_assets']
#非流動負債/淨資產
data['debtequityratio']=df['total_non_current_liability']/df['net_assets']
#現金比率=(貨幣資金+有價證券)÷流動負債
data['cashratio']=df['cash_to_current_liability']
#流動比率=流動資產/流動負債*100%
data['currentratio']=df['current_ratio']
#總市值取對數
data['ln_capital']=np.log(df['market_cap'])
#TTM所需時間
his_date = [pd.to_datetime(date) - datetime.timedelta(90*i) for i in range(0, 4)]
tmp = pd.DataFrame()
tmp['code']=list(stock)
for i in his_date:
tmp_adjusted_dividend = get_fundamentals(query(indicator.code, indicator.adjusted_profit, \
cash_flow.dividend_interest_payment).
filter(indicator.code.in_(stock)), date = i)
tmp=pd.merge(tmp,tmp_adjusted_dividend,how='outer',on='code')
tmp=tmp.rename(columns={'adjusted_profit':'adjusted_profit'+str(i.month), \
'dividend_interest_payment':'dividend_interest_payment'+str(i.month)})
tmp=tmp.set_index('code')
tmp_columns=tmp.columns.values.tolist()
tmp_adjusted=sum(tmp[[i for i in tmp_columns if 'adjusted_profit'in i ]],1)
tmp_dividend=sum(tmp[[i for i in tmp_columns if 'dividend_interest_payment'in i ]],1)
#扣除非經常性損益後淨利潤(TTM)/總市值
data['EPcut']=tmp_adjusted/df['market_cap']
#近12個月現金紅利(按除息日計)/總市值
data['DP']=tmp_dividend/df['market_cap']
#扣除非經常性損益後淨利潤率TTM
data['profitmargin_ttm']=tmp_adjusted/df['operating_revenue_ttm']
#營業收入(YTD)同比增長率
#_x現在 _y前一年
his_date = pd.to_datetime(date) - datetime.timedelta(365)
name=['operating_revenue','net_profit','net_operate_cash_flow','roe']
temp_data=df[name]
his_temp_data = get_fundamentals(query(valuation.code, income.operating_revenue,income.net_profit,\
cash_flow.net_operate_cash_flow,indicator.roe).
filter(valuation.code.in_(stock)), date = his_date)
his_temp_data=his_temp_data.set_index('code')
#重命名 his_temp_data last_year
for i in name:
his_temp_data=his_temp_data.rename(columns={i:i+'last_year'})
temp_data =pd.concat([temp_data,his_temp_data],axis=1)
#營業收入(YTD)同比增長率
data['sales_g_q']=temp_data['operating_revenue']/temp_data['operating_revenuelast_year']-1
#淨利潤(YTD)同比增長率
data['profit_g_q']=temp_data['net_profit']/temp_data['net_profitlast_year']-1
#經營性現金流(YTD)同比增長率
data['ocf_g_q']=temp_data['net_operate_cash_flow']/temp_data['net_operate_cash_flowlast_year']-1
#ROE(YTD)同比增長率
data['roe_g_q']=temp_data['roe']/temp_data['roelast_year']-1
#計算beta部分
#輔助線性回歸的函數
def linreg(X,Y,columns=3):
X=sm.add_constant(array(X))
Y=array(Y)
if len(Y)>1:
results = regression.linear_model.OLS(Y, X).fit()
return results.params
else:
return [float("nan")]*(columns+1)
#個股60個月收益與上證綜指回歸的截距項與BETA
stock_close=get_price(list(stock), count = 12*20+1, end_date=date, frequency='daily', fields=['close'])['close']
SZ_close=get_price('000001.XSHG', count = 12*20+1, end_date=date, frequency='daily', fields=['close'])['close']
stock_pchg=stock_close.pct_change().iloc[1:]
SZ_pchg=SZ_close.pct_change().iloc[1:]
beta=[]
stockalpha=[]
for i in stock:
temp_beta, temp_stockalpha = stats.linregress(SZ_pchg, stock_pchg[i])[:2]
beta.append(temp_beta)
stockalpha.append(temp_stockalpha)
#此處alpha beta為list
#data['alpha']=stockalpha
data['beta']=beta
#反轉
data['reverse_1m']=stock_close.iloc[-21]/stock_close.iloc[-1]-1
data['reverse_3m']=stock_close.iloc[-63]/stock_close.iloc[-1]-1
#波動率(一個月、三個月標準差)
data['std_1m']=stock_close[-20:].std()
data['std_3m']=stock_close[-60:].std()
#換手率
#tradedays_1m = get_tradeday_list(start=date,end=date,frequency='day',count=21)#最近一個月交易日
tradedays_3m = get_tradeday_list(start=date,end=date,frequency='day',count=63)#最近一個月交易日
data_turnover_ratio=pd.DataFrame()
data_turnover_ratio['code']=list(stock)
for i in tradedays_3m:
q = query(valuation.code,valuation.turnover_ratio).filter(valuation.code.in_(stock))
temp = get_fundamentals(q, i)
data_turnover_ratio=pd.merge(data_turnover_ratio, temp,how='left',on='code')
data_turnover_ratio=data_turnover_ratio.rename(columns={'turnover_ratio':i})
data['turn_3m']= (data_turnover_ratio.set_index('code').T).mean()
data['turn_1m']= (data_turnover_ratio.set_index('code').T)[-21:].mean()
'''
#技術指標部分
date_1 = tradedays_before(date,1)
data['PSY']=pd.Series(PSY(stock, date_1, timeperiod=20))
data['RSI']=pd.Series(RSI(stock, date_1, N1=20))
data['BIAS']=pd.Series(BIAS(stock,date_1, N1=20)[0])
dif,dea,macd=MACD(stock, date_1, SHORT = 10, LONG = 30, MID = 15)
#data['DIF']=pd.Series(dif)
#data['DEA']=pd.Series(dea)
data['MACD']=pd.Series(macd)
'''
return data
#多期獲取數據
#輸入:指數名稱,統計時間列表
#輸出:字典,key為時間,vlaues為因子值數據
factor_data_dict = {}
#設置統計數據區間
index = '000905.XSHG' #中證500
date_start = '2009-1-1'
date_end = '2019-1-1'
#獲取交易日列表,每月首個交易日
date_list = get_tradeday_list(start=date_start,end=date_end,frequency='month',count=None)
#循環獲取因子數據,並將值存在字典里面(耗時較長)
#並計算個期股票收益情況,加入指數漲跌幅
import time
t1 = time.time()
for date_1,date_2 in zip(date_list[:-1],date_list[1:]):
pool = get_index_stocks(index,date = date_1)
pool = filter_stock(pool,str(date_1)[:10],days=21*3) #進行新股、ST股票過濾
#計算指數漲跌幅
df_1 = get_price(index,end_date=date_1,fields=['close'],count = 1)['close']
df_2 = get_price(index,end_date=date_2,fields=['close'],count = 1)['close']
index_pct = df_2.values[0]/df_1.values[0] - 1#具體數值
#計算各股票漲跌幅
df_1 = get_price(pool,end_date=date_1,fields=['close'],count = 1)['close']
df_2 = get_price(pool,end_date=date_2,fields=['close'],count = 1)['close']
df_3 = pd.concat([df_1,df_2],axis=0).T #進行合並
stock_pct = df_3.iloc[:,1]/df_3.iloc[:,0] - 1 #計算pct,series
#記錄因子值
factor_df = get_factor_data(pool,date=date_1) #進行因子值獲取
factor_df['init_pct'] = stock_pct
factor_df['pct'] = stock_pct-index_pct
del factor_df['code']
#對數據進行處理、標準化、去極值、中性化
factor_df = winsorize_med(factor_df, scale=3, inclusive=True, inf2nan=True, axis=0) #中位數去極值處理
factor_df = standardlize(factor_df, inf2nan=True, axis=0) #對每列做標準化處理
factor_df = neutralize(factor_df, how=['sw_l1', 'market_cap'], date=date_1, axis=0,fillna='sw_l1')#中性化
factor_df['pct_init'] = stock_pct-index_pct
factor_df['pct_'] = stock_pct
factor_data_dict[date_1] = factor_df
t2 = time.time()
print('計算因子耗時:{0}'.format(t2-t1))
factor_data_dict[date_1].head(5)
下面兩段代碼將因子值存儲
主要是方便下次使用的時候不用計算
#將計算出來的因子值進行存儲
#使用pickle模塊將數據對象保存到文件
import pickle
pkl_file = open('factor_data_10year.pkl', 'wb')
pickle.dump(factor_data_dict, pkl_file, 0)
pkl_file.close()
#讀取計算出來的因子值
import pickle
pkl_file = open('factor_data_10year.pkl', 'rb')
factor_data_dict = pickle.load(pkl_file)
pkl_file.close()
#數據合並,用於模型訓練
#將字典組成一個無日期的大表
all_data = pd.DataFrame()
for d in date_list[:60]:
all_data = pd.concat([all_data,factor_data_dict[d]],axis=0) #進行縱向拚接
print(d)
print(all_data.shape)
all_data.head(5)
空值檢查
for colu in all_data.columns:
if sum(all_data[colu].isnull())> 0.2*len(all_data[colu]):
print('因子:%s缺失值較多,不做為統計特征處理'%colu)
del all_data[colu]
all_data.shape
#進行空值處理
for colu in all_data.columns:
if sum(all_data[colu].isnull())> 0:#0.05*len(all_data[colu]):
all_data[colu].fillna(all_data[colu].mean(),inplace=True)
#del all_data[colu]
all_data.shape
第二部分 模型搭建 進行因子值預測
- 滾動獲取因子數據與y
- 滾動擬合模型
- 預測y值
- 保持預測值格式,對接回測
- 記錄特征重要性,樣本內樣本外得分
#第二部分:決策樹模型
from sklearn import tree
from sklearn.model_selection import train_test_split #對數據進行隨機劃分
進行分類方法處理
#進行因子值預測(分類方式)
df_y = pd.DataFrame()
df_score = pd.DataFrame(index = ['score_p','score_r'])
colus = factor_data_dict[date_list[60]].columns[:-4]
df_features_c = pd.DataFrame(index=colus)
for i in range(60,len(date_list)-1):
print('正在計算{0}...數據'.format(date_list[i]))
#數據合並,用於模型訓練
#將字典組成一個無日期的大表
all_data = pd.DataFrame()
for d in date_list[i-60:i]:
all_data = pd.concat([all_data,factor_data_dict[d]],axis=0) #進行縱向拚接
#空值NAN值處理
for colu in all_data.columns:
if sum(all_data[colu].isnull())> 0.2*len(all_data[colu]):
print('因子:%s缺失值較多,不做為統計特征處理'%colu)
del all_data[colu]
#進行空值處理
for colu in all_data.columns:
if sum(all_data[colu].isnull())> 0:#0.05*len(all_data[colu]):
all_data[colu].fillna(all_data[colu].mean(),inplace=True)
#del all_data[colu]
#獲取前三分之一和後三分之一的數據
all_data = all_data.sort_values('pct_init')
#print(all_data.shape)
all_data['num'] = list(range(len(all_data)))
all_data = all_data[(all_data['num']<=len(all_data)/3) | (all_data['num']>=2*len(all_data)/3)]
#print(all_data.shape)
#記錄標簽y
y1 = (all_data["pct_init"]>0).astype("int") #走完流程在改?
y = all_data['pct_init']
x = all_data.iloc[:,:-5]
X_train, X_test, y_train, y_test = train_test_split(x,y1.values, test_size=0.3)
#建立最大深度為2的決策樹,並用測試數據來訓練這顆樹
clf = tree.DecisionTreeClassifier(max_depth = 6
)
clf = clf.fit(X_train, y_train) #模型訓練
s_p = clf.score(X_test,y_test)#樣本內得分
#print(clf.score(X_test,y_test)) #模型得分
#模型預測
factor_df_temp = factor_data_dict[date_list[i]].iloc[:,:-4]
factor_df_temp1 = factor_df_temp.dropna(axis=0)
df_temp = pd.DataFrame(index=factor_df_temp1.index)
df_temp[date_list[i]] = [i[1] for i in clf.predict_proba(factor_df_temp1)]
#記錄模型真實數據得分
y_t = factor_data_dict[date_list[i]].dropna()
x_s = y_t.iloc[:,:-4]
y_s = (y_t["pct_init"]>0).astype("int")
s_r = clf.score(x_s,y_s)
df_score[date_list[i]] = [s_p,s_r]
#記錄特征重要性
df_features_c[date_list[i]] = clf.feature_importances_
df_y = pd.concat([df_y,df_temp],axis=1) #進行橫向拚接
df_y
研究搭建回測
輸入:factor_df數據
- index為日期列表
- columns為股票名稱
- values為因子值
輸出:
- 做多頭部超額收益
- 分組收益
factor_df = df_y.T
'''
選取頭部股票計算超額收益
輸入:factor_df,index為日期,column是股票名字,values是進行排序的因子值
'''
#選取頭部股票構造組合進行回測
return_df = pd.DataFrame()
for d1,d2 in zip(factor_df.index[:-1],factor_df.index[1:]):
#獲取頭部股票
df_temp = factor_df.loc[d1,:].sort_values() #mo默認從小到大排序
#pool_temp = df_temp.index[:50]
pool_temp = df_temp.index[-50:]
#計算組合收益
df1 = get_price(list(pool_temp),end_date=d1,count=1,fields=['close'])['close'] #index為日期,columns為股票名稱
df1 = df1.dropna(axis=1) #去掉NAN值,刪除列
df2 = get_price(list(df1.columns),end_date=d2,count=1,fields=['close'])['close']
ret = (df2.values/df1.values - 1).mean() #計算組合收益均值
#計算同期指數收益率
df_index1 = get_price('000905.XSHG',end_date=d1,count=1,fields=['close'])['close']
df_index2 = get_price('000905.XSHG',end_date=d2,count=1,fields=['close'])['close']
index_ret = df_index2.values[-1]/df_index1.values[-1]-1
return_df[d1] = [ret-index_ret] #記錄超額收益
#print(ret)
return_df = return_df.T
return_df.columns = ['return']
print(return_df.head(5))
(return_df+1).cumprod().plot(figsize=(12,6))
'''
分組回測部分
'''
#分組回測部分
#輸入:index為日期,column是股票名,values是因子值得factor_df
#輸出:股票池分組收益
group = 10 #分組組數
pool_dict = {}
for i in range(len(factor_df.index)):
temp_se = factor_df.iloc[0,:].sort_values(ascending=False)#從大到小排序
#pool = temp_se[temp_se>0].index #去掉小於0的值
temp_se = temp_se.dropna() #去掉空值
pool = temp_se.index #不做負值處理
num = int(len(pool)/group)
#print('第%s期每組%s只股票'%(i,num))
pool_dict[factor_df.index[i]] = pool
trade_list = factor_df.index
group_pct = get_all_pct(pool_dict,trade_list,groups=group)
group_pct.columns = ['group'+str(i) for i in range(len(group_pct.columns))]
group_pct.cumprod().plot(figsize=(12,6))
#進行模型樣本內樣本外得分記錄
print(df_score.mean(axis=1))
(df_score.T).plot(figsize=(12,6))
#回歸方式
#進行因子值預測(回歸方式)
df_y_r = pd.DataFrame()
df_score_r = pd.DataFrame(index = ['score_p','score_r'])
colus = factor_data_dict[date_list[60]].columns[:-4]
df_features = pd.DataFrame(index=colus)
for i in range(60,len(date_list)-1):
print('正在計算{0}...數據'.format(date_list[i]))
#數據合並,用於模型訓練
#將字典組成一個無日期的大表
all_data = pd.DataFrame()
for d in date_list[i-60:i]:
all_data = pd.concat([all_data,factor_data_dict[d]],axis=0) #進行縱向拚接
#空值NAN值處理
for colu in all_data.columns:
if sum(all_data[colu].isnull())> 0.2*len(all_data[colu]):
print('因子:%s缺失值較多,不做為統計特征處理'%colu)
del all_data[colu]
#進行空值處理
for colu in all_data.columns:
if sum(all_data[colu].isnull())> 0:#0.05*len(all_data[colu]):
all_data[colu].fillna(all_data[colu].mean(),inplace=True)
#del all_data[colu]
#獲取前三分之一和後三分之一的數據
all_data = all_data.sort_values('pct_init')
#print(all_data.shape)
all_data['num'] = list(range(len(all_data)))
all_data = all_data[(all_data['num']<=len(all_data)/3) | (all_data['num']>=2*len(all_data)/3)]
#print(all_data.shape)
#記錄標簽y
y1 = (all_data["pct_init"]>0).astype("int") #走完流程在改?
y = all_data['pct_init']
x = all_data.iloc[:,:-5]
#擬合模型
#對數據進行分割,訓練集、測試集
X_train, X_test, y_train, y_test = train_test_split(x.values,y.values, test_size=0.3)
#建立最大深度為2的決策樹,並用測試數據來訓練這顆樹
rlf = tree.DecisionTreeRegressor(max_depth = 6
)
rlf = rlf.fit(X_train, y_train) #模型訓練
#print(rlf.score(X_test,y_test)) #模型得分
s_p = rlf.score(X_test,y_test)#樣本內得分
#模型預測y
factor_df_temp = factor_data_dict[date_list[i]].iloc[:,:-4]
factor_df_temp1 = factor_df_temp.dropna(axis=0)
df_temp = pd.DataFrame(index=factor_df_temp1.index)
df_temp[date_list[i]] = rlf.predict(factor_df_temp1)
#記錄模型真實數據得分
y_t = factor_data_dict[date_list[i]].dropna()
x_s = y_t.iloc[:,:-4]
y_s = y_t["pct_init"]
s_r = rlf.score(x_s,y_s)
df_score_r[date_list[i]] = [s_p,s_r]
#記錄特征重要性
df_features[date_list[i]] = rlf.feature_importances_
#記錄預測值進行拚接
df_y_r = pd.concat([df_y_r,df_temp],axis=1) #進行橫向拚接
df_y_r
factor_df = df_y_r.T
'''
選取頭部股票計算超額收益
輸入:factor_df,index為日期,column是股票名字,values是進行排序的因子值
'''
#選取頭部股票構造組合進行回測
return_df = pd.DataFrame()
for d1,d2 in zip(factor_df.index[:-1],factor_df.index[1:]):
#獲取頭部股票
df_temp = factor_df.loc[d1,:].sort_values() #mo默認從小到大排序
#pool_temp = df_temp.index[:50]
pool_temp = df_temp.index[-50:]
#計算組合收益
df1 = get_price(list(pool_temp),end_date=d1,count=1,fields=['close'])['close'] #index為日期,columns為股票名稱
df1 = df1.dropna(axis=1) #去掉NAN值,刪除列
df2 = get_price(list(df1.columns),end_date=d2,count=1,fields=['close'])['close']
ret = (df2.values/df1.values - 1).mean() #計算組合收益均值
#計算同期指數收益率
df_index1 = get_price('000905.XSHG',end_date=d1,count=1,fields=['close'])['close']
df_index2 = get_price('000905.XSHG',end_date=d2,count=1,fields=['close'])['close']
index_ret = df_index2.values[-1]/df_index1.values[-1]-1
return_df[d1] = [ret-index_ret] #記錄超額收益
#print(ret)
return_df = return_df.T
return_df.columns = ['return']
print(return_df.head(5))
(return_df+1).cumprod().plot(figsize=(12,6))
'''
分組回測部分
'''
#分組回測部分
#輸入:index為日期,column是股票名,values是因子值得factor_df
#輸出:股票池分組收益
group = 10 #分組組數
pool_dict = {}
for i in range(len(factor_df.index)):
temp_se = factor_df.iloc[0,:].sort_values(ascending=False)#從大到小排序
#pool = temp_se[temp_se>0].index #去掉小於0的值
temp_se = temp_se.dropna() #去掉空值
pool = temp_se.index #不做負值處理
num = int(len(pool)/group)
#print('第%s期每組%s只股票'%(i,num))
pool_dict[factor_df.index[i]] = pool
trade_list = factor_df.index
group_pct = get_all_pct(pool_dict,trade_list,groups=group)
group_pct.columns = ['group'+str(i) for i in range(len(group_pct.columns))]
group_pct.cumprod().plot(figsize=(12,6))
#進行模型樣本內樣本外得分記錄(回歸)
print(df_score_r.mean(axis=1))
(df_score_r.T).plot(figsize=(12,6))
第四部分 特征統計(因子重要性)
特征重要性進行說明
模型調用featureimportances即可獲取
下面我們針對這個內容進行圖形化展示
df_features.mean(axis=1).plot(kind='bar',figsize=(12,6))