テクニカル分析を駆使して成長銘柄を予言しよう!
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')