问题描述
解决方案
这是一个非常经典的量化交易需求,通常被称为**“市场状态识别”或“双模策略”**(Regime Switching Strategy)。
要实现这个策略,核心难点在于如何定义“趋势”和“震荡”。
在本策略中,我将使用**布林带(Bollinger Bands)及其衍生指标布林带宽度(Bandwidth)**来实现:
-
市场状态识别(过滤器):
- 趋势模式:当布林带开口扩大(带宽变大)时,意味着波动率上升,市场可能进入趋势行情。
- 震荡模式:当布林带开口收窄(带宽变小)时,意味着波动率下降,市场处于横盘整理。
-
交易逻辑:
- 在趋势模式下(追涨杀跌): 价格突破上轨买入,跌破中轨卖出(止盈/止损)。
- 在震荡模式下(高抛低吸): 价格触及下轨买入(抄底),触及上轨卖出(高抛)。
策略代码实现
以下是基于 PTrade 框架的完整策略代码。
import numpy as np
import pandas as pd
def initialize(context):
"""
初始化函数,设置策略参数和股票池
"""
# 设置要操作的股票,这里以恒生电子为例
g.security = '600570.SS'
set_universe(g.security)
# 策略参数设置
g.n = 20 # 布林带计算周期(通常为20)
g.k = 2 # 布林带标准差倍数(通常为2)
# 核心参数:用于判断趋势还是震荡的阈值
# 逻辑:计算过去一段时间的平均带宽,如果当前带宽 > 平均带宽 * 1.2,认为是趋势;否则为震荡
# 这个系数需要根据具体标的波动性进行调整
g.trend_threshold_ratio = 1.2
# 设置滑点和手续费(回测时建议设置,更接近实盘)
set_commission(commission_ratio=0.0003, min_commission=5.0, type="STOCK")
set_slippage(slippage=0.002)
def get_bollinger_bands(security, n, k):
"""
计算布林带数据的辅助函数
返回: upper(上轨), middle(中轨), lower(下轨), bandwidth(带宽)
"""
# 获取过去 n+5 天的数据,保证计算均线时数据充足
# include=True 包含当前未完成的K线,用于实时计算
hist = get_history(n + 5, '1d', 'close', security, fq='pre', include=True)
if len(hist) < n:
return None, None, None, None
# 提取收盘价序列
close_prices = hist['close']
# 计算中轨(N日移动平均线)
middle = close_prices.rolling(window=n).mean()
# 计算标准差
std = close_prices.rolling(window=n).std()
# 计算上轨和下轨
upper = middle + k * std
lower = middle - k * std
# 计算带宽 (Bandwidth) = (上轨 - 下轨) / 中轨
# 带宽反映了市场的波动率
bandwidth = (upper - lower) / middle
# 返回最新的值(序列的最后一个)以及带宽序列(用于计算历史平均带宽)
return upper.iloc[-1], middle.iloc[-1], lower.iloc[-1], bandwidth
def handle_data(context, data):
"""
盘中运行函数,每个周期执行一次
"""
security = g.security
# 1. 获取布林带数据
up, mid, low, bandwidth_series = get_bollinger_bands(security, g.n, g.k)
if up is None:
return # 数据不足,暂不交易
# 2. 获取当前价格
current_price = data[security]['close']
# 3. 判断市场状态 (Regime Switching)
# 计算当前带宽
current_bandwidth = bandwidth_series.iloc[-1]
# 计算过去20天的平均带宽(作为基准)
avg_bandwidth = bandwidth_series[-20:].mean()
# 定义状态:如果当前波动率显著高于近期平均水平,判定为趋势模式
is_trend_mode = current_bandwidth > (avg_bandwidth * g.trend_threshold_ratio)
# 获取当前持仓
position = get_position(security).amount
cash = context.portfolio.cash
# 4. 执行交易逻辑
if is_trend_mode:
# ====================
# 模式 A: 趋势跟随 (Trend Following)
# ====================
# 逻辑:波动率放大,市场选择了方向,我们要顺势而为
# 买入信号:价格突破上轨,且空仓
if current_price > up and position == 0:
log.info("【趋势模式】价格突破上轨,波动率放大,追涨买入 %s" % security)
order_value(security, cash)
# 卖出信号:价格跌破中轨(趋势减弱或反转),清仓止损/止盈
# 注意:趋势策略中,通常不等到跌破下轨才卖,中轨往往是趋势的生命线
elif current_price < mid and position > 0:
log.info("【趋势模式】价格跌破中轨,趋势可能结束,平仓卖出 %s" % security)
order_target(security, 0)
else:
# ====================
# 模式 B: 震荡回归 (Mean Reversion)
# ====================
# 逻辑:波动率低,价格在布林带通道内运行,高抛低吸
# 买入信号:价格触及下轨(超卖),且空仓
if current_price < low and position == 0:
log.info("【震荡模式】价格触及下轨,波动率平稳,抄底买入 %s" % security)
order_value(security, cash)
# 卖出信号:价格触及上轨(超买),清仓止盈
elif current_price > up and position > 0:
log.info("【震荡模式】价格触及上轨,回归压力位,高抛卖出 %s" % security)
order_target(security, 0)
# 记录日志方便调试
# log.info("当前价格: %.2f, 上轨: %.2f, 下轨: %.2f, 状态: %s" % (
# current_price, up, low, "趋势" if is_trend_mode else "震荡"
# ))
策略逻辑详解
1. 核心指标:布林带宽度 (Bandwidth)
- 计算公式:
(上轨 - 下轨) / 中轨 - 作用:这是衡量市场“活跃度”的关键。
- 当带宽很窄时,说明股价波动很小,市场处于蓄势整理阶段(震荡)。
- 当带宽突然变大时,说明股价波动剧烈,通常伴随着突破,市场进入单边行情(趋势)。
2. 状态切换机制
代码中使用了 g.trend_threshold_ratio = 1.2。
- 我们计算当前的带宽,并与过去20天的平均带宽做比较。
- 如果
当前带宽 > 平均带宽 * 1.2,说明现在的波动率比平时高出20%,我们认为市场“醒了”,开始走趋势。 - 否则,认为市场还在“睡觉”或正常波动,适合高抛低吸。
3. 为什么趋势模式下卖出逻辑不同?
- 震荡时:我们在上轨卖出,因为预期价格会回落。
- 趋势时:价格突破上轨是买入信号(强者恒强),此时不能卖出。在趋势模式下,我们通常用中轨作为移动止损线,只要价格还在中轨上方,就一直持有,直到跌破中轨才认为趋势结束。
优化建议
- 参数调整:
g.trend_threshold_ratio是最敏感的参数。如果你发现策略频繁在震荡中追高被套,可以调高这个值(例如 1.5),让策略更难进入趋势模式。 - 增加过滤:在趋势买入时,可以增加成交量的判断(如:
volume > 20日均量),确保突破是有效的。 - 止损保护:震荡策略最怕的是“假震荡真突破”。例如在下轨买入后,价格没有反弹反而暴跌(单边下跌趋势开始)。建议在震荡买入后设置一个固定的硬止损(如亏损5%强平)。
Q&A
Q: 这个策略适合什么周期的回测?
A: 建议使用日线级别。分钟级别噪音较大,布林带的假突破会更多,导致频繁止损。
Q: 为什么使用 order_value 和 order_target?
A: order_value(security, cash) 表示用当前所有现金买入。order_target(security, 0) 表示将持仓调整为0(即清仓)。这是 PTrade 中最常用的仓位管理函数。
Q: 如何在 PTrade 中查看策略表现?
A: 将上述代码复制到 PTrade 的“策略研究”或“回测”模块中,点击“运行回测”,选择一个较长的时间段(如2020年至今),即可看到收益曲线和交易明细。