01-微观结构理论
预计学习时间:3 小时
难度:⭐⭐⭐
核心问题:订单簿里正在发生什么?价格为什么会有买卖价差?知情交易者如何影响价格?
从一个直觉出发
你在某只股票的行情软件上看到:买一 10.00 元,卖一 10.02 元。
你点了一个”市价买入”,系统以 10.02 元成交。你觉得”就这么简单”。
但在这 0.01 秒内,背后发生了一系列事情:
- 你的市价单到达交易所,匹配了卖一的挂单
- 卖一的 10.02 元被吃掉,卖二 10.03 元变成新的卖一
- 买一和卖一之间的 0.01 元——这个”价差”——就是做市商的利润空间
- 如果此时一个”知情交易者”也在买(他刚看到利好消息),他会把价格推得更高
- 做市商发现价格在动,会迅速调整报价来保护自己
微观结构理论就是研究这些过程的学科。
一、订单簿(Order Book)
1.1 订单簿长什么样
订单簿是交易所的核心数据结构。每一档都记录了”在这个价格上,有多少人想买/卖”。
卖五 10.08 │ 3,200 股
卖四 10.07 │ 1,500 股
卖三 10.06 │ 2,800 股
卖二 10.05 │ 1,000 股
卖一 10.04 │ 800 股 ← 最优卖价(Ask)
─────────────┼──────────
卖一 10.02 │ 1,200 股 ← 最优买价(Bid) ← 这是更常见的写法
买二 10.01 │ 2,000 股
买三 10.00 │ 3,500 股
买四 9.99 │ 1,800 股
买五 9.98 │ 2,600 股
上面画反了,标准格式如下:
卖盘(Ask/Sell)
──────────────────────────
卖五 10.06 │ 2,800 股
卖四 10.05 │ 1,000 股
卖三 10.04 │ 500 股
卖二 10.03 │ 1,200 股
卖一 10.02 │ 800 股 ← 最低卖出价(Best Ask)
════════════╪═══════════ ← 价差 = 10.02 - 10.00 = 0.02
买一 10.00 │ 1,200 股 ← 最高买入价(Best Bid)
买二 9.99 │ 2,000 股
买三 9.98 │ 3,500 股
买四 9.97 │ 1,800 股
买五 9.96 │ 2,600 股
──────────────────────────
买盘(Bid/Buy)
中间价(Mid Price) = (Best Ask + Best Bid) / 2 = (10.02 + 10.00) / 2 = 10.01
1.2 价格优先、时间优先
撮合引擎按两个原则匹配订单:
- 价格优先:买价高的优先成交,卖价低的优先成交
- 时间优先:同价位的订单,先挂单的优先成交
# 模拟价格优先原则
class OrderBook:
"""简化的订单簿模拟"""
def __init__(self):
self.bids = [] # 买单列表 [(价格, 数量, 时间戳)]
self.asks = [] # 卖单列表 [(价格, 数量, 时间戳)]
def add_bid(self, price, volume, timestamp):
self.bids.append((price, volume, timestamp))
# 价格优先(降序),时间优先(升序)
self.bids.sort(key=lambda x: (-x[0], x[2]))
def add_ask(self, price, volume, timestamp):
self.asks.append((price, volume, timestamp))
# 价格优先(升序),时间优先(升序)
self.asks.sort(key=lambda x: (x[0], x[2]))
def best_bid(self):
return self.bids[0] if self.bids else None
def best_ask(self):
return self.asks[0] if self.asks else None
def spread(self):
if self.bids and self.asks:
return self.asks[0][0] - self.bids[0][0]
return None
# 示例
ob = OrderBook()
ob.add_bid(10.00, 1200, 1) # 时间戳 1
ob.add_bid(10.00, 500, 2) # 同价,后到
ob.add_bid(9.99, 2000, 0) # 价格低,排在后面
ob.add_ask(10.02, 800, 1)
ob.add_ask(10.03, 1200, 0)
print(f"最优买价: {ob.best_bid()}") # (10.00, 1200, 1)
print(f"最优卖价: {ob.best_ask()}") # (10.02, 800, 1)
print(f"买卖价差: {ob.spread()}") # 0.021.3 限价单 vs 市价单
| 类型 | 特点 | 风险 | 适用场景 |
|---|---|---|---|
| 限价单 | 指定价格,不保证成交 | 不确定性风险(可能不成交) | 不急于交易,想控制价格 |
| 市价单 | 按最优价格立即成交 | 价格风险(可能滑点大) | 急于交易,接受当前价格 |
做市商主要用限价单提供流动性,普通投资者常用市价单消费流动性。
二、买卖价差(Bid-Ask Spread)
2.1 价差的三种来源
价差不是交易所”收”的,而是由三个因素自然形成的:
买卖价差 = 信息不对称成本 + 库存风险成本 + 订单处理成本
- 信息不对称成本:做市商怕遇到知情交易者(知道股票即将涨跌的人)。为了补偿这种风险,做市商会加宽价差。
- 库存风险成本:做市商买了股票后,价格可能下跌。持有存货是有风险的。
- 订单处理成本:交易所手续费、技术成本、人力成本。
2.2 有效价差的衡量
简单价差:
有效价差(考虑实际成交在中间价的哪一侧):
其中 是实际成交价, 是当时的中间价。
Rolled 价差(不需要逐笔数据,只需要日频数据):
直觉:如果收益率序列存在负自相关(今天涨明天跌),说明你在”被价差来回收割”。
import numpy as np
def rolled_spread(returns):
"""Rolled 价差估计"""
# 收益率的一阶自协方差
autocov = np.cov(returns[:-1], returns[1:])[0, 1]
if autocov >= 0:
return 0.0 # 正自相关说明不是价差效应
return 2 * np.sqrt(-autocov)
# 模拟:带价差的收益率(负自相关)
np.random.seed(42)
n = 10000
mid_returns = np.random.normal(0, 0.02, n) # 中间价的真实收益率
# 加入买卖价差效应:买家付 ask,卖家收 bid
# 每次交易"损失"半个价差
half_spread = 0.001 # 半价差
trade_returns = mid_returns - half_spread * np.random.choice([-1, 1], n)
print(f"Rolled 价差估计: {rolled_spread(trade_returns):.6f}")
print(f"真实半价差: {half_spread:.6f}")
# 输出接近 2 * half_spreadCORWIN-SCHULTZ 价差估计(基于日内最高价和最低价):
def corwin_schultz(high, low):
"""CORWIN-SCHULTZ 价差估计器
基于 Snover (2006)、Corwin & Schultz (2012)
high, low: numpy 数组,日最高价和最低价
"""
n = len(high)
spreads = []
for t in range(1, n):
# 两天各自的对数高低价差
beta_1 = (np.log(high[t-1] / low[t-1])) ** 2
beta_2 = (np.log(high[t] / low[t])) ** 2
# 两天合并的对数高低价差
beta = (np.log(max(high[t-1], high[t]) / min(low[t-1], low[t]))) ** 2
gamma = np.log(beta) - np.log(beta_1 + beta_2 + 1e-10)
# 限制 gamma 范围
gamma = max(gamma, -37)
#alpha = (np.sqrt(2 * beta_1) - np.sqrt(beta_1 * beta_2)) / (3 - 2 * np.sqrt(beta_1 / (beta_1 + 1e-10)))
alpha_1 = np.sqrt(2 * beta_1) - np.sqrt(beta)
alpha_2 = np.sqrt(3 * beta_1) - np.sqrt(2 * beta)
# 简化估计
s_squared = 2 * (np.exp(alpha_1) - 1) / (1 + np.exp(alpha_2))
if s_squared > 0:
spreads.append(np.sqrt(s_squared))
return np.mean(spreads) if spreads else 0.02.3 为什么价差会变化
- 信息事件前后:财报发布前价差变宽(做市商怕知情交易)
- 交易活跃度:流动性好的股票价差窄,冷门股票价差宽
- 波动率:市场波动大时做市商加宽价差
- 订单簿深度:挂单多,价差窄;挂单少,价差宽
三、Kyle (1985) 模型:知情交易者如何影响价格
3.1 核心直觉
Kyle 模型是微观结构最重要的模型之一。它回答了一个关键问题:
一个知道内幕信息的交易者,他的交易会如何影响价格?
场景:一个股票的真实价值是 (只有知情交易者知道),市场中有一个做市商,以及大量噪音交易者(随机买卖)。
- 做市商根据观察到的总订单流设定价格
- 知情交易者线性地提交订单:,其中 是做市商的预期
- 噪音交易者随机提交订单
做市商看到总订单流 ,设定价格为:
其中 是价格冲击系数(Kyle’s Lambda)。
3.2 Lambda 的直觉
越大,说明每一单位交易量对价格的推动越大。
价格冲击 = lambda * 交易量
lambda 大 → 市场流动性差 → 大单很难不推高价格
lambda 小 → 市场流动性好 → 大单可以悄悄完成
在 Kyle 模型的均衡中:
- :资产价值的不确定性(知情交易者的信息优势越大,lambda 越大)
- :噪音交易的波动(噪音交易越多,知情交易者越容易”隐藏”)
3.3 Python 模拟价格冲击
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(42)
# ============================================================
# Kyle (1985) 模型模拟
# ============================================================
n_rounds = 200 # 交易轮次
sigma_v = 2.0 # 资产真实价值的标准差
sigma_u = 5.0 # 噪音交易量的标准差
mu = 100 # 资产真实价值的均值
# Kyle 模型参数
# lambda = sigma_v / (2 * sigma_u)
lam = sigma_v / (2 * sigma_u)
# 知情交易者的最优交易策略系数
beta = sigma_u / sigma_v
# 模拟
true_values = np.random.normal(mu, sigma_v, n_rounds)
noise_trades = np.random.normal(0, sigma_u, n_rounds)
informed_trades = beta * (true_values - mu)
total_orderflow = informed_trades + noise_trades
# 做市商设定的价格
prices = mu + lam * total_orderflow
# 知情交易者的利润
informed_profit = informed_trades * (true_values - prices)
# 可视化
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 价格 vs 真实价值
axes[0, 0].plot(true_values, label='真实价值', alpha=0.5)
axes[0, 0].plot(prices, label='做市商价格', alpha=0.8)
axes[0, 0].set_title(f'价格 vs 真实价值 (lambda={lam:.4f})')
axes[0, 0].legend()
# 订单流 vs 价格变动
axes[0, 1].scatter(total_orderflow, prices - mu, alpha=0.3, s=10)
axes[0, 1].plot(total_orderflow, lam * total_orderflow, 'r-', label=f'斜率=lambda={lam:.4f}')
axes[0, 1].set_title('价格冲击:订单流 vs 价格变动')
axes[0, 1].legend()
# 知情交易者利润
axes[1, 0].hist(informed_profit, bins=50, edgecolor='black', alpha=0.7)
axes[1, 0].axvline(np.mean(informed_profit), color='red', linestyle='--')
axes[1, 0].set_title(f'知情交易者利润 (均值={np.mean(informed_profit):.2f})')
# 信息不对称的影响
sigmas_u = np.linspace(1, 20, 100)
lambdas = [sv / (2 * su) for su in sigmas_u]
axes[1, 1].plot(sigmas_u, lambdas)
axes[1, 1].set_xlabel('噪音交易波动 (sigma_u)')
axes[1, 1].set_ylabel('价格冲击系数 (lambda)')
axes[1, 1].set_title('噪音越多,知情交易者越容易隐藏')
plt.tight_layout()
plt.show()关键洞察:
- 知情交易者永远不会一次性全仓买入(那会暴露自己),而是线性地分批交易
- 噪音交易者越多,知情交易者越容易”混在其中”
- 价格是部分反映信息的——做市商只能从订单流中推断出部分信息
四、Glosten-Milgrom 模型
4.1 做市商如何设价
Glosten-Milgrom (1985) 模型从一个更直观的角度出发:
做市商不知道对手方是”知情”还是”不知情”的,他如何设价?
假设:
- 无消息概率 ,好消息概率 ,坏消息概率
- 如果是好消息,股票值 ;坏消息值 ;无消息值
做市商看到一笔买入,更新他对好消息概率的判断(Bayes 法则):
做市商的最优报价:
4.2 白话理解
当一笔买单到来时,做市商会想:
“这笔买单是知情交易者看到利好来买的概率有多大?如果概率大,我就应该提高卖价来保护自己。”
这就是信息不对称如何转化为价差的机制。
五、Avellaneda-Stoikov 做市模型
5.1 核心思想
Avellaneda-Stoikov (2008) 回答的问题是:做市商应该把买卖报价挂在什么位置?
关键变量:
- :当前中间价
- :做市商当前持仓(正=多头,负=空头)
- :做市商的交易时间窗口
- :当前时间
- :做市商的风险厌恶程度
- :订单到达速率参数
最优报价:
直觉:持仓越多,卖价越激进(更接近中间价)以减少库存;买价越保守(远离中间价)以避免加仓。
5.2 Python 模拟做市商行为
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(42)
# ============================================================
# Avellaneda-Stoikov 做市模拟
# ============================================================
T = 1.0 # 交易时间窗口(天)
dt = 0.001 # 时间步长
n_steps = int(T / dt)
gamma = 0.1 # 风险厌恶系数
sigma = 0.3 # 资产波动率
k = 1.5 # 订单到达速率参数
# 模拟中间价(几何布朗运动)
mid_price = 100.0
inventory = 0 # 初始持仓
cash = 0.0 # 现金
mid_prices = [mid_price]
inventories = [inventory]
ask_prices = []
bid_prices = []
pnl = [0.0]
for t_idx in range(n_steps):
t = t_idx * dt
tau = T - t # 剩余时间
# Avellaneda-Stoikov 最优报价
half_spread_base = 1.0 / (2 * k)
inventory_adj = gamma * sigma**2 * tau * inventory
ask = mid_price + half_spread_base + inventory_adj + gamma * sigma**2 * tau * 0.5
bid = mid_price - half_spread_base + inventory_adj - gamma * sigma**2 * tau * 0.5
ask_prices.append(ask)
bid_prices.append(bid)
# 模拟订单到达(简化:每步有一定概率成交)
if np.random.random() < 0.3: # 有 30% 概率有对手方
if np.random.random() < 0.5:
# 买入(我们卖出,减少库存)
inventory -= 1
cash += ask
else:
# 卖出(我们买入,增加库存)
inventory += 1
cash -= bid
# 中间价变动
mid_price *= np.exp(-0.5 * sigma**2 * dt + sigma * np.sqrt(dt) * np.random.normal())
mid_prices.append(mid_price)
inventories.append(inventory)
# 计算 PnL
pnl.append(cash + inventory * mid_price)
# 可视化
fig, axes = plt.subplots(3, 1, figsize=(14, 10))
axes[0].plot(mid_prices[:200], label='中间价', linewidth=0.5)
axes[0].plot(ask_prices[:200], label='Ask', alpha=0.5, linewidth=0.5)
axes[0].plot(bid_prices[:200], label='Bid', alpha=0.5, linewidth=0.5)
axes[0].set_title('Avellaneda-Stoikov 做市报价')
axes[0].legend()
axes[1].plot(inventories[:200])
axes[1].set_title('做市商库存变化')
axes[2].plot(pnl[:200])
axes[2].set_title('做市商 PnL')
plt.tight_layout()
plt.show()六、知情交易概率(PIN)
6.1 什么是 PIN
PIN(Probability of Informed Trading)衡量的是:在当前市场中,一笔交易是知情交易的概率有多大?
PIN 越高,做市商面临的逆向选择风险越大,价差应该越宽。
6.2 简化计算
import numpy as np
from scipy.optimize import minimize
def estimate_pin_simple(buys, sells, n_days=252):
"""简化的 PIN 估计
buys: 每日主动买入笔数
sells: 每日主动卖出笔数
"""
total_buys = np.sum(buys)
total_sells = np.sum(sells)
# 总交易量
total = total_buys + total_sells
# 买入占比偏斜(简化版 PIN)
buy_frac = total_buys / total
sell_frac = total_sells / total
# 简化估计:PIN ≈ 信息不对称导致的不平衡程度
# 实际 PIN 需要最大似然估计
imbalance = np.abs(buys - sells) / (buys + sells + 1e-10)
# 日均不平衡
daily_imbalance = np.mean(imbalance)
return daily_imbalance
# 模拟
np.random.seed(42)
n_days = 252
# 正常交易:买卖大致平衡
noise_buys = np.random.poisson(100, n_days)
noise_sells = np.random.poisson(100, n_days)
# 加入知情交易(买入偏多)
informed_buys = np.random.poisson(30, n_days)
informed_sells = np.random.poisson(10, n_days)
total_buys = noise_buys + informed_buys
total_sells = noise_sells + informed_sells
pin = estimate_pin_simple(total_buys, total_sells)
print(f"简化 PIN 估计: {pin:.4f}")七、订单流毒性(VPIN)
7.1 什么是 VPIN
VPIN(Volume-Synchronized Probability of Informed Trading)是 Easley, Lopez de Prado 和 O’Hara (2012) 提出的指标。
核心思想:当订单流变得”有毒”(不平衡程度异常高)时,通常预示着价格即将大幅变动。
VPIN = 基于成交量桶(volume bucket)计算的知情交易概率,比传统 PIN 更适合高频数据。
7.2 Python 简化计算
import numpy as np
def compute_vpin(prices, volumes, bucket_size=10000):
"""简化的 VPIN 计算
prices: 成交价格序列
volumes: 成交量序列
bucket_size: 每个桶的目标成交量
"""
# 判断买卖方向:价格高于前一笔为买入,低于为卖出
directions = np.sign(np.diff(prices))
directions = np.concatenate([[0], directions])
# 去掉方向为 0 的
mask = directions != 0
dirs = directions[mask]
vols = volumes[mask]
# 按成交量分桶
cum_vol = 0
buckets = []
current_bucket = []
for v, d in zip(vols, dirs):
current_bucket.append((v, d))
cum_vol += v
if cum_vol >= bucket_size:
buckets.append(current_bucket)
current_bucket = []
cum_vol = 0
# 计算每个桶的买卖不平衡
imbalances = []
for bucket in buckets:
buy_vol = sum(v for v, d in bucket if d > 0)
sell_vol = sum(v for v, d in bucket if d < 0)
total = buy_vol + sell_vol
if total > 0:
imb = abs(buy_vol - sell_vol) / total
imbalances.append(imb)
# VPIN = 最近 L 个桶的平均不平衡
L = min(50, len(imbalances))
vpin = np.mean(imbalances[-L:]) if imbalances else 0.0
return vpin, imbalances
# 模拟
np.random.seed(42)
n_trades = 50000
base_price = 100
prices = [base_price]
volumes = np.random.exponential(100, n_trades)
for i in range(1, n_trades):
# 正常波动
ret = np.random.normal(0, 0.001)
# 在某些时段加入大单(模拟知情交易)
if 20000 < i < 21000:
ret += 0.005 # 有利好的知情交易
prices.append(prices[-1] * (1 + ret))
prices = np.array(prices)
vpin_val, imbalances = compute_vpin(prices, volumes, bucket_size=5000)
print(f"VPIN: {vpin_val:.4f}")八、价格发现(Hasbrouck 模型)
8.1 核心思想
Hasbrouck (1991) 问的是:信息如何逐步反映到价格中?
在真实市场中,信息不是瞬间反映的。一笔新的信息可能出现后:
- 第一秒:少部分交易者反应
- 第二秒:更多交易者看到并反应
- 第三秒:做市商调整报价
- …持续数秒到数分钟
Hasbrouck 用 VAR 模型捕捉这个过程:
其中 是收益率, 是带符号的交易量(买入为正、卖出为负)。
信息份额(Information Share):衡量订单流对价格发现贡献的比例。在多市场(如沪深两市同时交易)中,可以衡量哪个市场主导了价格发现。
九、Ho-Stoll 库存模型
9.1 做市商如何管理库存风险
做市商的核心难题:他必须不断买卖来提供流动性,但每笔交易都会改变他的持仓,持仓越大,价格反向变动的风险越高。
Ho-Stoll (1981) 模型刻画了做市商如何在价差和库存之间做权衡。
做市商的最优报价:
其中 是做市商当前库存(多头为正,空头为负), 是库存调整系数。
直觉:
- 当库存为正(持有多头)时,做市商会降低 Ask 和 Bid(降低卖价吸引买家,降低买价阻止卖家)
- 当库存为负(持有空头)时,做市商会提高 Ask 和 Bid
- 目标:把库存拉回零
这就是为什么做市商的报价不是对称的——他的库存会”扭曲”报价。
十、本文件核心要点
| 模型 | 核心问题 | 关键参数 |
|---|---|---|
| 订单簿 | 价格如何匹配? | 价格优先、时间优先 |
| 价差理论 | 为什么买价和卖价不同? | 信息不对称、库存风险 |
| Kyle (1985) | 知情交易者如何影响价格? | lambda(价格冲击系数) |
| Glosten-Milgrom | 做市商如何应对信息不对称? | Bayes 更新 |
| Avellaneda-Stoikov | 做市商应该挂什么价? | 库存、风险厌恶、时间窗口 |
| PIN | 交易对手是否知情? | 知情交易概率 |
| VPIN | 订单流是否有毒? | 成交量桶不平衡度 |
| Hasbrouck | 信息如何反映到价格? | 信息份额 |
| Ho-Stoll | 做市商如何管理库存? | 库存调整系数 |
一句话总结:微观结构理论的核心是理解”交易的真实成本”,以及这些成本从何而来、如何变化。