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,几分钟内下载历史数据,看看你的策略是否真的有效。