🚀 AI 一键生成 qmt 策略代码

如何使用 QMT API 实现全自动化的新股新债申购?

qmt | 发布时间: 2025/12/17
以下内容由 EasyQuant 生成。

问题描述

我想写个程序自动打新,怎么用代码查询我的申购额度,获取今天有哪些新股可以申购,然后自动下单?

解决方案

这是一个非常实用的量化需求。在 QMT 中,实现自动打新主要涉及三个核心 API 函数:

  1. get_new_purchase_limit(accid): 查询账户的新股申购额度。
  2. get_ipo_data("STOCK"): 获取当日的新股申购信息(包括代码、价格、申购上限等)。
  3. 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

代码逻辑详解

  1. 初始化 (init):

    • ContextInfo.accid = 'YOUR_ACCOUNT_ID': 关键步骤,请务必将 'YOUR_ACCOUNT_ID' 替换为你实际的资金账号(字符串格式)。
    • ContextInfo.run_time(...): 设置定时器。这里设置为每天 10:30:00 运行。打新通常在交易时间内进行即可,避开早盘高峰期可以防止网络拥堵。
  2. 获取额度 (get_new_purchase_limit):

    • 这个函数返回一个字典,包含了你在上海(SH)、深圳(SZ)等市场的可申购额度。
    • 额度是根据你 T-2 日的持仓市值计算的。
  3. 获取新股 (get_ipo_data):

    • type="STOCK" 确保只获取新股数据。
    • 返回的数据包含了申购代码(注意:申购代码通常以 7 强头,与股票代码不同)、发行价格、个人申购上限等关键信息。
  4. 计算下单量:

    • 打新有一个规则:你的申购数量不能超过你的持有市值对应的额度,也不能超过该新股规定的个人申购上限。
    • 代码中使用 min(available_quota, max_qty) 来确保下单数量合规。
  5. 下单 (passorder):

    • opType=23 代表买入。
    • prType=11 代表指定价格(即发行价)。
    • 注意:新股申购不需要冻结资金(目前A股规则),但需要有额度。

如何使用

  1. 打开 QMT 客户端,进入【模型研究】或【策略编辑器】。
  2. 新建一个 Python 策略。
  3. 将上述代码复制进去。
  4. 修改代码中的 YOUR_ACCOUNT_ID 为你的真实资金账号。
  5. 点击【编译】检查是否有语法错误。
  6. 在【模型交易】或【策略交易】中运行该策略,选择“实盘运行”(注意:打新必须是实盘,回测模式下无法模拟真实的额度和新股数据)。

常见问题与注意事项

  • 字段名差异:不同券商提供的 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 客户端。