fix(critical): remove dead manifold.get_probability() from legacy scan and fix BUY_NO payout calculation
CI/CD / build-and-push (push) Successful in 29s
CI/CD / build-and-push (push) Successful in 29s
- Legacy scan called ManifoldClient.get_probability(), removed in the v3
matcher migration, causing AttributeError when positions had changed
family keys. The block used Manifold to escalate positions to
CLOSE_RECOMMENDED (inversion detection) — a trading decision forbidden
under MANIFOLD_SIGNAL_ENABLED=false — so the dependency is removed
entirely; the scan keeps family re-keying and sibling-conflict logic.
- PaperExecutor.close_position() computed cash += position_cost * resolution,
ignoring direction: a winning BUY_NO (resolution=0.0) paid out $0 and
reported a loss. Now settles per trade:
BUY_YES: payout = shares * resolution
BUY_NO: payout = shares * (1 - resolution)
with pnl = payout - net_cost; Telegram win/loss keys off pnl > 0.
Adds read-only Database.get_open_trades_for_market().
- tests/test_paper_close.py covers the 4 deterministic payout cases;
tests/conftest.py shims datetime.UTC for local Python 3.10 (prod is 3.11).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
3a353c7e5b
commit
340c8523cf
+34
-9
@@ -235,24 +235,49 @@ class PaperExecutor:
|
||||
"""Close a paper position after market resolution.
|
||||
|
||||
resolution: 1.0 if YES won, 0.0 if NO won.
|
||||
Persists resolution and close_pnl to DB (computed via SQL from stored
|
||||
entry_price and shares). Returns approximate P&L for logging.
|
||||
Settlement payout per trade:
|
||||
BUY_YES: shares * resolution
|
||||
BUY_NO: shares * (1 - resolution)
|
||||
pnl = payout - net_cost.
|
||||
Persists resolution and close_pnl to DB. Returns realized P&L for
|
||||
logging, or None if no position is open.
|
||||
"""
|
||||
if market_id not in self._portfolio.positions:
|
||||
return None
|
||||
|
||||
position_cost = self._portfolio.positions.pop(market_id)
|
||||
self._portfolio.cash += position_cost * resolution # pay out winnings
|
||||
open_trades = await self._db.get_open_trades_for_market(market_id)
|
||||
|
||||
if open_trades:
|
||||
payout = sum(
|
||||
float(t["shares"])
|
||||
* (resolution if t["direction"] == "BUY_YES" else 1.0 - resolution)
|
||||
for t in open_trades
|
||||
)
|
||||
net_cost = sum(float(t["net_cost"]) for t in open_trades)
|
||||
pnl = payout - net_cost
|
||||
else:
|
||||
# In-memory position with no open DB trades: direction/shares are
|
||||
# unknown, so settle at break-even instead of guessing the payout.
|
||||
log.warning(
|
||||
"close_position: no open DB trades for market %s — "
|
||||
"settling at break-even", market_id,
|
||||
)
|
||||
payout = position_cost
|
||||
pnl = 0.0
|
||||
|
||||
self._portfolio.cash += payout
|
||||
|
||||
await self._db.close_paper_position(
|
||||
market_id,
|
||||
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)
|
||||
log.info(
|
||||
"Closed position in %s, resolution=%.1f payout=$%.2f pnl=%+.2f",
|
||||
market_id, resolution, payout, pnl,
|
||||
)
|
||||
# Approximate PnL: settlement value minus cost. Exact value is in close_pnl.
|
||||
return approx_pnl
|
||||
asyncio.create_task(
|
||||
telegram.trade_closed(question or market_id, pnl)
|
||||
)
|
||||
return pnl
|
||||
|
||||
Reference in New Issue
Block a user