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:
+28
-5
@@ -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).
|
||||
|
||||
Reference in New Issue
Block a user