先週、自作のランダムフォレストで製造業株の翌日騰落を予測するモデルを久しぶりに回してみたら、テスト精度が58%って出てきたんです。思わず「イケるやん!」って一人でガッツポーズしちゃいました。。。でも冷静になって考えたら、コイントスが50%なんだから58%って本当に凄いのか、それとも「まあまあ普通」なのか、僕には全然判断がつかなくて。逆に不安になって、機械学習で株価を予測するときの「現実的な精度」ってどれくらいなのか調べてみることにしました。
株価予測の「現実的な精度」ってどれくらい?
いろいろ調べてみると、株価の翌日騰落方向(上がるか下がるかの二値分類)を予測するモデルの精度は、55〜65%あたりが現実的な上限というのが業界的な共通認識のようです。逆に「99%の精度で予測できます」みたいな謳い文句のサービスは、ほぼ間違いなく過学習しているか、単に怪しい商材だと思ったほうがいいです。市場は効率的市場仮説がある程度は成り立っていて、簡単に予測できるならみんなが真似して優位性が消えてしまうので、55〜65%という「ちょっとだけコイントスより良い」くらいが妥当な着地点なんですよね。僕の58%は、決して悪くない数字だったみたいで、ちょっと安心しました。
なぜ50%台でも儲かる可能性があるのか
ここが僕が一番誤解していたポイントなんですが、的中率が50%台でも、リスクリワード比(勝った時の利益と負けた時の損失の比率)とポジションサイズの設計次第で、トータルの期待値はプラスにできます。的中率60%・平均利益1.5%・平均損失1%だとすると、期待値は「0.6×1.5 − 0.4×1.0 = 0.5」となり、1トレードあたりプラスになる計算です。つまり「当たるかどうか」だけを見て一喜一憂するんじゃなくて、「当たった時にどれだけ伸ばし、外れた時にどれだけ早く損切りするか」のほうが本当は大事だったんですね。以前カルマン基準(Kelly Criterion)についてこのブログでも書きましたが、精度の話とポジションサイジングの話はセットで考えないと意味がないと、今回あらためて実感しました。
「99%的中」を謳う商材はほぼ詐欺
SNSとかでたまに見かける「AIが99%の精度で株価を予測」みたいな触れ込みのツールは、正直かなり怪しいです。理由は単純で、そんなモデルが本当に存在するなら、開発者は自分でトレードして億万長者になっているはずで、わざわざ他人に売る理由がありません。バックテストの期間だけ都合よく切り取っていたり、未来の情報を混ぜてしまう「リーク」が起きていたりするケースがほとんどだと思っておいたほうが安全です。
Pythonで精度と期待値を検証してみる
実際に自分のモデルの精度と期待値を計算したコードを簡略化して載せておきます。
import yfinance as yf
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import TimeSeriesSplit
from sklearn.metrics import accuracy_score
# 製造業銘柄の例:ファナック
df = yf.download("6954.T", period="3y")
df["return"] = df["Close"].pct_change()
df["target"] = (df["return"].shift(-1) > 0).astype(int)
# 特徴量は簡易的にラグリターンとRSIなどを想定
features = ["return"] # 本来はテクニカル指標を複数追加
df = df.dropna()
X, y = df[features], df["target"]
tscv = TimeSeriesSplit(n_splits=5)
accs, expectancies = [], []
for train_idx, test_idx in tscv.split(X):
model = RandomForestClassifier(n_estimators=200, max_depth=4, random_state=42)
model.fit(X.iloc[train_idx], y.iloc[train_idx])
pred = model.predict(X.iloc[test_idx])
acc = accuracy_score(y.iloc[test_idx], pred)
accs.append(acc)
# 期待値の簡易計算(勝率×平均利益 − 敗率×平均損失)
ret = df["return"].shift(-1).iloc[test_idx]
win_rate = acc
avg_win = ret[ret > 0].mean()
avg_loss = abs(ret[ret < 0].mean())
expectancy = win_rate * avg_win - (1 - win_rate) * avg_loss
expectancies.append(expectancy)
print(f"平均精度: {sum(accs)/len(accs):.2%}")
print(f"平均期待値: {sum(expectancies)/len(expectancies):.4%}")
時系列クロスバリデーション(TimeSeriesSplit)を使うことで、未来の情報が学習に混ざるリークを防ぎつつ、精度だけでなく期待値まで一緒に見るようにしています。精度の数字だけを追いかけていた頃の自分に教えてあげたいコードです。
まとめ
58%という数字に一喜一憂していた自分でしたが、大事なのは精度そのものより「期待値がプラスかどうか」「損切りルールがちゃんと機能しているか」ということだと再確認できました。個人的には、次はこのモデルの予測を実際のポジションサイジング(Kelly基準ベース)と組み合わせて、トータルのリターンがどう変わるかを検証してみようと思います。地道ですが、これが一番効くやり方な気がしています。

