Skip to content

量化交易入门:手把手教你用两支ETF实现调仓套利

作 者:老余捞鱼

原创不易,转载请标明出处及原作者。

写在前面的话:今天我分享一个ETF调仓套利策略。它利用月末机构调仓的规律,在TLT和SPY间做对冲交易,追求稳定的小收益。代码简单,我手把手教你回测和优化,适合新手入门。不用复杂模型,就能捕捉市场机会!

一、月末调仓套利策略揭秘

很多人觉得,想在股市里赚钱,就得搞点高深莫测的东西。什么AI预测、高频交易、深度学习……听着就头大。

其实啊,真正的“印钞机”策略,往往藏在最不起眼的地方。

今天我要分享的,就是一个“傻瓜式”策略——每个月只动两次手,就能从市场里“薅羊毛”。

它不靠猜明天是涨是跌,也不靠听专家喊“牛市来了”。它靠的是——基金公司月底“装门面”的老毛病

你没听错,就是那些管理着几百亿、几千亿资金的大佬们,每到月底、季末,都会偷偷调整一下自己的持仓,让报表看起来更漂亮。

比如,卖掉表现差的股票,多买点涨得好的;或者,为了显得“稳健”,临时买点国债。

这一买一卖,就会让市场在特定时间点出现“小波动”。而我们,就是要抓住这些“小波动”,稳稳地赚点小钱。

这个策略的核心,就是两只ETF:

  • TLT:跟踪美国长期国债,相当于“避风港”,股市跌的时候它常涨。
  • SPY:跟踪标普500指数,代表美国大盘股,股市涨它就涨。

这两兄弟,平时走势经常“对着干”,简直是天生一对。

我们的操作很简单:

每月倒数第7个交易日:买TLT,卖空SPY
下个月第一个交易日:卖TLT,买回SPY
再过一周:全部平仓

就这么简单。不用盯盘,不用分析财报,不用研究宏观经济。只要记住两个日子,按计划操作就行。

这叫“市场中性策略”——不赌大盘涨跌,只赚两只ETF之间的“相对差价”。

好处是啥?风险低啊!就算2022年那种熊市,股市和债市一起跌,但它们跌的速度不一样,我们照样能赚到“剪刀差”。

二、为什么这个策略能“偷懒赚钱”?

说白了,这就是在“薅机构的羊毛”。

基金公司年底或季末要发报告,老板和客户都要看。他们不想看到自己重仓了一堆“垃圾股”,所以会做点“美化”。

比如:

  • 卖掉最近表现差的股票,换成涨得好的。
  • 临时买点国债,显得“风控做得好”。

这种行为,在金融圈有个专有名词——“窗口装饰”(Window Dressing)。

听起来很专业,其实就是“临考前突击背书”。

历史上就有经典案例:

  • 2008年雷曼兄弟:季度末用“Repo 105”把负债暂时移出报表,假装自己没那么危险。
  • 普通公募基金:季末狂卖亏损股,猛买明星股,导致股价被“扭曲”几个百分点。
  • 保险公司:月底集中买国债,推高价格,导致国债最后几天平均能涨0.25%。

这些行为,都是有规律可循的。而我们的策略,就是专门抓这些“规律”。

这不是赌博,这是利用人性弱点赚钱。

三、手把手教学

下面,我手把手带你用Python把这个策略跑起来。全程代码清晰,注释详细,照着敲就能跑。

第一步:安装必备工具包

打开你的Python环境(推荐Jupyter Notebook或Google Colab),先装这几个库:

pip install yfinance pandas numpy vectorbt

如果你不会装,直接去Google Colab,新建一个Notebook,粘贴代码就能跑,完全免费!

第二步:导入数据(2010年至今)

这段代码从2010年到现在下载TLT和SPY的数据。yfinance库能自动从雅虎财经抓数据,超级方便。

import yfinance as yf
import pandas as pd
import numpy as np
import vectorbt as vbt

# 设置时间范围
start_date = "2010-01-01"
end_date = pd.Timestamp.today().strftime("%Y-%m-%d")  # 自动获取当前日期
symbols = ["TLT", "SPY"]  # 我们要操作的两只ETF

# 下载收盘价
try:
    price_data = yf.download(symbols, start=start_date, end=end_date, auto_adjust=False)["Close"]
except Exception as e:
    print(f"下载数据失败: {e}")
    # 可以换Alpha Vantage等其他数据源

# 填充缺失值(比如节假日)
price_data = price_data.fillna(method='ffill').dropna()

第三步:找关键交易日

我们要找的是:

  • 每个月倒数第7个交易日(开仓日)
  • 每个月第一个交易日(平仓+反向开仓日)
  • 平仓日之后第7个交易日(最终平仓日)

代码如下:

dates = price_data.index
year_month = dates.to_period('M')
grouped = pd.DataFrame({"dt": dates, "ym": year_month}).groupby("ym")["dt"]

first_trading_days = grouped.first().values  # 每月第一个交易日
last_trading_days = grouped.last().values    # 每月最后一个交易日

# 定义函数:找前面第N个交易日
def get_prev_trading_day_idx(trading_dates, base_dates, offset):
    idx = []
    trading_dates = pd.Series(trading_dates)
    for d in base_dates:
        pos = trading_dates.searchsorted(d)  # 找到插入点
        prev_idx = pos - offset
        if prev_idx >= 0:
            idx.append(trading_dates.iloc[prev_idx])
    return pd.DatetimeIndex(idx)

# 定义函数:找后面第N个交易日
def get_offset_trading_day_idx(trading_dates, base_dates, offset):
    idx = []
    trading_dates = pd.Series(trading_dates)
    for d in base_dates:
        pos = trading_dates.searchsorted(d)
        target_idx = pos + offset
        if target_idx < len(trading_dates):
            idx.append(trading_dates.iloc[target_idx])
    return pd.DatetimeIndex(idx)

# 计算关键日期
pre_end_idx = get_prev_trading_day_idx(dates, last_trading_days, 7)   # 7天前开仓
month_start_idx = get_offset_trading_day_idx(dates, last_trading_days, 1)  # 月初反向
week_after_start_idx = get_offset_trading_day_idx(dates, month_start_idx, 7)  # 一周后平仓

第四步:创建交易信号

我们创建一个信号表,告诉程序什么时候买、什么时候卖。

# 初始化信号表
signals = pd.DataFrame(
    index=dates,
    columns=pd.MultiIndex.from_product([symbols, ['long_entry', 'long_exit', 'short_entry', 'short_exit']]),
    data=False
)

# 开仓信号:月底前7天,买TLT,卖SPY
signals.loc[pre_end_idx, ("TLT", "long_entry")] = True
signals.loc[pre_end_idx, ("SPY", "short_entry")] = True

# 反向开仓信号:月初,卖TLT,买SPY
signals.loc[month_start_idx, ("TLT", "short_entry")] = True
signals.loc[month_start_idx, ("SPY", "long_entry")] = True

# 平仓信号:月初平掉第一笔,一周后平掉第二笔
signals.loc[month_start_idx, ("TLT", "long_exit")] = True
signals.loc[month_start_idx, ("SPY", "short_exit")] = True
signals.loc[week_after_start_idx, ("TLT", "short_exit")] = True
signals.loc[week_after_start_idx, ("SPY", "long_exit")] = True

# 整理成vectorbt能识别的格式
long_entry = pd.DataFrame({sym: signals[(sym, "long_entry")] for sym in symbols})
long_exit = pd.DataFrame({sym: signals[(sym, "long_exit")] for sym in symbols})
short_entry = pd.DataFrame({sym: signals[(sym, "short_entry")] for sym in symbols})
short_exit = pd.DataFrame({sym: signals[(sym, "short_exit")] for sym in symbols})

这些函数帮你精准定位交易点,避免错过时机。

第五步:回测策略

最后,我们用vectorbt回测,看看策略表现。假设起始资金10万美元。代码如下:

# 开始回测,初始资金10万美金
pf = vbt.Portfolio.from_signals(
    price_data,
    entries=long_entry,
    exits=long_exit,
    short_entries=short_entry,
    short_exits=short_exit,
    freq='D',
    size_type=1,      # 用百分比下单
    size=np.inf,      # 无限资金(简化版,实际可用size=1)
    init_cash=100_000
)

# 输出统计结果
print(pf.stats())

运行完,你会看到类似这样的结果:

是不是看着不高?别急,这可是15年无脑操作的结果,而且最大回撤只有9%,风险极低。

对比一下,同期买入持有SPY,年化可能有7-8%,但最大回撤能到30%以上。

我们这个策略,胜在

四、进阶玩法

上面的代码是基础版,如果你想让它更赚钱,可以试试这些改进:

1. 调整开仓时间

原版是“月底前7天”,你可以改成“前3天”或“前10天”,看看哪个效果更好。

pre_end_idx = get_prev_trading_day_idx(dates, last_trading_days, 3)  # 改成3天前

2. 换ETF组合

除了TLT和SPY,还可以试试:

  • IEF(中期国债) + SPY
  • QQQ(纳斯达克) + TLT
  • AGG(综合债券) + QQQ

不同的组合,有不同的“剪刀差”。

3. 加个“趋势过滤器”

如果最近一个月,TLT涨得比SPY多,我们就执行策略;否则,休息。

# 计算20日涨幅
tlr_change = price_data['TLT'].pct_change(20)
spy_change = price_data['SPY'].pct_change(20)

# 只有当TLT涨得更多时,才开仓
trade_filter = tlr_change > spy_change

# 应用到开仓信号
long_entry_filtered = long_entry & trade_filter
short_entry_filtered = short_entry & trade_filter

这样能避免在“极端行情”下亏钱。

4. 加入手续费和融券成本

现实中,卖空SPY是要付利息的,大概每年0.3%-1%。我们可以模拟一下:

pf = vbt.Portfolio.from_signals(
    price_data,
    entries=long_entry,
    exits=long_exit,
    short_entries=short_entry,
    short_exits=short_exit,
    freq='D',
    size_type=1,
    size=np.inf,
    init_cash=100_000,
    fees=0.001,         # 每笔交易0.1%手续费
    short_fees=0.0005   # 卖空费,按日计算(约0.5%/年)
)

加上成本后,收益会略降,但更贴近现实。

五、观点总结

总的来说,这是一个“低风险、低收益、高稳定”的策略。它不适合追求暴富的人,但非常适合:

  • 想学量化交易的新手。
  • 不想天天盯盘的上班族。
  • 希望资产稳健增值的保守派。

它最大的优点是:简单、透明、可验证。你不需要相信任何“大师”,只需要相信数据和代码。

  • 每月只操作两次,省时省力,适合懒人。
  • 利用基金“窗口装饰”行为,赚取市场“小波动”。
  • 代码开源,小白照抄就能跑,附带详细注释。
  • 风险极低,最大回撤不到10%,适合保守投资者。
  • 可优化空间大,换ETF、调时间、加过滤器,都能提升收益。

#关键词

#Python量化 #ETF交易 #基金调仓 #月度策略 #躺赚策略 #投资理财 #量化交易入门 #Python代码 #金融干货 #A股美股 #被动收入 #投资小白 #回测教程 #市场中性 #低风险高收益 #低风险投资 #对冲套利

本文代码我已经尽量写得简单易懂,大家可以直接复制使用。如果对文中内容有任何疑问,欢迎留言,我会尽快回复。祝您投资顺利,收益长虹!


本文内容仅限技术探讨和学习,不构成任何投资建议。

Published inAI&Invest专栏

Be First to Comment

    发表回复