feat(metrics): Fix 3 — DB-computed metrics, stateless tracker, resolution tracking
CI/CD / build-and-push (push) Successful in 1m47s

schema.sql
  trades:        + close_pnl, resolution (market outcome storage)
  metrics_daily: + unrealized_pnl_est, realized_pnl, open/closed/resolved_count

db.py
  close_paper_position(): accepts resolution; computes close_pnl in SQL
    BUY_YES: (resolution − entry_price) × shares
    BUY_NO:  ((1 − resolution) − entry_price) × shares
  save_daily_metrics(): persists new columns
  compute_metrics_from_db(): single DB query for all metrics; no in-memory state

tracker.py — complete rewrite (stateless)
  Removed self._trades, self._daily_returns, compute_metrics(), _compute_sharpe(),
  check_promotion_thresholds(), _empty_metrics()
  update_daily_summary() now reads compute_metrics_from_db() every cycle
  Safe across pod restarts: always reflects full DB history

paper.py
  close_position(): passes resolution to close_paper_position()

api/main.py  /api/summary
  Added unrealized_pnl_est (estimated, open trades) and realized_pnl (exact,
  closed+resolved) as separate fields alongside total_pnl
  win_rate: null if < 5 resolved trades (was proxy on entry_price < 0.5)
  calibration_score: Brier-based, null if < 10 resolved trades
  resolved_count exposed as field
  Each field annotated with: exact/estimated, source, null conditions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chemavx
2026-04-21 17:34:48 +00:00
parent 9b62636a3e
commit 9a5be27532
5 changed files with 268 additions and 160 deletions
+13 -6
View File
@@ -177,16 +177,23 @@ class PaperExecutor:
return cost
async def close_position(self, market_id: str, resolution: float) -> Optional[float]:
"""
Close a paper position after market resolution.
"""Close a paper position after market resolution.
resolution: 1.0 if YES won, 0.0 if NO won.
Returns P&L in USDC.
Persists resolution and close_pnl to DB (computed via SQL from stored
entry_price and shares). Returns approximate P&L for logging.
"""
if market_id not in self._portfolio.positions:
return None
# This would be called by a settlement watcher (future feature)
# For now, positions auto-expire at market end date
position_cost = self._portfolio.positions.pop(market_id)
log.info("Closed position in %s, resolution=%.0f", market_id, resolution)
self._portfolio.cash += position_cost * resolution # pay out winnings
await self._db.close_paper_position(
market_id,
reason=f"market_resolved resolution={resolution:.1f}",
resolution=resolution,
)
log.info("Closed position in %s, resolution=%.1f", market_id, resolution)
# Approximate PnL: settlement value minus cost. Exact value is in close_pnl.
return position_cost * resolution - position_cost