ZhuLinsen/daily_stock_analysis · warning · DataFetchError

[Finnhub] {stock_code} is not a US stock

Error message

[Finnhub] {stock_code} is not a US stock

What it means

FinnhubFetcher serves US equities only; _fetch_raw_data rejects any code failing is_us_stock_code. It is a deliberate capability guard so the failover chain skips Finnhub for A-share/HK/other codes instead of sending a doomed API request.

Source

Thrown at data_provider/finnhub_fetcher.py:45

class FinnhubFetcher(BaseFetcher):
    name = "FinnhubFetcher"
    priority = 2

    def __init__(self):
        from src.config import get_config
        config = get_config()
        self._api_key = getattr(config, 'finnhub_api_key', None) or os.getenv('FINNHUB_API_KEY')
        if not self._api_key:
            logger.debug("[Finnhub] API key not configured, fetcher disabled")

    def _is_us_stock(self, stock_code: str) -> bool:
        return is_us_stock_code(stock_code)

    def _fetch_raw_data(self, stock_code: str, start_date: str, end_date: str) -> pd.DataFrame:
        if not self._api_key:
            raise DataFetchError("[Finnhub] API key not configured")
        if not self._is_us_stock(stock_code):
            raise DataFetchError(f"[Finnhub] {stock_code} is not a US stock")

        symbol = stock_code.strip().upper()
        start_ts = int(datetime.strptime(start_date, '%Y-%m-%d').timestamp())
        end_ts = int(datetime.strptime(end_date, '%Y-%m-%d').timestamp())

        url = f"{_FINNHUB_BASE_URL}/stock/candle"
        params = {
            'symbol': symbol,
            'resolution': 'D',
            'from': start_ts,
            'to': end_ts,
            'token': self._api_key,
        }

        try:
            self.random_sleep(0.3, 0.8)
            resp = requests.get(url, params=params, timeout=15)
            resp.raise_for_status()

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Route non-US codes through DataFetcherManager so market-appropriate sources handle them.
  2. Pre-check with the same predicate (is_us_stock_code / fetcher._is_us_stock) before calling FinnhubFetcher.
  3. Catch DataFetchError and skip to the next source — do not retry the same fetcher with the same non-US code.

Example fix

# before
f = FinnhhubFetcher()
df = f.get_daily_data('600519', ...)  # raises
# after
if f._is_us_stock(code):
    df = f.get_daily_data(code, ...)
else:
    df = manager.get_daily_data(code, ...)
Defensive patterns

Strategy: type-guard

Validate before calling

from src.utils.market_detector import is_us_stock_code  # same predicate the fetcher uses
if is_us_stock_code(code):
    df = finnhub.get_daily_data(code, ...)

Type guard

def is_us_ticker(code: str) -> bool:
    return is_us_stock_code(code)

Try / catch

try:
    df = finnhub.get_daily_data(code, ...)
except DataFetchError as e:
    if 'is not a US stock' in str(e):
        continue  # routing mistake — route via manager instead

Prevention

When it happens

Trigger: Directly invoking FinnhubFetcher.get_daily_data with '600519', 'hk00700', etc. — codes is_us_stock_code rejects. Through DataFetcherManager the US routing only offers Finnhub for US codes (and US indices via Yfinance-first order), so this normally appears only in direct/test usage.

Common situations: Tests driving every fetcher with a mixed-market list; custom loops bypassing the manager's market filter.

Related errors


AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15). Data as JSON: /api/errors/e74371ccd3884919. Report an issue: GitHub.