Akahachi
注1 他にも、REIT(不動産投資信託)やNameからは商品性が判断できないファンド等も結構ある。また、よく見るとSectorが定義されている銘柄の中にもこのような銘柄がある(キリがないので今回は無視)
注2 今思うと、LightGBMよりも、過学習しないような手法に持ち込んだ方がスッキリする
注3 株価があまりに低い銘柄は、市場全体の値動きとは無関係に、極端な値動きをしがちなため
コンペ開催中に提出したファイルを作成したコードに誤りがあり、本来意図していなかった処理をしていました。誤りがあった個所は株の特徴量作成の部分です。
提出したファイル public:0.03756, private:0.03701
意図した通りに処理した結果 public:0.03774, private:0.03706
# Googleドライブ直下に必要ファイルを圧縮したProbSpace_USEquity.zipがある前提です
from google.colab import drive
drive.mount('/content/drive')
!unzip /content/drive/MyDrive/ProbSpace_USEquity.zip
Mounted at /content/drive Archive: /content/drive/MyDrive/ProbSpace_USEquity.zip inflating: company_list.csv inflating: submission_template.csv inflating: train_data.csv
import warnings
warnings.simplefilter('ignore')
import datetime
from IPython.display import clear_output
import pandas as pd
import numpy as np
from sklearn.model_selection import StratifiedKFold
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from lightgbm import LGBMRegressor
!pip install --q prophet
from prophet import Prophet
clear_output()
print('Done!')
Done!
train = pd.read_csv('train_data.csv')
train['date'] = pd.to_datetime(train['Date'])
train = train.set_index('date')
train.drop('Date', axis=1, inplace=True)
company = pd.read_csv('company_list.csv')
company = company[company['Symbol'].isin(train.columns)].drop('List', axis=1)
company = company.drop_duplicates().set_index('Symbol')
# "株"のSymbolのリスト
stocks = company[pd.isna(company['Sector'])==False].index
# 株以外のSymbolのリスト(Companyに記載のない銘柄はfundの可能性があるためこちらに分類)
funds = [c for c in train.columns if c not in stocks]
ln_train = np.log(train)
ln_return = ln_train.diff().dropna(how='all')
funds_to_est = [f for f in funds if f not in ['VTHR', 'IEF']] #間接的に予測するファンド類
stock_returns = ln_return[stocks]
fund_returns = ln_return[funds_to_est]
equity_bm = ln_return['VTHR']
bond_bm = ln_return['IEF']
print(f"株式市場全体のベンチマーク - VTHR: {company.at['VTHR', 'Name']}")
print(f"債券市場全体のベンチマーク - IEF: {company.at['IEF', 'Name']}")
株式市場全体のベンチマーク - VTHR: Vanguard Russell 3000 ETF 債券市場全体のベンチマーク - IEF: iShares 7-10 Year Treasury Bond ETF
# VTHR, IEFの対数収益率はProphetで予測
def predict_bm(asset:str)->float:
if asset=='equity':
y, reg = 'VTHR', 'IEF'
elif asset=='bond':
y, reg = 'IEF', 'VTHR'
bms = ln_return[[y, reg]].reset_index()
bms['lag_reg'] = bms[reg].shift()
bms = bms.rename(columns={'date':'ds', y:'y'}).drop(reg, axis=1).dropna(how='any')
m = Prophet()
m.add_regressor('lag_reg')
m.fit(bms)
future = m.make_future_dataframe(periods=1, freq='W')
future = future.merge(ln_return[reg].reset_index(drop=True), how='left', left_index=True, right_index=True)
future.rename(columns={reg:'lag_reg'}, inplace=True)
forecast = m.predict(future)
return forecast.iloc[417]['yhat']
return_equity_bm = predict_bm('equity')
return_bond_bm = predict_bm('bond')
clear_output()
print(f"最終週のベンチマークリターン予測値 株(VTHR):{return_equity_bm:.3%}, 債券(IEF):{return_bond_bm:.3%}")
最終週のベンチマークリターン予測値 株(VTHR):0.654%, 債券(IEF):0.139%
last_day = datetime.datetime(2019,11,17)
# 最終週の対数収益率を格納する辞書
test_return_dic = {}
test_return_dic['VTHR'] = return_equity_bm
test_return_dic['IEF'] = return_bond_bm
特徴量は
# 株価が1ドル未満になったことのある銘柄をtrainから外す
tmp = (ln_train<0).sum()
not_too_cheap = tmp[tmp==0].index
train_sample = [s for s in not_too_cheap if s in stocks]
sector_dic = dict(zip(company['Sector'].unique(), range(company['Sector'].nunique()+1)))
company['n_Sector'] = company['Sector'].map(sector_dic)
company_dic = dict(company)
lag_return_df = pd.concat([stock_returns.shift(), equity_bm.shift()], axis=1)
# 提出ファイルでは以下の2行の処理をしていなかったため、本来は前週のVTHRとの相対収益率のはずが、単なる前週の収益率になっていた
'''
for s in stocks:
lag_return_df[s] = lag_return_df[s] - lag_return_df['VTHR']
'''
df = pd.DataFrame()
for s in train_sample:
tmp_df = pd.concat([ln_return[[s, 'VTHR']].rename(columns={s:'return', 'VTHR':'stock_mkt'}),
lag_return_df[s]], axis=1).rename(columns={s:'rel_to_mkt'})
tmp_df['Symbol'] = s
tmp_df['IPOyear'] = company_dic['IPOyear'][s]
tmp_df['Sector'] = company_dic['n_Sector'][s]
df = pd.concat([df, tmp_df[1:]])
df.reset_index(inplace=True)
test_df = pd.DataFrame(ln_return[-1:][stocks].T-ln_return.at[last_day, 'VTHR']).rename(columns={last_day:'rel_to_mkt'})
test_df['stock_mkt'] = return_equity_bm
test_df['IPOyear'] = company_dic['IPOyear'][test_df.index]
test_df['Sector'] = company_dic['n_Sector'][test_df.index]
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=0)
oof = np.zeros(len(df))
pred = np.zeros(len(stocks))
lgb = LGBMRegressor(max_depth=5, subsample=0.8, random_state=0)
for i, (train_idx, valid_idx) in enumerate(skf.split(df, df['date'])):
x_train, y_train = df.loc[train_idx][['stock_mkt', 'rel_to_mkt', 'IPOyear', 'Sector']], df.loc[train_idx]['return']
x_valid, y_valid = df.loc[valid_idx][['stock_mkt', 'rel_to_mkt', 'IPOyear', 'Sector']], df.loc[valid_idx]['return']
lgb.fit(x_train, y_train, categorical_feature=['Sector'])
oof[valid_idx] = lgb.predict(x_valid)
oof_rmse = np.sqrt(mean_squared_error(y_valid, oof[valid_idx]))
print(f"{oof_rmse:.4f}", end=' ')
pred += lgb.predict(test_df[['stock_mkt', 'rel_to_mkt', 'IPOyear', 'Sector']])
tot_rmse = np.sqrt(mean_squared_error(df['return'], oof))
print()
print(f"Total RMSE:{tot_rmse:.4f}")
for i, s in enumerate(stocks):
test_return_dic[s] = pred[i]
0.0507 0.0510 0.0502 0.0508 0.0505 Total RMSE:0.0506
特徴量は
回帰手法の変更や特徴量の追加・変更をするつもりがなかったのでCVもしていません
bm_forecast = np.array([return_equity_bm, return_bond_bm]).reshape(1,-1)
for f in funds_to_est:
tmp_df = ln_return[[f, 'VTHR', 'IEF']].rename(columns={'VTHR':'stock_mkt', 'IEF':'bond_mkt'})
lr = LinearRegression()
lr.fit(tmp_df[['stock_mkt', 'bond_mkt']], tmp_df[f])
test_return_dic[f] = lr.predict(bm_forecast)[0]
price_dic = {}
for c in ln_train.columns:
price_dic[c] = np.exp(ln_train.at[last_day, c] + test_return_dic[c])
sub = pd.read_csv('submission_template.csv').set_index('id')
for i in sub.index:
sub.at[i, 'y'] = price_dic[i]
sub.to_csv('submission.csv')