What's New
This release focuses on stale-quote defence and adverse-selection mitigation — an oracle-price guard, earlier close-order concession on toxic coins, and a per-coin entry cap — plus operational hardening (startup SDK-version gate, quieter logs, an ML dataset feed).
Features
-
Oracle divergence guard (#161) — Opt-in WebSocket guard that consumes the HIP-3
oraclePxstream (activeAssetCtx, ~3s cadence, each step capped at 1% of the previous value) as a defence against stale-quote sniping. It cancels entry orders only — close orders and the force-close path are never touched. Two gates: a divergence gate cancels the stale side's entry quotes when|book mid − oraclePx|exceeds a threshold (state-transition firing, rate-limited, re-arms only after returning to neutral), and a momentum gate cancels both sides and blocks placement for a short window when one oracle step pins near the 1% cap or N same-direction steps of a minimum size occur. Automatic market-close detection (a staleness TTL) disarms a coin whose oracle has stopped changing — e.g. equity perps outside US hours — so a frozen book is never mistaken for divergence; the guard arms only after observing a genuine value change while ticking and re-baselines on recovery. A placement-skip hook prevents requoting into the same stale band on the next cycle. Fail-safe by design: missing oracle data, missing mids, or parse failures leave behaviour identical to the guard being absent. Default disabled. -
Per-coin close tier overrides & toxicity-linked tier acceleration (#160) — Two mechanisms to concede close orders earlier on coins suffering heavy adverse selection, improving the odds of a maker exit before the expensive taker force-close fires (force-close and taker-fallback timing are unchanged).
coin_close_tier_overrides("COIN:BREAKEVEN/AGGRESSIVE,...") overrides the breakeven/aggressive tier-transition fractions per coin, resolveddex:coin→ bare name → global; values above the globals relax instead of tighten, so calmer coins keep a wider take-profit window.close_tier_toxicity_*(default OFF): while a coin's recent-window average markout (from theAdverseSelectionTracker) is at or below a threshold, its tier fractions are multiplied down (default 0.6×), bounded by a fraction floor, an absolute-seconds floor (close_tier_min_seconds), and multiplier validation (0.1–1.0). Fail-safe: any missing/unmatured/stale data falls through to base behaviour. -
Fill feature logging for ML dataset building (#159) — Persists per-fill order-book features (spread, book imbalance, micro-price skew, top-of-book sizes) plus the adverse-selection tracker's 5s/30s/60s markout samples as one JSON line per fill in a daily-rotated JSONL file — a ready-made supervised dataset for offline adverse-selection modelling. Feature definitions live in a single pure function (
fill_features.compute_fill_features) shared by the logger and any future in-bot inference, structurally preventing train/serve skew. Observation-only and fail-silent: the WebSocket thread performs no file IO (in-memory buffer, main-loop flush after a 65s maturity window so markout labels land in the same line); IO errors and size/buffer caps are counted and never affect trading. Default OFF. -
Per-coin entry-side position cap (#156) — Suppresses same-direction quotes once a coin's accumulated position value (
|size| × mid_price) reachesmax_position_multiple × effective_order_size_usd. Opposite-side entries continue to place so existing inventory can unwind through normal quoting. Guards against a position accumulating well above the configured order size and later closing in a single oversized maker order, exposing the bot to a large adverse fill under a hostile move during the hold. Default0.0(disabled). -
Minimum SDK version assertion at startup (#155) — Validates the installed
hyperliquid-python-sdkversion before anyhyperliquid.*import; below the required minimum (currently0.23.0) the bot exits immediately with a clear remediation message instead of crashing later insideExchange.__init__with an opaque traceback. The minimum is a code-level invariant (MINIMUM_HYPERLIQUID_SDK_VERSION) bumped in lockstep with thepyproject.tomlpin, with a test pinning the two together so they cannot drift. -
Post-only rejection log downgrade & 5-min aggregation (#151) — Routine post-only rejections (
Post only order would have immediately matched) are an expected retry signal under maker-only quoting but historically logged at ERROR, dominating the ERROR stream (~95% of records on maker-only deployments) and hiding real anomalies. A newOrderRejectionTrackerclassifies each rejection against a static routine-pattern map, makes the routine-match log level configurable (--rejection-log-level), and emits a 5-min aggregate[reject-summary]INFO line (--rejection-summary-interval). Default unchanged (rejection_log_level=error); unknown rejection text always surfaces at ERROR, so exchange-side format changes stay visible. -
Local timestamp in
check_balance.pyoutput — The balance snapshot now prints a local timestamp for easier log correlation.
Refactors / Observability
reason=tag oncancel_all_orders_for_coinlog line (#152) — The cancel-cleanup log line was hard-coded to(post-fill cleanup)regardless of caller, actively misleading on illiquid coins where the BBO-guard path dominates and zero fills occur. Each of the five callers now passes a short tag (fill/ws_fill/bbo_guard/drain/quiet_hour) that appears verbatim:Cancelled X/Y orders for COIN (reason=bbo_guard): [...]. Pure observability change — no behavioural difference in cancellation logic, and no new config.
Chores
- Bump
hyperliquid-python-sdk0.23.0 → 0.24.0 (#157) - Bump
numpy2.4.5 → 2.4.6 (#154) - Bump the pip-dependencies group with 2 updates (#153)
- Bump
actions/checkout6 → 7 (#158)
Upgrade Notes
- All changes are backward compatible — defaults preserve existing behaviour exactly:
- Oracle divergence guard is default-disabled. Without it, the WebSocket subscription set and strategy placement path are byte-identical to v0.27.0 (
activeAssetCtxsubscriptions are only established when a ctx listener is registered). - Toxicity-linked tier acceleration and fill feature logging are default-OFF; per-coin close tier overrides only apply to coins named in the override string. The entry-side position cap is inert at
max_position_multiple = 0.0. - Post-only rejection logging is byte-identical at the default
errorlevel — only the additive[reject-summary]INFO line is new. Flip--rejection-log-level warningonce the summary is trusted.
- Oracle divergence guard is default-disabled. Without it, the WebSocket subscription set and strategy placement path are byte-identical to v0.27.0 (
- SDK version gate: after
pip install ".[dev]", deployments running an out-of-date venv now fail fast at startup instead of crashing deep inside the SDK. Ensure your venv matches thepyproject.tomlpin (minimum0.23.0). - Level 1 risk guardrails (
risk_manager.pyhardcoded values) are untouched in this release.
Operator Checklist
- Oracle guard Phase A: enable on the most sniping-prone coins first; watch the periodic
[oracle-guard]summary and confirm the market-close disarm behaves as expected on any equity-hours-bounded coin before widening the roster. - Toxicity tier acceleration Phase A: turn on with the default 0.6× multiplier and confirm the
AdverseSelectionTrackeris wired (a startup warning fires if enabled without it); watch state-transition[close-tier]INFO lines and verify tiers only tighten, never loosen, relative to base. - Fill feature logging: enable on a low-volume coin first, confirm the daily JSONL rotates and the size/buffer caps hold, then widen. Purely a data feed — no trading impact.