价格形成的地方
屏幕上的价格是排队的结果。在交易所里,买卖双方挂出限价单:“以 99.50 买入 300”“以 100.00 卖出 200”。没成交的订单停留在限价订单簿中:买盘(bid)最优的是最高价,卖盘(ask)最优的是最低价。新到的订单与挂着的订单相遇时成交。前面各节所说的价格、价差、滑点和冲击,全都来自这个结构。
# A side of the book as a dict {price: total size}
bids = {99.5: 300, 99.4: 500, 99.0: 1200}
asks = {100.0: 200, 100.1: 400, 100.5: 800}
best_bid, best_ask = max(bids), min(asks)
print(best_bid, best_ask) # 99.5 100.0
print(round(best_ask - best_bid, 2), (best_bid + best_ask) / 2) # the spread 0.5 and the mid 99.75价差、中间价与深度
最优买价和最优卖价是订单簿的顶部。两者之差是价差,也是立即成交的代价:以卖价买入再以买价卖回的买家会损失这个价差。两者正中间是中间价,通常用作公允价格的参考。深度是各价位及其后挂着的数量,说明价格移动之前能成交多少。某一侧为空表示那一侧根本没有价格,所以价差和中间价无定义,应返回 None 而不是零。
def best_bid_ask(bids, asks):
return (max(bids) if bids else None, min(asks) if asks else None)
def spread_and_mid(bids, asks):
bid, ask = best_bid_ask(bids, asks)
if bid is None or ask is None:
return None, None
return ask - bid, (bid + ask) / 2
print(spread_and_mid({99.5: 300, 99.4: 500}, {100.0: 200, 100.1: 400})) # (0.5, 99.75)
print(spread_and_mid({}, {100.0: 200})) # (None, None): one side is empty添加订单而不改动过去
新的限价单要么加入已有价位、增加该价位的总数量,要么新开一个价位。把它写成返回新字典、而不是修改旧字典的函数,有个实际好处:你保留了下单前订单簿的快照,便于测试和回放。用 dict(side) 复制,修改副本,再返回。悄悄修改自己参数的函数,在同一个订单簿被两处使用时是经典的 bug 来源。
def add_limit_order(side, price, size):
new = dict(side) # a copy: the original book is not touched
new[price] = new.get(price, 0) + size
return new
bids = {99.5: 300, 99.4: 500}
after = add_limit_order(bids, 99.5, 100) # joins the existing level
after = add_limit_order(after, 99.3, 50) # opens a new level
print(after) # {99.5: 400, 99.4: 500, 99.3: 50}
print(bids) # {99.5: 300, 99.4: 500} unchanged撮合市价单
市价买单从最低价开始向上吃卖盘:先吃完第一档,再吃第二档,直到订单成交完或订单簿被吃空。对排序后的价格循环,每档取 min(left, size_at_price),记录成交,然后删除该档(全部吃掉)或减少该档(部分吃掉)。成交列表给出你在订单成交一节中见过的平均价,剩下的订单簿就是你成交之后的市场:顶部变薄,最优卖价变差。
def match_market_buy(asks, size):
remaining = dict(asks)
fills = []
left = size
for price in sorted(asks): # cheapest first
if left <= 0:
break
take = min(left, asks[price])
fills.append((price, take))
left -= take
if take == asks[price]:
del remaining[price] # the level is used up
else:
remaining[price] = asks[price] - take
return fills, remaining
asks = {100.0: 200, 100.1: 400, 100.5: 800}
fills, remaining = match_market_buy(asks, 500)
print(fills) # [(100.0, 200), (100.1, 300)]
print(remaining) # {100.1: 100, 100.5: 800}真实的订单簿是队列按价格汇总总量的字典隐藏了队列:在每个价位,订单按到达顺序成交(价格-时间优先),你的订单在队列中的位置决定它能否成交。真实行情还有大量撤单和改单,它们主导了消息流量。这里的汇总订单簿是理解深度、价差和冲击的正确模型,也是下一节订单流失衡的起点。
- 每一侧是字典
{price: size}:最优买价max(bids),最优卖价min(asks);价差为ask - bid,中间价为二者平均,若一侧为空则都是None。 - 添加订单的方法:复制字典、在该价位累加、返回副本;传入的订单簿保持不变。
- 市价买单遍历排序后的卖盘,每档取
min(left, size),删除耗尽的档、减少部分成交的档;真实订单簿在每个价位还有排队。
下一节:订单流失衡,通过读取订单簿中的压力来预判下一次价格变动。
实验:评分内容
- 用
best_bid_ask找到订单簿顶部25 分 - 用
spread_and_mid度量价差25 分 - 用
add_limit_order挂入一笔订单25 分 - 用
match_market_buy逐档吃卖盘25 分