add support for alternative ticker universe formatting
Deploy / lint (push) Successful in 5s
Deploy / test (push) Successful in 26s
Deploy / deploy (push) Successful in 19s

This commit is contained in:
2026-03-07 17:27:57 +01:00
parent d331f551e3
commit 79ca19f45f
2 changed files with 30 additions and 9 deletions
+14 -3
View File
@@ -25,13 +25,24 @@ class AlpacaOHLCVProvider:
raise ProviderError("Alpaca API key and secret are required")
self._client = StockHistoricalDataClient(api_key, api_secret)
@staticmethod
def _to_alpaca_symbol(symbol: str) -> str:
"""Convert internal symbol format (BRK-B) to Alpaca format (BRK.B)."""
return symbol.replace("-", ".")
@staticmethod
def _from_alpaca_symbol(symbol: str) -> str:
"""Convert Alpaca symbol format (BRK.B) back to internal format (BRK-B)."""
return symbol.replace(".", "-")
async def fetch_ohlcv(
self, ticker: str, start_date: date, end_date: date
) -> list[OHLCVData]:
"""Fetch daily OHLCV bars for *ticker* between *start_date* and *end_date*."""
alpaca_symbol = self._to_alpaca_symbol(ticker)
try:
request = StockBarsRequest(
symbol_or_symbols=ticker,
symbol_or_symbols=alpaca_symbol,
timeframe=TimeFrame.Day,
start=start_date,
end=end_date,
@@ -42,11 +53,11 @@ class AlpacaOHLCVProvider:
bars = await asyncio.to_thread(self._client.get_stock_bars, request)
results: list[OHLCVData] = []
bar_set = bars.get(ticker, []) if hasattr(bars, "get") else getattr(bars, "data", {}).get(ticker, [])
bar_set = bars.get(alpaca_symbol, []) if hasattr(bars, "get") else getattr(bars, "data", {}).get(alpaca_symbol, [])
for bar in bar_set:
results.append(
OHLCVData(
ticker=ticker,
ticker=ticker, # use original internal symbol
date=bar.timestamp.date(),
open=float(bar.open),
high=float(bar.high),
+16 -6
View File
@@ -38,6 +38,14 @@ def _safe_float(value: object) -> float | None:
return None
def _to_api_symbol(symbol: str) -> str:
"""Convert internal symbol format (BRK-B) to API format (BRK.B).
Finnhub and Alpha Vantage use dot-separated share class notation.
"""
return symbol.replace("-", ".")
class FinnhubFundamentalProvider:
"""Fundamentals provider backed by Finnhub free endpoints."""
@@ -49,19 +57,20 @@ class FinnhubFundamentalProvider:
async def fetch_fundamentals(self, ticker: str) -> FundamentalData:
unavailable: dict[str, str] = {}
api_symbol = _to_api_symbol(ticker)
async with httpx.AsyncClient(timeout=30.0, verify=_CA_BUNDLE_PATH) as client:
profile_resp = await client.get(
f"{self._base_url}/stock/profile2",
params={"symbol": ticker, "token": self._api_key},
params={"symbol": api_symbol, "token": self._api_key},
)
metric_resp = await client.get(
f"{self._base_url}/stock/metric",
params={"symbol": ticker, "metric": "all", "token": self._api_key},
params={"symbol": api_symbol, "metric": "all", "token": self._api_key},
)
earnings_resp = await client.get(
f"{self._base_url}/stock/earnings",
params={"symbol": ticker, "limit": 1, "token": self._api_key},
params={"symbol": api_symbol, "limit": 1, "token": self._api_key},
)
for resp, endpoint in (
@@ -121,19 +130,20 @@ class AlphaVantageFundamentalProvider:
async def fetch_fundamentals(self, ticker: str) -> FundamentalData:
unavailable: dict[str, str] = {}
api_symbol = _to_api_symbol(ticker)
async with httpx.AsyncClient(timeout=30.0, verify=_CA_BUNDLE_PATH) as client:
overview_resp = await client.get(
self._base_url,
params={"function": "OVERVIEW", "symbol": ticker, "apikey": self._api_key},
params={"function": "OVERVIEW", "symbol": api_symbol, "apikey": self._api_key},
)
earnings_resp = await client.get(
self._base_url,
params={"function": "EARNINGS", "symbol": ticker, "apikey": self._api_key},
params={"function": "EARNINGS", "symbol": api_symbol, "apikey": self._api_key},
)
income_resp = await client.get(
self._base_url,
params={"function": "INCOME_STATEMENT", "symbol": ticker, "apikey": self._api_key},
params={"function": "INCOME_STATEMENT", "symbol": api_symbol, "apikey": self._api_key},
)
for resp, endpoint in (