问题描述
解决方案
这是一个基于 QMT 平台的 ORB (Opening Range Breakout,开盘区间突破) 策略实现。
策略逻辑说明
- 定义区间:设定开盘后的一段时间(例如 09:30 - 10:00)为“开盘区间”。
- 计算上下轨:记录该时间段内的最高价(High)和最低价(Low)。
- 突破交易:
- 当价格突破上轨时,买入(做多)。
- 当价格跌破下轨时,卖出(平仓或做空,本代码以股票做多为例,跌破下轨则平仓止损)。
- 尾盘平仓:在收盘前(例如 14:55)强制平仓,不持仓过夜(日内策略)。
策略代码
请在 QMT 的策略编辑器中新建一个 Python 策略,并将以下代码复制进去。
# -*- coding: gbk -*-
import pandas as pd
import time
import datetime
def init(ContextInfo):
"""
初始化函数
"""
# 1. 设置策略参数
ContextInfo.stock_code = '600000.SH' # 交易标的,例如浦发银行
ContextInfo.account_id = 'YOUR_ACCOUNT_ID' # 请替换为您的资金账号
ContextInfo.account_type = 'STOCK' # 账号类型:'STOCK'股票, 'FUTURE'期货
# ORB 参数设置
ContextInfo.orb_end_time = "100000" # 开盘区间结束时间,格式 HHMMSS (10:00:00)
ContextInfo.close_time = "145500" # 尾盘平仓时间 (14:55:00)
ContextInfo.trade_qty = 100 # 每次交易数量 (股/手)
# 2. 设置股票池
ContextInfo.set_universe([ContextInfo.stock_code])
# 3. 设置账号 (实盘/回测必须)
ContextInfo.set_account(ContextInfo.account_id)
# 4. 初始化全局变量
ContextInfo.current_date = None # 当前交易日期记录
ContextInfo.high_band = -1 # 上轨
ContextInfo.low_band = 999999 # 下轨
ContextInfo.has_traded = False # 当日是否已开仓标记
print("ORB 策略初始化完成")
def handlebar(ContextInfo):
"""
K线周期运行函数 (建议在 1分钟 或 5分钟 周期运行)
"""
# 获取当前 K 线的位置
index = ContextInfo.barpos
# 获取当前 K 线的时间戳 (毫秒)
timetag = ContextInfo.get_bar_timetag(index)
# 将时间戳转换为 datetime 对象和字符串
dt_obj = timetag_to_datetime(timetag, '%Y%m%d%H%M%S')
current_date_str = dt_obj[0:8] # YYYYMMDD
current_time_str = dt_obj[8:] # HHMMSS
# --- 1. 新的一天,重置变量 ---
if ContextInfo.current_date != current_date_str:
ContextInfo.current_date = current_date_str
ContextInfo.high_band = -1
ContextInfo.low_band = 999999
ContextInfo.has_traded = False
print(f"=== 新交易日: {current_date_str} ===")
# 获取当前 K 线的行情数据
# 注意:get_market_data 返回的是 pandas Series/DataFrame
# 这里取当前这根 bar 的高低收
current_high = ContextInfo.get_market_data(['high'], stock_code=[ContextInfo.stock_code], count=1, period=ContextInfo.period, dividend_type='follow')
current_low = ContextInfo.get_market_data(['low'], stock_code=[ContextInfo.stock_code], count=1, period=ContextInfo.period, dividend_type='follow')
current_close = ContextInfo.get_market_data(['close'], stock_code=[ContextInfo.stock_code], count=1, period=ContextInfo.period, dividend_type='follow')
# 数据校验,防止取不到数据报错
if current_close.empty:
return
price = current_close.iloc[-1]
high = current_high.iloc[-1]
low = current_low.iloc[-1]
# --- 2. 计算 ORB 区间 (09:30 - 10:00) ---
if current_time_str <= ContextInfo.orb_end_time:
# 更新区间最高价
if high > ContextInfo.high_band:
ContextInfo.high_band = high
# 更新区间最低价
if low < ContextInfo.low_band:
ContextInfo.low_band = low
# 在区间结束的那一刻打印一下确定的范围
if current_time_str == ContextInfo.orb_end_time:
print(f"ORB区间确定: 上轨={ContextInfo.high_band}, 下轨={ContextInfo.low_band}")
# --- 3. 交易逻辑 (10:00 - 14:55) ---
elif ContextInfo.orb_end_time < current_time_str < ContextInfo.close_time:
# 获取当前持仓
position = get_position(ContextInfo, ContextInfo.stock_code)
# 策略逻辑:如果还没交易过,且没有持仓
if not ContextInfo.has_traded and position == 0:
# 突破上轨 -> 买入
if price > ContextInfo.high_band:
print(f"时间:{current_time_str} 价格:{price} 突破上轨:{ContextInfo.high_band} -> 买入")
order_shares(ContextInfo.stock_code, ContextInfo.trade_qty, 'fix', price, ContextInfo, ContextInfo.account_id)
ContextInfo.has_traded = True
# 跌破下轨 -> (如果是期货可以做空,股票则无操作或视为弱势不操作)
elif price < ContextInfo.low_band:
# 股票策略通常不做空,这里仅打印日志
# 如果是期货,可以使用 buy_open / sell_open
print(f"时间:{current_time_str} 价格:{price} 跌破下轨:{ContextInfo.low_band} -> 弱势区域,不操作")
ContextInfo.has_traded = True # 标记为已触发过信号,避免反复判断
# 止损逻辑:如果持有仓位,且价格跌破下轨 -> 卖出止损
elif position > 0:
if price < ContextInfo.low_band:
print(f"时间:{current_time_str} 价格:{price} 跌破下轨:{ContextInfo.low_band} -> 止损平仓")
order_shares(ContextInfo.stock_code, -position, 'fix', price, ContextInfo, ContextInfo.account_id)
# --- 4. 尾盘清仓 (14:55 以后) ---
elif current_time_str >= ContextInfo.close_time:
position = get_position(ContextInfo, ContextInfo.stock_code)
if position > 0:
print(f"尾盘清仓: {current_time_str}, 卖出 {position} 股")
order_shares(ContextInfo.stock_code, -position, 'fix', price, ContextInfo, ContextInfo.account_id)
def get_position(ContextInfo, stock_code):
"""
辅助函数:获取指定标的的持仓数量
"""
# 获取持仓对象列表
positions = get_trade_detail_data(ContextInfo.account_id, ContextInfo.account_type, 'POSITION')
for pos in positions:
if pos.m_strInstrumentID + "." + pos.m_strExchangeID == stock_code:
# 返回当前持仓量
return pos.m_nVolume
return 0
使用说明与注意事项
-
运行周期:
- 请务必在 1分钟 (1m) 或 5分钟 (5m) 周期下运行此策略。如果在日线周期运行,
handlebar每天只调用一次,无法捕捉日内的 10:00 时间点和盘中突破。
- 请务必在 1分钟 (1m) 或 5分钟 (5m) 周期下运行此策略。如果在日线周期运行,
-
账号设置:
- 代码中的
ContextInfo.account_id = 'YOUR_ACCOUNT_ID'必须修改为您实际的资金账号。 - 如果是股票,
account_type设为'STOCK';如果是期货,设为'FUTURE'。
- 代码中的
-
标的设置:
- 修改
ContextInfo.stock_code为您想交易的股票或合约代码(如'600000.SH'或'IF2312.IF')。
- 修改
-
交易函数:
- 本代码使用了
order_shares(指定股数交易)。 'fix'代表限价单(以触发时的最新价下单)。如果需要更快的成交,可以改为'market'(市价单,需注意滑点)。
- 本代码使用了
-
回测与实盘:
- 回测:请确保已下载对应品种的分钟级历史数据。
- 实盘:请确保 QMT 客户端已登录交易账号,并且策略界面中的“模型交易”已勾选该策略。
常见问题 (Q&A)
Q: 为什么策略没有在 10:00 准时下单?
A: 策略是基于 K 线驱动的。如果使用的是 5 分钟 K 线,信号只会在 5 分钟结束时触发。如果该股票流动性差,某分钟没有成交(没有 K 线生成),handlebar 也不会运行。建议使用流动性好的标的配合 1 分钟周期。
Q: 如何改为期货做空策略?
A: 在代码的“跌破下轨”逻辑中,将 print 语句替换为 passorder 或 sell_open 函数进行开空操作,并在尾盘平仓逻辑中加入平空单的逻辑(buy_close)。
Q: 为什么 get_market_data 有时候报错?
A: 请检查是否补充了完整的数据。另外,QMT 的数据接口返回的是 Pandas 格式,代码中使用了 .iloc[-1] 来获取最新值,确保了格式的正确解析。