feat: Telegram daily digest + System-2 manage-only refactor
Two changes ship together — both reshape the Telegram-bot surface.
──── 1. Telegram daily digest (3-section brief)
Once-a-day push covering Macro Vibes / KOL talks-vs-trades / Trump 24h.
Body is rule-based templating (no LLM); each section reads structured DB
fields and picks a phrasing. Per-user opt-out + per-user UTC hour.
- 023 migration: TelegramBinding gains digest_enabled, digest_hour_utc,
last_digest_sent_at (idempotent against coalesced cron / restarts).
- New services/telegram_digest.py: build_global_digest +
format_digest + send_daily_digest + send_preview_for.
- Hourly cron at :00 fans out to bindings whose digest_hour_utc matches.
- New bot commands: /digest (preview), /digest on|off, /digest_time HH.
- HELP_TEXT + /status updated to surface the new prefs.
──── 2. System-2 manage-only refactor
The Macro Vibes (BTC bottom + funding) signal no longer auto-opens
positions. Strategy is day-K — a 24h entry delay is irrelevant — but the
auto-open path carried real execution surface (leverage clipping, daily
budget split, concurrency caps, paper branches, key handling) for ~zero
alpha. The valuable part — multi-month exit management (5-rung stop
ladder + de-risk + pyramid + peak-trail) — is preserved and runs
against positions the user adopts.
Flow: scanner fires → Telegram alert with "/adopt" CTA → user opens
manually on Hyperliquid → /adopt picks the position via inline keyboard
→ picks Standard or Aggressive mode → bot creates BotTrade + registers
watchdog. Escape hatch: /release marks released_at, unregisters
watchdog, leaves the HL position open under user control.
- 024 migration: BotTrade.released_at — "user took back control" marker.
- bot_engine.process_post early-returns for sys2 (no auto-open path).
- New services/adoption.py: list_hl_positions + adopt_position +
release_management + AdoptionError. Per-wallet asyncio lock prevents
dual-adopt race. Pre-flight checks: no_subscription / no_hl_key /
paper_mode / circuit_breaker / already_adopted / concurrency_cap.
Protective stop + de-risk + addon + peak-trail ladders all built
against the ACTUAL HL leverage so the "inside liquidation" guarantee
holds.
- New API: GET /positions/hl/{wallet}, POST /positions/adopt,
POST /positions/{id}/release (all signed).
- telegram.py: send_message supports reply_markup; new edit_message +
answer_callback for the inline-keyboard pickers; sys2 alert format
now ends with the "/adopt" CTA.
- telegram_bot.py: /adopt + /release commands with picker → confirm
→ execute flow via inline keyboards. New _handle_callback dispatches
on "adopt:*" / "release:*" callback_data; run_bot_loop now consumes
callback_query updates alongside messages.
- recovery.py + reconciler.py skip released_at IS NOT NULL rows so a
restart doesn't silently re-attach the watchdog to a released trade.
- /positions/open, /positions/today, and telegram_digest's user-state
line all filter released_at so they don't lie about what the bot is
actually managing.
Tests: 26 new (8 digest snapshot + 14 adoption + 4 absorbed via existing
suites). All 64 pass.
Deploy: alembic upgrade head (runs 023 + 024) → restart backend. Existing
TelegramBindings get digest_enabled=true / digest_hour_utc=12 via server
defaults. In-flight auto-opened System-2 positions continue to be managed
(recovery rehydrates them) — no in-flight trade is abandoned.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -98,6 +98,12 @@ class BotTrade(Base):
|
||||
wallet_address: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
|
||||
opened_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=utcnow)
|
||||
closed_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
|
||||
# User-initiated "stop managing" marker. When set: recovery skips
|
||||
# re-register on restart, monitor stops driving, HL position keeps
|
||||
# running under the user's own control. NOT the same as closed_at —
|
||||
# there's no exit_price / pnl on a released row. Used by /release in
|
||||
# the adopt-then-release flow for System-2 positions.
|
||||
released_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
|
||||
hl_order_id: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)
|
||||
# Snapshot of user settings AT TIME OF ENTRY. Required so changes to
|
||||
# Subscription.position_size_usd / leverage after-the-fact don't corrupt
|
||||
@@ -414,6 +420,14 @@ class TelegramBinding(Base):
|
||||
total_alerts_sent: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
total_alerts_failed: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
||||
|
||||
# Daily 3-section digest (Macro / KOL / Trump). Independent of the
|
||||
# per-signal alerts above — a user can disable all real-time alerts but
|
||||
# still want the once-a-day overview, or vice versa. See migration 023
|
||||
# and app/services/telegram_digest.py.
|
||||
digest_enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
||||
digest_hour_utc: Mapped[int] = mapped_column(Integer, nullable=False, default=12)
|
||||
last_digest_sent_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
|
||||
|
||||
|
||||
class MacroSnapshot(Base):
|
||||
"""Daily snapshot of all macro indicators surfaced on the BTC page.
|
||||
|
||||
Reference in New Issue
Block a user