用多因子策略構建強大的加密資產投資組合 #因子有效性檢驗篇#

書接上回,關於《用多因子模型構建強大的加密資產投資組合》系列文章中,我們已經發布了兩篇: 《理論基礎篇》《數據預處理篇》

本篇是第三篇:因子有效性檢驗。

在求出具體的因子值後,需要先對因子進行有效性檢驗,篩選符合顯著性、穩定性、單調性、收益率要求的因子;因子有效性檢驗通過分析本期因子值與預期收益率的關係,從而確定因子的有效性。主要有3種經典方法:

  • IC / IR法:IC / IR值為因子值與預期收益率的相關係數,越大因子表現越好。

  • T值(迴歸法):T值體現下期收益率對本期因子值線性迴歸後係數的顯著性,通過比較該回歸係數是否通過t檢驗,來判斷本期因子值對下期收益率的貢獻程度,通常用於多元(即多因子)迴歸模型。

  • 分層回測法:分層回測法基於因子值對token分層,再計算每層token的收益率,從而判斷因子的單調性

一、IC / IR法

(1)IC / IR的定義

IC :即信息係數Information Coefficient,代表因子預測Tokens收益的能力。某一期IC值為本期因子值和下期收益率的相關係數。

$$$ ICₜ = Correlation(fₜ , Rₜ₊₁) $$$

fₜ: 第t期因子值

Rₜ₊₁: 第t+1期token的收益率

IC∈(-1,1),IC越大的因子,選幣能力就越強。

IC 越接近1,說明因子值和下期收益率的正相關性越強,IC=1,表示該因子選幣100%準確,對應的是排名分最高的token,選出來的token在下個調倉週期中,漲幅最大;

IC 越接近-1,說明因子值和下期收益率的負相關性越強,如果IC=-1,則代表排名分最高的token,在下個調倉週期中,跌幅最大,是一個完全反指的指標;

若IC 越接近0,則說明該因子的預測能力極其弱,表明該因子對於token沒有任何的預測能力。

IR:信息比率information ratio,代表因子獲取穩定Alpha的能力。 IR 為所有期IC 均值除以所有期IC 標準差。

$$$ IR = mean(ICₜ) / std(ICₜ) $$$

當IC的絕對值大於0.05(0.02) 時,因子的選股能力較強。當IR大於0.5時,因子穩定獲取超額收益能力較強。

(2)IC的計算方式

  • Normal IC (Pearson correlation):計算皮爾森相關係數,最經典的一種相關係數。但該計算方式存在較多假設前提:數據連續,正態分佈,兩個變量滿足線性關係等等。

$$$ ICₚₑₐᵣₛₒₙ,ₜ = cov(fₜ , Rₜ₊₁)/√var(fₜ)var(Rₜ₊₁) = ∑ᵗₜ₌₁(fₜ - fₜ)(Rₜ₊₁ - Rₜ₊₁)/√∑ᵗₜ ₌₁(fₜ - fₜ)²(Rₜ₊₁ - Rₜ₊₁)² $$$

  • Rank IC (Spearman's rank coefficient of correlation):計算斯皮爾曼秩相關係數,先對兩個變量排序,再根據排序後的結果求皮爾森相關係數。斯皮爾曼秩相關係數評估的是兩個變量之間的單調關係,並且由於轉換為排序值,受數據異常值影響較小;而皮爾森相關係數評估的是兩個變量之間的線性關係,不僅對原始數據有一定的前提條件,並且受數據異常值影響較大。在現實計算中,求rank IC更符合。

(3)IC / IR法代碼實現

創建一個按日期時間升序排列的唯一日期時間值的列表--記錄調倉日期def choosedate(dateList,cycle)

 class TestAlpha(object): def __init__(self,ini_data): self.ini_data = ini_data def chooseDate(self,cycle,start_date,end_date): ''' cycle: day, month, quarter, year df: 原始數據框df,date列的處理''' chooseDate = [] dateList = sorted(self.ini_data[self.ini_data['date'].between(start_date,end_date)]['date'].drop_duplicates().values) dateList = pd.to_datetime(dateList) for i in range(len(dateList)-1): if getattr(dateList[i], cycle) != getattr(dateList[i + 1], cycle): chooseDate.append(dateList[i]) chooseDate.append(dateList[-1]) chooseDate = [date.strftime('%Y-%m-%d') for date in chooseDate] return chooseDate def ICIR(self,chooseDate,factor): # 1.先展示每個調倉日期的IC,即ICt testIC = pd.DataFrame(index=chooseDate,columns=['normalIC','rankIC']) dfFactor = self.ini_data[self.ini_data['date'].isin(chooseDate)][['date','name','price',factor]] for i in range(len(chooseDate)-1): # (1) normalIC X = dfFactor[dfFactor['date'] == chooseDate[i]][['date','name','price',factor]].rename(columns={'price':'close0'}) Y = pd.merge(X,dfFactor[dfFactor['date'] == chooseDate[i+1]][['date','name','price']], on=['name']).rename(columns={'price':'close1'}) Y['returnM'] = (Y['close1'] - Y['close0']) / Y['close0'] Yt = np.array(Y['returnM']) Xt = np.array(Y[factor]) Y_mean = Y['returnM'].mean() X_mean = Y[factor].mean() num = np.sum((Xt-X_mean)*(Yt-Y_mean)) den = np.sqrt(np.sum((Xt-X_mean)**2)*np.sum((Yt-Y_mean)**2)) normalIC = num / den # pearson correlation # (2) rankIC Yr = Y['returnM'].rank() Xr = Y[factor].rank() rankIC = Yr.corr(Xr) testIC.iloc[i] = normalIC, rankIC testIC =testIC[:-1] # 2.基於ICt,求['IC_Mean', 'IC_Std','IR','IC<0佔比--因子方向','|IC|>0.05比例'] ''' ICmean: |IC|>0.05, 因子的選幣能力較強,因子值與下期收益率相關性高。|IC|<0.05,因子的選幣能力較弱,因子值與下期收益率相關性低IR: |IR|>0.5,因子選幣能力較強,IC值較穩定。|IR|<0.5,IR值偏小,因子不太有效。若接近0,基本無效IClZero(IC less than Zero): IC<0佔比接近一半->因子中性.IC>0超過一大半,為負向因子,即因子值增加,收益率降低ICALzpF(IC abs large than zero poin five): |IC|>0.05比例偏高,說明因子大部分有效''' IR = testIC.mean()/testIC.std() IClZero = testIC[testIC<0].count()/testIC.count() ICALzpF = testIC[abs(testIC)>0.05].count()/testIC.count() combined =pd.concat([testIC.mean(),testIC.std(),IR,IClZero,ICALzpF],axis=1) combined.columns = ['ICmean','ICstd','IR','IClZero','ICALzpF'] # 3.IC 調倉期內IC的累積圖print("Test IC Table:") print(testIC) print("Result:") print('normal Skewness:',combined['normalIC'].skew(),'rank Skewness:',combined['rankIC'].skew()) print('normal Skewness:',combined['normalIC'].kurt(),'rank Skewness:',combined['rankIC'].kurt()) return combined,testIC.cumsum().plot()

二、T值檢驗(迴歸法)

T值法同樣檢驗本期因子值和下期收益率關係,但與ICIR法分析二者的相關性不同,t值法將下期收益率作為因變量Y,本期因子值作為自變量X,由Y對X迴歸,對迴歸出因子值的迴歸係數進行t檢驗,檢驗其是否顯著異於0,即本期因子是否影響下期收益率。

該方法本質是對雙變量回歸模型的求解,具體公式如下:

$$$ Rₜ₊₁ = αₜ + βₜfₜ + μₜ $$$

Rₜ₊₁: 第t+1期token收益率

fₜ:第t期因子值

βₜ:第t期因子值的迴歸係數,即因子收益率

αₜ:截距項,反映所有未包含到模型中的變量對Rₜ₊₁ 的平均影響

(1)迴歸法理論

  • 設定顯著性水平α,通常為10%、5%、1%。

  • 檢驗假設:$$H₀ : βₜ =0$$, $$H₁ : βₜ ≠ 0$$

$$$ T統計量= (βʌₜ - βₜ) / se(βʌₜ) ~ tα/₂(nk) $$$

k:迴歸模型中的參數個數

  • 如果|t統計量| > |tα/₂(nk)| → 拒絕H₀,即本期因子值fₜ對下期收益率Rₜ₊₁有顯著的影響。

(2)迴歸法代碼實現

def regT(self,chooseDate,factor,return_24h): testT = pd.DataFrame(index=chooseDate,columns=['coef','T']) for i in range(len(chooseDate)-1): X = self.ini_data[self.ini_data['date'] == chooseDate[i]][factor].values Y = self.ini_data[self.ini_data['date'] == chooseDate[i+1]][return_24h].values b, intc = np.polyfit(X, Y, 1) # 斜率ut = Y - (b * X + intc) # 求t值t = (\hat{b} - b) / se(b) n = len(X) dof = n - 2 # 自由度std_b = np.sqrt(np.sum(ut**2) / dof) t_stat = b / std_b testT.iloc[i] = b, t_stat testT = testT[:-1] testT_mean = testT['T'].abs().mean() testTL196 = len(testT[testT['T'].abs() > 1.96]) / len(testT) print('testT_mean:',testT_mean) print('T值大於1.96的佔比:',testTL196) return testT

三、分層回測法

分層指對所有token分層,回測指計算每層token組合的收益率。

(1)分層

首先獲取token池對應的因子值,通過因子值對token進行排序。升序排序,即因子值較小的排在前面,根據排序對token進行等分。第0層token的因子值最小,第9層token的的因子值最大。

理論上“等分”是指均等分拆token的個數,即每層token個數相同,藉助分位數實現。現實中token總數不一定是層數的倍數,即每層token個數不一定相等。

(2)回測

將token按因子值升序分完10組後,開始計算每組token組合的收益率。該步驟將每層的token當成一個投資組合(不同回測期,每層的token組合所含的token都會有變化),並計算該組合整體的下期收益率。 ICIR、t值分析的是當期因子值和下期整體的收益率,但分層回測需要計算回測時間內每個交易日的分層組合收益率。由於有很多回測期有很多期,在每一期都需要進行分層和回測。最後對每一層的token收益率進行累乘,計算出token組合的累積收益率。

理想狀態下,一個好的因子,第9組的曲線收益最高,第0組的曲線收益最低。

第9組減去第0組(即多空收益)曲線呈現單調遞增。

(3)分層回測法代碼實現

def layBackTest(self,chooseDate,factor): f = {} returnM = {} for i in range(len(chooseDate)-1): df1 = self.ini_data[self.ini_data['date'] == chooseDate[i]].rename(columns= {'price':'close0'}) Y = pd.merge(df1,self.ini_data[self.ini_data['date'] == chooseDate[i+1]] [['date','name','price']],left_on=['name'],right_on=['name']).rename(columns= {'price':'close1'}) f[i] = Y[factor] returnM[i] = Y['close1'] / Y['close0'] -1 labels = ['0','1','2','3','4','5','6','7','8','9'] res = pd.DataFrame(index=['0','1','2','3','4','5','6','7','8','9','LongShort']) res[chooseDate[0]] = 1 for i in range(len(chooseDate)-1): dfM = pd.DataFrame({'factor':f[i],'returnM':returnM[i]}) dfM['group'] = pd.qcut(dfM['factor'], 10, labels=labels) dfGM = dfM.groupby('group').mean()[['returnM']] dfGM.loc['LongShort'] = dfGM.loc['0']- dfGM.loc['9'] res[chooseDate[i+1]] = res[chooseDate[0]] * (1 + dfGM['returnM']) data = pd.DataFrame({'分層累積收益率':res.iloc[:10,-1],'Group': [0,1,2,3,4,5,6,7,8,9]}) df3 = data.corr() print("Correlation Matrix:") print(df3) return res.T.plot(title='Group backtest net worth curve')

關於LUCIDA & FALCON

Lucida ( https://www.lucida.fund/ )是行業領先的量化對沖基金,在2018年4月進入Crypto市場,主要交易CTA / 統計套利/ 期權波動率套利等策略,現管理規模3000萬美元。

Falcon ( https://falcon.lucida.fund /)是新一代的Web3投資基礎設施,它基於多因子模型,幫助用戶“選”、“買”、“管”、“賣”加密資產。 Falcon在2022年6月由Lucida所孵化。

更多內容可訪問https://linktr.ee/lucida_and_falcon

往期文章

Mirror
免責聲明:以上內容僅為作者觀點,不代表Followin的任何立場,不構成與Followin相關的任何投資建議。
喜歡
收藏
評論