3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
这是一个基于 QMT Python API 的简单做市商(Market Maker)策略示例。
这个策略的核心逻辑是**“盘口双边挂单”**,适用于 T+0 品种(如 ETF、可转债或期货)或持有底仓的股票。
# -*- coding: gbk -*-
import time
def init(ContextInfo):
"""
策略初始化函数
"""
# 1. 设置交易标的 (例如: 510050.SH 上证50ETF)
ContextInfo.stock_code = '510050.SH'
# 2. 设置资金账号 (请替换为您真实的资金账号)
ContextInfo.account_id = 'YOUR_ACCOUNT_ID'
# 设置账号类型: 'STOCK' (股票/ETF), 'FUTURE' (期货)
ContextInfo.account_type = 'STOCK'
# 3. 绑定账号
ContextInfo.set_account(ContextInfo.account_id)
# 4. 策略参数设置
ContextInfo.volume = 100 # 单笔挂单数量 (股票/ETF通常为100的倍数)
ContextInfo.spread = 0.001 # 挂单价差 (基于盘口的偏移量)
ContextInfo.tick_size = 0.001 # 最小变动价位 (根据标的调整,ETF通常是0.001)
print("做市商策略初始化完成")
def handlebar(ContextInfo):
"""
K线/Tick驱动函数
"""
# 仅在实盘的最后一根K线(实时行情)运行,回测或历史K线不运行
if not ContextInfo.is_last_bar():
return
# 获取当前标的
stock_code = ContextInfo.stock_code
# 1. 获取全推Tick数据 (获取盘口买一卖一)
tick_data = ContextInfo.get_full_tick([stock_code])
if not tick_data or stock_code not in tick_data:
return
current_tick = tick_data[stock_code]
# 获取买一价和卖一价
# bidPrice: [买1, 买2, ...], askPrice: [卖1, 卖2, ...]
bid_1 = current_tick.get('bidPrice', [0])[0]
ask_1 = current_tick.get('askPrice', [0])[0]
# 简单的数据校验,防止行情异常
if bid_1 <= 0 or ask_1 <= 0:
return
# 2. 撤销之前的未成交挂单 (风控:防止挂单堆积)
cancel_all_orders(ContextInfo)
# 3. 计算挂单价格
# 策略:在买一价下方挂买单,在卖一价上方挂卖单,赚取流动性溢价
# 注意:价格需要符合最小变动价位
buy_price = round(bid_1 - ContextInfo.spread, 3)
sell_price = round(ask_1 + ContextInfo.spread, 3)
# 4. 执行双边下单
# 4.1 挂买单
# opType: 23 (股票买入), orderType: 1101 (单股单账号普通下单), prType: 11 (限价)
passorder(23, 1101, ContextInfo.account_id, stock_code, 11, buy_price, ContextInfo.volume, ContextInfo)
print(f"挂买单: {stock_code}, 价格: {buy_price}, 数量: {ContextInfo.volume}")
# 4.2 挂卖单
# opType: 24 (股票卖出)
# 注意:如果是T+1股票,必须确保账户里有底仓才能卖出
passorder(24, 1101, ContextInfo.account_id, stock_code, 11, sell_price, ContextInfo.volume, ContextInfo)
print(f"挂卖单: {stock_code}, 价格: {sell_price}, 数量: {ContextInfo.volume}")
def cancel_all_orders(ContextInfo):
"""
辅助函数:撤销当前标的的所有未结委托
"""
# 获取未结委托 (Order)
# get_trade_detail_data 参数: 账号, 账号类型, 数据类型('ORDER')
orders = get_trade_detail_data(ContextInfo.account_id, ContextInfo.account_type, 'ORDER')
for order in orders:
# 筛选条件:
# 1. 属于当前策略操作的标的
# 2. 状态为未成交 (48:未报, 49:待报, 50:已报, 51:已报待撤, 52:部成待撤, 55:部成)
# 注意:具体状态码参考QMT文档,通常只要不是 56(已成), 54(已撤), 57(废单) 就需要撤单
if order.m_strInstrumentID == ContextInfo.stock_code:
if order.m_nOrderStatus in [48, 49, 50, 51, 52, 55]:
# 执行撤单
cancel(order.m_strOrderSysID, ContextInfo.account_id, ContextInfo.account_type, ContextInfo)
# print(f"撤单: {order.m_strOrderSysID}")
get_full_tick:
get_market_data,get_full_tick 返回的是最新的快照数据,包含买一至买五、卖一至卖五的详细盘口,速度更快,适合高频操作。passorder 函数:
opType: 23 代表股票买入,24 代表股票卖出。如果是期货,需要改为 0 (开多) / 3 (开空) 等。prType: 11 代表限价单(Limit Order),这是做市商必须使用的,因为我们需要指定价格挂单,而不是市价吃单。撤单逻辑 (cancel_all_orders):
get_trade_detail_data 返回的订单列表,找到状态为“未成交”或“部分成交”的订单进行撤销。风险提示:
init 函数中的 ContextInfo.account_id 为你的真实资金账号。ContextInfo.stock_code 为你想交易的品种。Q: 这个策略可以直接用于期货吗?
A: 代码逻辑通用,但需要修改 passorder 的参数。期货的 opType 不同(开仓、平今、平昨),且需要区分多空方向。此外,ContextInfo.account_type 需要改为 'FUTURE'。
Q: 为什么使用 get_full_tick 而不是 handlebar 的传入数据?
A: handlebar 虽然由行情驱动,但在高频场景下,主动调用 get_full_tick 能确保获取到内存中最新的切片数据,减少数据延迟。
Q: 如何控制挂单频率?
A: 目前代码是每个 Tick(约3秒一次)触发一次。如果觉得太快,可以在 handlebar 里增加 time.time() 的判断逻辑,例如每隔 5 秒才执行一次下单逻辑。