第 03 課:数学と統計の基礎
クオンツ取引の本質は、数学的言語で市場を記述することである。数学的仮定が間違っていれば、モデルが精密であるほど、より早く損失を出す。
LTCM: ノーベル賞受賞者の致命的な仮定
1998年、Long-Term Capital Management(LTCM)には2人のノーベル経済学賞受賞者、ウォール街のトップ数学者がおり、$1,250億の資産を持っていた。
彼らのモデルは精巧で、一つの中核仮定に基づいていた:市場リターンは正規分布に従う。
正規分布によると、彼らの戦略が1日で1%以上損失する確率は10万分の1だった。年間252取引日で、理論的にはこれは400年に1回起こる。
そしてロシアがデフォルトした。
1998年8月21日、LTCMは1日で$5億5,000万を失った。次の1ヶ月で、彼らは$46億を失った - ほぼすべての資本。正規分布によると、このイベントの確率は10^-27で、宇宙誕生以来毎秒起こらないことに相当する。
しかし起こった。
何が間違っていたのか?
- 市場は正規分布に従わない: 実際の市場には「裾の重い分布」があり、極端なイベントは正規分布が予測するよりはるかに頻繁に発生する
- 相関が突然急上昇する: 通常時、資産の相関は低い。危機時、すべての資産が相関1に急上昇する
- ボラティリティクラスタリング: 大きな下落の後、さらに大きな下落が続くことが多い - これは独立した「コイン投げ」ではない
LTCMの教訓: モデルが不正確だったのではなく、数学的仮定が根本的に間違っていた。
このレッスンの目標: 金融データの真の特性を理解し、「コイン投げ」の数学を使って市場を分析することを避ける支援をする。
3.1 時系列の基礎
系列 vs IID仮定
従来の統計はデータが**IID(独立同一分布)**であると仮定する:
- 独立: 今日のデータは昨日とは無関係
- 同一分布: 各日のデータは同じ分布から来る
金融データはIID仮定をほとんど満たさない:
import numpy as np
# IIDデータ: コイン投げ
coin_flips = np.random.choice([0, 1], size=100) # 各投げは独立、確率は一定
# 金融データ: 今日の価格は昨日に依存
prices = [100]
for _ in range(99):
# 今日の価格 = 昨日の価格 + ランダム変化
prices.append(prices[-1] * (1 + np.random.normal(0, 0.02)))
なぜこれが重要か?
- IID仮定でモデル化すると、連続損失の確率を過小評価する
- 実際の市場には「モメンタム」がある:上昇は上昇を続け、下落は下落を続ける傾向
ラグと自己相関
自己相関: 現在の値と過去の値との相関。
紙の計算例:
連続5日のリターンを仮定: +2%, -1%, +3%, +2%, -2%
| 今日 (t) | 昨日 (t-1) |
|---|---|
| -1% | +2% |
| +3% | -1% |
| +2% | +3% |
| -2% | +2% |
観察されたパターン:
- 昨日上昇、今日は時々上昇時々下落 -> 自己相関0付近(ランダム)
- 昨日上昇なら通常今日も上昇 -> 正の自己相関(モメンタム)
- 昨日上昇なら通常今日は下落 -> 負の自己相関(平均回帰)
SPY(S&P 500 ETF)の日次リターン自己相関は通常0付近(例: 2010-2020年で約0.02)、米国株の日次リターンはランダムウォークに近く、短期予測が困難であることを示す。正確な値はサンプル期間と方法論によって異なる。
解釈:
- 自己相関0付近 -> 過去は未来の予測価値が低い(弱形効率市場)
- 有意な正の自己相関 -> モメンタム効果(トレンドフォローが機能する可能性)
- 有意な負の自己相関 -> 平均回帰効果
コード実装(エンジニア向け)
import pandas as pd
def calculate_autocorrelation(series, lag=1):
"""ラグ期間での自己相関係数を計算"""
return series.autocorr(lag=lag)
# 例: SPY(S&P 500 ETF)の日次リターン自己相関
returns = prices_series.pct_change().dropna()
print(f"ラグ1自己相関: {calculate_autocorrelation(returns, 1):.3f}")
print(f"ラグ5自己相関: {calculate_autocorrelation(returns, 5):.3f}")定常性テスト
定常性: 統計的特性(平均、分散)が時間とともに変化しない。
from statsmodels.tsa.stattools import adfuller
def check_stationarity(series, name="Series"):
"""ADF検定: 系列が定常かどうかを判断"""
result = adfuller(series.dropna())
print(f"{name}:")
print(f" ADF統計量: {result[0]:.4f}")
print(f" p値: {result[1]:.4f}")
print(f" 結論: {'定常' if result[1] < 0.05 else '非定常'}")
# 価格系列は通常非定常
check_stationarity(prices_series, "価格系列")
# リターン系列は通常定常
check_stationarity(returns_series, "リターン系列")
なぜ定常性をテストするのか?
- ほとんどの統計モデルは定常入力を仮定
- 非定常系列のモデル化は「見せかけの回帰」を生む
- 解決策: 価格ではなくリターン(差分)を使用
3.2 リターンの数学的定義
単純リターン vs 対数リターン
紙の計算:
AAPLが$100から$110になると仮定:
単純リターン = (110 - 100) / 100 = 10%
対数リターン = ln(110/100) = ln(1.1) ~ 9.53%
なぜクオンツトレーダーは対数リターンを使うことが多いのか? この例を見る:
加法性問題(計算機で検証):
| 1日目 | 2日目 | 合計リターン | |
|---|---|---|---|
| 価格 | $100 -> $110 | $110 -> $100 | $100 -> $100 |
| 単純リターン | +10% | -9.09% | 0% |
| 直接合計 | 10% - 9.09% = 0.91% X | ||
| 対数リターン | +9.53% | -9.53% | 0% |
| 直接合計 | 9.53% - 9.53% = 0% ✓ |
結論: 対数リターンは直接合計できる。単純リターンは乗算する必要がある。100日の累積リターンを計算する場合、対数リターンは100回の加算のみ必要だが、単純リターンは100回の乗算が必要。
| 特性 | 単純リターン | 対数リターン |
|---|---|---|
| 加法性 | X 複数期間リターンを直接合計できない | O 複数期間リターンを直接合計できる |
| 対称性 | X 10%上昇してから10%下落 != 0 | O より対称的 |
| 正規性仮定 | 満たしにくい | 正規に近い |
累積リターン計算
紙の計算:
連続5日の日次リターン: +1%, -2%, +3%, +1%, -1%
単純リターン累積(乗算):
(1+0.01) x (1-0.02) x (1+0.03) x (1+0.01) x (1-0.01) - 1
= 1.01 x 0.98 x 1.03 x 1.01 x 0.99 - 1
= 1.0194 - 1 = 1.94%
対数リターン累積(直接合計):
0.01 + (-0.02) + 0.03 + 0.01 + (-0.01) = 2%
両方の方法は類似した結果を与えるが、対数リターンは計算が簡単。
年率換算リターン
紙の計算:
60取引日で5%獲得したと仮定、年率換算リターンは?
年率換算リターン = (1 + 5%)^(252/60) - 1
= 1.05^4.2 - 1
= 1.227 - 1
= 22.7%
なぜ252? 米国株は年間約252取引日(365日 - 週末 - 休日)。
よくある間違い:
- X 月次リターンを12倍して年率換算(複利を無視)
- X 暦日を使う(252を使うべき、365ではない)
- X 手数料を引く前に年率換算
コード実装(エンジニア向け)
def cumulative_return(returns, method='simple'):
"""累積リターンを計算"""
if method == 'simple':
return (1 + returns).prod() - 1 # 乗算
else:
return returns.sum() # 直接合計
def annualize_return(total_return, days, trading_days_per_year=252):
"""年率換算リターン"""
years = days / trading_days_per_year
return (1 + total_return) ** (1 / years) - 1
# 例: 60日で5%獲得
ann_return = annualize_return(0.05, 60)
print(f"年率換算リターン: {ann_return:.2%}") # ~ 22.7%3.3 リスクの数学的定義
分散と標準偏差
紙の計算:
5日間の日次リターンを仮定: +1%, -2%, +3%, 0%, -1%
ステップ1: 平均を計算
平均 = (1 - 2 + 3 + 0 - 1) / 5 = 0.2%
ステップ2: 分散を計算(平均からの二乗偏差の平均)
分散 = [(1-0.2)^2 + (-2-0.2)^2 + (3-0.2)^2 + (0-0.2)^2 + (-1-0.2)^2] / 5
= [0.64 + 4.84 + 7.84 + 0.04 + 1.44] / 5
= 14.8 / 5 = 2.96 (%^2)
ステップ3: 標準偏差を計算(ボラティリティ)
日次ボラティリティ = sqrt(2.96) ~ 1.72%
ステップ4: 年率換算
年率換算ボラティリティ = 日次ボラティリティ x sqrt(252) ~ 1.72% x 15.87 ~ 27.3%
ボラティリティの直感的意味:
| 年率換算ボラティリティ | 典型的資産 | 可能な1年範囲(68%確率) |
|---|---|---|
| 15% | 大型株(SPY) | -15% to +15% |
| 30% | テック株(TSLA) | -30% to +30% |
| 80% | 暗号通貨(BTC) | -80% to +80% |
この式を覚える: 年率換算ボラティリティ = 日次ボラティリティ x sqrt(252) ~ 日次ボラティリティ x 16
コード実装(エンジニア向け)
def calculate_volatility(returns, annualize=True, trading_days=252):
"""ボラティリティ(標準偏差)を計算"""
daily_vol = returns.std()
if annualize:
return daily_vol * np.sqrt(trading_days)
return daily_vol
# 例
daily_returns = pd.Series([0.01, -0.02, 0.03, 0, -0.01])
annual_vol = calculate_volatility(daily_returns)
print(f"年率換算ボラティリティ: {annual_vol:.2%}")共分散と相関
直感的理解:
相関係数は、2つの資産が「一緒に動く」かどうかを測定する:
| 相関 | 意味 | 例 |
|---|---|---|
| +1 | 完全に同期: A上昇はB上昇を意味 | 同セクター株(AAPL vs MSFT) |
| 0 | 無関係: Aの動きはBと無関係 | 金 vs テック株 |
| -1 | 完全に反対: A上昇はB下落を意味 | 株式 vs VIX(恐怖指数) |
実際の市場相関(参考値):
| 資産ペア | 通常期の相関 | 危機期の相関 |
|---|---|---|
| AAPL vs MSFT | +0.7 | +0.9 |
| SPY vs TLT(債券) | -0.3 | -0.5 or +0.8 |
| SPY vs GLD(金) | +0.1 | -0.2 |
危機時の「相関急上昇」: 通常時に無相関の資産が危機時に突然0.9の相関に急上昇することがある。これがLTCMを罠にかけた - 彼らは安定した相関を仮定していた。
マルチエージェントの視点: Portfolio Agentの中核的仕事は低相関資産を見つけてポートフォリオを構築することだが、Risk Agentは「危機時の相関急上昇」のリスクを考慮する必要がある。
コード実装(エンジニア向け)
def analyze_correlation(returns_a, returns_b):
"""2つの資産間の相関を分析"""
corr = returns_a.corr(returns_b)
print(f"相関係数: {corr:.3f}")
if corr > 0.7:
print("-> 高い正の相関: 一緒に動く、分散効果が低い")
elif corr < -0.3:
print("-> 負の相関: 自然なヘッジ、ポートフォリオに良い")
else:
print("-> 低相関: リスク分散に価値がある")分布仮定の危険性
正規分布仮定はあなたを殺す。 これはLTCMからの中核教訓。
紙の計算:
SPYが年率換算ボラティリティ20%を持つと仮定、日次ボラティリティは約1.26%(= 20% / sqrt(252))。
正規分布下では:
- 日次下落 > 1 sigma(1.26%)の確率 = 16%(通常)
- 日次下落 > 2 sigma(2.52%)の確率 = 2.3%(月に0.5回)
- 日次下落 > 3 sigma(3.78%)の確率 = 0.13%(3年に1回)
- 日次下落 > 4 sigma(5.04%)の確率 = 0.003%(125年に1回)
しかし実際に何が起こったのか?
| 日付 | イベント | SPY単日下落 | 正規分布による「何sigma」 | 理論的頻度 |
|---|---|---|---|---|
| 2020-03-16 | COVIDクラッシュ | -12.0% | 9.5 sigma | 10^20年 |
| 2008-10-15 | 金融危機 | -9.0% | 7.1 sigma | 10^12年 |
| 2011-08-08 | 米国格下げ | -6.7% | 5.3 sigma | 1,500万年 |
| 2018-02-05 | ボラティリティクラッシュ | -4.1% | 3.3 sigma | 4年 |
結論: 正規分布が「10^20年に1回」と言うイベントが実際には数年ごとに起こる。
これが「裾の重い分布」の力 - 極端なイベントは正規分布が予測するよりはるかに頻繁に発生する。リスクモデルが正規分布を仮定すると、ある「ブラックスワン」イベント時に必ず爆発する。
3.4 金融時系列の特殊な特性
裾の重さ
2つの重要な指標:
-
歪度: 分布は対称か?
- 歪度 = 0 -> 対称(上昇と下落の大きさが類似)
- 歪度 < 0 -> 左に歪んだ(暴落がラリーより多い)
- 株式市場の歪度は通常-0.5から-1、暴落リスクがラリー機会を超えることを示す
-
尖度: 裾はどれくらい「太い」か?
- 尖度 = 3 -> 正規分布
- 尖度 > 3 -> 裾が太い(正規分布が予測するより極端なイベントが多い)
- 株式市場の尖度は通常5-10、正規分布の3をはるかに超える
| 指標 | 正規分布 | SPY実際 | 意味 |
|---|---|---|---|
| 歪度 | 0 | -0.7 | 暴落がより深刻 |
| 尖度 | 3 | 8 | 頻繁な極端イベント |
裾の重さが戦略に与える影響:
- 損切りはギャップリスクを考慮する必要(一夜の暴落が損切り価格を飛び越える可能性)
- VaRモデルはリスクを深刻に過小評価する
- VaRの代わりにExpected Shortfall(CVaR)を使用する必要
コード実装(エンジニア向け)
def analyze_tail_risk(returns):
"""裾リスクを分析"""
skew = returns.skew() # 歪度
kurt = returns.kurtosis() # 尖度(超過)
print(f"歪度: {skew:.3f} {'(負の歪み、暴落に注意)' if skew < -0.5 else ''}")
print(f"尖度: {kurt+3:.3f} {'(裾が太い!)' if kurt > 0 else ''}")ボラティリティクラスタリング
GARCH効果: 大きなボラティリティは大きなボラティリティに続き、小さなボラティリティは小さなボラティリティに続く傾向。
直感的理解:
2020年3月のCOVIDクラッシュを思い出す:
- 3月9日: SPY下落7.6%
- 3月12日: SPY下落9.5%
- 3月16日: SPY下落12.0%
暴落は独立した「コイン投げ」ではない - 一度暴落が始まると、しばしば続く。これがボラティリティクラスタリング。
ボラティリティ自己相関は0.7-0.9と高い、リターン自己相関(約0)よりはるかに高い。これは:
- リターンはほぼ予測不可能(ランダムウォーク)
- ボラティリティは予測可能(明日のボラティリティは今日と類似する可能性が高い)
実用的な意味:
- ボラティリティ予測可能性 > リターン予測可能性
- Trend Agentは低ボラティリティ時にポジションを構築し、高ボラティリティ時に削減すべき
- Risk Agentは最近のボラティリティに基づいて損切りを動的に調整すべき
コード実装(エンジニア向け)
def detect_volatility_clustering(returns, window=20):
"""ボラティリティクラスタリングを検出"""
rolling_vol = returns.rolling(window).std()
vol_autocorr = rolling_vol.autocorr(lag=1)
print(f"ボラティリティラグ1自己相関: {vol_autocorr:.3f}")非定常性とRegimeシフト
市場は異なる「Regime」間を切り替え、それぞれ異なる統計的特性を持つ:
| Regime | 特性 | 適切な戦略 |
|---|---|---|
| 強気市場 | 低ボラティリティ、正のリターン、低相関 | モメンタム、買い持ち |
| 横ばい | 中ボラティリティ、リターン0付近 | 平均回帰 |
| 危機 | 高ボラティリティ、負のリターン、高相関 | ポジション削減、ヘッジ |
典型的な構造変化ケース(概算値):
注: 下表は典型的な規模を示す。具体的な値は方法論、ウィンドウ、データソースによって異なる。
| 時間 | イベント | ボラティリティ変化 | 相関変化 |
|---|---|---|---|
| 2008.09 | リーマン崩壊 | 15% -> 80% | 0.3 -> 0.95 |
| 2020.03 | COVID発生 | 12% -> 85% | 0.4 -> 0.90 |
| 2022.01 | Fed利上げ | 15% -> 30% | 0.5 -> 0.70 |
Regimeシフトが戦略に与える影響:
- 古いRegimeで訓練されたモデルは新しいRegimeで失敗する
- 相関急上昇は分散が突然失敗することを意味する
- ボラティリティ急上昇は損切りポジションの再計算が必要であることを意味する
これがマルチエージェントが必要な理由 - 異なるRegimeは異なる戦略が必要。1つのモデルではすべてを処理できない。Meta Agentの中核タスクは現在のRegimeを識別し、適切な専門Agentに割り当てること。
コード実装(エンジニア向け)
def detect_regime_change(returns, window=60):
"""シンプルなRegime変更検出"""
rolling_vol = returns.rolling(window).std()
vol_change = rolling_vol.diff().abs()
threshold = vol_change.quantile(0.95)
regime_changes = vol_change > threshold
print(f"{regime_changes.sum()}個の潜在的Regime変更ポイントを検出")
return regime_changes3.5 よくある誤解
誤解1: 正規分布が市場を適切に記述する
間違い。実際の市場は裾の重い分布を持つ。極端なイベントは正規分布が予測するよりはるかに頻繁に発生する。LTCMは正規分布を仮定し、「100万年に1回」のイベントが数週間以内に起こった。
誤解2: ボラティリティが低いほど常に安全
完全には正しくない。低ボラティリティは嵐の前の静けさかもしれない。より危険なのは、低ボラティリティ期間がしばしばレバレッジ増加と一致すること。ボラティリティが突然急上昇すると、損失が増幅される。
誤解3: 相関は安定して予測可能
危険な仮定。通常時に0.3の相関を持つ資産が危機時に0.9に急上昇することがある。分散が最も必要な時に失敗する可能性がある。
誤解4: 過去のデータが未来を予測できる
部分的にのみ正しい。市場はRegimeシフト(構造変化)を経験し、過去の統計パターンが突然無効になることがある。2008年と2020年の市場は以前とは完全に異なっていた。
3.6 マルチエージェントの視点
異なるAgentは異なる統計仮定を使用できる:
| Agent | 統計仮定 | 適用シナリオ |
|---|---|---|
| Trend Agent | モメンタム存在(正の自己相関) | トレンド市場 |
| Mean Reversion Agent | 平均回帰(負の自己相関) | 横ばい市場 |
| Risk Agent | 裾の重い分布 + ボラティリティクラスタリング | すべてのシナリオ |
| Regime Agent | 非定常 + 構造変化 | Regime識別 |
重要な洞察:
- すべてのAgentに同じ統計仮定を使わせない
- Risk Agentは最も保守的な仮定を使用する必要(裾が太い、頻繁な極端イベント)
- Meta Agentの責任は現在のRegimeを識別し、適切なAgentに割り当てること
レッスンの成果物
このレッスンを完了すると、以下を持つことになる:
- 金融データ特性の正しい理解 - なぜ「コイン投げ」モデルが市場で機能しないかを知る
- リターン計算能力 - 対数リターンと年率換算リターンの正しい計算を習得
- リスク測定の直感 - ボラティリティ、相関、裾の重い分布の実用的意味を理解する
- 統計仮定への警戒 - 正規分布仮定が致命的なエラーにどのようにつながるかを知る
検証チェックリスト
| チェックポイント | 検証基準 | 自己テスト方法 |
|---|---|---|
| リターン計算 | 対数リターンと年率換算リターンを手計算できる | $100->$110->$99が与えられた場合、2日間の対数リターンと累積を計算 |
| ボラティリティ計算 | 標準偏差を手計算し年率換算できる | 5つの日次リターンが与えられた場合、年率換算ボラティリティを計算 |
| 裾の重さ理解 | 正規分布仮定がなぜ危険かを説明できる | ノートなしで、LTCMの統計的失敗理由を説明 |
| Regime意識 | 3つの市場Regimeと対応する戦略をリストできる | Regime切り替え図を描き、各Regimeの特性をラベル付け |
総合演習(計算機で完了):
日中戦略:
- 10日間のリターン: +1%, -0.5%, +2%, -1%, +0.5%, -2%, +1.5%, 0%, -0.5%, +1%
- 計算: (1) 累積リターン (2) 日次ボラティリティ (3) 年率換算ボラティリティ (4) これは高または低ボラティリティ戦略か?
答えを表示するにはクリック
- 累積リターン(単純法) ~ 2.0%
- 日次ボラティリティ ~ 1.2%
- 年率換算ボラティリティ = 1.2% x sqrt(252) ~ 19%
- 年率換算ボラティリティ19%は中程度のボラティリティ、SPYのボラティリティレベルに近い
主要なポイント
- 金融データがIID仮定を満たさないことを理解する。自己相関と非定常性が存在する
- 対数リターン vs 単純リターンの違いと年率換算方法を習得する
- 正規分布仮定の危険性を認識する: 裾の重さ、ボラティリティクラスタリング、極端イベント
- Regimeシフトが戦略に与える影響と、マルチエージェントシステムがどのように応答するかを理解する
拡張読書
- 背景: AlphaとBeta - リターン分解の数学的基礎
- 背景: 有名なクオンツ災害 - 裾の重さを無視するコスト
- 背景: シャープレシオの統計的落とし穴 - 推定誤差、多重検定、Deflated Sharpe
次のレッスンプレビュー
レッスン 04: テクニカル指標の真の役割
MACD、RSI、Bollinger Bands... これらの指標は実際に有用か?答え: これらは「買い/売りシグナル」ではなく、特徴量エンジニアリングである。次のレッスンでテクニカル指標の真の性質を明らかにする。