python線性迴歸 多因子模型選股思路

PB-ROE提供了一種投資的框架,這種框架是說,股票的PB和ROE之間存在近似的線性關係,ROE越高,PB越高,因此如果同時根據PB、ROE值來投資,很難選到同時滿足PB最小、ROE最大的股票。但可以根據他們的線性關係進行選擇,迴歸直線上的點可以視爲合理的PB、ROE組合水平,這樣位於迴歸線下方的股票都是PB被低估的,未來有很大的上升修復空間,而位於迴歸線上方的股票都是當前PB被高估的,未來會下降,因此投資可以選擇位於迴歸線下方的股票。

使用這種方法最重要的點是迴歸必須是靠譜的,比如ROE應該是穩定的,確保未來可持續,比如應想辦法消除行業間的差異等等。

import numpy as np
from atrader import *
import pandas as pd
set_setting('ALLOW_CONSOLE_SYSTEM_WARN', False)
import statsmodels.api as sm

#獲取滬深300的成分股代碼
get_code_list('hs300')

#獲取PB,ROE數據
import atrader as at
dfData=get_factor_by_day(factor_list=['PB','ROE'], target_list=list(get_code_list('hs300').code), date='2019-08-30').set_index('code')
dfData

# MAD:中位數去極值
def extreme_MAD(dt,n):
    median = dt.quantile(0.5)   # 找出中位數
    new_median = (abs((dt - median)).quantile(0.5))   # 偏差值的中位數
    dt_up = median + n*new_median    # 上限
    dt_down = median - n*new_median  # 下限
    return dt.clip(dt_down, dt_up, axis=1)    # 超出上下限的值,賦值爲上下限

# Z值標準化
def standardize_z(dt):
    mean = dt.mean()     #  截面數據均值
    std = dt.std()       #  截面數據標準差
    return (dt - mean)/std

# 行業中性化
shenwan_industry = {
'SWNLMY1':'sse.801010',
'SWCJ1':'sse.801020',
'SWHG1':'sse.801030',
'SWGT1':'sse.801040',
'SWYSJS1':'sse.801050',
'SWDZ1':'sse.801080',
'SWJYDQ1':'sse.801110',
'SWSPCL1':'sse.801120',
'SWFZFZ1':'sse.801130',
'SWQGZZ1':'sse.801140',
'SWYYSW1':'sse.801150',
'SWGYSY1':'sse.801160',
'SWJTYS1':'sse.801170',
'SWFDC1':'sse.801180',
'SWSYMY1':'sse.801200',
'SWXXFW1':'sse.801210',
'SWZH1':'sse.801230',
'SWJZCL1':'sse.801710',
'SWJZZS1':'sse.801720',
'SWDQSB1':'sse.801730',
'SWGFJG1':'sse.801740',
'SWJSJ1':'sse.801750',
'SWCM1':'sse.801760',
'SWTX1':'sse.801770',
'SWYH1':'sse.801780',
'SWFYJR1':'sse.801790',
'SWQC1':'sse.801880',
'SWJXSB1':'sse.801890'
}

# 構造行業啞變量矩陣
def industry_exposure(target_idx):
    # 構建DataFrame,存儲行業啞變量
    df = pd.DataFrame(index = [x.lower() for x in target_idx],columns = shenwan_industry.keys())
    for m in df.columns:        # 遍歷每個行業
        # 行標籤集合和某個行業成分股集合的交集
        temp = list(set(df.index).intersection(set(get_code_list(m).code.tolist())))
        df.loc[temp, m] = 1      # 將交集的股票在這個行業中賦值爲1
        df = df.fillna(0)
    return df        # 將 NaN 賦值爲0


# 需要傳入單個因子值和總市值
def neutralization(factor,MktValue,industry = True):
    Y = factor.fillna(0)
    Y.rename(index = str.lower,inplace = True)
    df = pd.DataFrame(index = Y.index, columns = Y.columns)    # 構建輸出矩陣
    for i in range(Y.shape[1]):    # 遍歷每一天的截面數據
        if type(MktValue) == pd.DataFrame:
            lnMktValue = MktValue.iloc[:,i].apply(lambda x:math.log(x))   # 市值對數化
            lnMktValue.rename(index=str.lower, inplace=True)
            if industry:              # 行業、市值
                dummy_industry = industry_exposure(Y.index.tolist())
                X = pd.concat([lnMktValue,dummy_industry],axis = 1,sort = False)  # 市值與行業合併
            else:                     # 僅市值
                X = lnMktValue
        elif industry:              # 僅行業
            dummy_industry = industry_exposure(factor.index.tolist())
            X = dummy_industry
      # X = sm.add_constant(X)
        result = sm.OLS(Y.iloc[:,i].astype(float),X.astype(float)).fit()   # 線性迴歸
        df.iloc[:,i] = result.resid  # 每日的截面數據存儲到df中
    return df
dfData = dfData.loc[dfData['PB']>0]
dfData = dfData.loc[dfData['PB']<10]
dfData = dfData.loc[dfData['ROE']>0]
dfData = dfData.loc[dfData['ROE']<10]

# 去極值和標準化
data_S = standardize_z(dfData)
# 行業中性化
data_S_ID_PB = neutralization(data_S[['PB']],0)
data_S_ID_ROE = neutralization(data_S[['ROE']],0)

#迴歸數據準備
x = data_S_ID_ROE[['ROE']]
x['Intercept'] = 1
x_ = x[['Intercept','ROE']]
y = data_S_ID_PB['PB']

#迴歸
model = sm.OLS(y.astype(float),x_.astype(float))
result=model.fit()

#迴歸係數
result.params

#answer:
#Intercept    0.04428
#ROE          0.42821


#擬合曲線
y_fitted = data_S_ID_ROE*result.params[1]+result.params[0]

df = pd.DataFrame()
df[['PB']] = data_S_ID_PB
df['ROE'] = data_S_ID_ROE
df['y_fitted'] = y_fitted

import matplotlib.pyplot as plt
plt.figure(figsize = (8,6))
plt.plot(df['ROE'], df['PB'],'ko', label='sample')
plt.plot(df['ROE'], df['y_fitted'], 'black',label='OLS',linewidth = 2)
plt.xlabel('ROE')
plt.ylabel('PB')
plt.show()

df_choice = df.loc[df['PB']<df['y_fitted']]
df_choice.head()

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章