🚀

JQuants APIとNumerai Signalsで具体的に金融時系列評価を学ぶ

2023/07/13に公開

朝 目が覚めて
真っ先に思い浮かぶ
オーバーナイトした含み損ポジのこと
思い切って
それを切った
「どうしたの?今日も顔暗w」って
聞かれたくなくて
...
メルト
(資産が)溶けてしまいそう
まだ切ってない爆損ポジあるなんて
絶対に言えない
だけど
(資産が)メルト 
(残高と)目も合わせられない

by ??? (20??年)

はじめに

こんにちは。日本爆損防止委員会です←

みなさん、今日も爆損ポジと楽しくお過ごしでしょうか

前回、以下の記事にてNumeraiを通じて金融時系列の評価指標を学ぶことで、爆損を無意味に重ねなくて済むようになるのではというお話をしました

https://zenn.dev/katsu1110/articles/c1269aec88ee05

quantstatsという様々な評価指標を一気に計算し可視化とレポーティングまでやってくれるライブラリも紹介し、すでにトレードロジックのある人には必ず役に立つでしょうというお話もしました

https://github.com/ranaroussi/quantstats

今回は、自分でまだトレードロジックを持っていない人のために、Numerai Signalsという自分でデータを持ち寄るNumeraiのコンペに提出するモデルを実際に作成し、そのdiagnostics(検証結果)を見ていくことで、より具体的な評価指標の理解を目指したいと思います

こちらが今回作成したNumerai Signalsのモデルです

https://www.kaggle.com/code/code1110/numeraisignals-starter-with-jquants-api

データはJQuants APIから取得しています

JQuants API x Numerai Signalsという企画をすることで、JPX様とNumerai様両者より何かもらえるんじゃないかと期待しているわけでは...ええありませんよええ

それではやっていきます

Numerai Signalsってなんぞ?

Numerai Signalsは、USのクオンツヘッジファンドであるNumeraiが実際に取引候補ユニバースとしている約5,000銘柄(世界株)の20日将来リターンを予測するゲームです

データもターゲットも提供されるNumerai Tournamentとは別のコンペで、Tournamentでは個別銘柄と日付が共にマスクされているのに対し、Signalsではターゲットしか与えられていないものの、そのターゲットに対応する個別銘柄と日付が与えられているためより具体的な取引のイメージはしやすいです

特徴量は一切与えられていないので、データは参加者側で準備する必要があり、与えられたターゲットに上手くマージする必要があるのでTournamentに比べると上級者向きです

日本株だけなの?

Numeraiの取引候補ユニバースは全世界から時価総額の大きい約5,000銘柄の個別株から成り、本来はなるべく多くの銘柄について、予測値(シグナル)を提出するのがNumerai Signalsで高いスコアを得るコツではあります

ただ、最低でも5銘柄の予測値を提出すれば受付はされるので、今回はJQuants API由来の日本株の予測値だけ提出したいと思います

ユニバースの約5,000銘柄のうち、日本株は約700と国別では米国に続き2位の銘柄数であるため、いいシグナルができればこれだけでもそこそこのスコアが取れることは期待できます

Notebookでやっていること

Notebookにはコメントも入れていますのでそちらを見ていただくのが間違いないですが、ざっくり

1️⃣ JQuants APIから高品質な日本株のデータを取得し、

2️⃣ Numeraiから提供されているターゲットを予測する機械学習モデルを作成し、

3️⃣ Numerai Signalsコンペに提出

をしています。

Kaggle notebookの環境と、NumeraiのAPIが想定する環境と、JQuants APIが想定する環境が微妙にずれているため、正直あまり実用的ではないかもしれないです(kaggle notebookやNumerai APIを古いバージョンにしたり、JQuants API Clientの公式ラッパーを使わないことでなんとか動かしました)

Pythonは発展が著しく、安定した開発環境を作るのはそんなに簡単じゃないですね

Numerai Signalsのターゲット

Numerai SignalsはTournamentと違って特徴量は提供されていませんが、targetは(2023年7月現在)5種類提供されています

そのうち、今回は

target_20d_factor_feat_neutral

という、Numerai側で思いつくファクタや特徴量で直交化された20日リターンターゲットを使っていきます

どういうファクタや特徴量で直交化されているのかは非公開ですが、Numeraiで働いている方はCEOのリチャード、CTOのアンソンをはじめめっちゃ優秀なので、まぁなんかきっといい感じのターゲットになってるんじゃないでしょうか(適当)

target_20d_factor_neutral

という、ファクタのみ(国、セクター、ベータ、モメンタム、サイズ)で直交化されているターゲットもありますが、今回は使いません

「いやそんなやつら信用できん...生リターンを寄越せぇえぇ!」

という方は、target_20d_raw_returnも提供されてますのでそちらを使われると良いかと思います

機械学習でシストレをやってらっしゃる方は個別株の「生リターン」を使っていらっしゃる方が多いとは思いますが、特に調整済み株価を使ってターゲットリターンを作っている場合、その時点より後に起こる株式分割や、配当が含まれているなど、実はリークの温床になりがちです

Numerai提供のtarget_20d_raw_returnは、(bin化されてはいますが)そのへんの難しいところをいい感じに対処しているため、安心して機械学習のターゲットにできます

いずれのターゲットも、bin化されています

train期間、validation期間は以下のようになっています

  • data_type = train (20030131 - 20121228)
  • data_type = validation (20130104 - 20230623)

2003年からデータがありますので、こちらで用意する特徴量もそれくらい古くからあるのが理想です

JQuants APIの使用

JQuants APIより(プレミアム会員でないとだめですが)2008年以降の価格データおよび財務データを取得しています

https://jpx.gitbook.io/j-quants-ja/outline/data-spec

Signalsのターゲットに合わせて2003年から欲しいですが、現状プレミアム会員でも、価格や財務データは2008年以降となっており、Numerai Signalsで使うにはやや厳しいですね...ううっ毎月お金払ってるのに

特に、強そうな売買内訳データが2015年からしかなく、Signalsのtrain期間に存在しないため今回は

  • 価格データ (2008/5/7〜)
  • 財務データ (2008/7/7〜)

のみ使っていきます。いやぁ2003年まで遡ってほしいなぁ(チラッ

多少データの質がアレでも、全期間、全銘柄についてのデータを使ってSignalsをやりたい!という将来有望すぎる貴方には、私が昔作ったYahoo Finance APIを使って作ったベースラインがあるので、そちらをご参照ください

https://www.kaggle.com/code/code1110/numeraisignals-starter-for-beginners

JQuants由来の特徴量とSignalsターゲットのマージ

データが取得できたら、簡単な特徴量エンジニアリングをして、ターゲットにマージして、機械学習に入れます

SignalsもTournament同様、daily submissionの対象ではありますが、相変わらずSignalsのターゲットは週次更新で、日付はfriday_dateと金曜日からスタートするラウンドが基準になっていますので、マージ後のデータは週次単位のデータマートになります

機械学習

Validation strategyの選定は金融時系列では(リーク、オーバーフィットを避けるため)特に大切ですが、今回はNumeraiのDiagnosticsツールを活用するため、単純に与えられたtrain、validation期間で分けます

決定木系のモデル、今回はHistGradientBoostingRegressor を使って、train期間だけ訓練を行い、validation期間で推論して予測値を作ります

モデルの特徴量の重要度をPermutation importanceで出しましたが、まぁ財務系特徴量は強いですね

直交化 (neutralization)に対する興味の高まりも勝手に感じていますので、今回は決定木系モデルから得られた予測値を、JQuants APIから得られた業種情報によって直交化する処理を行いました

以下のように、validation期間の予測値から、セクター情報だけを使って訓練した線形モデルの予測値を引いてやります

# degree of neutralization (0 for no neutralization, 1 for full neutralization)
NEUT = 1

# onehot sector info
train_sector_df = pd.get_dummies(train_df['Sector33CodeName'])
val_sector_df = pd.get_dummies(val_df['Sector33CodeName'])
    
# fit a linear model
lin_model = LinearRegression()
lin_model.fit(train_sector_df.values, train_df[target])
    
# neutralization
val_df['predict'] = val_df['predict'] - NEUT * lin_model.predict(val_sector_df.values)

こうすることで、予測値がセクターニュートラルになり、より(セクターの値動きに影響されない)個別銘柄の値動き、アルファが抽出できていることを期待します

もともと使っているターゲットはセクターニュートラルになっていますが、今回は直交化のデモも兼ねてということで、直交化の実装をしてみました

提出、検証期間のパフォーマンスの確認

NumerAPIを使って、検証期間の予測値を提出します(Numeraiのアカウント作成とAPIの設定が必要です)

提出し、以下からモデルスロットの右端のフラスコみたいなアイコンを押すと、検証期間のパフォーマンスが見れます

https://signals.numer.ai/scores

縦に6つの指標(FNCv4, RIC, CORR, CORRv4, ICv2, Churn)と、それぞれに対し4つの統計量(Mean, Standard Deviation, Sharpe, Max Drawdown)が計算されています

それぞれの説明の前にまず、Signalsでの予測値の取り扱いがTournamentとやや異なることを知っておかないといけません

Numerai Signalsではユーザが提出した予測値は、Numeraiが用意したファクタや特徴量(非公開)、さらにはその他の参加者の予測値によって直交化された上、評価指標が計算されるのです(RIC、ICなど一部指標は除く)

なぜかというと、ユーザが特定のファクタ(時価総額とか)に依存した予測値を出したときに、それをナイーブにトレーディングに使ってしまうのは危険だからですね。既知のリスクで直交化した上で、残ったアルファだけで取引をしたいわけです

Tournamentでは特徴量もNumeraiが与えているため、それを元に予測値を作れば、さほどおかしなことは起きないでしょうが、Signalsでは特徴量自体もユーザが作るため、よくわかってない爆損芸人のユーザがリスクの高い予測値を提出してくることは十分考えられます

そのため、Numerai側で予測値の直交化を行いリスクを下げないと、Numeraiも爆損してしまいます

生の提出値が必ずしもそのままスコアリングされるわけではないのですね。Notebook内でもvalidation期間のcorrなど計算してはいますが、提出してwebsiteで見る結果とは(傾向は似ますが)一致しません

前置きが長く成りましたが、以下指標の簡単な説明です

  • FNCv4 (Feature Neutral Correlation V4) - 提出された予測値を直交化したものと、target_20d_factor_feat_neutralとのラウンドごとのNumerai相関(現在のメイン評価指標)。高い方が良い

  • RIC (Raw Information Coefficient) - 提出された予測値(直交化なし)と、target_20d_factor_neutralとのラウンドごとのNumerai相関。高い方が良い

  • CORR - 提出された予測値(直交化なし)と、target_20dとのラウンドごとのスペアマン相関係数。高い方が良い

  • CORRv4 - 提出された予測値(直交化なし)と、target_20d_factor_feat_neutralとのラウンドごとのNumerai相関。高い方が良い

  • ICv2 - 提出された予測値(直交化なし)と、target_20d_raw_returnとのラウンドごとのスペアマン相関係数。高い方が良い

  • Churn - どれくらい頻繁にポートフォリオの入れ替えが発生しているか定量しているもので、Numeraiからは0.15以下なら良い予測値とされている。ある程度低い方が良い(実装

Numerai相関というのは、ロングショート戦略において大切な予測値の分布の端の部分を強調した相関係数の計算で、計算は以下でした


def numerai_corr(preds, target):
    """
    https://github.com/numerai/example-scripts/blob/8f1abbc56c7a1a19b8c6e615a8bd0d9becb7b244/utils.py#L175C1-L185C52
    """
    # rank (keeping ties) then gaussianize predictions to standardize prediction distributions
    ranked_preds = (preds.rank(method="average").values - 0.5) / preds.count()
    gauss_ranked_preds = scipy.stats.norm.ppf(ranked_preds)

    # center targets around 0
    centered_target = target - target.mean()

    # raise both preds and target to the power of 1.5 to accentuate the tails
    preds_p15 = np.sign(gauss_ranked_preds) * np.abs(gauss_ranked_preds) ** 1.5
    target_p15 = np.sign(centered_target) * np.abs(centered_target) ** 1.5

    # finally return the Pearson correlation
    return np.corrcoef(preds_p15, target_p15)[0, 1]

いずれの指標もラウンドごとに計算されているので、ラウンド数だけ数値が得られます

その平均(Mean)、標準偏差(Standard Deviation)、Sharpe(平均 / 標準偏差)、Max Drawdown(最大損失)がそれぞれの指標の横に計算されていますね。これらの具体的な計算方法は、面倒なので以前の記事に譲ります

https://zenn.dev/katsu1110/articles/c1269aec88ee05

自分が作成した例だと、FNCv4のsharpe ratioが0.1代と結構...うん、弱いですが、みなさんならもちろんもっと強いもの作れますよね?^^

計算された評価指標の下側に、デフォルトだとFNCv4のラウンドごとの値を可視化したものがあります

"TB200"のボタンを押すと全体ではなく予測値の上位下位200銘柄だけ使って計算したものに変わります(より実際の取引でのパフォーマンスに近くなる)

TB200にした方がFNCv4のsharpeは高くなりました。取引に関わる予測値の分布の端の部分でより、ターゲットと相関しているようです。これはgoodですね。

一方、Max Drawdownは悪化しました。全てのラウンドで予測通りに市場が動くわけはないので、実際流すとしたらこれくらいの損失は覚悟する必要があります

"CUMULATIVE"を押すと、ラウンドごとの値ではなく累積の値が可視化されます

一応、右肩上がりですね。

最後に

あるあるですが、特に検証期間が長いとたいして強くないロジックでも累積で見ると右肩上がりで強そうに見えるんですよね

ただラウンドごとの値を見るともちろんずっと勝っているわけではなく、負けが続く期間も全然あるわけで、Max Drawdownの数字もそうですがどれだけの負けに自分が耐えられるか、シビアに判断していく必要があります

爆損してからではなかなか理性的に判断できませんから、流す前、検証期間の評価指標を見てるときに、理性的に判断しないといけません

マケデコではよく機関投資家を交えたイベントもやっていただいていますが、

自分が機関投資家なら、このモデルを自信を持って投資家に説明できて、納得していただいてお金を出してもらえるだろうか?

をイメトレしてみると、爆損芸人の方は良いかもしれません

もちろん検証結果とライブの結果がずれることは時系列問題を扱う限りあるあるですから、ペーパートレード、Numeraiでもできますが、NMRをstakeせずに、お金をかけずにモデルを本番に限りなく近い環境で流してみることで、ある程度ライブでもワークしそうか判断することは絶対必要かと思います

...ということで、今回はJQuants API x Numerai Signalsという企画でした

JPX様とNumerai様両者より何かもらえるんじゃないかと期待しているわけでは...ええありませんよええ

引用

Discussion