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

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

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

在求出具體的因子值後,需要先對因子進行有效性檢驗,篩選符合顯著性、穩定性、單調性、收益率要求的因子;因子有效性檢定透過分析本期因子值與預期收益率的關係,從而確定因子的有效性。主要有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ₜ₊₁) =∑ᵗₜ₂₁ (Lₜ-₊₁) =∑ ₜ₌₁( 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: 天, 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%。
  • 檢定假設:H0:β0=0, H1:βₜ≠0

T統計=(βʌₜ−βₜ)/se(βʌₜ)~ tα/₂(n−k)

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’)

Total
0
Shares
Related Posts