💲

謎の指標X

2022/12/18に公開

概要

  • S/N比が低い問題にはMLモデルの予測を直接用いるのは難しい。
  • 正しく作られたモデルの特徴量重要度を使って戦略を考えましょう。
  • J-Quants APIのおかげで良い感じの指標ができました。

ML予測をそのまま使わない方が良い。(過去の経験から)

去年一昨年のリッチマン先生の活躍を見て、暗号資産のML bot を作り始めた人は多いのではないだろうか。かく言う私もその一人である。しかし、リッチマン先生の最初のチュートリアルのように、MLモデルを予測に直接使うのは、特別な理由がない限り私は勧めない。理由は以下の3点。

  • みんなが強Kaggler になれるわけではない。
  • 予測がブラックボックスすぎると止める判断ができない。
  • 実際に効いている特徴量が1~2個で、それらを使った方が説明性がありかつ同等以上のパフォーマンスを持つ戦略ができる。

みんなが強Kaggler になれるわけではない。

一言で言うと、実践に持っていけるほどのパフォーマンスを出すことができないからである。しかしこれはもしかしたら筆者だけの問題かもしれない。
筆者はいくつかの分析案件をやってきたが、まともな成果を出せたことがない。期限を守れないのである。特に、一番苦手な作業は特徴量エンジニアリングである。これは短期間のうちに問題設定を理解することができないためであり、特徴量エンジニアリングができないことはデータサイエンティストとして死を意味するので現在進行形で死んでいる。Kaggleのようなデータサイエンスのコンペは、決められた期限以内にモデルの精度を競う競技になっており、非常にうまくできているように思う。Kaggle ができる人は、仕事もちゃんとこなせているだろう。
幸い、ファイナンスデータについてはここ2〜3年くらい触れ続けてきたので、勘どころがわかるようになってきた。また、この種目は所謂kaggle とは少し違う競技であるようなので、まだ戦える基盤はありそうだろ見ている。しかし安心はしていない。私にできないことはたくさんあるが、私にできることは他人にはできるからである。そのため、エッジの話は一切しない。

予測がブラックボックスすぎると止める判断ができない。

とても稼ぐモデルというのはなぜ稼いでいるのかわからないモデルであることが多いそうだが、これはあまり勧められない。マーケットに何らかのストレスが与えられたとき、モデルが負け始めたときにどうすれば良いのか分からなくなる。最初から停止条件を決めておいてもそうなる。あらかじめ戦略がホワイトボックスになっていれば対処しやすいのであるが、そうでないと理論的よりどころがなくなりストレスがすごいことになります。戦略の立て方としては、その戦略には誰がお金を払っているのをクリアになっていることが望ましい。それはアノマリーだったり、行動バイアスによるものかもしれない。これらによらず、戦略の先にお金を払ってくれる人が分からない戦略は、かなり脆いものとなるだろう。

少数の特徴量だけで予測の説明ができてしまうパターンが多い。

リッチマン先生のチュートリアルに感銘を受けて素朴にモデルを作った人は、モデルを改良していくうちに、最終的には実はいくつかの特徴量で戦略の説明が済んでしまうことに気づかれたのではないだろうか。実際、私のモデルは一つの特徴量で説明されていた。それならこの特徴量を元に判断をした方が良いだろう。以下に、このような具体例を述べることとする。

重要度から戦略を立てましょう。

まずはこんな感じのデータが与えられる。

target が何なのか、それを明らかにしていく。まずは以下の通り特徴量を追加

df['r1_2'] = df['r1'] ** 2
df['r2_2'] = df['r2'] ** 2
df['r3_2'] = df['r3'] ** 2
df['m1_2'] = df['m1'] ** 2
df['m2_2'] = df['m2'] ** 2
df['m3_2'] = df['m3'] ** 2
test_df['r1_2'] = test_df['r1'] ** 2
test_df['r2_2'] = test_df['r2'] ** 2
test_df['r3_2'] = test_df['r3'] ** 2
test_df['m1_2'] = test_df['m1'] ** 2
test_df['m2_2'] = test_df['m2'] ** 2
test_df['m3_2'] = test_df['m3'] ** 2
feats = ['r1', 'r2', 'r3', 'm1', 'm2', 'm3', 'r1_2', 'r2_2', 'r3_2', 'm1_2', 'm2_2', 'm3_2']

for feat in feats:
  print(f'{feat}:', np.corrcoef(df[feat], df['target'])[0, 1])

こんな感じになる。

インサンプルデータの特徴量重要度を計算

from sklearn.model_selection import cross_validate
import matplotlib.pyplot as plt

model = RandomForestRegressor(n_estimators=2000, max_depth=5, max_features=int(1), n_jobs=-1)

scores = cross_validate(model, df[feats], df['target'], cv=5, n_jobs=-1, return_estimator=True)

imp_feats = pd.DataFrame(data=scores['estimator'][0].feature_importances_, index=feats, columns=['importances0'])
for i in range(1, len(scores)):
  imp_feats[f'importance{i}'] = scores['estimator'][i].feature_importances_
imp_feats['mean'], imp_feats['std'] = imp_feats.mean(axis=1), imp_feats.std(axis=1)
imp_feats['mean'].plot.barh(xerr=imp_feats['std'])


何となく、r1, r1_2 が強そうです。ここでpermutation importances を測ってみましょう。

from sklearn.inspection import permutation_importance

model = RandomForestRegressor(n_estimators=2000, max_depth=5, max_features=int(1), n_jobs=-1)

model.fit(df[feats], df['target'])

result = permutation_importance(
    model, test_df[feats], test_df['target'], n_repeats=5, random_state=42
)
pd.DataFrame(result.importances_mean, index=feats, columns=['importances']).sort_values(by='importances').plot.barh(xerr=result.importances_std)


r1, r1_2, m1, m1_2くらいが良い特徴量になっていそうです。これらに対して再度Permutation importances をかけます。

feats = ['r1', 'r1_2', 'm1', 'm1_2']

model = RandomForestRegressor(n_estimators=2000, max_depth=5, max_features=int(1), n_jobs=-1)

model.fit(df[feats], df['target'])

result = permutation_importance(
    model, test_df[feats], test_df['target'], n_repeats=5, random_state=42
)
pd.DataFrame(result.importances_mean, index=feats, columns=['importances']).sort_values(by='importances').plot.barh(xerr=result.importances_std)


強そう。これらのターゲットを元に指標を作ります。r1_2と, m1, m1_2 あたりを調べると良さそうですね。r1_2はターゲットに対して負の相関、他二つは正の相関があります。r1_2 のヒストグラムを見ると、常に正の値を取っているので、1/r1_2とtarget との相関を見ると何か分かるかもしれません。

print('CORR of m1 / r2_2:', np.corrcoef((test_df['m1'] / test_df['r1_2']), test_df['target'])[0, 1])
print('CORR of m1_2 / r2_2:', np.corrcoef((test_df['m1_2'] /test_df['r1_2']), test_df['target'])[0, 1])


こうして、m1 / r1_2 という指標がtarget との相関が"0.047"とかなり高いことがわかりました。ちなみに、予測モデルそのものとの相関はどうでしょうか。こちらを計算すると、以下のようになります。

test_df['pred'] = model.predict(test_df[feats])
print('CORR of pred:', np.corrcoef(test_df['pred'], test_df['target'])[0, 1])


お手製で作った指標の方がより良い感じの指標になっていますね。
ちなみに、答え合わせをすると、次のようなデータセットの構成となっていました。

N=10000
noise = 7
df = pd.DataFrame(data=abs(np.random.randn(N, 6)) + 0.1, columns=['r1', 'r2', 'r3', 'm1', 'm2', 'm3'])
df['target'] = 0.01 * df['m1'] * df['m2'] / (df['r1']**2) + noise*abs(np.random.randn(N))
test_df = pd.DataFrame(data=abs(np.random.randn(N, 6)) + 0.1, columns=['r1', 'r2', 'r3', 'm1', 'm2', 'm3'])
test_df['target'] = 0.01 * test_df['m1'] * test_df['m2'] / (test_df['r1']**2) + noise*abs(np.random.randn(N))

答えの指標は m1*m2/(r1**2) だったのですが、かなり近い指標を構築することができました。
以上のように、本質的な戦略をデータから導き出すことに取り組んだ方が良いように思われます。

J-Quants APIのおかげでいい感じの指標ができました。

重要度分析の先にあった特徴量を元に謎の指標Xができました。これは中々気付けないが、時間をかければ見つかる。MLが見つけたパターンなのでそこまで大外れはないだろう。


デプロイ待ったなし。
ちなみにこいつはもっとずっと強くすることができます。こんな感じです。

効率的市場仮説より非効率的市場仮説を研究した方が、社会にも良いインパクトを与えられるのではなかろうか。
Kabu is easy!

まとめ

  • 素朴にMLを使うことの問題点を述べた。
    • 予測に求められる精度の問題、予測がブラックボックスである場合に起こる不足事態への対処
    • MLは使い所が肝心
  • 予測性能を上げるだけでなく、特徴量重要度分析によるクリアな戦略構築をするべき。
  • J-Quants API と特徴量分析を用いて仮説を立て、実際にワークする(ように見える)謎の指標Xを見つけることができました。これはとても強いです。
  • まだまだパフォーマンスを上げられます。

最後に

JQuant API とマケデコの繁栄を切に願います。

Discussion