✳︎以下はClaude fable 5により生成した記事です。
きっかけ: 外部の数字と合わない
watch list ランキングのスコアリング対象は約1,600銘柄。この数字を「配当投資対象としてはほぼ100%カバー」と説明してきました。
ところが、外部データで「会社予想ベースで年間配当がある国内上場銘柄」を概算すると約2,700〜2,750銘柄。市場区分や金融業種などこちらの設計上の除外条件を揃えて調整しても、期待値は約2,400〜2,500銘柄になります。
実際のスコアリング対象は1,612件。約800件、説明のつかないギャップがありました。
「無配当銘柄1,124件の除外は意図的な設計」— 以前の調査ではそう結論づけていました。しかしこの数字自体が怪しい。調査を始めました。
調査: 除外1,162件の再分解
Stage A通過2,756銘柄のうち、スコアリングから除外されていた1,162件を、除外理由のコードパスごとに分類しました。
| 分類 | 件数 | 内容 |
|---|---|---|
| FY行遮蔽バグ | 689件 | 配当実績が存在するのに参照されていない |
| 予想配当で救済可能 | 208件 | 実績は空だが会社予想配当が存在 |
| 過去FY実績で救済可能 | 79件 | データ欠落型 |
| 予想が明示的に0 | 29件 | 無配予想(除外は妥当) |
| 真の無配当 | 157件 | 除外は正当 |
除外の約6割が、データはあるのに見ていないバグでした。
原因: 「いちばん新しい行」の落とし穴
上場企業の開示には、年間配当が記載される本決算(FY)と、配当欄が空欄になりがちな四半期決算(1Q/2Q/3Q)があります。1銘柄の財務データには両方の行が混在します。
問題のコードは、各銘柄について「最も新しく開示された行」を1行取り、その配当欄で配当の有無を判定していました。
latest = df_fin.iloc[-1] # 開示種別を問わず直近行
ある銘柄の実例(1381)ではこうなっていました。
- 本決算を開示 → 「年間配当112.5円」と記載
- その後、四半期決算を開示 → 配当欄は空欄
- システムは「いちばん新しい行=四半期」を採用
- 空欄を見て「無配当」と判定 → 対象から除外
配当実績はすぐ下の行に保存されているのに、です。
このバグの厄介な点は、本決算の直後だけは正しく動くことでした。四半期開示が入るたびに配当情報が”覆い隠され”、年の大半の期間、その銘柄は無配当扱いになる。週によってスコアリング対象が数百件単位で揺らいでいた可能性があります。
修正: 3つの変更
1. FY行の優先探索(689件を救済)
「直近行」ではなく「有効な配当実績を持つ最新のFY行」を優先的に探すよう変更。ROE等の計算に使う他フィールドも同じ行から取得し、一貫性を保ちました。
2. 対象判定への予想配当フォールバック(約290件を救済)
実績が見つからない場合、会社予想の年間配当(FDivAnn/NxFDivAnn)を順に参照。復配予定の銘柄も対象に入るようになりました。ただし予想が明示的に0円の銘柄は「会社が無配を予想している」情報を尊重し、除外を維持しています。
役割分担も明確にしました。対象判定には予想配当も使うが、スコア計算(配当性向・増配履歴)は実績のみ。予想値をスコアに混ぜないことで、評価の性質を変えずに母集団だけを正しくしています。
3. 横断確認で同種バグを3ファイルで発見
同じ iloc[-1] パターンをコードベース全体でgrepしたところ、週次スクリーナー本体(MBO/TOB候補)と親子上場スコアリングにも同じ問題があり、同時に修正しました。1つのバグ調査がパイプライン全体の品質底上げにつながった形です。
結果
| 項目 | 修正前 | 修正後 |
|---|---|---|
| スコアリング対象 | 1,612件 | 2,617件(+62%) |
| スコア8.0以上 | 14件 | 27件 |
| 監視銘柄のスコア | — | 全銘柄変化なし(回帰確認) |
修正後の2,617件は、事前の救済見込み(2,588〜2,617件)の範囲に収まり、外部推計とも整合する水準になりました。ランキング上位50には修正によるNEW銘柄が23件登場し、これまでシステムが一度も見せてくれなかった候補が含まれています。
なお、進行中のフォワード検証(2026年度コホート)は修正前の母集団で凍結済みのため改変せず、検証ページの既知の限界に追記する形で対応しました。予測の遡及改変をしないのは検証の作法です。
教訓
1. 外部の物差しと突き合わせる。 内部的には「1,124件の無配当除外は設計通り」と一度は正当化していました。外部推計との800件のズレという違和感がなければ、このバグは今も動き続けていたはずです。
2. 「最新の1行」は時系列データの罠。 開示データのように種別の異なるレコードが混在する時系列では、「いちばん新しい」は「いちばん正しい」を意味しません。
3. バグを見つけたら同型を横断検索する。 同じ発想で書かれたコードには同じバグが潜んでいます。今回は3ファイルで同種の問題が見つかりました。
「動いているように見える」ことと「正しく動いている」ことの間には、外から検証しない限り気づけない距離があります。その距離を一つ埋めた記録として、本記事を残します。