gs-quant 价差均值回归策略实战:从安装、写信号到回测解读
【免费下载链接】gs-quantPython toolkit for quantitative finance项目地址: https://gitcode.com/GitHub_Trending/gs/gs-quant
发现10年期与2年期利率互换的价差连续两周被拉高,直觉上它会缩回来——但何时进场、何时离场,靠感觉总不准。用 gs-quant 的均值回归触发器和回测引擎,你可以把"偏离均值2个标准差就交易"写成规则,几分钟跑完一年回测,拿到净值曲线和夏普比率。
快速上手:安装 gs-quant 并完成会话认证
先让环境可用,后面所有代码都依赖这两步。安装只需一行:
pip install gs-quant --upgrade认证用你向 Marquee 开发者平台申请到的 client_id 与 client_secret,写入会话即可,不需要额外配置文件:
from gs_quant.session import GsSession # 换成你申请到的凭据;需要哪些数据权限由 scopes 参数决定 GsSession.use(client_id='your-client-id', client_secret='your-client-secret')会话初始化逻辑位于 gs_quant/session.py。
策略机制:价差均值回归到底在判断什么
一句话公式:Z = (当前价差 − 近30日均值) / 近30日标准差;Z 超过 +2 做空价差,低于 −2 做多价差,价差回到均值就平仓。这和商品期货的跨期套利是同一类思路,只是把标的换成了利率互换的期限价差。
打个比方:价差像一根橡皮筋,均值是它的自然长度。正常抖动你不管;一旦被拉出去超过 2 个"常见波动幅度"(标准差),你就赌它回弹。三个术语先说清楚:
- 价差:两个相关价格之差,本文取 10 年期减 2 年期互换利率,衡量"长端相对短端贵不贵";
- 均值回归:序列围绕某个中心波动、偏离久了有拉回倾向的统计特性;
- Z分数:当前值偏离均值几个标准差,是"偏离有多夸张"的统一量尺,本策略入场阈值取 ±2。
整个策略每个交易日只回答一个问题:今天的价差越界了吗?
这套逻辑 gs-quant 已内置为MeanReversionTrigger,你不用自己写状态机:它每天自动算滚动均值、标准差和 Z 分数,越界时按方向下单,价差回归均值时自动平仓。触发语义实现位于 gs_quant/backtests/triggers.py。
分步实现:取数据、算信号、定规则、跑回测
下面是一个标准的 python 量化回测流程,四小节按依赖顺序排列。
拉取利率数据并构造价差序列
这段代码从 SWAPRATES_STANDARD 数据集取一段区间的利率,用 tenor 列筛出两条期限后相减,得到价差序列。
from datetime import date from gs_quant.data import Dataset start_date = date(2023, 1, 1) end_date = date.today() ds = Dataset('SWAPRATES_STANDARD') # assetId 换成你有权限的资产,下面用的是官方示例里的那一个 df = ds.get_data(start_date, end_date, assetId=['MA5WM2QWRVMYKDK0']) # 10y 减 2y,两条曲线同一日期索引,直接相减得到价差 spread = df[df['tenor'] == '10y']['rate'] - df[df['tenor'] == '2y']['rate']数据访问入口位于 gs_quant/data/dataset.py。
用 Z 分数刻画偏离程度
这段代码把价差相对滚动均值的偏离换算成 Z 分数,先"看见"信号长什么样,再交给触发器执行。
# pandas 滚动窗口即可:30 日均值与 30 日标准差 rolling_mean = spread.rolling(30).mean() rolling_std = spread.rolling(30).std() z_scores = (spread - rolling_mean) / rolling_std z_scores.plot(figsize=(10, 4), title='10y-2y spread Z-score')提示:触发器在回测内部会以完全相同的口径重算滚动均值、标准差和 Z 分数(见 triggers.py 中has_triggered方法),这里只是预览,不算重复劳动。
定义交易规则与入场动作
这段代码定义三件事:交易什么(10 年期 EUR 利率互换)、何时进场(Z 分数越界 ±2)、进场做什么(按方向加仓)。
from gs_quant.common import Currency, PayReceive from gs_quant.instrument import IRSwap from gs_quant.backtests.actions import AddTradeAction from gs_quant.backtests.data_sources import GenericDataSource, MissingDataStrategy from gs_quant.backtests.triggers import MeanReversionTrigger, MeanReversionTriggerRequirements # 策略标的:10 年期、付固定收浮动的 EUR 利率互换 swap = IRSwap( pay_or_receive=PayReceive.Pay, termination_date='10y', notional_currency=Currency.EUR, notional_amount=1e4, fixed_rate='ATM', name='swap_10y', ) # 把价差序列喂给触发器,缺失值用前值填充 data_source = GenericDataSource(spread, MissingDataStrategy.fill_forward) # 越界阈值 ±2,滚动窗口 30 日;多空方向由触发器自动判断 trig_req = MeanReversionTriggerRequirements( data_source=data_source, z_score_bound=2, rolling_mean_window=30, rolling_std_window=30, ) trigger = MeanReversionTrigger(trig_req, AddTradeAction(swap))进场与平仓的判定逻辑写在MeanReversionTriggerRequirements.has_triggered中,交易动作与仓位命名位于 gs_quant/backtests/actions.py。
运行回测并导出结果
这段代码把触发器装进策略,用通用引擎按"每个交易日检查一次"的频率跑完全程,并打印成交台账与逐日结果。
from gs_quant.backtests.strategy import Strategy from gs_quant.backtests.generic_engine import GenericEngine strategy = Strategy(None, trigger) # 初始空仓 engine = GenericEngine() backtest = engine.run_backtest( strategy, start=start_date, end=end_date, frequency='1b', show_progress=True # 1b = 每个交易日一个状态点 ) backtest.trade_ledger() # 每笔成交:日期、方向、数量 backtest.result_summary # 逐日汇总:盯市价值、累计现金等策略对象定义在 gs_quant/backtests/strategy.py,逐日推进的引擎实现在 gs_quant/backtests/generic_engine.py。
结果解读:净值曲线和三个关键指标怎么看
📈 回测跑完,先画净值曲线,再读指标。净值 = 盯市价值 + 累计现金,两者都在result_summary里。
import pandas as pd from gs_quant.risk import Price from gs_quant.timeseries.econometrics import sharpe_ratio, max_drawdown # 净值曲线:横轴是日期,纵轴是账户总价值 nav = backtest.result_summary[Price] + backtest.result_summary['Cumulative Cash'] nav.plot(figsize=(10, 6), title='Portfolio value')曲线怎么读:关注锯齿的宽度和台阶的位置——锯齿宽说明单笔波动大;台阶出现在 Z 分数触发平仓的附近才符合策略预期;一条几乎直线上升的曲线反而要警惕,说明回测假设可能过于理想。
三个指标各回答一个问题:
| 指标 | 回答什么 | 怎么读 |
|---|---|---|
| 年化收益 | 一年平均赚多少 | 与同期无风险利率比较,溢价部分才是策略贡献 |
| 夏普比率 | 每承担一份波动换回多少收益 | 经验上大于1算合格;跨策略比较时口径必须一致(都用日频年化) |
| 最大回撤 | 最惨时从高点跌了多少 | 决定你能不能拿得住;均值回归策略的回撤大多发生在"价差继续扩大"阶段 |
计算可直接用现成函数:sharpe_ratio(nav, Currency.USD)会把价格序列自动年化,max_drawdown(nav)输出回撤序列,实现位于 gs_quant/timeseries/econometrics.py。
调优与风控:三个最实用的方向
动态调整触发阈值
- 问题:固定 ±2 在波动放大的阶段容易频繁打脸,在平静阶段又几乎不开仓。
- 做法:同时调
z_score_bound和滚动窗口——高波动期把阈值放大、窗口拉长,用参数扫描代替拍脑袋。
trig_req = MeanReversionTriggerRequirements(data_source, 2.5, 60, 60) # 更严的入场 + 更慢的信号加入交易成本
- 问题:均值回归策略换手不低,零成本回测会系统性高估收益。
- 做法:给入场动作挂一个固定成本模型,每次成交扣一笔现金。
from gs_quant.backtests.backtest_objects import ConstantTransactionModel # 每次成交成本 100 美元 trigger = MeanReversionTrigger(trig_req, AddTradeAction(swap, transaction_cost=ConstantTransactionModel(100)))成本模型定义在 gs_quant/backtests/backtest_objects.py。
设置强制止损
- 问题:价差可能长期不回归(趋势行情),只靠"回到均值"平仓会扛住大亏。
- 做法:再挂一个基于风险度量的触发器,组合价值跌破阈值时清掉全部仓位;风险度量选你引擎实际输出的那一项。
from gs_quant.common import RiskMeasure from gs_quant.backtests.actions import ExitAllPositionsAction from gs_quant.backtests.triggers import StrategyRiskTrigger, RiskTriggerRequirements, TriggerDirection stop_trigger = StrategyRiskTrigger( RiskTriggerRequirements(risk=RiskMeasure.PnL, trigger_level=-5000, direction=TriggerDirection.BELOW), ExitAllPositionsAction(name='stop_loss'), # 亏损超过 5000 美元即清仓 ) strategy = Strategy(None, [trigger, stop_trigger])小结与延伸阅读
- 均值回归策略 = 一个价差序列 + 一个 Z 分数阈值 + 一个内置触发器,状态机部分 gs-quant 已全部封装。
- 先画 Z 分数和净值曲线"看",再读年化、夏普、回撤"信",指标口径一致才可比。
- 阈值、窗口、成本是最值得先扫的三个参数,止损触发器负责兜住极端行情。
仓库内参考入口:
- 官方文档:docs/index.rst
- 回测模块源码:gs_quant/backtests/
- 均值回归官方示例:gs_quant/documentation/04_backtesting/examples/03_GenericEngine/040304_strategy_mean_reversion.ipynb
- 回测示例目录:gs_quant/documentation/04_backtesting/examples/
- 时序函数库:gs_quant/timeseries/
风险提示:本文示例仅用于说明方法,参数取自公开示例数据,实盘前请自行验证流动性、交易成本与极端行情下的表现。
【免费下载链接】gs-quantPython toolkit for quantitative finance项目地址: https://gitcode.com/GitHub_Trending/gs/gs-quant
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考