🚀 AI 一键生成 qmt 策略代码

利用 Level-2 逐笔委托数据计算订单流不平衡 (OBI)

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

问题描述

怎么利用 Level2 数据计算买卖压力失衡?

解决方案

利用 QMT 的 Level 2 数据计算 买卖压力失衡(Order Imbalance) 是高频交易和短线策略中常用的因子。买卖压力失衡主要衡量订单簿中买盘和卖盘力量的差异,用于预测短期价格走势。

在 QMT 中,我们可以通过两种主要方式利用 Level 2 数据来计算失衡:

  1. 基于盘口挂单(Order Book)的失衡:利用 get_full_tick 获取买一到买十(或买五)的挂单量,计算加权或简单的量比。
  2. 基于全市场总委托(Total Queue)的失衡:利用 l2quoteaux(Level 2 行情快照指标)获取全市场的委买总量委卖总量

下面我将为您提供详细的计算逻辑和一个完整的 Python 策略代码示例。

核心计算逻辑

1. 盘口深度失衡 (Depth Imbalance)

这是最常用的微观结构因子。公式如下:
$$ Imbalance = \frac{\sum(BidVol_i) - \sum(AskVol_i)}{\sum(BidVol_i) + \sum(AskVol_i)} $$

  • BidVol_i: 第 i 档买单量
  • AskVol_i: 第 i 档卖单量
  • 取值范围: [-1, 1]。接近 1 表示买压极大,接近 -1 表示卖压极大。

2. 总委托失衡 (Total Queue Imbalance)

利用 Level 2 特有的“总委买量”和“总委卖量”数据:
$$ TotalImbalance = \frac{TotalBidQty - TotalOffQty}{TotalBidQty + TotalOffQty} $$

  • TotalBidQty: 委买总量(所有价位)
  • TotalOffQty: 委卖总量(所有价位)

QMT 策略代码实现

以下代码展示了如何在 QMT 中同时计算这两种失衡指标。

# -*- coding: gbk -*-
import pandas as pd
import numpy as np

def init(ContextInfo):
    # 1. 设置股票池 (示例使用 浦发银行 和 平安银行)
    # 注意:Level 2 数据通常需要开通相应权限
    ContextInfo.stock_list = ['600000.SH', '000001.SZ']
    ContextInfo.set_universe(ContextInfo.stock_list)
    
    # 2. 设置账号 (实盘需要,回测可忽略或设置虚拟账号)
    # ContextInfo.set_account('您的资金账号')
    
    # 3. 订阅行情
    # 'tick': 用于获取盘口挂单 (买一到买五/买十)
    # 'l2quoteaux': Level 2 特色指标,包含总委买/委卖量、撤单量等
    for stock in ContextInfo.stock_list:
        ContextInfo.subscribe_quote(stock, period='tick', dividend_type='none')
        ContextInfo.subscribe_quote(stock, period='l2quoteaux', dividend_type='none')

    print("策略初始化完成,已订阅 Tick 和 Level 2 快照数据")

def handlebar(ContextInfo):
    # 获取当前 K 线位置,避免在历史数据回放时重复计算(视需求而定)
    if not ContextInfo.is_last_bar():
        return

    # 遍历股票池计算压力失衡
    for stock in ContextInfo.stock_list:
        calculate_pressure_imbalance(ContextInfo, stock)

def calculate_pressure_imbalance(ContextInfo, stock_code):
    """
    计算买卖压力失衡的核心函数
    """
    
    # ------------------------------------------------------------------
    # 方法一:基于盘口挂单 (Order Book) 的失衡计算
    # ------------------------------------------------------------------
    full_tick = ContextInfo.get_full_tick([stock_code])
    
    if not full_tick:
        return

    tick_data = full_tick.get(stock_code)
    
    if tick_data:
        # 获取买盘和卖盘的挂单量列表 (通常为5档或10档,取决于行情权限)
        # bidVol: [买1量, 买2量, ...]
        # askVol: [卖1量, 卖2量, ...]
        bid_vols = tick_data.get('bidVol', [])
        ask_vols = tick_data.get('askVol', [])
        
        # 简单求和计算 (也可以根据档位加权,例如给买1更高的权重)
        sum_bid_vol = sum(bid_vols)
        sum_ask_vol = sum(ask_vols)
        
        # 防止分母为0
        if (sum_bid_vol + sum_ask_vol) > 0:
            # 计算盘口失衡度 [-1, 1]
            lob_imbalance = (sum_bid_vol - sum_ask_vol) / (sum_bid_vol + sum_ask_vol)
        else:
            lob_imbalance = 0.0
            
        print(f"[{stock_code}] 盘口失衡度(LOB): {lob_imbalance:.4f} (买量:{sum_bid_vol}, 卖量:{sum_ask_vol})")

    # ------------------------------------------------------------------
    # 方法二:基于 Level 2 总委托量 (Total Queue) 的失衡计算
    # ------------------------------------------------------------------
    # 使用 get_market_data_ex 获取 l2quoteaux 数据
    # 注意:count=-1 表示取最新一笔
    l2_data = ContextInfo.get_market_data_ex(
        fields=[],  # 空列表代表获取所有字段
        stock_code=[stock_code], 
        period='l2quoteaux', 
        count=1,
        subscribe=True
    )
    
    if stock_code in l2_data and not l2_data[stock_code].empty:
        df_l2 = l2_data[stock_code]
        
        # 获取最新的一行数据
        latest_l2 = df_l2.iloc[-1]
        
        # 字段说明 (参考 QMT 文档):
        # totalBidQuantity: 委买总量
        # totalOffQuantity: 委卖总量
        total_bid_qty = latest_l2.get('totalBidQuantity', 0)
        total_off_qty = latest_l2.get('totalOffQuantity', 0)
        
        # 计算总委托失衡度
        if (total_bid_qty + total_off_qty) > 0:
            total_imbalance = (total_bid_qty - total_off_qty) / (total_bid_qty + total_off_qty)
        else:
            total_imbalance = 0.0
            
        print(f"[{stock_code}] 总委托失衡度(L2): {total_imbalance:.4f} (总委买:{total_bid_qty}, 总委卖:{total_off_qty})")
        
        # --------------------------------------------------------------
        # 简单的交易信号示例
        # --------------------------------------------------------------
        # 逻辑:如果盘口买压大 且 总委买量也大,视为强买入信号
        if lob_imbalance > 0.6 and total_imbalance > 0.3:
            print(f"!!! 发现买入信号: {stock_code} 买压显著 !!!")
            # order_shares(stock_code, 100, 'fix', tick_data['lastPrice'], ContextInfo, 'account_id')

    print("-" * 30)

代码关键点解析

  1. 数据订阅 (subscribe_quote)

    • 必须显式订阅 period='l2quoteaux' 才能获取 Level 2 的快照指标(如总委买/委卖量)。
    • period='tick' 用于获取高频的盘口挂单数据。
  2. 获取盘口数据 (get_full_tick)

    • 返回的字典中 bidVolaskVol 是列表。
    • 在标准版中通常是 5 档数据,在开通 Level 2 权限的环境下,部分接口可能返回 10 档数据。
    • 进阶技巧:您可以对 bid_vols 进行加权。例如,买一的权重设为 1.0,买五的权重设为 0.2,因为越靠近成交价的挂单对价格影响越大。
  3. 获取 Level 2 统计数据 (get_market_data_ex + l2quoteaux)

    • 这是计算宏观买卖压力的关键。
    • totalBidQuantity (委买总量) 和 totalOffQuantity (委卖总量) 反映了市场上所有的意愿,不仅仅是前几档。
    • 该接口返回的是 pandas.DataFrame,方便进行时间序列分析。

常见问题

  • 为什么 l2quoteaux 数据为空?

    • 请确认您的 QMT 账号是否已开通 Level 2 行情权限。
    • 请确认是否在 init 中正确调用了 subscribe_quote
    • 该数据仅在实盘或模拟盘的交易时间段内推送,非交易时间可能无数据。
  • 如何利用撤单数据?

    • l2quoteaux 中还包含 withdrawBidQuantity (买入撤单总量) 和 withdrawOffQuantity (卖出撤单总量)。
    • 撤单压力逻辑:如果价格上涨,但卖单撤单量激增,可能意味着压盘撤退,利好上涨。您可以将撤单量加入到失衡公式中进行修正。

Q&A

Q: get_full_tick 返回的是 5 档还是 10 档数据?
A: 这取决于您的券商服务器配置和账号权限。通常普通账号是 5 档,Level 2 账号可能是 10 档。您可以通过 len(tick_data['bidVol']) 来打印查看实际长度。

Q: 这个策略可以直接用于回测吗?
A: l2quoteaux (Level 2 快照) 的历史数据量非常大,并非所有券商的 QMT 服务器都提供完整的 Level 2 历史数据回测支持。通常 Tick 级回测支持较好,但 Level 2 统计字段可能需要您先下载补充数据或仅在实盘中使用。

Q: 如何对盘口进行加权计算?
A: 您可以定义一个权重数组,例如 weights = [1.0, 0.8, 0.6, 0.4, 0.2],然后使用 np.average(bid_vols, weights=weights) 来计算加权买量,这样能更准确地反映即时压力。