问题描述
解决方案
这是一个非常实用的需求。在 QMT 中,实现“卖出股票后自动还款”主要有两种思路:
- 直接使用“卖券还款”指令(推荐):在卖出时直接使用
opType=31(卖券还款),这样卖出的资金会直接用于归还负债,无需分两步操作,这是最合规且效率最高的方式。 - 分步操作(卖出 -> 收到成交回报 -> 发起直接还款):如果你必须使用普通卖出(例如为了配合某些算法交易),然后想用脚本自动把钱还进去,则需要利用
deal_callback(成交回报)函数。
下面我将为你提供 第二种思路(分步操作) 的完整代码实现。这个脚本会监听你的信用账户,一旦检测到“卖出”成交,就会立即发起一笔“直接还款”的委托。
策略逻辑说明
init初始化:设置信用账户 ID 并绑定回调。deal_callback成交回报:这是核心。当有一笔交易成交时,系统会自动调用此函数。- 判断成交是否属于当前设置的信用账户。
- 判断交易方向是否为“卖出”(
m_nDirection == 49)。 - 获取成交金额(
m_dTradeAmount)。 - 执行还款:调用
passorder函数,使用opType=32(直接还款),金额为刚才卖出的成交额。
QMT 策略代码
# -*- coding: gbk -*-
import time
def init(ContextInfo):
# ================= 配置区域 =================
# 请在此处替换您的信用资金账号
ContextInfo.credit_account = '88000001'
# ===========================================
# 设置交易账号,必须在init中设置才能接收主推回调
ContextInfo.set_account(ContextInfo.credit_account)
print(f"策略已启动,正在监听账号 {ContextInfo.credit_account} 的卖出成交...")
def handlebar(ContextInfo):
# 本策略主要依赖事件驱动(callback),handlebar中无需写逻辑
pass
def deal_callback(ContextInfo, dealInfo):
"""
成交回报回调函数
当有成交发生时,系统自动调用此函数
"""
# 1. 安全检查:确保成交的账号是我们要操作的信用账号
if dealInfo.m_strAccountID != ContextInfo.credit_account:
return
# 2. 判断交易方向
# m_nDirection: 48=买入, 49=卖出
# 我们只关心“卖出”操作产生的资金
if dealInfo.m_nDirection == 49:
stock_code = dealInfo.m_strInstrumentID
trade_amount = dealInfo.m_dTradeAmount # 成交金额(元)
print(f"检测到卖出成交: {stock_code}, 成交金额: {trade_amount:.2f}")
# 3. 执行直接还款逻辑
# opType = 32: 直接还款
# orderType = 1102: 单股、单账号、普通、金额(元)方式下单
# 注意:由于卖出包含手续费和印花税,实际到账金额会略少于 trade_amount。
# 如果账户内没有额外可用资金,全额还款可能会因资金不足失败。
# 建议保留一点缓冲,或者确保账户有少量留存资金。
# 这里演示尝试归还全部成交金额
repay_amount = trade_amount
print(f"正在发起直接还款: {stock_code}, 金额: {repay_amount:.2f}")
passorder(
32, # opType: 32 代表直接还款
1102, # orderType: 1102 代表按金额(元)下单
ContextInfo.credit_account, # 账号
stock_code, # 代码 (通常填对应的标的代码)
5, # prType: 5 代表最新价 (还款时价格参数通常不敏感,但需符合格式)
-1, # price: 价格忽略
repay_amount, # volume: 还款金额
ContextInfo # 上下文对象
)
def order_callback(ContextInfo, orderInfo):
# 委托回报(可选),用于调试查看还款委托是否发出
pass
代码关键点解析
-
passorder参数详解 (针对还款):opType=32:这是 QMT 中“直接还款”的操作码。它告诉柜台你要用现金归还负债。orderType=1102:这一点非常重要。普通的股票买卖通常按“股数”下单(1101),但还款通常是按“金额”进行的。1102表示volume参数的单位是“元”。volume=repay_amount:传入刚才卖出获得的金额。
-
关于手续费的提示:
dealInfo.m_dTradeAmount返回的是成交金额(价格 × 数量)。- 实际回到你账户的资金 = 成交金额 - 佣金 - 印花税 - 过户费。
- 风险点:如果你账户里原本余额为 0,直接用
trade_amount去还款,可能会因为资金不足(差了手续费那部分)而导致还款委托废单。 - 建议:在实际使用中,建议将
repay_amount乘以一个系数(如 0.99),或者确保账户里本身留有一部分现金作为缓冲。
如何运行
- 打开 QMT 客户端,进入【模型研究】->【策略编辑器】。
- 新建一个 Python 策略,将上述代码粘贴进去。
- 修改账号:将
ContextInfo.credit_account = '88000001'中的号码改为你实际的信用资金账号。 - 点击【运行】(注意:必须是实盘模式,且需要在交易设置中勾选“模型交易”相关权限)。
- 之后你在交易界面手动卖出股票,或者其他策略卖出股票时,该脚本都会自动触发还款。
常见问题 Q&A
Q: 为什么不直接用“卖券还款” (opType 31)?
A: 如果你是手动下单或写下单策略,直接用 opType=31 是最好的。但有些场景下(例如使用条件单、网格交易等第三方工具),它们可能只发出了普通卖出指令。这个脚本就是为了给这些普通卖出指令做“善后”工作的。
Q: 脚本报错“资金不足”怎么办?
A: 如代码解析中所述,这是因为卖出成交额包含了手续费。你可以将代码中的 repay_amount = trade_amount 改为 repay_amount = trade_amount * 0.995 (预留 0.5% 扣除费用),或者在账户里常备几百块钱现金。
Q: 这个脚本会重复还款吗?
A: deal_callback 是由成交推送触发的。如果一笔委托分成了 3 次成交(例如 1000 股分成了 300、300、400 成交),这个函数会被调用 3 次,每次分别归还对应部分的金额。这是正确的逻辑,不会导致重复还款。