{"record":{"id":"450d6016c4cee111","repo":"virattt/ai-hedge-fund","slug":"ticker-as-of-as-of-only-len-metrics-filed","errorCode":null,"errorMessage":"{ticker} as of {as_of}: only {len(metrics)} filed periods (need {MIN_PERIODS})","messagePattern":"(.+?) as of (.+?): only (.+?) filed periods \\(need (.+?)\\)","errorType":"exception","errorClass":"InsufficientData","httpStatus":null,"severity":"warning","filePath":"hedge_fund/features/snapshot.py","lineNumber":134,"sourceCode":"\n\ndef build_snapshot(\n    ticker: str,\n    as_of: str,\n    data_client: DataClient,\n    periods: int = 20,\n) -> FundamentalsSnapshot:\n    \"\"\"Build the point-in-time snapshot for (ticker, as_of).\n\n    Raises InsufficientData if fewer than MIN_PERIODS filed periods exist.\n    Data-layer failures propagate (fail loud) — a broken snapshot must never\n    silently become a neutral view.\n    \"\"\"\n    metrics = data_client.get_financial_metrics(\n        ticker, as_of, period=\"ttm\", limit=periods,\n    )\n    if len(metrics) < MIN_PERIODS:\n        raise InsufficientData(\n            f\"{ticker} as of {as_of}: only {len(metrics)} filed periods \"\n            f\"(need {MIN_PERIODS})\"\n        )\n\n    # Market cap comes from the most recent FILED metrics row. Deliberately\n    # NOT data_client.get_market_cap(): that prefers company_facts.market_cap,\n    # which is latest-only — lookahead in a backtest.\n    facts = data_client.get_company_facts(ticker)\n\n    rows = [\n        PeriodFundamentals(**m.model_dump(include=set(PeriodFundamentals.model_fields)))\n        for m in metrics\n    ]\n\n    return FundamentalsSnapshot(\n        ticker=ticker,\n        as_of=as_of,\n        # Sector/industry are slow-moving company attributes; using latest","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/virattt/ai-hedge-fund/blob/eff8a7320fcf0b473b135690fa1a5b0d9b022a83/hedge_fund/features/snapshot.py#L116-L152","documentation":"Raised by build_snapshot (hedge_fund/features/snapshot.py:134) when fewer than MIN_PERIODS (=4) filed financial-metrics periods exist for the ticker as of the given date. The point-in-time snapshot needs a multi-period history to compute trends, so thin coverage is a hard stop, not a neutral view. It raises the dedicated InsufficientData (a ValueError subclass) so callers can distinguish 'this stock is too new' from real data failures.","triggerScenarios":"Calling data_client.get_financial_metrics(ticker, as_of, period='ttm', limit=periods) for: a recently IPO'd company with <4 quarters filed; a ticker that delisted before as_of; a foreign filer with sparse coverage on the provider; as_of dates earlier than the company's first filings. Note a provider outage would raise FDClientError instead — this error means the API answered with real, but too few, rows.","commonSituations":"Backtests whose start date predates a company's IPO (e.g. universe includes a 2021 listing but the window starts 2019); SPACs and recent IPOs in the universe; tiny OTC tickers the provider barely covers; survivorship-biased ticker lists containing dead tickers.","solutions":["Catch InsufficientData per ticker in the pipeline and skip that name for that cycle (treat as TickerSkip), rather than letting it kill the whole run.","Shift the backtest start date to at least 4 quarters after the ticker's first filing.","Remove the thin-coverage ticker from the universe if it will never have history (delisted before the window).","Increase the requested periods or verify coverage first with a direct get_financial_metrics call before adding the ticker to the universe."],"exampleFix":"# before\nsnap = build_snapshot(data_client, ticker, as_of)  # IPO'd 3 quarters ago -> crashes the cycle\n\n# after\nfrom hedge_fund.features.snapshot import build_snapshot, InsufficientData\n\ntry:\n    snap = build_snapshot(data_client, ticker, as_of)\nexcept InsufficientData:\n    skipped.append(TickerSkip(ticker=ticker, reason=\"insufficient filing history\"))\n    continue","handlingStrategy":"try-catch","validationCode":"MIN_PERIODS = 4  # keep in sync with hedge_fund.features.snapshot\n\ndef has_enough_history(client, ticker: str, as_of: str) -> bool:\n    try:\n        rows = client.get_financial_metrics(ticker, as_of, period=\"ttm\", limit=20)\n    except Exception:\n        return False  # let the real error surface later; this is only a pre-check\n    return len(rows) >= MIN_PERIODS","typeGuard":"from hedge_fund.features.snapshot import InsufficientData\n\ndef is_insufficient_history(e: BaseException) -> bool:\n    return isinstance(e, InsufficientData)","tryCatchPattern":"from hedge_fund.features.snapshot import build_snapshot, InsufficientData\n\ntry:\n    snap = build_snapshot(data_client, ticker, as_of)\nexcept InsufficientData:\n    skips.append(TickerSkip(ticker=ticker, reason=\"insufficient filing history\"))\n    continue  # skip the name for this cycle, keep the run alive\nexcept FDClientError:\n    raise  # data-layer failures are infrastructure: never swallow","preventionTips":["Catch InsufficientData per ticker — it is the designed skip signal, unlike FDClientError which must crash.","Filter recently-IPO'd and delisted tickers out of universes for early start dates.","Keep the local MIN_PERIODS constant in sync with the library's (currently 4)."],"tags":["fundamentals","data-validation","point-in-time","ipo"],"backgroundTag":null,"analyzedSha":"eff8a7320fcf0b473b135690fa1a5b0d9b022a83","analyzedAt":"2026-08-15T00:22:46.567Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}