Skip to content

v0.28.0

Latest

Choose a tag to compare

@keitaj keitaj released this 12 Jul 11:44
a005c27

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 oraclePx stream (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, resolved dex: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 the AdverseSelectionTracker) 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) reaches max_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. Default 0.0 (disabled).

  • Minimum SDK version assertion at startup (#155) — Validates the installed hyperliquid-python-sdk version before any hyperliquid.* import; below the required minimum (currently 0.23.0) the bot exits immediately with a clear remediation message instead of crashing later inside Exchange.__init__ with an opaque traceback. The minimum is a code-level invariant (MINIMUM_HYPERLIQUID_SDK_VERSION) bumped in lockstep with the pyproject.toml pin, 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 new OrderRejectionTracker classifies 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.py output — The balance snapshot now prints a local timestamp for easier log correlation.

Refactors / Observability

  • reason= tag on cancel_all_orders_for_coin log 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-sdk 0.23.0 → 0.24.0 (#157)
  • Bump numpy 2.4.5 → 2.4.6 (#154)
  • Bump the pip-dependencies group with 2 updates (#153)
  • Bump actions/checkout 6 → 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 (activeAssetCtx subscriptions 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 error level — only the additive [reject-summary] INFO line is new. Flip --rejection-log-level warning once the summary is trusted.
  • 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 the pyproject.toml pin (minimum 0.23.0).
  • Level 1 risk guardrails (risk_manager.py hardcoded 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 AdverseSelectionTracker is 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.