luohoa97's picture
Deploy BitNet-Transformer Trainer
d5b7ee9 verified
"""Market data fetching — Alpaca historical bars with yfinance fallback."""
from __future__ import annotations
import logging
import time
from datetime import datetime, timedelta, timezone
from typing import TYPE_CHECKING
import pandas as pd
if TYPE_CHECKING:
from trading_cli.execution.alpaca_client import AlpacaClient
logger = logging.getLogger(__name__)
def fetch_ohlcv_alpaca(
client: "AlpacaClient",
symbol: str,
days: int = 90,
) -> pd.DataFrame:
"""Fetch OHLCV bars from Alpaca historical data API."""
try:
from alpaca.data.requests import StockBarsRequest
from alpaca.data.timeframe import TimeFrame
end = datetime.now(tz=timezone.utc)
start = end - timedelta(days=days + 10) # extra buffer for weekends
request = StockBarsRequest(
symbol_or_symbols=symbol,
timeframe=TimeFrame.Day,
start=start,
end=end,
feed="iex",
)
bars = client.historical_client.get_stock_bars(request)
df = bars.df
if isinstance(df.index, pd.MultiIndex):
df = df.xs(symbol, level=0) if symbol in df.index.get_level_values(0) else df
df.index = pd.to_datetime(df.index, utc=True)
df = df.rename(columns={"open": "Open", "high": "High", "low": "Low",
"close": "Close", "volume": "Volume"})
return df.tail(days)
except Exception as exc:
logger.warning("Alpaca OHLCV fetch failed for %s: %s — falling back to yfinance", symbol, exc)
return fetch_ohlcv_yfinance(symbol, days)
def fetch_ohlcv_yfinance(symbol: str, days: int = 90) -> pd.DataFrame:
"""Fetch OHLCV bars from yfinance. Period can be long for daily interval."""
try:
import yfinance as yf
# No more 730d cap for 1d data; yfinance handles 10y+ easily for daily.
period = f"{days}d"
df = yf.download(symbol, period=period, interval="1d", progress=False, auto_adjust=True)
if df.empty:
return pd.DataFrame()
# Flatten MultiIndex columns if present (common in newer yfinance versions)
if isinstance(df.columns, pd.MultiIndex):
df.columns = df.columns.get_level_values(0)
return df.tail(days)
except Exception as exc:
logger.error("yfinance fetch failed for %s: %s", symbol, exc)
return pd.DataFrame()
def get_latest_quote_alpaca(client: "AlpacaClient", symbol: str) -> float | None:
"""Get latest trade price from Alpaca."""
try:
from alpaca.data.requests import StockLatestTradeRequest
req = StockLatestTradeRequest(symbol_or_symbols=symbol, feed="iex")
trades = client.historical_client.get_stock_latest_trade(req)
return float(trades[symbol].price)
except Exception as exc:
logger.warning("Alpaca latest quote failed for %s: %s", symbol, exc)
return None
def get_latest_quote_yfinance(symbol: str) -> float | None:
"""Get latest price from yfinance (free tier fallback)."""
try:
import yfinance as yf
ticker = yf.Ticker(symbol)
info = ticker.fast_info
price = getattr(info, "last_price", None) or getattr(info, "regularMarketPrice", None)
if price:
return float(price)
hist = ticker.history(period="2d", interval="1d")
if not hist.empty:
return float(hist["Close"].iloc[-1])
return None
except Exception as exc:
logger.warning("yfinance latest quote failed for %s: %s", symbol, exc)
return None
def get_latest_quotes_batch(
client: "AlpacaClient | None",
symbols: list[str],
) -> dict[str, float]:
"""Return {symbol: price} dict for multiple symbols."""
prices: dict[str, float] = {}
if client and not client.demo_mode:
try:
from alpaca.data.requests import StockLatestTradeRequest
req = StockLatestTradeRequest(symbol_or_symbols=symbols, feed="iex")
trades = client.historical_client.get_stock_latest_trade(req)
for sym, trade in trades.items():
prices[sym] = float(trade.price)
return prices
except Exception as exc:
logger.warning("Batch Alpaca quote failed: %s — falling back", exc)
# yfinance fallback
for sym in symbols:
price = get_latest_quote_yfinance(sym)
if price:
prices[sym] = price
time.sleep(0.2) # avoid hammering
return prices