feat(kevin): reconcile Alpaca bracket auto-closes + order status

Bracket stop-loss/take-profit legs fill at Alpaca without passing through the executor, so those closes (and their P&L) were invisible locally.

- broker: add get_order(nested) + list_orders to BaseBroker/AlpacaBroker (+ SimulatedBroker); BrokerOrder carries child legs

- Trade gains broker_order_id (migration f6a7b8c9d0e1); executor stamps the entry order id

- new api_gateway trade-reconcile loop: books a closing SELL + realized P&L when a bracket leg fills (idempotent on the leg order id), syncs PENDING->terminal status, logs drift; runs alongside portfolio_sync

[ci skip]

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Viktor Barzin 2026-06-04 22:31:24 +00:00
parent 52b3c76482
commit 82dc622544
13 changed files with 1049 additions and 8 deletions

View file

@ -9,7 +9,13 @@ changing strategy or execution logic.
from abc import ABC, abstractmethod
from shared.schemas.trading import AccountInfo, OrderRequest, OrderResult, PositionInfo
from shared.schemas.trading import (
AccountInfo,
BrokerOrder,
OrderRequest,
OrderResult,
PositionInfo,
)
class BaseBroker(ABC):
@ -85,3 +91,45 @@ class BaseBroker(ABC):
Current state of the order including fill price if applicable.
"""
...
@abstractmethod
async def get_order(
self, order_id: str, *, nested: bool = True
) -> BrokerOrder | None:
"""Fetch an order including its bracket child legs.
Parameters
----------
order_id:
The brokerage-assigned order identifier.
nested:
When ``True`` (the default), child legs (stop-loss / take-profit)
are populated on the returned :class:`BrokerOrder` so callers can
tell which leg filled and at what price.
Returns
-------
BrokerOrder | None
The order with its legs, or ``None`` if the order does not exist.
"""
...
@abstractmethod
async def list_orders(
self, *, status: str = "all", limit: int = 100
) -> list[OrderResult]:
"""List orders, optionally filtered by status.
Parameters
----------
status:
One of ``"open"``, ``"closed"``, or ``"all"`` (the default).
limit:
Maximum number of orders to return.
Returns
-------
list[OrderResult]
One entry per matching order.
"""
...