fix(resolution): cast $3 explicitly in close_paper_position and persist before mutating portfolio
CI/CD / build-and-push (push) Successful in 8s

Cycle-10 resolution check found market 562186 resolved (Paxton YES) but the
close failed with asyncpg AmbiguousParameterError: Postgres cannot infer the
type of a bare '$3 IS NOT NULL' in the close_pnl CASE. Reproduced via PREPARE
in the postgres pod; fixed by casting every $3 use to double precision.

The failed DB write also left memory/DB diverged: close_position() popped the
position and credited cash before persisting, so the retry at cycle 20 skipped
the market (pnl=n/a) while the DB row stayed open. Now the DB write happens
first and memory mutates only on success; check_resolutions() also isolates
per-market close failures so one error doesn't abort the cycle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
chemavx
2026-06-11 14:15:05 +00:00
co-authored by Claude Fable 5
parent e137116e7f
commit 5aa54eb423
4 changed files with 45 additions and 9 deletions
+7 -3
View File
@@ -245,7 +245,7 @@ class PaperExecutor:
if market_id not in self._portfolio.positions:
return None
position_cost = self._portfolio.positions.pop(market_id)
position_cost = self._portfolio.positions[market_id]
open_trades = await self._db.get_open_trades_for_market(market_id)
if open_trades:
@@ -266,13 +266,17 @@ class PaperExecutor:
payout = position_cost
pnl = 0.0
self._portfolio.cash += payout
# Persist first, mutate memory after: if the DB write fails, the
# in-memory portfolio must keep the position so the next resolution
# check can retry the close.
await self._db.close_paper_position(
market_id,
reason="resolved",
resolution=resolution,
)
self._portfolio.positions.pop(market_id)
self._portfolio.cash += payout
log.info(
"Closed position in %s, resolution=%.1f payout=$%.2f pnl=%+.2f",
market_id, resolution, payout, pnl,