diff --git a/bot/executor/paper.py b/bot/executor/paper.py index 2b75bbe..eae38a5 100644 --- a/bot/executor/paper.py +++ b/bot/executor/paper.py @@ -22,6 +22,19 @@ log = logging.getLogger(__name__) # NOTE: this is a heuristic — see COMMISSION_RATE in bayesian.py for context. POLYMARKET_FEE = 0.02 # 2% +# Strong references to in-flight notification tasks. The event loop only +# keeps a weak reference to tasks created via create_task(), so without this +# set a pending Telegram notification could be garbage-collected before it +# runs. Tasks remove themselves from the set on completion. +_background_tasks: set[asyncio.Task] = set() + + +def _notify_in_background(coro) -> None: + """Fire-and-forget a Telegram notification, keeping the task referenced.""" + task = asyncio.create_task(coro) + _background_tasks.add(task) + task.add_done_callback(_background_tasks.discard) + @dataclass class Trade: @@ -205,7 +218,7 @@ class PaperExecutor: # Persist to DB await self._db.save_trade(trade) - asyncio.create_task( + _notify_in_background( telegram.trade_opened(trade.question, trade.direction, trade.size_usdc, trade.edge_net) ) @@ -226,7 +239,7 @@ class PaperExecutor: "LEGACY_CLOSE market=%s | returned $%.2f to cash | %s", market_id, cost, reason[:80], ) - asyncio.create_task( + _notify_in_background( telegram.trade_legacy_closed(question or market_id, cost, reason) ) return cost @@ -281,7 +294,7 @@ class PaperExecutor: "Closed position in %s, resolution=%.1f payout=$%.2f pnl=%+.2f", market_id, resolution, payout, pnl, ) - asyncio.create_task( + _notify_in_background( telegram.trade_closed(question or market_id, pnl) ) return pnl