问题描述
解决方案
这是一个非常实用的策略需求。在 QMT 中,我们可以利用 get_ipo_data 获取当日新股信息,并结合 get_new_purchase_limit 获取账户的申购额度来实现自动打新。
策略设计思路
- 获取新股信息:使用
get_ipo_data("STOCK")获取当天所有可申购的新股列表,包含申购代码、发行价格、申购上限等信息。 - 获取账户额度:使用
get_new_purchase_limit(accid)获取当前资金账号在上海主板、深圳市场、科创板的可用申购额度。 - 计算申购数量:
- 对于每一只新股,我们需要判断它属于哪个市场(沪市主板、深市、科创板)。
- 申购数量 =
min(账户在该市场的可用额度, 该新股的单账户申购上限)。 - 确保申购数量大于 0。
- 执行下单:使用
passorder函数进行申购。申购通常使用“限价”委托,价格为发行价。 - 运行频率控制:打新每天只需执行一次。我们需要在策略中增加逻辑,确保在交易时间内(如 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)
代码关键点解析
-
get_ipo_data("STOCK"):- 这是获取新股数据的核心函数。它返回一个列表,包含了当天所有可以申购的新股详情(代码、名称、发行价、申购上限等)。
-
get_new_purchase_limit(accid):- 这是查询账户“打新门票”的函数。它返回一个字典,例如
{'上海主板': 10000, '深圳市场': 5000, '上海科创板': 0}。 - 注意:你需要持有一定市值的股票(通常是 T-2 日前 20 个交易日的日均市值)才能获得额度。
- 这是查询账户“打新门票”的函数。它返回一个字典,例如
-
额度匹配逻辑:
- 代码中通过判断股票代码后缀 (
.SH,.SZ) 和前缀 (688,787) 来区分是上海主板、科创板还是深圳市场,从而去匹配purchase_limit字典中对应的额度。
- 代码中通过判断股票代码后缀 (
-
passorder下单:opType=23: 代表股票买入(申购本质上是买入行为)。prType=11: 代表指定价格(Limit Price)。新股申购必须严格按照发行价申购,所以我们将price参数设置为stock.issuePrice。
-
防重复机制:
- 使用了
ContextInfo.last_run_date变量。策略在handlebar中每根 K 线(或 Tick)都会被触发,但我们通过对比日期,保证每天只执行一次核心逻辑。
- 使用了
如何使用
- 打开 QMT 客户端,进入【模型研究】或【策略编辑器】。
- 新建一个 Python 策略。
- 将上述代码复制进去。
- 修改
ContextInfo.acc_id:将'YOUR_ACCOUNT_ID'替换为你真实的资金账号。 - 点击【编译】检查无误。
- 在【模型交易】或【实盘】界面运行该策略。建议运行周期选择“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。