2、市场微观结构:订单簿、买卖价差、交易量分布、市场冲击成本
好,咱们进入第二章。这一章聊的是市场微观结构,说白了就是「市场到底是怎么运作的」。很多人做量化,上来就撸因子、跑回归,结果实盘一跑就崩。为什么?因为忽略了市场的底层逻辑。
我个人习惯,做任何策略之前,先花一周时间把微观结构摸透。你想想看,连订单簿长什么样都不知道,你怎么敢说自己懂Alpha?
2.1 订单簿:市场的「实时心电图」
订单簿,英文叫Order Book。它记录了所有未成交的买单和卖单。每一笔交易,本质上都是订单簿上的「撮合」。
我举个例子。假设某只股票当前买一价是10.00元,卖一价是10.01元。你想买入100股,如果直接吃卖一,成交价就是10.01。如果你挂10.00的限价单,那就得等别人卖给你。
订单簿的核心结构很简单:
- 买盘(Bid):从高到低排列,买一最高
- 卖盘(Ask):从低到高排列,卖一最低
- 深度(Depth):每个价位上的挂单量
嗯,这里要注意。订单簿不是静态的。它每时每刻都在变化。高频交易者就是靠捕捉订单簿的「瞬间失衡」来赚钱的。
关键点:订单簿的斜率变化,往往预示着价格即将突破。我在项目中遇到过,当买盘深度突然变厚,而卖盘深度不变时,价格大概率会向上走。
咱们用Python拉一下订单簿数据,看看长什么样:
import pandas as pd
import numpy as np
# 模拟一个简单的订单簿
def generate_order_book(mid_price=100.0, spread=0.02, depth=10):
bids = []
asks = []
for i in range(depth):
bid_price = mid_price - spread/2 - i * 0.01
ask_price = mid_price + spread/2 + i * 0.01
bid_vol = np.random.randint(100, 1000)
ask_vol = np.random.randint(100, 1000)
bids.append([bid_price, bid_vol])
asks.append([ask_price, ask_vol])
return pd.DataFrame(bids, columns=['price', 'volume']), pd.DataFrame(asks, columns=['price', 'volume'])
bids, asks = generate_order_book()
print("买盘前5档:")
print(bids.head())
print("\n卖盘前5档:")
print(asks.head())
这段代码模拟了10档深度。实际交易中,你看到的Level 2数据就是这种结构。
2.2 买卖价差:你交易的成本「隐形杀手」
买卖价差(Bid-Ask Spread),就是买一和卖一之间的差价。它直接决定了你的交易成本。
举个例子。你看到某股票买一10.00,卖一10.02。价差是0.02元。如果你现在买入,成交价是10.02。如果你立刻卖出,成交价是10.00。一进一出,你就亏了0.02元。这就是价差成本。
价差的大小,反映了市场的流动性。流动性好的股票,价差可能只有1分钱。流动性差的股票,价差可能几毛钱甚至几块钱。
我曾经踩过一个坑。做回测时,我假设以收盘价成交。结果实盘一跑,发现每次交易都多亏了0.5%的价差。半年下来,收益直接腰斩。从那以后,我再也不敢忽略价差了。
实战技巧:计算有效价差时,别只看买一卖一。我习惯用「成交量加权平均价差」,这样更贴近实际成交成本。
价差的计算公式很简单:
spread = (ask_price - bid_price) / mid_price * 10000 # 单位:基点
比如价差0.02,中间价10.01,那么价差就是 (0.02 / 10.01) * 10000 ≈ 20个基点。嗯,20个基点是什么概念?如果你每天交易一次,一年250个交易日,光价差成本就吃掉50%的收益。
2.3 交易量分布:谁在买?谁在卖?
交易量分布,说白了就是「钱往哪儿流」。它包含两个维度:时间分布和价格分布。
时间分布:一天中,交易量并不是均匀的。开盘和收盘时交易量最大,午盘相对清淡。为什么?因为开盘时信息集中释放,收盘时机构调仓。
价格分布:某个价格区间内,成交了多少量。这其实就是Volume Profile(成交量分布图)。
我个人习惯,用Volume Profile来识别「价值区域」。如果价格跌破了价值区域的下沿,我会考虑做多。反之亦然。
咱们画一下交易量的时间分布:
import matplotlib.pyplot as plt
import numpy as np
# 模拟一天内的交易量分布
minutes = np.arange(0, 240) # 4小时交易时间
volume = np.zeros(240)
# 开盘和收盘放量
volume[:30] = np.random.uniform(100, 200, 30)
volume[30:210] = np.random.uniform(50, 100, 180)
volume[210:] = np.random.uniform(100, 200, 30)
plt.figure(figsize=(10, 4))
plt.plot(minutes, volume)
plt.title('日内交易量分布(模拟)')
plt.xlabel('分钟')
plt.ylabel('成交量')
plt.grid(True, alpha=0.3)
plt.show()
你看,这个U型分布很典型。如果你做的是高频策略,最好避开午盘那段时间,流动性太差,滑点大。
注意:别把交易量分布当成静态的。市场环境变了,分布也会变。比如突发新闻时,交易量会瞬间爆发。我建议每周重新计算一次分布参数。
2.4 市场冲击成本:大单的「隐形税」
市场冲击成本,就是你下单时「推动价格」造成的额外成本。你想想看,如果你要买100万股,而买一只有1000股,那你只能一路往上吃,把价格推高。这个推高的部分,就是冲击成本。
冲击成本的计算,业界常用Almgren-Chriss模型。核心公式是:
Impact = k * sigma * (Q / V)^alpha
其中:
- k:冲击系数,一般取0.1~0.5
- sigma:波动率
- Q:你的交易量
- V:市场总成交量
- alpha:指数,一般取0.5~1.0
举个例子。某股票日成交量100万股,波动率20%。你想买1万股。假设k=0.2,alpha=0.5,那么冲击成本就是:
Impact = 0.2 * 0.2 * (10000 / 1000000)^0.5
= 0.04 * 0.1
= 0.004 # 即0.4%
也就是说,你这一单下去,价格会被推高0.4%。如果你做的是高频策略,0.4%的冲击成本足以让策略从盈利变成亏损。
我曾经帮一个客户优化算法交易。他原来的策略是直接市价单全仓买入,冲击成本高达1.2%。我建议他改用TWAP(时间加权平均价格)算法,把大单拆成小单,分批下单。结果冲击成本降到了0.3%。
核心结论:冲击成本是Alpha的天敌。你辛辛苦苦挖出来的因子,可能赚了0.5%的收益,结果冲击成本吃掉0.8%。所以,做策略时一定要把冲击成本算进去。
2.5 知识体系总览
说了这么多,咱们用一张图把这一章的核心逻辑串起来:
这张图把四个核心概念串起来了。订单簿是基础,价差和交易量分布是中间层,冲击成本是最终要解决的问题。你想想看,如果不理解这些,你挖出来的Alpha很可能被交易成本吃光。
我的建议:做策略之前,先花一周时间跑一下订单簿数据。看看你的标的物,价差有多大?冲击成本有多高?交易量分布是什么样的?这些数据,比任何因子都重要。
好了,这一章就到这里。记住一句话:市场微观结构,是Alpha的「土壤」。土壤不好,种子再好也长不出庄稼。