add support for alternative ticker universe formatting
This commit is contained in:
+14
-3
@@ -25,13 +25,24 @@ class AlpacaOHLCVProvider:
|
|||||||
raise ProviderError("Alpaca API key and secret are required")
|
raise ProviderError("Alpaca API key and secret are required")
|
||||||
self._client = StockHistoricalDataClient(api_key, api_secret)
|
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(
|
async def fetch_ohlcv(
|
||||||
self, ticker: str, start_date: date, end_date: date
|
self, ticker: str, start_date: date, end_date: date
|
||||||
) -> list[OHLCVData]:
|
) -> list[OHLCVData]:
|
||||||
"""Fetch daily OHLCV bars for *ticker* between *start_date* and *end_date*."""
|
"""Fetch daily OHLCV bars for *ticker* between *start_date* and *end_date*."""
|
||||||
|
alpaca_symbol = self._to_alpaca_symbol(ticker)
|
||||||
try:
|
try:
|
||||||
request = StockBarsRequest(
|
request = StockBarsRequest(
|
||||||
symbol_or_symbols=ticker,
|
symbol_or_symbols=alpaca_symbol,
|
||||||
timeframe=TimeFrame.Day,
|
timeframe=TimeFrame.Day,
|
||||||
start=start_date,
|
start=start_date,
|
||||||
end=end_date,
|
end=end_date,
|
||||||
@@ -42,11 +53,11 @@ class AlpacaOHLCVProvider:
|
|||||||
bars = await asyncio.to_thread(self._client.get_stock_bars, request)
|
bars = await asyncio.to_thread(self._client.get_stock_bars, request)
|
||||||
|
|
||||||
results: list[OHLCVData] = []
|
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:
|
for bar in bar_set:
|
||||||
results.append(
|
results.append(
|
||||||
OHLCVData(
|
OHLCVData(
|
||||||
ticker=ticker,
|
ticker=ticker, # use original internal symbol
|
||||||
date=bar.timestamp.date(),
|
date=bar.timestamp.date(),
|
||||||
open=float(bar.open),
|
open=float(bar.open),
|
||||||
high=float(bar.high),
|
high=float(bar.high),
|
||||||
|
|||||||
@@ -38,6 +38,14 @@ def _safe_float(value: object) -> float | None:
|
|||||||
return 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:
|
class FinnhubFundamentalProvider:
|
||||||
"""Fundamentals provider backed by Finnhub free endpoints."""
|
"""Fundamentals provider backed by Finnhub free endpoints."""
|
||||||
|
|
||||||
@@ -49,19 +57,20 @@ class FinnhubFundamentalProvider:
|
|||||||
|
|
||||||
async def fetch_fundamentals(self, ticker: str) -> FundamentalData:
|
async def fetch_fundamentals(self, ticker: str) -> FundamentalData:
|
||||||
unavailable: dict[str, str] = {}
|
unavailable: dict[str, str] = {}
|
||||||
|
api_symbol = _to_api_symbol(ticker)
|
||||||
|
|
||||||
async with httpx.AsyncClient(timeout=30.0, verify=_CA_BUNDLE_PATH) as client:
|
async with httpx.AsyncClient(timeout=30.0, verify=_CA_BUNDLE_PATH) as client:
|
||||||
profile_resp = await client.get(
|
profile_resp = await client.get(
|
||||||
f"{self._base_url}/stock/profile2",
|
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(
|
metric_resp = await client.get(
|
||||||
f"{self._base_url}/stock/metric",
|
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(
|
earnings_resp = await client.get(
|
||||||
f"{self._base_url}/stock/earnings",
|
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 (
|
for resp, endpoint in (
|
||||||
@@ -121,19 +130,20 @@ class AlphaVantageFundamentalProvider:
|
|||||||
|
|
||||||
async def fetch_fundamentals(self, ticker: str) -> FundamentalData:
|
async def fetch_fundamentals(self, ticker: str) -> FundamentalData:
|
||||||
unavailable: dict[str, str] = {}
|
unavailable: dict[str, str] = {}
|
||||||
|
api_symbol = _to_api_symbol(ticker)
|
||||||
|
|
||||||
async with httpx.AsyncClient(timeout=30.0, verify=_CA_BUNDLE_PATH) as client:
|
async with httpx.AsyncClient(timeout=30.0, verify=_CA_BUNDLE_PATH) as client:
|
||||||
overview_resp = await client.get(
|
overview_resp = await client.get(
|
||||||
self._base_url,
|
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(
|
earnings_resp = await client.get(
|
||||||
self._base_url,
|
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(
|
income_resp = await client.get(
|
||||||
self._base_url,
|
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 (
|
for resp, endpoint in (
|
||||||
|
|||||||
Reference in New Issue
Block a user