feat: advanced microstructure modules — HLP, Hawkes, Whipsaw, Term Structure, Liq Waterfall, Spoof Detector
6 new modules with 46 new tests (230 total): #21 HLP Vault Monitor (live/monitors/hlp_vault.py): Tracks Hyperliquid's native protocol market maker at address 0xfefefe... Queries clearinghouseState + metaAndAssetCtxs. - Delta exposure per asset (notional + PnL) - Overextension detection (notional exceeds M threshold) - Rebalancing signals: fade_short when HLP too short, fade_long when HLP too long (front-run forced rebalancing) - Toxicity score: HLP losing money = absorbing informed flow - Historical delta tracking #29 Hawkes Processes (microstructure/hawkes.py): Multivariate Hawkes calibrator for limit order book dynamics. - MLE calibration via SGD gradient descent on log-likelihood - Branching ratio enforcement (alpha/beta < 0.99 for stationarity) - Intensity computation λ_i(t) with cross-excitation - Activity forecasting (expected event count in horizon) - Synthetic event generator (Ogata thinning) - Pure functions: hawkes_intensity, hawkes_log_likelihood, generate_hawkes_events #23 Funding Whipsaw Trader (live/strategies/funding_whipsaw.py): Premium index decay trading in final 60s of funding epoch. - Detects deterministic convergence of premium→0 at settlement - Time-scaled position sizing (larger closer to settlement) - Auto-close after funding epoch completes - Confidence scoring based on premium magnitude #32 Term Structure Monitor (live/monitors/term_structure.py): Perp/quarterly/bi-quarterly futures basis curve trading. - Quarterly-perp basis with z-score anomaly detection - BiQ-quarterly curve steepness monitoring - Fair quarterly price via interest rate parity + funding carry - Calendar spread signals: buy_basis, sell_basis, curve_steepener, curve_flattener #24 Liquidation Waterfall (live/monitors/liq_waterfall.py): Cross-margin liquidation order prediction. - Margin ratio tracking (equity / maintenance margin) - Danger/critical level classification - Asset liquidation priority: maintenance / book_liquidity ratio (least liquid asset relative to margin = dumped first) - Strategy output: widen_spreads on target, tighten on rest #31 Spoof Detector (microstructure/spoof_detector.py): Adversarial ML-style spoofing pattern recognition. - Rule 1: Large order far from mid, cancelled immediately - Rule 2: Cancel right before trade approaches price level - Rule 3: Oversized order with no fill within short lifetime - Spoof probability (rolling window ratio) - Cancel-to-fill ratio monitoring
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
"""
|
||||
Funding rate whipsaw trader — premium index decay in final seconds of funding epoch.
|
||||
|
||||
Hyperliquid funding settles every 8 hours (UTC 00:00, 08:00, 16:00).
|
||||
In the final 60 seconds before settlement, the premium index (Mark - Oracle)
|
||||
must converge to prevent arbitrage. HFTs trade this convergence deterministically.
|
||||
|
||||
The strategy:
|
||||
- If premium is positive with <60s until funding → SHORT perp (price will drop)
|
||||
- If premium is negative with <60s until funding → LONG perp (price will rise)
|
||||
- Scale position based on premium magnitude and time remaining
|
||||
- Close position at funding settlement (T+0)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class FundingWhipsawTrader:
|
||||
"""Trade the deterministic decay of the premium index in the final seconds
|
||||
before Hyperliquid's funding settlement.
|
||||
|
||||
Funding epochs: 00:00, 08:00, 16:00 UTC every day.
|
||||
Premium index = (mark_price - oracle_price) / oracle_price
|
||||
|
||||
Usage:
|
||||
trader = FundingWhipsawTrader()
|
||||
trader.update(mark_px=64500, oracle_px=64480)
|
||||
signal = trader.signal()
|
||||
if signal['action'] != 'none':
|
||||
# place order: signal['side'], signal['size'], signal['confidence']
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
min_premium_bps: float = 0.5, # minimum premium in bps to trigger
|
||||
max_size: float = 0.001, # max position size
|
||||
enter_seconds_before: float = 60.0, # seconds before funding to enter
|
||||
close_seconds_after: float = 5.0, # seconds after funding to close
|
||||
):
|
||||
self._min_premium_bps = min_premium_bps
|
||||
self._max_size = max_size
|
||||
self._enter_seconds = enter_seconds_before
|
||||
self._close_seconds = close_seconds_after
|
||||
|
||||
self._mark_px: float = 0.0
|
||||
self._oracle_px: float = 0.0
|
||||
self._premium_bps: float = 0.0
|
||||
self._last_update: float = 0.0
|
||||
self._in_position: bool = False
|
||||
self._position_side: str = ""
|
||||
self._entry_time: float = 0.0
|
||||
|
||||
def update(self, mark_px: float, oracle_px: float):
|
||||
"""Feed current mark and oracle prices."""
|
||||
self._mark_px = mark_px
|
||||
self._oracle_px = oracle_px
|
||||
self._last_update = time.time()
|
||||
|
||||
if oracle_px > 0:
|
||||
self._premium_bps = (mark_px - oracle_px) / oracle_px * 10000
|
||||
|
||||
def seconds_to_funding(self) -> float:
|
||||
"""Seconds until the next funding settlement (every 8 hours, UTC)."""
|
||||
now = datetime.now(timezone.utc)
|
||||
epoch_hours = [0, 8, 16] # Funding at 00:00, 08:00, 16:00 UTC
|
||||
current_hour = now.hour
|
||||
|
||||
# Find next funding epoch
|
||||
next_epoch_hour = None
|
||||
for h in epoch_hours:
|
||||
if h > current_hour or (h == current_hour and now.minute == 0 and now.second < 5):
|
||||
next_epoch_hour = h
|
||||
break
|
||||
|
||||
if next_epoch_hour is None:
|
||||
# After 16:00, next is 00:00 tomorrow
|
||||
next_epoch = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
from datetime import timedelta
|
||||
next_epoch += timedelta(days=1)
|
||||
else:
|
||||
next_epoch = now.replace(hour=next_epoch_hour, minute=0, second=0, microsecond=0)
|
||||
|
||||
delta = (next_epoch - now).total_seconds()
|
||||
return max(0.0, delta)
|
||||
|
||||
def seconds_since_funding(self) -> float:
|
||||
"""Seconds since the most recent funding settlement."""
|
||||
seconds_to = self.seconds_to_funding()
|
||||
if seconds_to < 3600: # <1 hour to next
|
||||
return 8 * 3600 - seconds_to
|
||||
return 8 * 3600 + (3600 - seconds_to % 3600) # Approximate
|
||||
|
||||
def signal(self) -> dict:
|
||||
"""Generate trading signal based on premium and time to funding.
|
||||
|
||||
Returns:
|
||||
action: "enter_long", "enter_short", "close", "none"
|
||||
side: "buy" or "sell" (for orders)
|
||||
size: position size (scaled by time remaining)
|
||||
confidence: 0-1 confidence in the signal
|
||||
premium_bps: current premium in bps
|
||||
seconds_to_funding: time until settlement
|
||||
"""
|
||||
secs = self.seconds_to_funding()
|
||||
premium = self._premium_bps
|
||||
|
||||
# After funding + small delay: close any position
|
||||
secs_since = self.seconds_since_funding()
|
||||
if self._in_position and secs_since < self._close_seconds:
|
||||
self._in_position = False
|
||||
return {
|
||||
"action": "close",
|
||||
"side": "sell" if self._position_side == "buy" else "buy",
|
||||
"size": self._max_size,
|
||||
"confidence": 1.0,
|
||||
"premium_bps": round(premium, 2),
|
||||
"seconds_to_funding": round(secs, 1),
|
||||
"reason": "funding_settled",
|
||||
}
|
||||
|
||||
# Outside entry window: no action
|
||||
if secs > self._enter_seconds or secs < 0:
|
||||
return {
|
||||
"action": "none",
|
||||
"side": "",
|
||||
"size": 0.0,
|
||||
"confidence": 0.0,
|
||||
"premium_bps": round(premium, 2),
|
||||
"seconds_to_funding": round(secs, 1),
|
||||
"reason": "outside_entry_window",
|
||||
}
|
||||
|
||||
# Already in position
|
||||
if self._in_position:
|
||||
return {
|
||||
"action": "hold",
|
||||
"side": self._position_side,
|
||||
"size": self._max_size,
|
||||
"confidence": 0.8,
|
||||
"premium_bps": round(premium, 2),
|
||||
"seconds_to_funding": round(secs, 1),
|
||||
"reason": "holding",
|
||||
}
|
||||
|
||||
# Check premium threshold
|
||||
if abs(premium) < self._min_premium_bps:
|
||||
return {
|
||||
"action": "none",
|
||||
"side": "",
|
||||
"size": 0.0,
|
||||
"confidence": 0.0,
|
||||
"premium_bps": round(premium, 2),
|
||||
"seconds_to_funding": round(secs, 1),
|
||||
"reason": "premium_too_small",
|
||||
}
|
||||
|
||||
# Scale size by time remaining (more remaining = more uncertainty = smaller size)
|
||||
time_factor = max(0.3, secs / self._enter_seconds)
|
||||
scaled_size = self._max_size * (1.0 - time_factor * 0.5)
|
||||
|
||||
if premium > 0:
|
||||
# Premium positive → perp is expensive → short it
|
||||
side = "sell"
|
||||
action = "enter_short"
|
||||
confidence = min(1.0, abs(premium) / self._min_premium_bps * 0.3)
|
||||
else:
|
||||
# Premium negative → perp is cheap → long it
|
||||
side = "buy"
|
||||
action = "enter_long"
|
||||
confidence = min(1.0, abs(premium) / self._min_premium_bps * 0.3)
|
||||
|
||||
self._in_position = True
|
||||
self._position_side = side
|
||||
self._entry_time = time.time()
|
||||
|
||||
return {
|
||||
"action": action,
|
||||
"side": side,
|
||||
"size": round(scaled_size, 8),
|
||||
"confidence": round(confidence, 3),
|
||||
"premium_bps": round(premium, 2),
|
||||
"seconds_to_funding": round(secs, 1),
|
||||
"reason": f"premium_{premium:.1f}bps_{secs:.0f}s",
|
||||
}
|
||||
|
||||
@property
|
||||
def premium_bps(self) -> float:
|
||||
return self._premium_bps
|
||||
|
||||
@property
|
||||
def in_position(self) -> bool:
|
||||
return self._in_position
|
||||
Reference in New Issue
Block a user