策略示例与回测

前面几章讲了机制、下单、行情、数据。这一章把它们串起来,给四套抄起来改参数就能跑的完整策略:基于 handlebar 的回测、双均线实盘立即下单、subscribe 订阅下单、run_time 定时任务全市场扫描,最后讲回测参数和常见陷阱。

示例 1:双均线回测(handlebar)

回测用 handlebar 遍历本地历史,subscribe=False 读本地数据、速度快。复制到策略编辑器,主图选要交易的股票,点回测即可:

#coding:gbk
import numpy as np

def init(C):
    C.stock = C.stockcode + '.' + C.market
    C.fast = 10          # 快线
    C.slow = 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)   # 金叉买入
        C.draw_text(1, 1, '开')
    elif hold > 0 and ma_f < ma_s:
        passorder(24, 1101, C.accountid, C.stock, 5, -1, hold, C)  # 死叉卖出
        C.draw_text(1, 1, '平')

回测撮合规则:指定价在当根 K 线高低点内按指定价撮合,超出按收盘价;委托量大于可用量按可用量撮合。

示例 2:双均线实盘(handlebar + 立即下单)

实盘要点:用 is_last_bar() 跳过历史重放、过滤非交易时段、quicktrade=2 立即下单、用普通全局对象 A 存状态、waiting_list 防超单:

#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 = []
    A.buy_code = 23 if A.acct_type == 'STOCK' else 33
    A.sell_code = 24 if A.acct_type == 'STOCK' else 34

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

    account = get_trade_detail_data(A.acct, A.acct_type, 'account')
    if len(account) == 0:
        print('账号未登录')
        return
    avail = int(account[0].m_dAvailable)

    # 核对未成交委托
    if A.waiting:
        deals = get_trade_detail_data(A.acct, A.acct_type, 'deal')
        done = [d.m_strRemark for d in deals if d.m_strRemark in A.waiting]
        A.waiting = [w for w in A.waiting if w not in done]
    if A.waiting:
        print('有未查到委托,暂停报单', A.waiting)
        return

    holdings = {h.m_strInstrumentID + '.' + h.m_strExchangeID: h.m_nCanUseVolume
                for h in get_trade_detail_data(A.acct, A.acct_type, 'position')}

    data = C.get_market_data_ex(['close'], [A.stock], period='1d', count=A.slow + 1)
    cl = data[A.stock].values
    if len(cl) < A.slow + 1:
        return
    pre_f, pre_s = np.mean(cl[-A.fast-1:-1]), np.mean(cl[-A.slow-1:-1])
    cur_f, cur_s = np.mean(cl[-A.fast:]), np.mean(cl[-A.slow:])

    vol = int(A.amount / cl[-1] / 100) * 100
    if (A.amount < avail and vol >= 100 and A.stock not in holdings
            and pre_f < pre_s and cur_f > cur_s):
        msg = f'双均线实盘 买入 {vol}股'
        passorder(A.buy_code, 1101, A.acct, A.stock, 14, -1, vol,
                  '双均线实盘', 2, msg, C)
        A.waiting.append(msg)
    if (A.stock in holdings and holdings[A.stock] > 0
            and pre_f > pre_s and cur_f < cur_s):
        msg = f'双均线实盘 卖出 {holdings[A.stock]}股'
        passorder(A.sell_code, 1101, A.acct, A.stock, 14, -1, holdings[A.stock],
                  '双均线实盘', 2, msg, C)
        A.waiting.append(msg)

示例 3:订阅下单(subscribe)

基于实时分笔推送,新分笔到达触发回调。注意下单函数需要 ContextInfo,所以回调里用闭包捕获 C:

#coding:gbk
class a(): pass
A = a()
A.bought = []

def init(C):
    def on_tick(data):
        for stock in data:
            cur = data[stock]['close']
            pre = data[stock]['preClose']
            ratio = cur / pre - 1
            if ratio > 0 and stock not in A.bought:
                msg = f'{stock} 涨幅{ratio:.2%} 买入100股'
                print(msg)
                # passorder(23, 1101, account, stock, 5, -1, 100,
                #           '订阅下单', 2, msg, C)   # 实测时放开
                A.bought.append(stock)
    for stock in ['600000.SH', '000001.SZ']:
        C.subscribe_quote(stock, period='1d', callback=on_tick)

订阅下单默认注释掉真实报单,实测时再放开,避免误下单。

示例 4:定时任务全市场扫描(run_time)

用 run_time 固定间隔触发,配合 get_full_tick 一次取全市场快照,扫描涨幅:

#coding:gbk
import time, datetime

class a(): pass
A = a()

def init(C):
    A.hsa = C.get_stock_list_in_sector('沪深A股')
    A.vol = {s: C.get_last_volume(s) for s in A.hsa}
    A.bought = []
    C.run_time('scan', '1nSecond', '2026-08-13 13:20:00')   # 每秒一次

def scan(C):
    t0 = time.time()
    now = datetime.datetime.now()
    ticks = C.get_full_tick(A.hsa)
    total_mv, total_ratio = 0, 0
    for s in A.hsa:
        ratio = ticks[s]['lastPrice'] / ticks[s]['lastClose'] - 1
        if ratio > 0.09 and s not in A.bought:
            print(f'{now} {s} {C.get_stock_name(s)} 涨幅{ratio:.2%}')
            # passorder(...)  实测时放开
            A.bought.append(s)
        mv = ticks[s]['lastPrice'] * A.vol[s]
        total_ratio += ratio * mv
        total_mv += mv
    print(f'{now} A股加权涨幅 {total_ratio/total_mv*100:.2f}% '
          f'耗时{time.time()-t0:.3f}s')

回测操作流程

  1. 下载数据:客户端「操作 → 数据管理」,选周期、板块、时间范围「全部」。
  2. 写策略:策略编辑器粘贴代码,确保第一行 #coding:gbk。
  3. 设基本信息:默认周期、默认主图(在「我的界面」点回测生效)。
  4. 副图运行:回测必须以副图模式执行,不要选主图/主图叠加。
  5. 看报告:收益曲线、净值走势、逐笔日志。

回测参数字段

回测右侧的基本信息与回测参数(如默认周期、主图、资金、起止时间等)决定回测行为;在行情 K 线下点回测则以当前 K 线的周期品种为准。具体字段以客户端「回测参数」面板为准。

常见陷阱

陷阱说明对策
没写 #coding:gbk中文乱码、报错第一行必写
缩进混用缩进错误全文统一空格或 Tab
回测选主图模式回测失败必须副图模式
多品种未下载数据取数返回空先下载对应周期数据
实盘在 init 取实时行情行情未就绪、脏数据实时查询放 9:10 后或 handlebar 内
实盘立即下单存 ContextInfo状态丢失quicktrade=2 用普通全局变量
未防超单重复报单waiting_list + 成交回报核对
价格笼子废单报价超 ±2%控制报价在最新价 ±2% 内

三种机制的选用

策略类型推荐机制理由
回测handlebar唯一支持回测的机制
模拟逐 K 线的实盘handlebar(quicktrade=0)回测代码可直接迁移
盘中即时反应subscribe_quote分笔驱动
全市场轮询/定时调仓run_time固定间隔,不逐笔

下一步

到这里 QMT 的机制、函数、数据、示例都讲完了。需要查具体函数签名可回到对应章节,或回到 迅投 QMT 总览。想用外部 Python 环境跑,看 XtQuant外部接口。