Den Spread verdienen, das Risiko tragen
Ein Market Maker stellt Bid und Ask ein und wartet. Trifft ein Verkäufer den Bid und nimmt ein Käufer den Ask, kauft der Maker billig, verkauft teuer und behält den Spread. Das ist das Einkommen. Die Kosten sind das Bestandsrisiko: Zwischen den beiden Trades hält der Maker eine Position, deren Wert sich mit dem Preis bewegt, und er wird öfter von jemandem ausgeführt, der mehr weiß (Adverse Selection), als von jemandem, der nichts weiß. Gutes Market Making balanciert Quotes, die eng genug sind, um oft getroffen zu werden, und weit genug, um das Risiko zu vergüten, während der Bestand nahe null bleibt.
# The income: buy at the bid, sell at the ask, keep the spread
bid, ask, size = 99.95, 100.05, 100
cash = -bid * size + ask * size
print(round(cash, 2)) # 10.0 a round trip of 100 shares earned the 0.10 spread
# The risk: between the two trades the price moves against the inventory
inventory, mid_after = 100, 99.80
print(round(inventory * (mid_after - bid), 2)) # -15.0 the position lost more than the spread would have earnedAvellaneda-Stoikov: wo man quotiert
Das Avellaneda-Stoikov-Modell liefert eine geschlossene Antwort. Erstens ist der Reservierungspreis r = mid - q * gamma * sigma^2 * tau der Punkt, an dem der Maker bei Bestand q gern handeln würde: Ein Long-Maker (q > 0) sollte den Wert unter dem Mid ansetzen und tiefer quotieren, um Käufer anzulocken, ein Short-Maker höher. Risikoaversion gamma, Volatilität sigma und Restzeit tau bestimmen, wie stark er sich neigt. Zweitens hat der optimale Spread gamma * sigma^2 * tau + (2 / gamma) * ln(1 + gamma / k) zwei Teile: eine Gebühr für das Risiko, die Position bis zum Horizont zu halten, und einen Term für den Orderfluss, wobei k angibt, wie schnell Ausführungen versiegen, je weiter man vom Mid quotiert. Die Quotes sind r - spread/2 und r + spread/2.
# The reservation price leans against inventory: long -> quote lower, short -> quote higher
def reservation_price(mid, inventory, gamma, sigma, tau):
return mid - inventory * gamma * sigma ** 2 * tau
for inventory in (-50, 0, 50):
print(inventory, round(reservation_price(100.0, inventory, 0.1, 0.2, 1.0), 4))
# -50 100.2 short: quote higher to buy back
# 0 100.0 flat: centred on the mid
# 50 99.8 long: quote lower to sell# The optimal spread and the two quotes around the reservation price
import math
def optimal_spread(gamma, sigma, tau, k):
return gamma * sigma ** 2 * tau + (2 / gamma) * math.log(1 + gamma / k)
def make_quotes(mid, inventory, gamma, sigma, tau, k):
centre = mid - inventory * gamma * sigma ** 2 * tau
half = optimal_spread(gamma, sigma, tau, k) / 2
return centre - half, centre + half
print(round(optimal_spread(0.1, 0.2, 1.0, 1.5), 4)) # 1.2948
bid, ask = make_quotes(100.0, 0, 0.1, 0.2, 1.0, 1.5)
print(round(bid, 4), round(ask, 4)) # 99.3526 100.6474 symmetric around the mid when flat
bid, ask = make_quotes(100.0, 50, 0.1, 0.2, 1.0, 1.5)
print(round(bid, 4), round(ask, 4)) # 99.1526 100.4474 long 50: both quotes move down by 0.2Bestand, Fills und P&L
Verfolgen Sie den Zustand mit zwei Zahlen, Bestand und Cash. Wird der Bid getroffen, haben Sie gekauft: Der Bestand steigt, das Cash sinkt um price * quantity; wird der Ask getroffen, haben Sie verkauft: umgekehrt. Ihr Vermögen ist jederzeit Cash plus zum Mid bewerteter Bestand, cash + inventory * mid, und seine Änderung ist Ihr P&L. Dieses P&L stammt aus dem vereinnahmten Spread und den Preisbewegungen während des Haltens; Quoten um den Reservierungspreis ziehen den Bestand zurück auf null, weil die Seite, die ihn abbaut, attraktiver wird. Limits zählen in der Praxis: eine harte Bestandsobergrenze, eine Pause in schnellen Märkten und bei jeder Quote die Prüfung, dass der Bid unter dem Ask liegt.
# Booking fills and marking the book to market
def apply_fill(state, side, price, quantity):
inventory, cash = state
if side == "bid":
return inventory + quantity, cash - price * quantity
return inventory - quantity, cash + price * quantity
state = (0, 0.0)
state = apply_fill(state, "bid", 99.95, 100) # our bid is hit: we buy 100
print(state) # (100, -9995.0)
state = apply_fill(state, "ask", 100.05, 100) # our ask is hit: we sell 100
print(state) # (0, 10.0) flat again, and the spread is the profit
inventory, cash = apply_fill(state, "bid", 99.9, 50)
print(round(cash + inventory * 99.9, 2)) # 10.0 marked at the price we just paid, the equity is unchanged- Ein Market Maker verdient den Spread, trägt aber Bestandsrisiko und Adverse Selection; das Ziel: eng genug zum Handeln, weit genug zur Vergütung, Bestand nahe null.
- Der Reservierungspreis
mid - q * gamma * sigma^2 * tauneigt sich gegen den Bestand; der Spreadgamma * sigma^2 * tau + (2/gamma) ln(1 + gamma/k)wächst mit der risikoaversionsgewichteten Volatilität und der Zeit. - Verfolgen Sie Bestand und Cash, bewerten Sie zum Mid, erzwingen Sie harte Bestandslimits und testen Sie die Strategie in einem Simulator auf aufgezeichneten Daten, bevor Sie sie live nutzen.
Als Nächstes: das Heston-Modell, eine Volatilität, die selbst zufällig ist.
Lab: Was bewertet wird
- Mit
reservation_pricegegen den Bestand lehnen25 Pkt. - Die Breite mit
optimal_spreadfestlegen25 Pkt. - Mit
make_quotesbeide Seiten quotieren25 Pkt. - Mit
apply_filleinen Fill verbuchen25 Pkt.