【完結編】10分の壁を突破せよ!全4,400銘柄の財務データ・価格・順位付けを 9分台で完遂する Turbo パイプラインの全貌
をアップしました。合わせて、閲覧いただけると幸いです。
1. はじめに
想定読者
- Pythonで日本株のスクリーニングツールを自作したい個人開発者
- 無料データソース(
yfinanceなど)のNoneやNaNに悩まされている方 - 実運用で「429 Too Many Requests」やデータ不整合に直面し、処理が止まってしまう方
この記事のゴール
yfinance は非常に便利ですが、日本株データ(特に中小型株)は欠損が多く、そのままでは実用的なスクリーニングが困難です。
本記事では、データ欠損を自動で補完・算出し、実運用に耐えうるデータ品質を確保するための 「自己修復(Self-Healing)」アーキテクチャ を解説します。
2. 準備:キャッシュによる高速化と負荷軽減
スクリーニングで数千銘柄にアクセスする場合、毎回サーバーにリクエストを送るのは非効率であり、API制限(429エラー)の原因になります。
requests-cache を使い、ローカルにデータをキャッシュすることで、2回目以降の実行を爆速化します。
pip install yfinance tenacity pandas requests-cache
import yfinance as yf
from requests_cache import CachedSession
# キャッシュセッションの作成
# expire_after: キャッシュの有効期限(秒)。日次データなら数時間〜1日でOK
session = CachedSession('yfinance_cache', expire_after=3600 * 12)
# 使い方: Ticker初期化時に session を渡す
# ticker = yf.Ticker("7203.T", session=session)
3. 基礎:安定した取得のためのリトライとジッター
API取得の基本は、エラーを前提とした設計です。特に短時間の大量アクセス時には API レートリミットに抵触しやすいため、tenacity を使い、スパイクを避けるための「ジッター(揺らぎ)」を加えたリトライ処理を実装します。
import random
from tenacity import retry, stop_after_attempt, wait_exponential, wait_random
@retry(
stop=stop_after_attempt(3),
# 2〜10秒の指数バックオフに ±1秒のランダムな揺らぎを加える
wait=wait_exponential(multiplier=1, min=2, max=10) + wait_random(0, 1),
reraise=True
)
def fetch_info_safe(ticker):
"""yfinance info取得 (ジッター付きリトライ)"""
try:
return ticker.info
except Exception as e:
print(f"Error fetching info: {e}")
return {}
[!TIP]
実装上の注意: ジッターを加えることで、複数スレッドからのリクエストが同時に再試行される「サンダリング・ハード(Thundering Herd)」問題を軽減できます。
4. 実装の要:安全な数値変換ユーティリティ
データの型が int, float, str, None と不安定なため、変換時に落ちないための小さなガードを用意しておくのがコツです。
def safe_float(x, default=None):
"""強固な数値変換・型チェック"""
try:
if x is None: return default
if isinstance(x, str):
x = x.replace(',', '').replace('%', '').strip()
val = float(x)
# 極端な外れ値(1兆倍など)やNaNをガード
if val != val or abs(val) > 1e15: return default
return val
except (ValueError, TypeError):
return default
5. 自己修復ロジック1:D/E比からの高速推定 (Fast Estimation)
info['equityRatio'](自己資本比率)が欠損していても、info['debtToEquity'](負債資本倍率)が取れている場合があります。この場合、以下の式で自己資本比率を逆算します。
$$自己資本比率 \approx \frac{1}{1 + \frac{D/E}{100}} \times 100$$
ここで注意が必要なのは、D/E比の単位です。データソースによっては「%表記(例: 150.0)」と「倍率表記(例: 1.5)」が混在することがあります。
def repair_equity_ratio(info):
"""D/E比から自己資本比率を推定"""
de = safe_float(info.get("debtToEquity"))
if de is not None and de >= 0:
# 簡易判定: 10以上ならパーセント表記とみなして100で割る
# (D/Eが10倍=1000%を超える企業は稀なため)
if de > 10.0:
de = de / 100.0
return (1 / (1 + de)) * 100
return None
6. 自己修復ロジック2:財務諸表からの精密修復 (Deep Repair)
PER (trailingPE) が取れない場合、より低速ですが確実な 財務三表(financials / balance_sheet) を直接参照します。
実行コストとトレードオフ
財務三表の取得は info よりも通信量が多く、実行時間が数秒伸びます。
[!IMPORTANT]
運用のコツ: 全銘柄に対して一律で Deep Repair を実行すると、処理時間が膨大になるだけでなく、確実に Rate Limit (429) に抵触します。
if per is None:のように「本当に必要な場合のみ」発動させるガード句が必須です。
実装例
まず、複数候補のキーからデータを安全に抽出するユーティリティを用意します。
import pandas as pd
def get_value_from_df(df, keys, default=None):
"""DataFrameの行(index)から複数の候補キーで値を探索"""
if df is None or df.empty:
return default
for k in keys:
if k in df.index:
# 最新決算期の値を取得(列の先頭)
series = df.loc[k]
val = series.iloc[0] if hasattr(series, 'iloc') else series
return val if val == val else default # NaNチェック
return default
これを利用して、PERを精密に修復します。ここで stock.financials が空のDataFrameを返すケースもケアします。
import logging
def repair_per_deep(stock, info):
per = safe_float(info.get("trailingPE"))
price = safe_float(info.get("currentPrice"))
# PERが欠損しており、かつ現在値がある場合のみ、重い処理(Deep Repair)を実行
if per is None and price:
try:
# プロパティアクセス時に通信が発生するためtry-catch必須
inc = stock.financials
bs = stock.balance_sheet
# DataFrameが空でないかチェック
if inc is not None and not inc.empty and bs is not None and not bs.empty:
# フィールド名の揺れ("Net Income" vs "NetIncome"等)を吸収
net_income = get_value_from_df(inc, ["Net Income", "NetIncome", "Net Income Loss"])
shares = get_value_from_df(bs, ["Ordinary Shares Number", "Shares Issued", "Share Issued"])
if net_income and shares and shares > 0:
eps = net_income / shares
if eps > 0:
per = price / eps
logging.info(f"Deep Repair Success: Calculated PER {per:.2f}")
except Exception as e:
logging.warning(f"Deep repair failed for {stock.ticker}: {e}")
return per
7. 修復による効果:Before / After
このロジックにより「判定不能」だった有望銘柄がどのように救済されるかの視覚的イメージです。
| 項目 | 修復前(取得不可) | 修復後(救済成功) | 修復の手段 |
|---|---|---|---|
| 銘柄A (中小型) | PER: None
|
PER: 12.4 | FinancialsからEPSを算出 |
| 銘柄B (新興) | 自己資本比率: None
|
自己資本比率: 45.2% | D/E比から逆算 |
| 銘柄C (優待株) | 配当利回り: None
|
利回り: 3.2% |
dividendRate / price で再計算 |
8. まとめと法務上の注意点
まとめ
- APIは万能ではない: 欠損を前提とした「粘り強い」ロジックこそがツールの価値を決める。
-
階層型アプローチ:
info(高速) → 推定 (中速) → 財務三表 (低速) の順で呼び出しを最適化する。 -
キャッシュの活用:
requests-cacheを挟むだけで、開発効率と安定性が劇的に向上する。
データの取り扱いと法務について
-
日本株の指定:
yfinanceで日本株を取得する際は、ティッカーシンボルに.Tを付ける必要があります(例: トヨタ自動車 →7203.T)。 -
利用規約の遵守:
yfinanceは Yahoo Finance の非公式APIを利用しています。データの商用利用や再配布については、Yahooの規約および各証券取引所の規定を必ず確認してください。 - 一次ソースの重要性: 自動投資判断を行う場合、最終的な確認は EDINET や適時開示情報(TDnet)などの一次ソース、あるいは信頼性の高い有料データ提供サービスを併用することを強く推奨します。
おわりに
これで、どれだけスクレイピングしても(ほぼ)止まらない堅牢なスクリーナーの基礎が完成しました。
あとは好みの指標でフィルタリングするだけです。楽しい投資開発ライフを!
注意: 投資は自己責任です。本記事のプログラムは投資助言ではありません。
続編 【yfinace】欠損の多い RSI をPandas MultiIndex で 4,000 銘柄をだいたい2分で高速復旧するを投稿しました。併せてご覧いただけると幸いです
第三弾手続き」から「宣言」へ。Polars Expression で 4,000 銘柄を 0.03 秒で「捌く」設計の妙を投稿しました
本記事は制御可能性中心設計(Controllability-Centered Design)シリーズ Layer 4 の実践編です