2026年9月3日 · データエンジニアリング

存在しない分: OHLCV履歴のギャップ、取引停止、出来高ゼロのバー

存在しない分: OHLCV履歴のギャップ、取引停止、出来高ゼロのバー

暦年で1-minuteバーがあれば、525,600行になるはずです。2025年のBTCUSDT無期限先物のデータを取得したところ525,557行で、43分足りず、完全性は99.992%でした。同じ取引所、同じ取得処理、同じコードパスで取得した時価総額中位のアルト無期限先物は1,247行不足し、99.76%でした。同じ年の米国株の1分足系列は暗号資産よりおよそ427,000行少なかったものの、これはギャップではなく、市場が閉まるためです。

このうち3つの数字は取り立てて面白くありません。千の言葉に値するのは43です。

525,600うるう年でない年の1-minuteバー数
0.008%2025年のBTCUSDTで欠けていた割合
61%欠損分のうち、ボラティリティ上位10%の時間帯にあった割合

データフレーム上ではすべて穴に見える、4つの異なる事象

43分の話に入る前に、まずは分類です。多くのギャップ処理コードは、そもそもこの段階ですでに間違っています。ローカルのparquetに行がないとき、何が原因だったのかは分かりません。そして原因ごとに、適切な対処は異なります。

種類実際に起きたことデータ上の現れ方適切な対処
取引なし市場は開いていたが、その1分間は誰もスプレッドを越えて取引しなかったエンドポイントによっては出来高ゼロのバー(OHLCがすべて同じ、trades=0)、別のエンドポイントでは行が欠落残す。誰も取引したがらなかったという実際の情報です。
取引所の停止予定の有無にかかわらず、マッチングエンジンが停止行が欠落し、ときには連続して複数行が欠ける古いデータとしてマークし、その間は取引しない。
銘柄の取引停止LULDバンドへの抵触、ニュース待ち、上場廃止の通知行が欠け、その後、取引再開時のオークション約定が記録される古いデータとしてマークし、再開を不連続として扱う。
こちらの不具合ページネーションのレート制限、ページを落としたリトライ、ループ内のタイムゾーン境界のバグ上記と見分けのつかない行の欠落検知して再取得する。修正できるのはこれです。

この表の最初の項目について、取引所ごとに扱いが違います。厄介なのはここです。Coinbaseのローソク足エンドポイントは空の時間枠を丸ごと省くため、流動性の低いペアの履歴には本当の穴がたくさんできます。一方、Binanceのklinesは通常、出来高0でopen=high=low=closeが直前の取引価格に固定された合成バーを返します。同じ事実でもデータの形は異なります。全分足グリッドに合わせてローダーで再インデックスすると、その違いが通知されないまま一方から他方へ変わってしまいます。ギャップ補完を書く前に、使う取引所の仕様を確認してください。書いた後では遅いのです。

アルトで欠けていた1,247分は、ほぼすべて分類1でした。日曜の04:00 UTC、板が薄く、誰も取引していなかった時間です。面倒ではありますが、理解しやすく、戦略もその時間に取引していなかったので、ほぼ無害でした。だからこそ、私は調べるのをやめて43分のほうに戻ったのです。

43分は散らばっていなかった

欠損が一様に起きていたなら、1年の43分はぽつんと孤立した43個の点になり、8日半に1回ずつ現れる程度で、それぞれは丸め誤差のようなものだったでしょう。実際は違いました。欠損は6つのまとまりで起きていました。連続19分が1回、11分が1回、4分が2回、そして2分続くものがいくつかです。43件の偶然ではなく、6件の事象でした。

しかも、これらの事象は私たちが気にする要因に左右されます。取引所は負荷が高いときに停止し、負荷が高くなるのは価格が動いているときです。年間の各時間を実現ボラティリティで区分し、欠損分がどこにあったかを調べたところ、その61%は上位10%の時間帯に集中していました。任意の1分が欠ける無条件確率は0.008%です。ボラティリティ上位10%の時間帯に限ると、およそ0.05%で、6倍高いうえに、さらに固まって発生していました。

つまり、データ品質ダッシュボードの完全性指標は、測るべきものを測っていません。99.992%と聞けば、もう気にしなくてよいデータセットに思えます。しかし実態は、戦略が何もしない時間帯には完全で、あらゆる取引を行う時間帯には穴がある系列です。ボラティリティの拡大をきっかけに動くモメンタムシステムは、見出しの数字が示すよりもはるかに高い確率でギャップに遭遇し、それも取引の途中で遭遇します。

数行後に前方補完が引き起こすこと

この記事を書くきっかけになった失敗を紹介します。19分間の欠損を考えてください。よくあるデータ整形では、全分足グリッドに再インデックスし、直近の終値でOHLCを前方補完し、出来高を0にします。すると系列は連続し、インジケーターもNaNなしで計算できます。

この19本のバーは、high == low == closeです。どれも真の値幅は0です。停止前の値がおよそ240 USDTだったとすると、その時間帯のATR(14)は取引所が復旧する頃にはおよそ34まで低下します。期間内に実在した5本のバーだけで平均を計算することになるからです。これをボラティリティ連動のポジションサイザーに渡すと、よくあるsize = risk_budget / ATR方式では、ポジションサイズが7倍になります。

次の実在するバーは取引再開時の約定価格で、静かな値動きではありません。今回の例では、停止前の最後の終値から1.8%離れた価格で始まりました。バックテストは、実現できない約定と誰も提示していない価格を前提に、1.8%のギャップへ7倍のポジションを何の疑いもなく投入しました。その合成取引1つが、損益曲線上では正反対の方向に、正当な1か月分の損益を上回る影響をもたらしました。データフレームを整えるために書かれたデータクレンジングの1行が、すべての原因でした。

行を補完せずに削除するのも解決策ではありません。見た目が違うだけで、同じバグです。行を削除すると、整数インデックスを使うルックバックが誤った結果を出します。「20-bar EMA」は停止をまたいだ39分間を対象にするようになり、境界をまたぐバー間リターンは1.8%の変動全体を1分の値動きとして扱い、バーごとのボラティリティ推定はそれを60シグマの事象として読み取ります。何も警告は出ません。インデックスは単調増加のままです。

リサンプリングで問題が見えなくなる

ほとんどの分析は1-minuteバーではなく、集約した足で行います。そして集約すると、問題が覆い隠されます。5-minute足にリサンプリングすると、19分の穴は4本のバーになり、最初と最後は一部の時間だけを含みます。Pandasは、残っていた2分から一見まっとうなOHLCを計算し、それを5分すべて揃ったバーと同じようにラベル付けします。出力だけでは両者を区別できません。

私が知る最も手軽な修正方法は、リサンプリングのたびにbars_in_window列を引き継ぎ、決して捨てないことです。各行に整数を1つ持たせれば、後続処理でバーが信頼できるかどうかを判断できます。実行レイヤーが対応できる形式で同じ情報を持つseconds_since_last_real_printも引き継いでいます。

現時点の運用ルール

現在、私たちのエージェントは次の順に適用しています。

  1. 無通知で再インデックスしない。 ローダーは、開始時刻、終了時刻、長さ、そして4つの分類のどれと判断したかを含むギャップ一覧を出力します。欠損が3本未満で、前後のバーの出来高が薄ければ、取引のない1分間と判断します。取引時間中にそれ以上続く欠損は、別の証拠が得られるまでは取引所の停止として扱います。
  2. 判断する前に再取得する。 初期に見つかったギャップの半分は、ページネーションのバグでした。別のエンドポイントや別のベンダーからもう一度取得すれば分類4を解消でき、判断が必要になる前に問題を小さくできます。
  3. ギャップを埋めず、データの古さを判定する。 戦略にはdata_age入力と明確なルールを渡します。最後の実在する約定がN本以上前なら新規エントリーは禁止し、保有ポジションは再開後、ギャップリスクを明示的に割り引いた価格での成行注文によってのみ解消します。実現できない約定は、取引を見送るよりも悪い結果を招きます。
  4. インジケーターには作り物ではなくNaNを渡す。 前方補完した価格を特徴量レイヤーに渡すことはありません。ATRを計算できないなら未定義とし、未定義ならポジションを持ちません。静かな7倍より、明確な失敗のほうがましです。
  5. ギャップ発生条件に応じたパフォーマンスを報告する。 提出するすべてのバックテストで、見出しとなる損益と並べて、取引所停止の前後に行われた取引を除外した損益も示します。その取引が結果を支えているなら、その結果はデータ由来の見せかけです。

今日すぐできる簡単な監査方法です。欠損した分を連続するまとまりに分けてから、バックテストの取引のうち、各まとまりの開始または終了の30分以内にエントリーまたは決済した割合を調べます。1%未満なら、ギャップが結果を左右している可能性は低いでしょう。5%以上なら、損益曲線は取引所の停止についての物語を一部含んでいます。

私が頼りにするようになった手がかりは、欠損の量よりもその形です。出来高の少ない時間帯に散らばる何千もの穴があるデータセットなら、たいてい問題ありません。まとまった欠損がいくつかあるデータセットは、高負荷時に何かが壊れていると示しています。そして高負荷時に壊れるものが、戦略の活動する場所なのです。

OHLCVのギャップデータエンジニアリングバックテストリサンプリング暗号資産先物
← 記事一覧