fix(executor): keep strong references to fire-and-forget Telegram notification tasks
CI/CD / build-and-push (push) Successful in 7s

asyncio.create_task() results were discarded, and the event loop only holds
a weak reference to running tasks — a pending notification could be
garbage-collected before executing, silently dropping Telegram messages
(documented asyncio pitfall).

Route the three notification call sites (trade_opened, trade_legacy_closed,
trade_closed) through _notify_in_background(), which stores the task in a
module-level set and discards it on completion. Notifications stay
fire-and-forget; no business logic changed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
chemavx
2026-06-11 17:12:03 +00:00
co-authored by Claude Fable 5
parent 4867141c4b
commit 02cbfc0b94
+16 -3
View File
@@ -22,6 +22,19 @@ log = logging.getLogger(__name__)
# NOTE: this is a heuristic — see COMMISSION_RATE in bayesian.py for context. # NOTE: this is a heuristic — see COMMISSION_RATE in bayesian.py for context.
POLYMARKET_FEE = 0.02 # 2% 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 @dataclass
class Trade: class Trade:
@@ -205,7 +218,7 @@ class PaperExecutor:
# Persist to DB # Persist to DB
await self._db.save_trade(trade) 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) 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", "LEGACY_CLOSE market=%s | returned $%.2f to cash | %s",
market_id, cost, reason[:80], market_id, cost, reason[:80],
) )
asyncio.create_task( _notify_in_background(
telegram.trade_legacy_closed(question or market_id, cost, reason) telegram.trade_legacy_closed(question or market_id, cost, reason)
) )
return cost return cost
@@ -281,7 +294,7 @@ class PaperExecutor:
"Closed position in %s, resolution=%.1f payout=$%.2f pnl=%+.2f", "Closed position in %s, resolution=%.1f payout=$%.2f pnl=%+.2f",
market_id, resolution, payout, pnl, market_id, resolution, payout, pnl,
) )
asyncio.create_task( _notify_in_background(
telegram.trade_closed(question or market_id, pnl) telegram.trade_closed(question or market_id, pnl)
) )
return pnl return pnl