fix: pre-launch hardening — HYPE price feed, KOL wallet cleanup, Telegram Trump alert, rate limiting, brittle test
Batch of the pre-launch audit campaign (BUG-01…14 plus three new features): Pricing / TP-SL protection - Add app/services/hl_price_feed.py: supplemental HL allMids poller for HL-native assets (HYPE, PURR) not listed on Binance. Pumps price_store + tp_sl_monitor.on_price_tick so bot trades on these assets keep full stop-loss / take-profit / trailing protection instead of max-hold only. - Wire feed into main.py lifespan (startup task + graceful shutdown cancel). Telegram - Add format_trump_mention + PATH B in _dispatch: crypto-relevant Trump posts with no directional signal (relevant=True, signal=hold) now alert the public channel only (no per-subscriber noise). - Rate limiter (slowapi) on the API; assorted bot/digest fixes. KOL on-chain - seed_kol_wallets.py: KOL_FEEDS coverage cross-check; reversibly deactivate orphaned wallets (handle not in KOL_FEEDS → can never produce divergence) so the scanner stops burning cycles on them. Tests / misc - Fix brittle test_macro_ahr999_uses_same_formula_as_scanner: mock now uses realistic ms timestamps so the in-progress-day drop fires, matching the fetcher's bar count (was 0.3179 vs 0.3178 off-by-one). - Refresh stale notify_signal comment in truth_social.py. Frontend reduce-action type fix lives in the sibling repo. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
"""Supplemental price feed for HL-native assets not listed on Binance.
|
||||
|
||||
Polls HL allMids endpoint every 2 seconds and pumps price_store +
|
||||
tp_sl_monitor for assets in HL_PRICE_ASSETS. Runs alongside run_binance_ws().
|
||||
|
||||
WHY THIS EXISTS:
|
||||
tp_sl_monitor.on_price_tick(asset, price) is the sole mechanism that fires
|
||||
stop-loss, take-profit, and trailing-stop for live bot trades.
|
||||
HL-native assets (HYPE, PURR, …) are not listed on Binance, so the
|
||||
Binance WebSocket in binance.py cannot provide their prices.
|
||||
Without this feed, any bot trade on an HL-native asset silently loses ALL
|
||||
TP/SL protection — only the max_hold asyncio timer remains as an exit.
|
||||
|
||||
COVERAGE:
|
||||
• Binance WS → BTC, ETH, SOL, TRUMP, BNB, DOGE, LINK, AAVE (ASSET_MAP)
|
||||
• This feed → HYPE, PURR (HL_PRICE_ASSETS)
|
||||
|
||||
Adding a new HL-native asset:
|
||||
1. Add its symbol (uppercase) to HL_PRICE_ASSETS below.
|
||||
2. Verify the symbol appears in HL allMids by running:
|
||||
curl -s -X POST https://api.hyperliquid.xyz/info \\
|
||||
-H 'Content-Type: application/json' \\
|
||||
-d '{"type":"allMids"}' | python3 -m json.tool | grep HYPE
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
|
||||
from app.services.price_store import price_store
|
||||
from app.ws.manager import manager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
HL_API_URL = "https://api.hyperliquid.xyz/info"
|
||||
|
||||
# HL-native assets not covered by the Binance WS. Add new ones here as needed.
|
||||
HL_PRICE_ASSETS: frozenset[str] = frozenset({"HYPE", "PURR"})
|
||||
|
||||
# Poll cadence — 2 s gives ~same freshness as a Binance 1-min kline close
|
||||
# without hammering the free HL REST endpoint (30 req/min well within limit).
|
||||
_POLL_INTERVAL = 2.0
|
||||
|
||||
# Exponential backoff caps: start at 5 s, max at 60 s, reset on clean loop.
|
||||
_BACKOFF_START = 5
|
||||
_BACKOFF_MAX = 60
|
||||
|
||||
|
||||
async def run_hl_price_feed() -> None:
|
||||
"""Long-running loop. Retries with exponential backoff on connection failure.
|
||||
Designed to be created once as an asyncio task at startup and cancelled at
|
||||
shutdown — matches the run_binance_ws() lifecycle pattern."""
|
||||
logger.info("HL supplemental price feed starting for %s", sorted(HL_PRICE_ASSETS))
|
||||
backoff = _BACKOFF_START
|
||||
while True:
|
||||
try:
|
||||
await _poll_loop()
|
||||
except asyncio.CancelledError:
|
||||
logger.info("HL price feed cancelled — shutting down.")
|
||||
return
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"HL price feed outer error: %s — retrying in %ds", exc, backoff
|
||||
)
|
||||
await asyncio.sleep(backoff)
|
||||
backoff = min(backoff * 2, _BACKOFF_MAX)
|
||||
else:
|
||||
backoff = _BACKOFF_START # reset on clean exit (shouldn't happen normally)
|
||||
|
||||
|
||||
async def _poll_loop() -> None:
|
||||
"""Inner loop: tick every 2 s. Individual tick failures are swallowed so
|
||||
a transient HTTP 5xx doesn't break the whole loop."""
|
||||
while True:
|
||||
try:
|
||||
await _tick()
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception as exc:
|
||||
# Transient: network blip, HL rate limit, malformed JSON.
|
||||
logger.debug("HL price tick error (suppressed): %s", exc)
|
||||
await asyncio.sleep(_POLL_INTERVAL)
|
||||
|
||||
|
||||
async def _tick() -> None:
|
||||
"""Single price fetch + dispatch cycle for all HL_PRICE_ASSETS."""
|
||||
now_ms = int(time.time() * 1000)
|
||||
|
||||
async with httpx.AsyncClient(timeout=4.0) as c:
|
||||
r = await c.post(HL_API_URL, json={"type": "allMids"})
|
||||
r.raise_for_status()
|
||||
mids: dict = r.json() # {"BTC": "74541.0", "HYPE": "13.5", …}
|
||||
|
||||
for asset in HL_PRICE_ASSETS:
|
||||
raw: Optional[str] = mids.get(asset)
|
||||
if raw is None:
|
||||
# Asset not listed on HL yet — skip silently.
|
||||
continue
|
||||
try:
|
||||
price = float(raw)
|
||||
except (TypeError, ValueError):
|
||||
logger.warning("HL price feed: bad value for %s: %r", asset, raw)
|
||||
continue
|
||||
if price <= 0:
|
||||
continue
|
||||
|
||||
# Synthetic 1-minute candle aligned to the current minute bucket.
|
||||
# All OHLC set to the mid price (no spread data from allMids).
|
||||
# price_store.update() handles the same-bucket replace logic.
|
||||
candle = {
|
||||
"time": (now_ms // 60_000) * 60_000,
|
||||
"open": price,
|
||||
"high": price,
|
||||
"low": price,
|
||||
"close": price,
|
||||
"volume": 0.0,
|
||||
}
|
||||
price_store.update(asset, candle)
|
||||
|
||||
# TP/SL + trailing-stop evaluation on every tick.
|
||||
from app.services.tp_sl_monitor import on_price_tick
|
||||
on_price_tick(asset, price)
|
||||
|
||||
# Price-impact peak tracker for Trump-post windows.
|
||||
from app.services.price_impact_monitor import on_price_tick as pi_tick
|
||||
pi_tick(asset, price, price, price)
|
||||
|
||||
# Live price broadcast to the frontend dashboard.
|
||||
await manager.broadcast({
|
||||
"type": "price",
|
||||
"asset": asset,
|
||||
"price": price,
|
||||
"time": now_ms,
|
||||
})
|
||||
Reference in New Issue
Block a user