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:
k
2026-05-29 11:57:19 +08:00
parent 6471e44aac
commit d6c802ef26
40 changed files with 1833 additions and 209 deletions
+16 -8
View File
@@ -112,12 +112,19 @@ async def _process_entry(entry: dict, db: AsyncSession) -> Optional[Post]:
analysis = await analyze_post(text)
asset = analysis["asset"]
# `tracked_asset`: the asset whose price impact we measure and display.
# Use target_asset (the perp we actually trade — may be SOL/TRUMP/etc.)
# when available; fall back to the sentiment asset (BTC/ETH) otherwise.
# Bug fix: previously always used `asset` (BTC/ETH), which measured the
# wrong price move when the bot traded a different perp.
tracked_asset = analysis.get("target_asset") or asset
# Only capture the price AT post time. The m5/m15/m1h peaks are filled in
# asynchronously by price_impact_monitor as the windows elapse — avoids
# recording 0.00% because future candles don't exist yet at entry time.
price_at_post = None
if asset and analysis["relevant"]:
price_at_post = price_store.get_price_at(asset, published_at)
if tracked_asset and analysis["relevant"]:
price_at_post = price_store.get_price_at(tracked_asset, published_at)
post = Post(
external_id=external_id,
@@ -131,8 +138,8 @@ async def _process_entry(entry: dict, db: AsyncSession) -> Optional[Post]:
prefilter_reason=analysis.get("prefilter_reason"),
analysis_version=analysis.get("analysis_version"),
relevant=analysis["relevant"],
# `asset` (BTC/ETH only) feeds the existing price_impact tracker.
price_impact_asset=asset if analysis["relevant"] else None,
# Track the actually-traded asset (target_asset ?? sentiment_asset).
price_impact_asset=tracked_asset if analysis["relevant"] else None,
price_impact_m5=None, # filled by price_impact_monitor after 5 m
price_impact_m15=None, # filled by price_impact_monitor after 15 m
price_impact_m1h=None, # filled by price_impact_monitor after 1 h
@@ -146,11 +153,11 @@ async def _process_entry(entry: dict, db: AsyncSession) -> Optional[Post]:
await db.flush()
# Register with the live peak tracker so it starts watching immediately.
if asset and analysis["relevant"] and price_at_post:
if tracked_asset and analysis["relevant"] and price_at_post:
from app.services.price_impact_monitor import register_post
register_post(
post_id=post.id,
asset=asset,
asset=tracked_asset,
signal=analysis.get("signal"),
entry_price=price_at_post,
published_at=published_at,
@@ -219,8 +226,9 @@ async def poll_truth_social(db_session_factory) -> None:
for post in new_posts:
await manager.broadcast(_post_to_ws_payload(post))
logger.info("Saved new post id=%d: %s", post.id, post.text[:60])
# Telegram fan-out (fire-and-forget). Only fires if
# signal is buy/short; noise posts are filtered inside.
# Telegram fan-out (fire-and-forget). _dispatch filters
# internally: buy/short → per-subscriber + public channel;
# relevant-but-hold → public channel only; noise → dropped.
try:
from app.services.telegram import notify_signal
notify_signal(post)