市場が取引するときに取引する
出来高加重平均価格は、取引されたすべてのものの平均価格で、各約定をその大きさで重み付けします(sum(price * volume) / sum(volume))。その日の平均的な参加者が支払った価格なので、機関投資家の取引で最もよく使われるベンチマークです。VWAP アルゴリズムは、*市場の出来高に比例して*取引することでこれに並ぶか上回ろうとします。市場が活発なとき(寄り付き、引け)は多く、静かなとき(昼時)は少なくします。TWAP が時計に従うのに対し、VWAP は群衆に従うので、各注文は他の参加者の取引に対して同程度の割合となり、価格を動かしにくくなります。
# The benchmark: each price counts as much as the volume that traded at it
def vwap(prices, volumes):
return sum(p * v for p, v in zip(prices, volumes)) / sum(volumes)
prices = [100.0, 101.0, 102.0]
volumes = [100, 300, 600]
print(round(vwap(prices, volumes), 4)) # 101.5 most volume traded near 102, so the VWAP leans that way
print(sum(prices) / len(prices)) # 101.0 the plain average ignores how much traded at each price出来高プロファイルとそれに従うスケジュール
その日の出来高は事前には分からないため、VWAP アルゴリズムは過去のデータから推定した出来高プロファイルを使います。たとえば直近20営業日で、各区間に取引された1日の出来高の平均的な割合です。日中の出来高は通常 U 字型で、寄り付きと引けで多くなります。スケジュールは各区間に total * 割合 株を割り当てます。これはめったに整数にならず、それぞれを四捨五入すると株数が増減しかねません。最大剰余法がこれを解決します。各区間に切り捨てた値を与え、残りの株を、小数部が最も大きい区間に1株ずつ配ることで、スケジュールの合計は常に注文数量と一致します。
# From volumes to a profile, then to whole shares by the largest remainder method
import math
def volume_profile(volumes):
total = sum(volumes)
return [v / total for v in volumes]
def vwap_schedule(total, profile):
exact = [total * share for share in profile]
shares = [math.floor(value + 1e-9) for value in exact]
leftover = total - sum(shares)
for index in sorted(range(len(profile)), key=lambda i: (-(exact[i] - shares[i]), i))[:leftover]:
shares[index] += 1
return shares
profile = volume_profile([300, 100, 100, 200, 500, 800]) # a U-shape: busy at the open and the close
print([round(p, 3) for p in profile]) # [0.15, 0.05, 0.05, 0.1, 0.25, 0.4]
print(vwap_schedule(1003, profile)) # [151, 50, 50, 100, 251, 401] adds up to 1003
print(sum(vwap_schedule(1003, profile))) # 1003参加率の上限とベンチマークの追跡
スケジュールは計画であり、市場はプロファイルの予測より静かかもしれません。参加率の上限は、各区間での取引を、その区間の実際の市場出来高の一定割合(たとえば10%)に制限し、フローの大半を自分が占めないようにします。取引できなかった株は後の区間に繰り越され、最後に未執行が残ることもあります。そのときは、リスクを増やすか、時間枠を延ばすか、中止するかを決めます。1日の終わりに、平均価格を市場の VWAP と比べます。その差(ベーシスポイント)がアルゴリズムのトラッキングエラーです。上昇相場で遅く取引して VWAP に勝っても実力ではありません。アルゴリズムは、多数の日にわたるスリッページで評価しましょう。
# A participation cap: never more than a fraction of the market's volume, carrying the rest forward
import math
def cap_participation(schedule, market_volumes, rate):
trades, carry = [], 0
for scheduled, volume in zip(schedule, market_volumes):
wanted = scheduled + carry
done = min(wanted, math.floor(rate * volume + 1e-9))
trades.append(done)
carry = wanted - done
return trades, carry
print(cap_participation([100, 100, 100], [500, 1000, 1000], 0.1)) # ([50, 100, 100], 50)
print(cap_participation([100, 0, 0, 0], [500, 1000, 400, 800], 0.1)) # ([50, 50, 0, 0], 0) the carry is traded when the market allows it# Following the clock (TWAP) and following the volume (VWAP) give different average prices on the same day
prices = [100.0, 100.2, 100.1, 100.4, 100.3, 100.5]
volumes = [300, 100, 100, 200, 500, 800] # busy at the open and the close
market_vwap = sum(p * v for p, v in zip(prices, volumes)) / sum(volumes)
twap_average = sum(prices) / len(prices) # equal pieces at every time
print(round(market_vwap, 4), round(twap_average, 4)) # 100.33 100.25
print(round((twap_average - market_vwap) / market_vwap * 10000, 1)) # -8.0 a TWAP buy beat the VWAP here, by trading light when volume was heavy- VWAP は
sum(price * volume) / sum(volume)。一般的なベンチマークで、VWAP アルゴリズムは市場の出来高に比例して取引する - 過去から出来高プロファイルを推定し、最大剰余法で注文を分割して、整数株の合計をちょうど合わせる
- 各区間の参加率に上限を設けて残りを繰り越し、多数の日にわたるトラッキングエラーとスリッページでアルゴリズムを評価する
次はマーケットメイクです。両サイドに気配を出し、その結果生じる在庫を管理します。
ラボ:採点される内容
vwapでベンチマークを計算する25点volume_profileで1日の形を捉える25点vwap_scheduleで注文を分割する25点cap_participationで市場への影響を抑える25点