Cartea-Jaimungal, Queue Imbalance, Guéant MM: 3 new quant finance strategies + backtests
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
"""
|
||||
Guéant Closed-Form Market Making Model.
|
||||
|
||||
Extends Avellaneda-Stoikov with closed-form asymptotic solutions
|
||||
that are computationally efficient and embed asymmetric information.
|
||||
|
||||
Reference: Guéant, Lehalle, Fernandez-Tapia — "Dealing with the
|
||||
Inventory Risk: A solution to the market making problem" (2012)
|
||||
|
||||
Key improvements over standard A-S:
|
||||
1. Closed-form solutions (no PDE solving needed)
|
||||
2. Explicit handling of asymmetric information (adverse selection)
|
||||
3. Explicit dependence on order book shape
|
||||
4. Better terminal condition handling
|
||||
|
||||
Optimal quotes:
|
||||
δ_a(t,q) = σ²γ(T-t)/2 + (1/γ)log(1 + γ/k)
|
||||
δ_b(t,q) = δ_a(t,q)
|
||||
r(t,q) = s - q·σ²γ(T-t) [reservation price]
|
||||
|
||||
where:
|
||||
s = mid price
|
||||
q = inventory
|
||||
γ = risk aversion
|
||||
σ = volatility
|
||||
k = order arrival intensity
|
||||
T-t = remaining time
|
||||
|
||||
Bid = r(t,q) - δ_b
|
||||
Ask = r(t,q) + δ_a
|
||||
|
||||
The Guéant extension adds:
|
||||
- Asymmetric spreads when adverse selection detected
|
||||
- Queue-position dependent fill probabilities
|
||||
- Better parameter estimation from LOB data
|
||||
|
||||
Usage:
|
||||
from strategies.gueant import GueantMM
|
||||
mm = GueantMM(gamma=0.1, sigma=0.01)
|
||||
bid, ask = mm.optimal_quotes(mid, inventory, elapsed, adverse)
|
||||
"""
|
||||
import math
|
||||
|
||||
class GueantMM:
|
||||
"""Closed-form market making with asymmetric information handling."""
|
||||
|
||||
def __init__(self, gamma: float = 0.1, sigma: float = 0.01,
|
||||
k: float = 1.5, T: float = 60.0, max_pos: float = 0.005):
|
||||
"""
|
||||
Args:
|
||||
gamma: risk aversion (0.01=v.aggressive, 1.0=v.conservative)
|
||||
sigma: volatility (annualized)
|
||||
k: baseline order arrival intensity
|
||||
T: trading session length in seconds
|
||||
max_pos: max absolute position
|
||||
"""
|
||||
self.gamma = gamma
|
||||
self.sigma = sigma
|
||||
self.k = k
|
||||
self.T = T
|
||||
self.max_pos = max_pos
|
||||
|
||||
def optimal_spread(self, tau: float, adverse_prob: float = 0) -> float:
|
||||
"""Compute optimal half-spread.
|
||||
|
||||
Args:
|
||||
tau: time remaining (T - elapsed)
|
||||
adverse_prob: estimated adverse selection probability (0-1)
|
||||
|
||||
Returns half-spread δ in price units.
|
||||
"""
|
||||
if tau < 0.01:
|
||||
tau = 0.01
|
||||
|
||||
sig2 = self.sigma**2
|
||||
gamma = self.gamma
|
||||
|
||||
# Base Guéant spread: σ²γτ/2 + (1/γ)log(1+γ/k)
|
||||
base_spread = gamma * sig2 * tau / 2.0
|
||||
|
||||
if gamma > 0 and self.k > 0:
|
||||
log_term = (1.0 / gamma) * math.log(1.0 + gamma / self.k)
|
||||
else:
|
||||
log_term = 0.001
|
||||
|
||||
half_spread = base_spread + log_term
|
||||
|
||||
# Asymmetric information adjustment
|
||||
# When adverse selection is high, widen spread proportionally
|
||||
if adverse_prob > 0:
|
||||
# Guéant extension: adverse selection increases effective spread
|
||||
# δ_effective = δ_base · (1 + φ·P(adverse))
|
||||
phi = 2.0 # Sensitivity to adverse selection
|
||||
half_spread *= (1.0 + phi * adverse_prob)
|
||||
|
||||
return max(half_spread, 0.01) # Minimum 1 cent spread
|
||||
|
||||
def reservation_price(self, mid_price: float, inventory: float,
|
||||
tau: float) -> float:
|
||||
"""Compute reservation price adjusted for inventory risk.
|
||||
|
||||
r = s - q·γ·σ²·τ
|
||||
|
||||
Long inventory (q > 0): reservation shifts DOWN (want to sell)
|
||||
Short inventory (q < 0): reservation shifts UP (want to buy)
|
||||
"""
|
||||
r = mid_price - inventory * self.gamma * self.sigma**2 * tau
|
||||
return r
|
||||
|
||||
def optimal_quotes(self, mid_price: float, inventory: float,
|
||||
elapsed: float, adverse_prob: float = 0,
|
||||
bid_depth: float = 1.0, ask_depth: float = 1.0) -> dict:
|
||||
"""Compute optimal bid and ask quotes.
|
||||
|
||||
Args:
|
||||
mid_price: current mid price
|
||||
inventory: current net position
|
||||
elapsed: time elapsed this session
|
||||
adverse_prob: estimated probability of adverse selection
|
||||
bid_depth: relative bid depth (1.0 = normal, >1 = deeper book)
|
||||
ask_depth: relative ask depth (1.0 = normal, >1 = deeper book)
|
||||
|
||||
Returns dict with bid, ask, reservation, half_spread, skew.
|
||||
"""
|
||||
tau = self.T - elapsed
|
||||
if tau < 0.01:
|
||||
tau = 0.01
|
||||
|
||||
r = self.reservation_price(mid_price, inventory, tau)
|
||||
spread = self.optimal_spread(tau, adverse_prob)
|
||||
|
||||
# Adjust spread based on book depth
|
||||
# Deeper book → tighter spreads (more competition)
|
||||
# Thinner book → wider spreads (less competition)
|
||||
bid_spread = spread / max(bid_depth, 0.5)
|
||||
ask_spread = spread / max(ask_depth, 0.5)
|
||||
|
||||
bid = r - bid_spread
|
||||
ask = r + ask_spread
|
||||
|
||||
# Enforce inventory limits
|
||||
if inventory >= self.max_pos:
|
||||
bid = 0 # Don't buy more
|
||||
if inventory <= -self.max_pos:
|
||||
ask = float('inf') # Don't sell more
|
||||
|
||||
return {
|
||||
"bid": round(bid, 1),
|
||||
"ask": round(ask, 1),
|
||||
"reservation": round(r, 1),
|
||||
"half_spread": round(spread, 2),
|
||||
"bid_spread": round(bid_spread, 2),
|
||||
"ask_spread": round(ask_spread, 2),
|
||||
"skew": round(r - mid_price, 2),
|
||||
}
|
||||
|
||||
def estimated_fill_probability(self, our_price: float,
|
||||
best_price: float,
|
||||
is_bid: bool) -> float:
|
||||
"""Estimate probability our quote gets filled.
|
||||
|
||||
Based on distance from best and queue position.
|
||||
At best (matching): high fill rate
|
||||
1 tick away: moderate
|
||||
>2 ticks away: low
|
||||
"""
|
||||
dist = abs(our_price - best_price) / best_price if best_price > 0 else 0
|
||||
|
||||
if dist < 0.0001: # At the best price level
|
||||
return 0.30 # ~30% chance per tick
|
||||
elif dist < 0.0005: # Within 1 tick
|
||||
return 0.10
|
||||
elif dist < 0.002: # Within 2 ticks
|
||||
return 0.03
|
||||
return 0.01
|
||||
Reference in New Issue
Block a user