问题描述
解决方案
这是一个基于聚宽(JoinQuant)API编写的早盘区间突破策略(Opening Range Breakout, ORB)。
策略逻辑说明
- 核心思想:利用开盘后的一段时间(例如前30分钟)的市场波动来确定当天的支撑位和阻力位。
- 区间定义:
- 上轨(High):开盘前30分钟(09:30 - 10:00)内的最高价。
- 下轨(Low):开盘前30分钟(09:30 - 10:00)内的最低价。
- 交易信号:
- 买入:当价格突破上轨,且当前无持仓时,全仓买入。
- 止损/卖出:当价格跌破下轨,且当前有持仓时,清仓止损。
- 尾盘平仓:无论盈亏,在收盘前(例如 14:55)强制平仓,不持仓过夜(日内策略)。
- 标的:示例中使用
000001.XSHE(平安银行),您可以根据需要修改。
策略代码
# -*- coding: utf-8 -*-
# 导入聚宽函数库
import jqdata
import datetime
def initialize(context):
"""
初始化函数,设定基准、手续费、滑点、全局变量等
"""
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 设置日志级别
log.set_level('order', 'info')
# 股票类每笔交易时的手续费是:买入时佣金万分之三,卖出时佣金万分之三加千分之一印花税, 每笔交易佣金最低扣5块钱
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# 定义全局变量
g.security = '000001.XSHE' # 操作的标的:平安银行
g.range_minutes = 30 # 定义早盘区间的时间长度(分钟),这里是30分钟
# 每日区间的高低点变量
g.range_high = None
g.range_low = None
g.range_calculated = False # 标记今日是否已经计算过区间
# 运行频率:分钟级回测
# 每天开盘前运行一次
run_daily(before_market_open, time='before_open', reference_security=g.security)
# 交易时间内每分钟运行一次
run_daily(trade_logic, time='every_bar', reference_security=g.security)
# 收盘后运行一次
run_daily(after_market_close, time='after_close', reference_security=g.security)
def before_market_open(context):
"""
开盘前运行函数
"""
# 重置每日变量
g.range_high = None
g.range_low = None
g.range_calculated = False
log.info("准备开始新的交易日: %s" % context.current_dt.date())
def trade_logic(context):
"""
主交易逻辑,每分钟执行一次
"""
# 获取当前时间
current_dt = context.current_dt
current_time_str = current_dt.strftime('%H:%M')
# 1. 定义时间窗口
# 计算区间结束的时间点 (09:30 + g.range_minutes)
# 简单的处理方式:直接判断分钟数。注意:聚宽的分钟bar时间是该分钟结束的时间。
# 09:30-10:00 共有30根bar。第30根bar的时间是 10:00。
# 设定区间结束时间字符串 (这里简化处理,假设是10:00)
range_end_time = '10:00'
# 设定尾盘平仓时间
exit_time = '14:55'
# 2. 计算区间高低点 (仅在指定时间点计算一次)
if not g.range_calculated and current_time_str == range_end_time:
# 获取过去 g.range_minutes 分钟的分钟线数据
# include_now=True 包含当前这一分钟(10:00)的数据
bars = get_bars(g.security, count=g.range_minutes, unit='1m',
fields=['high', 'low'], include_now=True)
if bars is not None and len(bars) > 0:
g.range_high = bars['high'].max()
g.range_low = bars['low'].min()
g.range_calculated = True
log.info("早盘区间确定 (09:30-%s): High=%.2f, Low=%.2f" % (range_end_time, g.range_high, g.range_low))
else:
log.warn("获取行情数据失败,无法计算区间")
# 3. 交易执行逻辑 (在区间确定后,且在收盘平仓前)
if g.range_calculated and current_time_str > range_end_time and current_time_str < exit_time:
# 获取当前最新价格
current_data = get_current_data()
current_price = current_data[g.security].last_price
# 获取当前持仓
position = context.portfolio.positions[g.security]
# 策略逻辑:
# 如果价格突破上轨,且无持仓 -> 买入
if current_price > g.range_high and position.closeable_amount == 0:
# 全仓买入
order_value(g.security, context.portfolio.available_cash)
log.info("价格(%.2f)突破上轨(%.2f),买入 %s" % (current_price, g.range_high, g.security))
# 如果价格跌破下轨,且有持仓 -> 卖出 (止损)
elif current_price < g.range_low and position.closeable_amount > 0:
# 清仓
order_target(g.security, 0)
log.info("价格(%.2f)跌破下轨(%.2f),止损卖出 %s" % (current_price, g.range_low, g.security))
# 4. 尾盘强制平仓
if current_time_str == exit_time:
position = context.portfolio.positions[g.security]
if position.closeable_amount > 0:
order_target(g.security, 0)
log.info("尾盘(%s)强制平仓" % exit_time)
def after_market_close(context):
"""
收盘后运行函数
"""
log.info("交易日结束")
# 打印当天的账户权益
log.info("当前总权益: %.2f" % context.portfolio.total_value)
常见问题 Q&A
Q: 这个策略适合什么频率的回测?
A: 必须选择 “分钟” 级回测。因为策略依赖于日内的分钟K线来确定早盘区间(09:30-10:00)以及盘中的实时突破,天级回测无法获取这些细节。
Q: 如何修改早盘区间的时间长度?
A: 修改 initialize 函数中的 g.range_minutes 变量。例如改为 60,则区间变为 09:30 - 10:30。同时需要注意修改 trade_logic 中 range_end_time 的字符串判断逻辑以匹配新的时间。
Q: 为什么使用 get_bars 而不是 attribute_history?
A: get_bars 是聚宽较新的API,支持获取包含当前分钟(include_now=True)的数据,这在计算截止到整点(如10:00)的区间时非常准确。
Q: 策略支持做空吗?
A: 上述代码是针对A股股票设计的,默认只支持做多(Long Only)。如果价格跌破下轨,策略会执行卖出平仓(止损),但不会开空单。如果您交易期货,可以使用 order_target 配合 side='short' 进行修改。