
如何用 Python 构建加密货币量化交易策略:从行情数据到回测
学习如何用 Python 构建加密货币量化交易策略,涵盖 AllTick 行情数据获取、EMA 交易信号、数据清洗、回测逻辑与交易成本评估。
对于开发加密货币量化交易策略的开发者来说,写出一条买卖信号并不难,难的是确认信号是否建立在可靠的行情数据上,以及回测结果是否真正反映了策略的表现。
例如,使用 BTC/USDT 的小时 K 线测试双均线策略时,如果数据存在重复时间戳、策略提前使用了当前 K 线收盘价,或者忽略了交易手续费,即使回测曲线持续上涨,也不代表策略具有实际价值。
因此,Python 量化交易的关键流程不只是计算指标,而是将行情获取、数据清洗、信号生成和回测评估连接起来。
从可靠的行情数据开始
构建策略前,首先需要确定使用哪种市场数据。对于基于趋势指标的中低频策略,历史 K 线通常足以完成初步研究;如果研究重点是订单簿变化、短期价格冲击或高频交易,则可能需要更细粒度的逐笔行情。
以小时级双均线策略为例,可以通过 AllTick 的加密货币 K 线接口获取历史数据。相关接口使用 HTTP GET 请求,基础路径为 quote-b-api/kline,并通过 token 和 URL 编码的 JSON 查询参数传递认证信息与请求条件。K 线数据包含时间戳、开盘价、最高价、最低价、收盘价等字段。
获取数据后,不应直接进入指标计算。需要先将价格字段转换为数值,统一时间格式,并按时间升序排列、去除重复时间戳。出现缺失 K 线时,也不能直接用前一根收盘价填充,否则可能把没有发生的市场变化带入回测。
用 Python 获取行情并生成交易信号
下面使用 20 周期和 50 周期指数移动平均线(EMA)构建一个简单的趋势策略。当短周期 EMA 高于长周期 EMA 时持有多头仓位,否则空仓。这个例子用于演示量化交易策略的开发与验证流程,不代表经过优化的交易系统。
先安装依赖:
pip install requests pandas设置环境变量 ALLTICK_API_TOKEN,然后运行以下代码:
import os
import json
import uuid
import requests
import pandas as pd
BASE_URL = "https://quote.alltick.co/quote-b-api"
TOKEN = os.environ["ALLTICK_API_TOKEN"]
def fetch_klines(code="BTCUSDT", count=500):
query = {
"trace": str(uuid.uuid4()),
"data": {
"code": code,
"kline_type": 5, # 1小时K线
"kline_timestamp_end": 0,
"query_kline_num": count,
"adjust_type": 0
}
}
response = requests.get(
f"{BASE_URL}/kline",
params={
"token": TOKEN,
"query": json.dumps(query)
},
timeout=15
)
response.raise_for_status()
result = response.json()
if result.get("ret") != 200:
raise RuntimeError(f"API error: {result.get('msg')}")
rows = result.get("data", {}).get("kline_list", [])
if not rows:
raise ValueError("No K-line data returned")
df = pd.DataFrame(rows).rename(columns={
"open_price": "open",
"high_price": "high",
"low_price": "low",
"close_price": "close"
})
df["timestamp"] = pd.to_numeric(df["timestamp"])
df["timestamp"] = pd.to_datetime(
df["timestamp"], unit="s", utc=True
)
for col in ["open", "high", "low", "close"]:
df[col] = pd.to_numeric(df[col], errors="coerce")
return (
df.dropna(subset=["close"])
.sort_values("timestamp")
.drop_duplicates("timestamp")
.set_index("timestamp")
)
df = fetch_klines()
df["ema20"] = df["close"].ewm(
span=20, adjust=False
).mean()
df["ema50"] = df["close"].ewm(
span=50, adjust=False
).mean()
df["signal"] = (df["ema20"] > df["ema50"]).astype(int)
# 使用上一根K线产生的信号,避免提前使用本根收盘信息
df["position"] = df["signal"].shift(1).fillna(0)
df["market_return"] = df["close"].pct_change().fillna(0)
df["turnover"] = (
df["position"].diff().abs()
.fillna(df["position"].abs())
)
# 示例费率:每次仓位变化按5个基点估算
fee_rate = 0.0005
df["strategy_return"] = (
df["position"] * df["market_return"]
- df["turnover"] * fee_rate
)
test = df.iloc[50:].copy()
test["equity"] = (1 + test["strategy_return"]).cumprod()
print("策略累计收益率:", round(
test["equity"].iloc[-1] - 1, 4
))代码中的 BTCUSDT 是示例交易品种代码,实际请求前应确认其与账户当前可用的 AllTick 产品代码一致。接口单次最多返回 500 根 K 线;如果需要更长的回测历史,应使用加密货币接口支持的时间戳参数分批查询,并在合并数据时去重。
回测结果为什么不能只看收益率
上述代码通过 EMA 判断趋势,并将信号延后一个周期使用,避免在同一根 K 线收盘后生成信号、却又假设自己能以该收盘价提前成交。
它还加入了手续费估算,但这仍然只是简化的回测。示例中的费率是占位参数,并非实际交易所费率;滑点、资金费率、订单成交情况和市场冲击也没有纳入模型。因此,输出的累计收益率不能直接作为真实交易表现的预测。
正式评估策略时,至少还应检查最大回撤、交易次数、胜率和扣除成本后的收益,并使用独立的样本外数据检验策略是否仍然有效。对于参数较多的策略,也应避免反复调整参数直到历史曲线看起来最好,以降低过拟合风险。
另一个容易忽视的问题是数据覆盖范围。500 根小时 K 线约覆盖 20 天,不能据此判断策略在不同市场周期中的稳定性。更长周期的历史数据、不同波动环境下的测试,以及对缺失区间的核验,都是回测可靠性的重要组成部分。
从历史回测到实时行情
历史数据适合开发和验证策略,实时行情则可用于后续监控和信号更新。AllTick 提供加密货币行情数据,并支持 REST 与 WebSocket 接入。开发者可以先利用历史 K 线建立研究流程,再根据策略对更新频率和数据粒度的要求,评估是否需要实时行情流。
开始开发前,可查看 AllTick Crypto API 产品页面 和 K 线 API 技术文档,确认可用交易品种、历史数据范围、接口限制和请求参数。
一个可靠的量化交易策略,不只是能够生成买卖信号,更需要能够追溯数据来源、解释成交假设,并在扣除交易成本后接受检验。 只有数据和回测逻辑都经得起核查,策略的历史表现才具有参考价值。