AllTick
-
量化交易市场数据策略回测加密货币数据

如何用 Python 构建加密货币量化交易策略:从行情数据到回测

学习如何用 Python 构建加密货币量化交易策略,涵盖 AllTick 行情数据获取、EMA 交易信号、数据清洗、回测逻辑与交易成本评估。

AllTick阅读 5 分钟

对于开发加密货币量化交易策略的开发者来说,写出一条买卖信号并不难,难的是确认信号是否建立在可靠的行情数据上,以及回测结果是否真正反映了策略的表现。

例如,使用 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 时持有多头仓位,否则空仓。这个例子用于演示量化交易策略的开发与验证流程,不代表经过优化的交易系统。

先安装依赖:

text
pip install requests pandas

设置环境变量 ALLTICK_API_TOKEN,然后运行以下代码:

text
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 技术文档,确认可用交易品种、历史数据范围、接口限制和请求参数。

一个可靠的量化交易策略,不只是能够生成买卖信号,更需要能够追溯数据来源、解释成交假设,并在扣除交易成本后接受检验。 只有数据和回测逻辑都经得起核查,策略的历史表现才具有参考价值。

立即开始接入市场数据

几秒内生成免费 API 密钥,通过一个端点连接所有市场。