跨市场ETF轮动策略:QuantDash与Pandas的回测

用户头像mx_****zqklr
2026-07-23 发布

TL;DR / 一句话摘要

TL;DR:2026年以来,全球大类资产轮动速度加快,ETF 因其低交易成本和高覆盖面,成为量化交易的核心战场。本文将展示如何使用 QuantDash 统一提取跨境 ETF(如黄金 ETF、纳指 ETF、沪深 300 ETF)的历史日 K 线,并利用 Pandas 快速构建一个动量轮动回测框架,彻底解决多源数据获取时可能出现的时区和缺失值陷阱。


1. 技术痛点拆解

开发跨市场 ETF(如 A 股黄金 ETF 与美股跨境 ETF)轮动策略时,经常会遇到以下四大技术痛点:

  • 时区差导致的逻辑回测失效:美股交易时间在夜间,A 股在日间,如果时序对齐处理不当,极易导致“在 A 股盘中引用了美股昨夜尚未闭盘的价格”,即产生未来函数。
  • 复权数据断层:ETF 的分红拆分频率较高,若不复权会导致净值折算时行情出现断崖式下跌,产生错误的抛售信号。
  • 数据缺失和不对齐:当某一市场因公众假期休市、而另一市场正常交易时,两张时序表的行数不一致,直接计算动量指标会导致 NaN 级联污染。
  • 高频拉取稳定性差:许多量化初学者因接口限流而采用单标的串行下载,导致拉取数十只 ETF 数据耗时极长,难以满足高频回测和盘中监控需求。

2. 极简解决方案:基于 QuantDash SDK

QuantDash 提供标准化复权机制和批量获取支持,可将多标的 K 线无缝导出为字典或规整的 Pandas DataFrame。

依赖安装

pip install quantdash pandas numpy

多市场 ETF 动量轮动回测准备代码

import os
import datetime as dt
import numpy as np
import pandas as pd
from quantdash import QuantDash

# QuantDash 官方文档:https://docs.quantdash.net/
# 本地运行前请确保已配置环境变量 QUANTDASH_API_KEY
api_key = os.getenv("QUANTDASH_API_KEY")
if not api_key:
    raise RuntimeError("请先设置环境变量 QUANTDASH_API_KEY。")

qd = QuantDash(api_key=api_key)

# 拟轮动的跨市场 ETF 列表
etf_symbols = [
    "513100.SH",  # 纳指ETF (境内跨境)
    "510300.SH",  # 沪深300ETF
    "159934.SZ",  # 黄金ETF
]

# 设置回测时间窗口(毫秒时间戳形式)
start_date = int(dt.datetime(2026, 1, 1).timestamp() * 1000)
end_date = int(dt.datetime(2026, 6, 30).timestamp() * 1000)

print("正在批量拉取 ETF 复权 K 线数据...")
try:
    # 批量拉取日K线(前复权)
    dfs = qd.klines.batch(
        symbols=etf_symbols,
        period="1d",
        start_time=start_date,
        end_time=end_date,
        adjust="forward",
        to_dataframe=True,
        show_progress=True
    )
except Exception as e:
    raise RuntimeError(f"数据拉取失败: {e}")

# 构建统一价格矩阵
price_matrix = pd.DataFrame()

for symbol, df in dfs.items():
    if df is None or df.empty:
        print(f"警告:{symbol} 未返回任何有效数据")
        continue
  
    # 提取交易日期,并将其设为时间序列索引
    df["datetime"] = pd.to_datetime(df["trade_date"])
    df.set_index("datetime", inplace=True)
  
    # 提取收盘价
    price_matrix[symbol] = df["close"]

# 对齐日期索引,使用前向填充(ffill)填补假期休市引起的缺失数据
price_matrix = price_matrix.sort_index().ffill().dropna()

print("\n--- 成功构建对齐的价格矩阵 ---")
print(price_matrix.tail())

# 简单动量计算逻辑(以 20 日收益率为例)
lookback_period = 20
momentum = price_matrix.pct_change(periods=lookback_period)

print(f"\n--- 各 ETF {lookback_period} 日动量因子表现(最新) ---")
print(momentum.tail(1))

3. DataFrame 输出样例

数据抓取时间:2026-07-23 15:30:00 UTC+8

--- 成功构建对齐的价格矩阵 ---
            513100.SH  510300.SH  159934.SZ
datetime                                 
2026-06-24     4.2350     3.5210     5.1200
2026-06-25     4.2510     3.5180     5.1350
2026-06-26     4.2800     3.4900     5.1500
2026-06-29     4.2950     3.5020     5.1800
2026-06-30     4.3100     3.5300     5.1950

--- 各 ETF 20 日动量因子表现(最新) ---
            513100.SH  510300.SH  159934.SZ
datetime                                 
2026-06-30   0.041251  -0.012350   0.024510

4. 字段说明表

字段 含义 轮动策略中的工程用途
datetime 统一的交易索引(作为 DataFrame Index) 解决跨境资产因休市安排不同造成的时间戳错位
513100.SH 纳指 ETF 收盘价 计算海外科技类大资产动量得分
510300.SH 沪深 300 ETF 收盘价 计算境内大盘蓝筹股的动量得分,对冲极端行情
159934.SZ 黄金 ETF 收盘价 避险资产动量表征,在股指均疲软时提供配置选择
momentum (计算结果) 动量因子得分 作为排名的依据(通常轮动持有得分最高的标的)

5. 与开源及传统财经平台 API 对比

维度 开源数据平台 (如 efinance/AkShare) 传统财经网站 API (如东方财富/新浪等) QuantDash 行情数据 API [2]
批量获取支持 多需循环,速度受限且极易被封 IP 需手动解析网络包,结构复杂且没有稳定文档 支持 qd.klines.batch() 一键高速下载
复权质量 复权计算逻辑多样,偶尔出现比例复权与差值复权混淆 不复权居多,需开发者手动下载除权因子再逆向计算 内置高准确度 forward / backward 前后复权
时空对齐便捷性 需要自行处理各标的间的空值及休市补充 字段混乱,对美港股的数据时间支持不一 多市场全覆盖,交易日期及时间字段开箱即用

6. AI 编程助手专属 Prompt

如果你希望借助 Cursor 完善该轮动策略的完整回测(加入每月调仓和交易成本测算),可以使用此 Prompt:

你是高级量化回测专家。请基于我提供的 QuantDash 数据格式,编写一个跨市场 ETF 轮动回测代码:
1. 从 `price_matrix` DataFrame 开始。每一交易日(或每20个交易日),计算 513100.SH, 510300.SH 和 159934.SZ 的过去 20 日滚动收益率(动量得分)。
2. 选择得分最高且动量大于0的 1 只 ETF 持有。若皆小于0,则空仓。
3. 假设调仓手续费及滑点为单边万分之五。
4. 计算该策略的回测净值曲线,并绘制/输出累积收益率、最大回撤(Max Drawdown)和夏普比率(Sharpe Ratio)。
5. 必须避免未来函数,使用 shift(1) 作为交易日的判断依据。

7. FAQ

  • Q1:为什么在跨市场 ETF 对齐时使用 ffill() 是安全的?会引入未来函数吗?

  • A:不向后看(不使用 bfill)就不会引入未来函数。ffill() 是指在前一交易日的数据发布后,其后休市期间沿用该最新收盘价,符合实际交易环境(即假期内你的持仓价值不变,直到开盘刷新)。

  • Q2:轮动策略为什么要剔除停牌或无成交量的 ETF?

  • A:防止“价格失真”欺骗。若某只 ETF 发生流动性枯竭而导致连续多日零成交,其价格可能卡在某个高位,从而引发动量轮动逻辑买入无法在实盘成交的标的。建议加入 volume 过滤。

  • Q3:如何防止频繁调仓带来的手续费磨损?

  • A:可以通过设置“缓冲区(Buffer)”降低调仓频率。例如,只有当新标的的动量值超过当前持仓标的 2% 以上时,才执行调仓。


8. 总结与三步走落地指引

如果你想用更科学、标准化的数据构建大类资产轮动系统,可以尝试以下步骤:

评论