先月、製造メーカーの決算ラッシュで「この銘柄、安くなってる気がするんだけど割安なのかどうか全然わからない」という状況が続いた。毎回証券会社のサイトでPBRを手動で調べていて、正直しんどかった。。。どうせPythonで株価取得してるなら、ファンダメンタルズ指標のスクリーニングも自動化しようと思い立った。
なぜPBR・PERなのか
PBR(株価純資産倍率)とPER(株価収益率)は、株が「割安かどうか」を測る基本指標だ。製造メーカーは設備・在庫などの資産が多く、PBRが比較的低い傾向がある。東証が「PBR1倍割れ企業への改善要請」を出してから、PBR1倍以下の銘柄が市場で注目されるようになった。
PBR = 株価 ÷ 1株純資産(BPS)
PER = 株価 ÷ 1株利益(EPS)
一般的に、PBR1倍以下は「解散価値以下」、PER15倍以下は「利益に対して安い」と判断されることが多い。ただしセクターや成長率によって基準は変わるので、あくまでスクリーニングの入口として使う。
yfinanceでファンダメンタルズデータを取得する
yfinanceのTicker.infoには財務指標が含まれている。ただし日本株の場合、データが取得できない銘柄や古いデータが入っていることがあるので注意が必要だ。
import yfinance as yf
import pandas as pd
import time
# 製造メーカー銘柄リスト(例:トヨタ、デンソー、ファナック、小松製作所、ホンダ等)
tickers_jp = [
"7203.T", # トヨタ自動車
"6902.T", # デンソー
"6954.T", # ファナック
"6301.T", # 小松製作所
"7267.T", # ホンダ
"6752.T", # パナソニック
"6501.T", # 日立製作所
"7011.T", # 三菱重工
"6326.T", # クボタ
"7013.T", # IHI
"6103.T", # オークマ
"6113.T", # アマダ
]
def get_fundamentals(ticker_symbol):
"""yfinanceから財務指標を取得する"""
try:
t = yf.Ticker(ticker_symbol)
info = t.info
return {
"ticker": ticker_symbol,
"name": info.get("longName", info.get("shortName", "")),
"price": info.get("currentPrice", info.get("regularMarketPrice")),
"pbr": info.get("priceToBook"),
"per": info.get("trailingPE"),
"forward_per": info.get("forwardPE"),
"dividend_yield": info.get("dividendYield"),
"market_cap": info.get("marketCap"),
"roe": info.get("returnOnEquity"),
}
except Exception as e:
print(f" エラー {ticker_symbol}: {e}")
return None
# データ取得(レート制限対策で0.5秒待機)
records = []
for t in tickers_jp:
print(f"取得中: {t}")
data = get_fundamentals(t)
if data:
records.append(data)
time.sleep(0.5)
df = pd.DataFrame(records)
print(df[["ticker", "name", "price", "pbr", "per"]].to_string(index=False))
スクリーニング条件を適用する
データが揃ったら条件フィルタを掛ける。PBRとPERはfloatで取得されるが、欠損(None/NaN)があるので dropna() を先にやる。
df_screen = df.dropna(subset=["pbr", "per"]).copy()
# 条件定義
PBR_MAX = 1.5 # PBR 1.5倍以下(1.0倍以下に絞るとかなり厳しいので少し緩めた)
PER_MAX = 15.0 # PER 15倍以下
ROE_MIN = 0.05 # ROE 5%以上(収益性を担保するため)
mask = (
(df_screen["pbr"] <= PBR_MAX) &
(df_screen["per"] <= PER_MAX) &
(df_screen["roe"] >= ROE_MIN)
)
result = df_screen[mask].sort_values("pbr").reset_index(drop=True)
# 表示フォーマット整理
result["pbr"] = result["pbr"].map("{:.2f}".format)
result["per"] = result["per"].map("{:.1f}".format)
result["roe"] = result["roe"].map("{:.1%}".format)
result["dividend_yield"] = result["dividend_yield"].map(
lambda x: f"{x:.2%}" if pd.notna(x) else "-"
)
print(f"\n=== スクリーニング結果(PBR≤{PBR_MAX}, PER≤{PER_MAX}, ROE≥{ROE_MIN:.0%})===")
print(result[["ticker", "name", "price", "pbr", "per", "roe", "dividend_yield"]].to_string(index=False))
結果をCSVに保存して毎朝自動実行する
スクリーニング結果を日付付きCSVに保存するようにして、Windowsのタスクスケジューラで毎朝8時に実行する設定にした。
from datetime import date
# CSVに保存
output_path = f"screening_{date.today().strftime('%Y%m%d')}.csv"
result.to_csv(output_path, index=False, encoding="utf-8-sig")
print(f"結果を保存しました: {output_path}")
# ===== タスクスケジューラ設定(参考・Windowsのみ) =====
# schtasks /create /tn "StockScreening" /tr "python C:\scripts\screening.py"
# /sc DAILY /st 08:00 /f
スクリーニング結果をチャートで確認する
PBRとPERの散布図にプロットすると、どの銘柄がどのゾーンにいるか一目でわかる。
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(10, 7))
fig.patch.set_facecolor("#0d1117")
ax.set_facecolor("#0d1117")
# 全銘柄をプロット
df_plot = df_screen.copy()
df_plot["pbr"] = pd.to_numeric(df_plot["pbr"], errors="coerce")
df_plot["per"] = pd.to_numeric(df_plot["per"], errors="coerce")
ax.scatter(df_plot["per"], df_plot["pbr"],
color="#38bdf8", alpha=0.6, s=80, zorder=3)
# 割安ゾーンを強調
ax.axvline(PER_MAX, color="#f87171", linewidth=0.8, linestyle="--", alpha=0.7)
ax.axhline(PBR_MAX, color="#f87171", linewidth=0.8, linestyle="--", alpha=0.7)
ax.fill_between([0, PER_MAX], [0, 0], [PBR_MAX, PBR_MAX],
color="#34d399", alpha=0.08, label="割安ゾーン")
# ティッカーラベル
for _, row in df_plot.dropna(subset=["per","pbr"]).iterrows():
ax.annotate(row["ticker"].replace(".T",""),
(row["per"], row["pbr"]),
textcoords="offset points", xytext=(6, 4),
fontsize=8, color="#8b949e")
ax.set_xlabel("PER(倍)", color="#8b949e")
ax.set_ylabel("PBR(倍)", color="#8b949e")
ax.set_title("製造メーカー銘柄 PBR × PER マップ", color="#f0f6fc", fontsize=13)
ax.tick_params(colors="#8b949e")
ax.spines[:].set_color("#30363d")
ax.legend(facecolor="#161b22", labelcolor="#e6edf3")
plt.tight_layout()
plt.savefig("pbr_per_map.png", dpi=150, facecolor="#0d1117")
plt.show()
使ってみてわかった注意点
yfinanceの財務データは証券会社のデータと若干ずれることがある。特に日本株は決算発表直後に数値が更新されないことがあった。「PERが異常に低い」と思って調べたら前期の利益が使われていた、ということが実際にあった。。。スクリーニング結果はあくまで「候補リスト」として扱い、最終確認は証券会社や決算短信で行うのがマストだ。
あとPBR1倍以下でもROEが低い(利益をほとんど生んでいない)銘柄は罠になりやすい。だからROEの足切り条件(5%以上)を入れた。これだけで候補数がぐっと減る。
まとめ
yfinanceのTicker.infoを使えば、PBR・PER・ROEなどのファンダメンタルズ指標を一括取得してスクリーニングできる。製造メーカー株のような「資産が厚い・PBRが低め」な銘柄を探すのにうってつけの手法だ。個人的には毎朝の自動スクリーニングを実装してから、手動調査の時間がかなり減った。次はJ-Quantsの決算データと組み合わせて、より精度の高いスクリーニングに発展させたいと考えている。

