feat(notify): Telegram alerts on trade open and close
CI/CD / build-and-push (push) Successful in 7s

New module bot/notify/telegram.py — httpx async, fire-and-forget via
asyncio.create_task, swallows all errors so notifications never affect
trade execution.

Three alert types:
  📈/📉 TRADE ABIERTO  — direction, size, edge_net (in execute())
  /  GANADO/PERDIDO — approx PnL (in close_position())
  🔒    LEGACY CLOSE   — recovered capital + reason (in close_legacy_position())

close_position() and close_legacy_position() gain an optional question=""
param so the message shows the market name instead of market_id.
bot/main.py updated to pass question= to close_legacy_position().

Credentials (TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID) read from env vars
injected via bot-secrets k8s secret.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chemavx
2026-04-26 15:02:39 +00:00
parent 1f40c59e3c
commit 39cebd3be3
4 changed files with 70 additions and 4 deletions
+16 -3
View File
@@ -4,6 +4,7 @@ Paper Trading Executor — simulates order execution without real money.
Simulates realistic slippage and fees to get accurate paper P&L.
All trades are logged to PostgreSQL for metrics analysis.
"""
import asyncio
import logging
import uuid
from dataclasses import dataclass, field
@@ -12,6 +13,7 @@ from typing import Optional
from bot.risk.manager import Order, Portfolio
from bot.data.db import Database
from bot.notify import telegram
log = logging.getLogger(__name__)
@@ -183,9 +185,13 @@ class PaperExecutor:
# Persist to DB
await self._db.save_trade(trade)
asyncio.create_task(
telegram.trade_opened(trade.question, trade.direction, trade.size_usdc, trade.edge_net)
)
return trade
async def close_legacy_position(self, market_id: str, reason: str) -> float:
async def close_legacy_position(self, market_id: str, reason: str, question: str = "") -> float:
"""
Close a paper position flagged by the legacy scan.
@@ -200,9 +206,12 @@ class PaperExecutor:
"LEGACY_CLOSE market=%s | returned $%.2f to cash | %s",
market_id, cost, reason[:80],
)
asyncio.create_task(
telegram.trade_legacy_closed(question or market_id, cost, reason)
)
return cost
async def close_position(self, market_id: str, resolution: float) -> Optional[float]:
async def close_position(self, market_id: str, resolution: float, question: str = "") -> Optional[float]:
"""Close a paper position after market resolution.
resolution: 1.0 if YES won, 0.0 if NO won.
@@ -220,6 +229,10 @@ class PaperExecutor:
reason=f"market_resolved resolution={resolution:.1f}",
resolution=resolution,
)
approx_pnl = position_cost * resolution - position_cost
log.info("Closed position in %s, resolution=%.1f", market_id, resolution)
asyncio.create_task(
telegram.trade_closed(question or market_id, approx_pnl)
)
# Approximate PnL: settlement value minus cost. Exact value is in close_pnl.
return position_cost * resolution - position_cost
return approx_pnl