Pre-launch hardening: KOL module, Telegram, scanners, WS resilience

Big-picture changes since b941223:

KOL pipeline (new) — Substack/podcast/blog RSS → AI ticker extraction →
on-chain wallet diff → talks-vs-trades divergence detection. Daily polls,
19 feeds, divergence emits Post + Telegram fan-out.

Telegram push (new) — walletless free tier + wallet-linked Pro upgrade,
in-bot preference commands (/trump /btc /funding /kol /conf /quiet),
signed-envelope API for dashboard. Disconnect-wallet keeps free
subscription.

BTC funding-rate reversal scanner (new) — hourly cron, 30d cumulative
funding threshold + mean-revert + 7d price confirm, emits via
/api/signals/ingest. BTC bottom-reversal scanner promoted to System 2.

WS broadcast rewrite — per-client send timeout + parallel fan-out
(asyncio.gather). Fixes "Binance WS no close frame" reconnect storms +
APScheduler 11-min job misses, both caused by one slow client stalling
the kline loop.

Error visibility — three silent-error sites (trumpstruth/truth_social
fetchers, funding_reversal scanner) now include exception type name so
httpx ConnectError-style empty-message errors stop logging blank lines.
Telegram bot loop now classifies ReadTimeout vs network vs unknown +
logger.exception for the unknown bucket.

Security hygiene — trumpsignal.db untracked from git (held subscriber
wallets + encrypted HL keys + 22 bot trades); .gitignore now blocks
*.db/.next/backups. CORS only allows FRONTEND_URL in production.

New ops scripts —
  - scripts/preflight.py: env/DB/Telegram/AI auth verification gate
  - scripts/backup_db.sh: cron-friendly daily DB backup (SQLite + Postgres)
  - scripts/seed_kol_wallets.py: idempotent KOL on-chain wallet seeder

15 new Alembic migrations (007-021) covering convex strategy fields,
phase-1 safety, two-system frozen exits, invalidation prices, dynamic
SYS2 leverage, staged de-risk + pyramiding, peak gain tracking, risk
mode, auto-trade + grow flags, KOL module, KOL on-chain, KOL divergence,
Telegram bindings + walletless.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
k
2026-05-25 00:52:56 +08:00
parent b941223c88
commit 5fb1d52026
81 changed files with 13251 additions and 158 deletions
+29 -6
View File
@@ -1,7 +1,7 @@
import logging
from datetime import datetime, timezone
from fastapi import APIRouter, Depends
from fastapi import APIRouter, Depends, Request
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
@@ -17,15 +17,29 @@ ACTION_SUBSCRIBE = "subscribe"
@router.post("/subscribe", response_model=SubscribeResponse)
async def subscribe(body: SubscribeRequest, db: AsyncSession = Depends(get_db)):
async def subscribe(request: Request, db: AsyncSession = Depends(get_db)):
"""Activate a subscription. Optional `paper_mode` flag in the body lets
new users try the system safely (no Hyperliquid call, simulated fills).
Signed message body is the RAW dict so the canonical hash matches what
the frontend signed — same pattern as set_user_settings.
"""
raw = await request.json()
body = SubscribeRequest(**raw)
wallet = body.wallet.lower().strip()
# The signed body excludes the envelope fields. For backwards compatibility
# with old clients that signed `body=None` (no paper_mode key), accept
# either signature.
paper_mode = bool(raw.get("paper_mode", False))
signed_body = {"paper_mode": paper_mode} if paper_mode else None
verify_signed_request(
action=ACTION_SUBSCRIBE,
wallet=wallet,
timestamp_ms=body.timestamp,
signature=body.signature,
body=None,
body=signed_body,
)
result = await db.execute(
@@ -35,13 +49,22 @@ async def subscribe(body: SubscribeRequest, db: AsyncSession = Depends(get_db)):
now = datetime.now(timezone.utc).replace(tzinfo=None)
if sub is None:
sub = Subscription(wallet_address=wallet, active=True, subscribed_at=now)
sub = Subscription(
wallet_address=wallet,
active=True,
subscribed_at=now,
paper_mode=paper_mode,
)
db.add(sub)
else:
sub.active = True
if sub.subscribed_at is None:
sub.subscribed_at = now
# Re-subscribing is allowed to change paper mode (e.g. user wants to
# promote from paper to live). Otherwise leave existing flag alone.
if paper_mode != sub.paper_mode:
sub.paper_mode = paper_mode
await db.commit()
logger.info("Subscription activated for wallet %s", wallet)
return SubscribeResponse(status="ok", wallet=wallet)
logger.info("Subscription activated for %s (paper_mode=%s)", wallet, paper_mode)
return SubscribeResponse(status="ok", wallet=wallet, paper_mode=paper_mode)