问题描述
解决方案
在 QMT 量化交易平台中,区分期货的“平今仓”(Close Today)和“平昨仓”(Close Yesterday)主要涉及两个方面:一是如何获取当前持仓的今昨明细,二是如何发送指定的平仓指令。
这对于上期所(SHFE)等对平今仓手续费有特殊规定的交易所尤为重要。
以下是具体的实现方案和代码示例。
核心逻辑说明
-
获取持仓明细:
使用get_trade_detail_data函数获取持仓对象列表。每个持仓对象都有一个属性m_bIsToday。m_bIsToday为True:表示是今仓。m_bIsToday为False:表示是昨仓(老仓)。
-
发送交易指令:
使用passorder函数时,通过opType参数来指定平仓类型:- 平昨多(Close Long Yesterday):
opType = 1 - 平今多(Close Long Today):
opType = 2 - 平昨空(Close Short Yesterday):
opType = 4 - 平今空(Close Short Today):
opType = 5
- 平昨多(Close Long Yesterday):
策略代码实现
下面的代码展示了如何编写一个函数来统计特定合约的今昨持仓量,并演示如何分别发出平今和平昨的指令。
# -*- coding: gbk -*-
from coding_public import * # 导入常用模块
def init(ContextInfo):
# 设置资金账号,请替换为您的真实期货账号
ContextInfo.accID = 'YOUR_ACCOUNT_ID'
ContextInfo.set_account(ContextInfo.accID)
# 设置要操作的标的,例如螺纹钢主力
ContextInfo.symbol = 'rb2405.SF'
# 绑定行情,确保handlebar能驱动
ContextInfo.set_universe([ContextInfo.symbol])
def get_position_breakdown(ContextInfo, symbol):
"""
获取指定合约的持仓明细,区分多空及今昨
返回字典结构
"""
# 获取持仓列表
positions = get_trade_detail_data(ContextInfo.accID, 'future', 'position')
# 初始化统计数据
pos_stats = {
'long_today': 0, # 多头今仓
'long_yday': 0, # 多头昨仓
'short_today': 0, # 空头今仓
'short_yday': 0 # 空头昨仓
}
for pos in positions:
# 过滤非目标合约
if pos.m_strInstrumentID + '.' + pos.m_strExchangeID != symbol:
continue
# 判断方向 (48: 多头, 49: 空头)
is_long = (pos.m_nDirection == 48)
# 判断今昨 (m_bIsToday: True为今仓, False为昨仓)
is_today = pos.m_bIsToday
volume = pos.m_nVolume
if is_long:
if is_today:
pos_stats['long_today'] += volume
else:
pos_stats['long_yday'] += volume
else:
if is_today:
pos_stats['short_today'] += volume
else:
pos_stats['short_yday'] += volume
return pos_stats
def handlebar(ContextInfo):
# 获取当前K线位置,避免历史回测重复发单(如果是实盘)
if not ContextInfo.is_last_bar():
return
symbol = ContextInfo.symbol
# 1. 获取持仓分布
pos_data = get_position_breakdown(ContextInfo, symbol)
print(f"当前持仓状态 [{symbol}]: {pos_data}")
# 2. 示例操作逻辑:
# 假设策略逻辑是:如果有昨多仓,优先平昨;如果没有昨仓但有今仓,则平今。
# --- 平多头示例 ---
target_close_vol = 1 # 假设我们要平1手多单
if pos_data['long_yday'] >= target_close_vol:
# 昨仓充足,执行“平昨多”
print("执行:平昨多")
passorder(1, 1101, ContextInfo.accID, symbol, 5, -1, target_close_vol, ContextInfo)
elif pos_data['long_today'] >= target_close_vol:
# 昨仓不足,今仓充足,执行“平今多”
# 注意:上期所平今手续费可能较贵,策略需考虑成本
print("执行:平今多")
passorder(2, 1101, ContextInfo.accID, symbol, 5, -1, target_close_vol, ContextInfo)
else:
print("多头持仓不足,无法平仓")
# --- 平空头示例 ---
# 假设我们要平1手空单,且强制平今(例如做日内超短)
target_close_short_vol = 1
if pos_data['short_today'] >= target_close_short_vol:
print("执行:平今空")
# opType 5: 平今空
passorder(5, 1101, ContextInfo.accID, symbol, 5, -1, target_close_short_vol, ContextInfo)
关键参数详解
在 passorder(opType, orderType, accountID, orderCode, prType, price, volume, ContextInfo) 函数中,opType 是控制开平及今昨的核心参数:
| opType 值 | 含义 | 适用场景 |
|---|---|---|
| 0 | 开多 | 买入开仓 |
| 1 | 平昨多 | 卖出平仓(指定平昨仓) |
| 2 | 平今多 | 卖出平仓(指定平今仓) |
| 3 | 开空 | 卖出开仓 |
| 4 | 平昨空 | 买入平仓(指定平昨仓) |
| 5 | 平今空 | 买入平仓(指定平今仓) |
替代方案:使用便捷函数
如果您不想自己判断持仓数量,只想按照“优先平今”或“优先平昨”的顺序进行操作,QMT 也提供了封装好的便捷函数(底层会自动拆单):
sell_close_tdayfirst(...): 卖出平仓(平多),平今优先。如果今仓不够,自动平昨。sell_close_ydayfirst(...): 卖出平仓(平多),平昨优先。如果昨仓不够,自动平今。buy_close_tdayfirst(...): 买入平仓(平空),平今优先。buy_close_ydayfirst(...): 买入平仓(平空),平昨优先。
示例:
# 平多,优先平掉昨天的老仓(通常用于降低手续费或长期持有逻辑)
sell_close_ydayfirst(ContextInfo.symbol, 1, 'LATEST', 0, ContextInfo, ContextInfo.accID)
总结
- 如果需要精确控制(例如只平昨,绝对不平今),请先通过
get_trade_detail_data判断m_bIsToday属性统计数量,然后使用passorder配合opType(1, 2, 4, 5) 下单。 - 如果只需要模糊控制(例如优先平昨,不够了再平今),直接使用
*_ydayfirst系列辅助函数即可。
Q&A
Q: 为什么在上期所(SHFE)交易中区分平今和平昨特别重要?
A: 上期所的部分品种(如螺纹钢、热卷等)在特定时期会对“平今仓”收取较高的手续费(有时是开仓费用的数倍),而“平昨仓”通常费率较低或免费。因此,策略逻辑中正确区分两者可以显著降低交易成本。
Q: get_trade_detail_data 返回的持仓列表中,如果我有 5 手今仓和 5 手昨仓,会返回几条记录?
A: 通常会返回两条记录。一条记录的 m_bIsToday 为 True(数量5),另一条为 False(数量5)。你需要遍历列表并将它们分别统计。
Q: 如果我使用 passorder 发送了平昨指令,但我实际只有今仓,会发生什么?
A: 委托会成为废单(Junk Order)或被柜台拒绝,提示“平仓量超过持仓量”或“无可平仓位”,因为你指定的“昨仓”数量为 0。因此在发送精确指令前,务必检查对应的持仓数量。