第四章:订单失衡率指标构建

各位同学,今天我们来聊聊一个非常实战的指标——订单失衡率。说实话,这个指标在我早期的交易生涯中帮了大忙。那时候我刚接触高频数据,看着逐笔成交记录一头雾水,直到有一天我发现了买方和卖方主动成交之间的秘密。

4.1 逐笔成交数据解析

先说说什么是逐笔成交数据。你打开交易软件的成交明细,每一笔交易都会记录:成交时间、价格、数量,以及最重要的——这笔交易是由买方主动发起的,还是卖方主动发起的。

我举个例子。假设当前盘口是这样的:

  • 卖一价:10.01元,挂单1000股
  • 买一价:10.00元,挂单800股

这时候突然来了一笔成交,价格是10.01元,数量500股。你想想看,谁会主动去吃掉卖一价的单子?当然是买方。所以这笔交易就是「买方主动成交」,也叫「外盘」。

反过来,如果成交在10.00元,那就是卖方主动砸盘,属于「卖方主动成交」,也叫「内盘」。

核心概念:

  • 买方主动成交(外盘):买方直接吃卖单,说明买方更着急,看涨情绪强
  • 卖方主动成交(内盘):卖方直接砸买单,说明卖方更着急,看跌情绪强

嗯,这里要注意一点。有些行情软件会把「主动」和「被动」搞混。我个人习惯是看成交价格:成交价≥卖一价,就是买方主动;成交价≤买一价,就是卖方主动。如果成交价在中间,那可能是双方同时撤单再成交,这种情况比较少,可以忽略。

4.2 订单失衡率(OIR)计算公式

有了买方主动成交量和卖方主动成交量,我们就可以构建订单失衡率了。公式其实很简单:

OIR = (买方主动成交量 - 卖方主动成交量) / (买方主动成交量 + 卖方主动成交量)

这个值在-1到1之间波动:

  • OIR接近1:买方绝对主导,市场情绪极度看涨
  • OIR接近-1:卖方绝对主导,市场情绪极度看跌
  • OIR接近0:买卖双方力量均衡,市场处于犹豫状态

我在项目中遇到过一个问题:如果某段时间内只有买方主动成交,没有卖方主动成交,分母就是买方成交量本身,OIR=1。这看起来没问题,但实际中这种情况很少见。更常见的是,某只冷门股一天都没几笔成交,这时候OIR的参考意义就不大了。

避坑指南:我曾经在分析一只小盘股时,发现OIR连续几天都是0.9以上,以为发现了大牛股。结果仔细一看,那几天总共就成交了十几笔,全是散户在买。这种低流动性下的OIR,说白了就是噪音。所以一定要结合成交量来看。

4.3 OIR的滚动窗口计算(Python实现)

实际应用中,我们不会只看单笔交易的OIR,而是计算一段时间内的滚动OIR。比如过去5分钟、过去30分钟、或者过去100笔交易。

下面是我常用的实现方式:

import pandas as pd
import numpy as np

def calculate_rolling_oir(df, window=100):
    """
    计算滚动订单失衡率
    
    参数:
    df: DataFrame,必须包含 'buy_volume' 和 'sell_volume' 两列
    window: 滚动窗口大小,默认100笔
    
    返回:
    DataFrame,新增 'oir' 列
    """
    # 计算每笔交易的净主动成交量
    df['net_volume'] = df['buy_volume'] - df['sell_volume']
    df['total_volume'] = df['buy_volume'] + df['sell_volume']
    
    # 滚动求和
    df['net_volume_roll'] = df['net_volume'].rolling(window=window).sum()
    df['total_volume_roll'] = df['total_volume'].rolling(window=window).sum()
    
    # 计算OIR,避免除零
    df['oir'] = np.where(
        df['total_volume_roll'] > 0,
        df['net_volume_roll'] / df['total_volume_roll'],
        0
    )
    
    return df

# 使用示例
# 假设你有一个逐笔成交数据
tick_data = pd.DataFrame({
    'time': pd.date_range('2024-01-01', periods=1000, freq='1s'),
    'buy_volume': np.random.randint(0, 100, 1000),
    'sell_volume': np.random.randint(0, 100, 1000)
})

result = calculate_rolling_oir(tick_data, window=50)
print(result[['time', 'oir']].tail())

这段代码的核心逻辑是:先算每笔的净主动量,然后滚动求和,最后归一化。我习惯用100笔作为默认窗口,因为100笔在A股市场大概对应1-3分钟,既能捕捉短期情绪,又不会太敏感。

个人经验:窗口大小的选择很关键。太小的窗口(比如10笔)噪声太大,频繁出现极端值;太大的窗口(比如1000笔)又太迟钝,等信号出来行情都走完了。我一般会同时计算三个窗口:50笔(短线)、100笔(中线)、200笔(长线),然后看它们是否共振。

4.4 OIR的阈值设定与信号生成

有了OIR值,怎么用它来生成交易信号?这就涉及到阈值设定了。

我常用的阈值体系是这样的:

OIR范围 信号强度 操作建议
OIR > 0.6 强买方信号 考虑做多,但需确认成交量放大
0.3 < OIR ≤ 0.6 温和买方信号 关注,可轻仓试探
-0.3 ≤ OIR ≤ 0.3 中性 观望,等待方向
-0.6 ≤ OIR < -0.3 温和卖方信号 减仓或对冲
OIR < -0.6 强卖方信号 考虑做空或清仓

你可能会问:为什么是0.6和0.3?这两个数字是我回测了上百只股票后总结出来的经验值。不同股票、不同市场环境,阈值其实应该动态调整。比如在牛市中,0.6可能太保守,可以放宽到0.7;在熊市中,0.6可能又太激进,需要收紧到0.5。

下面是我生成信号的代码:

def generate_signals(df, buy_threshold=0.6, sell_threshold=-0.6):
    """
    根据OIR生成交易信号
    
    参数:
    df: DataFrame,必须包含 'oir' 列
    buy_threshold: 买入阈值
    sell_threshold: 卖出阈值
    
    返回:
    DataFrame,新增 'signal' 列:1=买入,-1=卖出,0=无操作
    """
    df['signal'] = 0
    
    # 买入信号
    df.loc[df['oir'] > buy_threshold, 'signal'] = 1
    
    # 卖出信号
    df.loc[df['oir'] < sell_threshold, 'signal'] = -1
    
    return df

# 生成信号
result_with_signals = generate_signals(result, buy_threshold=0.6, sell_threshold=-0.6)

# 查看信号分布
print(result_with_signals['signal'].value_counts())

重要提醒:OIR信号不能单独使用。我见过太多人只看OIR就冲进去,结果被假信号坑惨了。一定要结合以下条件过滤:

  • 成交量确认:OIR发出信号时,成交量必须大于过去20期的均值
  • 价格趋势:OIR信号要与价格趋势方向一致,逆势信号要谨慎
  • 市场环境:大盘暴跌时,个股的OIR买方信号大概率是诱多

最后,我想分享一个我自己的实战案例。去年有一只股票,OIR在10分钟内从-0.2飙到0.8,同时成交量放大到平时的3倍。我当时觉得不对劲——这涨得太快了。结果呢?果然是个庄家对倒拉高出货的局。第二天股价直接跌停。所以啊,OIR是个好工具,但别迷信它。市场永远在变,我们的策略也要跟着变。

订单失衡率(OIR)指标构建流程 逐笔成交数据 数据解析 买方主动成交量 vs 卖方主动成交量 OIR计算 OIR = (买方 - 卖方) / (买方 + 卖方) 滚动窗口计算(50/100/200笔) 阈值设定 ±0.3 / ±0.6 交易信号生成

最后的小建议:刚开始用OIR时,别急着实盘。先拿历史数据回测一个月,看看你的阈值设置是否合理。我当初就是太自信,直接上实盘,结果亏了一笔才老实回去做回测。记住,策略开发是个迭代的过程,没有一劳永逸的圣杯。

专注资料整理