以下の過去コンペへの取り組みの備忘録を残そうと思います。
https://www.kaggle.com/competitions/jpx-tokyo-stock-exchange-prediction/overview
コンペ概要
- 予測対象銘柄(約2000銘柄)から予測された株式の利益を順位付けし、上位200銘柄と下位200銘柄を比較して、その差異を評価する
- 評価はシャープレシオに基づいて評価され、上位200銘柄が購入、下位200銘柄が空売りとして扱われ翌日売買したと仮定し合計リターンが計算される
コンペの把握
ひとまずEasy to understand the competitionを読んでいき、どういったデータがあるのか理解していきます
前準備
まずstock_pricesテーブルのTargetカラムが翌日と翌々日の終値の変化率という理解で正しいか確認します
tmpdf["Close_shift1"] = tmpdf["Close"].shift(-1)
tmpdf["Close_shift2"] = tmpdf["Close"].shift(-2)
tmpdf["rate"] = (tmpdf["Close_shift2"] - tmpdf["Close_shift1"]) / tmpdf["Close_shift1"]
tmpdf
上記のコードを実行して得られた値とstock_pricesテーブルのTargetカラムの値が一致したため理解は正しいようです
次に各Targetカラムを参照してrank付を行います
tmpdf2["rank"] = tmpdf2["Target"].rank(ascending=False,method="first") -1
tmpdf2 = tmpdf2.sort_values("rank").reset_index(drop=True)
tmpdf2
ランクの数が小さいほど変化率が正の値として大きいためその銘柄は購入すると利益が出る可能性があり、ランクの数が大きいほど変化率が負の値として大きいため、空売りすると利益が出る可能性があリます
ランク付けした結果から上位200銘柄を抜き出し、1~2の間の数値で重み付をします
- linspace()
- 重み付け用の等差数列を生成
tmpdf2_top200 = tmpdf2.iloc[:200,:]
weights = np.linspace(start=2, stop=1, num=200)
tmpdf2_top200["weights"] = weights
tmpdf2_top200["calc_weights"] = tmpdf2_top200["Target"] * tmpdf2_top200["weights"]
tmpdf2_top200.head(3)
得られた値を用いてSupを求めます
Sup = \frac{\sum_{i=1}^{200} (r(up_i,t)*linearfunction(2,1)_i)) }{Average(linearfunction(2,1))}
Sup = tmpdf2_top200["calc_weights"].sum()/np.mean(weights)
Sup
同様の処理を下位銘柄でも行いSdownを求めます
Sup,Sdownから日次スプレッド収益を算出します
daily_spread_return = Sup - Sdown
daily_spread_return
これは毎日計算され、Scoreは日次スプレッド収益の平均を標準偏差で割ったものになります
Score = \frac{Average(R_{day_1-day_x})}{STD(R_{day_1-day_x})}
上記の流れをまとめると以下になります
import numpy as np
import pandas as pd
def calc_spread_return_sharpe(df: pd.DataFrame, portfolio_size: int = 200, toprank_weight_ratio: float = 2) -> float:
"""
Args:
df (pd.DataFrame): predicted results
portfolio_size (int): # of equities to buy/sell
toprank_weight_ratio (float): the relative weight of the most highly ranked stock compared to the least.
Returns:
(float): sharpe ratio
"""
def _calc_spread_return_per_day(df, portfolio_size, toprank_weight_ratio):
"""
Args:
df (pd.DataFrame): predicted results
portfolio_size (int): # of equities to buy/sell
toprank_weight_ratio (float): the relative weight of the most highly ranked stock compared to the least.
Returns:
(float): spread return
"""
assert df['Rank'].min() == 0
assert df['Rank'].max() == len(df['Rank']) - 1
weights = np.linspace(start=toprank_weight_ratio, stop=1, num=portfolio_size)
purchase = (df.sort_values(by='Rank')['Target'][:portfolio_size] * weights).sum() / weights.mean()
short = (df.sort_values(by='Rank', ascending=False)['Target'][:portfolio_size] * weights).sum() / weights.mean()
return purchase - short
buf = df.groupby('Date').apply(_calc_spread_return_per_day, portfolio_size, toprank_weight_ratio)
sharpe_ratio = buf.mean() / buf.std()
return sharpe_ratio
2021年だけスコアを計算
日によっては2000銘柄以下の日があるため、2000銘柄ある日だけをピックアップします
結果2000銘柄あるのは2020年の12月23日以降だとわかったので、絞り込みランク表示し、スコアを算出
スコアが高いほど利益が出る
idcount = stock_prices.groupby("Date")["SecuritiesCode"].count().reset_index()
plt.plot(idcount["Date"],idcount["SecuritiesCode"])
idcount.loc[idcount["SecuritiesCode"]==2000,:]
stock_prices2 = stock_prices.loc[stock_prices["Date"]>= "2021-01-01"].reset_index(drop=True)
stock_prices2["Rank"] = stock_prices2.groupby("Date")["Target"].rank(ascending=False,method="first") -1
stock_prices2["Rank"] =stock_prices2["Rank"].astype("int") # floatだとエラー
score = calc_spread_return_sharpe(stock_prices2, portfolio_size= 200, toprank_weight_ratio= 2)
各フォルダの意味
-
data_specifications
各ファイルのカラムの意味を定義するファイルがあるフォルダ -
train_files
トレーニングデータ -
Supplemental_files
最新の株価データ -
example_test_files
提出時に提供されるファイル形式の例。Targetは含まれていないので、Closeから自分で計算する
各ファイルについて
- stcok_prices
日々の終値やTargetカラムなどのデータがある - trades
前営業週の取引量を集計したもの - secondary_stock_prices
流動性の低い証券データも含まれており、市場全体の評価の参考になる - financials
四半期決算報告書 - stock_list
証券コードと会社の名前が載っています
一通りどういうコンペかザックリ分かったので次回はどのような解析をすればいいか学んでいこうと思います