fix: position sync now reconciles — removes positions broker no longer holds
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/push/build-2 Pipeline failed
ci/woodpecker/push/build-1 Pipeline was successful
ci/woodpecker/push/build-3 Pipeline was successful
ci/woodpecker/push/finalize unknown status
Build and Push / lint-and-test (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.adapters.broker_adapter name:broker-adapter]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.aggregation.worker name:aggregation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.extractor.worker name:extractor]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.ingestion.worker name:ingestion]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.lake_publisher.worker name:lake-publisher]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.parser.worker name:parser]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.recommendation.worker name:recommendation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.scheduler.app name:scheduler]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.api.app:app --host 0.0.0.0 --port 8000 name:query-api]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.risk.app:app --host 0.0.0.0 --port 8000 name:risk]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.symbol_registry.app:app --host 0.0.0.0 --port 8000 name:symbol-registry]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.trading.app:app --host 0.0.0.0 --port 8000 name:trading-engine]) (push) Has been cancelled
Build and Push / build-dashboard (push) Has been cancelled
Build and Push / build-superset (push) Has been cancelled
Build and Push / integration-test (push) Has been cancelled
Build and Push / beta-gate (push) Has been cancelled
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/push/build-2 Pipeline failed
ci/woodpecker/push/build-1 Pipeline was successful
ci/woodpecker/push/build-3 Pipeline was successful
ci/woodpecker/push/finalize unknown status
Build and Push / lint-and-test (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.adapters.broker_adapter name:broker-adapter]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.aggregation.worker name:aggregation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.extractor.worker name:extractor]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.ingestion.worker name:ingestion]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.lake_publisher.worker name:lake-publisher]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.parser.worker name:parser]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.recommendation.worker name:recommendation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.scheduler.app name:scheduler]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.api.app:app --host 0.0.0.0 --port 8000 name:query-api]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.risk.app:app --host 0.0.0.0 --port 8000 name:risk]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.symbol_registry.app:app --host 0.0.0.0 --port 8000 name:symbol-registry]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.trading.app:app --host 0.0.0.0 --port 8000 name:trading-engine]) (push) Has been cancelled
Build and Push / build-dashboard (push) Has been cancelled
Build and Push / build-superset (push) Has been cancelled
Build and Push / integration-test (push) Has been cancelled
Build and Push / beta-gate (push) Has been cancelled
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:
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user