问题描述
解决方案
R-Breaker 是一种经典的日内交易策略,结合了趋势跟随(Trend)和反转(Reversal)两种逻辑。它在量化交易中非常流行,尤其适用于期货市场,也可以用于波动较大的股票。
该策略的核心在于根据前一个交易日的最高价(High)、最低价(Low)和收盘价(Close)计算出 6 个关键价位(3 个阻力位和 3 个支撑位),并结合当日盘中的价格走势来触发交易信号。
以下是基于 PTrade 框架实现的 R-Breaker 策略代码及详细说明。
1. 策略原理与公式
基准价(Pivot):
$$Pivot = (High_{prev} + Low_{prev} + Close_{prev}) / 3$$
六个关键价位:
- 突破买入价(Bbreak / R3): $High_{prev} + 2 \times (Pivot - Low_{prev})$
- 观察卖出价(Ssetup / R1): $2 \times Pivot - Low_{prev}$
- 反转卖出价(Senter / R2): $Pivot + (High_{prev} - Low_{prev})$
- 反转买入价(Benter / S2): $Pivot - (High_{prev} - Low_{prev})$
- 观察买入价(Bsetup / S1): $2 \times Pivot - High_{prev}$
- 突破卖出价(Sbreak / S3): $Low_{prev} - 2 \times (High_{prev} - Pivot)$
交易逻辑:
- 趋势策略(突破):
- 若价格 > R3,开多仓(做多)。
- 若价格 < S3,开空仓(做空)。
- 反转策略:
- 做空反转: 当日最高价 > R1(观察卖出价),且当前价格跌破 R2(反转卖出价)时,开空仓。
- 做多反转: 当日最低价 < S1(观察买入价),且当前价格突破 S2(反转买入价)时,开多仓。
- 日内平仓: 收盘前强制平仓。
2. PTrade 策略代码实现
以下代码可以直接复制到 PTrade 的策略编辑器中运行。为了演示方便,这里以股指期货(如 IF 主连)为例,因为 R-Breaker 需要做空机制才能发挥最大效用。如果是股票,只能做多和平仓。
import numpy as np
def initialize(context):
"""
初始化函数,设置策略参数
"""
# 设置标的:这里以沪深300股指期货主力合约为例
# 如果是股票回测,请修改为具体股票代码,如 '600519.SS'
g.security = 'IF2312.CCFX'
# 设置股票池
set_universe([g.security])
# 设置手续费(根据实际情况调整,期货通常按手或万分比)
# set_commission(commission_ratio=0.0001, min_commission=5.0, type='FUTURE')
# 策略参数
g.open_position = 0 # 持仓状态:0空仓,1多仓,-1空仓
# 每日盘前计算的关键价位
g.R3 = 0 # 突破买入价
g.R2 = 0 # 反转卖出价
g.R1 = 0 # 观察卖出价
g.S1 = 0 # 观察买入价
g.S2 = 0 # 反转买入价
g.S3 = 0 # 突破卖出价
# 记录当日盘中的最高价和最低价
g.high_today = 0
g.low_today = 0
# 设定每日收盘平仓时间
run_daily(context, close_all_positions, time='14:55')
def before_trading_start(context, data):
"""
盘前处理:获取昨日数据,计算 R-Breaker 的 6 个价位
"""
# 获取过去 1 天的日线数据(不包含当天)
# 注意:get_history 返回的是 DataFrame
hist = get_history(1, frequency='1d', field=['high', 'low', 'close'], security_list=g.security, include=False)
if len(hist) > 0:
# 提取昨日数据
# 注意:get_history 返回的 DataFrame 索引可能是时间,或者是多级索引,需根据 PTrade 版本处理
# 这里假设返回的是标准 DataFrame,直接取最后一行
high_prev = hist['high'].values[-1]
low_prev = hist['low'].values[-1]
close_prev = hist['close'].values[-1]
# 计算 Pivot
pivot = (high_prev + low_prev + close_prev) / 3.0
# 计算 6 个关键价位
g.R3 = high_prev + 2 * (pivot - low_prev) # 突破买入
g.S3 = low_prev - 2 * (high_prev - pivot) # 突破卖出
g.R1 = 2 * pivot - low_prev # 观察卖出
g.S1 = 2 * pivot - high_prev # 观察买入
g.R2 = pivot + (high_prev - low_prev) # 反转卖出
g.S2 = pivot - (high_prev - low_prev) # 反转买入
# 打印日志方便调试
log.info("R-Breaker Levels for {}: R3={:.2f}, R2={:.2f}, R1={:.2f}, S1={:.2f}, S2={:.2f}, S3={:.2f}".format(
g.security, g.R3, g.R2, g.R1, g.S1, g.S2, g.S3))
# 重置当日最高最低价记录
g.high_today = 0
g.low_today = 1000000 # 设置一个极大值初始值
def handle_data(context, data):
"""
盘中处理:每分钟运行一次,判断信号并交易
"""
# 获取当前时间,如果是收盘平仓时间段则跳过逻辑
current_hour = context.blotter.current_dt.hour
current_minute = context.blotter.current_dt.minute
if current_hour == 14 and current_minute >= 55:
return
security = g.security
# 获取当前分钟的最新价、最高价、最低价
# 注意:data[security] 获取的是当前分钟 Bar 的数据
current_price = data[security]['close']
current_high = data[security]['high']
current_low = data[security]['low']
# 更新当日盘中最高价和最低价
# 如果是开盘第一分钟,直接赋值
if g.high_today == 0:
g.high_today = current_high
g.low_today = current_low
else:
g.high_today = max(g.high_today, current_high)
g.low_today = min(g.low_today, current_low)
# 获取当前持仓数量
position = get_position(security).amount
# --- 交易逻辑 ---
# 1. 趋势突破逻辑
# 突破 R3,且空仓或持有空单 -> 做多
if current_price > g.R3:
if g.open_position <= 0:
log.info("触发趋势买入信号:价格 {:.2f} > R3 {:.2f}".format(current_price, g.R3))
order_target(security, 1) # 这里的 1 代表 1手,如果是股票代表 100股,需根据实际调整
g.open_position = 1
# 跌破 S3,且空仓或持有多单 -> 做空
elif current_price < g.S3:
if g.open_position >= 0:
log.info("触发趋势卖出信号:价格 {:.2f} < S3 {:.2f}".format(current_price, g.S3))
order_target(security, -1) # -1 代表持有空单 1手
g.open_position = -1
# 2. 反转逻辑
# 做空反转:当日最高价超过 R1 (观察卖出价),且当前价格跌破 R2 (反转卖出价)
elif g.high_today > g.R1 and current_price < g.R2:
if g.open_position >= 0:
log.info("触发反转卖出信号:最高价 {:.2f} > R1, 现价 {:.2f} < R2".format(g.high_today, current_price))
order_target(security, -1)
g.open_position = -1
# 做多反转:当日最低价低于 S1 (观察买入价),且当前价格突破 S2 (反转买入价)
elif g.low_today < g.S1 and current_price > g.S2:
if g.open_position <= 0:
log.info("触发反转买入信号:最低价 {:.2f} < S1, 现价 {:.2f} > S2".format(g.low_today, current_price))
order_target(security, 1)
g.open_position = 1
def close_all_positions(context):
"""
收盘前强制平仓
"""
security = g.security
position = get_position(security).amount
if position != 0:
log.info("收盘前强制平仓")
order_target(security, 0)
g.open_position = 0
3. 代码关键点解析
-
数据获取 (
get_history):- 我们在
before_trading_start中调用get_history获取前一天的日线数据。 include=False确保不包含当天的数据,避免未来函数。
- 我们在
-
盘中高低点追踪:
- R-Breaker 的反转逻辑依赖于“当日最高价是否触及观察线”。
- 因此,我们在
handle_data中使用g.high_today和g.low_today实时更新当日的最高和最低价。
-
交易执行 (
order_target):- 使用
order_target(security, amount)是最方便的调仓函数。 amount=1表示目标持仓为多头 1 手(或 100 股)。amount=-1表示目标持仓为空头 1 手。amount=0表示平仓。- 注意:如果是股票交易,PTrade 不支持直接传入负数进行做空(除非开通融券并使用融券接口)。如果是股票策略,你需要修改逻辑为:买入信号买入,卖出信号仅平掉多头仓位。
- 使用
-
日内平仓:
- R-Breaker 是典型的日内策略,不留隔夜仓以规避跳空风险。
- 使用
run_daily在14:55触发close_all_positions函数清空仓位。
4. 常见问题与调整
- 股票 vs 期货:
- 上述代码逻辑包含做空(
order_target设为负数)。如果你在做纯股票策略,请将所有涉及做空的代码(order_target(security, -1))改为order_target(security, 0)(仅平仓),或者删除做空逻辑。
- 上述代码逻辑包含做空(
- 参数优化:
- 标准的 R-Breaker 参数是固定的(系数为 2 和 1)。你可以尝试调整这些系数,或者根据波动率(ATR)来动态调整距离。
- 回测频率:
- 请确保回测频率选择 “分钟” 级别,因为
handle_data需要在盘中实时监控价格突破情况。
- 请确保回测频率选择 “分钟” 级别,因为
Q&A
Q: 为什么我的策略在股票回测中报错?
A: 股票通常不能直接做空。如果代码中执行了 order_target(security, -1) 而账户没有融券权限或回测不支持融券,会报错或忽略。请将做空逻辑改为平仓逻辑。
Q: 如何在实盘中使用?
A: 实盘中需要确保 g.security 是正确的合约代码(期货合约通常有月份后缀,如 IF2312.CCFX)。此外,实盘建议增加滑点控制和订单状态检查。
Q: 可以在 tick 级别运行吗?
A: 可以。PTrade 支持 tick_data 函数。你可以将 handle_data 中的逻辑迁移到 tick_data 中,以获得更快的响应速度(3秒一次),逻辑基本一致。