AllTick
バックテストに利用するXAUUSDヒストリカルTickデータのクリーニングと前処理方法
ティックデータクオンツ取引バックテスト

バックテストに利用するXAUUSDヒストリカルTickデータのクリーニングと前処理方法

Pythonを使って過去のXAUUSDティックデータをクリーニングする方法を解説。タイムスタンプの検証、重複データの削除、異常な気配値の検出、売買スプレッドを考慮した信頼性の高いバックテストを紹介します。

AllTick読了 1 分

金取引戦略は、バックテストでは高いパフォーマンスを示していても、実際の市場では異なる結果になることがあります。クオンツトレーダーにとって、その原因は戦略そのものではなく、評価に使用した過去のXAUUSDティックデータにあるかもしれません。

重複した気配値はイベント数を歪め、不正確なタイムスタンプはシグナルの発生順序を変えてしまう可能性があります。また、Bid-Askスプレッドを考慮しなければ、想定収益を過大評価するおそれがあります。そのため、信頼性の高いバックテストには、実際の市場変動を残しながら無効なレコードを除外する、一貫したデータクリーニングのプロセスが必要です。

この記事では、主要な前処理手順、再利用可能なPythonコードの例、そして信頼性の高いXAUUSDバックテスト環境を構築する際に考慮すべき約定関連の要素を解説します。

前処理の前に過去のティックデータを確認する

過去の気配値データには、一般的にタイムスタンプ、Bid価格、Ask価格が含まれます。ただし、具体的なフィールドはデータソースによって異なるため、クリーニングルールを適用する前にデータ構造を確認してください。

text
import pandas as pd
import numpy as np

df = pd.read_csv("xauusd_tick.csv")

required = {"timestamp", "bid", "ask"}
missing = required - set(df.columns)

if missing:
    raise ValueError(f"Missing columns: {missing}")

print("Original rows:", len(df))
print(df.isna().sum())

変更を加えていない元データのコピーを保存しておきましょう。データを変更する前に、タイムスタンプの形式、タイムゾーン、価格の定義を確認します。たとえば、Unixミリ秒タイムスタンプと、すでに日付形式に変換された文字列では、解析方法が異なります。

タイムスタンプを統一し、重複レコードを削除する

個々の市場イベントを順番に処理する戦略では、時系列の正確性が重要です。データソースがUnixミリ秒タイムスタンプを使用している場合は、変換時に単位を明示します。

text
df["timestamp"] = pd.to_datetime(
    df["timestamp"],
    unit="ms",
    utc=True,
    errors="coerce"
)

invalid_time = df["timestamp"].isna().sum()
df = df.dropna(subset=["timestamp"])

df = df.sort_values(
    "timestamp",
    kind="stable"
).reset_index(drop=True)

before = len(df)

df = df.drop_duplicates(
    subset=["timestamp", "bid", "ask"]
).copy()

print("Invalid timestamps:", invalid_time)
print("Duplicates removed:", before - len(df))

unit="ms"を使用するのは、データソースがミリ秒単位であることを確認できた場合に限ります。タイムスタンプだけを基準にレコードを削除すると、記録時刻が同じでも実際には有効な気配値更新を誤って除外する可能性があります。イベントIDやシーケンス番号が利用できる場合は、それらを使って重複判定を改善できます。

気配値を検証し、異常な価格を調査する

一般的な気配値データでは、BidとAskが存在し、有限の数値であり、かつゼロより大きいことが求められます。BidがAskを上回っている場合は、データが破損していると即断せず、調査対象としてフラグを付けます。

text
df["bid"] = pd.to_numeric(df["bid"], errors="coerce")
df["ask"] = pd.to_numeric(df["ask"], errors="coerce")

valid = (
    np.isfinite(df["bid"]) &
    np.isfinite(df["ask"]) &
    (df["bid"] > 0) &
    (df["ask"] > 0) &
    (df["ask"] >= df["bid"])
)

df["invalid_quote"] = ~valid
df["spread"] = df["ask"] - df["bid"]
df["mid"] = (df["ask"] + df["bid"]) / 2

print("Invalid quotes:", int((~valid).sum()))

大幅な価格変動も慎重に確認する必要があります。経済指標の発表時にはXAUUSDが急変することがあり、そのような動きが実際の市場変動である可能性もあります。そのため、固定のしきい値だけを使って外れ値を自動的に削除すべきではありません。

text
df["mid_return"] = df["mid"].pct_change()

df["review_move"] = (
    df["mid_return"].abs() > 0.005
)

print("Price moves flagged:", int(df["review_move"].sum()))

ここで使用している0.5%というしきい値は説明用の例であり、あらゆる状況に適用できる基準ではありません。フラグが付いたレコードは、前後の気配値、スプレッドの変化、利用可能なデータソースの証拠と照らし合わせて確認します。定めたデータ品質ルールと裏付けとなる証拠に基づいて、除外するかどうかを判断してください。

欠損時間帯を確認し、バックテスト用データを準備する

隣接するタイムスタンプの間隔が空いていても、必ずしもデータが欠損しているとは限りません。通常の市場休場、気配値更新の頻度の変化、データ収集の中断など、さまざまな要因で時間間隔が生じます。

text
df["gap_seconds"] = (
    df["timestamp"].diff().dt.total_seconds()
)

gaps = df[df["gap_seconds"] > 60]

print("Intervals over 60 seconds:", len(gaps))

ここで使用する60秒という基準は、調査対象を抽出するための例です。戦略のサンプリング頻度や、データ提供元が公表している取引時間に応じて調整してください。欠損したティックレコードを補間し、生成した合成価格を実際に観測された市場イベントとして扱うことは避けましょう。

個々の気配値更新を直接利用する戦略もあれば、ティックデータを一定時間ごとのバーに集約する戦略もあります。たとえば、次のコードでは1秒単位のシンプルな気配値系列を作成できます。

text
valid_df = df.loc[
    ~df["invalid_quote"]
].copy()

bars = (
    valid_df.set_index("timestamp")
    .resample("1s")
    .agg({
        "bid": "last",
        "ask": "last",
        "mid": "last"
    })
)

有効な気配値が存在しない時間帯は、空のまま維持されます。これにより、観測データの欠損を実際の市場気配値と誤認することを防げます。

Bid-Askスプレッドと取引コストを考慮する

単一の価格系列だけを使用すると、短期売買戦略のパフォーマンスを過大評価する可能性があります。簡易的な気配値ベースのモデルでは、ロングポジションはAskでエントリーし、Bidで決済します。ショートポジションでは、その逆の価格関係になります。

text
entry_ask = valid_df["ask"].iloc[0]
exit_bid = valid_df["bid"].iloc[-1]

gross_change = exit_bid - entry_ask

print("Gross price change:", gross_change)

この例は、仮定上のロング取引1回におけるスプレッドの影響を示すものであり、完全な取引シミュレーションではありません。より現実的なバックテストでは、適用される手数料、スリッページ、ファイナンスコスト、ポジションサイズ、注文執行ルールなども考慮する必要があります。

シグナルの生成には、意思決定時点ですでに利用可能だった情報だけを使用してください。そうしなければ、先読みバイアスが発生し、実際の取引で得られるパフォーマンスよりも戦略が優れているように見える可能性があります。

過去データとリアルタイムデータの処理方法を統一する

クオンツトレーダーは、タイムスタンプの規則、データ検証ルール、レコードの除外条件を文書化し、過去データの分析とリアルタイムデータ処理の両方で互換性のあるロジックを使用することが重要です。処理方法を統一することで、バックテストと実運用の結果に差が生じた際、その原因を調査しやすくなります。

AllTickはRESTおよびWebSocketの市場データインターフェースを提供しています。金関連の取扱銘柄を確認するには、AllTick 商品APIの製品ページを参照してください。APIドキュメントでは、インターフェースの統合に関する情報を確認できます。

ただし、リアルタイムの気配値にアクセスできることと、過去のティックデータを取得できることは同じではありません。バックテスト用のデータソースを選ぶ前に、必要な過去データのフィールド、データ粒度、保存期間に対応しているかを確認してください。

バックテスト前のデータ品質チェックリスト

バックテスト結果を採用する前に、タイムスタンプと気配値フィールドが有効であること、重複削除のルールが文書化されていること、異常な価格が確認されていること、原因不明の時間的な欠損が調査されていることを確認しましょう。また、約定価格にBid-Askスプレッドが反映され、適用される取引コストが計上されていることも重要です。

データの品質を高めても、戦略の利益が保証されるわけではありません。しかし、再現可能な前処理プロセスを構築することで、クオンツトレーダーは実際の戦略シグナルとデータ品質に起因する問題を区別しやすくなります。

金市場に関連するアプリケーションを開発している場合は、AllTick 商品APIと関連ドキュメントを確認し、利用可能なデータが研究要件を満たしているかを検討してください。

今すぐマーケットデータ配信を開始

数秒で無料 API キーを発行し、1 つのエンドポイントからすべての市場に接続できます。