まとめβ(ベータ)は「市場が 1% 動いたときにその銘柄が何 % 動くか」を表す市場感応度で、CAPM では \( r_i = \alpha + \beta \cdot r_m + \epsilon \) の傾きにあたります。全期間で 1 つの β を求めるのではなく、直近 120 営業日などの窓をずらしながら計算すると、銘柄の性格が時間とともに変わる様子を追えます。このページでは真の β を 1.2(AAA)と 0.7(BBB)に設定した 520 営業日の擬似リターンを作り、asset.rolling(window).apply の中で np.cov / np.var を計算する関数 rolling_beta() で推定値の推移を描きます。ウィンドウ長を 60・120・250 で比べると、短い窓ほど敏感で荒く、長い窓ほど滑らかで遅れるという取引関係が図で確認できます。
直感
#
市場全体(例えば S&P 500)が 1% 上がった日に、ある銘柄が平均して 1.2% 上がるなら β は 1.2 で、市場より振れ幅の大きい「アグレッシブ」な銘柄です。0.7 なら市場の 7 割しか動かない「ディフェンシブ」な銘柄になります。β は散布図に引いた回帰直線の傾きで、計算上は共分散を市場の分散で割った \( \text{Cov}(r_i, r_m) / \text{Var}(r_m) \) です。ただし企業の事業内容や相場環境が変われば β も変わるので、過去 2 年を 1 つの値にまとめるより、直近 120 日の窓を 1 日ずつずらして計算し続けるほうが「今の β」に近い値が得られます。
市場リターンと銘柄リターンの日次系列→
直近 window 日を切り出す→
Cov(銘柄, 市場) / Var(市場)→
1 日ずらして繰り返す→
β の時系列→
β = 1 の線と比べる
何が起きているか(ステップで見る)
#
コードを読む前に、この手法が図の上で何をしているかを追ってみましょう。「再生」を押すか、スライダーを動かすと 1 手順ずつ進みます。
見るポイント:r は「直線に沿う強さ」。曲線的な関係や外れ値があると、r だけでは関係を見誤る。β はこの直線の「傾き」で、r が同じでも銘柄の振れ幅が大きければ β は大きくなります。
擬似データの生成
#
真の β が分かっているデータで試すために、市場リターンに β を掛けてノイズを足した 2 銘柄を作ります。推定した β が 1.2 と 0.7 に近づけば、関数が正しく動いている確認になります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pathlib import Path
np.random.seed(7)
plt.style.use("scripts/k_dm.mplstyle")
dates = pd.date_range("2022-01-01", periods=520, freq="B")
market_returns = np.random.normal(0.0004, 0.01, size=len(dates))
signals = {}
for ticker, beta, vol in [
("AAA", 1.2, 0.012),
("BBB", 0.7, 0.009),
]:
residual = np.random.normal(0, vol, size=len(dates))
signals[ticker] = beta * market_returns + residual
returns = pd.DataFrame({"market": market_returns, **signals}, index=dates)
|
ローリング β を計算する関数
#
窓の中の共分散と分散の比として \( \beta = \text{Cov}(r_i, r_m) / \text{Var}(r_m) \) を求め、rolling().apply で窓を 1 日ずつずらします。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| def rolling_beta(asset: pd.Series, benchmark: pd.Series, window: int = 120) -> pd.Series:
def _calc(x: pd.Series) -> float:
ref = benchmark.loc[x.index]
cov = np.cov(x, ref)[0, 1]
var = np.var(ref)
return cov / var if var > 0 else np.nan
return asset.rolling(window).apply(_calc, raw=False)
betas = pd.DataFrame(
{
ticker: rolling_beta(returns[ticker], returns["market"])
for ticker in ["AAA", "BBB"]
}
)
|
β の推移を可視化
#
2 銘柄の β を折れ線で描き、β = 1 の基準線と比べます。
1
2
3
4
5
6
7
8
9
10
11
12
| fig, ax = plt.subplots(figsize=(9, 4.2))
betas.plot(ax=ax, linewidth=1.6)
ax.axhline(1.0, color="#ef4444", linestyle="--", linewidth=1, label="β=1")
ax.set_title("120営業日ローリングβ(サンプルデータ)")
ax.set_ylabel("β")
ax.legend()
ax.grid(alpha=0.3)
output = Path("static/images/finance/main/rolling_beta_trend.svg")
output.parent.mkdir(parents=True, exist_ok=True)
fig.tight_layout()
fig.savefig(output)
|

実行結果の読み方
#
AAA の β は 1.0〜1.4 の間を揺れながら 1.2 の近くに、BBB は 0.5〜0.9 の間で 0.7 の近くに留まります。真の β は一定なのに線が揺れているのは、ノイズ residual が窓の中で偶然偏るためで、この揺れ幅がローリング β を読むときの「誤差の目安」になります。2 本の線が交わる場面が無いことから、120 日の窓なら β 1.2 と 0.7 の銘柄は確実に区別できると分かります。
ウィンドウ長の感度分析
#
窓を 60 日に短くすると β の変化がすぐ反映される一方でノイズも増え、250 日に長くすると滑らかになる代わりに構造変化への反応が遅れます。AAA について 3 つの窓幅を重ねて比べます。
1
2
3
4
5
6
7
8
9
10
11
12
13
| fig, ax = plt.subplots(figsize=(9, 4.2))
for w, color, ls in [(60, "#0ea5e9", "-"), (120, "#6366f1", "-"), (250, "#f97316", "--")]:
rb = rolling_beta(returns["AAA"], returns["market"], window=w)
ax.plot(rb.index, rb, linewidth=1.4, color=color, linestyle=ls, label=f"w={w}")
ax.axhline(1.0, color="#ef4444", linestyle="--", linewidth=0.8, alpha=0.5)
ax.set_title("AAA: ウィンドウ長によるローリングβの違い")
ax.set_ylabel("β")
ax.legend()
ax.grid(alpha=0.3)
fig.tight_layout()
plt.show()
|
実行結果の読み方
#
w=60 の線は 0.9〜1.5 まで大きく揺れ、w=250 の線は 1.1〜1.3 の狭い範囲に収まります。真の β が 1.2 で一定の擬似データでは長い窓のほうが正解に近く見えますが、実データで β が本当に変化したときは、長い窓はその変化を 250 日かけてゆっくり反映するので、どちらが良いかは「β の変化を捉えたいか、ノイズを抑えたいか」で決まります。
β の要約統計量
#
期間全体の β の平均・標準偏差を出し、データ生成時に設定した真の β(1.2 と 0.7)との乖離を確かめます。
1
2
3
4
5
| summary = betas.dropna().describe().round(3)
print(summary)
print()
print("設定値: AAA=1.2, BBB=0.7")
print(f"推定平均: AAA={betas['AAA'].mean():.3f}, BBB={betas['BBB'].mean():.3f}")
|
実務でのポイント
#
窓幅やリターンの頻度(日次か週次か)は資産ごとに調整し、ボラティリティの高い資産は 250 日などの長い窓で平滑化すると β が安定します。ベンチマークは株価指数 1 本に限らず、金利やコモディティのリターンを並べて np.linalg.lstsq で同時に回帰すればマルチファクターモデルに拡張できます。ローリング β が 1.5 を超えたらポジションを縮小するといった閾値ルールに組み込むと、市場感応度の管理を自動化できます。β の急変動は決算発表や地政学イベントと重なることが多いので、イベントの日付と突き合わせると「なぜ変わったか」の解釈が深まります。
まとめ
#
ローリング β は、直近 window 日の \( \text{Cov}(r_i, r_m) / \text{Var}(r_m) \) を rolling(window).apply(_calc, raw=False) で 1 日ずつずらして計算した時系列です。真の β を 1.2 と 0.7 に設定した擬似データでは、120 日窓の推定平均が設定値にほぼ一致し、揺れ幅は標準偏差 0.1 前後に収まりました。窓を 60 日にすると敏感だが荒く、250 日にすると滑らかだが遅れるので、β の変化を捉えたいのかノイズを抑えたいのかを決めてから窓幅を選びます。