first commit
This commit is contained in:
63
app/providers/alpaca.py
Normal file
63
app/providers/alpaca.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""Alpaca Markets OHLCV provider using the alpaca-py SDK."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import date
|
||||
|
||||
from alpaca.data.historical import StockHistoricalDataClient
|
||||
from alpaca.data.requests import StockBarsRequest
|
||||
from alpaca.data.timeframe import TimeFrame
|
||||
|
||||
from app.exceptions import ProviderError, RateLimitError
|
||||
from app.providers.protocol import OHLCVData
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AlpacaOHLCVProvider:
|
||||
"""Fetches daily OHLCV bars from Alpaca Markets Data API."""
|
||||
|
||||
def __init__(self, api_key: str, api_secret: str) -> None:
|
||||
if not api_key or not api_secret:
|
||||
raise ProviderError("Alpaca API key and secret are required")
|
||||
self._client = StockHistoricalDataClient(api_key, api_secret)
|
||||
|
||||
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*."""
|
||||
try:
|
||||
request = StockBarsRequest(
|
||||
symbol_or_symbols=ticker,
|
||||
timeframe=TimeFrame.Day,
|
||||
start=start_date,
|
||||
end=end_date,
|
||||
)
|
||||
|
||||
# alpaca-py's client is synchronous — run in a thread
|
||||
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, [])
|
||||
for bar in bar_set:
|
||||
results.append(
|
||||
OHLCVData(
|
||||
ticker=ticker,
|
||||
date=bar.timestamp.date(),
|
||||
open=float(bar.open),
|
||||
high=float(bar.high),
|
||||
low=float(bar.low),
|
||||
close=float(bar.close),
|
||||
volume=int(bar.volume),
|
||||
)
|
||||
)
|
||||
return results
|
||||
|
||||
except Exception as exc:
|
||||
msg = str(exc).lower()
|
||||
if "rate" in msg and "limit" in msg:
|
||||
raise RateLimitError(f"Alpaca rate limit hit for {ticker}") from exc
|
||||
logger.error("Alpaca provider error for %s: %s", ticker, exc)
|
||||
raise ProviderError(f"Alpaca provider error for {ticker}: {exc}") from exc
|
||||
Reference in New Issue
Block a user