内置Python运行机制

内置 Python 是 QMT 最常用的开发方式。它的核心是事件驱动:你实现几个约定名称的函数,引擎在特定时机回调它们。QMT 提供两大类、共三种运行机制——理解这三种机制分别在什么时候触发,是写对策略的基础。

两个约定函数:init 与 handlebar

最小可运行的内置 Python 策略只需要两个函数:

#coding:gbk
def init(C):
    # C 是 ContextInfo 对象,整个策略的上下文
    C.stock = C.stockcode + '.' + C.market   # 主图品种,如 600000.SH

def handlebar(C):
    # 每个 K 线触发一次(回测:逐根历史 K 线;实盘:每个分笔)
    print(C.barpos)                           # 当前 K 线位置
  • init(C):模型启动时调用一次。用于初始化变量、读取参数、订阅品种、注册定时任务。C 是 ContextInfo 对象,约定缩写为 C。
  • handlebar(C):核心事件函数。回测时自左向右每根历史 K 线触发一次;实盘时主图品种每个新分笔(约 3 秒)触发一次。

⚠️ 第一行必须是 #coding:gbk,且全文缩进统一(全空格或全 Tab,不能混)。这两条不满足,脚本会直接报错或中文乱码。

三种运行机制对比

QMT 把触发方式分成事件驱动与定时任务两类,共三种:

机制分类触发方式回测实盘适合场景
handlebar(逐 K 线)事件驱动历史 K 线 + 盘中分笔✅✅同时支持回测和实盘,模拟逐 K 线效果
subscribe_quote(订阅推送)事件驱动盘中订阅品种分笔❌✅盘中随分笔行情判断交易
run_time(定时任务)定时任务固定时间间隔❌✅盘中固定间隔轮询判断

逐 K 线驱动:handlebar

handlebar 同时吃「主图历史 K 线」和「盘中订阅推送」:运行开始时,所选周期的历史 K 线从左向右每根触发一次 handlebar;进入盘中后,主图品种每个新分笔到达触发一次。

回测只用 handlebar 遍历本地历史数据;实盘的 handlebar 则在分笔驱动下、但逐 K 线生效(见下文)。

事件驱动:subscribe 订阅推送

在 init 里订阅指定品种,盘中该品种每个新分笔到达时,触发你指定的回调函数。适合需要随分笔行情即时反应的策略。

#coding:gbk
def init(C):
    def on_tick(data):
        for stock in data:
            price = data[stock]['close']
            print(stock, price)
    C.subscribe_quote('600000.SH', period='1d', callback=on_tick)

定时任务:run_time

指定固定时间间隔,持续触发指定回调。适合全市场扫描、定时调仓等不需要逐笔响应的场景。

#coding:gbk
def init(C):
    C.run_time('my_job', '1nSecond', '2026-08-13 13:20:00')  # 每秒一次

def my_job(C):
    print('定时任务触发')

ContextInfo 对象

ContextInfo(缩写 C)是内置 Python 的核心上下文对象,既是入参、又是状态容器。常用属性与方法:

属性/方法说明
C.stockcode / C.market主图品种代码与市场,组合成 600000.SH 形式
C.period当前周期(1d/1m/5m/tick 等)
C.barpos当前 K 线位置
C.is_last_bar()是否是最新一根 K 线(实盘跳过历史重放用)
C.is_new_bar()是否是新 K 线
C.capital回测初始资金
C.benchmark基准
C.dividend_type复权类型
C.get_bar_timetag(barpos)取 K 线时间标签
C.get_market_data_ex(...)取行情数据
C.subscribe_quote(...)订阅行情
C.get_full_tick(codes)取全市场快照
C.get_stock_list_in_sector(name)取板块成分
C.get_stock_name(code)取品种名称
C.get_instrument_detail(code)取合约基础信息
C.run_time(...)注册定时任务
C.draw_text(...) / C.draw(...)在主图上画图标注

⚠️ 逐 K 线保存机制:默认逐 K 线模式(quicktrade=0)下,存在 ContextInfo 自定义属性里的状态会被引擎在 K 线切换时做等待/回退处理,交易记录可以安全地存在 C 的属性里。但立即下单模式(quicktrade=2)下不能用 ContextInfo 存状态,必须改用普通全局变量。

回测 handlebar 示例:双均线

回测取本地历史,subscribe=False 不订阅实时、速度更快:

#coding:gbk
import numpy as np

def init(C):
    C.stock = C.stockcode + '.' + C.market
    C.fast, C.slow = 10, 20
    C.accountid = 'testS'   # 回测资金账号可填任意字符串

def handlebar(C):
    bar_date = timetag_to_datetime(C.get_bar_timetag(C.barpos), '%Y%m%d%H%M%S')
    data = C.get_market_data_ex(['close'], [C.stock], end_time=bar_date,
                                period=C.period, count=C.slow, subscribe=False)
    closes = list(data[C.stock].iloc[:, 0])
    if len(closes) < C.slow:
        return
    ma_f = round(np.mean(closes[-C.fast:]), 2)
    ma_s = round(np.mean(closes[-C.slow:]), 2)

    account = get_trade_detail_data('test', 'stock', 'account')[0]
    holdings = {h.m_strInstrumentID + '.' + h.m_strExchangeID: h.m_nVolume
                for h in get_trade_detail_data('test', 'stock', 'position')}
    hold = holdings.get(C.stock, 0)

    if hold == 0 and ma_f > ma_s:
        vol = int(account.m_dAvailable / closes[-1] / 100) * 100
        passorder(23, 1101, C.accountid, C.stock, 5, -1, vol, C)   # 买入开仓
    elif hold > 0 and ma_f < ma_s:
        passorder(24, 1101, C.accountid, C.stock, 5, -1, hold, C)  # 卖出平仓

回测注意:

  • 回测必须以副图模式执行,不要选主图/主图叠加。
  • 基本信息(默认周期、默认主图)在「我的界面」点回测时生效;在行情 K 线下点回测,以当前 K 线周期品种为准。
  • 多品种回测要先把对应周期的历史数据下载齐。

实盘 handlebar 示例:立即下单

实盘的 handlebar 启动时会重放历史 K 线,用 is_last_bar() 跳过,只在最新 K 线处理;立即下单用 quicktrade=2,状态存在普通全局对象里:

#coding:gbk
import numpy as np, datetime

class a(): pass
A = a()                       # 普通全局对象,存委托状态

def init(C):
    A.stock = C.stockcode + '.' + C.market
    A.acct = account           # 模型交易界面选择的账号/账号类型(内置变量)
    A.acct_type = accountType
    A.amount = 10000
    A.fast, A.slow = 17, 27
    A.waiting = []             # 未查到委托列表,防超单

def handlebar(C):
    if not C.is_last_bar():    # 跳过历史 K 线
        return
    now = datetime.datetime.now().strftime('%H%M%S')
    if now < '093000' or now > '150000':
        return
    # ... 取行情、算信号、passorder(..., 2, msg, C) 立即下单

逐 K 线生效 vs 立即下单

这是实盘最容易踩坑的点。passorder 的 quicktrade 参数决定下单时机:

quicktrade行为状态存放适合
0(默认)信号暂存,当前 K 线结束、下一根首笔时才发;中途的信号会被丢弃可存 ContextInfo盘中模拟逐 K 线效果
2立刻发出委托,不等待不丢弃必须用普通全局变量需要即时响应的策略

以 1 分钟周期为例,每根 K 线内约有 20 个分笔(3 秒一个):逐 K 线模式下前 19 个分笔产生的信号会被丢弃,只有最后一个分笔的信号在下一根 K 线首笔到达时延迟约 3 秒发出。

编码与缩进约定

约定要求
编码声明第一行 #coding:gbk,脚本统一 GBK
缩进全文统一为空格或 Tab,不可混用
入参init/handlebar 的 C 是 ContextInfo
内置变量account、accountType 等在模型交易界面运行时自动注入

下一步

机制清楚了,接下来学怎么下单:内置Python交易函数。