diff --git a/services/api/app.py b/services/api/app.py index 3148134..d208f8b 100644 --- a/services/api/app.py +++ b/services/api/app.py @@ -1179,7 +1179,12 @@ async def get_order(order_id: str): async def list_positions( ticker: Optional[str] = None, ): - """List current positions.""" + """List current positions with Polygon market prices overlaid. + + The current_price from the broker (Alpaca paper) can be stale or + inaccurate. We overlay the latest close from market_snapshots + (Polygon daily bars) and recompute unrealized P&L from that. + """ if ticker: rows = await pool.fetch( """SELECT p.id, p.broker_account_id, p.ticker, p.quantity, @@ -1195,7 +1200,31 @@ async def list_positions( p.unrealized_pnl, p.realized_pnl, p.updated_at FROM positions p ORDER BY p.ticker""", ) - return [_row_to_dict(r) for r in rows] + + # Build a price map from the latest Polygon bars + tickers = list({r["ticker"] for r in rows}) + price_map: dict[str, float] = {} + if tickers: + price_rows = await pool.fetch( + """SELECT DISTINCT ON (ticker) ticker, (data->>'c')::float AS close + FROM market_snapshots + WHERE ticker = ANY($1) AND snapshot_type = 'bar' + ORDER BY ticker, captured_at DESC""", + tickers, + ) + price_map = {r["ticker"]: r["close"] for r in price_rows if r["close"]} + + results = [] + for r in rows: + d = _row_to_dict(r) + polygon_price = price_map.get(d["ticker"]) + if polygon_price: + d["current_price"] = polygon_price + qty = d.get("quantity", 0) or 0 + entry = d.get("avg_entry_price", 0) or 0 + d["unrealized_pnl"] = round(qty * (polygon_price - entry), 2) + results.append(d) + return results # ---------------------------------------------------------------------------