Proper A-S: side selection via reservation price (not spread formula)

The AS optimal spread formula gives absurd spreads at crypto scale.
Real market makers quote at the MARKET spread (best bid/ask) and use
AS to decide WHEN to quote based on inventory-adjusted fair value:
  r = s - q * gamma * sigma^2 * tau

If r < best_bid (long-biased) → stop quoting bid
If r > best_ask (short-biased) → stop quoting ask
If circuit breaker active → pause both sides

Decoupled: spread is market-driven, inventory skew is AS-driven.
This commit is contained in:
ramseshk
2026-08-06 08:04:49 +00:00
parent a5de7d526f
commit f9bed72b1c
2 changed files with 102 additions and 106 deletions
+34 -33
View File
@@ -425,48 +425,49 @@ async def main():
if has_position:
continue # Don't replace existing orders
# Avellaneda-Stoikov: proper optimal control (reservation price + spread)
# Avellaneda-Stoikov: side selection via reservation price
if name == "Avellaneda-Stoikov":
try:
from strategies.as_quoter import ASQuoter
if "_as_quoter" not in dir():
globals()["_as_quoter"] = ASQuoter(
gamma=0.1, k=1.5, tau=1.0,
min_spread=0.0001, max_inventory=cfg["size"] * 5,
)
q = ASQuoter
asq = globals()["_as_quoter"]
asq.observe(mid)
from strategies.as_quoter import ASMarketMaker
if "_as_mm" not in dir():
globals()["_as_mm"] = ASMarketMaker(gamma=0.1, tau=1.0, max_inventory=cfg["size"] * 10)
asmm = globals()["_as_mm"]
asmm.observe(mid)
# Get A-S inventory from position tracking
as_inv = STRATEGIES[name].get("position", 0.0)
elapsed = (tick * 1.0) % (asq.tau * 3600) / 3600.0 # 1-hour virtual sessions
elapsed = (tick * 1.0) % (asmm.tau * 3600) / 3600.0
result = asq.quotes(mid, as_inv, elapsed)
if result is None:
continue # Circuit breaker active — skip this tick
selection = asmm.should_quote(mid, bid, ask, as_inv, elapsed)
quote_bid = selection["quote_bid"]
quote_ask = selection["quote_ask"]
r_price = selection.get("reservation", mid)
r_price = result["reservation"]
as_bid = int(result["bid"])
as_ask = int(result["ask"])
# Clamp: never cross the market
as_bid = min(as_bid, int(bid))
as_ask = max(as_ask, int(ask))
# Quote selected sides at best bid/ask
if quote_bid:
cid_bid = ClientOrderId(str(UUID4()))
try:
client.submit_order(instrument_id=perp.id, client_order_id=cid_bid, order_side=OrderSide.BUY, order_type=OrderType.LIMIT, quantity=Quantity.from_str(str(cfg["size"])), price=Price.from_str(str(int(bid))), time_in_force=TimeInForce.GTC, post_only=True)
active_cloids[name + "_bid"] = str(cid_bid)
active_cloids_times[name + "_bid"] = tick
active_cloids_px[name + "_bid"] = bid
except Exception:
pass
if quote_ask:
cid_ask = ClientOrderId(str(UUID4()))
try:
client.submit_order(instrument_id=perp.id, client_order_id=cid_ask, order_side=OrderSide.SELL, order_type=OrderType.LIMIT, quantity=Quantity.from_str(str(cfg["size"])), price=Price.from_str(str(int(ask))), time_in_force=TimeInForce.GTC, post_only=True)
active_cloids[name + "_ask"] = str(cid_ask)
active_cloids_times[name + "_ask"] = tick
active_cloids_px[name + "_ask"] = ask
except Exception:
pass
cid_bid = ClientOrderId(str(UUID4()))
cid_ask = ClientOrderId(str(UUID4()))
try:
client.submit_order(instrument_id=perp.id, client_order_id=cid_bid, order_side=OrderSide.BUY, order_type=OrderType.LIMIT, quantity=Quantity.from_str(str(cfg["size"])), price=Price.from_str(str(as_bid)), time_in_force=TimeInForce.GTC, post_only=True)
client.submit_order(instrument_id=perp.id, client_order_id=cid_ask, order_side=OrderSide.SELL, order_type=OrderType.LIMIT, quantity=Quantity.from_str(str(cfg["size"])), price=Price.from_str(str(as_ask)), time_in_force=TimeInForce.GTC, post_only=True)
if tick % 60 == 0:
log.info(f"[AS] r={r_price:.1f} σ={asq.sigma*100:.2f}% BID {cfg['size']} @ ${as_bid:,} | ASK {cfg['size']} @ ${as_ask:,} (spread ${as_ask - as_bid:,})")
active_cloids[name] = str(cid_bid)
active_cloids_times[name] = tick
active_cloids_px[name] = as_bid
except Exception:
pass
if tick % 60 == 0 and (quote_bid or quote_ask):
sides = ("BID" if quote_bid else "") + ("|" if quote_bid and quote_ask else "") + ("ASK" if quote_ask else "")
log.info(f"[AS] r={r_price:.1f} σ={selection.get('sigma',0)*100:.2f}% q={as_inv:.6f} {sides}")
except Exception:
# Fallback: best bid/ask if module unavailable
# Fallback: best bid/ask both sides
cid_bid = ClientOrderId(str(UUID4()))
cid_ask = ClientOrderId(str(UUID4()))
try: