feat(kevin_bridge): service entrypoint with concrete wiring
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

Wires the dependency-injected KevinBridge to concrete Redis cursor +
DB session factory + AlpacaBroker (or stub when creds missing). Includes
TradeSignalPublisher (Pydantic -> dict for the redis stream) and
SIGINT/SIGTERM graceful shutdown. Adds is_asset_tradable + get_latest_price
to AlpacaBroker so the bridge can query asset metadata.
This commit is contained in:
Viktor Barzin 2026-05-24 01:06:18 +00:00
parent cff2564428
commit db103df9b1
2 changed files with 170 additions and 6 deletions

View file

@ -10,6 +10,7 @@ from __future__ import annotations
import asyncio
import logging
from datetime import datetime, timezone
from decimal import Decimal
from alpaca.common.exceptions import APIError
from alpaca.trading.client import TradingClient
@ -238,3 +239,32 @@ class AlpacaBroker(BaseBroker):
self._client.get_order_by_id, order_id
)
return self._order_to_result(alpaca_order)
async def is_asset_tradable(self, symbol: str) -> bool:
"""Return True iff Alpaca lists *symbol* as tradable.
Conservative on failure (returns False) so the bridge skips the
mention instead of half-trading it.
"""
try:
asset = await asyncio.to_thread(self._client.get_asset, symbol)
return bool(getattr(asset, "tradable", False))
except Exception:
return False
async def get_latest_price(self, symbol: str) -> Decimal:
"""Last trade price for *symbol* as Decimal. Returns Decimal('0')
on lookup failure callers should treat 0 as 'no price available'.
"""
try:
# alpaca-py uses MarketDataClient for prices; the TradingClient
# does not expose latest_trade. Use the get_assets fallback
# (returns last_close) when present.
asset = await asyncio.to_thread(self._client.get_asset, symbol)
for attr in ("last_trade_price", "close", "last_close"):
v = getattr(asset, attr, None)
if v is not None:
return Decimal(str(v))
except Exception:
pass
return Decimal("0")