# Trump Alpha โ€” Backend > AI-powered crypto signal aggregator. Surfaces four uncorrelated signal > streams (Trump Truth Social, Macro Vibes, KOL talks-vs-trades, funding > reversal) and runs an optional execution layer on Hyperliquid perps. > Real money โ€” handle every change to the trading layer like surgery. This file is the **first thing an AI agent should read** when entering this repo. It encodes the invariants that aren't visible from any single file. --- ## ๐Ÿ›‘ Read this BEFORE touching anything trading-related This backend manages real Hyperliquid leveraged positions for real users. Bugs cost users money. **Five non-negotiable rules:** 1. **Two systems, one execution layer.** System 1 (Trump scalp) auto-opens. System 2 (Macro Vibes / reversal) is **MANAGE-ONLY since v2.0** โ€” the user opens manually on Hyperliquid, then `/adopt` hands it to the bot. `process_post()` early-returns for sys2 sources. Re-enabling sys2 auto-open would silently reintroduce all the leverage/budget/concurrency race conditions we excised. Don't do it without an ADR. 2. **`released_at` is the "user took back control" marker.** A trade with `released_at IS NOT NULL` is **OUT OF BOUNDS for the bot**: - `recovery.rehydrate_open_trades` skips it - `reconciler` skips it - `close_and_finalize`'s atomic claim requires `released_at IS NULL` unless `force=True` (only manual_close passes that) - `partial_derisk` and `pyramid_add` early-return idempotent-success - `/positions/open`, `/positions/today`, telegram_digest all filter it If you add ANY new code path that touches BotTrade rows, ask yourself "does this respect released_at?" Almost always yes. 3. **Effective exit params are FROZEN on the BotTrade row at open time.** See the `eff_*` columns on `BotTrade`. Recovery rebuilds the watchdog from these, NOT from the live Subscription. Without this, restarting the backend silently rewrites every open System-2 reversal's stop loss to the user's Trump scalp setting (1.5%). NEVER read live `Subscription.stop_loss_pct` etc. in the close path. 4. **HL leverage is what HL says, not what the user requested.** Hyperliquid silently clips the requested leverage to the asset's max (memes capped at 3ร—). `hyperliquid.open_position()` returns `effective_leverage` โ€” bot_engine and adoption must use THAT value to compute `sys2_protective_stop_pct(lev)` and the derisk ladder. Using the requested value puts the stop OUTSIDE the real liquidation line. 5. **Per-wallet asyncio locks wrap "check + open" critical sections.** See `_wallet_lock` in `bot_engine` and `_wallet_adopt_lock` in `adoption`. Without them, two concurrent signals can both pass the daily-budget / concurrency / already-open check before either commits. --- ## What this product does (90 seconds) ``` Four signal sources โ†’ one bot โ†’ optional Hyperliquid execution โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ 1. Trump Truth Social โ”‚โ”€โ”€ auto-classify (DeepSeek) โ†’ "buy"/"short"/"noise" โ”‚ (every post, <3s) โ”‚ if actionable: Trump scalp auto-open (System 1) โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ Tight 1.5% SL, 12h cooldown, โ‰ฅ30min min-hold Optional: post prediction tweet via x_poster.py โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ 2. Macro Vibes โ”‚โ”€โ”€ 8 daily macro indicators (AHR999, F&G, etc.) โ”‚ (BTC bottom + funding)โ”‚ + 2-of-3 bottom-reversal trigger โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ Telegram alert with /adopt CTA โ€” NO auto-open. User opens on HL โ†’ /adopt โ†’ bot manages with 5-rung stop ladder, de-risk, pyramid, peak-trail. โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ 3. KOL talks-vs-trades โ”‚โ”€โ”€ Substack/podcast/X ingest + ETH on-chain diff โ”‚ (29 feeds, daily) โ”‚ Divergence (publicly bullish, secretly selling) โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ is the platform's highest-conviction signal. Telegram alert only โ€” never auto-trades. x_analysis.py adds real-time X post scoring. โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ 4. Funding extreme โ”‚โ”€โ”€ Hourly BTC perp funding scan โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ Alert only (manage-only via /adopt like Macro) โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Telegram daily digest โ”‚โ”€โ”€ Once-a-day 3-section brief (Macro/KOL/Trump) โ”‚ (per-user hour, opt-out)โ”‚ to every subscriber. Cron at minute=0 each hour. โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` Free tier = read everything + Telegram alerts. Pro tier (Hyperliquid wallet linked) = Trump auto-trade + /adopt manage-only flow for sys2. --- ## Stack - **Python 3.9+** / FastAPI / async SQLAlchemy 2.x / APScheduler - **DB**: SQLite dev, **Postgres prod**. All schema lives in `alembic/versions/NNN_*.py`, ordered. Currently at head **026**. - **AI**: DeepSeek via OpenAI-compatible API (`AI_BASE_URL`, `AI_MODEL`). - Live scoring uses `AI_LIVE_MODEL` (~2s, latency-critical) - Batch / reanalysis uses `AI_MODEL` (quality, ~10s) - **Trading**: Hyperliquid SDK; API-wallet keys are envelope-encrypted with `ENCRYPTION_KEY` (KEK), per-user DEK derivation via `crypto.py`. - **Prices**: Two feeds: - `binance.py` WebSocket โ†’ BTC, ETH, SOL, TRUMP, BNB, DOGE, LINK, AAVE - `hl_price_feed.py` polls HL `allMids` every 2s โ†’ HYPE, PURR (HL-native assets not on Binance) Both pump `price_store` + `tp_sl_monitor` on every tick. - **Telegram**: long-poll mode (single instance), HTML messages, inline keyboards. `telegram.py` send/edit/answer + `telegram_bot.py` commands. Public channel broadcast (`TELEGRAM_PUBLIC_CHANNEL_ID` env) sends a sanitised `format_public_post` version (no execution details, tier label instead of raw confidence) after every per-user fan-out in `_dispatch`. - **X (Twitter)**: `x_poster.py` โ€” optional viral prediction tweets after each actionable Trump signal. Gated by `x_enabled=False` (off by default). Full no-op if creds missing. OAuth 1.0a hand-signed with stdlib hmac โ€” no extra deps. --- ## Module map (where things live) ``` app/ โ”œโ”€โ”€ api/ HTTP routes โ”‚ โ”œโ”€โ”€ signals.py POST /api/signals/ingest โ† scanners write here โ”‚ โ”œโ”€โ”€ positions.py /positions/open|today|close|grow|adopt|release|hl โ”‚ โ”œโ”€โ”€ user.py /subscribe|settings|manual-window|auto-trade โ”‚ โ”œโ”€โ”€ telegram.py /telegram/{preferences,bind,unbind,test} โ”‚ โ”œโ”€โ”€ macro.py /macro/{snapshot,history} โ”‚ โ”œโ”€โ”€ kol.py /kol/{posts,digest,wallets,divergence} โ”‚ โ”œโ”€โ”€ performance.py /performance โ† wallet-scoped real-money stats (30d) โ”‚ โ”œโ”€โ”€ funding_reversal.py /funding/snapshot โ† live funding state + 7d history โ”‚ โ”œโ”€โ”€ funding_signal.py /signal/{status,toggle,history} โ† breakout monitor โ”‚ โ””โ”€โ”€ dev.py Dev-only routes (only mounted in development env) โ”œโ”€โ”€ services/ โ”‚ โ”œโ”€โ”€ bot_engine.py โ˜… TRADING CORE โ€” process_post, _execute_for_subscriber, โ”‚ โ”‚ _broadcast_trade_alert (WS failure notifications), โ”‚ โ”‚ close_and_finalize, partial_derisk, pyramid_add โ”‚ โ”œโ”€โ”€ adoption.py โ˜… /adopt + /release flow (sys2 manage-only) โ”‚ โ”œโ”€โ”€ tp_sl_monitor.py Per-price-tick close evaluator. on_price_tick is โ”‚ โ”‚ called from binance.py + hl_price_feed.py once/sec โ”‚ โ”œโ”€โ”€ hyperliquid.py HL trader (open/close/reduce/leverage) โ”‚ โ”œโ”€โ”€ recovery.py Startup rehydration of open BotTrades into watchdog โ”‚ โ”œโ”€โ”€ reconciler.py Every 60s: compare DB โ†” HL state, mark drift โ”‚ โ”œโ”€โ”€ circuit_breaker.py Per-system (sys1/sys2) CB, daily DD + N-loss streak โ”‚ โ”œโ”€โ”€ signal_categories.py CRITICAL CONFIG โ€” sys1/sys2 sources, ladders, โ”‚ โ”‚ leverage clamping, protective stop formulas โ”‚ โ”œโ”€โ”€ regime_filter.py Sys1 only โ€” recent-move / vol-contraction gates โ”‚ โ”œโ”€โ”€ analysis.py AI signal scoring (DeepSeek) for Trump posts โ”‚ โ”œโ”€โ”€ x_analysis.py AI scoring for X (Twitter) KOL posts. Three tiers: โ”‚ โ”‚ TRADE_SIGNAL / DIRECTIONAL / NOISE. Strict NOISE โ”‚ โ”‚ default โ€” most X posts should be filtered out. โ”‚ โ”‚ Consumed by kol_x.py. tickers come out in the โ”‚ โ”‚ {ticker,action,conviction} shape kol_divergence reads. โ”‚ โ”œโ”€โ”€ x_poster.py X (Twitter) auto-poster for Trump signals. โ”‚ โ”‚ Fires a prediction tweet then a follow-up at โ”‚ โ”‚ +x_followup_minutes with the actual move. Gated by โ”‚ โ”‚ x_enabled env var (False by default). Full no-op โ”‚ โ”‚ if creds missing โ€” never blocks signal flow. โ”‚ โ”œโ”€โ”€ entry_filter.py Cheap text-based pre-filter (skip RT/URL-only) โ”‚ โ”œโ”€โ”€ telegram.py send_message / edit_message / answer_callback โ”‚ โ”œโ”€โ”€ telegram_bot.py Long-poll loop + /start /digest /adopt /release ... โ”‚ โ”œโ”€โ”€ telegram_digest.py Daily 3-section brief (rule-based; no LLM) โ”‚ โ”œโ”€โ”€ price_store.py In-memory latest price per asset โ”‚ โ”œโ”€โ”€ price_backfill.py Backfill historical 5min bars from Binance โ”‚ โ”œโ”€โ”€ hl_price_feed.py Supplemental HL price feed for HL-native assets โ”‚ โ”‚ (HYPE, PURR). Polls allMids every 2s. Runs โ”‚ โ”‚ alongside binance.py. Without this, TP/SL โ”‚ โ”‚ silently stops protecting HL-native trades. โ”‚ โ”œโ”€โ”€ backtest.py Single-post backtest harness. Fetches 1m Binance โ”‚ โ”‚ candles for [published_at, +max_hold_h] and โ”‚ โ”‚ replays current exit rules. Conservative (uses โ”‚ โ”‚ HIGH/LOW within bar). No fees. Batch runner on top. โ”‚ โ”œโ”€โ”€ crypto.py HL API-key envelope encryption โ”‚ โ”œโ”€โ”€ scanner_state.py In-memory toggle + observability for scanners โ”‚ โ”œโ”€โ”€ macro/ โ”‚ โ”‚ โ”œโ”€โ”€ fetchers.py 8 macro indicator HTTP fetchers (each @_none_on_fail) โ”‚ โ”‚ โ”œโ”€โ”€ scoring.py Weighted composite -100..+100 โ”‚ โ”‚ โ””โ”€โ”€ poll.py Daily UPSERT into macro_snapshots โ”‚ โ”œโ”€โ”€ scanners/ โ”‚ โ”‚ โ”œโ”€โ”€ btc_bottom_reversal.py 2-of-3 AHR999 + 200WMA + Pi Bottom โ”‚ โ”‚ โ”œโ”€โ”€ funding_reversal.py Hourly funding extreme โ”‚ โ”‚ โ””โ”€โ”€ sma_reclaim.py (archive โ€” not scheduled) โ”‚ โ”œโ”€โ”€ kol_substack.py RSS ingest for 29 KOL feeds (substack/blog/podcast) โ”‚ โ”œโ”€โ”€ kol_x.py X (Twitter) ingest via twitterapi.io โ†’ x_analysis โ†’ โ”‚ โ”‚ KolPost(source="twitter"). Daily 01:30 UTC. No-op โ”‚ โ”‚ if twitterapi_io_key unset. Provides the post-side โ”‚ โ”‚ feed for X-only KOLs (andrewkang, murad). โ”‚ โ”œโ”€โ”€ kol_onchain.py HL public API + Etherscan diff โ”‚ โ”œโ”€โ”€ kol_divergence.py Cross-ref talks vs trades within ยฑ7d โ”‚ โ”œโ”€โ”€ kol_analysis.py AI ticker/direction/conviction extract (Substack) โ”‚ โ”œโ”€โ”€ bottom_indicators.py AHR999 / Pi Cycle / 200WMA math โ”‚ โ”œโ”€โ”€ funding_signal.py Real-time funding extreme detector โ”‚ โ””โ”€โ”€ signed_request.py EIP-191 signature verification (+ replay cache) โ”œโ”€โ”€ scrapers/ โ”‚ โ”œโ”€โ”€ truth_social.py CNN archive poller (5s interval) โ€” primary โ”‚ โ””โ”€โ”€ trumpstruth.py trumpstruth.org RSS fallback poller. Same post id โ”‚ hash โ†’ automatic dedup. Whoever sees first wins. โ”‚ Offset by half the interval so the two pollers โ”‚ don't hit upstream simultaneously. โ”œโ”€โ”€ ws/ โ”‚ โ””โ”€โ”€ manager.py WebSocket fan-out for live UI updates. โ”‚ Broadcasts trade_alert events (execution_failed / โ”‚ insufficient_balance / budget_reached) via โ”‚ _broadcast_trade_alert() in bot_engine.py. โ”œโ”€โ”€ models.py โ˜… All SQLAlchemy models in one file โ”œโ”€โ”€ database.py Async engine + session factory โ”œโ”€โ”€ config.py Pydantic Settings โ€” reads .env โ””โ”€โ”€ main.py FastAPI lifespan, scheduler setup, route mount Includes: singleton lock guard (one-leader, multi- worker safe), deep health check /api/health/deep, boot-grace window for price feeds. alembic/versions/ Migrations (numbered NNN). Latest = 026 026 = composite index (wallet_address, closed_at) on bot_trades scripts/ One-shot ops โ”œโ”€โ”€ preflight.py Pre-launch readiness gate (env / DB / TG / AI) โ”œโ”€โ”€ launch_smoke.py End-to-end smoke (14 checks against running API) โ”œโ”€โ”€ launch_seed.py Pre-launch data prep: drops test sources, trims KOL โ”‚ window to last 30d, refetches all upstream sources. โ”‚ Run ONCE before flipping traffic to a fresh DB. โ”œโ”€โ”€ seed_kol_wallets.py Seeds the KOL wallet table with known addresses. โ”‚ Idempotent (INSERT OR IGNORE). Run once at first deploy. โ”œโ”€โ”€ rescore_v5.py Re-score every Post with current AI prompt โ”œโ”€โ”€ backfill_signals.py Fill in signal for posts missing it โ””โ”€โ”€ verify_sys2_lifecycle.py Manual System-2 lifecycle walk-through tests/ pytest, 83 tests, fast (<3s total) โ”œโ”€โ”€ test_adoption.py Adoption + release flow (snapshot-style, no real HL/AI) โ”œโ”€โ”€ test_telegram_digest.py Daily digest formatting โ”œโ”€โ”€ test_kol_x.py X ingest: dedup / mapping / no-op (mocked fetch+AI) โ”œโ”€โ”€ test_ratelimit.py Rate limit coverage (BUG-02 fix) โ”œโ”€โ”€ test_bottom_reversal_strategy.py btc_bottom_reversal 2-of-3 logic โ”œโ”€โ”€ test_macro_fetchers_timing.py Macro fetcher timeout + error handling โ””โ”€โ”€ test_production_readiness.py Environment / config sanity checks ``` --- ## The two trading systems (memorise this) ``` System 1 System 2 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Source "truth" "btc_bottom_reversal" (+"funding_reversal" alert-only) Trigger Trump posts a thing Daily scanner: 2-of-3 confluence Latency need Seconds (price moves fast) Days/weeks (signal lives a long time) Open path Auto (bot_engine.process_post fires _execute_for_subscriber) MANUAL: user opens on HL UI, then /adopt hands it to the bot Stop loss User-configured + tight sys2_protective_stop_pct(actual_lev) 1.5% floor (TRUMP_*) = 85% ร— (100/lev), capped at 35% Exit model TP / trailing / SL 5-rung stop ladder + downside de-risk + pyramid + peak-trail. NO TP. Min hold 30 min (suppresses TP) n/a Max hold 6h 18 months (ladder is the real exit) Sizing base ร— regime multiplier Whatever user opened on HL Concurrency cap n/a 3 positions / wallet (correlated beta) Confidence min 88 (platform) / user 85 (platform) Circuit breaker sys1_* sys2_* (independent) Daily budget Full daily_budget_usd n/a โ€” user controls notional on HL Telegram alert Trump alert format Macro/funding alert + /adopt CTA X tweet Optional prediction tweet n/a ``` **If you're tempted to put sys2 logic in `_execute_for_subscriber`**: stop. `process_post()` early-returns for sys2. The function only runs for sys1 now. The dead sys2 branches inside `_execute_for_subscriber` are kept for diff minimalism โ€” don't extend them. --- ## The /adopt flow (System-2 lifecycle in detail) ``` 1. Scanner fires โ””โ”€ POST /api/signals/ingest (source=btc_bottom_reversal, signal=buy) โ””โ”€ Post row created โ””โ”€ process_post() early-returns for sys2 (no auto-open) โ””โ”€ notify_signal() โ†’ Telegram fan-out with /adopt CTA appended 2. User opens BTC long on Hyperliquid manually โ””โ”€ size / leverage of their choice 3. User in bot: /adopt โ””โ”€ adoption.list_hl_positions(wallet) reads HL state โ””โ”€ Telegram inline keyboard: tap [๐ŸŸข BTC long $1500 @72k ยท 2x] โ””โ”€ Mode picker: [๐Ÿ“ˆ Standard] or [๐Ÿš€ Aggressive] โ””โ”€ adoption.adopt_position(wallet, asset, mode): a. Per-wallet asyncio lock acquired b. Pre-flight: no_subscription / no_hl_key / paper_mode / circuit_breaker (sys2 CB still gates adopt!) / already_adopted / concurrency_cap (3) c. Re-read HL state inside lock (fresh entry/size/lev) d. Reject if leverage > SYS2_MAX_LEVERAGE (BUG-09 fix) e. Resolve sys2_protective_stop_pct(HL_actual_leverage) f. INSERT BotTrade with eff_* frozen + sys2_mode + hl_order_id="adopted:" + trigger_post_id=NULL g. register_trade() with full ladder/de-risk/addon/peak_trail 4. tp_sl_monitor drives the position โ””โ”€ Stop ratchet, downside de-risk partial reduces, pyramid add-ons, peak-trail close, max_hold backstop. All staged through the lock-protected partial_derisk / pyramid_add / close_and_finalize. 5a. User wants out: /release โ””โ”€ Sets BotTrade.released_at = now; unregister(trade_id) from watchdog โ””โ”€ HL position UNTOUCHED โ€” bot stops driving, user has manual control 5b. Bot drives the close (ladder / max-hold) โ””โ”€ close_and_finalize() atomic claim sets closed_at, computes pnl 5c. User force-closes via UI: POST /api/positions/{id}/close โ””โ”€ manual_close calls close_and_finalize(force=True) โ€” bypasses released_at guard. Works on adopted-and-released trades too. 6. Recovery on restart: โ””โ”€ recovery.rehydrate_open_trades reads BotTrade WHERE closed_at IS NULL AND released_at IS NULL โ””โ”€ For each sys2 trade: rebuild ladder from sys2_mode + adopted fallback. Also reschedules _time_stop_check with remaining seconds (elapsed windows fire immediately with delay=0). This is the critical fix โ€” without it adopted trades lose their entire sys2 ladder on restart. ``` --- ## Singleton lock guard (critical infrastructure detail) `main.py:lifespan` calls `_acquire_singleton_lock()` at boot using an advisory file lock (`/tmp/trumpsignal-backend.lock`, configurable via `SINGLETON_LOCK_PATH`). Only ONE process โ€” "the leader" โ€” starts the scheduler, scrapers, price feeds, and Telegram poller. Any additional worker (e.g. accidental `--workers 2`) serves HTTP reads only and logs `SINGLETON LOCK NOT ACQUIRED`. The OS releases the lock automatically when the leader exits, so a crashed leader unblocks the next start. The `/api/health/deep` endpoint surfaces `"is_leader": false` and adds it to `problems[]` so uptime monitors catch mis-configured multi-worker deployments. --- ## Critical invariants checklist (when reviewing any trading change) - [ ] Does this code path respect `released_at IS NULL`? - [ ] Does it use `eff_*` (frozen) not live `Subscription.*` for exit math? - [ ] If it opens a new position, does it use HL's actual leverage (not requested)? - [ ] If it touches an open position concurrently, is it wrapped in the per-trade `_lock_for(trade_id)` lock? - [ ] If it opens, is it inside `_wallet_lock(wallet)` so the budget / concurrency check is atomic with the write? - [ ] If it closes, does it use the conditional `UPDATE ... WHERE closed_at IS NULL` atomic claim? - [ ] Does it handle the `already_closed` path from HL gracefully (preserve banked partial PnL)? - [ ] Does it correctly check sys1 vs sys2 CB independently? --- ## Common workflows ### Add a new signal source 1. Decide: System 1 (auto-trade) or System 2 (alert + /adopt) or alert-only? 2. Write a scanner under `app/services/scanners/NEW.py` that posts to `POST /api/signals/ingest` with `{source: "your_new_source", ...}`. 3. Schedule it in `app/main.py` (`_scheduler.add_job`). 4. Add the source to `signal_categories.SYSTEM_1_SOURCES` or `SYSTEM_2_SOURCES` if it should trade. Leave it out if alert-only. 5. Add a Telegram preference column to `TelegramBinding` (migration) + a mapping entry in `telegram._pref_column_for_source`. 6. Add a label to `telegram._source_label` and `_signal_emoji`. 7. Add a `/yoursource on|off` command in `telegram_bot.py`. 8. If sys2: extend `signal_categories._CATEGORY_EXITS` if it needs a custom exit profile (otherwise default works). 9. Add the deep-link path in `telegram.format_post` AND `telegram.format_public_post`. ### Add a new bot command 1. `_cmd_x` async function in `telegram_bot.py`. 2. Route it in `_handle_message`. 3. If it needs inline buttons: build `reply_markup` payload, handle callbacks in `_handle_callback` (route by `callback_data` prefix). 4. Update `HELP_TEXT` and remind the user to add it to BotFather `/setcommands` after deploy. ### Add a column to an existing table 1. New migration `alembic/versions/NNN_description.py`. - Use `op.batch_alter_table` (sqlite-compatible). - Default values via `server_default=` so backfill is implicit. 2. Mirror the field on the SQLAlchemy model in `app/models.py`. 3. Apply locally: `DATABASE_URL= alembic upgrade head`. ### Enable X (Twitter) posting 1. Create a Twitter developer app with OAuth 1.0a user context permissions. 2. Set in `.env`: `X_API_KEY`, `X_API_SECRET`, `X_ACCESS_TOKEN`, `X_ACCESS_SECRET`, `X_ENABLED=true`. 3. Optionally tune: `X_DAILY_CAP` (default 40), `X_FOLLOWUP_MINUTES` (default 15). 4. Verify with a dry-run: set `X_ENABLED=false` and check logs โ€” the poster logs what it WOULD tweet without sending. ### Deploy ```bash # On the server: DATABASE_URL=$PROD_URL alembic upgrade head systemctl restart trumpalpha-backend # or whatever the unit is python scripts/preflight.py # MUST pass before flipping traffic python scripts/launch_smoke.py --base https://api.trumpalpha.io ``` --- ## Running it ```bash cd backend && python -m venv venv && source venv/bin/activate pip install -r requirements.txt cp .env.example .env # fill in: ENCRYPTION_KEY, AI_API_KEY, INGEST_API_KEY uvicorn app.main:app --reload # dev only; prod uses --workers 1, no --reload ``` --- ## Testing ```bash source venv/bin/activate python -m pytest tests/ -q # 79 tests, ~2s python scripts/preflight.py # env + DB + TG + AI auth checks python scripts/launch_smoke.py # 14 end-to-end checks vs running API ``` Adoption + telegram_digest are snapshot-style (no real HL/AI). End-to-end trading is verified manually via the bot. --- ## Telegram bot mechanics (since it's a custom integration) - **Long-poll mode** via `getUpdates`. Only ONE process can long-poll a given bot token at a time โ€” if you horizontally scale, switch to webhook (not done yet). - Bot must be re-bound via `@BotFather` `/setcommands` whenever new commands are added (the slash-menu users see is separate from what the bot internally handles). - `send_message` returns `False` on failure; per-user binding rows track `total_alerts_sent` / `total_alerts_failed` counters. - **Inline keyboards** = the `reply_markup` payload to `sendMessage`. Callback data is capped at 64 bytes; keep it short (`adopt:mode:BTC:standard`). `_handle_callback` MUST end with `answer_callback` or the button spins forever on the user's client. - Free tier = walletless `/start` (chat_id only). Pro tier = wallet bound via `/start CODE` where CODE comes from Settings UI. --- ## Why "Macro Vibes" became manage-only (the ADR) V1.0: System 2 auto-opened sys2 trades on user wallets. Carried real execution surface: leverage clipping, daily budget split, concurrency caps, sys2 paper branches, key handling per user. Audit surfaced ~6 bugs. V2.0 (current): sys2 manage-only. The strategy is day-K โ€” entry delay of 24h doesn't matter. The valuable part is multi-month exit management (5-rung ladder, de-risk, pyramid, peak-trail), which still runs against positions the user adopts. **Net effect**: massive reduction in execution risk surface; same alpha (strategy logic unchanged); legal/responsibility shifts from "bot opened this for you" to "you opened it, bot manages your discipline". --- ## Things that LOOK like bugs but aren't - **`_execute_for_subscriber` has lots of `if sub["_is_system_2"]` branches.** Dead code under v2.0 (process_post early-returns for sys2). Kept for diff minimalism โ€” don't extend or re-enable. - **`kol_x.X_KOLS` handle โ‰  X username.** `handle` is the CANONICAL key (e.g. "cryptohayes") that MUST match `KolWallet.handle` so divergence can join post-side โ†” on-chain; `x_username` is the screen name fetched from X. andrewkang/murad wallets stay dark (zero divergence detections) until X ingestion supplies their post side โ€” that's the whole reason kol_x exists. Empty `twitterapi_io_key` โ†’ kol_x is a full no-op (no error). - **`funding_reversal` source is in `SYSTEM_2_SOURCES`? No.** It's intentionally NOT in either supported set โ€” it ingests as a Post for audit + sends Telegram alert via the CTA path, but doesn't trigger any auto-trading. Adopt still works (it's asset-based, not signal-based). - **`Subscription.sys2_budget_pct` defaults to 0.7.** Legacy field from the auto-open era. With v2.0 manage-only, it's effectively unused โ€” sys1 (Trump) reads full `daily_budget_usd`. Don't read it for new code. - **Adopted trades have `hl_order_id` starting with `"adopted:"`.** Distinct from auto-opened (HL order id integer) and paper (`"paper"` literal). Useful for telemetry filtering. - **`telegram.send_message` accepts `int | str` for `chat_id`.** Intentional. Integer = private chat, string = public channel username (e.g. `"@trumpalpha"`). - **`format_public_post` deliberately omits `expected_move_pct`, `invalidation_price`, and `/adopt` CTA.** Execution-sensitive data stays private. The public version shows confidence tier (HIGH/MED/LOW) instead of the raw score. - **`_adopt_locks` in adoption.py** is an `OrderedDict` capped at 512 with LRU eviction โ€” matches the `_WALLET_LOCK_MAX` pattern in `bot_engine`. - **`trumpstruth.py` runs at half-interval offset** โ€” the two CNN + trumpstruth pollers are deliberately staggered so they don't hammer upstream simultaneously. The offset is `truth_social_poll_seconds // 2`, set via APScheduler `next_run_time` at boot. --- ## Open known issues (not blocking launch but worth fixing later) - **`adopt:choose:BTC` callback may show stale prices** if user takes >60s to tap (HL fees, partial fills can change entry/size). adopt_position re-reads HL at mode-tap time so the FROZEN BotTrade is always fresh, but the picker label could be outdated. - ~~**Telegram bot offset on restart**~~ **FIXED 2026-06-01**: startup drain added. Stale `/adopt` replays suppressed. - **`/adopt` picker label** can show stale price if user waits >60s to tap (frozen BotTrade is always fresh, but the Telegram picker label may be outdated). **Deferred security items (need interface change or DB migration):** - **C3**: Read endpoints pass `ts`/`sig` as URL query params โ†’ access log exposure. - **H4**: HL API key KEK uses single unsalted SHA-256. Needs re-encryption migration. - **M1**: Adopted sys2 positions miscounted against sys1 daily budget. - **M5**: `GET /telegram/{wallet}/status` unauthenticated โ€” exposes chat_id/tg_username. --- ## Repos in this project - **This repo** (`/Users/k/Public/trumpsignal/backend`) โ€” Python/FastAPI backend - **Sibling frontend** (`/Users/k/Public/trumpsignal/frontend`) โ€” Next.js 16 dashboard at trumpsignal.com. See its own CLAUDE.md. Both deployed independently. Backend serves the JSON API + Telegram bot. Frontend is a thin SPA over the API + WebSocket.