diff --git a/app/api/performance.py b/app/api/performance.py index e5cc81b..058094e 100644 --- a/app/api/performance.py +++ b/app/api/performance.py @@ -36,12 +36,19 @@ async def get_performance( ) since = datetime.now(timezone.utc).replace(tzinfo=None) - timedelta(days=PERIOD_DAYS) + # Period basis is closed_at (realized-PnL-in-window), NOT opened_at. This + # matches how the frontend Analytics page filters every time window, so the + # 30d numbers shown there (and on the dashboard tile) use one consistent + # basis. Ordering by closed_at also makes the drawdown equity curve reflect + # realization order. A trade opened before the window but closed inside it + # correctly counts; one opened inside but still open does not (closed_at IS + # NOT NULL already excludes it). result = await db.execute( select(BotTrade) .where(BotTrade.wallet_address == wallet) .where(BotTrade.closed_at.is_not(None)) - .where(BotTrade.opened_at >= since) - .order_by(BotTrade.opened_at.asc()) + .where(BotTrade.closed_at >= since) + .order_by(BotTrade.closed_at.asc()) ) trades = result.scalars().all() diff --git a/app/api/posts.py b/app/api/posts.py index 0d4be94..9bf3515 100644 --- a/app/api/posts.py +++ b/app/api/posts.py @@ -74,13 +74,21 @@ async def get_posts( request: Request, limit: int = Query(default=20, ge=1, le=500), page: int = Query(default=1, ge=1), + source: Optional[str] = Query( + default=None, + description="Filter to a single source (e.g. 'btc_bottom_reversal', " + "'funding_reversal', 'truth'). Without it, rare-source " + "signals can be pushed off the latest-N page by Trump posts.", + ), db: AsyncSession = Depends(get_db), response: Response = None, ): offset = (page - 1) * limit - result = await db.execute( - select(Post).order_by(Post.published_at.desc()).offset(offset).limit(limit) - ) + stmt = select(Post) + if source: + stmt = stmt.where(Post.source == source) + stmt = stmt.order_by(Post.published_at.desc()).offset(offset).limit(limit) + result = await db.execute(stmt) posts = result.scalars().all() # Posts are scraped every 5s but rarely change once written — allow CDN/browser # to cache for 30s. stale-while-revalidate=60 means stale content is served diff --git a/app/services/scanners/funding_reversal.py b/app/services/scanners/funding_reversal.py index 59b39e5..4f26fbd 100644 --- a/app/services/scanners/funding_reversal.py +++ b/app/services/scanners/funding_reversal.py @@ -367,6 +367,10 @@ async def get_current_snapshot() -> dict: return { "ok": True, "asset": ASSET, + # Report the ACTUAL funding venue so the frontend label follows the + # data source instead of hardcoding "Binance" (which silently lies if + # for_asset() routes to HL, or ASSET becomes an HL-native perp). + "venue": getattr(for_asset(ASSET), "name", None), "cadence_hours": round(cadence_h, 2), "coverage_days": round(span_days, 1), "latest_rate_pct": round(latest, 5),