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-05)

1
Last updated at Posted at 2026-10-04

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

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

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

この記事は2部構成の後半です

内容 公開
前半 株価データにコピーが混ざっているのを見つけた経緯と、見つかった4つの型 2026-10-04(上のリンク)
後半(この記事) 何を消して何を消さなかったかの判断、削除では直せずに残った課題、削除が狙ったデータだけだったかの確かめ方 2026-10-05

前半(2026-10-04公開)の要点は次の3つです。

  • 株価データの中に、別の日の値をそのまま写したようなデータ(前日のコピー等)が混ざっていた
  • 2026-09-30に1,021件、2026-10-03に全約1,600万件を調べ直して4つの型で3,441件、合わせて4,462件(全体の約0.03%)を削除した
  • 原因は、現時点でも解明できていない

この記事では、1社の1日分の株価(始値・高値・安値・終値・出来高)を「1件」と数えます。始値・高値・安値・終値の4つの値を、「四本値」と呼びます。

先に結論

  • 「四本値と出来高が前の日と同じ」というだけでは、消しませんでした。出来高の大きさか、日付のまとまりを条件に加えています
  • 出来高が少ない銘柄で、同じ値が3日前後続いた5か所は、外部サイトの日足(1日ごとの株価)と全て一致した実データでした。これは残しました
  • 削除では直せず、残っているものが3つあります(815件・約4,100件・出来高が大半0になる平日)。3つとも、まだ対応していません
  • 削除が狙ったデータだけだったかは、全社の件数などを作業の前後で比べて確かめました

全件調査で見つかったものの行き先

全件調査(2026-10-03)で見つかったものが、削除・残す・課題のどこに分かれたかは次のとおりです。

「同じ値が続く」だけでは消さなかった

全件調査では、「四本値と出来高が前の日と全く同じ」データがたくさん見つかりました。ただし、同じ値が続いていても、必ずコピーとは限りません。そこで、2つのことを確かめて、消す条件を絞りました。

偶然の一致は、どのくらい起きるか

同じ銘柄の、10〜30日離れた日どうしで、四本値と出来高が一致する割合を調べました。離れた日どうしなので、一致はほとんど偶然です(遠い日のコピーも含むため、偶然の割合としては大きめに出る、上限の目安です)。

出来高 偶然に一致する割合(上限の目安)
100〜1,000株 約0.01%(1万件に1件程度)
1,000〜1万株 約0.003%
1万〜10万株 約0.0006%
100万株以上 ほぼ0

出来高が大きいほど、四本値と出来高が偶然に一致することは、ほとんどありません。

出来高が少ない銘柄の「同じ値が3日前後続く」は、実データだった

一方で、出来高が少ない銘柄(200〜3,000株)に、四本値と出来高が同じ日が3日前後続く5か所がありました。外部サイトの日足と照合したところ、5か所とも全て一致しました。実際の市場で、同じ値が続いていたのです。外部サイトの日足を確認できるのは2014年7月以降なので、照合できたのはその範囲です。

そのため、「同じ値が複数回ある」というだけでは削除せず、出来高の大きさか、日付のまとまりを条件に加えました。

型 「前の日と同じ」に加えた条件 削除した件数
① 2014年7月より前で、出来高1万株以上の銘柄に、同じ四本値が5営業日以内に3回以上ある。この組のうち、実データであり得るのは最大1件だが、どれか見分けられないため、全ての出現を削除した 66件
② 前日と四本値・出来高が完全に同じで、出来高が0より大きく、値動きがある。かつ、特定の7日にまとまっている(偶然の一致は1日あたり約1〜2件と見積もられるのに対し、この7日は1日に120〜1,080件) 2,954件
③ 取引所の休場日なのに、出来高が0より大きい(休場日に実際の取引は存在しない) 210件
④ 2014年7月より前で、前日と同じ株価のうち、次のどれかに当てはまる。出来高10万株以上/その日に前日と同じ株価が8件以上集中している/4〜7件の日で出来高1万株以上 211件
合計 3,441件

型②の削除に使ったコード

削除は、1つのトランザクション(途中で止まったら、全て元に戻る単位)で行いました。次は、型②の削除スクリプトの抜粋です。接続情報などは除き、省略した部分は(省略)と書いています。

for cid in ids:
    rows = byc[cid]
    # (省略)
    a, b = rows[-2], rows[-1]
    assert a[3:8] == b[3:8], (cid, d, a, b)          # OHLCV完全一致
    assert b[7] > 0 and b[4] > b[5], (cid, d, b)        # 出来高>0・高値>安値
# (省略)
cur.execute(f"DELETE FROM stock_prices WHERE company_id IN ({lit}) AND time >= '{lo}' AND time < '{hi}'")
assert cur.rowcount == len(ids), (d, cur.rowcount, len(ids))
# (省略)
if DRY:
    conn.rollback(); print('DRY RUN: rolled back')
else:
    conn.commit(); print('COMMITTED')
  • 削除する日ごとに、対象の各社の直近の行を取り直し、「当日の行と直前の行で、四本値と出来高(コード中のOHLCV)が完全に一致している」「出来高が0より大きい」「高値が安値より高い(値動きがある)」を確かめます。1社でも外れると、assertで止まり、コミットされません
  • 削除した件数が、対象の社数と一致しなければ、同じく止まります
  • 実行時にdryを付けると、最後にコミットせず、巻き戻します。本番の削除の前に、この形で一度試しました

消さずに残したデータと、その理由

残したデータ 件数 理由
出来高が少ない銘柄(200〜3,000株)で、四本値・出来高が3日前後続いたもの 5か所 外部サイトの日足と全て一致した実データだった
休場日に入っていた、出来高0の株価 1日あたり約1,700件 取引がなかったことを表す穴埋めのため、残した

このほかに、削除していない815件(根拠が足りず、消せなかったもの)は、次の節で書きます。

削除では直せず、残っている課題

3つあります。どれも、対応は終わっていません。

課題1: 偶然の一致と見分けられない815件

2014年7月より前で、出来高が1万株未満、かつ特定の日に集中していない、前日と同じ株価が815件あります。上の表の割合と比べると、約9割はコピーと推定されます。ただし、1件ずつ見たときに、偶然の一致と見分ける方法がありません。削除する根拠がないため、削除していません。

課題2: 日付が1営業日ずれた型の約4,100件

2014年7月以降に、前日と同じ株価が約4,100件あります。2019年4月〜2020年3月には、1日に60〜120社ありました。流動性の低い(取引量が少ない)ETF・ETNで、日付が1営業日ずれた区間の終わりに、同じ値の重複が出る型です。約700銘柄に及びます。

ある銘柄(2019年11月5日〜8日)では、外部サイトの日足と照合して、データの11月5日・6日・7日の3日分が、実際は6日・7日・8日の値だったと確認できました。ずれた日付の行には、別の日の値が入っています。重複した行を削除しても、その日の株価が抜けるだけで、正しい値にはなりません。外部サイトの値で入れ直す必要があり、数千件規模になります。どう直すかは、まだ決めていません。

課題3: 平日なのに、大半の株価で出来高が0になる日

平日なのに、その日の株価の大半で出来高が0になっている日が、いくつかあります。出来高が付いている株価の割合は、次のとおりです。

日付 出来高が付いている株価の割合
2000-11-14・11-16・11-29・12-29 約49%
2001-03-14 2.4%
2003-08-15 24%
2015-12-25 8%
2016-07-11 7.7%

取得元側のデータの欠けとみていますが、まだ調べていません。削除も訂正もしていません。

原因について

現時点でも、原因はまだ解明できていません。取得元のデータにもともと入っていたのか、取り込みの途中で入ったのかも、まだ区別できていません。今回の作業は、見つかったコピーの削除が中心でした。

削除したのは狙ったデータだけだったか

削除の前後で、次の3つを確かめました(2026-10-03)。

  • 全社について、「件数・全ての行の内容から計算した照合用の値(ハッシュ値)・価格の合計」を、作業の前後で比べました。削除の対象でなかった社は、全社で一致しました(①の削除は4,587社、②は3,083社、③と④は4,382社)
  • 削除した社は、「作業後 = 作業前 − 削除した分」が全社で成り立ちました(②は1,516社中1,516社、③と④は217社中217社)。残った株価は、1件も変わっていません
  • 移動平均などの指標を、株価から独立に計算し直して、データベースの値と比べました。最大の差は0.00005で、丸め誤差の範囲です

照合に使ったコード

作業前後の集計は、次のSQLで社ごとに出しました(接続情報は除いています)。

select company_id, count(*), bit_xor(hashtextextended(t::text,0)),
  coalesce(sum(open_price),0), coalesce(sum(high_price),0), coalesce(sum(low_price),0), coalesce(sum(close_price),0),
  coalesce(sum(adjusted_close),0), coalesce(sum(volume),0), min(trade_date), max(trade_date)
  from stock_prices t group by company_id

bit_xor(hashtextextended(t::text,0))が、各行の内容から計算した値(ハッシュ値)を、社ごとに全行分、XOR(排他的論理和)で1つにまとめたものです。同じ値どうしをXORすると互いに打ち消し合うので、作業後の値は、作業前の値に、削除した行の値をXORしたものと一致するはずです。削除した行は、バックアップのCSVから取り出して同じ集計をし、社ごとに比べました。次は、その比較の抜粋です(cbが作業前、caが作業後、rが削除した行の集計です)。

for cid, r in dele.items():
    cb, ca = b[cid], a[cid]
    cnt_ok = ca[0] == cb[0] - r[1]
    h_ok = ca[1] == (cb[1] ^ r[2])
    sums_ok = all(Decimal(ca[i]) == Decimal(cb[i]) - Decimal(r[i+1]) for i in range(2, 8))  # open..volume

件数と価格の合計は引き算で、ハッシュ値はXORで比べています。

まとめ

  • 「同じ値が続く」だけでは消さず、出来高の大きさか、日付のまとまりを条件に加えた。出来高が少ない銘柄の5か所は、実データだったので残した
  • 削除では直せず、残っているのは、815件・約4,100件・出来高が大半0になる平日の3つで、いずれも対応していない
  • 削除が狙ったデータだけだったかは、全社の前後比較で確かめた。原因は、現時点でも解明できていない

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

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?