1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【日本株分析プラットフォーム】毎晩の「異常値の削除」が、正当な過去の株価まで消していた話(2026-10-16)

1
Last updated at Posted at 2026-10-15

このプロジェクトについて

EDINET(有価証券報告書等)の開示データをもとに、日本株の財務指標・投資判断スコアを日次で算出・公開しているサービスです。

公開サイトはこちら → https://stocks.happy-life-design.net
前回の記事(2026-10-15公開)はこちら → https://qiita.com/rightcross-ae86/items/7308f69291dc98f57bc5

この記事について

株価データには、毎晩自動で動く保守処理があります。その中に、終値が100万円を超える行を「明らかなデータの誤り」として削除する規則がありました。2026-10-11に、この規則が、正当な過去の株価まで毎晩消していたことが分かりました。株式併合の調整で、昔の価格が100万円を超えて表示される会社があったためです。

この記事は、見つけ方、直し方、直したあとの確かめ方をまとめたものです。2026-10-12には、同じ型のもう1社も補完しました。

この記事は、単独で読めます。2026-10-15公開の復元の検証の記事の続きではなく、別の話題です。2026-10-13・2026-10-14公開の株式分割の記事とは、別の原因です。

この記事では、次のように呼びます。

  • 「行」: 1社の1日分の株価(始値・高値・安値・終値・出来高)
  • 「保守処理」: 毎晩自動で動く、データの整合を確かめ、不要なデータを取り除く処理
  • 「株式併合」: 複数の株を1株にまとめること。10株を1株にまとめると、株価はおよそ10倍になります
  • 「調整」: 併合や分割があると、前後で株価が不連続になります。過去の価格も同じ比率で換算して、前後をつなげて比べられるようにすること。1,000分の1の併合なら、過去の価格を1,000倍します
  • 「補完」: データベースに無い行を、外部サイトの値で足すこと
  • 「テクニカル指標」: 株価と出来高から計算する指標(移動平均・RSI・OBVなど)。株価の行を足すと、その周りの日付の指標も変わります
  • 「全社の指紋」: 会社ごとに、全ての行の内容から計算した値。反映の前後で比べて、対象外の会社が1社も変わっていないことを確かめます
  • 「ドライラン」: 実際には書き込まずに、何を書くかだけを確かめる実行
  • 「往復試験」: 書き込み、取り消して、全てが元どおりになることを確かめる試験
  • 「本番」: 実際に稼働していて、公開サイトが読んでいるデータベース
  • 「OBV」: 出来高を、株価が上がった日は足し、下がった日は引いて、積み上げた指標

先に要点

  • 毎晩の保守処理に、終値が100万円を超える行を削除する規則がありました。システムを作った当初からある規則です
  • 株式併合の調整で、昔の価格が100万円を超える会社があり、その正当な過去の行が消えていました。1社で334日分、別の1社で78日分、計412日分です
  • 最初は「最初から取り込めていなかった」と見ていましたが、誤りでした。欠けていた334日が全て終値100万円超だったことで、削除規則が原因だと分かりました
  • 補完の前に、規則に会社単位の例外を入れました。補完しても、翌朝の保守処理でまた消えるためです。例外は印を付けた会社だけで、1,000万円を超える桁外れの値は従来どおり削除します
  • 334行を補完し、対象外の4,619社は株価・指標・企業情報とも全社で変わっていないことを確かめました
  • 補完すると、既存の指標も変わりました。OBVは、既存の全4,847行が変わりました
  • 2026-10-12の翌朝の保守処理のあとも、補完した行は消えていませんでした

起きたこと: データベースに無い334日

2026-10-11、別の調査の途中で、ある会社の株価を、外部サイトに載っている現在の値と突き合わせました。外部サイトには5,181行、データベースには4,847行があり、差の334日(2005-09-29〜2007-02-27)が、データベースに無い状態でした。

この334日について、最初は「削除されたのではなく、最初に取り込んだ時点から無かったのだろう」と考え、原因を調べる前に、そう記録していました。これは誤りでした。

原因: 欠けた334日は、全て終値が100万円を超えていた

欠けていた334日の外部サイトの値を確かめると、全て終値が100万円を超えていました(最大197.8万円)。データベースに残っている行は、全て100万円以下で、最大の終値はちょうど1,000,000円でした。

この境目が、毎晩の保守処理の削除規則と一致しました。終値が100万円を超える行を、明らかなデータの誤りとして削除する規則です(元のSQLは「技術的な補足」に載せています)。

なぜ、正当な価格が100万円を超えるのか。この会社は、2回の株式併合(100株を1株に、10株を1株に)で、累計1,000分の1になっています。そのため、調整後の過去の価格は、当時の実際の価格の1,000倍です。2005年当時の実際の終値は約1,000円で、調整後は100万〜200万円になります。

次の小さなコードで、この仕組みを再現しました。このコードは、この記事のために書いたもので、実際の保守処理のコードではありません。値は説明用で、実際の株価ではありません。

THRESHOLD = 1_000_000      # 毎晩の削除規則の基準: 終値がこれを超えたら削除
FACTOR = 1000               # 累計の調整倍率(1,000分の1の併合なら、過去の価格を1,000倍する)

for raw in (980, 1000, 1050, 1980):   # 当時の実際の終値(説明用の値。実際の株価ではない)
    adjusted = raw * FACTOR           # 調整後の終値
    print(raw, adjusted, "削除される" if adjusted > THRESHOLD else "残る")

実行すると、次のように出ます(実行結果のままです)。

980 980000 残る
1000 1000000 残る
1050 1050000 削除される
1980 1980000 削除される
当時の実際の終値(説明用) 調整後の終値 削除規則
980円 980,000円 残る
1,000円 1,000,000円 残る
1,050円 1,050,000円 削除される
1,980円 1,980,000円 削除される

データベースに残っていた最大の終値がちょうど100万円なのは、この境目のためです。

流れを図にすると、次のとおりです。

他に、同じ型の会社はあるか

データベースの最大終値が10万円以上の76社を、外部サイトの値と突き合わせました。終値が100万円を超えていて、データベースに無い行があったのは、ここまでの1社(334日分)と、もう1社(78日分・2005-02-17〜2006-05-09・最大143万円)の2社だけでした。

10万円未満の会社は、価格の連続性から該当しにくいと考えていますが、全社を確かめたわけではありません。

直す順番: 補完の前に、規則の例外

欠けた行を足しても、翌朝の保守処理で、また消えます。そこで、補完より先に、規則に例外を入れました。

  • 企業マスタ(会社ごとの基本情報の表)の補足情報に、例外の印を付けた会社だけ、上限を100万円から1,000万円に引き上げる
  • 印のない会社は、従来どおり100万円を超えたら削除する
  • 印のある会社でも、1,000万円を超える桁外れの値は削除する

変更前と変更後の削除SQLは、「技術的な補足」に載せています。

例外が効くことは、次の2つで確かめました。

  1. 本番のデータベースの中で、印を付け、補完する334行を仮に入れてから、新しい規則を実行する試験を、最後に必ず取り消す(ROLLBACK)形で行いました。削除は0行で、行数は変わりませんでした
  2. 反映後の本番に対して、保守処理のコードから取り出した実際のSQLを実行して、削除が0行で、334行が残ることを確かめました

補完: 334行を足して、全社で変わっていないことを確かめる

外部サイトの現在の値で、334行を足しました。次の順で進めました。

  1. 補完の前の状態をバックアップする(株価・指標とも4,847行)
  2. 全社の指紋を取る
  3. ドライランをする
  4. 往復試験をする
  5. 本番に334行を書き込む
  6. テクニカル指標を再計算する(5,181件)
  7. 全社の指紋を取り直して、前と比べる

確かめた結果は、次のとおりです。

確かめたこと 結果
足した334行 期待した値と全列が一致
既存の4,847行 行の内容から作った値(ハッシュ値)をまとめた値が、補完の前と同一
対象外の4,619社 株価・指標・企業情報とも全社一致(違うもの0・増えたもの0・消えたもの0)
指標 株価から独立に計算し直した値との最大差が0.00005
API 2005-09-29〜2007-03-05の368行を返す

補完すると、過去の指標も変わった

欠けていた行を足すと、その周りの日付の指標も変わります。補完で変わった、既存の指標の行数は、次のとおりです。

指標 変わった既存の行
移動平均(直近の複数日の終値の平均)(5日・25日・75日・200日) 各24〜30行
指数移動平均・MACD(終値の履歴から計算する指標) 130〜262行
RSI(上昇と下落の勢いを測る指標) 155行
OBV(出来高を積み上げる指標) 全4,847行

OBV以外は、足した期間のあとの、おおむね2008年2月までの行が変わりました。OBVは積み上げの指標なので、既存の全ての行が変わりました。2007-02-28以降の4,817行は、一律に2,410株増えています。

2社目と、補完しなかった1社

2026-10-12、もう1社(78日分)も同じ手順で補完しました。対象外の4,619社は、株価・指標・企業情報とも全社で変わらず、指標は独立に計算し直した値との最大差が0.00005でした。

穴に見えても、原因が違う会社もありました。ある1社は、2006年に65日分の行がありませんが、欠損ではなく、別の規則(1円未満の異常な値を取り除く規則)で、意図して削除した行でした。外部サイトは、この期間だけ、前後のおよそ1,000分の1の価格を返しています。同じ行を戻すと、その規則でまた消えます。1,000倍して戻す案もありますが、根拠が前後の連続性だけなので、していません。

本番で効いたか

2026-10-12の毎晩の保守処理のあと、次を確認しました。

  • 保守処理のタスクが成功していた
  • 補完した会社の行数が、5,181行のままだった
  • 終値が100万円を超える334行が、残っていた
  • データの完全性チェックが、正常だった

気づくのが遅れた理由と、次から守ること

欠けた行は、エラーも警告も出しません。消す規則は、書かれたとおりに動いていました。規則が、併合の調整後の価格まで巻き込むことに、気づいていませんでした。

次から守ることを、2つ決めました。

  • データが欠けているとき、「最初から無かった」と推定する前に、既存の削除規則(保守処理・取り込み時の検査)の条件に、欠けた行が全て当てはまらないかを、先に調べる
  • 規則の例外を入れるときは、補完より先に入れる。補完しても、翌朝の保守処理で消えるため

限界

  • 例外の印を付けたのは2社だけです。他の会社で同じ型が起きても、毎晩の保守処理は従来どおり削除します。全社の確認は、最大終値が10万円以上の76社が対象で、10万円未満の会社は、全社を確かめたわけではありません
  • 取り込み時の検査(終値が100万円を超える行は取り込まない)には、例外を入れていません。2社の現在の価格は小さく、通常の取り込みでは当たらないためです
  • 補完した値は、外部サイトの現在の値を正として足しました。そのうち31行は出来高が0の行です(取引のなかった日の穴埋めなどの行)。他の会社に残っている同じ種類の行を残す方針に合わせて、足しました
  • 公開サイトのチャートは最長2年の表示なので、補完した2005年からの行は、画面では見られません(APIでは返ります)
  • 翌朝の保守処理での確認は、先に補完した1社の334行についての記録です。2社目の78行について、同じ確認の記録は読み取れていません

技術的な補足(実際に使ったコード)

ここに載せるのは、実際のソースコードからの抜粋で、元のファイルの連続した行です。実物と変えたのは、共通のインデントを除いたことと、例外の印の値(社内で問題を管理するための番号)を<番号>に置き換えたことの2つです。データベースの接続情報は含まれません。

1. 変更前の削除規則

毎晩の保守処理の一部です。終値が100万円を超える行を、全社一律に削除します。RETURNINGは、削除した行の会社と日付を、後続の処理に渡します。

cur.execute("DELETE FROM stock_prices WHERE close_price > 1000000 RETURNING company_id, trade_date")

2. 変更後の削除規則(印を付けた会社だけ、1,000万円まで残す)

AND NOT (...)の中が、例外です。終値が100万円を超える行のうち、「1,000万円以下で、かつ、印のある会社の行」だけを、削除の対象から外します。

cur.execute("""
    DELETE FROM stock_prices
    WHERE close_price > 1000000
      AND NOT (close_price <= 10000000
               AND company_id IN (SELECT id FROM companies WHERE meta->>'price_cap_exempt' = '<番号>'))
    RETURNING company_id, trade_date
""")

まとめ

  • 毎晩の保守処理の「終値100万円超を削除」する規則が、株式併合の調整で100万円を超える会社の、正当な過去の行まで消していました(2026-10-11に判明)
  • 欠けた日が全て終値100万円超だったことで、原因が分かりました。「最初から無かった」という見立ては、原因を調べる前の推定で、誤りでした
  • 補完の前に、規則へ会社単位の例外を入れ、334行(1社)と78行(もう1社)を補完しました。対象外の4,619社は全社で変わりませんでした
  • 補完すると、既存の指標も変わります。OBVは、既存の全4,847行が変わりました
  • 例外は、印を付けた2社だけです。他の会社には広げていません

気になる点・ご質問があればコメントでお知らせください。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?