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
+28 -5
View File
@@ -3,8 +3,11 @@ import logging
from contextlib import asynccontextmanager
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.errors import RateLimitExceeded
from slowapi.util import get_remote_address
from app.config import settings
from app.database import AsyncSessionLocal, engine
@@ -12,6 +15,7 @@ from app.models import Base
from app.scrapers.truth_social import poll_truth_social, backfill_history
from app.scrapers.trumpstruth import poll_trumpstruth
from app.services.binance import run_binance_ws
from app.services.hl_price_feed import run_hl_price_feed
from app.ws.manager import manager
# Import all routers
@@ -37,14 +41,15 @@ logging.basicConfig(
logger = logging.getLogger(__name__)
from typing import Optional
_binance_task: Optional[asyncio.Task] = None
_telegram_task: Optional[asyncio.Task] = None
_scheduler: Optional[AsyncIOScheduler] = None
_binance_task: Optional[asyncio.Task] = None
_hl_price_task: Optional[asyncio.Task] = None
_telegram_task: Optional[asyncio.Task] = None
_scheduler: Optional[AsyncIOScheduler] = None
@asynccontextmanager
async def lifespan(app: FastAPI):
global _binance_task, _telegram_task, _scheduler
global _binance_task, _hl_price_task, _telegram_task, _scheduler
# 1. Dev convenience only. Production should rely on Alembic so schema
# ownership stays explicit and startup never mutates the DB implicitly.
@@ -67,6 +72,10 @@ async def lifespan(app: FastAPI):
_binance_task = asyncio.create_task(run_binance_ws(), name="binance_ws")
logger.info("Binance WebSocket task started.")
# 3b. Supplemental HL price feed for assets not on Binance (HYPE, PURR, …)
_hl_price_task = asyncio.create_task(run_hl_price_feed(), name="hl_price_feed")
logger.info("HL supplemental price feed task started.")
# 3. Start Truth Social poller via APScheduler
_scheduler = AsyncIOScheduler()
# Signal monitor — polls every 5 minutes
@@ -251,6 +260,12 @@ async def lifespan(app: FastAPI):
await _binance_task
except asyncio.CancelledError:
pass
if _hl_price_task and not _hl_price_task.done():
_hl_price_task.cancel()
try:
await _hl_price_task
except asyncio.CancelledError:
pass
if _telegram_task and not _telegram_task.done():
_telegram_task.cancel()
try:
@@ -261,12 +276,20 @@ async def lifespan(app: FastAPI):
logger.info("Shutdown complete.")
# Rate limiter — keyed by client IP (X-Forwarded-For → remote_addr fallback).
# Public read endpoints: 60 req/min. Signed mutations: 20 req/min (enforced
# per-route). Limits are generous enough for normal use but block scrapers
# and accidental polling loops.
limiter = Limiter(key_func=get_remote_address, default_limits=["60/minute"])
app = FastAPI(
title="TrumpSignal API",
version="1.0.0",
description="Crypto trading signals derived from Trump's Truth Social posts.",
lifespan=lifespan,
)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
# CORS
# In production we only allow the canonical frontend origin (FRONTEND_URL).