feat: advanced microstructure — sequencer latency, dealer GEX, tick regime, triangular arb

4 new modules with 20 tests:

#22 Sequencer Latency Detector (live/monitors/sequencer_latency.py):
  Detects stale-state windows between WebSocket and REST API.
  - WebSocket vs REST timestamp delta tracking
  - Transport latency percentiles (p50, p99)
  - Liquidation-triggered stale-state detection
  - Stale asset identification for cross-margin arbitrage

#25 Dealer GEX (microstructure/dealer_gex.py):
  Dealer Gamma Exposure estimation via Black-Scholes.
  - Per-strike gamma × OI × spot² GEX computation
  - Pin level detection (strikes where dealers are long gamma)
  - Net GEX aggregation
  - Signal: fade_breakout (long gamma pinning) vs ride_momentum
    (short gamma amplification)
  - nearest_pin() for distance-to-magnet calculation

#30 Tick-Size Regime Exploitation (microstructure/tick_regime.py):
  Detects when asset price approaches tick-size boundaries.
  - Hyperliquid tick schedule (BTC 0.1/0.5, ETH 0.01/0.05, SOL 0.001/0.005)
  - Boundary approach detection with configurable threshold
  - Linear trend estimation for expected bars-to-cross
  - Signal: widen_quotes or tighten_quotes with urgency classification
  - Per-coin state tracking

#33 Triangular Latency Arb (live/monitors/triangular_arb.py):
  Cross-venue A→B→C triangular arbitrage detection.
  - Internal triangular: BTC-USDT → ETH-BTC → ETH-USDT
  - Cross-venue: price discrepancy across slow/fast venues
  - Latency gap detection between venue pairs
  - Implied cross-rate computation vs direct quote
  - Minimum spread threshold gating
This commit is contained in:
ramseshk
2026-08-10 10:34:56 +08:00
parent 27c096dc9e
commit e0be9f4d40
5 changed files with 937 additions and 0 deletions
+179
View File
@@ -0,0 +1,179 @@
"""
Dealer Gamma Exposure (GEX) estimation and pinning/fading signals.
In options markets, dealers delta-hedge their portfolios. Their gamma
exposure determines whether they amplify or suppress price movements:
- Long gamma → dealers buy low, sell high → suppress vol, "pin" price
- Short gamma → dealers buy high, sell low → amplify vol, create gamma squeeze
GEX = Σ (gamma_per_contract × open_interest × spot_price)
For crypto (no options on Hyperliquid yet), we approximate GEX using:
- BTC/ETH options open interest from Deribit
- Estimated dealer gamma via Black-Scholes
- Pin levels at strikes with max GEX
Strategy: when dealer GEX is heavily positive at a strike, fade breakouts.
When GEX is negative, ride the momentum with the dealers.
"""
from __future__ import annotations
import math
from collections import defaultdict
from typing import Optional
class DealerGEX:
"""Estimate dealer gamma exposure and generate pinning/fading signals.
Uses option open interest data and Black-Scholes gamma to compute
per-strike GEX. Aggregates to net GEX and identifies pin levels.
Usage:
gex = DealerGEX(spot=64500, risk_free=0.05)
gex.add_strike(strike=65000, call_oi=500, put_oi=300, expiry_days=7, iv=0.60)
print(gex.pin_levels())
print(gex.signal())
"""
def __init__(
self,
spot: float = 0.0,
risk_free: float = 0.05,
pin_threshold: float = 0.01, # 1% of spot to consider "at pin level"
):
self._spot = spot
self._rf = risk_free
self._pin_threshold = pin_threshold
self._strikes: list[dict] = [] # [{strike, call_oi, put_oi, gamma, gex_usd, ...}]
self._net_gex: float = 0.0
self._pin_levels: list[dict] = []
# ── Data feed ────────────────────────────────────────────
def set_spot(self, spot: float):
self._spot = spot
def add_strike(
self,
strike: float,
call_oi: float = 0,
put_oi: float = 0,
expiry_days: float = 30,
iv: float = 0.50,
):
"""Add OI data at a specific strike."""
gamma_call = self._bs_gamma(strike, "call", expiry_days, iv)
gamma_put = self._bs_gamma(strike, "put", expiry_days, iv)
# GEX per strike = gamma × OI × spot² × 0.01 (scaling)
gex_call = gamma_call * call_oi * self._spot * self._spot * 0.01
gex_put = gamma_put * put_oi * self._spot * self._spot * 0.01
self._strikes.append({
"strike": strike,
"call_oi": call_oi,
"put_oi": put_oi,
"gamma_call": round(gamma_call, 8),
"gamma_put": round(gamma_put, 8),
"gex_call_usd": round(gex_call, 2),
"gex_put_usd": round(gex_put, 2),
"gex_total_usd": round(gex_call + gex_put, 2),
})
def compute(self):
"""Aggregate GEX and find pin levels."""
self._net_gex = sum(s["gex_total_usd"] for s in self._strikes)
# Find pin levels: strikes where GEX is highest (magnets)
# and is positive (dealers long gamma = pinning)
if self._strikes:
max_gex = max(abs(s["gex_total_usd"]) for s in self._strikes)
self._pin_levels = [
{"strike": s["strike"], "gex_usd": s["gex_total_usd"], "is_pin": s["gex_total_usd"] > 0}
for s in self._strikes if abs(s["gex_total_usd"]) > max_gex * 0.3
]
self._pin_levels.sort(key=lambda l: abs(l["gex_usd"]), reverse=True)
# ── Signals ─────────────────────────────────────────────
def nearest_pin(self) -> dict | None:
"""Find the nearest pin level to current spot."""
if not self._spot or not self._strikes:
return None
distances = []
for s in self._strikes:
dist_pct = abs(s["strike"] - self._spot) / self._spot
if s["gex_total_usd"] > 0: # only positive GEX acts as pin
distances.append({
"strike": s["strike"],
"distance_pct": round(dist_pct * 100, 2),
"gex_usd": s["gex_total_usd"],
"strength": "strong" if s["gex_total_usd"] > abs(self._net_gex) * 0.1 else "weak",
})
if not distances:
return None
return min(distances, key=lambda d: d["distance_pct"])
def signal(self) -> dict:
"""Generate trading signal based on dealer GEX.
Returns:
signal: "fade_breakout", "ride_momentum", "neutral"
reason: explanation
"""
self.compute()
if not self._spot:
return {"signal": "neutral", "reason": "no_spot"}
pin = self.nearest_pin()
dist_pct = pin["distance_pct"] if pin else 100.0
if self._net_gex > 1e6 and pin and dist_pct < self._pin_threshold * 100:
return {
"signal": "fade_breakout",
"reason": f"Dealers long gamma ${self._net_gex:,.0f} — pinning at ${pin['strike']:,.0f} ({dist_pct:.1f}%)",
"net_gex_usd": round(self._net_gex, 2),
"pin_strike": pin["strike"],
"direction": "mean_reversion",
}
elif self._net_gex < -1e6:
return {
"signal": "ride_momentum",
"reason": f"Dealers short gamma ${self._net_gex:,.0f} — amplifying moves",
"net_gex_usd": round(self._net_gex, 2),
"direction": "trend_following",
}
else:
return {"signal": "neutral", "reason": "balanced_gex", "net_gex_usd": round(self._net_gex, 2)}
# ── Black-Scholes gamma ─────────────────────────────────
def _bs_gamma(self, strike: float, opt_type: str, T_days: float, sigma: float) -> float:
"""Black-Scholes gamma for a European option."""
if self._spot <= 0 or strike <= 0 or T_days <= 0 or sigma <= 0:
return 0.0
T = T_days / 365.0
d1 = (math.log(self._spot / strike) + (self._rf + 0.5 * sigma * sigma) * T) / (sigma * math.sqrt(T))
phi = math.exp(-d1 * d1 / 2.0) / math.sqrt(2.0 * math.pi)
return phi / (self._spot * sigma * math.sqrt(T))
# ── Summary ─────────────────────────────────────────────
def summary(self) -> dict:
pin = self.nearest_pin()
return {
"spot": self._spot,
"net_gex_usd": round(self._net_gex, 2),
"net_gex_m": round(self._net_gex / 1e6, 2),
"strikes_tracked": len(self._strikes),
"pin_levels": self._pin_levels[:5],
"nearest_pin": pin,
"signal": self.signal(),
}
+191
View File
@@ -0,0 +1,191 @@
"""
Tick-size and lot-size regime exploitation.
Exchanges dynamically adjust minimum price increments (tick size)
and minimum order sizes (lot size) based on asset price. When an
asset crosses these thresholds, spreads widen or narrow instantly.
Hyperliquid tick sizes (example — actual values from exchange):
BTC: 0.1 USD tick (<$100K), 0.5 USD tick ($100K-$1M) — approximate
ETH: 0.01 USD tick
SOL: 0.001 USD tick
Strategy: when price crosses a tick-size boundary, be the first to adjust
your quoting engine. Competitors quoting at old tick sizes will be either
non-competitive (too wide) or illegal (too tight). Capture the spread gap.
"""
from __future__ import annotations
from collections import deque
from typing import Optional
# Hyperliquid tick size schedule (example values — confirm from exchange docs)
TICK_SCHEDULE = {
"BTC": [
(float("-inf"), 100000, 0.1),
(100000, float("inf"), 0.5),
],
"ETH": [
(float("-inf"), 10000, 0.01),
(10000, float("inf"), 0.05),
],
"SOL": [
(float("-inf"), 1000, 0.001),
(1000, float("inf"), 0.005),
],
# Default for unknown coins
"DEFAULT": [
(float("-inf"), float("inf"), 0.01),
],
}
class TickRegimeMonitor:
"""Monitor and exploit tick-size regime changes.
Tracks when an asset price approaches a tick-size boundary
and generates signals to adjust quoting before competitors.
"""
def __init__(
self,
approach_threshold_pct: float = 1.0, # 1% from boundary
history_window: int = 100,
):
self._approach_threshold = approach_threshold_pct / 100
self._prices: dict[str, deque[float]] = {}
self._current_tick: dict[str, float] = {}
self._approaching_boundary: dict[str, dict] = {}
self._window = history_window
# ── Data feed ────────────────────────────────────────────
def get_tick_size(self, coin: str, price: float) -> float:
"""Get current tick size for a coin at given price."""
schedule = TICK_SCHEDULE.get(coin.upper(), TICK_SCHEDULE["DEFAULT"])
for lo, hi, tick in schedule:
if lo <= price < hi:
return tick
return schedule[-1][2] # fallback
def update_price(self, coin: str, price: float):
"""Feed a new price observation."""
c = coin.upper()
self._prices.setdefault(c, deque(maxlen=self._window)).append(price)
self._current_tick[c] = self.get_tick_size(c, price)
# Check if approaching boundary
schedule = TICK_SCHEDULE.get(c, TICK_SCHEDULE["DEFAULT"])
for lo, hi, tick in schedule:
if lo <= price < hi:
# Check approach to upper boundary
if hi != float("inf"):
dist_up = (hi - price) / price
if 0 < dist_up < self._approach_threshold:
next_tick = self.get_tick_size(c, hi)
if next_tick != tick:
self._approaching_boundary[c] = {
"direction": "up",
"current_tick": tick,
"next_tick": next_tick,
"boundary_price": hi,
"distance_pct": round(dist_up * 100, 2),
"crosses_in_bars": self._estimate_cross_bars(c, hi),
}
return
# Check approach to lower boundary
if lo != float("-inf"):
dist_down = (price - lo) / price
if 0 < dist_down < self._approach_threshold:
prev_tick = self.get_tick_size(c, lo - 1)
if prev_tick != tick:
self._approaching_boundary[c] = {
"direction": "down",
"current_tick": tick,
"next_tick": prev_tick,
"boundary_price": lo,
"distance_pct": round(dist_down * 100, 2),
"crosses_in_bars": self._estimate_cross_bars(c, lo),
}
return
self._approaching_boundary.pop(c, None)
def _estimate_cross_bars(self, coin: str, boundary_price: float) -> int:
"""Estimate how many bars until price crosses the boundary."""
prices = list(self._prices.get(coin, []))
if len(prices) < 5:
return -1
recent = prices[-min(20, len(prices)):]
if len(recent) < 5:
return -1
# Simple linear trend estimate
xs = list(range(len(recent)))
n = len(xs)
sum_x = sum(xs)
sum_y = sum(recent)
sum_xy = sum(x * y for x, y in zip(xs, recent))
sum_x2 = sum(x * x for x in xs)
slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x) if (n * sum_x2 - sum_x * sum_x) != 0 else 0
last_price = recent[-1]
if abs(slope) < 1e-10:
return -1
bars_to_cross = int((boundary_price - last_price) / slope)
return max(0, bars_to_cross) if bars_to_cross > 0 else -1
# ── Signal ──────────────────────────────────────────────
def signal(self, coin: str) -> dict:
"""Generate tick-size regime signal.
Returns action to take before competitors adjust.
"""
c = coin.upper()
boundary = self._approaching_boundary.get(c)
current_tick = self._current_tick.get(c, 0.01)
if not boundary:
return {
"action": "none",
"current_tick": current_tick,
"reason": "no_regime_change",
}
tick_delta = boundary["next_tick"] - boundary["current_tick"]
bars = boundary["crosses_in_bars"]
if tick_delta > 0:
# Tick size INCREASING → spread will widen → HFTs will post wider quotes
# Be the first to widen to the new tick
action = "widen_quotes"
reason = f"Tick increasing {boundary['current_tick']}→{boundary['next_tick']} "
reason += f"({boundary['distance_pct']}% away" + (f", ~{bars} bars" if bars > 0 else "") + ")"
else:
# Tick size DECREASING → spread will narrow → HFTs will tighten
# Be the first to tighten to the new tick
action = "tighten_quotes"
reason = f"Tick decreasing {boundary['current_tick']}→{boundary['next_tick']} "
reason += f"({boundary['distance_pct']}% away" + (f", ~{bars} bars" if bars > 0 else "") + ")"
return {
"action": action,
"current_tick": boundary["current_tick"],
"next_tick": boundary["next_tick"],
"tick_delta": round(tick_delta, 5),
"boundary_price": boundary["boundary_price"],
"distance_pct": boundary["distance_pct"],
"estimated_bars_to_cross": bars,
"reason": reason,
"urgent": boundary["distance_pct"] < 0.5, # within 0.5% = urgent
}
def summary(self) -> dict:
return {coin: self.signal(coin) for coin in self._prices}