価格が形成される場所
画面上の価格は行列の結果です。取引所では、買い手と売り手が「300株を99.50で買う」「200株を100.00で売る」のような指値注文を出します。約定しなかったものは指値注文板(リミット・オーダーブック)に残ります。買い注文(ビッド)は最高価格が最良、売り注文(アスク)は最低価格が最良です。取引は、新しく来た注文が待っている注文とぶつかったときに成立します。これまでのトピックで価格、スプレッド、スリッページ、インパクトと呼んだものはすべてこの構造から生まれます。
# 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スプレッド、仲値、板の厚み
最良買い気配(ベストビッド)と最良売り気配(ベストアスク)が板の頂点です。その差がスプレッドで、すぐに取引するための価格です。アスクで買ってビッドで売り戻す買い手はスプレッド分を失います。2つの中間が仲値で、公正価格の標準的な目安です。板の厚みは各価格水準とその先に並ぶ数量で、価格が動くまでにどれだけ取引できるかを示します。片側が空なら、その側には価格がまったくないので、スプレッドと仲値は未定義で、ゼロではなく 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) でコピーし、コピーを変更して返します。引数を黙って書き換える関数は、同じ板を2か所で使うとバグの典型的な原因になります。
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成行注文を約定させる
成行の買いは、売り板を最安値から順に上へ取っていきます。最初の水準を全部、次に2番目と、注文が埋まるか板が空になるまで進みます。ソートした価格をループし、各水準で 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点