Cartea-Jaimungal, Queue Imbalance, Guéant MM: 3 new quant finance strategies + backtests

This commit is contained in:
ramseshk
2026-08-04 06:19:19 +00:00
parent 2c0750e355
commit 9768bf80cc
8 changed files with 11311 additions and 574 deletions
+175
View File
@@ -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