feat: company names for tickers (Alpaca backfill + subtle display)
Store an optional company name on Ticker (migration 014) and backfill it from Alpaca's asset list in a single Trading-API call for the whole universe — no per-ticker fetch. Runs automatically at the end of universe bootstrap and via a manual "Backfill Names" button (admin) / POST /admin/tickers/backfill-names. The name ships on /tickers; a shared symbol→name map (useTickerNames) lets any view show it without its own request. Displayed subtly next to the symbol — in the global search, the ticker header, and as a small muted line under the symbol in Top Setups and Open Trades (no extra column, truncated so it never widens the table). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ well-known universes (S&P 500, NASDAQ-100, NASDAQ All).
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
@@ -357,6 +358,55 @@ async def fetch_universe_symbols(db: AsyncSession, universe: str) -> list[str]:
|
||||
raise ProviderError(f"Universe '{normalised_universe}' returned no valid symbols. Attempts: {reason}")
|
||||
|
||||
|
||||
async def _fetch_alpaca_asset_names() -> dict[str, str]:
|
||||
"""One Alpaca Trading-API call → {internal_symbol: company_name} for all US
|
||||
equities. Tries paper and live endpoints so it works with either key type."""
|
||||
if not settings.alpaca_api_key or not settings.alpaca_api_secret:
|
||||
raise ValidationError("Alpaca API credentials are required to backfill names")
|
||||
|
||||
from alpaca.trading.client import TradingClient
|
||||
from alpaca.trading.enums import AssetClass, AssetStatus
|
||||
from alpaca.trading.requests import GetAssetsRequest
|
||||
|
||||
req = GetAssetsRequest(status=AssetStatus.ACTIVE, asset_class=AssetClass.US_EQUITY)
|
||||
last_err: Exception | None = None
|
||||
for paper in (True, False):
|
||||
try:
|
||||
client = TradingClient(settings.alpaca_api_key, settings.alpaca_api_secret, paper=paper)
|
||||
assets = await asyncio.to_thread(client.get_all_assets, req)
|
||||
names: dict[str, str] = {}
|
||||
for asset in assets:
|
||||
sym = getattr(asset, "symbol", None)
|
||||
nm = getattr(asset, "name", None)
|
||||
if sym and nm:
|
||||
names[sym.replace(".", "-").upper()] = nm # BRK.B → BRK-B
|
||||
if names:
|
||||
return names
|
||||
except Exception as exc: # noqa: BLE001 — try the other endpoint
|
||||
last_err = exc
|
||||
|
||||
raise ProviderError(f"Failed to fetch asset names from Alpaca: {last_err}")
|
||||
|
||||
|
||||
async def backfill_ticker_names(db: AsyncSession, *, only_missing: bool = True) -> dict[str, int]:
|
||||
"""Fill Ticker.name from Alpaca in a single request for the whole universe."""
|
||||
result = await db.execute(select(Ticker))
|
||||
tickers = list(result.scalars().all())
|
||||
targets = [t for t in tickers if not t.name] if only_missing else tickers
|
||||
if not targets:
|
||||
return {"updated": 0, "checked": 0, "unmatched": 0}
|
||||
|
||||
names = await _fetch_alpaca_asset_names()
|
||||
updated = 0
|
||||
for ticker in targets:
|
||||
nm = names.get(ticker.symbol.upper())
|
||||
if nm and nm != ticker.name:
|
||||
ticker.name = nm[:120]
|
||||
updated += 1
|
||||
await db.commit()
|
||||
return {"updated": updated, "checked": len(targets), "unmatched": len(targets) - updated}
|
||||
|
||||
|
||||
async def bootstrap_universe(
|
||||
db: AsyncSession,
|
||||
universe: str,
|
||||
@@ -387,6 +437,13 @@ async def bootstrap_universe(
|
||||
|
||||
await db.commit()
|
||||
|
||||
# Best-effort: fill company names for any tickers still missing one. Never let
|
||||
# a name-fetch hiccup fail the bootstrap itself.
|
||||
try:
|
||||
await backfill_ticker_names(db, only_missing=True)
|
||||
except Exception: # noqa: BLE001
|
||||
logger.warning("Ticker name backfill failed during bootstrap", exc_info=True)
|
||||
|
||||
return {
|
||||
"universe": normalised_universe,
|
||||
"total_universe_symbols": len(symbols),
|
||||
|
||||
Reference in New Issue
Block a user