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:
+158
@@ -57,9 +57,167 @@ async def insert_fake_post(
|
||||
return {"id": post.id, "text": post.text[:80]}
|
||||
|
||||
|
||||
@router.post("/dev/fake-signal")
|
||||
async def inject_fake_signal(
|
||||
symbol: str = "ETHUSDT",
|
||||
close: float = 1850.42,
|
||||
tbr: float = 0.72,
|
||||
vol_mult: float = 3.1,
|
||||
bb_pct: float = 8.5,
|
||||
btc_trend: str = "↑ uptrend",
|
||||
):
|
||||
"""Inject a synthetic funding_signal for end-to-end testing."""
|
||||
from datetime import datetime, timezone
|
||||
from app.services import funding_signal as fs
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
alert = {
|
||||
"type": "funding_signal",
|
||||
"symbol": symbol,
|
||||
"time": now.isoformat(),
|
||||
"close": close,
|
||||
"tbr": tbr,
|
||||
"vol_mult": vol_mult,
|
||||
"bb_pct": bb_pct,
|
||||
"bb_upper": round(close * 1.002, 4),
|
||||
"btc_trend": btc_trend,
|
||||
"enabled": fs.is_enabled(),
|
||||
}
|
||||
fs._recent_signals.append(alert)
|
||||
|
||||
if fs.is_enabled():
|
||||
await manager.broadcast(alert)
|
||||
return {"status": "broadcast", "alert": alert}
|
||||
else:
|
||||
return {"status": "recorded_only (monitor OFF)", "alert": alert}
|
||||
|
||||
|
||||
@router.post("/dev/reanalyze")
|
||||
async def trigger_reanalyze(
|
||||
background_tasks: BackgroundTasks,
|
||||
limit: int = 500,
|
||||
dry_run: bool = False,
|
||||
delay_secs: float = 0.5,
|
||||
legacy_signals: bool = False,
|
||||
model: str = "",
|
||||
):
|
||||
"""
|
||||
Batch re-run AI analysis on unscored posts.
|
||||
model: override the AI model (default: ai_live_model=flash for speed).
|
||||
Runs in the background — poll GET /dev/reanalyze/status for progress.
|
||||
"""
|
||||
from app.services.reanalyze import reanalyze_unscored, get_state
|
||||
from app.config import settings as _s
|
||||
state = get_state()
|
||||
if state["running"]:
|
||||
return {"status": "already_running", "state": state}
|
||||
effective_model = model or _s.ai_live_model # default to flash
|
||||
background_tasks.add_task(
|
||||
reanalyze_unscored, AsyncSessionLocal,
|
||||
limit=limit, dry_run=dry_run, delay_secs=delay_secs,
|
||||
legacy_signals=legacy_signals, model=effective_model,
|
||||
)
|
||||
return {
|
||||
"status": "started",
|
||||
"limit": limit,
|
||||
"dry_run": dry_run,
|
||||
"delay_secs": delay_secs,
|
||||
"legacy_signals": legacy_signals,
|
||||
"model": effective_model,
|
||||
"message": f"Re-analyzing up to {limit} posts in background. Poll /api/dev/reanalyze/status.",
|
||||
}
|
||||
|
||||
|
||||
@router.get("/dev/reanalyze/status")
|
||||
async def reanalyze_status():
|
||||
"""Current progress of the background re-analyzer."""
|
||||
from app.services.reanalyze import get_state
|
||||
return get_state()
|
||||
|
||||
|
||||
@router.post("/dev/backfill-prices")
|
||||
async def trigger_price_backfill(background_tasks: BackgroundTasks, asset: str = "BTC"):
|
||||
"""触发历史帖子价格回溯(后台运行)"""
|
||||
from app.services.price_backfill import backfill_price_impact
|
||||
background_tasks.add_task(backfill_price_impact, AsyncSessionLocal, asset)
|
||||
return {"status": "started", "asset": asset, "message": "后台回溯中,约需 1-2 分钟"}
|
||||
|
||||
|
||||
# ─── Convex-strategy backtest ──────────────────────────────────────────────
|
||||
|
||||
@router.post("/dev/backtest/post")
|
||||
async def backtest_one_post(
|
||||
post_id: int,
|
||||
stop_loss_pct: float = 1.5,
|
||||
trailing_stop_pct: float = 2.5,
|
||||
trailing_activate_at_pct: float = 5.0,
|
||||
take_profit_pct: float = 0.0, # 0 = treat as None (no fixed TP)
|
||||
max_hold_hours: int = 168,
|
||||
):
|
||||
"""Replay one post through the new convex-strategy exit rules.
|
||||
|
||||
Pulls 1m candles from Binance for the post's hold window and simulates
|
||||
trailing-stop + SL + max-hold. Useful for sanity-checking parameters.
|
||||
Pass `take_profit_pct=0` to disable the fixed TP and run pure trailing.
|
||||
"""
|
||||
from app.services.backtest import backtest_post, BacktestParams
|
||||
params = BacktestParams(
|
||||
stop_loss_pct=stop_loss_pct,
|
||||
trailing_stop_pct=trailing_stop_pct or None,
|
||||
trailing_activate_at_pct=trailing_activate_at_pct or None,
|
||||
take_profit_pct=take_profit_pct or None,
|
||||
max_hold_hours=max_hold_hours,
|
||||
)
|
||||
r = await backtest_post(post_id, params)
|
||||
if r is None:
|
||||
return {"status": "skipped", "post_id": post_id}
|
||||
return {"status": "ok", "result": r.to_dict()}
|
||||
|
||||
|
||||
@router.post("/dev/paper-mode")
|
||||
async def toggle_paper_mode(
|
||||
wallet: str,
|
||||
enabled: bool,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Flip paper_mode for a subscription. Dev-only — no signature required.
|
||||
|
||||
Paper mode: trades are simulated end-to-end (entry/exit from Binance,
|
||||
DB row with hl_order_id='paper') but no Hyperliquid call is made.
|
||||
"""
|
||||
from sqlalchemy import select
|
||||
from app.models import Subscription
|
||||
wallet = wallet.lower().strip()
|
||||
result = await db.execute(select(Subscription).where(Subscription.wallet_address == wallet))
|
||||
sub = result.scalar_one_or_none()
|
||||
if sub is None:
|
||||
return {"status": "not_found", "wallet": wallet}
|
||||
sub.paper_mode = bool(enabled)
|
||||
await db.commit()
|
||||
return {"status": "ok", "wallet": wallet, "paper_mode": sub.paper_mode}
|
||||
|
||||
|
||||
@router.post("/dev/backtest/batch")
|
||||
async def backtest_batch_route(
|
||||
limit: int = 50,
|
||||
min_confidence: int = 80,
|
||||
stop_loss_pct: float = 1.5,
|
||||
trailing_stop_pct: float = 2.5,
|
||||
trailing_activate_at_pct: float = 5.0,
|
||||
take_profit_pct: float = 0.0,
|
||||
max_hold_hours: int = 168,
|
||||
):
|
||||
"""Batch backtest: every directional post with conf ≥ min_confidence.
|
||||
|
||||
WARNING: synchronous — for 50 posts at ~10s each on Binance fetches this
|
||||
can take several minutes. Run with limit=5 first to sanity-check.
|
||||
"""
|
||||
from app.services.backtest import backtest_batch, BacktestParams
|
||||
params = BacktestParams(
|
||||
stop_loss_pct=stop_loss_pct,
|
||||
trailing_stop_pct=trailing_stop_pct or None,
|
||||
trailing_activate_at_pct=trailing_activate_at_pct or None,
|
||||
take_profit_pct=take_profit_pct or None,
|
||||
max_hold_hours=max_hold_hours,
|
||||
)
|
||||
return await backtest_batch(limit=limit, min_confidence=min_confidence, params=params)
|
||||
|
||||
Reference in New Issue
Block a user