このプロジェクトについて
EDINET(有価証券報告書等)の開示データをもとに、日本株の財務指標・投資判断スコアを日次で算出・公開しているサービスです。
公開サイトはこちら →
https://stocks.happy-life-design.net
前回の記事はこちら →
https://qiita.com/rightcross-ae86/items/565f5228328b2d9c8adb
今回やったこと
自己資本(equity)の値が信頼できるかどうかを日次で自動判定している仕組みがあります。連結決算と個別決算の差が非支配株主持分や新株予約権で説明できる範囲かを見て、範囲内なら「妥当」と判定する処理です。この判定処理の中で、新株予約権の額そのものを取り違えるバグを見つけて修正しました。
バグの内容: タイブレークが不安定で個別の小さい値を拾ってしまう
新株予約権は非支配株主持分と違い、親会社単体(個別)の開示書類にも載ります。そのため同じ開示書類・同じ提出日の中に「連結」の値と「個別」の値が両方存在するケースがあり、本来はここから連結側の値を選ぶ必要がありました。
-- Before: 提出日が同じ場合のタイブレークが不定
DISTINCT ON (company_id, fiscal_year)
...
ORDER BY company_id, fiscal_year, filed_date DESC
このORDER BYには「連結を優先する」条件が入っておらず、提出日が同一の場合にどちらが選ばれるかが実質ランダムでした。実際に2社で、本来は連結側の数億円という値を使うべきところ、個別側の数千万円という桁違いに小さい値を誤って取得していたことが判明しました。
最初の修正案: 個別を一律除外 → 28社で回帰
対処として、まず「個別の値は最初から候補にしない」という単純な除外を実装しました。ところが全社の計算結果を修正前後で突き合わせたところ、28社で逆に「妥当」判定が「要確認」に変わってしまう回帰が発生しました。
原因を調べると、これらの企業はそもそも連結側に新株予約権のタグ自体が存在せず、個別側の値だけが正しいデータでした。個別を一律除外すると、そうした企業の正しいデータまで失ってしまっていたのです。
優先順位方式への設計変更
「個別を除外する」のではなく、「連結を優先し、連結が無い場合だけ個別にフォールバックする」という優先順位に設計を変更しました。
-- After: 連結を優先するタイブレークを明示
DISTINCT ON (company_id, fiscal_year)
...
ORDER BY company_id, fiscal_year, filed_date DESC,
CASE WHEN context_id LIKE '%NonConsolidatedMember%' THEN 1 ELSE 0 END
この方式なら、連結タグが存在する企業は今まで通り連結の値が選ばれ、連結タグが存在しない企業だけ個別の値にフォールバックします。全社での再検証で、回帰は0件、改善したのは最初に見つけた2社のみという結果になりました。
検証
本番反映前にバックアップを取得し、反映後に全件をJSON化して突き合わせたところ、変化があったのは想定通りの行・列のみで、行数の増減もありませんでした。既存のテストスイート(261件)も反映の前後で全件通過しています。
一律除外という単純な対処が、別の企業の正しいデータを壊してしまう副作用を持ちうるという点を、実際の回帰を通じて確認できた修正でした。
気になる点・ご質問があればコメントでお知らせください。