国际ETF配对交易策略
登录后收藏学术论文
国际ETF上的配对交易
Panagiotis Schizas; Dimitrios D. Thomakos; Tao Wang
Institute of Finance and Banking
University of Zurich
University of Zurich - Department of Banking and Finance
National and Kapodistrian University of Athens
Athens University of Economics and Business
University of Athens, Department of Business Administration
City University of New York
City University of New York (CUNY) - Department of Economics
导读
投资标的:EWA、EWK、EWZ、EWC、FXI、EWQ、EWG、EWH、EWI、EWJ、EWN、EWS。再平衡:每月。Universe: EWA, EWK, EWZ, EWC, FXI, EWQ, EWG, EWH, EWI, EWJ, EWN, EWS. Parameters: rebalance=monthly.
策略概要
该策略投资于22只国际ETF。为每只ETF构建含股息的标准化累计总回报指数,形成期内起始价格归一为1美元。经过120天形成期后,以两条标准化价格序列的平方偏差之和衡量配对距离,选取距离最小的前5对进入随后20天交易期。每日监控,当价差偏离超过历史标准差的0.5倍时开仓:做多被低估ETF、做空被高估ETF;若收敛或满20个交易日则平仓。配对等权,组合每日再平衡。
经济逻辑
若一对ETF过去高度协整,二者很可能共享共同的基本面收益来源。临时冲击可能使其中一只偏离共同价格带,从而形成统计套利机会。配对池持续更新,不再同步的配对会被剔除,只保留收敛概率较高的组合。
入场与出场
入场
每月检查 EWA, EWK, EWZ, EWC, FXI, EWQ, EWG, EWH, EWI, EWJ (+2 more)。收盘价高于 200 日均线的标的等权做多;若无一合格则空仓。空仓(目标权重为空字典)等于 100% 现金。
出场
下一再平衡日收盘价跌破 200 日均线即剔除该标的。全部跌破均线时组合转为现金。引擎按 5 bps 佣金 + 2 bps 滑点成交。
所需行情数据
需要 EWA, EWK, EWZ, EWC, FXI, EWQ (+6 more) 至少 200 根复权收盘价,才能形成第一个有效信号。ETF 仅交易日有行情。 实验室通过 backtest.data.load_daily_prices 读取 Yahoo Finance。
标的
EWA, EWK, EWZ, EWC, FXI, EWQ, EWG, EWH, EWI, EWJ, EWN, EWS
频率
每日 · 日频复权收盘价;信号按月再平衡(用当月最后一个有效交易日)
所需历史
每个标的至少 200 根交易日 K 线(约 10 个交易月),外加数根缓冲才能发出第一个信号。
字段
- 复权收盘价(Adj Close / auto_adjust Close)
- 交易日日期索引
凭证
- 回测与实验室:无需密钥
- 实盘下单才需要券商 / 交易所 API
API 与数据源
Quant Buffet 数据 API
实验室用 load_daily_prices 拉取 ASSETS 的复权收盘价面板,磁盘缓存后回测可复现。
- 提供方
- backtest.data.load_daily_prices
- 鉴权
- 回测无需密钥
- 接口
- load_daily_prices(ASSETS, start="2000-01-01")
Yahoo Finance(yfinance)
美股 / ETF 复权 OHLCV,免费研究接口,有速率限制。
- 提供方
- yfinance → Yahoo Finance
- 鉴权
- 公开历史无需密钥
- 接口
- Ticker.history / yf.download(auto_adjust=True)
Onsite backtest IDE
Quant Buffet 原生回测 IDEEdit and run Quant Buffet Python for 国际ETF配对交易策略 in the browser. Results update live with equity, drawdown, and metrics charts. Allowed: backtest.data, backtest.engine, backtest.metrics, numpy, pandas. Define ASSETS and make_on_day(prices). Shortcut: Ctrl+Enter. API docs →
Quant Buffet syntax cheat sheet (copy / insert)
Paste these fragments into the editor. The sandbox rejects QuantConnect, os, and network libraries.
from __future__ import annotations
import numpy as np
import pandas as pd
from backtest.data import load_daily_prices
from backtest.engine import EngineConfig, PortfolioEngine
from backtest.metrics import compute_metricsASSETS = ["SPY", "QQQ", "TLT", "GLD", "BIL"]def make_on_day(prices: pd.DataFrame):
cols = [c for c in ASSETS if c in prices.columns]
sma = prices[cols].rolling(200, min_periods=200).mean()
state = {"last": None}
def on_day(engine: PortfolioEngine, dt: pd.Timestamp) -> None:
if sma.loc[dt].isna().all():
return
key = (dt.year, dt.month)
if state["last"] == key:
return
state["last"] = key
long = [
s for s in cols
if pd.notna(prices.at[dt, s]) and pd.notna(sma.at[dt, s])
and prices.at[dt, s] > sma.at[dt, s]
]
weights = {} if not long else {s: 1.0 / len(long) for s in long}
engine.set_target_weights(dt, weights)
ready = sma.dropna(how="all").index.min() if sma.notna().any().any() else None
return on_day, readyengine.set_target_weights(dt, {"SPY": 0.60, "BIL": 0.40})Live backtest performance
Order records
Export to your platform
Transform Quant Buffet lab code (ASSETS + make_on_day / PortfolioEngine) into native classes for a third-party IDE — then copy and paste.
# Generated from Quant Buffet → QuantConnect LEAN
# Strategy: 国际ETF配对交易策略
# Detected pattern: SMA trend
# Source uses Quant Buffet lab APIs (ASSETS + make_on_day / PortfolioEngine).
# Review fees, data, and risk before live trading — educational export only.
from AlgorithmImports import *
class QuantBuffetExport(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2010, 1, 1)
self.SetCash(100000)
tickers = ["EWA", "EWK", "EWZ", "EWC", "FXI", "EWQ", "EWG", "EWH", "EWI", "EWJ", "EWN", "EWS"]
self.symbols = []
for t in tickers:
if "-" in t: # crypto proxy e.g. BTC-USD
self.symbols.append(self.AddCrypto(t.replace("-USD", ""), Resolution.Daily).Symbol)
else:
self.symbols.append(self.AddEquity(t, Resolution.Daily).Symbol)
self.Schedule.On(
self.DateRules.MonthStart(self.symbols[0]),
self.TimeRules.AfterMarketOpen(self.symbols[0], 30),
self.Rebalance,
)
# Logic: Long assets where close > SMA(200); equal-weight; monthly.
def Rebalance(self):
longs = []
for symbol in self.symbols:
hist = self.History(symbol, 200 + 5, Resolution.Daily)
if hist.empty: continue
close = hist["close"].unstack(level=0).iloc[:, 0] if hasattr(hist["close"], "unstack") else hist["close"]
if len(close) < 200: continue
if float(close.iloc[-1]) > float(close.iloc[-200:].mean()):
longs.append(symbol)
weight = 1.0 / len(longs) if longs else 0.0
for symbol in self.symbols:
self.SetHoldings(symbol, weight if symbol in longs else 0.0)
导出代码使用目标平台的原生类与库。请在第三方 IDE 中安装依赖后运行;实盘前请自行验证。