# 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 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ 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 ingest + ETH on-chain diff โ”‚ (19 KOLs, daily) โ”‚ Divergence (publicly bullish, secretly selling) โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ is the platform's highest-conviction signal. Telegram alert only โ€” never auto-trades. โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ 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**: Binance WS (`binance.py`) feeds `price_store` + powers the `tp_sl_monitor` per-tick evaluator. - **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`. --- ## 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} โ”œโ”€โ”€ services/ โ”‚ โ”œโ”€โ”€ bot_engine.py โ˜… TRADING CORE โ€” process_post, _execute_for_subscriber, โ”‚ โ”‚ 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 once per second โ”‚ โ”œโ”€โ”€ 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) โ”‚ โ”œโ”€โ”€ 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 โ”‚ โ”œโ”€โ”€ 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 19 KOL feeds โ”‚ โ”œโ”€โ”€ 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 โ”‚ โ”œโ”€โ”€ bottom_indicators.py AHR999 / Pi Cycle / 200WMA math โ”‚ โ””โ”€โ”€ funding_signal.py Real-time funding extreme detector โ”œโ”€โ”€ scrapers/ โ”‚ โ””โ”€โ”€ truth_social.py CNN + trumpstruth.org pollers (5s interval) โ”œโ”€โ”€ ws/ โ”‚ โ””โ”€โ”€ manager.py WebSocket fan-out for live UI updates โ”œโ”€โ”€ 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 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) โ”œโ”€โ”€ 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, 64 tests, fast (<1s total) ``` --- ## 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 ``` **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. Resolve sys2_protective_stop_pct(HL_actual_leverage) e. INSERT BotTrade with eff_* frozen + sys2_mode + hl_order_id="adopted:" + trigger_post_id=NULL f. register_trade() with full ladder/de-risk/addon/peak_trail โ””โ”€ Telegram confirmation w/ ladder summary 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 โ””โ”€ release_management(wallet, trade_id): a. Sets BotTrade.released_at = now b. unregister(trade_id) from watchdog โ””โ”€ HL position UNTOUCHED โ€” bot stops driving, user has manual control โ””โ”€ Across restarts: recovery skips released rows (released_at filter) โ””โ”€ Reconciler skips them. Released trades don't appear in /positions/open or the digest "your status" line. 5b. Bot drives the close (ladder / max-hold) โ””โ”€ close_and_finalize() atomic claim sets closed_at, computes pnl โ””โ”€ Trade is now CLOSED (closed_at set, exit_price + pnl_usd written) 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: rebuild ladder from sys2_mode + (category OR adopted fallback). The fallback for trigger_post_id IS NULL is the critical fix โ€” without it adopted trades lose their entire sys2 ladder on restart. ``` --- ## 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` (both the per-user private alert and the public channel use the same path map). ### 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`. ### 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 ``` --- ## Testing ```bash source venv/bin/activate python -m pytest tests/ -q # full suite, ~0.5s python scripts/preflight.py # env + DB + TG + AI auth checks python scripts/launch_smoke.py # 14 end-to-end checks vs running API ``` 64 tests. 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. - **`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"`). The public channel uses the string form; per-user alerts still pass integers. - **`format_public_post` deliberately omits `expected_move_pct`, `invalidation_price`, and `/adopt` CTA.** Execution-sensitive data stays private (per-user). The public version shows confidence tier (HIGH/MED/LOW) instead of the raw score for readability. - **`_adopt_locks` in adoption.py looks like it should have a cap like `_wallet_open_locks` (512).** It doesn't yet โ€” see Known Issues below. --- ## Open known issues (not blocking launch but worth fixing later) - ~~**`_time_stop_check` tasks not rehydrated on restart** (BUG-01, FIXED 2026-05-29):~~ `recovery.py` now imports `_time_stop_check` + `_background_tasks` from `bot_engine` and `get_exit_profile` from `signal_categories`. After each `register_trade()` call for a sys2 trade, it checks `exit_profile.time_stop_hours`, computes remaining seconds, and reschedules the task. Elapsed windows (backend was down longer than the time-stop period) fire immediately with `delay_seconds=0`. - **Rate limit bypass via proxy x-forwarded-for deletion** (BUG-02, HIGH): `app/api/proxy/[...path]/route.ts` (frontend) strips `x-forwarded-for` before forwarding. Backend `slowapi` sees the Next.js server IP and applies one rate-limit bucket to ALL users. Fix: relay the real client IP from `req.headers.get('x-forwarded-for')` or `x-real-ip` in the proxy. - ~~**`close_and_finalize` double-failure leaves DB/HL state inconsistent** (BUG-03, MITIGATED 2026-05-29):~~ Full two-phase-commit is out of scope. Mitigation: `reconciler._reconcile_wallet` now runs a "ghost position" pass โ€” queries DB-closed trades from the last 2h and cross-checks against HL open positions. Mismatches are logged at ERROR level and broadcast via WS (`reconcile_drift.ghost_positions`). Manual close required on HL UI. - ~~**`_adopt_locks` has no capacity cap** (BUG-04, FIXED 2026-05-29):~~ `adoption._adopt_locks` is now an `OrderedDict` capped at `_ADOPT_LOCK_MAX=512` with LRU eviction โ€” matches the `_WALLET_LOCK_MAX` pattern in `bot_engine`. - ~~**Reconciler runs wallets sequentially** (BUG-05, FIXED 2026-05-29):~~ `reconcile_all_once` now fans out with `asyncio.gather` + `asyncio.Semaphore(10)`. Worst-case tail latency is `ceil(N/10) ร— 30s` instead of `N ร— 30s`. - ~~**`/stop` also silently disables daily digest** (BUG-07, FIXED 2026-05-29):~~ `send_daily_digest` no longer filters on `alerts_enabled` โ€” digest and real-time alerts are independent. `/stop` reply updated to say "Real-time alerts paused โ€ฆ send `/digest off` to stop the daily brief separately". - ~~**`binance.py` ASSET_MAP only had BTC + ETH** (BUG-08, CRITICAL, FIXED 2026-05-29):~~ Trump's AI can set `target_asset` to SOL/TRUMP/BNB/DOGE/LINK/AAVE. `ASSET_MAP` now covers all those; `BINANCE_WS_URL` is derived from it automatically so the stream list and routing table can never diverge. **Still missing**: HYPE (HL-native, not on Binance) โ€” trades on HYPE fall back to max-hold only until a supplemental HL price feed is added. - ~~**`_close_locks` leaked one entry per concurrent-close loser** (BUG-11, FIXED 2026-05-29):~~ `close_and_finalize` now pops `_close_locks[trade_id]` in the `rowcount == 0` early-return path, not just on the success paths. - ~~**`signed_request._seen` O(n) purge at 5000 entries** (BUG-12, FIXED 2026-05-29):~~ Threshold reduced to `_SEEN_PURGE_THRESHOLD = 1000` and the purge now builds an `expired` list in one pass rather than calling `dict.pop` in a loop. - ~~**`_get_max_leverage` makes a fresh HL `meta()` call on every trade open** (BUG-10, FIXED 2026-05-29):~~ `_MAX_LEV_CACHE` added (same 300s TTL as `_SZ_DECIMALS_CACHE`). Cache miss populates ALL coins from the single `meta()` response, so concurrent opens pay one API call, not N. - ~~**Trump daily budget split**~~ (FIXED 2026-05-29): System-2 is manage-only so the `sys2_pct` reservation no longer makes sense for auto-opens. `daily_cap` for System-1 (Trump) is now `total_cap ร— 1.0` instead of `total_cap ร— (1 - sys2_pct)`. The split logic remains intact so it would work correctly if sys2 auto-open is ever re-enabled via ADR. - ~~**HL high-leverage adoption**~~ (BUG-09, FIXED 2026-05-29): `adopt_position` now rejects positions where `leverage > SYS2_MAX_LEVERAGE` with error code `leverage_too_high`. Previously the protective stop was computed for 10ร— but applied to a 25ร— position โ€” the stop was OUTSIDE the liquidation band. - **`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**: `getUpdates` may replay last 24h of messages on backend restart. Stale `/adopt` could re-fire. User can /release to recover. - ~~**`pyramid_add` double-add on DB commit failure** (BUG-13, FIXED 2026-05-29):~~ Same pattern as BUG-03 / partial_derisk. HL `open_position` succeeds but the subsequent `db.commit()` of `addon_steps_done` fails โ†’ retrigger sees same `step_idx` and double-adds. Fix: pre-claim `addon_steps_done = step_idx + 1` with a conditional UPDATE (WHERE `addon_steps_done == step_idx`) BEFORE calling HL. If `rowcount == 0`, return early. Second UPDATE writes `entry_price` and `size_usd` after fill confirmation. - ~~**`price_impact_monitor` measured wrong asset** (BUG-14, FIXED 2026-05-29):~~ `truth_social.py` passed `analysis["asset"]` (BTC/ETH sentiment proxy) to `register_post` instead of `target_asset` (SOL/TRUMP/etc. โ€” the perp we actually trade). Impact % was measuring BTC/ETH not the traded coin. Fix: introduced `tracked_asset = analysis.get("target_asset") or asset` and used it for `price_at_post`, `price_impact_asset`, and `register_post(asset=โ€ฆ)`. --- ## Repos in this project - **This repo** (`/Users/k/Public/Claude/backend`) โ€” Python/FastAPI backend - **Sibling frontend** (`/Users/k/Public/Claude/trumpsignal`) โ€” 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.