Qbot 布林线均值回归策略实战:从 BOLL 原理到回测与稳健性分析
2026/9/13 14:40:24 网站建设 项目流程

Qbot 布林线均值回归策略实战:从 BOLL 原理到回测与稳健性分析

【免费下载链接】Qbot[🔥updating ...] AI 自动量化交易机器人(完全本地部署) AI-powered Quantitative Investment Research Platform. 📃 online docs: https://ufund-me.github.io/Qbot ✨ :news: qbot-mini: https://github.com/Charmve/iQuant项目地址: https://gitcode.com/GitHub_Trending/qbot/Qbot

布林线均值回归是一种经典的统计套利型股票策略,利用均值与标准差构建的布林带判断价格"超买/超卖"状态,是 Qbot 量化投资平台中收录的经典策略之一。本文将基于 Qbot 仓库中的 布林线均值回归.md 文档,完整讲解 BOLL 指标的计算原理、均值回归策略的构建思路、掘金量化平台(gm.api)上的完整策略代码,以及不同回测期下的稳健性分析结论;同时结合仓库内pytraderqbot等目录下的源码实现,从指标计算、信号生成、回测框架三个层面做纵深印证,帮助读者掌握一套可复现、可改参、可迁移到多框架的布林带策略落地方法。

一、策略原理:布林带与均值回归

提起布林线均值回归策略,就不得不提布林带(Bollinger Bands)这个概念。布林带由约翰·布林格(John Bollinger)提出,是利用统计学中的均值标准差联合计算得出的价格通道,由上轨线、中轨线(均线)和下轨线三条线构成。布林线均值回归策略的核心假设是:标的价格绝大多数时间在上轨线与下轨线围成的带状区间内浮动,即使短期内突破上下轨,长期内仍然会回归到布林带之中。因此,一旦价格突破上下轨,即形成了潜在的买卖信号:

  • 当股价向上突破上界(上轨)时,认为价格短期偏高、面临回归压力,为卖出信号
  • 当股价向下突破下界(下轨)时,认为价格短期偏低、存在反弹空间,为买入信号

BOLL 线的计算公式

BOLL 线的三条轨道均基于 N 日窗口内的收盘价序列计算,公式如下:

中轨线(MID) = N日移动平均线 上轨线(UP) = 中轨线 + k × 标准差 下轨线(LOW) = 中轨线 - k × 标准差

其中:

  • N为计算周期(窗口长度),决定轨道对价格变化的敏感度;
  • k为标准差倍数,决定轨道的宽度,即对波动幅度的容忍程度。

在 Qbot 仓库中,BOLL 指标被列为每日股票指标体系的一员。在 股票指标.md 的 17 项日频指标清单中,第 9 项即"BOLL指标(布林线指标 Bollinger Bands)",说明该指标在 Qbot 的指标计算管线中具有标准地位,可直接作为选股因子与回测信号源使用。

二、策略思路与回测设定

布林线均值回归策略的实现路径非常清晰,共分三步:

  1. 第一步:根据历史数据计算 BOLL 线的上下界——利用收盘价序列的移动均值与移动标准差,构造中轨、上轨、下轨;
  2. 第二步:获得持仓信号——比较最新收盘价与上下轨的穿越关系,产生买入/卖出/持有指令;
  3. 第三步:回测分析——在指定标的上运行策略,统计收益、回撤、胜率等指标,检验策略有效性。

原文档给出的回测设定如下:

  • 回测标的:SHSE.600004(白云机场)
  • 回测期:2009-09-17 13:00:00 至 2020-03-21 15:00:00
  • 回测初始资金:1000 元

需要说明的是,该回测运行在掘金量化(GoldMiner)的gm.api接口之上,Qbot 仓库中的经典策略文档(如 Alpha对冲.md、多因子选股.md 等)均采用了相同的"回测代码 + 稳健性分析"文档体例,便于读者横向对比不同策略在同一平台上的表现。

三、策略代码:基于 gm.api 的完整实现

以下为原文档提供的完整策略代码,运行于掘金量化平台,策略采用日线频率,在价格向上穿越上轨时卖出、向下穿越下轨时买入:

# coding=utf-8 from __future__ import print_function, absolute_import from gm.api import * """ 本策略采用布林线进行均值回归交易。当价格触及布林线上轨的时候进行卖出,当触及下轨的时候,进行买入。 使用600004在 2009-09-17 13:00:00 到 2020-03-21 15:00:00 进行了回测。 注意: 1:实盘中,如果在收盘的那一根bar或tick触发交易信号,需要自行处理,实盘可能不会成交。 """ # 策略中必须有init方法 def init(context): # 设置布林线的三个参数 context.maPeriod = 26 # 计算BOLL布林线中轨的参数 context.stdPeriod = 26 # 计算BOLL 标准差的参数 context.stdRange = 1 # 计算BOLL 上下轨和中轨距离的参数 # 设置要进行回测的合约 context.symbol = 'SHSE.600004' # 订阅&交易标的, 此处订阅的是600004 context.period = max(context.maPeriod, context.stdPeriod, context.stdRange) + 1 # 订阅数据滑窗长度 # 订阅行情 subscribe(symbols= context.symbol, frequency='1d', count=context.period) def on_bar(context, bars): # 获取数据滑窗,只要在init里面有订阅,在这里就可以取的到,返回值是pandas.DataFrame data = context.data(symbol=context.symbol, frequency='1d', count=context.period, fields='close') # 计算boll的上下界 bollUpper = data['close'].rolling(context.maPeriod).mean() \ + context.stdRange * data['close'].rolling(context.stdPeriod).std() bollBottom = data['close'].rolling(context.maPeriod).mean() \ - context.stdRange * data['close'].rolling(context.stdPeriod).std() # 获取现有持仓 pos = context.account().position(symbol=context.symbol, side=PositionSide_Long) # 交易逻辑与下单 # 当有持仓,且股价穿过BOLL上界的时候卖出股票。 if data.close.values[-1] > bollUpper.values[-1] and data.close.values[-2] < bollUpper.values[-2]: if pos: # 有持仓就市价卖出股票。 order_volume(symbol=context.symbol, volume=100, side=OrderSide_Sell, order_type=OrderType_Market, position_effect=PositionEffect_Close) print('以市价单卖出一手') # 当没有持仓,且股价穿过BOLL下界的时候买出股票。 elif data.close.values[-1] < bollBottom.values[-1] and data.close.values[-2] > bollBottom.values[-2]: if not pos: # 没有持仓就买入一百股。 order_volume(symbol=context.symbol, volume=100, side=OrderSide_Buy, order_type=OrderType_Market, position_effect=PositionEffect_Open) print('以市价单买入一手') if __name__ == '__main__': ''' strategy_id策略ID,由系统生成 filename文件名,请与本文件名保持一致 mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST token绑定计算机的ID,可在系统设置-密钥管理中生成 backtest_start_time回测开始时间 backtest_end_time回测结束时间 backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST backtest_initial_cash回测初始资金 backtest_commission_ratio回测佣金比例 backtest_slippage_ratio回测滑点比例 ''' run(strategy_id='strategy_id', filename='main.py', mode=MODE_BACKTEST, token='token_id', backtest_start_time='2009-09-17 13:00:00', backtest_end_time='2020-03-21 15:00:00', backtest_adjust=ADJUST_PREV, backtest_initial_cash=1000, backtest_commission_ratio=0.0001, backtest_slippage_ratio=0.0001)

3.1 代码逐段拆解

  • 参数配置(initmaPeriod = 26stdPeriod = 26分别指定中轨均线与标准差的滚动窗口长度;stdRange = 1即公式中的标准差倍数kcontext.period = max(...) + 1取三者最大值加 1 作为订阅数据滑窗长度,保证滚动计算有足够的历史数据。
  • 行情订阅subscribe(symbols=..., frequency='1d', count=context.period)以日线频率订阅标的,count指定一次性拉取的 K 线根数。
  • BOLL 上下界计算:利用 pandas 的rolling().mean()rolling().std()直接实现公式——中轨为 N 日收盘价均值,上轨为中轨加stdRange倍标准差,下轨为中轨减stdRange倍标准差。
  • 信号与下单:只有当收盘价穿越上轨([-1]突破且[-2]未突破)且当前有持仓时才市价卖出 100 股;只有收盘价穿越下轨且当前无持仓时才市价买入 100 股。采用"穿越"而非"触碰"判断,可避免在轨道附近反复触发信号。
  • 回测入口(runmode=MODE_BACKTEST表示回测模式,backtest_adjust=ADJUST_PREV使用前复权数据,backtest_initial_cash=1000设定初始资金,backtest_commission_ratio=0.0001(0.01%)为佣金比例,backtest_slippage_ratio=0.0001(0.01%)为滑点比例。strategy_idtoken需替换为掘金平台上实际生成的策略 ID 与本机绑定密钥。

四、回测结果与稳健性分析

原文档设定初始资金 1000 元、手续费率 0.01%、滑点比率 0.01% 进行回测,回测结果要点如下:

  • 回测期累计收益率为99.77%
  • 年化收益率为9.49%
  • 同期沪深 300 收益率为10.03%策略整体跑输大盘
  • 最大回撤为32.04%
  • 胜率为73.47%

为验证策略的稳定性,原文档调整了回测周期,观察收益变化:

| 标的 | 回测期 | 年化收益率 | 最大回撤 | | -- | ------- | --- | --- | | SHSE.600004 | 2009.09.17-2020.03.21 | 9.49% | 32.04% | | SHSE.600004 | 2009.01.01-2014.12.30 | 2.64% | 17.07% | | SHSE.600004 | 2014.01.01-2020.03.21 | 20.75% | 17.21% | | SHSE.600004 | 2009.01.01-2019.03.21 | 8.18% | 31.95% |

调整不同的回测期后,策略的收益情况发生变化。整体收益均为正,但均跑输大盘。由此可以得出两点稳健性结论:

  1. 策略长期保持正收益:四个回测窗口年化收益均为正,说明均值回归逻辑在白云机场这只标的上具备持续的有效性基础,没有出现系统性失效;
  2. 收益对回测期高度敏感:2009-2014 窗口年化仅 2.64%,而 2014-2020 窗口年化达 20.75%,区间选择对结论影响显著。结合胜率 73.47% 但整体跑输沪深 300 的表现可以推断:布林线均值回归属于高胜率、低单笔盈亏比的策略类型,在震荡市中表现更佳,在单边上涨行情中容易过早卖出踏空主升浪,因此更适合作为组合中的卫星策略而非全仓押注的进攻策略。

注:此策略只用于学习、交流、演示,不构成任何投资建议。

五、仓库源码印证:BOLL 指标的三种实现视角

Qbot 仓库在pytraderqbot两个子项目中提供了多套 BOLL 相关的策略实现,可以与掘金版策略代码互为印证,帮助理解同一指标在不同框架下的落地差异。

5.1 TA-Lib 版:pytrader/backtest_strategies/BOLL.py

pytrader/backtest_strategies/BOLL.py 是基于 TA-Lib 库的 BOLL 策略类,继承自 pytrader/backtest_strategies/backtest_strategy_template.py 定义的BacktestStrategyTemplate

from backtest_strategies.backtest_strategy_template import BacktestStrategyTemplate from pandas import DataFrame from talib._ta_lib import BBANDS class BOLLStrategy(BacktestStrategyTemplate): """BOLL指标策略""" def get_singal(self, df: DataFrame): upper, middle, lower = self.get_boll(df) # 获取最新价格 current_price = df.close[-1] # 穿越上轨,买入信号 if current_price > upper[-1]: return 1 # 穿越下轨,卖出信号 if current_price < lower[-1]: return 0 return -1 def get_boll(self, df: DataFrame): return BBANDS(df.close, timeperiod=14, nbdevup=2, nbdevdn=2, matype=0)

这一实现与掘金版在信号方向上恰好相反(TA-Lib 版为突破上轨买入、跌破下轨卖出,即趋势跟踪用法),但在计算层面与文档完全同构:BBANDS返回(upper, middle, lower)三元组,timeperiod=14对应公式中的 N,nbdevup=2nbdevdn=2对应标准差倍数 k(此处上轨与下轨各用 2 倍),matype=0表示使用简单移动平均。对比可见:同一套 BOLL 公式既可服务于均值回归(价格触及轨道反向交易),也可服务于趋势突破(价格突破轨道顺势交易),信号方向完全由策略意图决定,这正是技术指标"中性工具、策略赋予语义"的典型体现。

模板基类BacktestStrategyTemplate提供了完整的回测脚手架:process()逐日调用get_singal生成持仓信号序列,output_earning_rate()(1 + close.pct_change(1).fillna(0) * signals).cumprod()计算策略净值曲线并与基准close / close[0]对比,show_plt()绘制收益与指标双面板图。在 pytrader/newbacktest.py 中,BOLLStrategyRSIStrategyCCIStrategyMACDStrategy等并列可供切换(相关代码行以注释形式存在),说明该策略类被设计为可插拔的公共回测组件。

5.2 Backtrader 版:qbot/strategies/boll_strategy_bt.py

qbot/strategies/boll_strategy_bt.py 提供的是基于 Backtrader 框架的独立可运行回测脚本,信号逻辑与掘金版文档完全一致——收盘价跌破下轨买入、突破上轨卖出

class BollStrategy(bt.Strategy): # BOLL策略程序 params = ( ("nk", 13), # 求均值的天数 ("printlog", False), ) # 打印log def __init__(self): # 初始化 self.data_close = self.datas[0].close # 指定价格序列 # 初始化交易指令、买卖价格和手续费 self.order = None self.buy_price = None self.buy_comm = None # Boll指标计算 self.top = bt.indicators.BollingerBands( self.datas[0], period=self.params.nk ).top self.bot = bt.indicators.BollingerBands( self.datas[0], period=self.params.nk ).bot # 添加移动均线指标 self.sma = bt.indicators.SimpleMovingAverage( self.datas[0], period=self.params.nk ) def next(self): # 买卖策略 if self.order: # 检查是否有指令等待执行 return if not self.position: # 没有持仓 if self.data_close[0] < self.bot[0]: # 收盘价格跌破下轨 self.log("BUY CREATE, %.2f" % self.data_close[0]) self.order = self.buy() # 执行买入 else: if self.data_close[0] > self.top[0]: # 收盘价格上涨突破上轨 self.log("SELL CREATE, %.2f" % self.data_close[0]) self.order = self.sell() # 执行卖出

该脚本通过 Backtrader 内置的bt.indicators.BollingerBands计算上下轨,并展示了完整的回测装配流程:通过 AKShare 的ak.stock_zh_a_hist(symbol=code, adjust="qfq", ...)获取前复权日线数据并规范字段命名,喂给bt.feeds.PandasData;随后cerebro.addstrategy(BollStrategy, nk=13, printlog=True)加载策略、cerebro.broker.setcash(1000000)设定初始资金、setcommission(commission=0.0005)设定万五手续费、bt.sizers.FixedSize(stake=100)设定每笔 100 股(1 手)。notify_ordernotify_trade两个回调分别记录成交明细与平仓收益,stop在回测结束时打印期末总资金。默认示例标的为 600036(招商银行),回测区间 2021-01-01 至 2022-12-06。这段代码可作为读者在本地复现"均值回归版布林策略"的最短路径:只需将code换成 SHSE.600004 对应的 A 股代码(600004)即可对照原文档结论。

5.3 指标管线版:qbot/qbot.py中的多因子信号

qbot/qbot.py 展示了 BOLL 指标在因子合成场景中的用法。该脚本用 TA-Lib 一次性计算 MA5/MA10/MA20、BIAS、BOLL、KDJ、RSI 等指标,其中布林带部分为:

data['upper'], data['middle'], data['lower'] = talib.BBANDS(data['close'], timeperiod=20, nbdevup=2, nbdevdn=2, matype=0)

随后将多个子策略信号加权合成:

# 第二个策略:股价低于BOLL线底 condition2 = (data['close'] < data['lower']) signals += 0.3 * condition2.astype(int)

这里的condition2正是布林线均值回归的经典触发条件——收盘价跌破下轨视为超卖,赋予 0.3 的权重参与总信号合成。这说明在 Qbot 的多因子体系中,布林线突破信号不是单独决定买卖,而是作为打分因子之一与其他指标(均线多头、KDJ 金叉、RSI 超买超卖)加权叠加,最终由orders[signals > 0] = 1生成买入指令。相比单一 BOLL 策略,这种用法更贴近实战中的因子组合思路。

5.4 三种实现的横向对照

| 实现 | 仓库位置 | 指标来源 | 信号方向 | 适用场景 | | -- | -- | -- | -- | -- | | 掘金 gm.api 版 | docs/02-经典策略/01-股票/布林线均值回归.md | pandas rolling 手算 | 破下轨买、破上轨卖(均值回归) | 掘金平台回测/实盘 | | TA-Lib 模板版 | pytrader/backtest_strategies/BOLL.py |talib.BBANDS| 破上轨买、破下轨卖(趋势) | 可插拔回测组件 | | Backtrader 版 | qbot/strategies/boll_strategy_bt.py |bt.indicators.BollingerBands| 破下轨买、破上轨卖(均值回归) | 本地独立回测 | | 因子合成版 | qbot/qbot.py |talib.BBANDS| 跌破下轨作为买入信号之一 | 多因子打分组合 |

六、参数解读与调参方向

结合原文档参数与仓库源码,将 BOLL 均值回归策略涉及的核心参数汇总如下:

| 参数 | 文档默认值 | 含义 | 调参影响 | | -- | -- | -- | -- | |maPeriod(N) | 26 | 中轨移动平均窗口 | 越大轨道越平滑、信号越少;越小反应越灵敏、假信号越多 | |stdPeriod(N) | 26 | 标准差计算窗口 | 通常与maPeriod保持一致 | |stdRange(k) | 1 | 轨道宽度(标准差倍数) | k 越大带宽越宽、触发次数越少;k 越小信号越频繁。经典取值为 2,文档取 1 相对激进 | |volume(下单量) | 100 股 | 每次交易手数 | A 股一手为 100 股,可按资金量等比例放大 | |backtest_initial_cash| 1000 | 初始资金 | 资金过小时受最小下单量约束明显 | |backtest_commission_ratio| 0.0001 | 佣金比例(0.01%) | 高频触发类策略对费率敏感 | |backtest_slippage_ratio| 0.0001 | 滑点比例(0.01%) | 市价单对滑点敏感 |

值得注意的调参细节:

  1. 标准差倍数 k:经典布林带默认 k=2(如 pytrader/backtest_strategies/BOLL.py 的nbdevup=2, nbdevdn=2),而本文策略取 k=1,轨道更窄、触发更频繁,与文档回测中 73.47% 的高胜率、但整体跑输大盘的结果是自洽的——窄轨道带来更多交易机会,但单次回归空间变小。
  2. 触发方式:文档采用"穿越"(上穿/下穿)而非"触碰"触发,data.close.values[-1]data.close.values[-2]的联合判断可有效过滤轨道附近的反复震荡。
  3. 复权方式:回测使用ADJUST_PREV(前复权),避免分红除权造成的价格跳空干扰指标计算;若涉及历史区间较长的回测,应统一数据复权口径。
  4. 实盘注意:代码注释明确指出,若在收盘那一根 bar 或 tick 触发交易信号,实盘可能无法成交,需要自行处理盘中确认逻辑。

七、总结

布林线均值回归是 Qbot 经典策略集中最简单、最容易上手复现的策略之一。其核心逻辑可以一句话概括:价格突破布林带边界即视为超买/超卖,反向交易等待价格回归中轨。本文从公式原理出发,完整复现了基于掘金量化平台的策略代码,并通过调整回测期验证了策略的稳健性边界——整体收益为正但多数时期跑输沪深 300,说明该策略适合作为震荡市中的卫星策略或组合因子,而非独立的大盘战胜者。同时,Qbot 仓库在pytraderqbot两个子项目中分别提供了 TA-Lib 模板、Backtrader 脚本与多因子合成三种 BOLL 落地形态,读者可根据自身回测框架(掘金 / 自研模板 / Backtrader)任选其一进行复现,并依据文中参数表调整窗口长度、标准差倍数、费率与滑点,进一步验证不同参数组合下的收益-回撤权衡。

再次提醒:以上策略与回测数据仅用于学习、交流、演示,不构成任何投资建议。实盘前请务必完成充分的样本外验证与风险控制设计。

【免费下载链接】Qbot[🔥updating ...] AI 自动量化交易机器人(完全本地部署) AI-powered Quantitative Investment Research Platform. 📃 online docs: https://ufund-me.github.io/Qbot ✨ :news: qbot-mini: https://github.com/Charmve/iQuant项目地址: https://gitcode.com/GitHub_Trending/qbot/Qbot

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询