问题描述
解决方案
这是一个非常实用的量化需求。在 QMT 中,实现自动打新主要涉及三个核心 API 函数:
get_new_purchase_limit(accid): 查询账户的新股申购额度。get_ipo_data("STOCK"): 获取当日的新股申购信息(包括代码、价格、申购上限等)。passorder(...): 发送交易指令进行申购。
下面是一个完整的 Python 策略代码,你可以直接在 QMT 的“策略编辑器”中新建一个 Python 策略并粘贴进去。
QMT 自动打新策略代码
# -*- coding: gbk -*-
import time
def init(ContextInfo):
# ================= 策略参数设置 =================
# 请在此处填写您的资金账号
ContextInfo.accid = 'YOUR_ACCOUNT_ID'
# 设置交易账号,必须在init中设置
ContextInfo.set_account(ContextInfo.accid)
# 设置定时任务:每个交易日的 10:30:00 执行一次打新任务
# 建议避开开盘和收盘集合竞价时段,选择盘中时间
ContextInfo.run_time("auto_ipo_subscription", "1nDay", "2023-01-01 10:30:00")
print("自动打新策略已启动,将在每个交易日 10:30 运行。")
def auto_ipo_subscription(ContextInfo):
"""
自动打新核心逻辑函数
"""
print("=" * 30)
print(f"开始执行自动打新任务 - {time.strftime('%Y-%m-%d %H:%M:%S')}")
# 1. 获取账户新股申购额度
# 返回结果通常是一个字典,如 {'SH': 10000, 'SZ': 5000}
try:
purchase_limit = get_new_purchase_limit(ContextInfo.accid)
print(f"当前账户申购额度: {purchase_limit}")
except Exception as e:
print(f"获取申购额度失败: {e}")
return
# 2. 获取当日新股信息
# type="STOCK" 表示只获取新股,不包含新债
ipo_data_list = get_ipo_data("STOCK")
if not ipo_data_list:
print("今日无新股申购。")
return
print(f"今日共有 {len(ipo_data_list)} 只新股申购。")
# 3. 遍历新股并下单
for ipo in ipo_data_list:
try:
# 解析新股数据
# 注意:不同券商版本的QMT返回的字段名可能略有不同,通常包含以下信息
# 建议先打印 ipo 对象查看具体属性
# 申购代码 (注意:申购代码可能与股票代码不同,如 732xxx)
sub_code = ipo.get('purchaseCode') or ipo.get('stockCode')
stock_name = ipo.get('purchaseName') or ipo.get('stockName')
issue_price = float(ipo.get('price', 0)) # 发行价
max_qty = int(ipo.get('maxPurchaseNum', 0)) # 该股申购上限
market = sub_code.split('.')[-1] # 获取市场后缀,如 SH, SZ
print(f"正在处理: {stock_name} ({sub_code}), 价格: {issue_price}, 单股上限: {max_qty}")
# 4. 计算可申购数量
# 逻辑:取 (账户对应市场的额度) 和 (该股申购上限) 的较小值
available_quota = 0
# 简单的市场匹配逻辑
if market == 'SH':
available_quota = purchase_limit.get('SH', 0)
elif market == 'SZ':
available_quota = purchase_limit.get('SZ', 0)
else:
# 科创板或其他,通常包含在 SH 或 SZ 额度中,或者有单独字段
# 这里做一个简单的回退处理
available_quota = purchase_limit.get(market, 0)
# 最终下单数量 = min(账户额度, 个股上限)
# 注意:必须是 1000 (沪市主板) 或 500 (深市/科创) 的整数倍,这里假设额度已经是合规的
order_vol = min(available_quota, max_qty)
if order_vol <= 0:
print(f" -> 额度不足或无申购资格,跳过。账户额度: {available_quota}")
continue
# 5. 下单申购
# opType=23: 买入
# orderType=1101: 单股/单账号/普通/股数下单
# prType=11: 限价(对于新股即为发行价)
print(f" -> 准备申购 {order_vol} 股")
passorder(
23, # opType: 买入
1101, # orderType: 单账号普通下单
ContextInfo.accid, # 账号
sub_code, # 下单代码 (申购代码)
11, # prType: 指定价/限价
issue_price, # 下单价格
order_vol, # 下单数量
ContextInfo
)
print(f" -> {stock_name} 申购指令已发送!")
except Exception as e:
print(f" -> 处理 {ipo} 时发生错误: {e}")
print("自动打新任务执行完毕。")
print("=" * 30)
def handlebar(ContextInfo):
# 这是一个必须存在的函数,但我们的逻辑主要在定时任务中
pass
代码逻辑详解
-
初始化 (
init):ContextInfo.accid = 'YOUR_ACCOUNT_ID': 关键步骤,请务必将'YOUR_ACCOUNT_ID'替换为你实际的资金账号(字符串格式)。ContextInfo.run_time(...): 设置定时器。这里设置为每天10:30:00运行。打新通常在交易时间内进行即可,避开早盘高峰期可以防止网络拥堵。
-
获取额度 (
get_new_purchase_limit):- 这个函数返回一个字典,包含了你在上海(SH)、深圳(SZ)等市场的可申购额度。
- 额度是根据你 T-2 日的持仓市值计算的。
-
获取新股 (
get_ipo_data):type="STOCK"确保只获取新股数据。- 返回的数据包含了申购代码(注意:申购代码通常以 7 强头,与股票代码不同)、发行价格、个人申购上限等关键信息。
-
计算下单量:
- 打新有一个规则:你的申购数量不能超过你的持有市值对应的额度,也不能超过该新股规定的个人申购上限。
- 代码中使用
min(available_quota, max_qty)来确保下单数量合规。
-
下单 (
passorder):opType=23代表买入。prType=11代表指定价格(即发行价)。- 注意:新股申购不需要冻结资金(目前A股规则),但需要有额度。
如何使用
- 打开 QMT 客户端,进入【模型研究】或【策略编辑器】。
- 新建一个 Python 策略。
- 将上述代码复制进去。
- 修改代码中的
YOUR_ACCOUNT_ID为你的真实资金账号。 - 点击【编译】检查是否有语法错误。
- 在【模型交易】或【策略交易】中运行该策略,选择“实盘运行”(注意:打新必须是实盘,回测模式下无法模拟真实的额度和新股数据)。
常见问题与注意事项
- 字段名差异:不同券商提供的 QMT 底层数据结构可能略有差异(例如有的版本用
purchaseCode,有的用stockCode)。代码中使用了.get()方法尝试兼容,如果发现获取不到数据,请在日志中查看打印出的ipo对象结构,并调整键名。 - 运行频率:代码中使用了
run_time定时任务,这意味着你只需要让策略保持运行状态,它每天会自动触发一次。不要在handlebar里直接写打新逻辑,否则每根 K 线都会触发一次下单,导致重复报错。 - 风险提示:虽然打新风险较低,但程序化交易涉及自动下单,建议首次运行时在盘中盯着日志输出,确认逻辑无误。
Q&A
Q: 为什么 get_ipo_data 返回为空?
A: 请确认当前日期是否为交易日,且当天确实有新股发行。如果是周末或节假日,或者当天没有新股排期,返回列表为空是正常的。
Q: 申购额度 purchase_limit 返回 0 是为什么?
A: 申购额度是根据 T-2 日(前两个交易日)的日均持仓市值计算的。如果你前两天没有持仓或持仓市值不足 1 万元,额度将为 0。
Q: 代码报错 NameError: name 'get_new_purchase_limit' is not defined?
A: 请确保你的 QMT 客户端版本较新。部分老旧版本的 QMT 可能不支持某些 API。如果遇到此问题,请联系券商升级 QMT 客户端。