第 09 課:クオンツにおける教師あり学習の応用
機械学習は魔法ではなく、統計的パターンを拡大する虫眼鏡だ。データにパターンがなければ、どんな強力なモデルでもアルファは抽出できない。
「聖杯」から「ツール」へ
2017年、あるヘッジファンドがトップAI研究者のチームを採用し、ディープラーニングでクオンツ取引を「革命」すると約束した。
彼らは10層のLSTMネットワークを構築し、20年分の分レベルデータで学習させ、GPUクラスターで3ヶ月間実行した。
バックテスト結果:年率150%のリターン、シャープレシオ4.0。
創業者は興奮して発表した:「聖杯を見つけた!」
ライブ運用開始から3ヶ月後:
- 1ヶ月目: +5% (期待通り)
- 2ヶ月目: -8% (心配し始める)
- 3ヶ月目: -15% (パニック)
累積損失18%、同時期にS&P 500は8%上昇。
何が起きたのか?
- 過去のノイズへの過学習: 10層のLSTMは数百万のパラメータを持ち、過去のデータを完璧に「記憶」したが、そのパターンは単なるノイズだった
- 予測と利益は別物: モデルの精度は52%で、まあまあに聞こえるが、取引コストを差し引くと純損失
- 分布ドリフト: 2017年の市場構造は学習データ(1997-2016)とすでに異なっていた
教訓: クオンツ取引における機械学習の正しい役割は「価格変動の予測」ではなく、ノイズから弱いが堅牢なシグナルを抽出すること。このレッスンでは教師あり学習の正しい使い方を教える。
9.1 教師あり学習のクオンツ視点
教師あり学習とは?
既知の答えを持つデータを使ってモデルを「学習」させ、未知のデータの答えを予測できるようにする。
学習データ:
入力 X (特徴量) -> 出力 Y (ラベル)
[昨日のリターン, 出来高, RSI] -> [明日上昇/下降]
[0.02, 1.5M, 65] -> [上昇]
[-0.01, 2.0M, 35] -> [下降]
モデルが学習すること:
「RSI > 60で出来高増加なら、明日上昇する確率が高い」
予測フェーズ:
新しい入力 [0.01, 1.8M, 70] -> モデル予測 [上昇?]
クオンツ取引における「ラベリング」のジレンマ
従来の機械学習: ラベルは明確 (猫/犬、スパム/非スパム)
クオンツ問題: ラベルはどう定義すべきか?
| ラベリング手法 | 問題点 |
|---|---|
| 「明日上昇 = 1, 下降 = 0」 | 0.01%の上昇も5%の上昇も両方「上昇」? |
| 「5日リターン > 1%」 | その5日間で、最初に10%下落してから回復するかも |
| 「リターンそのもの」 | ノイズが多すぎて、モデルがパターンを見つけにくい |
| 「シャープ > 1のポジション」 | 保有期間全体を見る必要があり、先読みバイアスのリスク |
正しいアプローチ: ラベルは実行可能な取引判断を反映すべきで、抽象的な予測ターゲットではない。
よくある誤解
| 誤解 | 現実 |
|---|---|
| 「精度が高いほど良い」 | 3:1の勝率損失比率で52%の精度は、1:1の比率で70%の精度よりはるかに良い |
| 「複雑なモデルほど強い」 | 金融データは低S/N比、シンプルなモデルの方がしばしば堅牢 |
| 「特徴量が多いほど良い」 | 特徴量が多すぎると次元の呪いと過学習を招く |
| 「ディープラーニングは万能」 | ディープラーニングには大量のデータが必要で、クオンツ取引では通常不足 |
9.2 特徴量エンジニアリング: クオンツ取引のコア戦場
アルファの80%は特徴量エンジニアリングから来る、モデル選択ではない。
特徴量の種類
| 特徴量タイプ | 例 | 情報源 |
|---|---|---|
| 価格特徴量 | リターン、ボラティリティ、モメンタム | OHLCV |
| テクニカル指標 | RSI、MACD、ボリンジャーバンド | 価格由来 |
| 統計的特徴量 | 歪度、尖度、自己相関 | 分布特性 |
| クロスアセット特徴量 | セクターモメンタム、市場センチメント | 関連資産 |
| オルタナティブデータ | 衛星画像、ソーシャルメディア | 外部データ |
特徴量構築例
AAPLの日次データがあるとして、以下の特徴量を構築する:
基礎データ (5日間):
日付 始値 高値 安値 終値 出来高
Day 1 $180 $182 $178 $181 10M
Day 2 $181 $185 $180 $184 12M
Day 3 $184 $186 $183 $183 11M
Day 4 $183 $184 $180 $181 15M
Day 5 $181 $183 $179 $182 13M
特徴量計算 (Day 5終値後):
1. モメンタム特徴量
5日リターン = (182 - 181) / 181 = 0.55%
3日リターン = (182 - 184) / 184 = -1.09%
2. ボラティリティ特徴量
5日リターンシーケンス: [1.66%, -0.54%, -1.09%, 0.55%]
日次ボラティリティ = std() = 1.11%
年率ボラティリティ = 1.11% x sqrt(252) = 17.6%
3. 出来高特徴量
5日平均出来高 = (10+12+11+15+13)/5 = 12.2M
今日の出来高 / 平均 = 13/12.2 = 1.07 (やや平均以上)
4. 価格ポジション
5日高値 = $186, 安値 = $178
現在のポジション = (182-178)/(186-178) = 50% (中間位置)
良い特徴量の基準
| 基準 | 検証方法 | 満たさない場合の結果 |
|---|---|---|
| 予測力 | 単変量テスト IC > 0.03 | 計算リソースの無駄 |
| 安定性 | 期間全体でICの変動が小さい | 特定期間への過学習 |
| 低相関 | 既存特徴量との相関 < 0.7 | 情報の冗長性 |
| 解釈可能 | ロジックを明確に説明できる | デバッグが困難 |
特徴量選択の実践的手法
手法1: 単変量スクリーニング
各特徴量とラベルの相関を計算する (IC, Information Coefficient):
IC = corr(特徴量ランキング, リターンランキング)
良い特徴量: IC平均 > 0.03, IC安定性 (IC/std(IC)) > 0.5
手法2: 重要度プルーニング
シンプルなモデル(Random Forestなど)を学習させ、特徴量重要度を確認:
上位5特徴量が重要度の80%以上を占める場合:
-> 上位5-10特徴量のみ保持
-> その他の特徴量はノイズの可能性
手法3: 再帰的除去
最も重要度の低い特徴量を段階的に削除し、モデルパフォーマンスを観察:
開始: 50特徴量, シャープ 1.2
30に削減: シャープ 1.3 (実際に改善!)
10に削減: シャープ 1.4 (さらに改善)
5に削減: シャープ 1.1 (低下し始める)
-> 最適な特徴量数は約10
9.3 一般的なモデルとその使用例
モデル比較
| モデル | 長所 | 短所 | 使用例 |
|---|---|---|---|
| 線形回帰 | シンプル、解釈可能、過学習に強い | 線形関係のみ捉える | ファクター投資、リスクモデル |
| Random Forest | 非線形、過学習に強い、特徴量重要度 | 遅い、外挿が苦手 | 分類、特徴量選択 |
| XGBoost/LightGBM | 強力、高速、欠損値処理 | 過学習しやすい、ブラックボックス | 一般的な分類/回帰 |
| LSTM | 時系列依存性を捉える | 大量データが必要、遅い | データが豊富なシナリオのみ |
| Transformer | 強力なアテンションメカニズム | さらに大量データ必要、学習困難 | 研究フロンティア、本番では稀 |
モデル失敗条件
線形モデルの失敗:
- 特徴量-リターン関係が非線形
- 強い相互作用効果が存在(例:「低バリュエーション + 高モメンタム」)
ツリーモデルの失敗:
- 外挿が必要(学習範囲外の値を予測)
- 特徴量が連続的に変化(ツリーモデルは階段状の予測)
ディープラーニングの失敗:
- データサイズ < 100,000サンプル(通常過学習)
- 特徴量品質が低い(ゴミ入力、ゴミ出力)
- 深刻な分布ドリフト(過去が未来を代表しない)
クオンツ取引における実践的選択
自問: どれくらいのデータがあるか?
データ < 5,000サンプル (例: 20年の日次データ)
-> 線形モデル、Ridge回帰
-> 特徴量 < 20
データ 5,000-50,000サンプル (例: 1年の分データ)
-> Random Forest、XGBoost
-> 特徴量 20-50
データ > 50,000サンプル (例: ティックデータ)
-> LSTM/Transformerを試せる
-> ただしシンプルなモデルを上回るか検証が必要
9.4 金融データの特殊な課題
低S/N比
金融シグナルは極めて弱い:
| データタイプ | S/N比 | 達成可能な予測力 |
|---|---|---|
| 画像認識 | 高 | 精度95%+ |
| 自然言語 | 中 | 精度80%+ |
| 金融予測 | 極めて低 | 52-55%の精度でもトップクラス |
なぜ金融のS/N比はこんなに低いのか?
- 市場はほぼ効率的: 明らかなパターンはすぐに裁定される
- 参加者が多い: あなたが見つけたパターンを他の人も使っている
- ノイズが支配的: 短期価格変動の90%はランダムな変動
非定常分布
学習データと予測データは異なる分布を持つ:
学習セット (2015-2019):
平均ボラティリティ 15%
トレンド支配的
テストセット (2020):
ボラティリティが80%に急上昇
極端な急騰と暴落
-> 2020年にモデルが失敗
対処法:
- ローリングウィンドウを使用し、常に再学習
- ローリング統計を使った特徴量正規化
- 異なるレジームを別々にモデル化
クラス不均衡
「大きな上昇(>3%) = 1, それ以外 = 0」を使う場合:
大きな上昇日: ~5%
通常日: ~95%
モデルは「常に0を予測」を学習、95%の精度だが完全に無用
対処法:
- クラス重みを調整
- 精度の代わりにAUCを評価に使用
- 層別サンプリングで学習セットのクラスバランスを確保
9.5 モデル評価: 精度ではない
クオンツ特有の評価指標
| 指標 | 計算 | 良い基準 |
|---|---|---|
| IC (Information Coefficient) | corr(予測ランキング, 実際のリターンランキング) | > 0.03 |
| IR (Information Ratio) | IC平均 / IC標準偏差 | > 0.5 |
| ロング-ショートリターン | 上位五分位 - 下位五分位のリターン差 | 有意に正 |
| ターンオーバー調整後リターン | リターン - 取引コスト | それでも正 |
モデル評価例
モデルが100銘柄の明日のリターンランキングを予測するとして:
モデル予測ランキング (高->低):
株A: 1 (最も上昇すると予測)
株B: 2
...
株Z: 100 (最も下落すると予測)
実際のリターンランキング (高->低):
株A: 5
株B: 10
...
株Z: 95
IC = corr([1,2,...,100], [5,10,...,95])
= 0.85 (予測ランキングは実際のランキングと高相関)
解釈:
- IC = 0.85は予測ランキングが実際のランキングと非常に一致
- 上位10をロング、下位10をショートすれば、有意な超過リターンが期待できる
- ただし実市場では、ICは通常0.02-0.05のみ
現実世界の期待値
トップクオンツファンドのIC: 0.03-0.05
平均的なクオンツ戦略IC: 0.01-0.03
ランダム予測IC: 0
IC > 0.1を目指すな、それはほぼ確実に過学習
本番グレードのIC計算
先ほど示したシンプルなIC計算は簡易分析には適しているが、本番システムではエッジケースの堅牢な処理が必要。以下は本番対応の実装:
import numpy as np
from scipy.stats import spearmanr
def calculate_ic(
signals: np.ndarray,
returns: np.ndarray,
method: str = "spearman"
) -> float:
"""Information Coefficient (IC) を計算 - シグナルとリターンの相関
Args:
signals: 予測シグナル配列
returns: 実際のリターン配列
method: "spearman" (推奨) または "pearson"
Returns:
IC値、範囲 [-1, 1]
"""
if len(signals) != len(returns):
raise ValueError("signals and returns must have same length")
if len(signals) < 2:
return 0.0
# 欠損値処理 - 本番では必須
mask = ~(np.isnan(signals) | np.isnan(returns))
signals, returns = signals[mask], returns[mask]
if len(signals) < 2:
return 0.0
if method == "spearman":
ic, _ = spearmanr(signals, returns)
else:
ic = np.corrcoef(signals, returns)[0, 1]
return float(ic) if not np.isnan(ic) else 0.0
なぜSpearmanがPearsonより好まれるか
| 側面 | Spearman | Pearson |
|---|---|---|
| 外れ値への感度 | 堅牢 - ランクを使用 | 非常に敏感 - 単一の外れ値が歪める |
| 関係タイプ | 単調(非線形)を捉える | 線形のみ捉える |
| 分布の仮定 | 不要 | 正規性を仮定 |
| 金融データへの適合 | より良い - リターンはファットテール | 悪い - 正規性仮定に違反 |
重要な洞察: 金融リターンはファットテールと外れ値で有名。単一の極端な日(市場クラッシュなど)がPearson相関を完全に歪める可能性がある。Spearmanは最初に値をランクに変換するため、外れ値の大きさに対して免疫がある。
使用例
基本IC計算:
# 日次シグナルと翌日リターン
signals = np.array([0.02, -0.01, 0.03, 0.01, -0.02])
returns = np.array([0.015, -0.005, 0.02, 0.008, -0.01])
ic = calculate_ic(signals, returns)
print(f"IC: {ic:.4f}") # IC: 0.9000 (強い相関)
ローリングIC (時系列):
def rolling_ic(
signals: pd.Series,
returns: pd.Series,
window: int = 20
) -> pd.Series:
"""スライディングウィンドウでローリングICを計算"""
ic_series = pd.Series(index=signals.index, dtype=float)
for i in range(window, len(signals)):
sig_window = signals.iloc[i-window:i].values
ret_window = returns.iloc[i-window:i].values
ic_series.iloc[i] = calculate_ic(sig_window, ret_window)
return ic_series
# 例: 20日ローリングIC
rolling_ic_values = rolling_ic(signal_series, return_series, window=20)
Information Ratio (IR) 計算:
def calculate_ir(ic_series: pd.Series) -> float:
"""Information Ratio を計算 = mean(IC) / std(IC)
IR > 0.5 は堅牢なシグナルを示す
"""
ic_clean = ic_series.dropna()
if len(ic_clean) < 2 or ic_clean.std() == 0:
return 0.0
return ic_clean.mean() / ic_clean.std()
# 例
ir = calculate_ir(rolling_ic_values)
print(f"IR: {ir:.2f}") # IR > 0.5 は良い
本番での注意点
-
データアライメント: シグナルとリターンが適切にアライメントされていることを確認。時刻Tのシグナルは、TからT+1(または予測ホライズン)のリターンに対応すべき。オフバイワンエラーは最も一般的なIC計算バグ。
-
最小サンプルサイズ: 意味のあるICには少なくとも30の観測が必要。サンプルが少ないと、相関推定は高分散で信頼できない。
-
IC減衰モニタリング: 時間経過でICを追跡。IC傾向の低下は以下を示す:
- アルファ減衰(シグナルが混雑してきた)
- レジーム変化(市場構造が変化)
- モデルドリフト(特徴量が予測力を失った)
# IC減衰検出
recent_ic = rolling_ic_values.tail(20).mean()
historical_ic = rolling_ic_values.tail(60).head(40).mean()
if recent_ic < historical_ic * 0.5:
print("WARNING: ICが50%+減衰しました")
-
クロスセクショナルvs時系列IC:
- クロスセクショナルIC: ある時点での銘柄間の相関(銘柄選択に典型的)
- 時系列IC: 1つの資産の時間経過での相関(タイミングシグナルに典型的)
上記の実装は両方に機能する; データが正しく構造化されていることを確認するだけ。
9.6 マルチエージェントの視点
Signal Agentのモデル選択
マルチエージェントシステムでは、教師あり学習モデルはSignal Agentのコア:
モデルが失敗したらどうする?
Signal Agentは自身のモデルが失敗しているかを検出する必要がある:
| 検出指標 | 閾値 | トリガーされるアクション |
|---|---|---|
| ローリングIC < 0 が20日連続 | - | シグナル重みを50%削減 |
| 予測分布の異常 | 歪度 > 2 | シグナル出力を一時停止 |
| モデル信頼度の低下 | 予測分散が増加 | Meta Agentに通知 |
重要な設計: Signal Agentは常に正しいわけではない; 「自己疑念」メカニズムが必要。
コード実装 (オプション)
コード例を展開
import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import TimeSeriesSplit
def create_features(df: pd.DataFrame, lookback: int = 20) -> pd.DataFrame:
"""クオンツ特徴量を構築"""
features = pd.DataFrame(index=df.index)
# モメンタム特徴量
features['ret_1d'] = df['close'].pct_change(1)
features['ret_5d'] = df['close'].pct_change(5)
features['ret_20d'] = df['close'].pct_change(20)
# ボラティリティ特徴量
features['vol_20d'] = df['close'].pct_change().rolling(20).std()
# 出来高特徴量
features['vol_ratio'] = df['volume'] / df['volume'].rolling(20).mean()
# 価格ポジション
features['price_pos'] = (
(df['close'] - df['low'].rolling(20).min()) /
(df['high'].rolling(20).max() - df['low'].rolling(20).min())
)
return features.shift(1) # 先読みバイアスを回避
def create_label(df: pd.DataFrame, horizon: int = 5, threshold: float = 0.02):
"""ラベルを作成: 将来n日のリターンが閾値を超えるか"""
future_ret = df['close'].pct_change(horizon).shift(-horizon)
label = (future_ret > threshold).astype(int)
return label
def calculate_ic(predictions: pd.Series, returns: pd.Series) -> float:
"""Information Coefficientを計算"""
return predictions.corr(returns, method='spearman')
def walk_forward_train(df: pd.DataFrame, n_splits: int = 5):
"""Walk-Forward学習と評価"""
features = create_features(df)
labels = create_label(df)
# データアライメント
valid_idx = features.dropna().index.intersection(labels.dropna().index)
X = features.loc[valid_idx]
y = labels.loc[valid_idx]
tscv = TimeSeriesSplit(n_splits=n_splits)
results = []
for train_idx, test_idx in tscv.split(X):
X_train, X_test = X.iloc[train_idx], X.iloc[test_idx]
y_train, y_test = y.iloc[train_idx], y.iloc[test_idx]
model = RandomForestClassifier(n_estimators=100, max_depth=5, random_state=42)
model.fit(X_train, y_train)
predictions = model.predict_proba(X_test)[:, 1]
# 将来リターン(次の5日)を計算、予測ホライズンとアライメント
forward_returns = df.loc[X_test.index, 'close'].pct_change(5).shift(-5)
ic = calculate_ic(pd.Series(predictions, index=X_test.index),
forward_returns)
results.append({
'test_start': X_test.index[0],
'test_end': X_test.index[-1],
'ic': ic,
'accuracy': (model.predict(X_test) == y_test).mean()
})
return pd.DataFrame(results)レッスンの成果物
このレッスン完了後、以下が得られます:
- クオンツ取引における教師あり学習の役割の理解 - 何ができて何ができないかを知る
- 実践的な特徴量エンジニアリングスキル - 効果的なクオンツ特徴量を構築・スクリーニングできる
- モデル選択フレームワーク - データサイズに基づいて適切なモデルを選択
- 正しい評価方法 - 精度ではなくIC/IRを使ってモデルを評価
受入基準
| チェック項目 | 受入基準 | 自己テスト方法 |
|---|---|---|
| 特徴量構築 | OHLCVデータから5つの特徴量を構築できる | 価格データが与えられたら、手動で特徴量値を計算 |
| モデル選択 | データサイズに基づいてモデルを推奨できる | 「3年の日次データ」シナリオで推奨を述べる |
| IC計算 | IC=0.03が何を意味するか説明できる | メモなしで、ICのビジネス上の意味を説明 |
| 失敗検出 | 3つのモデル失敗シグナルをリストできる | Signal Agentの自己チェックロジックを設計 |
レッスンまとめ
- クオンツ取引における教師あり学習の正しい役割を理解: 弱いシグナルの抽出、価格変動の予測ではない
- コア特徴量エンジニアリング手法をマスター: 構築、スクリーニング、評価
- 金融データの特殊な課題を認識: 低S/N比、非定常性、クラス不均衡
- 精度ではなくIC/IRを使ってモデルを評価することを学ぶ
- Signal Agentが教師あり学習モデルをどう統合するかを理解
さらなる読書
- Advances in Financial Machine Learning by Marcos Lopez de Prado
- Background: LLM Research in Quantitative Trading - 最新の研究方向
次のレッスンプレビュー
Lesson 10: モデルからエージェントへ
モデルは予測を出力するだけだが、取引には判断が必要。「予測モデル」を「判断を下せるAgent」にどうパッケージ化するか? 次のレッスンでは、モデルからAgentへの橋渡しを行う。