このプロジェクトについて
EDINET(有価証券報告書等)の開示データをもとに、日本株の財務指標・投資判断スコアを日次で算出・公開しているサービスです。
公開サイトはこちら → https://stocks.happy-life-design.net
前回の記事(2026-10-08公開)はこちら → https://qiita.com/rightcross-ae86/items/cf906f809d610bde52a8
この記事について
前回(2026-10-08公開の記事。以下、本文で「前回」と書くのは、全てこの2026-10-08公開の記事のことです)は、2019年3月11日〜4月11日の月〜木の18営業日の株価が、外部サイトの最終値とずれていた話を書きました。その中で、外部サイトの値への置き換えが済んでいるのは28社だけで、置き換えたあとに「対象外の会社の株価が1件も変わっていないこと」と「置き換えた会社の株価から計算し直した指標が合うこと」を確かめた、と1文だけ書きました。この記事は、その確かめ方の詳しい話です。
確かめ方は2部構成で書きます。この記事は前半です。置き換えた件の中身と、置き換えの対象でない会社・置き換えを計画した期間の外が変わっていないことの確かめ方を書きます。後半は別の日に公開します(公開日は決めていません)。計画の期間の中を1件ずつ突き合わせた確かめ方、指標の計算し直し、まだ確かめていないことを書く予定です。
残りの会社の外部サイトの値の取得には着手しています。開発の記録は2026-10-08の夜までのもので、それ以降の作業は反映していません。
この記事では、次のように呼びます。
- 「件」: 1社の1日分の株価(始値・高値・安値・終値・出来高)
- 「取得元」: 株価データを取り込んでいる元のサービス
- 「外部サイト」: 値の照合に使う、別の株価サイト
- 「穴埋め」: 取引がなかった日に、前の日の終値のまま、出来高0で入れてある株価
- 「計画の期間」: 置き換えを計画した日付の範囲
- 「指紋」: 会社ごとに作る目印。件数と、全ての件の中身から作った値、価格や出来高の合計をまとめたもの
先に結論
- 前回の28社・750件の置き換えは、2026-10-05に本番のデータベースへ反映しました。置き換えは、既にある件の値を書き換える操作で、件数は変わりません。件数を比べても、直した所以外が変わっていないことは確かめられません
- そこで、会社ごとの「指紋」を、反映の前後で比べました。置き換えの対象でなかった4,571社は、株価・指標・企業情報とも、全社で一致しました
- 置き換えた28社でも、計画の期間より前と後の株価は、28社とも変わっていませんでした
- 計画の期間の中の確かめ、指標の計算し直し、まだ確かめていないことは、後半に書きます
置き換えた750件の中身
置き換えた750件は、次の5種類です。
| 種類 | 件数 | 内容 |
|---|---|---|
| 日付ずれの復元 | 280 | データベースの1件が「次の取引日の値」になっていたものを、その日の値に戻した |
| 18営業日の途中経過値 | 366 | 前回の記事の18営業日の株価を、外部サイトの値に置き換えた |
| 穴埋めに戻した件 | 51 | 取引がなかった日に、次の取引日の値が入っていたものを、穴埋めに戻した |
| その他 | 50 | 18営業日の前後の期間や金曜日にあった、穴埋めのはずなのに実際の取引がある件など |
| 穴埋めの株価の直し | 3 | 穴埋めの株価の値を直した |
| 合計 | 750 |
このほかに、置き換えず、印だけ付けて残した件が5件あります。外部サイトには該当の日の株価がないのにデータベースには出来高がある件が4件と、株式分割などの倍率が決まらなかった件が1件です。この5件は、750件に入っていません。
なぜ、件数だけでは足りないのか
置き換えは、既にある件の値を書き換える操作です。件数は、反映の前後で変わりません。ですから、件数を比べても、何件が書き換わったのかも、狙っていない件まで書き換わっていないのかも、分かりません。
本当に確かめたいのは、次の4つです。
- 置き換えの対象でない会社が、1件も変わっていない
- 対象の会社でも、計画の期間の外の件が、1件も変わっていない
- 計画の期間の中では、計画した750件だけが期待した値になり、それ以外の件は全ての列が反映前と同じ
- 株価から計算している指標が、置き換えた株価と合っている
1と2をこの記事(前半)で、3と4を後半で書きます。
図のとおり、確かめ1と確かめ2は、反映の前後で作った指紋を比べます。確かめ3と確かめ4は、反映したあとのデータを、保存しておいたバックアップや、株価からの計算と比べます。
確かめ1: 会社ごとの指紋を、反映の前後で比べる
会社ごとに、次の3つをまとめた「指紋」を作ります。
- 件数
- 全ての件について、全ての列を並べた文字列から数値(ハッシュ値)を作り、排他的論理和(XOR)で1つにまとめた値。並び順によらず1つにまとまり、1件でも中身が変われば、まとめた値はほぼ確実に変わります
- 価格や出来高の列ごとの合計
株価・指標・企業情報の3つの表で作り、反映の前と後で比べます。置き換えの対象でない会社は、3つの表とも、指紋が完全に一致しなければなりません。
この確かめ方は、2026-10-03に株価を削除したときに作ったもので、今回の置き換えにも使いました。
結果は、置き換えの対象でなかった4,571社の全社で、株価・指標・企業情報とも指紋が一致しました。
確かめ2: 対象の会社でも、計画の期間の外は変わっていない
対象の28社は、置き換えで値が変わるのが当然なので、会社ごとの指紋を比べても「変わった」としか分かりません。そこで、計画の期間より前の件と、期間より後の件を分けて、それぞれ件数と指紋を作り、反映の前後で比べます。
結果は、28社のうち、計画の期間より前の株価は28社とも、期間より後の株価も28社とも、反映の前後で一致しました。ここで結果を確かめたのは株価です。指標は、置き換えた28社の全履歴を計算し直したため、後半に書く別の方法で確かめました。
技術的な補足(実際に使ったコード)
ここに載せるのは、実際に使ったスクリプトからの抜粋です。実物と変えたのは、長い箇所を...で省略したことと、共通のインデントを除いたことだけです。データベースの接続情報の行は載せていません。
1. 会社ごとの指紋を作る(株価の表の分)
conn.set_session(readonly=True)で、読み取り専用の接続にしています。指紋の中身は、件数・全ての列から作った値をbit_xorでまとめたもの・価格や出来高の列ごとの合計です。t::textは、1件の全ての列を1つの文字列にする書き方で、hashtextextendedがその文字列から数値を作ります。
conn.set_session(readonly=True)
cur = conn.cursor()
...
cur.execute("""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""")
res['sp'] = {str(r[0]): [r[1], r[2]] + [str(x) for x in r[3:]] for r in cur.fetchall()}
指標の表と企業情報の表も、同じ形で指紋を作ります(コードは省略)。反映の前と後で作った2つの指紋を、次のコードで比べます。spは株価、tiは指標、coは企業情報です。
2. 対象外の会社の指紋が、全社で一致するかを比べる
b = json.load(open(sys.argv[1])); a = json.load(open(sys.argv[2]))
targets = set(sys.argv[3].split(',')) if len(sys.argv)>3 and sys.argv[3] else set()
ok = True
for tbl in ('sp','ti','co'):
bk, ak = set(b[tbl]), set(a[tbl])
only_b, only_a = bk-ak, ak-bk
diff = [k for k in bk&ak if b[tbl][k]!=a[tbl][k]]
nt_diff = [k for k in diff if k not in targets]
nt_added = [k for k in only_a if k not in targets]; nt_removed=[k for k in only_b if k not in targets]
same_nontarget = len([k for k in bk&ak if k not in targets and b[tbl][k]==a[tbl][k]])
...
if nt_diff or nt_added or nt_removed: ok=False; ...
print('RESULT:', 'OK(対象外は全社一致)' if ok else 'NG')
bが反映の前、aが反映の後の指紋、targetsが置き換えの対象の会社です。対象外の会社で、指紋が違うもの・後から増えたもの・消えたものが1つでもあれば、NGになります。
3. 対象の会社は、計画の期間の前と後を分けて指紋を作る
...
for tbl in ('stock_prices', 'technical_indicators'):
cur.execute(f"""select company_id,
count(*) filter (where time < %s), bit_xor(hashtextextended(t::text,0)) filter (where time < %s),
count(*) filter (where time >= %s), bit_xor(hashtextextended(t::text,0)) filter (where time >= %s)
from {tbl} t where company_id = any(%s) group by company_id""", (lo, lo, hi, hi, ids))
res[tbl] = {str(r[0]): list(r[1:]) for r in cur.fetchall()}
loが計画の期間の始まりの日、hiが終わりの日の翌日です。filter (where time < %s)の側が期間より前の件、filter (where time >= %s)の側が期間より後の件で、それぞれ件数と指紋を作ります。期間の中の件は、この指紋には入れていません。
ここまでのまとめ(前半)
- 置き換えた750件は、日付ずれの復元280件・18営業日の途中経過値366件・穴埋めに戻した51件・その他50件・穴埋めの株価の直し3件です。印だけ付けて残した5件は、750件に入っていません
- 置き換えは件数が変わらない操作なので、件数だけでは、直した所以外が変わっていないことを確かめられません
- 会社ごとの指紋を反映の前後で比べて、置き換えの対象でなかった4,571社は、株価・指標・企業情報とも全社で一致しました
- 対象の28社でも、計画の期間より前と後の株価は、28社とも変わっていませんでした
- 後半では、計画の期間の中を1件ずつ突き合わせた結果、指標を計算し直した結果、まだ確かめていないことを書きます
原因について
取得元に再度問い合わせても、今も同じ値が返ってきます。取り込みのときだけに起きた不具合ではなく、取得元の側に残っている値とみています。なぜ取得元にその値が残っているのかについては現在調査中ですが、切り分けにより取得元の問題の可能性が高いと見ています。なぜ月〜木だけなのかは、調査中の段階です。原因と範囲の調査には着手しています。
気になる点・ご質問があればコメントでお知らせください。