fix: position sync now reconciles — removes positions broker no longer holds

The sync_positions loop only upserted positions from Alpaca but never
deleted DB rows for positions that were closed/liquidated on the broker
side. After a paper reset, the next sync would not remove the stale
positions because they simply weren't in Alpaca's response anymore.

Now performs full reconciliation: after upserting what Alpaca reports,
deletes any DB positions for the account that Alpaca no longer holds.
This commit is contained in:
Celes Renata
2026-04-29 12:02:57 +00:00
parent 4e010bc048
commit bb40a3cb8e
+21 -2
View File
@@ -428,10 +428,16 @@ async def sync_positions(
account_uuid: str, account_uuid: str,
minio_client: Any | None = None, minio_client: Any | None = None,
) -> None: ) -> None:
"""Sync current positions from Alpaca to PostgreSQL and publish to lake.""" """Sync current positions from Alpaca to PostgreSQL and publish to lake.
Performs a full reconciliation: upserts positions that Alpaca reports,
then removes any DB positions that Alpaca no longer holds (e.g. after
a paper reset or full liquidation).
"""
now = datetime.now(timezone.utc) now = datetime.now(timezone.utc)
try: try:
positions = await adapter.get_positions() positions = await adapter.get_positions()
broker_tickers = {pos.ticker for pos in positions}
async with pool.acquire() as conn: async with pool.acquire() as conn:
for pos in positions: for pos in positions:
await conn.execute( await conn.execute(
@@ -444,7 +450,20 @@ async def sync_positions(
pos.unrealized_pnl, pos.unrealized_pnl,
now, now,
) )
logger.info("Synced %d positions from Alpaca", len(positions)) # Remove positions that the broker no longer reports (closed/liquidated)
if broker_tickers:
await conn.execute(
"DELETE FROM positions WHERE broker_account_id = $1::uuid AND ticker != ALL($2::varchar[])",
account_uuid,
list(broker_tickers),
)
else:
# Broker reports zero positions — clear all local positions for this account
await conn.execute(
"DELETE FROM positions WHERE broker_account_id = $1::uuid",
account_uuid,
)
logger.info("Synced %d positions from Alpaca (reconciled)", len(positions))
POSITIONS_SYNCED.inc() POSITIONS_SYNCED.inc()
# Publish positions snapshot to analytical lake # Publish positions snapshot to analytical lake