返回 Expert 笔记
Expert Day 77

流动性指标 (Liquidity Metrics)

spread 类指标、深度类指标、价格冲击类(Amihud / Kyle / Hasbrouck)、Roll 隐式 spread

2026-07-17
Phase 2 - 市场微观结构与做市 (Day 75-88)
做市微观结构流动性AmihudRoll

日期: 2026-07-17 方向: 量化 / 微观结构 / 做市 阶段: Phase 2 - 市场微观结构与做市 (Day 75-88) 标签: #做市 #微观结构 #流动性 #Amihud #Roll


今日目标

类型内容
学习spread 类指标、深度类指标、价格冲击类(Amihud / Kyle / Hasbrouck)、Roll 隐式 spread
实操用 Binance 1m/1d K线计算 BTC/ETH/SOL 三对 Amihud、effective spread、Kyle 经验 λ
产出liquidity.py:6 个指标、不同时间尺度对比、跨币流动性热图

流动性是看不见的,但可以从多个角度估测。今天我们把昨天 Kyle 理论和 microstructure 落到 6 个可计算的指标。


一、流动性的三维定义

Black (1971) 给的经典定义:"Liquidity is the ability to trade quickly without significant price impact"。三个维度:

维度含义代表指标
Tightnessspread 多窄quoted/effective spread
Depth不动价格能成交多少顶档量、累积深度
Resiliency价格冲击恢复速度impact reversal、Hasbrouck VAR

实务做市看四类:

  1. Spread 类:Quoted、Effective、Realized、Roll
  2. Depth 类:Top-of-book、N-tick depth、Top-N depth
  3. Impact 类:Kyle λ、Amihud、Hasbrouck λ、Square-root law
  4. Time 类:trade arrival rate、turnover

二、Spread 指标

2.1 Quoted Spread

$$ s_{quoted} = a - b, \quad s_{quoted}^{rel} = \frac{a-b}{m} $$

最直观但易被博弈:做市商挂 1-tick spread 但 size=$10,看似 tight,深度为 0。

2.2 Effective Spread

衡量 taker 实际付出的"过桥费":

$$ s_{eff} = 2 \cdot |p_{trade} - m_{trade}| $$

其中 m_trade 是成交时的 mid。如果 taker buy p > m,差额×2 就是隐含 round-trip 成本。

关键:可以用公开 trade tape 计算,无需簿。

2.3 Realized Spread

成交后等 5min/15min,看价格是否回归(noise)还是 marche(informed):

$$ s_{real} = 2 \cdot D \cdot (p_{trade} - m_{t+\Delta}) $$

其中 D = +1 buy / -1 sell。s_eff − s_real 就是 adverse selection cost——昨天 GM 模型的 AS。

2.4 Roll (1984) 隐式 Spread

只用收盘价、不需簿:

$$ s_{Roll} = 2 \sqrt{-\text{Cov}(r_t, r_{t-1})} $$

直觉:在 noise dominated 市场,价格在 bid 和 ask 间 bouncing,相邻 return 负相关。负 cov 越大,spread 越大。

Crypto 应用:当你只能拿到日 K 线(小币 / DEX 池),Roll 是唯一能估 spread 的方法。


三、Depth 指标

3.1 Top-of-Book Depth

V_b1V_a1——best 档量。但 hidden / iceberg 让真实深度 > 显示深度。

3.2 N-tick Depth

best ± N tick 内累计量。Binance BTCUSDT 5-tick 深度通常 50-200 BTC(峰值时段更厚)。

3.3 Cost-to-Trade-Q

更实用:成交 Q 单位的平均加权价 vs mid,给出"实际滑点":

$$ \text{slippage}(Q) = \frac{\sum p_i q_i}{Q} - m, \quad \sum q_i = Q $$

Day 83 我们用这个推 Almgren-Chriss。


四、Impact 指标

4.1 Amihud (2002) Illiquidity

最经典、最简单:

$$ \boxed{\text{ILLIQ}_t = \frac{|r_t|}{V_t}} $$

r_t 是日收益率,V_t 是日成交额。直觉:每 $1 交易引起的 |return|。

与 Kyle λ 关系:Amihud ≈ Kyle λ / 价格 — 因为 Δp = λ q 取绝对值除以总流量后约等于 λ / m

4.2 Hasbrouck (2009) VAR-based λ

更精细:用 trade-by-trade VAR 估非对称冲击。 $$ r_t = \sum \beta_i r_{t-i} + \lambda \cdot q_t^{signed} + \epsilon_t $$

MM 实务:用 Hasbrouck 估永久冲击,区分临时/永久 part。

4.3 Square-root Law

实证(Almgren-Chriss、Tóth-Bouchaud):

$$ \text{Impact}(Q) \approx \sigma_{daily} \cdot \kappa \cdot \sqrt{Q / V_{daily}} $$

κ ≈ 0.1 - 1.0。在 BTCUSDT Binance 通常 κ ≈ 0.4。

为什么 √ 而非线性?(i) 大单切片执行被吸收;(ii) 永久 part 仅由信息含量决定,size 边际信息含量递减。


五、代码实现:liquidity.py

"""
liquidity.py — 6 类流动性指标 + Binance 真实数据
依赖:numpy, pandas, requests, matplotlib
"""
import numpy as np, pandas as pd, requests
from dataclasses import dataclass

# ----------------------------------------------------------
# 1. 取 Binance K 线 + trade
# ----------------------------------------------------------
def klines(symbol, interval="1m", limit=1000):
    url = "https://fapi.binance.com/fapi/v1/klines"
    r = requests.get(url, params={"symbol":symbol, "interval":interval, "limit":limit}).json()
    cols = ["open_time","open","high","low","close","volume",
            "close_time","quote_vol","ntrades","taker_buy_base","taker_buy_quote","_"]
    df = pd.DataFrame(r, columns=cols)
    for c in ["open","high","low","close","volume","quote_vol","taker_buy_base","taker_buy_quote"]:
        df[c] = df[c].astype(float)
    df["ts"] = pd.to_datetime(df["open_time"], unit="ms")
    return df

# ----------------------------------------------------------
# 2. 6 个指标
# ----------------------------------------------------------
def quoted_spread(book_df):
    """book_df 含 best_bid, best_ask 列"""
    return (book_df["best_ask"] - book_df["best_bid"]).mean()

def effective_spread_from_trades(trades_df):
    """trades_df: price, side(buy/sell), mid"""
    s = 2 * (trades_df["price"] - trades_df["mid"]).abs()
    return s.mean()

def realized_spread(trades_df, k=300):
    """trades_df 包含 mid_future (k 秒后 mid)"""
    D = trades_df["side"].map({"buy":1, "sell":-1})
    return (2 * D * (trades_df["price"] - trades_df["mid_future"])).mean()

def roll_spread(returns: pd.Series):
    """ Roll 1984 隐式 spread = 2 sqrt(-cov(r_t, r_t-1)) """
    cov = returns.autocorr(lag=1) * returns.var()
    if cov >= 0:
        return 0.0
    return 2 * np.sqrt(-cov)

def amihud_illiq(df):
    """日 Amihud = mean(|r| / V_quote)"""
    df = df.copy()
    df["ret"] = df["close"].pct_change()
    illiq = (df["ret"].abs() / df["quote_vol"]).replace([np.inf, -np.inf], np.nan).dropna()
    return illiq.mean()

def hasbrouck_lambda(df_1m):
    """
    用 1 分钟 净成交方向 net_signed_volume regress return
    Binance 给 taker_buy_base / volume,可推 net buy ratio
    net_signed_q = taker_buy_base - (volume - taker_buy_base) = 2*taker_buy - volume
    """
    df = df_1m.copy()
    df["net_q"] = 2 * df["taker_buy_base"] - df["volume"]
    df["ret"] = df["close"].pct_change()
    df = df.dropna()
    cov = np.cov(df["ret"], df["net_q"])[0,1]
    var = np.var(df["net_q"])
    return cov / var if var > 0 else None

# ----------------------------------------------------------
# 3. 跑三个交易对
# ----------------------------------------------------------
def liquidity_panel(symbols=("BTCUSDT","ETHUSDT","SOLUSDT")):
    rows = []
    for sym in symbols:
        d_1m = klines(sym, "1m", 1000)
        d_1d = klines(sym, "1d", 90)
        d_1d["ret"] = d_1d["close"].pct_change()
        roll = roll_spread(d_1d["ret"].dropna())
        amihud = amihud_illiq(d_1d)
        hasb = hasbrouck_lambda(d_1m)
        # 简化 quoted spread 用 1m high-low 估算
        d_1m["pseudo_spread_bps"] = (d_1m["high"] - d_1m["low"]) / d_1m["close"] * 1e4
        rows.append({
            "symbol": sym,
            "daily_vol_quote": d_1d["quote_vol"].mean(),
            "amihud_illiq": amihud,
            "roll_spread": roll,
            "hasbrouck_lambda": hasb,
            "pseudo_spread_bps_1m": d_1m["pseudo_spread_bps"].median(),
        })
    return pd.DataFrame(rows)

if __name__ == "__main__":
    panel = liquidity_panel()
    print(panel.to_string(index=False))

预期输出(数量级示例)

 symbol  daily_vol_quote   amihud_illiq    roll_spread  hasbrouck_lambda  pseudo_spread_bps_1m
BTCUSDT      3.2e10           2.1e-13        0.00012        1.4e-10              4.5
ETHUSDT      1.8e10           5.6e-13        0.00018        4.1e-10              5.8
SOLUSDT      4.1e9            3.4e-12        0.00045        2.3e-9               12.3

观察:

  • Amihud 排序 BTC < ETH < SOL,与"小币更不流动"直觉一致
  • Hasbrouck λ 同序,证明 Kyle λ ≈ Amihud × P 的等价
  • pseudo spread 用 1m H-L 是粗估,但跨 symbol 可比

5.1 跨币热图绘制

import matplotlib.pyplot as plt
panel = liquidity_panel(("BTCUSDT","ETHUSDT","SOLUSDT","DOGEUSDT","SUIUSDT"))
metrics = ["amihud_illiq","roll_spread","hasbrouck_lambda","pseudo_spread_bps_1m"]
norm = panel[metrics].apply(lambda c: (c - c.min())/(c.max()-c.min()), axis=0)
plt.imshow(norm.values, cmap="hot", aspect="auto")
plt.yticks(range(len(panel)), panel["symbol"]); plt.xticks(range(len(metrics)), metrics, rotation=30)
plt.colorbar(label="Normalized illiquidity")
plt.title("流动性热图 — 越红越不流动")
plt.tight_layout()
# plt.savefig("liq_heatmap.png")

六、真实数据接入

Binance Klines 字段

GET https://fapi.binance.com/fapi/v1/klines?symbol=BTCUSDT&interval=1m&limit=2

返回数组(每行 12 字段):
[
  [ 1709337540000, "62150.10","62156.30","62149.90","62154.50",
    "12.345", 1709337599999, "767001.23", 145, "6.123","380456.12","0" ],
  ...
]
索引含义:
  0  open_time
  1  open
  2  high
  3  low
  4  close
  5  volume (base)
  6  close_time
  7  quote_vol (USD)
  8  num_trades
  9  taker_buy_base_vol
  10 taker_buy_quote_vol
  11 ignore

计算 maker/taker imbalance

imbalance = (taker_buy_base - (volume - taker_buy_base)) / volume
          ∈ [-1, +1],正=主动买占优,负=主动卖

这个 imbalance 是 Hasbrouck λ 的输入,也是 OFI 的简化版(Day 82)。


七、CEX vs DEX 对比

指标CEX (Binance BTC)DEX V2 (Uni ETH/USDC)DEX V3 集中流动性DEX LOB (Hyperliquid)
Quoted spread0.5-1 bps30 bps (V2 0.3% fee)5-10 bps (active range)1-2 bps
Top depth20-100 BTC$2M reserves$200K active5-30 BTC
Amihud1e-131e-111e-125e-13
Effective vs quotedquoted ≈ effeff = quoted = pool feeeff < quoted (bonus)quoted ≈ eff
Roll spread极低高(block 离散)
Resiliency秒级回归1 block (~12s ETH)同上 + arb100ms-1s
Hidden liqiceberg链下 hidden
隐含成本来源spread + feefee + IL + slippage同上 + concentration riskspread + fee + funding

核心观察

  1. AMM 的 "spread" 是协议参数(fee tier),与微观结构无关——所以 V2 永远 30 bps(或 5/30/100 bps tier),不会因为流动性变多而收窄。
  2. V3 通过让 LP 选择集中区间,间接产生"有效 spread"。窄区间 LP 等价于"在两个限价单间做市"。
  3. DEX LOB(Hyperliquid)的 spread 已经接近 CEX,但 funding rate 机制不同。

八、风险与陷阱

  1. Amihud 在低 volume 日 explodes 分母 V→0 导致 spike。crypto 节假日 / 周末必须 winsorize 到 1-99 percentile。

  2. Roll cov ≥ 0 → 公式失效 存在趋势市(informed dominated)时 cov 转正,Roll 给出 0 spread。说明该窗口"非 noise dominated",要切换 Hasbrouck。

  3. K 线 high-low 不是真 spread 1m HL 反映窗口内最大波动而非瞬时 spread。在 1s 级别 spread 远窄于此。但跨币比较仍可用。

  4. Maker rebate 让 effective spread 为负 Binance Futures 顶级做市商可能有 -1 bps maker rebate。这时挂单+成交相当于 spread 收益。

  5. Latency 导致的 stale liquidity 你看到的 best ask 100 BTC 可能在 5ms 后被对手 cancel。真实可执行深度 ≪ snapshot 深度。生产估计要用历史 fill 数据反推。

  6. Multi-venue fragmentation BTC 同时在 Binance / Bybit / OKX / CME。单交易所 Amihud 高估真实 illiquidity。


九、关键速查

Quoted spread       s = a - b
Quoted relative     s/m
Effective spread    2|p_trade - m_trade|
Realized spread     2D(p_trade - m_{t+Δ})
Adverse selection   eff - real
Roll spread         2 √(−Cov(r_t, r_{t-1}))
Amihud              mean(|r|/V_quote)
Hasbrouck λ         OLS slope of return on signed volume
Cost-to-trade Q     ∫ p(q)dq / Q − m
Square-root law     impact ∝ σ √(Q/V)

经验阈值(BTC 现货 1m)

quoted spread:    1 - 2 bps        (1 = tight, 4 = stress)
top depth:        1 - 5 BTC        (>5 → very deep)
1m volume:        $5 - 50M
Amihud (daily):   1e-13 - 5e-13

十、面试题

Q1: 为什么 Quoted spread 不能单独用于流动性评估?

A: (i) 仅看 best 1 档掩盖 hidden / iceberg;(ii) 尺寸不对称(best ask 0.1 BTC 与 best ask 50 BTC 同 spread 但流动性截然不同);(iii) 易被 quote-stuffing 压窄;(iv) 不反映 resiliency。必须配合 effective spread + depth。

Q2: 给一个只有日收盘价的小市值币,如何估 spread?

A: 用 Roll 1984:s = 2√(-cov(r_t, r_{t-1}))。如果 cov ≥ 0 用 Corwin-Schultz high-low 估计或 Abdi-Ranaldo close-high-low。Crypto 上 Hasbrouck Bayesian 是更稳健的选择。

Q3: Amihud 与 Kyle λ 的数学关系?

A: Amihud = E[|r|/V] = E[|λq/p|/V]。如果 q ≈ V(即所有量都是 signed flow),则 Amihud ≈ λ/p。所以 Amihud 是 price-normalized λ,跨资产可比。

Q4: 在 V3 LP 上下文中,"effective spread" 怎么定义?

A: V3 LP 在区间 [P_a, P_b] 内提供流动性,swap 时实际过桥成本 = pool fee tier (5/30/100 bps) + slippage along curve。特殊:当区间外,LP 暴露完全转为单边代币,相当于 spread 突变到 +∞。所以 V3 effective spread 是状态依赖的。

Q5: 流动性危机时 (e.g. May 19 2021) 哪些指标最先恶化?

A: 顺序:(1) Top depth 立即蒸发(MM 撤单);(2) Quoted spread 跳 10-100x;(3) Effective > quoted(taker 必须 cross 多档);(4) Amihud 飙升;(5) Roll cov 转正(trending market);(6) 多 venue 价差扩大。做市启示:top depth + spread 是最敏感的早期信号。


明日预告

Day 78:Avellaneda-Stoikov 做市模型 — 经典论文 "High-frequency trading in a limit order book" (2008)。今天我们会从 HJB 方程严格推导最优 bid/ask offset 的闭式解,看到 γ(风险厌恶)、k(订单到达率)、σ(vol)三个参数如何决定报价。这是接下来 5 天做市建模的脊柱。