feat: reset endpoint now liquidates Alpaca positions and cancels orders
- Added cancel_all_orders() and close_all_positions() to AlpacaBrokerAdapter - Reset endpoint creates a temporary adapter to call Alpaca DELETE /v2/orders and DELETE /v2/positions before clearing DB and engine state - Also clears positions table and processed_recommendation_ids on reset - Broker reset is best-effort — DB/engine reset proceeds even if Alpaca fails
This commit is contained in:
@@ -276,6 +276,14 @@ class BrokerDataAdapter(BaseAdapter, ABC):
|
||||
"""Get account summary (balance, buying power, etc.)."""
|
||||
...
|
||||
|
||||
async def cancel_all_orders(self) -> int:
|
||||
"""Cancel all open orders. Returns the number of orders cancelled."""
|
||||
return 0
|
||||
|
||||
async def close_all_positions(self) -> int:
|
||||
"""Liquidate all open positions. Returns the number of positions closed."""
|
||||
return 0
|
||||
|
||||
|
||||
# --- Concrete Alpaca implementation ---
|
||||
|
||||
@@ -551,6 +559,50 @@ class AlpacaBrokerAdapter(BrokerDataAdapter):
|
||||
mode=self._mode,
|
||||
)
|
||||
|
||||
async def cancel_all_orders(self) -> int:
|
||||
"""Cancel all open orders on Alpaca.
|
||||
|
||||
Uses DELETE /v2/orders which cancels all open orders in bulk.
|
||||
Returns the number of orders that were cancelled.
|
||||
"""
|
||||
async with httpx.AsyncClient(timeout=30) as client:
|
||||
try:
|
||||
resp = await client.delete(
|
||||
f"{self.base_url}/v2/orders",
|
||||
headers=self._headers(),
|
||||
)
|
||||
resp.raise_for_status()
|
||||
# Alpaca returns a list of cancelled order objects (HTTP 207)
|
||||
data = resp.json()
|
||||
cancelled = len(data) if isinstance(data, list) else 0
|
||||
logger.info("Cancelled %d open orders on Alpaca", cancelled)
|
||||
return cancelled
|
||||
except Exception as e:
|
||||
logger.error("Cancel all orders failed: %s", e)
|
||||
return 0
|
||||
|
||||
async def close_all_positions(self) -> int:
|
||||
"""Liquidate all open positions on Alpaca.
|
||||
|
||||
Uses DELETE /v2/positions which closes all positions at market.
|
||||
Returns the number of positions that were closed.
|
||||
"""
|
||||
async with httpx.AsyncClient(timeout=30) as client:
|
||||
try:
|
||||
resp = await client.delete(
|
||||
f"{self.base_url}/v2/positions",
|
||||
headers=self._headers(),
|
||||
)
|
||||
resp.raise_for_status()
|
||||
# Alpaca returns a list of closed position order objects (HTTP 207)
|
||||
data = resp.json()
|
||||
closed = len(data) if isinstance(data, list) else 0
|
||||
logger.info("Closed %d positions on Alpaca", closed)
|
||||
return closed
|
||||
except Exception as e:
|
||||
logger.error("Close all positions failed: %s", e)
|
||||
return 0
|
||||
|
||||
def _parse_order_response(self, data: dict[str, Any]) -> OrderResponse:
|
||||
"""Parse an Alpaca order response into an OrderResponse."""
|
||||
status_map: dict[str, OrderStatus] = {
|
||||
|
||||
Reference in New Issue
Block a user