Files
momentum-system/alpaca_paper/sync.py
T
momentum devandClaude Opus 4.8 007cd95c79 Baseline before Postgres migration (WS0)
Snapshot before the DuckDB->PostgreSQL migration. data/, archive/, the 232MB
rebuild zip, the Windows .venv, and .env are excluded via .gitignore.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 23:39:29 -04:00

136 lines
4.6 KiB
Python

"""Sync the Alpaca paper account into the event store.
Emits account_summary_updated / account_positions_updated /
account_orders_updated events that the dashboard projections read.
"""
from __future__ import annotations
import logging
from datetime import datetime
from alpaca_paper.client import AlpacaPaperClient
from storage.event_schema import (
AccountOrdersUpdatedEvent,
AccountPositionsUpdatedEvent,
AccountSummaryUpdatedEvent,
EventMode,
)
from storage.event_store import EventStore
logger = logging.getLogger(__name__)
BROKER_NAME = "alpaca_paper"
class AlpacaPaperSync:
def __init__(
self,
store: EventStore,
client: AlpacaPaperClient | None = None,
session_id: str | None = None,
mode: EventMode = EventMode.PAPER,
):
self.store = store
self.client = client or AlpacaPaperClient()
self.session_id = session_id
self.mode = mode
def sync_account(self) -> dict | None:
try:
account = self.client.get_account()
except Exception:
logger.exception("alpaca account sync failed")
return None
account_id = str(account.get("account_number") or account.get("id") or "paper")
self.store.emit(
AccountSummaryUpdatedEvent(
timestamp=datetime.now(),
mode=self.mode,
correlation_id=self.session_id,
message=f"Alpaca paper equity {account.get('equity')}",
broker_name=BROKER_NAME,
account_id=account_id,
account_desc="Alpaca Paper",
total_equity=float(account.get("equity") or 0),
cash_balance=float(account.get("cash") or 0),
buying_power=float(account.get("buying_power") or 0),
net_liquidating_value=float(account.get("equity") or 0),
)
)
return account
def sync_positions(self) -> list[dict] | None:
try:
raw = self.client.get_positions()
except Exception:
logger.exception("alpaca positions sync failed")
return None
positions = [
{
"symbol": p.get("symbol"),
"quantity": float(p.get("qty") or 0),
"avg_entry_price": float(p.get("avg_entry_price") or 0),
"current_price": float(p.get("current_price") or 0),
"market_value": float(p.get("market_value") or 0),
"unrealized_pnl": float(p.get("unrealized_pl") or 0),
"unrealized_pnl_pct": float(p.get("unrealized_plpc") or 0),
"side": p.get("side"),
}
for p in raw
]
self.store.emit(
AccountPositionsUpdatedEvent(
timestamp=datetime.now(),
mode=self.mode,
correlation_id=self.session_id,
message=f"Alpaca paper positions: {len(positions)}",
broker_name=BROKER_NAME,
account_id="paper",
positions=positions,
)
)
return positions
def sync_orders(self, status: str = "all", limit: int = 50) -> list[dict] | None:
try:
raw = self.client.get_orders(status=status, limit=limit)
except Exception:
logger.exception("alpaca orders sync failed")
return None
orders = [
{
"broker_order_id": o.get("id"),
"client_order_id": o.get("client_order_id"),
"symbol": o.get("symbol"),
"side": o.get("side"),
"quantity": float(o.get("qty") or 0),
"filled_quantity": float(o.get("filled_qty") or 0),
"type": o.get("type"),
"status": o.get("status"),
"limit_price": o.get("limit_price"),
"filled_avg_price": o.get("filled_avg_price"),
"submitted_at": o.get("submitted_at"),
}
for o in raw
]
self.store.emit(
AccountOrdersUpdatedEvent(
timestamp=datetime.now(),
mode=self.mode,
correlation_id=self.session_id,
message=f"Alpaca paper orders: {len(orders)}",
broker_name=BROKER_NAME,
account_id="paper",
orders=orders,
)
)
return orders
def sync_all(self) -> dict:
return {
"account": self.sync_account(),
"positions": self.sync_positions(),
"orders": self.sync_orders(),
}