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
+25
View File
@@ -103,3 +103,28 @@ def test_unknown_market_returns_none():
ex = PaperExecutor(db=FakeDB({}), bankroll=1000.0)
return await ex.close_position("nope", 1.0)
assert asyncio.run(run()) is None
def test_db_failure_keeps_position_for_retry():
"""Regression: a DB error during close must not mutate the in-memory
portfolio — otherwise the next resolution check skips the market
(not in positions) and the DB row stays open forever."""
class FailingDB(FakeDB):
async def close_paper_position(self, market_id, reason="", resolution=None):
raise RuntimeError("db down")
async def run():
db = FailingDB({
"mkt1": [{"direction": "BUY_YES", "shares": 200.0, "net_cost": 102.0}],
})
ex = PaperExecutor(db=db, bankroll=1000.0)
ex._portfolio.cash = 898.0
ex._portfolio.positions["mkt1"] = 100.0
with pytest.raises(RuntimeError):
await ex.close_position("mkt1", 1.0)
return ex
ex = asyncio.run(run())
assert ex._portfolio.positions == {"mkt1": 100.0} # still open in memory
assert ex._portfolio.cash == pytest.approx(898.0) # payout not credited