【2026年9月追記】半年運用して判明した落とし穴と再設計について
本記事の「Turboパイプライン(9分台)」を半年ほど日次運用したところ、EDINETの訂正有報(書類種別130)を取りこぼしていたことと、yfinanceのレート制限(HTTP 429)が頻発したことが判明しました。
速度優先の設計を見直し、データの整合性と耐障害性を優先したアーキテクチャに作り直した過程を2本にまとめています。本記事のコードの「盲点」とその修正方法に興味がある方はぜひご覧ください。
TL;DR
- 課題: 365日分の EDINET XBRL 取得における I/O ボトルネックの解消(推定 6 時間の短縮)
- 解決: 並列スキャン & 非対称ダウンロード・パースによる「Turbo パイプライン」の実装
- 成果: バックフィル 7分42秒、週次分析 91秒 という高いパフォーマンスを実現
はじめに
これまで本連載では、個人投資家エンジニアの武器となる「止まらない・超高速なスクリーナー」を目指し、以下の技術的挑戦と知見を公開してきました。
- Python + yfinance で日本株分析:データ欠損・429エラーを「自己修復」して止まらないスクリーナーを作る
- 欠損の多いyfinanceの RSI をPandas MultiIndex で 4,000 銘柄をだいたい2分で高速復旧する
- 「手続き」から「宣言」へ。Polars Expression で 4,000 銘柄を 0.03 秒で「捌く」設計の妙
この記事は、連載の「総仕上げ」として、データの完全性と処理性能の双方を追求した実装の詳細 を解説します。当初、365日分のデータ取得において大きな課題となっていた順次処理のボトルネックを、いかにして最適化し、信頼性と速度を両立させたか、その技術的なアプローチを公開します。
💡 今回の戦略:適材適所のハイブリッド運用
- 財務データ(EDINET): 1円単位の正確さが求められるファンダメンタル指標は、公式ソースから自前で取得・検証
- マーケットデータ(yfinance): リアルタイム性や簡便さが重要な株価・チャート情報は、既存 API を賢く活用
この「公式の堅牢さ」と「API の機動力」を組み合わせたハイブリッド構成が、最強の武器となります。
1. EDINET とは?:日本市場の「公式データベース」
技術的な解説に入る前に、本パイプラインの心臓部である EDINET について整理しておきます。
- 正式名称: Electronic Disclosure for Investors' NETwork(EDINET公式サイト)
- 運営: 金融庁
- 役割: 金融商品取引法に基づく有価証券報告書等の提出・閲覧システム
- 最大の特徴: 全ての上場企業が、同一の規則(XBRL形式)で正確な財務数値を公開する**「日本市場の一次情報ソース」**です
多くの投資サイトや API が二次的な提供(転載)であるのに対し、EDINET は「官報」に近い立場であり、1円単位での正確性と、提出直後の速報性が保証されています。これを直接プログラムで叩くことは、投資家エンジニアとして究極の優位性を持つことを意味します。
2. なぜ「全銘柄 365日分」のスキャンが必要なのか
ここまでの高速化に挑んだ最大の理由は、「公式ソースによる完全な財務データベース」 を自前で持ちたかったからです。なぜ「365日」という期間にこだわったのか、それには明確な理論的根拠があります。
- 「絶対に逃さない」1枚の有報: 上場企業には 1 年に一度、必ず「有価証券報告書(本決算)」を提出する義務があります。365 日分を漏らさずスキャンすれば、どんなに変則決算や提出遅延があっても、全上場企業 3,900 社以上の「最新の本決算」を 100% 確実に捕捉 できます
- 再生可能性(Recovery by Speed)への渇望: GitHub Actions や Google Colab といったエフェメラル(一時的)なクラウド環境では、DB の消失や破損は常に隣り合わせのリスクです。しかし、リスクレベルが同等な場所にバックアップ(DBのスナップショット等)を保持し続けるより、「原本(EDINET)からいつでも、誰よりも速く最新状態を復元できる力」 を持つ方が、システムの堅牢性と鮮度において圧倒的に優位であると考えたからです
- yfinance の限界: yfinance は便利ですが、日本株の財務データにおいては「最新の一部」しか取れない、あるいは更新が遅延することがあります。「昨日提出されたばかりの有報」を即座に計算へ反映させるには、EDINET を自前で逐次スキャンするのが唯一の正解でした
- 比較の公平性: 全銘柄の「確定した通期データ」を 1 円単位で揃えることで初めて、PER や ROE といった指標を「同じ土俵(確定値)」で比較できる、最強のスクリーナーが誕生します
3. yfinance の「手軽さ」と「危うさ」
株価取得でおなじみの yfinance は、財務データも取れます。
-
メリット:
ticker.info一発で取れる -
デメリット:
- 日本株の連結・単独が混在したり、米国基準への変換で数値がズレることがある
- エンジニアリングの壁(逐次処理): 株価(マーケットデータ)は複数銘柄を一括ダウンロード(バッチ取得)できますが、財務データ(インフォメーション)は 1銘柄ずつ個別にリクエスト を投げる必要があります。4,000銘柄を愚直に回すと、API レート制限に即座に抵抵し、絶望的な待ち時間が発生します
これに対し、公式ソースである EDINET は、その設計思想からして対照的です。
- メリット(日付ベースの横断スキャン): EDINET API は「全銘柄がその日に提出した書類リスト」を一発で返してくれます。yfinance で全銘柄(約 4,000 社)の更新を確認するには 4,000 回の API 発行 が必要ですが、EDINET なら過去 1 年分を遡っても わずか 365 回のリクエスト で完遂できます。このリクエスト数の圧倒的な差(1/10 以下)が、スキャンの実行効率を劇的に向上させます
「より正確な確定値が、より高い並列性で欲しい」―― その答えは公式ソースの EDINET にありました。
4. 戦略的バックフィル:20GB の巨大データをどう回避するか?
EDINET の全書類データを「元帳(バルク)」で落とそうとすると、そのサイズは 20GB を軽く超えます。個人開発のストレージやネットワークでこれを毎回事実上のフルバックアップするのは非効率の極みです。
ここで採用したのが 「365日分の差分スキャン」 という戦略です。
- 大前提: 上場企業は年に一度、必ず「有価証券報告書(本決算)」を提出します
- 戦略: EDINET API v2 で過去 365 日分の「差分リスト」を取得し、そこから銘柄ごとに最新の本決算 1 枚を抽出して積み上げれば、最小限の通信量で、全銘柄の財務データを 100% 網羅 できます
「量で圧倒する」のではなく「期間で絞り、質(本決算)で抜く」。これが Turbo エンジンの設計思想です。
5. データの収集からランキング確定までの全体フロー
テクニカルな詳細に入る前に、システム全体がどのようにデータを統合し、最終的な投資判断(順位)を下しているか、その「大きな流れ」を可視化します。
このフローは、以下のタイミングで実行されることを想定しています。
- Phase 0(初期構築・復旧): 初回のみ(または DB 破損・環境移行時の「破壊からの復旧」時)
- Phase 1(日次同期・更新): 1 日 1 回。当日の提出書類を差分スキャンし、最新状態を維持します
- Phase 2(分析・評価): 任意のタイミング。最新の財務・市場データに基づき、いつでも最新のランキングを出力できます
この「公式情報の堅牢さ」を基盤に、「API の機動力」と「AI のインテリジェンス」を積み上げる多層構造が、分析の精度と速度を両立させています。
6. Turbo パイプラインの全体像(シーケンス図)
この「非対称かつロックフリー」な並列処理フローを視覚化すると、以下のようになります。ダウンロード(I/O)の待ち時間を、高負荷なパース処理(CPU)で埋める設計が要です。
7. 「Turbo エンジン」を実現する4つの技術的アプローチ
このパイプラインを支える具体的な実装と、その設計判断について解説します。
① 非対称パイプライン (Asymmetric Concurrency)
ネットワーク負荷(API制限)を考慮し、「ダウンロードは 2 並列(控えめ)」「パースは CPU コア数分(全開)」 の非対称モデルを採用しています。
▼ 実装のエッセンス
import os
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
def turbo_pipeline(targets):
# 1. DL(I/O)はAPI制限を考慮して控えめな並列数
# 2. パース(CPU)はマシンパワー全開(OSコア数)
with ThreadPoolExecutor(max_workers=2) as dl_pool, \
ProcessPoolExecutor(max_workers=os.cpu_count()) as parse_pool:
futures = []
for target in targets:
# DL完了時のコールバックで即座にパースを予約
dl_future = dl_pool.submit(download_xbrl, target)
dl_future.add_done_callback(
lambda f: futures.append(parse_pool.submit(parse_xbrl, f.result()))
)
return [f.result() for f in futures]
💡 設計のポイント
- 視覚的な意図:
max_workers=2とos.cpu_count()を並べることで、リソース配分の意図を明確にしています- 隙間のない処理:
add_done_callbackを活用し、ダウンロードが終わったものから順次パースに移ることで、CPU のアイドル時間を最小化しています
② ロックフリー・分散書き出し
並列数が増えるほど、単一のデータベースへの直接書き込みは「ロック競合」がボトルネックとなります。本システムでは、銘柄ごとに独立した一時ファイル(例: temp_7203.json)を書き出し、最後に一括マージする手法を採用しました。これにより、並列処理内の排他制御(Lock)を一切排除し、マルチコアの性能を 100% 引き出しています。
③ 財務的インテリジェンス:本決算(有報) 優先フィルタ
単に「最新の書類」を追うだけでは、通期データが四半期データで上書きされるリスクがあります。docTypeCode に基づく重み付けアルゴリズムを導入し、「有価証券報告書(120)」を絶対優先で抽出するように設計しました。
▼ 抽出ロジック
def select_best_document(docs):
# docTypeCode: '120' が有価証券報告書(本決算)
def get_weight(doc):
return 10 if doc['docTypeCode'] == '120' else 1
# 重み(有報優先)と提出日時の組み合わせで最適な1枚を決定
return max(docs, key=lambda d: (get_weight(d), d['submitDateTime']))
これにより、分析対象が 9,260 件から 約 3,900 件(銘柄ごとの代表1枚) に厳選され、データ品質の向上と処理のさらなる高速化を同時に実現しました。
④ 書類メタデータの一括取得 (Metadata Fetch)
365日分の API リクエスト(書類リストの取得)を並列化し、10 分近くかかっていたリスト作成をわずか 8.6 秒 に短縮しています。
8. 測定環境
「爆速」の裏付けとして、今回ベンチマークを測定したローカル環境のスペックを公開します。特筆すべきは、ハイエンドなサーバーではなく、ごく一般的なデスクトップ PC であっても、並列最適化を極めることでこの数値が出せるという点です。
-
CPU: Intel Core i3-12100 (12th Gen) / 4 Cores, 8 Threads
- エントリークラスの CPU ですが、並列化によりその性能を 100% 解放しています。
- Memory: 46 GiB
-
Storage: USB 3.0 外付け HDD (非SSD)
- 低速な HDD 環境であっても、「ロックフリー出力」により I/O ボトルネックを最小化しています。
- Network: 光回線 (レイテンシ ~10ms 程度の一般的な家庭用環境)
- OS: Linux (Ubuntu 22.04 LTS)
9. 運用フェーズ別の処理時間内訳
このシステムを実際に運用する際の「待ち時間」を、実測データに基づき分解しました。初回セットアップさえ終えれば、日々の分析は驚くほど軽快です。
① 初期化フェーズ(過去365日分のバックフィル)
新規導入時、あるいはクラウド環境でのデータベース消失など、「ゼロ(破壊)からの復旧」 が必要になった際に実行する工程です。原本(EDINET)が公開されている限り、この Turbo パイプラインがあれば即座に「全銘柄の財務元帳」を再構築できます。
| 工程 | 処理内容 | 所要時間 |
|---|---|---|
| スキャン | 365日分の書類リスト取得 (10並列) | 8.6 秒 |
| 選別 | 有報(120)優先フィルタリング | < 1.0 秒 |
| Turbo DL & Parse | ZIP取得とXML解析 (非対称並列) | 8 分 13 秒 |
| DBマージ | SQLiteへの一括正規化インポート | 約 10.0 秒 |
| 合計 | 約 8 分 30 秒 |
② 運用フェーズ(週次・日次ファンダメンタル分析)
DB が一旦構築された後の、通常の継続運用工程 です。日々の差分チェックだけで済むため、この 1 分半のルーチンだけで市場全体の最新状態を常に把握し続けることができます。
| 工程 | 処理内容 | 所要時間 |
|---|---|---|
| 公式更新 | 直近数日分の最新有報チェック | 数秒 |
| yfinance補完 | IPO直後や変則決算銘柄の yf 取得 | 約 75.0 秒 |
| スコアリング | 4,437銘柄の多次元計算・ランク付け (Polars Native) | 約 2.0 秒 |
| 通知 | Discord Webhook 送信 | 約 1.0 秒 |
| 合計 | (Orchestrator 全行程) | 91.28 秒 |
10. ハイブリッド分析による効率化:91秒の分析フロー
取得(Fetch)の効率化は、その後の 「全銘柄分析(Weekly Fundamental)」 において大きな成果をもたらしました。
3,939 銘柄もの「公式確定値」をデータベースに統合した状態で分析エンジンのスイッチを入れると、驚くべき光景が広がります。
- 総分析銘柄: 4,437 件
- EDINET (公式確定値) 利用: 3,939 件 (88.8%)
- yfinance (不足分補完): 498 件
- 分析全体の所要時間: 91.28 秒
これまで yfinance の API に 4,000 回以上のリクエストを送り、レート制限に怯えながら数十分〜数時間待っていた時間が、わずか 1 分半 にまで短縮されたのです。
特に、Polars を用いた計算フェーズは、銘柄ごとのループを廃止した「ベクトル演算の一括実行」により、4,000銘柄のスコアリングを わずか数秒 で完遂させます。さらに、単なる合計点ではなく、銘柄間の相対的な位置づけを正規分布へ射影する「Normal-Mapping」により、極めて直感的なスコア(平均 50 点、標準偏差 15 点)を実現しています。
yfinance への依存を 1/20 以下 に激減させつつ、データソースの 9 割近くを「1円単位で正確な公式データ」に差し替える。これこそが、エンジニアリングが投資にもたらす真の「優位性」です。
📌 コラム:なぜ 100% EDINET にしないのか?
ここで「なぜ 10割 EDINET にしないのか?」という疑問が湧くかもしれません。実は、現実のマーケット運用には以下の「公式データの空白地帯」が存在します。
- 新規上場 (IPO) の壁: 上場して 1 年未満の銘柄は、まだ「本決算(有価証券報告書)」を EDINET に一度も提出していない場合があります
- 書類形式(REIT/ETF): REIT などは通常の事業会社とは異なる書類コードで提出されることがあり、今回の「有報優先フィルタ」から漏れるケースがあります
- 変則決算や提出遅延: 決算期変更や不測の事態で、直近 1 年間に書類がない銘柄も極稀に存在します
こうした「わずかな隙間」を yfinance という速報ソースで埋めることで、「欠損ゼロ & 精度最大」 という、実運用に耐えうる極めて堅牢なパイプラインが完成するのです。
11. 最終結果:yfinance を超えた瞬間
| 項目 | yfinance 依存時代 | EDINET Turbo + Hybrid |
|---|---|---|
| 網羅性 | 低(制限により ~2,000件が限界) | 極高(4,437件:全上場銘柄を網羅) |
| *API発行数 (1) | 4,000 回以上 (銘柄ごと逐次) | 365 回 (日付ベース一括) |
| 信頼性 | 中(推計値や基準の混在あり) | 極高(1円単位の公式確定値 89%) |
| 取得スピード (*2) | 1.2s/it (逐次/レート制限待ち大) | 0.8s/it (並列バックフィル / 待機極小) |
| *分析全行程 (3) | 一部銘柄のみで 30分以上 (全件なら計約 2時間〜) | 全銘柄を対象に 91.28 秒で完遂 |
| 特徴 | 手軽だが、全件・正確な分析は困難 | 重厚・正確・圧倒的な処理効率 |
(*1) API発行数: 全銘柄の更新状況を確認するために必要な最小リクエスト数。この 10 分の 1 以下への削減が速度向上の最大の要因です。
(*2) s/it (seconds per iteration): 1 銘柄あたりの平均処理秒数(Fetch + Parse)。数値が小さいほど高速であることを示します。
(*3) 分析全行程: リスト更新・データ取得・Polars による 4,400 銘柄のスコアリング・ランキング・通知まで。
結論:なぜ yfinance ではなく EDINET なのか?
yfinance は「完成された料理」を提供してくれる便利な API です。しかし、その調理過程はブラックボックスであり、エンジニアが介入できる余地がありません。
一方、EDINET は「巨大な生食材(XBRL)」をそのまま投げつけてきます。
- yfinance: クラウド側の処理を「待つ」しかない
- EDINET: 生データが手元にあるからこそ、自分のマシンの CPU を全開にして「Turbo 化」できる。
今回、「重い生データ」を「最強の並列エンジン」で捌く ことで、yfinance を超える速度と公式の正確さを同時に手に入れました。この「手触り感のあるパイプライン」こそが、自前開発の最大の醍醐味であり、投資家としての強力な武器になるのです。
ADR (Architecture Decision Record): 意思決定の記録
今回の一挙的な性能向上とデータ精度担保を支えた、4 つの重要な設計判断を記録します。
1. 差分スキャン & 本決算(120)優先戦略
- 背景: EDINET の全件バルク取得(20GB+)は、個人開発のネットワーク負荷として非現実的
- 決定: 過去 365 日の書類リストから、銘柄ごとの最新の「docTypeCode: 120 (有価証券報告書)」を 1 枚だけ抽出
- 効果: 通信量を 1/2.5 以下に削減し、かつ「通期ベース」の正確な財務数値を 100% 網羅
2. 非対称並列パイプライン (Asymmetric Concurrency)
- 背景: ネットワーク I/O(ダウンロード)は API レート制限が厳しく、CPU(パース)はコア数分回せるという負荷の不均衡
- 決定: ダウンロードを 2 並列、パースを CPU 論理コア数分(今回 8 並列)に分離
- 効果: I/O 待ちの「隙間」をパース処理で埋め尽くし、6 時間予測を 8 分台に短縮
3. ロックフリー・一時ファイル出力
- 背景: SQLite 等の DB への直接書き込みは、並列数が増えるとロック競合で劇的に低下
- 決定: 処理中は銘柄ごとの個別 JSON に書き出し、最後に一括マージ
- 効果: 書き込み待機ゼロ. マルチコア性能を 100% 解放
4. yfinance とのインテリジェント・ハイブリッド
- 背景: IPO 直後や REIT など、EDINET に書類がない隙間(約 11%)が存在
- 決定: EDINET 確定値を絶対優先し、不足分のみ yfinance で補完
- 効果: 欠損ゼロを維持しつつ、データソースの 9 割を公式確定値へ
結びに:ついに実現した「毎日の全銘柄フル分析」
「データの欠損修復(第1回)」から始まり、「指標計算の高速復旧(第2回)」、「Polarsによる超高速スコアリング(第3回)」、そこで今回の「EDINET公式財務データのTurbo取得(最終決戦)」。
エンジニアリングの積み重ねの末に、私たちはついに到達しました。
「全上場企業の財務データの取り込み、日々のマーケットデータの最新化、そして多次元のファンダメンタル・テクニカル分析による順位付け」
これら一連の重厚な全工程が、今や 「毎日、出勤前のわずか数分で完遂できるアトミックな処理」 へと進化したのです。
API のレート制限による待機時間を最小化し、データの不完全性を克服することで、効率的かつ安定した分析パイプラインを構築することができました。
確定値と並列処理の最適化がもたらす、データ駆動型の投資環境が整ったと言えるでしょう。
あとがき:開発を終えて
本プロジェクト「Turbo パイプライン」は、単なるツールの作成に留まらず、実運用に耐えうる「堅牢なシステム」としての品質を追求しました。最後に、本システムの諸元を公開します。
プログラム諸元
| 項目 | 実績値 | 備考 |
|---|---|---|
| 開発 LOC | 約 21,044 行 |
stock-analyzer4 本体 (Python) |
| テストコード LOC | 約 17,131 行 |
tests ディレクトリ(本体の約 8 割に匹敵) |
| テストカバレッジ | 80% |
pytest-cov による計測 |
| 平均複雑度 (CC) | 3.56 (Rank A) |
radon による算出 |
| 保守性指数 (MI) | Rank A |
radon による算出。ほぼ全ファイルで最高ランク |
| 静的解析・Linter | Ruff / mypy / Black | 厳格な型定義とコード規約、フォーマットを遵守 |
開発タイムラインと役割分担
- 開発期間: 約 1 ヶ月 (コアエンジンの設計・実装)
- テスト・洗練期間: 約 2 ヶ月 (エッジケース対応・並列最適化・Polars 移行)
今回の開発は、従来のソフトウェア開発における「アーキテクト」と「コーダー」の役割分担を、人間と AI エージェントの間で忠実に再現する試みでもありました。
- akkyey (Architect / Reviewer): コードには直接手を触れず、全体の設計、詳細なレビュー、およびテストの受け入れ(品質判定)に専念。**「実装は AI に任せ、自分は『品質の門番』に徹した」**ことで、アーキテクトとしてのプロフェッショナリズムを最大限に発揮しました
- Antigravity (Coder / Implementation): 全ての実装、リファクタリング、およびテストコードの作成を担当
かつて人間相手に行っていた「設計と実装の分離」という手法をあえて AI エージェントに対して実施しましたが、人以上に過酷な要求や繰り返しの修正に耐える Antigravity の存在により、期待以上の成果が得られたことは特筆すべき点です。AI エージェントとの協調による未来のエンジニアリングスタイルを強く実感する開発体験となりました。
プログラムの性能改善は一旦ここまで。
次は、運用編。このプログラムを GitHub で公開し、GitHub Actions で毎日実行、GitHub Pages で毎日結果表示に挑みます。
akkyey & Antigravity(STATEFORCE)の冒険はまだまだ続きます。応援いただけると励みになります。ページも立ち上げ予定ですので、これらの処理の Deep Dive も実施予定です。今後ともよろしくお願いします。
この記事が参考になったら、ストックしておくとシリーズの続きを追いやすくなります。
質問や、別の環境で試した結果はコメントで教えてください。