このプロジェクトについて
EDINET(有価証券報告書等)の開示データをもとに、日本株の財務指標・投資判断スコアを日次で算出・公開しているサービスです。
公開サイトはこちら → https://stocks.happy-life-design.net
前半の記事(2026-10-09公開)はこちら → https://qiita.com/rightcross-ae86/items/0166a0094a3cbb52c541
18営業日の記事(2026-10-08公開)はこちら → https://qiita.com/rightcross-ae86/items/cf906f809d610bde52a8
この記事について
この記事は、2部構成の後半です。前半(2026-10-09公開の記事。以下、本文で「前半」と書くのは、全てこの2026-10-09公開の記事のことです)では、2026-10-08公開の記事(以下、本文で「18営業日の記事」と書きます)で書いた、外部サイトの値への置き換え(28社・750件)が、直した所以外は変わっていないことの確かめ方を、4つのうち2つ書きました。置き換えの対象でなかった4,571社は、株価・指標・企業情報とも全社で一致したこと、対象の28社でも、置き換えを計画した期間より前と後の株価は変わっていないことです。
この後半では、残りの2つ(計画の期間の中を1件ずつ突き合わせる確かめ方と、指標の計算し直し)と、未確認事項を書きます。
この記事で扱う28社・750件は、置き換えの最初の分です。残りの会社の外部サイトの値の取得と置き換えは、その後も進めていて、2026-10-09の夜の時点で165社になっています(詳しくは「未確認事項」に書きました)。2026-10-08公開の記事と前半に書いた「28社」は、最初の分の数字です。置き換えた750件を外部サイトから取り直して突き合わせる最終確認は、まだ行っていません。開発の記録は2026-10-09の夜までのもので、それ以降の作業は反映していません。
この記事では、次のように呼びます。
- 「件」: 1社の1日分の株価(始値・高値・安値・終値・出来高)
- 「取得元」: 株価データを取り込んでいる元のサービス
- 「外部サイト」: 値の照合に使う、別の株価サイト
- 「穴埋め」: 取引がなかった日に、前の日の終値のまま、出来高0で入れてある株価
- 「計画の期間」: 置き換えを計画した日付の範囲
- 「指標」: 株価から計算している数値。移動平均・MACD・RSI・ボリンジャーバンド・OBV(出来高の累積)など
先に結論
- 計画の期間の中にある7,896件を、反映前に保存したバックアップと1件ずつ突き合わせました。計画の750件は、すべて期待した値でした。計画に載っていない7,146件は、全ての列(更新日時を含む)が反映前と同じでした。追加された件も、消えた件も、0でした
- 置き換えた28社の指標を、株価だけから別に計算し直して比べました。最大の差は0.00005でした。最初に変更した日より前の指標118,867件は、反映前と一致しました
- 前日と同じ値が続く件は、28社で、反映前の137件から、反映後の0件になりました
- 未確認事項があります。置き換えた750件を外部サイトから取り直して突き合わせる最終確認が、その1つです
確かめ3: 計画の期間の中を、1件ずつ突き合わせる
計画の期間の中は、会社ごとの指紋ではなく、1件ずつ確かめます。反映の前に、対象の28社の計画の期間の中の全件(7,896件)を、全ての列のまま保存しています(バックアップ)。反映のあとに、同じ範囲の全件を読み取り専用で取り直し、バックアップと突き合わせます。
確かめるのは、次の3点です。
- 追加された件と、消えた件。追加された件は計画の追加分だけ、消えた件は0であること
- 計画に載っている750件。今の値が計画の期待した値と一致し、値の出どころが外部サイトを示す値になり、調整後の終値がNULLであること。売買代金・出来高加重平均・日付は、反映前から変わっていないこと
- 計画に載っていない件。更新日時を含む全ての列が、反映前と同じであること
結果は、次のとおりでした。
- 計画の750件のうち、期待した値と違うものは0件
- 計画に載っていない7,146件のうち、反映前と違うものは0件
- 追加された件も、消えた件も、0件
確かめ4: 指標を、株価だけから別に計算し直す
指標は株価から計算しているので、株価を置き換えると、指標も変わります。置き換えた28社の指標は、全履歴を計算し直しました(反映前の指標は170,305件)。この計算し直しが合っていることを、指標を作るプログラムとは別に作った計算で確かめます。
確かめるのは、次の3点です。
- 指標の日付の集合が、株価の日付の集合と同じであること(株価と指標が1対1になっている)
- 終値と出来高だけから、指標を別に計算し直した値が、データベースの値と許容の範囲で一致すること
- 各社の「最初に変更した日」より前の指標が、反映前のバックアップと一致すること。置き換えた日より前の指標は、変わらないはずだからです
結果は、次のとおりでした。
- 指標の日付の集合は、28社とも株価と一致しました
- 別に計算し直した値との差は、最大で0.00005でした
- 各社の最初に変更した日より前の指標118,867件は、反映前と一致しました。最大の差は0.0001で、1社の1日分のボリンジャーバンドの丸めの揺れでした
技術的な補足(実際に使ったコード)
ここに載せるのは、実際に使ったスクリプトからの抜粋です。実物と変えたのは1か所だけで、source(値の出どころ)に入っている外部サイトの名前を'(外部サイト名)'にしました。長い箇所は...で省略し、共通のインデントを除きました。データベースの接続情報の行は載せていません。
1. 計画の期間の中を、1件ずつバックアップと突き合わせる
cur_rowsが反映したあとの件、bkが反映前に保存したバックアップ、plannedが置き換えの計画です。dbは反映後の1件の各列の値、expは計画の期待した値(始値・高値・安値・終値・出来高)です。
added = set(cur_rows) - set(bk); removed = set(bk) - set(cur_rows)
ins_keys = {k for k, r in planned.items() if r.action == 'INSERT'}
print(f'(1) 追加された行={len(added)} (計画INSERT={len(ins_keys)}) 一致={added == ins_keys} / 消えた行={len(removed)}')
bad_pl = bad_un = n_pl = n_un = 0; bad_examples = []
for k, db in cur_rows.items():
...
if k in planned:
r = planned[k]; exp = [Decimal(x) for x in r.after.split('|')]
b = bk[k]
ok = ([N(db[3]), N(db[4]), N(db[5]), N(db[6]), N(db[8])] == [*exp[:4], exp[4]] and db[11] == '(外部サイト名)' and db[7] is None
and N(db[9]) == N(b['turnover']) and N(db[10]) == N(b['vwap']) and str(db[2]) == b['trade_date'])
n_pl += 1
if not ok: bad_pl += 1; bad_examples.append(k)
else:
n_un += 1
if k in bk and not same_all(db, bk[k]): bad_un += 1; bad_examples.append(k)
print(f'(2) 計画の行 {n_pl}行中 期待値と不一致={bad_pl}')
print(f'(3) 計画に無い行 {n_un}行中 適用前と全列不一致={bad_un}')
- 計画に載っている件(
if k in planned)は、始値・高値・安値・終値・出来高が期待した値で、db[11](値の出どころ)が外部サイトを示す値、db[7](調整後の終値)がNULLかを確かめます。あわせて、db[9](売買代金)・db[10](出来高加重平均)・db[2](日付)が、バックアップから変わっていないことも確かめます - 計画に載っていない件(
else)は、same_allで、更新日時を含む全ての列がバックアップと同じかを確かめます
2. 指標を、株価だけから別に計算し直して比べる
cが終値、vが出来高、indが計算し直した指標です。...の所では、移動平均を計算しています。
c = s['c'].reset_index(drop=True); v = s['v'].reset_index(drop=True); ind = {}
...
ind['ema12'] = c.ewm(span=12, adjust=False).mean(); ind['ema26'] = c.ewm(span=26, adjust=False).mean()
macd = ind['ema12'] - ind['ema26']; sig = macd.ewm(span=9, adjust=False).mean(); ind['macd'] = macd; ind['macd_signal'] = sig; ind['macd_hist'] = macd - sig
dl = c.diff(); g = dl.clip(lower=0).ewm(com=13, adjust=False).mean(); l = (-dl).clip(lower=0).ewm(com=13, adjust=False).mean()
rsi = 100 - 100 / (1 + g / l.replace(0, np.nan)); rsi = rsi.where(~((l == 0) & (g > 0)), 100.0); rsi = rsi.where(~((l == 0) & (g == 0)), 50.0); ind['rsi14'] = rsi
bm = c.rolling(25, min_periods=25).mean(); bs = c.rolling(25, min_periods=25).std(); ind['bb_middle'] = bm; ind['bb_upper'] = bm + 2 * bs; ind['bb_lower'] = bm - 2 * bs; ind['bb_width'] = (4 * bs) / bm.replace(0, np.nan)
ind['obv'] = (np.sign(c.diff()).fillna(0) * v).cumsum()
...
for col in COLS:
db = t[col].reset_index(drop=True); me = ind[col]
if not bool((db.isna() == me.isna()).all()): nullbad.append((cid, col)); co_bad = True; continue
m = db.notna() & me.notna(); dmax = float((db[m] - me[m]).abs().max()) if m.any() else 0.0
worst[col] = max(worst[col], dmax)
if dmax > tol[col]: co_bad = True
dbがデータベースの指標、meが株価から別に計算し直した指標です。まず、値のない(NULL)位置が一致するかを見て、次に、値のある位置の差の最大値(dmax)を求めます。差が列ごとの許容(tol)を超えた会社を、不一致として数えます。許容は、多くの列が0.001、ボリンジャーバンドの幅が0.00001、OBV(出来高の累積)が0.5です。
確かめた結果の一覧
| 確かめ | 結果 |
|---|---|
| 1. 置き換えの対象でなかった4,571社の指紋 | 株価・指標・企業情報とも、全社で一致 |
| 2. 対象の28社の、計画の期間の外の株価 | 期間より前は28社とも一致、期間より後も28社とも一致 |
| 3. 計画の期間の中の7,896件 | 計画の750件は全て期待した値。計画に載っていない7,146件は全ての列が反映前と同じ。追加・消えた件は0 |
| 4. 置き換えた28社の指標 | 日付の集合は株価と一致。別に計算し直した値との差は最大0.00005。最初に変更した日より前の指標118,867件は反映前と一致 |
| 前日と同じ値が続く件(出来高が0より大きく、高値と安値が違うもの)28社 | 反映前137件、反映後0件 |
確かめ1と確かめ2は、前半に書いたものです。
未確認事項
- 置き換えた750件を、外部サイトから取り直して突き合わせる最終確認: 今回の確認は、取得して保存してあった外部サイトの値が基準です。取り直した値との突き合わせは、まだ行っていません
- 外部サイトの値そのものが正しいか: 他に裏付けを取れる元がないため、外部サイトの最終値を正とみなしています。18営業日の記事と同じ前提です
- 28社以外の会社: 置き換えたのは28社だけです。この記事で確かめたのは、最初の28社・750件です。置き換えの対象の746社のうち、2026-10-09の夜の時点で、外部サイトの値を取得できたのが165社(最初の28社を含む)、取得できなかったのが121社(ETFなど、外部サイトに該当の期間の履歴がない)、まだ取得していないのが460社です。取得できた165社は、全て置き換えを終えています。最初の28社のあとの137社(63社と74社)は、2026-10-09に、この記事と同じ種類の確かめ(会社ごとの指紋・1件ずつの突き合わせ・指標の計算し直し)をしましたが、その結果はこの記事には含めていません。746社の外の約2,100社は、取得も置き換えもこれからです(取得は2026-10-10以降の計画です)
- 印だけ付けて残した5件: 前半に書いた5件は、置き換えていません
ほかに確認したこと
- 更新日時: 置き換えた件の更新日時は、反映した時刻になります。毎日の指標の計算は、直近1日に更新された会社の全履歴を計算し直す作りで、置き換えた28社がその対象に入りますが、影響はないと判断しています
- 保存領域の圧縮: データベースは、古い期間の株価を、週ごとのまとまりに圧縮して保存しています。反映で触った週のまとまりが、いったん「一部だけ圧縮された」状態になりました(2026-10-05)。自動の圧縮処理で元に戻る想定で、2026-10-07の確認では、通常の状態に戻っていました
原因について
取得元に再度問い合わせても、今も同じ値が返ってきます。取り込みのときだけに起きた不具合ではなく、取得元の側に残っている値とみています。なぜ取得元にその値が残っているのかについては現在調査中ですが、切り分けにより取得元の問題の可能性が高いと見ています。なぜ月〜木だけなのかは、調査中の段階です。原因と範囲の調査には着手しています。
前半(2026-10-09公開)を書いた時点から、原因について新しく分かったことはありません。進めたのは範囲の調査です。途中経過値の置き換えは、最初の366件のあと、2026-10-09に1,848件増えて、あわせて2,214件になりました。置き換えの対象の746社の外の約2,100社(38,066件)も数え、取得の計画を立てました。ほかにも同じ形の単日が3日見つかっていますが、まだ調べていません。
まとめ
- 計画の期間の中の7,896件を、バックアップと1件ずつ突き合わせました。計画の750件は全て期待した値、計画に載っていない7,146件は全ての列が反映前と同じで、追加された件も消えた件も0でした
- 置き換えた28社の指標を、株価だけから別に計算し直して比べ、最大の差は0.00005でした
- 前半の2つとあわせて、直した所以外が変わっていないことを、4つの方法で確かめました。件数だけに頼らず、会社ごとの指紋・1件ずつの突き合わせ・指標の計算し直しを組み合わせました
- 置き換えた750件を外部サイトから取り直して突き合わせる最終確認は、まだです
気になる点・ご質問があればコメントでお知らせください。