Python量化回測入門教程:用真實行情數據一步步完成策略回測

Python量化回測入門教程:用真實行情數據一步步完成策略回測
回測是指在歷史數據上模擬交易策略,看它過去會有怎樣的表現。這是發現一個想法行不通的最便宜方式——在真金白銀替你發現之前。本教程將使用 pandas 和 iTick API 的真實 OHLCV 數據,在 Python 中完成一個完整、嚴謹的回測。
準備工作
- Python 3.9+,安裝
pandas、numpy、requests - 一個免費的 iTick API Key,用於獲取歷史數據
- 大約 20 分鐘
pip install pandas numpy requests
第一步:下載歷史 OHLCV 數據
import requests
import numpy as np
import pandas as pd
TOKEN = "YOUR_API_TOKEN"
HEADERS = {"accept": "application/json", "token": TOKEN}
def load_bars(asset, region, code, ktype=8, limit=1000):
url = f"https://api.itick.org/{asset}/kline"
params = {"region": region, "code": code, "kType": ktype, "limit": limit}
rows = requests.get(url, params=params, headers=HEADERS, timeout=10).json()["data"]
df = pd.DataFrame(rows).rename(columns={"t": "time", "o": "open", "h": "high",
"l": "low", "c": "close", "v": "volume"})
df["time"] = pd.to_datetime(df["time"], unit="ms", utc=True)
return df.set_index("time").sort_index().astype(float)
df = load_bars("stock", "US", "SPY")
print(df.tail())
kType 選擇K線週期(見文檔),日線是不錯的起點。對數據格式不熟悉?請先閲讀 OHLCV數據是什麼。
第二步:定義策略
我們測試經典的 EMA 交叉:20 EMA 在 50 EMA 上方時持有,否則空倉。(背景知識:EMA是什麼)
fast, slow = 20, 50
df["ema_fast"] = df["close"].ewm(span=fast, adjust=False).mean()
df["ema_slow"] = df["close"].ewm(span=slow, adjust=False).mean()
# 信號基於當日收盤價計算……
df["signal"] = (df["ema_fast"] > df["ema_slow"]).astype(int)
# ……但只能在下一根K線交易 -> 向後平移一位,避免未來函數
df["position"] = df["signal"].shift(1).fillna(0)
shift(1) 是本教程最重要的一行。沒有它,回測會"提前知道"當日收盤價再按該價成交——這是典型的未來函數(前視偏差),幾乎能讓任何策略看起來都很出色。
第三步:計算收益(含交易成本)
fee = 0.0005 # 單邊 5 個基點,請根據券商和市場調整
df["ret"] = df["close"].pct_change().fillna(0)
df["trades"] = df["position"].diff().abs().fillna(0)
df["strategy_ret"] = df["position"] * df["ret"] - df["trades"] * fee
df["equity"] = (1 + df["strategy_ret"]).cumprod()
df["buy_hold"] = (1 + df["ret"]).cumprod()
第四步:評估績效
def stats(returns, periods_per_year=252):
equity = (1 + returns).cumprod()
years = len(returns) / periods_per_year
cagr = equity.iloc[-1] ** (1 / years) - 1
vol = returns.std() * np.sqrt(periods_per_year)
sharpe = returns.mean() / returns.std() * np.sqrt(periods_per_year) if returns.std() else 0
drawdown = equity / equity.cummax() - 1
return {"年化收益": f"{cagr:.2%}", "年化波動": f"{vol:.2%}",
"夏普比率": round(sharpe, 2), "最大回撤": f"{drawdown.min():.2%}"}
print("策略:", stats(df["strategy_ret"]))
print("買入持有:", stats(df["ret"]))
print("交易次數:", int(df["trades"].sum()))
| 指標 | 含義 |
|---|---|
| 年化收益(CAGR) | 複合年增長率 |
| 年化波動 | 收益率的年化標準差 |
| 夏普比率 | 單位風險的收益(此處忽略無風險利率) |
| 最大回撤 | 從高點到低點的最大虧損——你能承受嗎? |
股票日線使用 periods_per_year=252,加密貨幣約 365,日內K線按比例換算。
第五步:換個市場測試
iTick 所有資產類別使用同一數據結構,只需改一行即可在外匯、黃金或比特幣上運行同一回測:
eurusd = load_bars("forex", "GB", "EURUSD")
btc = load_bars("crypto", "BA", "BTCUSDT")
常見回測錯誤
- 未來函數——使用決策時尚不可得的信息,務必平移信號。
- 忽略成本——佣金、點差和滑點可能讓盈利策略變成虧損。
- 過擬合——測試上百組參數只留最好的。請保留樣本外區間。
- 倖存者偏差——只測試至今仍存在的股票。
- 數據太少——只在一輪牛市中測試過的策略等於沒測試。
從回測到實盤
策略通過樣本外檢驗後,再接入實時行情。使用 iTick WebSocket 行情推送 獲取實時報價與逐筆數據,增量更新指標,先模擬交易再上實盤。
常見問題
Python 回測用什麼庫最好? 學習階段用純 pandas(如上)最能看清每一步;大型項目可使用 Backtrader、vectorbt 等框架,提供訂單模擬與報告功能。
需要多少歷史數據? 要覆蓋不同市場環境——日線策略通常需要數年數據;日內系統更看重交易次數而非年數。
回測用的歷史數據從哪裏獲取?iTick 歷史行情數據API 提供股票、外匯、加密貨幣、指數和期貨的 OHLCV 數據,並有免費套餐可以起步。
開始驗證你的想法
免費獲取 API Key,幾分鐘內下載歷史數據,看看你的策略是否真的有效。