布林线均值回归策略

ZaynPei Lv6

布林线均值回归策略是一种结合了趋势跟踪均值回归两种不同交易理念的混合型策略, 不过主要是后者。它利用布林带(Bollinger Bands)来识别价格的极端状态,同时结合均线(Moving Averages)来确认市场的整体趋势方向。

布林带 (Bollinger Bands, BOLL) - 衡量价格的“常态”与“极端”

布林带由三条线组成,它在移动平均线的基础上引入了标准差 (Standard Deviation) 的概念,从而创造了一个动态的、能够自我适应市场波动性的价格通道。

  • 中轨线 (Middle Band): N日的简单移动平均线 (SMA)。

    • 代表了市场在过去 N 个周期内的平均成本或价值中枢,是趋势的基本方向。
  • 上轨线 (Upper Band): 中轨线 + K × N日标准差

  • 下轨线 (Lower Band): 中轨线 - K × N日标准差

    • 标准差是衡量数据离散程度的统计量。在这里,它衡量了过去 N 个周期内价格相对于中轨线的波动幅度。

    • K 是一个倍数参数(通常取2)。根据正态分布理论,大约95%的数据点会落在距离均值两个标准差的范围内。因此,上轨线和下轨线共同构建了一个动态的通道,这个通道定义了价格波动的“常态区间”。

    • 当市场波动加剧时,标准差会变大,通道随之变宽;当市场进入盘整,波动减小时,标准差会变小,通道随之收窄。这种自适应性是布林带指标的核心优势。

均值回归 (Mean Reversion) - 策略的哲学基础

均值回归策略基于一个金融学中的普遍现象:资产价格,即使在短期内可能因为市场情绪或随机事件而大幅偏离其内在价值,但从长期来看,总有一种力量会将其拉回到价值中枢。

布林带与均值回归的结合在于:

  • 策略将布林带的中轨线视为价格的“均值”或“价值中枢”。
  • 将上轨线和下轨线视为价格波动的“极端”边界。

核心假设: 当价格触摸甚至突破了上下轨时,意味着价格相对于其近期均值已经过度延伸 (Overextended),市场大概率出现了超买(突破上轨)或超卖(突破下轨)的情况。这种极端状态是不可持续的,价格在不久的将来有很高的概率会向中轨线回归。

策略逻辑

本策略的交易逻辑是均值回归思想最直接的应用:

第一步:计算指标

  • 根据设定的周期 N 和标准差倍数 K,计算出当前布林带的中轨、上轨和下轨。

第二步:定义交易信号

  • 卖出信号 (做空或平多仓): 当股价从通道内部向上穿越布林带上轨时触发。这被解释为市场超买,是价格即将回调的信号。

  • 买入信号 (做多或平空仓): 当股价从通道内部向下穿越布林带下轨时触发。这被解释为市场超卖,是价格即将反弹的信号。

第三步:仓位管理

  • 策略采用了一种简单的“买入持有,直至卖出信号出现”的模式。

    • 当收到买入信号且当前无持仓时,买入固定数量的股票。

    • 当收到卖出信号且当前有持仓时,卖出所有股票。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# 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)