问题描述
解决方案
这是一个关于**日内跳空缺口回补(Gap Filling)**策略的实现方案。
策略逻辑概述
缺口回补策略基于“均值回归”的思想。当市场开盘价格显著偏离前一交易日的收盘价(形成缺口)时,价格往往有动力向昨收价回归,以“回补”这个缺口。
- 跳空低开(Gap Down):
- 条件:今日开盘价 < 昨日收盘价 × (1 - 阈值)。
- 行为:买入。
- 止盈:价格回升至昨日收盘价附近。
- 止损:价格继续下跌超过一定幅度。
- 跳空高开(Gap Up):
- 条件:今日开盘价 > 昨日收盘价 × (1 + 阈值)。
- 行为:卖出(股票需有底仓,或用于期货/融券)。
- 止盈:价格回落至昨日收盘价附近。
- 止损:价格继续上涨超过一定幅度。
- 日内平仓: 无论盈亏,在收盘前(如14:55)强制平仓,保持日内交易特性。
注意事项(A股 T+1 限制)
- 股票交易: 中国A股实行 T+1 制度。如果你当天买入,当天无法卖出。因此,纯日内回补策略在A股通常需要“底仓”(即账户里已有该股票,通过买入新仓、卖出旧仓来实现T+0),或者应用于期货、可转债、ETF等支持T+0的品种。
- 本代码示例: 为了演示清晰,我将编写一个**做多回补(跳空低开买入,尾盘或回补后卖出)**的逻辑。如果你用于股票,请确保你理解T+1的限制;如果你用于期货,逻辑是通用的。
QMT 策略代码实现
# -*- coding: gbk -*-
import pandas as pd
import time
def init(ContextInfo):
# ================= 策略参数设置 =================
# 交易账号 (请修改为您的实际账号)
ContextInfo.account_id = '6000000000'
# 账号类型:'STOCK'股票, 'FUTURE'期货
ContextInfo.account_type = 'STOCK'
# 策略运行标的 (示例:平安银行)
ContextInfo.stock_code = '000001.SZ'
# 缺口阈值 (例如 1% = 0.01)
ContextInfo.gap_threshold = 0.01
# 止损阈值 (例如亏损 2% 止损)
ContextInfo.stop_loss_pct = 0.02
# 单笔交易数量
ContextInfo.trade_volume = 100
# ================= 状态变量 =================
# 标记今日是否已经开仓
ContextInfo.has_opened = False
# 记录开仓价格
ContextInfo.open_trade_price = 0.0
# 记录目标回补价格(即昨收价)
ContextInfo.target_price = 0.0
# 设置股票池
ContextInfo.set_universe([ContextInfo.stock_code])
# 绑定账号
ContextInfo.set_account(ContextInfo.account_id)
print("策略初始化完成:日内缺口回补策略")
def handlebar(ContextInfo):
# 获取当前K线的时间
index = ContextInfo.barpos
timetag = ContextInfo.get_bar_timetag(index)
# 将时间戳转换为 datetime 对象以便提取小时和分钟
bar_time_str = timetag_to_datetime(timetag, '%H%M%S')
bar_time_int = int(bar_time_str)
# 获取当前最新价格
current_price = ContextInfo.get_market_data(['close'], stock_code=[ContextInfo.stock_code], period='1d', count=1, dividend_type='follow')
# 注意:get_market_data 返回的是 pandas Series/DataFrame,需要取值
if isinstance(current_price, (pd.Series, pd.DataFrame)) and not current_price.empty:
# 这里取的是当前bar的收盘价作为最新价
last_price = ContextInfo.get_market_data(['close'], stock_code=[ContextInfo.stock_code], period='1m', count=1, dividend_type='follow')
if isinstance(last_price, pd.DataFrame):
current_price = last_price.iloc[-1]['close']
else:
return # 数据获取失败
else:
return
# ================= 1. 开盘检测缺口 (09:30 - 09:35) =================
# 每天只开仓一次
if not ContextInfo.has_opened and 93000 <= bar_time_int <= 93500:
# 获取日线数据:取最近2根日线(今天和昨天)
# index 0 是昨天,index 1 是今天(包含今天的开盘价)
daily_data = ContextInfo.get_market_data_ex(
['open', 'close', 'preClose'],
[ContextInfo.stock_code],
period='1d',
count=1,
dividend_type='follow'
)
if ContextInfo.stock_code in daily_data:
df_daily = daily_data[ContextInfo.stock_code]
if not df_daily.empty:
# 获取今日开盘价
today_open = df_daily.iloc[-1]['open']
# 获取昨日收盘价 (preClose 字段通常比取上一根K线close更准确)
yesterday_close = df_daily.iloc[-1]['preClose']
ContextInfo.target_price = yesterday_close
# 计算缺口幅度
gap_pct = (today_open - yesterday_close) / yesterday_close
# --- 策略逻辑:跳空低开 (Gap Down) 买入 ---
if gap_pct < -ContextInfo.gap_threshold:
print(f"检测到跳空低开: 开盘{today_open}, 昨收{yesterday_close}, 幅度{gap_pct:.2%}")
# 执行买入
passorder(23, 1101, ContextInfo.account_id, ContextInfo.stock_code, 5, -1, ContextInfo.trade_volume, ContextInfo)
ContextInfo.has_opened = True
ContextInfo.open_trade_price = current_price
print(f"买入开仓: 价格 {current_price}, 数量 {ContextInfo.trade_volume}")
# ================= 2. 盘中监控 (止盈/止损) =================
if ContextInfo.has_opened:
# 止盈条件:价格回升到昨日收盘价(缺口回补)
if current_price >= ContextInfo.target_price:
print(f"缺口回补成功: 当前价{current_price} >= 昨收{ContextInfo.target_price}")
close_position(ContextInfo)
# 止损条件:价格继续下跌超过止损线
elif current_price <= ContextInfo.open_trade_price * (1 - ContextInfo.stop_loss_pct):
print(f"触发止损: 当前价{current_price}, 成本{ContextInfo.open_trade_price}")
close_position(ContextInfo)
# ================= 3. 尾盘强制平仓 (14:55) =================
if ContextInfo.has_opened and bar_time_int >= 145500:
print("尾盘强制平仓")
close_position(ContextInfo)
# ================= 4. 收盘重置状态 (15:00 后) =================
# 如果是回测,可能需要手动重置;实盘中第二天init会重新运行,或者在这里判断日期变化
if bar_time_int >= 150000:
ContextInfo.has_opened = False
def close_position(ContextInfo):
"""
平仓函数
"""
# 卖出平仓 (股票卖出操作码 24)
passorder(24, 1101, ContextInfo.account_id, ContextInfo.stock_code, 5, -1, ContextInfo.trade_volume, ContextInfo)
print(f"执行平仓: {ContextInfo.stock_code}")
# 重置开仓状态,防止今日再次开仓
ContextInfo.has_opened = False
# 标记为今日已完成交易,避免重复开仓(如果需要严格控制)
# 在此简单逻辑中,has_opened = False 会导致如果还在9:35之前可能再次开仓,
# 实际应用中建议增加一个 ContextInfo.traded_today 变量来控制一天只做一次。
代码关键点解析
-
数据获取 (
get_market_data_ex):- 我们使用
period='1d'获取日线数据。这是因为我们需要准确的“今日开盘价”和“昨日收盘价”。 preClose字段是交易所提供的昨收价,比自己去取上一根K线的close更稳健(处理了除权除息等情况)。
- 我们使用
-
时间控制:
timetag_to_datetime用于将毫秒时间戳转换为易读的格式(如093000)。- 开仓窗口:限制在
09:30到09:35之间,确保是针对开盘缺口的交易。 - 尾盘平仓:设置在
14:55,确保在收盘前平掉仓位(如果是T+0品种)。
-
交易逻辑:
- 跳空低开:
gap_pct < -阈值。逻辑是市场反应过度,预期反弹。执行买入。 - 止盈:当
current_price >= yesterday_close时,说明缺口已补,任务完成,平仓。
- 跳空低开:
-
下单函数 (
passorder):23: 股票买入。24: 股票卖出。1101: 单股、单账号、普通、股/手方式下单。5: 最新价报价(也可以用14对手价以保证成交)。
如何在 QMT 中运行
- 打开 QMT 软件,进入【模型研究】或【策略编辑器】。
- 新建一个 Python 策略。
- 将上述代码复制进去。
- 修改账号:将
ContextInfo.account_id修改为你自己的实盘或模拟盘账号。 - 修改标的:将
ContextInfo.stock_code修改为你想交易的股票或ETF代码。 - 设置周期:在界面上将运行周期设置为 1分钟 或 5分钟(因为需要在盘中实时监控价格变化)。
- 点击【编译】检查无误后,可以进行【回测】或【运行】。
常见问题 Q&A
Q: 为什么我回测时没有交易?
A:
- 检查回测时间段内是否有满足
1%(0.01) 缺口的行情。 - 检查
ContextInfo.stock_code是否在你的数据下载范围内。 - 确保回测的主图周期是分钟线(如1m),而不是日线。如果在日线周期运行
handlebar,一天只运行一次,无法实现日内平仓。
Q: 如何做空(跳空高开卖出)?
A:
如果是期货,将买入代码改为 opType=3 (开空),平仓改为 opType=4 (平昨) 或 5 (平今)。
如果是股票,你需要持有底仓。在开仓逻辑中,判断 gap_pct > 阈值,然后执行 passorder(24, ...) 卖出;在止盈逻辑(价格跌回昨收)时,执行 passorder(23, ...) 买回。
Q: get_market_data 和 get_market_data_ex 有什么区别?
A: get_market_data_ex 是新版接口,返回的数据结构更清晰(通常是 Dict of DataFrames),且支持订阅逻辑,建议优先使用。代码中混用了两者是为了展示不同场景,核心逻辑依赖 ex 接口获取日线数据。