「PBR1倍割れ銘柄を買えばいい」とか「ROEが高い株は上がる」とか、ネットで拾ってきたスクリーニング条件をそのまま使っていた時期があった。でも冷静に考えると、そのファクターが本当に効いてるのか確かめたことが一度もない。子供が寝たあとにふとそれが気になって調べ始めたのが alphalens-reloaded だ。ファクターの有効性を定量的に測れるツールで、プロのクオンツが使うやつらしい。
alphalens-reloadedとは
もともとQuantopianが開発した alphalens を、現在もメンテされているのが alphalens-reloaded だ。ファクター値(例:PBR)と将来リターンの関係を可視化・数値化してくれる。主な指標は次の2つ。
① IC(Information Coefficient、情報係数):ファクター値と翌日・翌週リターンのスピアマン相関係数。+1で完璧な予測、0なら無意味。実用的には平均ICが0.05以上あると「まあ効いてる」とされる。
② クワンタイルリターン:銘柄をファクター値で5等分(Q1〜Q5)し、各グループの平均リターンを比べる。Q5(ファクター上位)がQ1(下位)より高ければ、そのファクターは有効といえる。
インストール
pip install alphalens-reloaded yfinance pandas numpy
データ準備:ファクター値と価格データ
今回は「移動平均乖離率」をファクターとして使う。PBRやROEは財務データAPIが必要で手間がかかるので、まず手に入りやすい価格ベースのファクターで動かし方を覚えるのがおすすめだ。
import yfinance as yf
import pandas as pd
import numpy as np
import alphalens
# 分析対象:日本の製造業銘柄10社
tickers_raw = [
"7203.T", # トヨタ
"7267.T", # ホンダ
"7011.T", # 三菱重工
"6301.T", # 小松製作所
"6326.T", # クボタ
"7269.T", # スズキ
"7270.T", # SUBARU
"7261.T", # マツダ
"6702.T", # 富士通
"6501.T", # 日立
]
# 2年分の終値取得
raw = yf.download(tickers_raw, period="2y", auto_adjust=True)["Close"]
raw.columns = [t.replace(".T", "") for t in tickers_raw]
raw = raw.dropna(how="all")
print(raw.shape)
ファクター値を計算する(25日移動平均乖離率)
# 25日移動平均からの乖離率を計算
ma25 = raw.rolling(25).mean()
factor_raw = (raw - ma25) / ma25 # 乖離率(プラスなら割高)
# alphalensが期待するフォーマット: MultiIndex (date, asset) の Series
factor_df = factor_raw.stack()
factor_df.index.names = ["date", "asset"]
factor_df.name = "ma25_deviation"
factor_df = factor_df.dropna()
print(factor_df.head())
alphalensに渡す価格データのフォーマット
# 価格データもMultiIndex形式に
prices = raw.copy()
prices.index = pd.to_datetime(prices.index)
# alphalens.utils.get_clean_factor_and_forward_returns でまとめて計算
factor_data = alphalens.utils.get_clean_factor_and_forward_returns(
factor=factor_df,
prices=prices,
quantiles=5, # Q1〜Q5の5グループ
periods=(1, 5, 10), # 1日後・5日後・10日後リターン
max_loss=0.35 # データ欠損が35%以上の銘柄を除外
)
print(factor_data.head())
ICの計算と可視化
import matplotlib.pyplot as plt
# IC の計算
ic = alphalens.performance.factor_information_coefficient(factor_data)
# 移動平均でトレンドを見やすくする
ic_ma = ic.rolling(20).mean()
fig, ax = plt.subplots(figsize=(12, 4))
ax.bar(ic.index, ic["1D"], color="#38bdf8", alpha=0.5, label="IC (1D)")
ax.plot(ic_ma.index, ic_ma["1D"], color="white", linewidth=1.5, label="IC 20MA")
ax.axhline(0, color="gray", linewidth=0.8)
ax.set_title("移動平均乖離率ファクター IC(1日後リターン)")
ax.set_ylabel("IC")
ax.legend()
plt.tight_layout()
plt.savefig("factor_ic.png", dpi=150)
plt.show()
mean_ic = ic["1D"].mean()
ic_ir = ic["1D"].mean() / ic["1D"].std() # ICIR(IC安定性の指標)
print(f"平均IC: {mean_ic:.4f}")
print(f"ICIR: {ic_ir:.4f}")
クワンタイルリターンでファクターの有効性を確認
# クワンタイル別平均リターン
mean_ret_by_q = alphalens.performance.mean_return_by_quantile(factor_data)
mean_ret_1d = mean_ret_by_q[0]["1D"] * 100 # %表示
mean_ret_1d.plot(
kind="bar",
color="#38bdf8",
figsize=(8, 4),
title="クワンタイル別平均1日リターン(移動平均乖離率ファクター)",
ylabel="平均リターン (%)"
)
plt.xticks(rotation=0)
plt.tight_layout()
plt.savefig("quantile_returns.png", dpi=150)
plt.show()
「Q5(乖離率が高い=割高)のリターンがQ1より低い」なら逆張りファクターとして有効、逆ならモメンタムファクターとして有効と判断できる。実際に試したところ、短期(1日後)では「乖離率が低い方がリターンが高い」逆張り効果が弱く出ていた。
ティアシートで一括確認
# フルのティアシート(IC・クワンタイルリターン・ターンオーバーなど一括表示)
alphalens.tears.create_full_tear_sheet(
factor_data,
long_short=True, # ロングショート前提で計算
group_neutral=False
)
create_full_tear_sheet を呼ぶだけで、IC時系列・クワンタイルリターン・ターンオーバー分析がまとめて出力される。Jupyter Notebook上で実行すると見やすい。
まとめ
alphalens-reloaded を使えば「このスクリーニング条件、本当に意味あるの?」という疑問を数字で確かめられる。感覚で選んでいたファクターにエビデンスが出るのは気持ちいい。個人的には、次はPBR(割安度)ファクターをjquants APIのデータで計算して、移動平均乖離率と比較してみたいと思っている。

