Skip to content

Client API Reference

StockFeedClient

stockfeed.client.StockFeedClient

Synchronous client with automatic provider selection and cache-first access.

By default provider selection is automatic: yfinance is always available as a free fallback; paid providers (Tiingo, Finnhub, …) are used first when their API keys are configured. Pass provider="tiingo" to any method to pin a specific provider.

Parameters:

Name Type Description Default
settings StockFeedSettings | None

Configuration (API keys, cache path, …). Reads from env / .env if not provided.

None
db_path str | None

Override the DuckDB path. Defaults to settings.cache_path.

None
Source code in src/stockfeed/client.py
class StockFeedClient:
    """Synchronous client with automatic provider selection and cache-first access.

    By default provider selection is automatic: yfinance is always available as a
    free fallback; paid providers (Tiingo, Finnhub, …) are used first when their
    API keys are configured. Pass ``provider="tiingo"`` to any method to pin a
    specific provider.

    Parameters
    ----------
    settings : StockFeedSettings | None
        Configuration (API keys, cache path, …). Reads from env / ``.env`` if
        not provided.
    db_path : str | None
        Override the DuckDB path. Defaults to ``settings.cache_path``.
    """

    def __init__(
        self,
        settings: StockFeedSettings | None = None,
        db_path: str | None = None,
    ) -> None:
        self.settings = settings or StockFeedSettings()
        self._db_path = db_path or self.settings.cache_path
        self._cache = CacheManager(db_path=self._db_path) if self.settings.cache_enabled else None
        self._rate_limiter = RateLimiter(db_path=self._db_path)
        self._health_checker = HealthChecker(db_path=self._db_path)
        self._market_hours = MarketHoursChecker()
        self._selector = ProviderSelector(
            registry=get_default_registry(),
            rate_limiter=self._rate_limiter,
            health_checker=self._health_checker,
            settings=self.settings,
        )
        self._options_selector = OptionsProviderSelector(
            registry=get_default_registry(),
            rate_limiter=self._rate_limiter,
            health_checker=self._health_checker,
            settings=self.settings,
        )

    # ------------------------------------------------------------------
    # OHLCV
    # ------------------------------------------------------------------

    def get_ohlcv(
        self,
        ticker: str,
        interval: str | Interval,
        start: str | datetime,
        end: str | datetime,
        provider: str | None = None,
    ) -> list[OHLCVBar]:
        """Return OHLCV bars for *ticker* over [start, end).

        Checks the cache first (skipped for intraday bars during open market hours).
        On a cache miss, tries providers in order until one succeeds, then caches
        the result. ``yfinance`` is always the final fallback.

        Parameters
        ----------
        ticker : str
            Uppercase ticker symbol.
        interval : str | Interval
            Bar width — ``"1d"``, ``"1h"``, etc., or an :class:`Interval` member.
        start : str | datetime
            Inclusive start. Accepts ``"YYYY-MM-DD"`` strings (parsed as UTC
            midnight) or a timezone-aware ``datetime``.
        end : str | datetime
            Exclusive end. Same format as *start*.
        provider : str | None
            Pin a specific provider by name (e.g. ``"tiingo"``). ``None`` means
            auto-select.
        """
        interval = parse_interval(interval)
        start = parse_dt(start)
        end = parse_dt(end)

        if self._cache and self._market_hours.should_use_cache(interval):
            cached = self._cache.read(ticker, interval, start, end)
            if cached is not None:
                return cached

        bars = self._ohlcv_with_failover(ticker, interval, start, end, provider)

        if self._cache:
            self._cache.write(bars)

        return bars

    def _ohlcv_with_failover(
        self,
        ticker: str,
        interval: Interval,
        start: datetime,
        end: datetime,
        preferred: str | None,
    ) -> list[OHLCVBar]:
        last_exc: Exception | None = None
        for p in self._selector.select(ticker, interval, preferred=preferred):
            try:
                return p.get_ohlcv(ticker, interval, start, end)
            except (ProviderRateLimitError, ProviderUnavailableError) as exc:
                last_exc = exc
            except (ProviderAuthError, TickerNotFoundError):
                raise
        raise ProviderUnavailableError(
            f"All providers failed for {ticker}", ticker=ticker
        ) from last_exc

    # ------------------------------------------------------------------
    # Quote
    # ------------------------------------------------------------------

    def get_quote(self, ticker: str, provider: str | None = None) -> Quote:
        """Return the latest quote for *ticker* with automatic provider failover.

        Parameters
        ----------
        ticker : str
            Uppercase ticker symbol.
        provider : str | None
            Pin a provider by name. ``None`` means auto-select.
        """
        last_exc: Exception | None = None
        for p in self._selector.select(ticker, Interval.ONE_DAY, preferred=provider):
            try:
                return p.get_quote(ticker)
            except (ProviderRateLimitError, ProviderUnavailableError) as exc:
                last_exc = exc
            except (ProviderAuthError, TickerNotFoundError):
                raise
        raise ProviderUnavailableError(
            f"All providers failed for {ticker}", ticker=ticker
        ) from last_exc

    # ------------------------------------------------------------------
    # Ticker info
    # ------------------------------------------------------------------

    def get_ticker_info(self, ticker: str, provider: str | None = None) -> TickerInfo:
        """Return company/asset metadata for *ticker*.

        Parameters
        ----------
        ticker : str
            Uppercase ticker symbol.
        provider : str | None
            Pin a provider by name. ``None`` means auto-select.
        """
        last_exc: Exception | None = None
        for p in self._selector.select(ticker, Interval.ONE_DAY, preferred=provider):
            try:
                return p.get_ticker_info(ticker)
            except (ProviderRateLimitError, ProviderUnavailableError) as exc:
                last_exc = exc
            except (ProviderAuthError, TickerNotFoundError):
                raise
            except NotImplementedError:
                continue  # tradier and others don't support ticker_info
        raise ProviderUnavailableError(
            f"All providers failed for {ticker}", ticker=ticker
        ) from last_exc

    # ------------------------------------------------------------------
    # Provider listing
    # ------------------------------------------------------------------

    def list_providers(self) -> list[ProviderInfo]:
        """Return metadata for every registered provider.

        Returns
        -------
        list[ProviderInfo]
            One entry per registered provider, sorted alphabetically by name.
        """
        return [
            ProviderInfo(
                name=cls.name,
                requires_auth=cls.requires_auth,
                supported_intervals=list(cls.supported_intervals),
            )
            for cls in sorted(get_default_registry().all().values(), key=lambda c: c.name)
        ]

    # ------------------------------------------------------------------
    # Health
    # ------------------------------------------------------------------

    def health_check(self, provider: str | None = None) -> dict[str, HealthStatus]:
        """Probe provider(s) and return health snapshots.

        Parameters
        ----------
        provider : str | None
            Check a single provider by name. ``None`` checks all registered providers.

        Returns
        -------
        dict[str, HealthStatus]
            Keyed by provider name.
        """
        registry = get_default_registry()
        targets = {provider: registry.get(provider)} if provider else registry.all()
        results: dict[str, HealthStatus] = {}
        for name, cls in targets.items():
            instance = self._selector._instantiate(cls)
            if instance is not None:
                results[name] = instance.health_check()
        return results

    # ------------------------------------------------------------------
    # Options
    # ------------------------------------------------------------------

    def get_option_expirations(self, ticker: str, provider: str | None = None) -> list[date]:
        """Return available option expiration dates for *ticker*.

        Parameters
        ----------
        ticker : str
            Uppercase ticker symbol (underlying).
        provider : str | None
            Pin a specific options provider. ``None`` means auto-select.
        """
        last_exc: Exception | None = None
        for p in self._options_selector.select(preferred=provider):
            try:
                return p.get_option_expirations(ticker)
            except (ProviderRateLimitError, ProviderUnavailableError) as exc:
                last_exc = exc
            except (ProviderAuthError, TickerNotFoundError):
                raise
            except NotImplementedError:
                continue
        raise ProviderUnavailableError(
            f"No options provider could return expirations for {ticker}", ticker=ticker
        ) from last_exc

    def get_options_chain(
        self, ticker: str, expiration: date, provider: str | None = None
    ) -> OptionChain:
        """Return the full options chain for *ticker* at *expiration*.

        Parameters
        ----------
        ticker : str
            Uppercase ticker symbol (underlying).
        expiration : date
            Expiration date.
        provider : str | None
            Pin a specific options provider. ``None`` means auto-select.
        """
        last_exc: Exception | None = None
        for p in self._options_selector.select(preferred=provider):
            try:
                return p.get_options_chain(ticker, expiration)
            except (ProviderRateLimitError, ProviderUnavailableError) as exc:
                last_exc = exc
            except (ProviderAuthError, TickerNotFoundError):
                raise
            except NotImplementedError:
                continue
        raise ProviderUnavailableError(
            f"No options provider could return chain for {ticker} {expiration}", ticker=ticker
        ) from last_exc

    def get_option_quote(self, symbol: str, provider: str | None = None) -> OptionQuote:
        """Return a live quote for the OCC option *symbol*.

        Parameters
        ----------
        symbol : str
            OCC option symbol (e.g. ``"AAPL240119C00150000"``).
        provider : str | None
            Pin a specific options provider. ``None`` means auto-select.
        """
        last_exc: Exception | None = None
        for p in self._options_selector.select(preferred=provider):
            try:
                return p.get_option_quote(symbol)
            except (ProviderRateLimitError, ProviderUnavailableError) as exc:
                last_exc = exc
            except (ProviderAuthError, TickerNotFoundError):
                raise
            except NotImplementedError:
                continue
        raise ProviderUnavailableError(
            f"No options provider could return quote for {symbol}"
        ) from last_exc

__init__(settings=None, db_path=None)

Source code in src/stockfeed/client.py
def __init__(
    self,
    settings: StockFeedSettings | None = None,
    db_path: str | None = None,
) -> None:
    self.settings = settings or StockFeedSettings()
    self._db_path = db_path or self.settings.cache_path
    self._cache = CacheManager(db_path=self._db_path) if self.settings.cache_enabled else None
    self._rate_limiter = RateLimiter(db_path=self._db_path)
    self._health_checker = HealthChecker(db_path=self._db_path)
    self._market_hours = MarketHoursChecker()
    self._selector = ProviderSelector(
        registry=get_default_registry(),
        rate_limiter=self._rate_limiter,
        health_checker=self._health_checker,
        settings=self.settings,
    )
    self._options_selector = OptionsProviderSelector(
        registry=get_default_registry(),
        rate_limiter=self._rate_limiter,
        health_checker=self._health_checker,
        settings=self.settings,
    )

get_ohlcv(ticker, interval, start, end, provider=None)

Return OHLCV bars for ticker over [start, end).

Checks the cache first (skipped for intraday bars during open market hours). On a cache miss, tries providers in order until one succeeds, then caches the result. yfinance is always the final fallback.

Parameters:

Name Type Description Default
ticker str

Uppercase ticker symbol.

required
interval str | Interval

Bar width — "1d", "1h", etc., or an :class:Interval member.

required
start str | datetime

Inclusive start. Accepts "YYYY-MM-DD" strings (parsed as UTC midnight) or a timezone-aware datetime.

required
end str | datetime

Exclusive end. Same format as start.

required
provider str | None

Pin a specific provider by name (e.g. "tiingo"). None means auto-select.

None
Source code in src/stockfeed/client.py
def get_ohlcv(
    self,
    ticker: str,
    interval: str | Interval,
    start: str | datetime,
    end: str | datetime,
    provider: str | None = None,
) -> list[OHLCVBar]:
    """Return OHLCV bars for *ticker* over [start, end).

    Checks the cache first (skipped for intraday bars during open market hours).
    On a cache miss, tries providers in order until one succeeds, then caches
    the result. ``yfinance`` is always the final fallback.

    Parameters
    ----------
    ticker : str
        Uppercase ticker symbol.
    interval : str | Interval
        Bar width — ``"1d"``, ``"1h"``, etc., or an :class:`Interval` member.
    start : str | datetime
        Inclusive start. Accepts ``"YYYY-MM-DD"`` strings (parsed as UTC
        midnight) or a timezone-aware ``datetime``.
    end : str | datetime
        Exclusive end. Same format as *start*.
    provider : str | None
        Pin a specific provider by name (e.g. ``"tiingo"``). ``None`` means
        auto-select.
    """
    interval = parse_interval(interval)
    start = parse_dt(start)
    end = parse_dt(end)

    if self._cache and self._market_hours.should_use_cache(interval):
        cached = self._cache.read(ticker, interval, start, end)
        if cached is not None:
            return cached

    bars = self._ohlcv_with_failover(ticker, interval, start, end, provider)

    if self._cache:
        self._cache.write(bars)

    return bars

get_quote(ticker, provider=None)

Return the latest quote for ticker with automatic provider failover.

Parameters:

Name Type Description Default
ticker str

Uppercase ticker symbol.

required
provider str | None

Pin a provider by name. None means auto-select.

None
Source code in src/stockfeed/client.py
def get_quote(self, ticker: str, provider: str | None = None) -> Quote:
    """Return the latest quote for *ticker* with automatic provider failover.

    Parameters
    ----------
    ticker : str
        Uppercase ticker symbol.
    provider : str | None
        Pin a provider by name. ``None`` means auto-select.
    """
    last_exc: Exception | None = None
    for p in self._selector.select(ticker, Interval.ONE_DAY, preferred=provider):
        try:
            return p.get_quote(ticker)
        except (ProviderRateLimitError, ProviderUnavailableError) as exc:
            last_exc = exc
        except (ProviderAuthError, TickerNotFoundError):
            raise
    raise ProviderUnavailableError(
        f"All providers failed for {ticker}", ticker=ticker
    ) from last_exc

get_ticker_info(ticker, provider=None)

Return company/asset metadata for ticker.

Parameters:

Name Type Description Default
ticker str

Uppercase ticker symbol.

required
provider str | None

Pin a provider by name. None means auto-select.

None
Source code in src/stockfeed/client.py
def get_ticker_info(self, ticker: str, provider: str | None = None) -> TickerInfo:
    """Return company/asset metadata for *ticker*.

    Parameters
    ----------
    ticker : str
        Uppercase ticker symbol.
    provider : str | None
        Pin a provider by name. ``None`` means auto-select.
    """
    last_exc: Exception | None = None
    for p in self._selector.select(ticker, Interval.ONE_DAY, preferred=provider):
        try:
            return p.get_ticker_info(ticker)
        except (ProviderRateLimitError, ProviderUnavailableError) as exc:
            last_exc = exc
        except (ProviderAuthError, TickerNotFoundError):
            raise
        except NotImplementedError:
            continue  # tradier and others don't support ticker_info
    raise ProviderUnavailableError(
        f"All providers failed for {ticker}", ticker=ticker
    ) from last_exc

health_check(provider=None)

Probe provider(s) and return health snapshots.

Parameters:

Name Type Description Default
provider str | None

Check a single provider by name. None checks all registered providers.

None

Returns:

Type Description
dict[str, HealthStatus]

Keyed by provider name.

Source code in src/stockfeed/client.py
def health_check(self, provider: str | None = None) -> dict[str, HealthStatus]:
    """Probe provider(s) and return health snapshots.

    Parameters
    ----------
    provider : str | None
        Check a single provider by name. ``None`` checks all registered providers.

    Returns
    -------
    dict[str, HealthStatus]
        Keyed by provider name.
    """
    registry = get_default_registry()
    targets = {provider: registry.get(provider)} if provider else registry.all()
    results: dict[str, HealthStatus] = {}
    for name, cls in targets.items():
        instance = self._selector._instantiate(cls)
        if instance is not None:
            results[name] = instance.health_check()
    return results

list_providers()

Return metadata for every registered provider.

Returns:

Type Description
list[ProviderInfo]

One entry per registered provider, sorted alphabetically by name.

Source code in src/stockfeed/client.py
def list_providers(self) -> list[ProviderInfo]:
    """Return metadata for every registered provider.

    Returns
    -------
    list[ProviderInfo]
        One entry per registered provider, sorted alphabetically by name.
    """
    return [
        ProviderInfo(
            name=cls.name,
            requires_auth=cls.requires_auth,
            supported_intervals=list(cls.supported_intervals),
        )
        for cls in sorted(get_default_registry().all().values(), key=lambda c: c.name)
    ]

get_option_expirations(ticker, provider=None)

Return available option expiration dates for ticker.

Parameters:

Name Type Description Default
ticker str

Uppercase ticker symbol (underlying).

required
provider str | None

Pin a specific options provider. None means auto-select.

None
Source code in src/stockfeed/client.py
def get_option_expirations(self, ticker: str, provider: str | None = None) -> list[date]:
    """Return available option expiration dates for *ticker*.

    Parameters
    ----------
    ticker : str
        Uppercase ticker symbol (underlying).
    provider : str | None
        Pin a specific options provider. ``None`` means auto-select.
    """
    last_exc: Exception | None = None
    for p in self._options_selector.select(preferred=provider):
        try:
            return p.get_option_expirations(ticker)
        except (ProviderRateLimitError, ProviderUnavailableError) as exc:
            last_exc = exc
        except (ProviderAuthError, TickerNotFoundError):
            raise
        except NotImplementedError:
            continue
    raise ProviderUnavailableError(
        f"No options provider could return expirations for {ticker}", ticker=ticker
    ) from last_exc

get_options_chain(ticker, expiration, provider=None)

Return the full options chain for ticker at expiration.

Parameters:

Name Type Description Default
ticker str

Uppercase ticker symbol (underlying).

required
expiration date

Expiration date.

required
provider str | None

Pin a specific options provider. None means auto-select.

None
Source code in src/stockfeed/client.py
def get_options_chain(
    self, ticker: str, expiration: date, provider: str | None = None
) -> OptionChain:
    """Return the full options chain for *ticker* at *expiration*.

    Parameters
    ----------
    ticker : str
        Uppercase ticker symbol (underlying).
    expiration : date
        Expiration date.
    provider : str | None
        Pin a specific options provider. ``None`` means auto-select.
    """
    last_exc: Exception | None = None
    for p in self._options_selector.select(preferred=provider):
        try:
            return p.get_options_chain(ticker, expiration)
        except (ProviderRateLimitError, ProviderUnavailableError) as exc:
            last_exc = exc
        except (ProviderAuthError, TickerNotFoundError):
            raise
        except NotImplementedError:
            continue
    raise ProviderUnavailableError(
        f"No options provider could return chain for {ticker} {expiration}", ticker=ticker
    ) from last_exc

get_option_quote(symbol, provider=None)

Return a live quote for the OCC option symbol.

Parameters:

Name Type Description Default
symbol str

OCC option symbol (e.g. "AAPL240119C00150000").

required
provider str | None

Pin a specific options provider. None means auto-select.

None
Source code in src/stockfeed/client.py
def get_option_quote(self, symbol: str, provider: str | None = None) -> OptionQuote:
    """Return a live quote for the OCC option *symbol*.

    Parameters
    ----------
    symbol : str
        OCC option symbol (e.g. ``"AAPL240119C00150000"``).
    provider : str | None
        Pin a specific options provider. ``None`` means auto-select.
    """
    last_exc: Exception | None = None
    for p in self._options_selector.select(preferred=provider):
        try:
            return p.get_option_quote(symbol)
        except (ProviderRateLimitError, ProviderUnavailableError) as exc:
            last_exc = exc
        except (ProviderAuthError, TickerNotFoundError):
            raise
        except NotImplementedError:
            continue
    raise ProviderUnavailableError(
        f"No options provider could return quote for {symbol}"
    ) from last_exc

AsyncStockFeedClient

stockfeed.async_client.AsyncStockFeedClient

Asynchronous client with automatic provider selection and cache-first access.

Mirrors :class:~stockfeed.client.StockFeedClient exactly but exposes async def methods so callers can await them inside an event loop.

Parameters:

Name Type Description Default
settings StockFeedSettings | None

Configuration (API keys, cache path, …). Reads from env / .env if not provided.

None
db_path str | None

Override the DuckDB path. Defaults to settings.cache_path.

None
Source code in src/stockfeed/async_client.py
class AsyncStockFeedClient:
    """Asynchronous client with automatic provider selection and cache-first access.

    Mirrors :class:`~stockfeed.client.StockFeedClient` exactly but exposes
    ``async def`` methods so callers can ``await`` them inside an event loop.

    Parameters
    ----------
    settings : StockFeedSettings | None
        Configuration (API keys, cache path, …). Reads from env / ``.env`` if
        not provided.
    db_path : str | None
        Override the DuckDB path. Defaults to ``settings.cache_path``.
    """

    def __init__(
        self,
        settings: StockFeedSettings | None = None,
        db_path: str | None = None,
        *,
        dev_mode: bool = False,
    ) -> None:
        if settings is None:
            settings = StockFeedSettings()
        if dev_mode:
            settings = settings.model_copy(update={"dev_mode": True})
        self.settings = settings
        self._db_path = db_path or self.settings.cache_path
        self._cache = CacheManager(db_path=self._db_path) if self.settings.cache_enabled else None
        self._rate_limiter = RateLimiter(db_path=self._db_path)
        self._health_checker = HealthChecker(db_path=self._db_path)
        self._market_hours = MarketHoursChecker()
        self._selector = ProviderSelector(
            registry=get_default_registry(),
            rate_limiter=self._rate_limiter,
            health_checker=self._health_checker,
            settings=self.settings,
        )
        self._options_selector = OptionsProviderSelector(
            registry=get_default_registry(),
            rate_limiter=self._rate_limiter,
            health_checker=self._health_checker,
            settings=self.settings,
        )

    # ------------------------------------------------------------------
    # OHLCV
    # ------------------------------------------------------------------

    async def get_ohlcv(
        self,
        ticker: str,
        interval: str | Interval,
        start: str | datetime,
        end: str | datetime,
        provider: str | None = None,
    ) -> list[OHLCVBar]:
        """Async variant of :meth:`~stockfeed.client.StockFeedClient.get_ohlcv`.

        Parameters
        ----------
        ticker : str
            Uppercase ticker symbol.
        interval : str | Interval
            Bar width — ``"1d"``, ``"1h"``, etc., or an :class:`Interval` member.
        start : str | datetime
            Inclusive start. Accepts ``"YYYY-MM-DD"`` strings (parsed as UTC
            midnight) or a timezone-aware ``datetime``.
        end : str | datetime
            Exclusive end. Same format as *start*.
        provider : str | None
            Pin a specific provider by name. ``None`` means auto-select.
        """
        interval = parse_interval(interval)
        start = parse_dt(start)
        end = parse_dt(end)

        if self._cache and self._market_hours.should_use_cache(interval):
            cached = self._cache.read(ticker, interval, start, end)
            if cached is not None:
                return cached

        bars = await self._ohlcv_with_failover(ticker, interval, start, end, provider)

        if self._cache:
            self._cache.write(bars)

        return bars

    async def _ohlcv_with_failover(
        self,
        ticker: str,
        interval: Interval,
        start: datetime,
        end: datetime,
        preferred: str | None,
    ) -> list[OHLCVBar]:
        last_exc: Exception | None = None
        for p in self._selector.select(ticker, interval, preferred=preferred):
            try:
                return await p.async_get_ohlcv(ticker, interval, start, end)
            except (ProviderRateLimitError, ProviderUnavailableError) as exc:
                last_exc = exc
            except (ProviderAuthError, TickerNotFoundError):
                raise
        raise ProviderUnavailableError(
            f"All providers failed for {ticker}", ticker=ticker
        ) from last_exc

    # ------------------------------------------------------------------
    # Quote
    # ------------------------------------------------------------------

    async def get_quote(self, ticker: str, provider: str | None = None) -> Quote:
        """Async variant of :meth:`~stockfeed.client.StockFeedClient.get_quote`.

        Parameters
        ----------
        ticker : str
            Uppercase ticker symbol.
        provider : str | None
            Pin a provider by name. ``None`` means auto-select.
        """
        last_exc: Exception | None = None
        for p in self._selector.select(ticker, Interval.ONE_DAY, preferred=provider):
            try:
                return await p.async_get_quote(ticker)
            except (ProviderRateLimitError, ProviderUnavailableError) as exc:
                last_exc = exc
            except (ProviderAuthError, TickerNotFoundError):
                raise
        raise ProviderUnavailableError(
            f"All providers failed for {ticker}", ticker=ticker
        ) from last_exc

    # ------------------------------------------------------------------
    # Ticker info
    # ------------------------------------------------------------------

    async def get_ticker_info(self, ticker: str, provider: str | None = None) -> TickerInfo:
        """Async variant of :meth:`~stockfeed.client.StockFeedClient.get_ticker_info`.

        Parameters
        ----------
        ticker : str
            Uppercase ticker symbol.
        provider : str | None
            Pin a provider by name. ``None`` means auto-select.
        """
        last_exc: Exception | None = None
        for p in self._selector.select(ticker, Interval.ONE_DAY, preferred=provider):
            try:
                return await p.async_get_ticker_info(ticker)
            except (ProviderRateLimitError, ProviderUnavailableError) as exc:
                last_exc = exc
            except (ProviderAuthError, TickerNotFoundError):
                raise
            except NotImplementedError:
                continue
        raise ProviderUnavailableError(
            f"All providers failed for {ticker}", ticker=ticker
        ) from last_exc

    # ------------------------------------------------------------------
    # Health
    # ------------------------------------------------------------------

    async def health_check(self, provider: str | None = None) -> dict[str, HealthStatus]:
        """Async variant of :meth:`~stockfeed.client.StockFeedClient.health_check`.

        Parameters
        ----------
        provider : str | None
            Check a single provider by name. ``None`` checks all registered providers.

        Returns
        -------
        dict[str, HealthStatus]
            Keyed by provider name.
        """
        registry = get_default_registry()
        targets = {provider: registry.get(provider)} if provider else registry.all()
        results: dict[str, HealthStatus] = {}
        for name, cls in targets.items():
            instance = self._selector._instantiate(cls)
            if instance is not None:
                results[name] = await instance.async_health_check()
        return results

    # ------------------------------------------------------------------
    # Options
    # ------------------------------------------------------------------

    async def get_option_expirations(self, ticker: str, provider: str | None = None) -> list[date]:
        """Async variant of :meth:`~stockfeed.client.StockFeedClient.get_option_expirations`.

        Parameters
        ----------
        ticker : str
            Uppercase ticker symbol (underlying).
        provider : str | None
            Pin a specific options provider. ``None`` means auto-select.
        """
        last_exc: Exception | None = None
        for p in self._options_selector.select(preferred=provider):
            try:
                return await p.async_get_option_expirations(ticker)
            except (ProviderRateLimitError, ProviderUnavailableError) as exc:
                last_exc = exc
            except (ProviderAuthError, TickerNotFoundError):
                raise
            except NotImplementedError:
                continue
        raise ProviderUnavailableError(
            f"No options provider could return expirations for {ticker}", ticker=ticker
        ) from last_exc

    async def get_options_chain(
        self, ticker: str, expiration: date, provider: str | None = None
    ) -> OptionChain:
        """Async variant of :meth:`~stockfeed.client.StockFeedClient.get_options_chain`.

        Parameters
        ----------
        ticker : str
            Uppercase ticker symbol (underlying).
        expiration : date
            Expiration date.
        provider : str | None
            Pin a specific options provider. ``None`` means auto-select.
        """
        last_exc: Exception | None = None
        for p in self._options_selector.select(preferred=provider):
            try:
                return await p.async_get_options_chain(ticker, expiration)
            except (ProviderRateLimitError, ProviderUnavailableError) as exc:
                last_exc = exc
            except (ProviderAuthError, TickerNotFoundError):
                raise
            except NotImplementedError:
                continue
        raise ProviderUnavailableError(
            f"No options provider could return chain for {ticker} {expiration}", ticker=ticker
        ) from last_exc

    async def get_option_quote(self, symbol: str, provider: str | None = None) -> OptionQuote:
        """Async variant of :meth:`~stockfeed.client.StockFeedClient.get_option_quote`.

        Parameters
        ----------
        symbol : str
            OCC option symbol (e.g. ``"AAPL240119C00150000"``).
        provider : str | None
            Pin a specific options provider. ``None`` means auto-select.
        """
        last_exc: Exception | None = None
        for p in self._options_selector.select(preferred=provider):
            try:
                return await p.async_get_option_quote(symbol)
            except (ProviderRateLimitError, ProviderUnavailableError) as exc:
                last_exc = exc
            except (ProviderAuthError, TickerNotFoundError):
                raise
            except NotImplementedError:
                continue
        raise ProviderUnavailableError(
            f"No options provider could return quote for {symbol}"
        ) from last_exc

    # ------------------------------------------------------------------
    # Streaming
    # ------------------------------------------------------------------

    async def stream_quote(
        self,
        ticker: str,
        *,
        interval: float = 5.0,
        provider: str | None = None,
        max_errors: int = 5,
    ) -> AsyncGenerator[Quote, None]:
        """Stream live quotes by polling *ticker* every *interval* seconds.

        Parameters
        ----------
        ticker : str
            Uppercase ticker symbol.
        interval : float
            Seconds between polls. Defaults to ``5.0``.
        provider : str | None
            Pin a specific provider. ``None`` means auto-select.
        max_errors : int
            Maximum consecutive transient errors before aborting.

        Yields
        ------
        Quote
        """
        from stockfeed.streaming.sse import stream_quote as _stream_quote

        async for quote in _stream_quote(
            ticker, self, interval=interval, provider=provider, max_errors=max_errors
        ):
            yield quote

    # ------------------------------------------------------------------
    # Dev / simulation
    # ------------------------------------------------------------------

    async def simulate(
        self,
        ticker: str,
        start: str | datetime,
        end: str | datetime,
        interval: str | Interval,
        *,
        speed: float = 1.0,
    ) -> AsyncGenerator[OHLCVBar, None]:
        """Replay historical bars as an async stream (dev/backtest mode).

        Requires :attr:`~stockfeed.config.StockFeedSettings.dev_mode` to be
        ``True`` (or pass ``dev_mode=True`` to the client constructor).

        Parameters
        ----------
        ticker : str
            Uppercase ticker symbol.
        start : str | datetime
            Inclusive start date. ``"YYYY-MM-DD"`` strings are accepted.
        end : str | datetime
            Exclusive end date.
        interval : str | Interval
            Bar width — ``"1d"``, ``"1h"``, etc.
        speed : float
            Playback multiplier. ``1.0`` replays in real time; ``0`` skips all
            sleeps (instant replay). Defaults to ``1.0``.

        Yields
        ------
        OHLCVBar
        """
        from stockfeed.dev.simulator import simulate as _simulate

        async for bar in _simulate(ticker, start, end, interval, speed=speed, client=self):
            yield bar

__init__(settings=None, db_path=None, *, dev_mode=False)

Source code in src/stockfeed/async_client.py
def __init__(
    self,
    settings: StockFeedSettings | None = None,
    db_path: str | None = None,
    *,
    dev_mode: bool = False,
) -> None:
    if settings is None:
        settings = StockFeedSettings()
    if dev_mode:
        settings = settings.model_copy(update={"dev_mode": True})
    self.settings = settings
    self._db_path = db_path or self.settings.cache_path
    self._cache = CacheManager(db_path=self._db_path) if self.settings.cache_enabled else None
    self._rate_limiter = RateLimiter(db_path=self._db_path)
    self._health_checker = HealthChecker(db_path=self._db_path)
    self._market_hours = MarketHoursChecker()
    self._selector = ProviderSelector(
        registry=get_default_registry(),
        rate_limiter=self._rate_limiter,
        health_checker=self._health_checker,
        settings=self.settings,
    )
    self._options_selector = OptionsProviderSelector(
        registry=get_default_registry(),
        rate_limiter=self._rate_limiter,
        health_checker=self._health_checker,
        settings=self.settings,
    )

get_ohlcv(ticker, interval, start, end, provider=None) async

Async variant of :meth:~stockfeed.client.StockFeedClient.get_ohlcv.

Parameters:

Name Type Description Default
ticker str

Uppercase ticker symbol.

required
interval str | Interval

Bar width — "1d", "1h", etc., or an :class:Interval member.

required
start str | datetime

Inclusive start. Accepts "YYYY-MM-DD" strings (parsed as UTC midnight) or a timezone-aware datetime.

required
end str | datetime

Exclusive end. Same format as start.

required
provider str | None

Pin a specific provider by name. None means auto-select.

None
Source code in src/stockfeed/async_client.py
async def get_ohlcv(
    self,
    ticker: str,
    interval: str | Interval,
    start: str | datetime,
    end: str | datetime,
    provider: str | None = None,
) -> list[OHLCVBar]:
    """Async variant of :meth:`~stockfeed.client.StockFeedClient.get_ohlcv`.

    Parameters
    ----------
    ticker : str
        Uppercase ticker symbol.
    interval : str | Interval
        Bar width — ``"1d"``, ``"1h"``, etc., or an :class:`Interval` member.
    start : str | datetime
        Inclusive start. Accepts ``"YYYY-MM-DD"`` strings (parsed as UTC
        midnight) or a timezone-aware ``datetime``.
    end : str | datetime
        Exclusive end. Same format as *start*.
    provider : str | None
        Pin a specific provider by name. ``None`` means auto-select.
    """
    interval = parse_interval(interval)
    start = parse_dt(start)
    end = parse_dt(end)

    if self._cache and self._market_hours.should_use_cache(interval):
        cached = self._cache.read(ticker, interval, start, end)
        if cached is not None:
            return cached

    bars = await self._ohlcv_with_failover(ticker, interval, start, end, provider)

    if self._cache:
        self._cache.write(bars)

    return bars

get_quote(ticker, provider=None) async

Async variant of :meth:~stockfeed.client.StockFeedClient.get_quote.

Parameters:

Name Type Description Default
ticker str

Uppercase ticker symbol.

required
provider str | None

Pin a provider by name. None means auto-select.

None
Source code in src/stockfeed/async_client.py
async def get_quote(self, ticker: str, provider: str | None = None) -> Quote:
    """Async variant of :meth:`~stockfeed.client.StockFeedClient.get_quote`.

    Parameters
    ----------
    ticker : str
        Uppercase ticker symbol.
    provider : str | None
        Pin a provider by name. ``None`` means auto-select.
    """
    last_exc: Exception | None = None
    for p in self._selector.select(ticker, Interval.ONE_DAY, preferred=provider):
        try:
            return await p.async_get_quote(ticker)
        except (ProviderRateLimitError, ProviderUnavailableError) as exc:
            last_exc = exc
        except (ProviderAuthError, TickerNotFoundError):
            raise
    raise ProviderUnavailableError(
        f"All providers failed for {ticker}", ticker=ticker
    ) from last_exc

get_ticker_info(ticker, provider=None) async

Async variant of :meth:~stockfeed.client.StockFeedClient.get_ticker_info.

Parameters:

Name Type Description Default
ticker str

Uppercase ticker symbol.

required
provider str | None

Pin a provider by name. None means auto-select.

None
Source code in src/stockfeed/async_client.py
async def get_ticker_info(self, ticker: str, provider: str | None = None) -> TickerInfo:
    """Async variant of :meth:`~stockfeed.client.StockFeedClient.get_ticker_info`.

    Parameters
    ----------
    ticker : str
        Uppercase ticker symbol.
    provider : str | None
        Pin a provider by name. ``None`` means auto-select.
    """
    last_exc: Exception | None = None
    for p in self._selector.select(ticker, Interval.ONE_DAY, preferred=provider):
        try:
            return await p.async_get_ticker_info(ticker)
        except (ProviderRateLimitError, ProviderUnavailableError) as exc:
            last_exc = exc
        except (ProviderAuthError, TickerNotFoundError):
            raise
        except NotImplementedError:
            continue
    raise ProviderUnavailableError(
        f"All providers failed for {ticker}", ticker=ticker
    ) from last_exc

health_check(provider=None) async

Async variant of :meth:~stockfeed.client.StockFeedClient.health_check.

Parameters:

Name Type Description Default
provider str | None

Check a single provider by name. None checks all registered providers.

None

Returns:

Type Description
dict[str, HealthStatus]

Keyed by provider name.

Source code in src/stockfeed/async_client.py
async def health_check(self, provider: str | None = None) -> dict[str, HealthStatus]:
    """Async variant of :meth:`~stockfeed.client.StockFeedClient.health_check`.

    Parameters
    ----------
    provider : str | None
        Check a single provider by name. ``None`` checks all registered providers.

    Returns
    -------
    dict[str, HealthStatus]
        Keyed by provider name.
    """
    registry = get_default_registry()
    targets = {provider: registry.get(provider)} if provider else registry.all()
    results: dict[str, HealthStatus] = {}
    for name, cls in targets.items():
        instance = self._selector._instantiate(cls)
        if instance is not None:
            results[name] = await instance.async_health_check()
    return results

stream_quote(ticker, *, interval=5.0, provider=None, max_errors=5) async

Stream live quotes by polling ticker every interval seconds.

Parameters:

Name Type Description Default
ticker str

Uppercase ticker symbol.

required
interval float

Seconds between polls. Defaults to 5.0.

5.0
provider str | None

Pin a specific provider. None means auto-select.

None
max_errors int

Maximum consecutive transient errors before aborting.

5

Yields:

Type Description
Quote
Source code in src/stockfeed/async_client.py
async def stream_quote(
    self,
    ticker: str,
    *,
    interval: float = 5.0,
    provider: str | None = None,
    max_errors: int = 5,
) -> AsyncGenerator[Quote, None]:
    """Stream live quotes by polling *ticker* every *interval* seconds.

    Parameters
    ----------
    ticker : str
        Uppercase ticker symbol.
    interval : float
        Seconds between polls. Defaults to ``5.0``.
    provider : str | None
        Pin a specific provider. ``None`` means auto-select.
    max_errors : int
        Maximum consecutive transient errors before aborting.

    Yields
    ------
    Quote
    """
    from stockfeed.streaming.sse import stream_quote as _stream_quote

    async for quote in _stream_quote(
        ticker, self, interval=interval, provider=provider, max_errors=max_errors
    ):
        yield quote

simulate(ticker, start, end, interval, *, speed=1.0) async

Replay historical bars as an async stream (dev/backtest mode).

Requires :attr:~stockfeed.config.StockFeedSettings.dev_mode to be True (or pass dev_mode=True to the client constructor).

Parameters:

Name Type Description Default
ticker str

Uppercase ticker symbol.

required
start str | datetime

Inclusive start date. "YYYY-MM-DD" strings are accepted.

required
end str | datetime

Exclusive end date.

required
interval str | Interval

Bar width — "1d", "1h", etc.

required
speed float

Playback multiplier. 1.0 replays in real time; 0 skips all sleeps (instant replay). Defaults to 1.0.

1.0

Yields:

Type Description
OHLCVBar
Source code in src/stockfeed/async_client.py
async def simulate(
    self,
    ticker: str,
    start: str | datetime,
    end: str | datetime,
    interval: str | Interval,
    *,
    speed: float = 1.0,
) -> AsyncGenerator[OHLCVBar, None]:
    """Replay historical bars as an async stream (dev/backtest mode).

    Requires :attr:`~stockfeed.config.StockFeedSettings.dev_mode` to be
    ``True`` (or pass ``dev_mode=True`` to the client constructor).

    Parameters
    ----------
    ticker : str
        Uppercase ticker symbol.
    start : str | datetime
        Inclusive start date. ``"YYYY-MM-DD"`` strings are accepted.
    end : str | datetime
        Exclusive end date.
    interval : str | Interval
        Bar width — ``"1d"``, ``"1h"``, etc.
    speed : float
        Playback multiplier. ``1.0`` replays in real time; ``0`` skips all
        sleeps (instant replay). Defaults to ``1.0``.

    Yields
    ------
    OHLCVBar
    """
    from stockfeed.dev.simulator import simulate as _simulate

    async for bar in _simulate(ticker, start, end, interval, speed=speed, client=self):
        yield bar

get_option_expirations(ticker, provider=None) async

Async variant of :meth:~stockfeed.client.StockFeedClient.get_option_expirations.

Parameters:

Name Type Description Default
ticker str

Uppercase ticker symbol (underlying).

required
provider str | None

Pin a specific options provider. None means auto-select.

None
Source code in src/stockfeed/async_client.py
async def get_option_expirations(self, ticker: str, provider: str | None = None) -> list[date]:
    """Async variant of :meth:`~stockfeed.client.StockFeedClient.get_option_expirations`.

    Parameters
    ----------
    ticker : str
        Uppercase ticker symbol (underlying).
    provider : str | None
        Pin a specific options provider. ``None`` means auto-select.
    """
    last_exc: Exception | None = None
    for p in self._options_selector.select(preferred=provider):
        try:
            return await p.async_get_option_expirations(ticker)
        except (ProviderRateLimitError, ProviderUnavailableError) as exc:
            last_exc = exc
        except (ProviderAuthError, TickerNotFoundError):
            raise
        except NotImplementedError:
            continue
    raise ProviderUnavailableError(
        f"No options provider could return expirations for {ticker}", ticker=ticker
    ) from last_exc

get_options_chain(ticker, expiration, provider=None) async

Async variant of :meth:~stockfeed.client.StockFeedClient.get_options_chain.

Parameters:

Name Type Description Default
ticker str

Uppercase ticker symbol (underlying).

required
expiration date

Expiration date.

required
provider str | None

Pin a specific options provider. None means auto-select.

None
Source code in src/stockfeed/async_client.py
async def get_options_chain(
    self, ticker: str, expiration: date, provider: str | None = None
) -> OptionChain:
    """Async variant of :meth:`~stockfeed.client.StockFeedClient.get_options_chain`.

    Parameters
    ----------
    ticker : str
        Uppercase ticker symbol (underlying).
    expiration : date
        Expiration date.
    provider : str | None
        Pin a specific options provider. ``None`` means auto-select.
    """
    last_exc: Exception | None = None
    for p in self._options_selector.select(preferred=provider):
        try:
            return await p.async_get_options_chain(ticker, expiration)
        except (ProviderRateLimitError, ProviderUnavailableError) as exc:
            last_exc = exc
        except (ProviderAuthError, TickerNotFoundError):
            raise
        except NotImplementedError:
            continue
    raise ProviderUnavailableError(
        f"No options provider could return chain for {ticker} {expiration}", ticker=ticker
    ) from last_exc

get_option_quote(symbol, provider=None) async

Async variant of :meth:~stockfeed.client.StockFeedClient.get_option_quote.

Parameters:

Name Type Description Default
symbol str

OCC option symbol (e.g. "AAPL240119C00150000").

required
provider str | None

Pin a specific options provider. None means auto-select.

None
Source code in src/stockfeed/async_client.py
async def get_option_quote(self, symbol: str, provider: str | None = None) -> OptionQuote:
    """Async variant of :meth:`~stockfeed.client.StockFeedClient.get_option_quote`.

    Parameters
    ----------
    symbol : str
        OCC option symbol (e.g. ``"AAPL240119C00150000"``).
    provider : str | None
        Pin a specific options provider. ``None`` means auto-select.
    """
    last_exc: Exception | None = None
    for p in self._options_selector.select(preferred=provider):
        try:
            return await p.async_get_option_quote(symbol)
        except (ProviderRateLimitError, ProviderUnavailableError) as exc:
            last_exc = exc
        except (ProviderAuthError, TickerNotFoundError):
            raise
        except NotImplementedError:
            continue
    raise ProviderUnavailableError(
        f"No options provider could return quote for {symbol}"
    ) from last_exc

ProviderInfo

stockfeed.client.ProviderInfo dataclass

Metadata about a registered provider.

Source code in src/stockfeed/client.py
@dataclass(frozen=True)
class ProviderInfo:
    """Metadata about a registered provider."""

    name: str
    requires_auth: bool
    supported_intervals: list[Interval]