🚀 AI 一键生成 qmt 策略代码

QMT 实战教程:如何结合 get_ipo_data 和 get_new_purchase_limit 实现智能打新?

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

问题描述

如何编写一个可以自动查询当天有哪些新股可以申购,并根据我的账户申购额度自动下单的策略?

解决方案

这是一个非常实用的策略需求。在 QMT 中,我们可以利用 get_ipo_data 获取当日新股信息,并结合 get_new_purchase_limit 获取账户的申购额度来实现自动打新。

策略设计思路

  1. 获取新股信息:使用 get_ipo_data("STOCK") 获取当天所有可申购的新股列表,包含申购代码、发行价格、申购上限等信息。
  2. 获取账户额度:使用 get_new_purchase_limit(accid) 获取当前资金账号在上海主板、深圳市场、科创板的可用申购额度。
  3. 计算申购数量
    • 对于每一只新股,我们需要判断它属于哪个市场(沪市主板、深市、科创板)。
    • 申购数量 = min(账户在该市场的可用额度, 该新股的单账户申购上限)
    • 确保申购数量大于 0。
  4. 执行下单:使用 passorder 函数进行申购。申购通常使用“限价”委托,价格为发行价。
  5. 运行频率控制:打新每天只需执行一次。我们需要在策略中增加逻辑,确保在交易时间内(如 10:00 后)只运行一次,避免重复下单。

策略代码实现

# -*- coding: gbk -*-
import time

def init(ContextInfo):
    # ================= 策略参数设置 =================
    # 请在此处填写您的资金账号
    ContextInfo.acc_id = 'YOUR_ACCOUNT_ID' 
    # 账号类型:'STOCK' 代表股票,'CREDIT' 代表信用账户
    ContextInfo.acc_type = 'STOCK'
    # ==============================================

    # 设置交易账号
    ContextInfo.set_account(ContextInfo.acc_id)
    
    # 用于记录当天是否已经执行过打新,格式为 'YYYYMMDD'
    ContextInfo.last_run_date = ''
    
    print("新股自动申购策略已启动,账号: {}".format(ContextInfo.acc_id))

def handlebar(ContextInfo):
    # 仅在实盘的最后一根K线(最新行情)运行
    if not ContextInfo.is_last_bar():
        return

    # 获取当前日期和时间
    current_date = time.strftime('%Y%m%d', time.localtime())
    current_time = time.strftime('%H%M%S', time.localtime())

    # 控制运行时间:建议在 09:45 到 14:50 之间运行,避开集合竞价的不确定性
    if not ('094500' <= current_time <= '145000'):
        return

    # 控制每天只运行一次
    if ContextInfo.last_run_date == current_date:
        return

    print("=" * 30)
    print("开始检查今日新股申购信息...")

    # 1. 获取当日新股信息
    # type="STOCK" 只获取新股,不包含新债
    ipo_data = get_ipo_data("STOCK")
    
    if not ipo_data:
        print("今日无新股可申购。")
        ContextInfo.last_run_date = current_date
        return

    # 2. 获取账户申购额度
    # 返回字典,通常包含 '上海主板', '深圳市场', '上海科创板' 等键
    purchase_limit = get_new_purchase_limit(ContextInfo.acc_id)
    print("当前账户申购额度: {}".format(purchase_limit))

    # 3. 遍历新股并下单
    # ipo_data 通常是一个列表,每个元素是一个对象或字典
    # 注意:不同券商版本返回结构可能略有差异,以下代码基于标准结构编写
    
    for stock in ipo_data:
        # 获取新股基本信息
        # 注意:这里假设 stock 是对象,如果报错请尝试 stock['instrument']
        try:
            code = stock.instrument      # 申购代码,如 732xxx.SH
            name = stock.name            # 股票名称
            price = stock.issuePrice     # 发行价格
            max_qty = stock.maxPurchaseNum # 单账户申购上限
        except:
            # 兼容字典访问方式
            code = stock.get('instrument')
            name = stock.get('name')
            price = stock.get('issuePrice')
            max_qty = stock.get('maxPurchaseNum')

        if not code:
            continue

        # 4. 判断市场并获取对应额度
        market_limit = 0
        market_name = ""

        if code.endswith('.SH'):
            if code.startswith('787') or code.startswith('688'): # 科创板申购代码通常以787开头,股票代码688
                market_name = '上海科创板'
                market_limit = purchase_limit.get(market_name, 0)
            else:
                market_name = '上海主板'
                market_limit = purchase_limit.get(market_name, 0)
        elif code.endswith('.SZ'):
            # 深圳市场(包含主板、创业板)额度通常合并计算
            market_name = '深圳市场'
            market_limit = purchase_limit.get(market_name, 0)
        
        # 5. 计算最终申购数量
        # 申购量 = min(账户额度, 新股申购上限)
        # 必须是 500 (科创板/沪市) 或 500 (深市) 的倍数,这里简化处理,直接取最小值
        # 实际上柜台会自动处理非整数倍的废单,或者我们可以向下取整
        
        if market_limit <= 0:
            print("股票: {} ({}) - 跳过,{} 额度不足 (额度: {})".format(name, code, market_name, market_limit))
            continue

        # 确定下单数量
        volume = min(market_limit, max_qty)
        
        # 再次确保数量大于0
        if volume <= 0:
            continue

        print("正在申购: {} ({}) | 价格: {} | 数量: {}".format(name, code, price, volume))

        # 6. 下单
        # opType=23 (买入)
        # orderType=1101 (单股单账号普通下单)
        # prType=11 (指定价/模型价),配合 price 参数使用
        passorder(23, 1101, ContextInfo.acc_id, code, 11, price, volume, ContextInfo)

    # 标记今日已运行
    ContextInfo.last_run_date = current_date
    print("今日新股申购任务执行完毕。")
    print("=" * 30)

代码关键点解析

  1. get_ipo_data("STOCK"):

    • 这是获取新股数据的核心函数。它返回一个列表,包含了当天所有可以申购的新股详情(代码、名称、发行价、申购上限等)。
  2. get_new_purchase_limit(accid):

    • 这是查询账户“打新门票”的函数。它返回一个字典,例如 {'上海主板': 10000, '深圳市场': 5000, '上海科创板': 0}
    • 注意:你需要持有一定市值的股票(通常是 T-2 日前 20 个交易日的日均市值)才能获得额度。
  3. 额度匹配逻辑:

    • 代码中通过判断股票代码后缀 (.SH, .SZ) 和前缀 (688, 787) 来区分是上海主板、科创板还是深圳市场,从而去匹配 purchase_limit 字典中对应的额度。
  4. passorder 下单:

    • opType=23: 代表股票买入(申购本质上是买入行为)。
    • prType=11: 代表指定价格(Limit Price)。新股申购必须严格按照发行价申购,所以我们将 price 参数设置为 stock.issuePrice
  5. 防重复机制:

    • 使用了 ContextInfo.last_run_date 变量。策略在 handlebar 中每根 K 线(或 Tick)都会被触发,但我们通过对比日期,保证每天只执行一次核心逻辑。

如何使用

  1. 打开 QMT 客户端,进入【模型研究】或【策略编辑器】。
  2. 新建一个 Python 策略。
  3. 将上述代码复制进去。
  4. 修改 ContextInfo.acc_id:将 'YOUR_ACCOUNT_ID' 替换为你真实的资金账号。
  5. 点击【编译】检查无误。
  6. 在【模型交易】或【实盘】界面运行该策略。建议运行周期选择“1分钟”或“日线”均可(因为代码里有时间过滤,周期影响不大,但为了响应及时,推荐 1 分钟线)。

常见问题 Q&A

Q: 为什么 get_new_purchase_limit 返回的额度是 0?
A: 这通常意味着你的账户在 T-2 日前 20 个交易日的日均持仓市值不足(沪市需 1 万元以上,深市需 1 万元以上),或者数据尚未更新。建议在交易时间(9:30 后)运行策略以获取准确数据。

Q: 策略报错 AttributeError: 'dict' object has no attribute 'instrument' 怎么办?
A: 不同券商的 QMT 底层数据结构可能微调(有的返回对象,有的返回字典)。我在代码中加入了 try...except 块来同时兼容对象属性访问(stock.instrument)和字典键值访问(stock.get('instrument')),这应该能解决大部分兼容性问题。

Q: 申购数量需要自己计算 500/1000 的倍数吗?
A: 严格来说是的(沪市主板 1000 股整数倍,科创板/深市 500 股整数倍)。但在实际操作中,如果你的下单数量不满足整数倍,柜台系统通常会自动拒绝或向下取整。上述代码为了简洁,直接取了 min(额度, 上限)。如果需要更严谨,可以增加取整逻辑,例如 volume = int(volume / 500) * 500