
“该策略通过在SPY(纽交所)和IUSA(瑞士证券交易所)之间进行套利操作,当价差超过1.002时,卖空被高估的ETF并买入被低估的ETF,价差收敛后平仓,每年执行约40笔交易。”
资产类别:ETF | 区域: 全球 | 频率: 日内 | 市场: 股票市场 | 关键词: 高频、套利、ETF孪生基金
I. 策略概要
该策略针对SPY(标普500ETF,纽交所交易)和IUSA(标普500ETF,瑞士证券交易所交易),仅在两个市场同时开放时交易。
交易频率: 每年执行约40笔交易,通过两个ETF之间的定价效率差异实现套利收益。
套利机会识别: 当以下条件满足时触发交易:
bid SPY / ask IUSA > 1.002
bid IUSA / ask SPY > 1.002
操作方式:
卖空被高估的ETF
买入被低估的ETF
平仓: 当买卖价差收敛后,关闭头寸。
II. 经济基础
ETF旨在紧密跟踪其基础指数,尽量减少跟踪误差。然而,由于每日“成分股偏差”导致的价格偏离时有发生。这种偏离可能源于:
- 基金允许的权重调整
- 不同市场的流动性差异
- 外汇波动或其他短期市场失衡
这些偏离为套利创造了机会。投资者可以通过买入被低估的ETF并卖空被高估的ETF,从价格差异中获利,同时锁定利润。这种机制利用了ETF的短期定价效率差,同时反映了其长期与基础指数表现一致的特性。
III. 论文来源
ETF Arbitrage: Intraday Evidence [点击浏览原文]
作者: 本·R·马歇尔(Ben R. Marshall),阮H·阮(Nhut H. Nguyen),和努塔瓦特·维萨尔塔纳乔提(Nuttawat Visaltanachoti)。梅西大学经济与金融学院,奥克兰理工大学,梅西大学经济与金融系。
<摘要>
我们分析了两只极为流动的标普500ETF在定价错误条件下的交易机会。尽管这些ETF并非完全可互换,但我们表明,它们之间的细微差异并非导致定价错误的原因。价差在套利机会出现前增大,这与流动性下降一致。随着市场变得单边化,订单不平衡增加,价差波动加剧,这表明流动性风险上升。这些价格偏离在经济上具有显著意义(扣除价差后的平均年化收益为6.6%),并且通常迅速向均值回归。


IV. 回测表现
| 年化收益率 | 28.91% |
| 波动率 | 14.69% |
| Beta | -0.002 |
| 夏普比率 | 1.7 |
| 索提诺比率 | N/A |
| 最大回撤 | N/A |
| 胜率 | 40% |
V. 完整python代码
from AlgorithmImports import *
from typing import List, Union
# endregion
class HighFrequencyArbitragewithETFTwins(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.spread_threshold:float = 1.002
self.spy_voo_ratio:Union[None, float] = None
self.voo_spy_ratio:Union[None, float] = None
self.symbols:List[Symbol] = [self.AddEquity(x, Resolution.Minute).Symbol for x in ['SPY', 'VOO']]
self.trade_direction_flag:Union[None, bool] = None
def OnData(self, data: Slice) -> None:
if self.symbols[0] in data and data[self.symbols[0]] and self.symbols[1] in data and data[self.symbols[1]]:
# get ratio of etfs
if self.Time.hour == 9 and self.Time.minute == 35:
self.spy_voo_ratio = self.Securities[self.symbols[0]].BidPrice / self.Securities[self.symbols[1]].AskPrice
self.voo_spy_ratio = self.Securities[self.symbols[1]].BidPrice / self.Securities[self.symbols[0]].AskPrice
if self.spy_voo_ratio is not None and self.voo_spy_ratio is not None and not self.Portfolio.Invested:
# decide on trading direction
self.trade_direction_flag = True \
if (self.Securities[self.symbols[0]].BidPrice / self.Securities[self.symbols[1]].AskPrice) >= self.spy_voo_ratio * self.spread_threshold \
else False \
if (self.Securities[self.symbols[1]].BidPrice / self.Securities[self.symbols[0]].AskPrice) >= self.voo_spy_ratio * self.spread_threshold \
else None
# trade execution
if self.trade_direction_flag is not None:
self.SetHoldings(self.symbols[0], (-1 if self.trade_direction_flag else 1) * 1)
self.SetHoldings(self.symbols[1], (-1 if self.trade_direction_flag else 1) * -1)
# closing trade
if self.Portfolio.Invested:
if self.trade_direction_flag:
if (self.Securities[self.symbols[0]].BidPrice / self.Securities[self.symbols[1]].AskPrice) < self.spy_voo_ratio * self.spread_threshold:
self.Liquidate()
self.trade_direction_flag = None
self.spy_voo_ratio = None
self.voo_spy_ratio = None
else:
if (self.Securities[self.symbols[1]].BidPrice / self.Securities[self.symbols[0]].AskPrice) < self.voo_spy_ratio * self.spread_threshold:
self.Liquidate()
self.trade_direction_flag = None
self.spy_voo_ratio = None
self.voo_spy_ratio = None
# close before market close
if self.Time.hour == 15 and self.Time.minute == 59 and self.Portfolio.Invested:
self.Liquidate()
self.trade_direction_flag = None
self.spy_voo_ratio = None
self.voo_spy_ratio = None