休市提醒日股將於 2026-11-03 休市(文化日),距今天還有 5 天。全部公告
文章

量化回測數據管道:還原、對齊與儲存

回測失真很少源於策略邏輯,多半出在數據管道。本文拆解四個階段——粒度選擇、還原處理、時間對齊、儲存查詢,說明為什麼停牌不能向前填充、為什麼後還原才能保證可復現,並給出完整的 Python 數據準備實現。

回測結果不可信,原因很少出在策略邏輯上,更多出在數據管道。一個看起來能跑通的回測,可能因為還原處理錯誤、時間對齊偏差或缺失值填充,得出了完全失真的結論——而且這些錯誤不會報錯,只會讓回測曲線變好看。

這篇文章講回測數據管道的四個階段,以及每個階段最容易出錯的地方。

回測數據管道的四個階段

階段目標主要風險
採集與粒度選擇拿到正確粒度的原始數據粒度不足導致信號丟失
還原處理價格序列可比拆股分紅造成虛假跳空
時間對齊多標的時間軸一致時區與交易日曆錯位
儲存與查詢高效可復現地取數查詢口徑不一致

四個階段的順序不能顛倒——先採集後還原,再對齊,最後儲存。反過來做會導致大量重複計算。

階段一:粒度選擇決定了策略的上限

粒度選擇的判斷標準很簡單:策略信號的產生頻率,必須高於數據粒度。

  • 按日調倉:日線數據足夠,且體積小、計算快。
  • 日內策略:至少需要分鐘線,具體取決於信號週期。
  • 高頻與微觀結構策略:必須用 tick 數據,且要關注 tick 級的成交方向。

一個常見的錯誤是「用分鐘線回測分鐘級信號」。這樣做會引入系統性偏差:分鐘線只保留該分鐘的 OHLC 四個點,策略在分鐘內的入場與出場價格無法還原,回測結果通常比實際樂觀。

另一個錯誤是反過來——用 tick 數據回測日頻策略。這不會讓結果更準,只會讓計算量和儲存成本高出一個數量級。

階段二:還原處理,最容易出錯的一步

股票發生拆股或分紅時,價格會「跳空」。如果不做還原處理,回測會把這次跳空當成一次真實的暴跌或暴漲,從而產生虛假信號。

還原有前還原與後還原兩種方式,選擇取決於回測類型:

方式特點適用場景
前還原以最新價格為基準向前調整歷史價格技術指標計算、圖表展示
後還原以上市首日為基準向後調整長期收益計算、回測
不還原保留原始成交價需要真實成交價的場景,如撮合模擬

回測場景通常推薦後還原,因為它不會因為新增數據而改變歷史價格,保證回測結果可復現。前還原的歷史序列會隨最新價格變化而變化,用前還原數據做的回測在不同時間跑出來的結果可能不同——這是很隱蔽的復現性問題。

還需要注意:還原因子必須來自可靠數據源,且要覆蓋所有公司行為(拆股、合股、分紅、配股)。漏掉任何一個都會在歷史上留下一個假跳空。

階段三:時間對齊與缺失處理

多標的回測時,時間對齊的核心原則是:不要用「向前填充」掩蓋缺失。

三種缺失需要區別對待:

  • 休市:正常情況,從時間軸上移除即可,不應該算作缺失。
  • 停牌:該標的當日無交易。回測中應保持倉位不變、標記為不可交易,而不是填充價格。
  • 數據缺失:數據源問題,需要告警並補數,絕不能靜默填充。

把停牌當成缺失並向前填充價格,會讓回測產生「零波動區間的虛假持倉」,從而低估風險、高估收益。這是回測失真最常見的原因之一。

階段四:儲存與查詢口徑

最後一步的目標是可復現。同一個回測,在不同時間、不同機器上跑,應該得到完全相同的結果。要做到這一點需要:

  1. 數據版本化:每次回測記錄所用數據的快照標識或時間範圍,而不是「取最新數據」。
  2. 查詢口徑固定:明確還原方式、時區、缺失處理規則,並寫進代碼而不是文檔。
  3. 列式儲存:回測通常只需要少數幾列,列式格式能顯著減少掃描量。
  4. 保留原始數據:聚合數據可以快速迭代,但原始數據是重算的依據,不應丟棄。

三個最常見的回測數據錯誤

  1. 倖存者偏差:只用當前仍在交易的標的回測,退市與併購的標的被排除,結果系統性偏樂觀。
  2. 未來函數:用當日收盤價生成信號,又用當日開盤價成交。這在真實交易中不可能實現。
  3. 填充掩蓋缺失:用向前填充處理停牌,人為壓低波動率。

這三個錯誤都不會讓回測報錯,只會讓曲線變好看。所以回測系統的第一優先級不是「跑得快」,而是「能發現自己的數據問題」。

Python 實現:回測數據準備

Python
import httpx
import pandas as pd

API_KEY = "your_api_key"
BASE = "https://api.tiqex.io/api/v1/market"


def fetch_candles(symbol: str, market: str, interval: str = "1d") -> pd.DataFrame:
    """拉取 K 線並規範化為 UTC 索引的 DataFrame。"""
    resp = httpx.get(
        f"{BASE}/{market}/kline",
        params={"symbols": symbol, "interval": interval},
        headers={"X-API-Key": API_KEY},
        timeout=30,
    )
    rows = resp.json()["data"]
    df = pd.DataFrame(rows)

    # 1) 時間統一為 UTC,避免跨市場錯位
    df["ts"] = pd.to_datetime(df["timestampMs"], unit="ms", utc=True)
    df = df.set_index("ts").sort_index()

    # 2) 校驗時間戳單調遞增,出現回退說明數據有問題
    if not df.index.is_monotonic_increasing:
        raise ValueError(f"{symbol} 時間戳非單調遞增,需人工檢查")

    # 3) 只保留回測需要的列,降低掃描量
    keep = [c for c in ("open", "high", "low", "close", "volume") if c in df.columns]
    return df[keep]


def apply_backward_adjustment(df: pd.DataFrame, factors: pd.Series) -> pd.DataFrame:
    """後還原:以上市首日為基準向後調整,保證歷史價格可復現。"""
    aligned = factors.reindex(df.index, method="ffill")
    out = df.copy()
    for col in ("open", "high", "low", "close"):
        if col in out.columns:
            out[col] = out[col] * aligned
    return out


def mark_trading_gaps(df: pd.DataFrame, calendar) -> pd.DataFrame:
    """把休市 / 停牌 / 缺失分開標記,而不是一律向前填充。"""
    expected = pd.DatetimeIndex(calendar.sessions_in_range(df.index[0], df.index[-1]))
    missing = expected.difference(df.index)
    if len(missing) == 0:
        return df

    # 用顯式標誌記錄,不填充價格
    df = df.reindex(df.index.union(missing))
    df["no_trade"] = df["close"].isna()
    return df


candles = fetch_candles("AAPL", "equity")
print(f"行數 {len(candles)}  區間 {candles.index[0].date()} → {candles.index[-1].date()}")
print(candles.tail(3))

這段代碼體現了三個關鍵約束:時間統一為 UTC、時間戳單調性校驗、缺失用顯式標誌而非填充。apply_backward_adjustment 用後還原保證歷史序列不隨新數據變化,這是回測可復現的前提。

常見問題

回測應該用前還原還是後還原?

通常推薦後還原。後還原的歷史價格不會隨新增數據變化,保證回測結果可復現;前還原的歷史序列會隨最新價格調整,同一策略在不同時間跑出的結果可能不同。

停牌期間應該怎麼處理?

保持倉位不變並標記為不可交易,絕對不要向前填充價格。填充會產生零波動區間的虛假持倉,低估風險並高估收益。

回測需要 tick 數據嗎?

取決於策略週期。日頻與分鐘級策略用 K 線數據即可;只有高頻與微觀結構策略才需要 tick 數據。用 tick 回測日頻策略只會增加成本,不會提高準確性。

為什麼同樣的回測跑兩次結果不一樣?

通常是數據口徑不固定:用了「取最新數據」而不是固定快照,或者用了前還原數據。解決辦法是數據版本化,並把還原方式、時區、缺失規則寫進代碼。

怎麼避免未來函數?

嚴格區分「信號生成時點」與「成交時點」。信號只能使用該時點已經可得的數據,成交價必須用信號之後的價格,並在回測框架裡顯式約束這一點。

下一步

回測數據管道的核心是三個詞:可復現、不填充、顯式標記。完整的數據準備流程見 回測數據準備指南,還原機制的細節見 拆股還原,K 線字段定義見 K 線 OHLCV 字段,場景方案可參考 回測方案。歷史數據的拉取量直接影響 套餐額度,建議先做一次量級估算。