问题描述
解决方案
这是一个非常实用的风控或日内交易需求。在 QMT 中实现每天下午 14:55 自动平仓的策略,核心逻辑是在 handlebar 函数中监控当前时间,当时间到达指定时刻且当天尚未执行过平仓时,获取当前持仓并遍历下单平仓。
以下是完整的策略代码及详细说明。
策略代码
# -*- coding: gbk -*-
import time
def init(ContextInfo):
"""
初始化函数
"""
# ================= 配置区域 =================
# 请在此处填写您的资金账号
ContextInfo.account_id = '6000000000'
# 账号类型:'STOCK' (股票), 'FUTURE' (期货)
ContextInfo.account_type = 'STOCK'
# ===========================================
# 设置交易账号
ContextInfo.set_account(ContextInfo.account_id)
# 用于记录上一次执行平仓的日期,防止同一天重复触发
ContextInfo.last_close_date = ''
print("策略初始化完成,将在每天 14:55 执行自动平仓检查")
def handlebar(ContextInfo):
"""
行情驱动函数,每个Tick或K线调用一次
"""
# 只在实盘的最新一根K线(或最新Tick)执行,跳过历史K线回放
if not ContextInfo.is_last_bar():
return
# 1. 获取当前时间
# get_tick_timetag 获取的是毫秒级时间戳
current_timetag = ContextInfo.get_tick_timetag()
# 转换为 datetime 格式字符串
current_datetime_str = timetag_to_datetime(current_timetag, '%Y%m%d%H%M%S')
current_date = current_datetime_str[0:8] # 年月日
current_time = current_datetime_str[8:14] # 时分秒
# 2. 判断时间条件:是否到达 14:55:00 且当天未执行过
# 注意:这里判断 >= 145500 且 < 150000 是为了确保在收盘前触发
if current_time >= '145500' and current_time < '150000':
# 检查今天是否已经执行过平仓
if ContextInfo.last_close_date != current_date:
print(f"当前时间 {current_time},触发自动平仓逻辑...")
close_all_positions(ContextInfo)
# 更新执行日期,标记今天已完成
ContextInfo.last_close_date = current_date
def close_all_positions(ContextInfo):
"""
自定义函数:平掉所有持仓
"""
# 1. 获取持仓信息
# 参数:账号ID, 账号类型, 数据类型('POSITION')
positions = get_trade_detail_data(ContextInfo.account_id, ContextInfo.account_type, 'POSITION')
if not positions:
print("当前无持仓,无需平仓。")
return
print(f"检测到 {len(positions)} 只持仓,开始执行平仓...")
# 2. 遍历持仓并下单
for pos in positions:
stock_code = pos.m_strInstrumentID # 证券代码
volume = pos.m_nCanUseVolume # 可用持仓数量
# 如果可用持仓为0,跳过
if volume <= 0:
continue
print(f"正在平仓: {stock_code}, 数量: {volume}")
# 3. 执行下单
# opType=24: 股票卖出 (如果是期货,需根据多空方向选择 46(平今) 或 47(平昨))
# orderType=1101: 单股单账号普通股数下单
# prType=14: 对手价 (为了快速成交,也可以用 5-最新价 或 27-市价)
# price=-1: 价格,使用对手价时填-1即可
# quickTrade=1: 立即触发下单,不等待K线走完
if ContextInfo.account_type == 'STOCK':
passorder(24, 1101, ContextInfo.account_id, stock_code, 14, -1, volume, 'AutoClose', 1, '', ContextInfo)
# 如果是期货,需要判断持仓方向
elif ContextInfo.account_type == 'FUTURE':
# m_nDirection: 48是多单,49是空单
if pos.m_nDirection == 48:
# 平多 (优先平今 46, 也可以用 6)
passorder(46, 1101, ContextInfo.account_id, stock_code, 14, -1, volume, 'AutoClose', 1, '', ContextInfo)
elif pos.m_nDirection == 49:
# 平空 (优先平今 48, 也可以用 8)
passorder(48, 1101, ContextInfo.account_id, stock_code, 14, -1, volume, 'AutoClose', 1, '', ContextInfo)
print("平仓指令发送完毕。")
代码核心逻辑解析
-
时间获取与判断:
- 使用
ContextInfo.get_tick_timetag()获取当前最新的 Tick 时间戳。 - 使用
timetag_to_datetime将其转换为HHMMSS格式的字符串。 - 判断条件
current_time >= '145500'确保在下午 2 点 55 分及以后触发。
- 使用
-
防止重复触发:
- 引入变量
ContextInfo.last_close_date。 - 每次触发平仓后,将当前日期记录下来。
- 下一次
handlebar运行时,如果发现last_close_date等于current_date,说明今天已经执行过,直接跳过。
- 引入变量
-
获取持仓:
- 使用
get_trade_detail_data(..., 'POSITION')获取当前账户的所有持仓对象列表。 - 通过
pos.m_nCanUseVolume获取可用持仓(防止卖出已冻结或当日买入不可卖出的股票)。
- 使用
-
下单平仓 (
passorder):- 股票模式:使用
opType=24(卖出)。 - 报价类型:使用
prType=14(对手价),这在尾盘急于平仓时比限价单更容易成交。 - 快速交易:设置
quickTrade=1,这非常重要。默认情况下 QMT 是 K 线走完才下单,设置为 1 可以让信号在 Tick 到达时立即发出,确保 14:55 准时动作。
- 股票模式:使用
使用注意事项
- 运行周期:建议将策略运行在 1分钟线 (1m) 或 Tick 周期上,以保证时间判断的灵敏度。
- 资金账号:请务必在
init函数中修改ContextInfo.account_id为您真实的资金账号。 - 期货交易:代码中包含了一个简单的期货平仓分支(判断多空方向),如果您主要做期货,可能需要根据平今/平昨的手续费差异调整
opType。 - 实盘模式:此策略依赖实时时间,请在“实盘”模式下运行。
Q&A
Q: 为什么使用 get_tick_timetag 而不是 Python 自带的 datetime.now()?
A: 在 QMT 回测或模拟中,datetime.now() 返回的是计算机系统当前时间,而 get_tick_timetag 返回的是行情数据的发生时间。使用 API 自带的时间函数可以保证策略在回测和实盘中的逻辑一致性,且更贴合行情驱动的机制。
Q: passorder 中的 quickTrade=1 是什么意思?
A: QMT 的默认机制是“K线走完确认信号后下单”。如果在日线周期运行,不加这个参数,策略会等到 15:00 收盘后才发出信号,导致无法成交。quickTrade=1 告诉系统只要代码运行到这一行,立即发送委托,不需要等待 K 线结束。
Q: 如果 14:55 停牌了怎么办?
A: passorder 发出委托后,如果股票停牌,交易所会拒单或废单。策略中的 pos.m_nCanUseVolume 只是本地判断可用持仓,无法预知交易所状态。如果需要更严谨,可以在下单前调用 ContextInfo.is_suspended_stock() 判断是否停牌。