第 06 課:データエンジニアリングの厳しい現実

データ問題はモデル問題よりも多くの戦略を殺します。


悪夢の週末

金曜日の夜11時、あるエンジニアが慎重に開発した米国株クオンツ戦略をデプロイしました。バックテストで年間80%リターン、シャープレシオ2.5、すべてが完璧に見えました。

月曜日の朝9時、彼はアラートメッセージで起こされました:「戦略エラー、データを取得できません。」

彼は起きて確認すると、Alpha Vantage APIが429 Too Many Requestsを返していました。彼のプログラムは1分間に10回リクエストしており、無料アカウントのAPIレート制限(1分間に5回)を超えていました。

それを修正した後、戦略はついに動き始めました。

月曜日の午後2時、別のアラート:「戦略ポジション異常。」

今回はデータ問題でした:APIが欠損した分足バーを返し、MACDがNaNを計算し、プログラムはNaNを売りシグナルとして扱い、すべてのポジションをクリアしました。

それを修正した後、彼は包括的なデータチェックを行うことにしました。すると、さらに多くの問題を発見しました:

  • 一部のタイムスタンプはUTC、他はET(東部時間)
  • 一部のバーは0出来高(プレマーケット/アフターアワーセッション)
  • 履歴データとリアルタイムデータは異なるフィールド名(close vs Close)

1週間まるまる、彼は戦略コードを1行も書かず - すべてデータ問題の修正に費やしました。

これがデータエンジニアリングの厳しい現実です。


6.1 データソース選択

無料データソース

データソース市場カバレッジメリットデメリット
Yahoo Finance (yfinance)米国株、ETF無料、長い履歴最近の安定性問題が報告されている(API変更/レート制限/欠損データ);堅牢なフォールバックを構築
Alpaca Markets米国株無料IEXデータ、開発者フレンドリーAPI無料ティアはデータカバレッジが制限
Binance API暗号通貨リアルタイム、無料Binanceデータのみ
Alpha Vantage株式、外国為替、暗号通貨無料ティアあり無料ティアのクォータは非常に低いことが多い(制限は変更される;公式サイトで確認)
CCXT暗号通貨統一インターフェース取引所APIに依存

注意:データベンダーは製品、API、クォータ、価格を変更したり、サービスを終了したりする可能性があります。データソースには「上場廃止リスク」があるものとして扱い、常にバックアップを用意してください。

有料データソース(2024-2025参考価格)

データソース市場カバレッジ月額コスト機能
Bloombergグローバル$2,500+機関標準、最高品質
LSEG Workspace (旧Refinitiv)グローバル見積ベース価格LSEG子会社、旧Reutersデータ
Polygon.io (現在Massiveにリブランド)米国株$99+リアルタイム + 履歴、開発者フレンドリー
Nasdaq Data Link (旧Quandl)代替データデータセットごとの価格衛星、消費者データなど

どう選ぶ?

個人学習/研究:
    -> 無料データソース + データ問題を許容

小規模ファンド(< $1M AUM):
    -> 有料データ(基本ティア) + データ検証構築

機関(> $10M AUM):
    -> Bloomberg/Refinitiv + マルチソースクロス検証

核心原則:データ品質は戦略の上限を決定。ゴミを入れれば、ゴミが出る。

データソース進化パス

段階推奨ソース備考
学習/プロトタイピングYahoo Finance (yfinance)無料日次バー、演習に十分
小規模ライブPolygon.io / Alpacaリアルタイムクォート、開発者フレンドリーAPI
機関本番Databento / 取引所直接フィードL1/L2リアルタイムデータ、低レイテンシ

このレッスンの演習はYahoo Financeで完璧に動作します。ライブ取引の準備ができたら、プロフェッショナルグレードのデータソースにアップグレードする必要があります — 詳細はレッスン19を参照してください。


6.2 APIの辛い現実

書くと思うコード vs 現実

あなたが思うこと:

data = api.get_history("AAPL", days=365)

現実:

疑似コードリファレンス(yfinance / alpaca / ccxtなどの特定のSDKに置き換える)
# 注意:これは堅牢なデータ取得設計パターンを示す疑似コードです
# api、log、EmptyDataError、RateLimitErrorは特定のライブラリに置き換える必要があります

import time
from typing import Optional
import pandas as pd

def get_history_robust(
    symbol: str,
    days: int,
    max_retries: int = 5,
    backoff_base: float = 2.0
) -> Optional[pd.DataFrame]:
    """
    堅牢なデータ取得関数

    処理される問題:
    - Rate Limiting(指数バックオフリトライ)
    - 空データ(検証 + アラート)
    - 接続エラー(自動再接続)
    - タイムアウト(合理的なタイムアウト設定)
    """
    for attempt in range(max_retries):
        try:
            # タイムアウトを設定(APIコールに置き換える)
            data = api.get_history(
                symbol,
                days=days,
                timeout=30
            )

            # データを検証(汎用ロジック)
            if data is None:
                raise EmptyDataError(f"No data for {symbol}")

            if len(data) == 0:
                raise EmptyDataError(f"Empty data for {symbol}")

            if len(data) < days * 0.9:  # 期待データの90%未満
                log.warning(f"Data incomplete: got {len(data)}, expected ~{days}")

            # 異常をチェック
            if data['close'].isnull().any():
                log.warning("NaN values detected, filling...")
                data['close'] = data['close'].ffill()

            return data

        except RateLimitError:
            wait_time = backoff_base ** attempt
            log.info(f"Rate limited, waiting {wait_time}s...")
            time.sleep(wait_time)

        except ConnectionError:
            log.warning("Connection lost, reconnecting...")
            api.reconnect()
            time.sleep(1)

        except TimeoutError:
            log.warning("Request timeout, retrying...")

        except Exception as e:
            log.error(f"Unexpected error: {e}")
            if attempt == max_retries - 1:
                raise

    return None

一般的なAPI問題

問題症状解決策
Rate Limiting429エラー指数バックオフ + リクエストキュー
欠損データ空のレスポンスまたは部分データマルチソースバックアップ + 欠損検出
データ遅延タイムスタンプの遅れ遅延を記録 + 戦略を調整
フォーマット不一致フィールド名が変わる統一データアダプタ層
不安定な接続切断、タイムアウトハートビート検出 + 自動再接続

本番ノート:正規データモデル 異なる市場とデータソースは異なるシンボル形式を使用します(600519.SHAAPL0700.HKBTC/USDT)。本番システムには、外部形式を内部標準に変換する統一正規化層(Normalizer)が必要です(例:SYMBOL.EXCHANGE:AAPL.NASDAQ0700.HKEX)。これはマルチマーケット取引の前提条件です。一般的なシンボル解決の落とし穴に注意してください:上場廃止ティッカーが静かに失敗、企業行動がシンボルを変更、マルチパートティッカー(例:BRK.B)など。詳細はレッスン19を参照してください。

Rate Limitingの実際のコスト

100シンボルの分足バーが必要だとします:

API制限完了時間
1リクエスト/秒100秒
10リクエスト/秒10秒
無制限< 1秒

高頻度戦略には高いAPIクォータが必要 - これが有料データソースの核心価値です。


6.3 時間調整問題

タイムゾーンの混乱

異なるデータソースは異なるタイムゾーンを使用:

データソースデフォルトタイムゾーン
BinanceUTC
Yahoo Finance取引所現地時間
中国A株UTC+8
米国株ET(東部時間)

処理しないと:

  • 9:30 A株データ + 9:30米国株データ -> 実際には12時間離れている
  • 戦略は「同じ瞬間」のデータを使用するが、実際には異なる瞬間

解決策:すべてをUTCに変換、UTCで保存して計算。

import pandas as pd

def normalize_timezone(df, source_tz='UTC'):
    """UTCに標準化"""
    if df.index.tz is None:
        df.index = df.index.tz_localize(source_tz)
    df.index = df.index.tz_convert('UTC')
    return df

ティックからローソク足への集計

ティックデータをローソク足に集計する際の注意:

問題説明
始値その期間の最初の取引価格
終値その期間の最後の取引価格
高値その期間の最高取引価格
安値その期間の最低取引価格
出来高その期間のすべての取引の合計

:特定の分に取引がない場合は?

オプション1:OHLCを前の分の終値で埋める
オプション2:欠損としてマーク、後で処理
オプション3:その時点を集計から除外

異なるアプローチは異なる指標計算につながります。処理ルールを標準化する必要があります

クロスアセットデータ調整

株式、先物、外国為替は異なる取引時間:

資産取引時間
中国A株9:30-11:30、13:00-15:00
米国株9:30-16:00 ET
外国為替ほぼ24/7
暗号通貨24/7

戦略がクロスアセットシグナルを必要とする場合:

  • A株がクローズするとき、米国はまだオープンしていない
  • A株クローズ + 米国オープンを使用?時間ギャップがある
  • 慎重な「同期ポイント」設計が必要

6.4 データ品質問題

異常検出と処理

異常タイプ検出方法処理
価格ジャンプ価格変動 > 20%実際か確認(株式分割?)
出来高異常0または極端な値取引所ステータスを確認
欠損値NaN前方埋めまたは削除
重複データ重複タイムスタンプ最初または最後を保持
def detect_anomalies(df, price_col='close', volume_col='volume'):
    """データ異常を検出"""
    issues = []

    # 価格ジャンプ
    returns = df[price_col].pct_change()
    jumps = returns.abs() > 0.20
    if jumps.any():
        issues.append(f"Price jumps detected: {jumps.sum()} times")

    # ゼロ出来高
    zero_volume = df[volume_col] == 0
    if zero_volume.any():
        issues.append(f"Zero volume: {zero_volume.sum()} bars")

    # 欠損値
    nulls = df.isnull().sum()
    if nulls.any():
        issues.append(f"Null values: {nulls.to_dict()}")

    # 重複タイムスタンプ
    duplicates = df.index.duplicated()
    if duplicates.any():
        issues.append(f"Duplicate timestamps: {duplicates.sum()}")

    return issues

株式配当と分割調整

株式の履歴価格には「調整」が必要:

イベント実際の変更未調整データ調整データ
2対1分割株式2倍、価格半分前後で価格の崖滑らかで連続
配当権利落ち控除権利落ち日のギャップ履歴価格が調整される

調整しない結果:

  • バックテストで、戦略が権利落ち日に偽シグナルを生成
  • リターン計算が極端な値を示す

先物ロール処理

先物契約は満期になり「ロール」が必要:

2024年1月:IF2401(1月契約)を保有
1月満期前:IF2402(2月契約)に切り替え

問題:IF2401は4000でクローズ、IF2402は4050でクローズ
      実際の価格変動ではなく、異なる契約

解決策:

  • 接続時にスプレッドを計算、履歴価格を調整
  • または「連続主契約」データを使用(データベンダーが事前処理)

6.5 サバイバーシップバイアス

サバイバーシップバイアスとは?

「今日も存在する株式」のみでバックテストすると:

2010年株式プール:1000株
2024年にまだ存在:800株
上場廃止/破産:200株

バックテストは800のみを使用、失敗した200社を無視
-> バックテストリターンが膨張

サバイバーシップバイアスはどれほど深刻?

研究結論
学術研究年間リターンが1-3%膨張
バリュー投資戦略バイアスがより深刻(安い株式は上場廃止の可能性が高い)
小型株戦略バイアスが最大

どう避ける?

方法難易度有効性
上場廃止株を含むデータベースを使用最も正確
履歴インデックス構成銘柄を使用かなり正確
結論でバイアスを認める少なくとも正直

有料データソースは通常「サバイバーシップバイアスフリー」データセットを提供 - 支払うもう一つの価値。


6.6 代替データ入門

代替データとは?

従来のデータ:価格、出来高、財務諸表 代替データ:その他すべて

タイプデータソース応用
衛星データ駐車場の車数、オイルタンクレベル小売収益を予測、石油在庫
テキストデータニュース、ソーシャルメディア、決算電話センチメント分析、イベント駆動
クレジットカードデータ支出統計企業収益を予測
WebトラフィックWebサイト訪問eコマースパフォーマンスを予測
GPSデータ電話位置フットトラフィック分析

代替データの課題

課題説明
高ノイズ信号/ノイズ比が価格データよりはるかに低い
コンプライアンスリスクプライバシー問題、データソースの合法性
速いAlpha減衰広く使用されると、利点が消える
高コストデータ自体が高価 + 処理コスト

マルチAgent視点

Data Pipeline Architecture

6.7 データパイプライン設計原則

3つの原則

原則説明
不変性生データを決して変更しない、処理後に新しいファイルを生成
追跡可能性各データポイントのソース、処理時間、処理バージョンを記録
冗長バックアップ少なくとも2つのデータソースでクロス検証

データパイプラインアーキテクチャ

データ収集層
    |
    |-> 生データストレージ(不変)
    |         |
    |         v
    |-> データクリーニング層
    |     - 異常処理
    |     - 欠損値埋め
    |     - タイムゾーン標準化
    |         |
    |         v
    |-> 特徴量エンジニアリング層
    |     - テクニカル指標計算
    |     - ラベル生成
    |         |
    |         v
    --> 準備完了データストレージ -> 戦略使用

監視とアラート

監視項目推奨しきい値アラートアクション
データ遅延> 1分警告
欠損データ率> 1%警告
異常比率> 0.1%調査
APIエラー率> 5%戦略一時停止

コード実装(オプション)

データ品質チェックフレームワーク

import pandas as pd
from dataclasses import dataclass
from typing import List

@dataclass
class DataQualityReport:
    symbol: str
    start_date: str
    end_date: str
    total_rows: int
    missing_rows: int
    null_values: dict
    anomalies: List[str]
    is_valid: bool

def check_data_quality(df: pd.DataFrame, symbol: str) -> DataQualityReport:
    """包括的データ品質チェック"""

    anomalies = []

    # 時間連続性をチェック
    # 注意:この例は*日次*バーを想定し、営業日を大まかな期待値として使用します。
    # /ティックデータの場合、取引所の取引セッション、休日、ランチブレーク(ある場合)などに基づいて
    # expected_indexを生成する必要があります。
    idx = pd.DatetimeIndex(df.index)
    expected_index = pd.bdate_range(start=idx.min().normalize(), end=idx.max().normalize())
    expected_rows = len(expected_index)
    actual_rows = len(df)
    missing_rows = expected_rows - actual_rows

    if missing_rows > expected_rows * 0.05:
        anomalies.append(f"High missing rate: {missing_rows/expected_rows:.1%}")

    # NULL値をチェック
    null_counts = df.isnull().sum().to_dict()

    # 価格ジャンプをチェック
    if 'close' in df.columns:
        returns = df['close'].pct_change()
        extreme_moves = (returns.abs() > 0.2).sum()
        if extreme_moves > 0:
            anomalies.append(f"Extreme price moves: {extreme_moves}")

    # 出来高をチェック
    if 'volume' in df.columns:
        zero_volume = (df['volume'] == 0).sum()
        if zero_volume > 0:
            anomalies.append(f"Zero volume periods: {zero_volume}")

    # タイムスタンプ重複をチェック
    duplicates = df.index.duplicated().sum()
    if duplicates > 0:
        anomalies.append(f"Duplicate timestamps: {duplicates}")

    return DataQualityReport(
        symbol=symbol,
        start_date=str(df.index[0]),
        end_date=str(df.index[-1]),
        total_rows=actual_rows,
        missing_rows=missing_rows,
        null_values=null_counts,
        anomalies=anomalies,
        is_valid=len(anomalies) == 0
    )

レッスン成果物

このレッスンを完了すると、以下が得られます:

  1. データ問題の深い理解 - データエンジニアリングがクオンツの主戦場であることを知る
  2. APIベストプラクティス - 堅牢なデータ取得コードフレームワーク
  3. データ品質チェック能力 - 一般的なデータ問題を識別して処理できる
  4. データパイプライン設計思考 - 本番グレードのデータシステムアーキテクチャを理解

重要ポイント

  • 無料と有料データソースのトレードオフを理解
  • APIレート制限、欠損データなどの処理方法をマスター
  • タイムゾーンの混乱、サバイバーシップバイアスなどの隠れた問題を認識
  • 代替データの価値と課題を理解
  • データパイプライン設計原則を理解

拡張読書


次のレッスンプレビュー

レッスン 07: バックテストシステムの落とし穴

データが問題なくても、バックテストはあなたを欺く可能性があります。Look-Ahead Bias、過剰適合、取引コストの無視...これらの罠は、見た目には完璧な戦略を無数にライブ取引で惨めに失敗させました。次のレッスンでバックテストの真実を明らかにします。

この章を引用する
Zhang, Wayland (2026). 第 06 課:データエンジニアリングの厳しい現実. In AIクオンツ取引:ゼロからイチへ. https://waylandz.com/quant-book-ja/Lesson-06-The-Harsh-Reality-of-Data-Engineering
@incollection{zhang2026quant_Lesson_06_The_Harsh_Reality_of_Data_Engineering,
  author = {Zhang, Wayland},
  title = {第 06 課:データエンジニアリングの厳しい現実},
  booktitle = {AIクオンツ取引:ゼロからイチへ},
  year = {2026},
  url = {https://waylandz.com/quant-book-ja/Lesson-06-The-Harsh-Reality-of-Data-Engineering}
}