{"record":{"id":"1d83f2834030076d","repo":"TauricResearch/TradingAgents","slug":"latest-row-is-latest-date-stale-days-days-b","errorCode":null,"errorMessage":"latest row is {latest.date()}, {stale_days} days before the requested {requested.date()} (stale) — refusing to use it","messagePattern":"latest row is (.+?), (.+?) days before the requested (.+?) \\(stale\\) — refusing to use it","errorType":"exception","errorClass":"NoMarketDataError","httpStatus":null,"severity":"warning","filePath":"tradingagents/dataflows/stockstats_utils.py","lineNumber":123,"sourceCode":"    it like any other \"no usable data from this vendor\" — try the next vendor,\n    then emit one clear unavailable signal. Empty frames are left to the\n    caller's existing no-data handling; this guards only the dangerous case of\n    present-but-stale rows (a vendor returning a year-old frame that would\n    otherwise feed wrong prices to the agent, #1021).\n    \"\"\"\n    if data is None or data.empty:\n        return\n    requested = pd.to_datetime(curr_date, errors=\"coerce\")\n    if pd.isna(requested):\n        return\n    requested = requested.normalize()\n    dates = _coerce_ohlcv_dates(data)\n    if dates.empty:\n        return\n    latest = dates.max().normalize()\n    stale_days = (requested - latest).days\n    if stale_days > max_stale_days:\n        raise NoMarketDataError(\n            symbol,\n            canonical,\n            f\"latest row is {latest.date()}, {stale_days} days before the \"\n            f\"requested {requested.date()} (stale) — refusing to use it\",\n        )\n\n\ndef _needs_same_day_refresh(data_file, curr_date_dt, today_date) -> bool:\n    \"\"\"Whether a cached frame must be refetched to reflect the requested day.\n\n    The cache file is keyed per day, so without this a run started before the\n    day's bar was final keeps serving that snapshot to every later run (#1150).\n    Two distinct staleness cases exist for a current-day request: the bar may be\n    missing entirely, or present but still in progress — Yahoo publishes a\n    partial daily candle during market hours, whose ``Close`` is not the closing\n    price. Row inspection cannot tell a partial bar from a final one, so the TTL\n    governs every current-day cache. Historical requests always reuse the cache,\n    since those rows are immutable.","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/TauricResearch/TradingAgents/blob/a33fd4c0f134485a43553a2c23a63cb14adbd88f/tradingagents/dataflows/stockstats_utils.py#L105-L141","documentation":"Raised by the staleness guard in tradingagents/dataflows/stockstats_utils.py when the newest OHLCV row (after normalization) is more than max_stale_days before the requested curr_date. It is a NoMarketDataError carrying (symbol, canonical, detail) — the router converts it into the explicit NO_DATA_AVAILABLE sentinel telling the agent not to fabricate values, rather than silently feeding year-old prices into indicators (#1021).","triggerScenarios":"Requesting curr_date=today on a symbol whose cache ends weeks earlier (e.g. a halted/delisted stock); weekend/holiday requests beyond max_stale_days; a cache file written mid-run before the day's bar existed (#1150) being reused for a later date; non-US calendars where the last trading day is far back.","commonSituations":"Backtests crossing market holidays; symbols with sparse trading (some OTC/ETCs); stale per-day cache files; analysts asking for 'today' data on a long weekend.","solutions":["Tune the tolerance: pass/raise max_stale_days appropriate to the market calendar (e.g. >= 4 days covers long weekends)","Refresh the cache for the requested date: delete the symbol's cached CSV so the next call refetches up to curr_date","Align curr_date to the last actual trading day (e.g. previous business day) instead of a weekend/holiday date","Handle NoMarketDataError (or the returned NO_DATA_AVAILABLE sentinel string) by skipping or reporting unavailable instead of retrying — it is a data verdict, not a transient failure"],"exampleFix":"# before\nget_stock_data_indicators_window_sma(\"HALTED\", \"2025-06-10\", 10, 10)\n# -> NoMarketDataError: ... latest row is 2025-05-20, 21 days before the requested 2025-06-10 (stale)\n\n# after\n# align to a real trading day and widen tolerance for weekends\nget_stock_data_indicators_window_sma(\"SPY\", \"2025-06-10\", 10, 10, max_stale_days=5)\nfrom tradingagents.dataflows.errors import NoMarketDataError\ntry: ...\nexcept NoMarketDataError as e: report_unavailable(e.symbol)","handlingStrategy":"validation","validationCode":"import pandas as pd\nfrom tradingagents.dataflows.stockstats_utils import load_ohlcv, _coerce_ohlcv_dates\n\ndef fresh_enough(symbol: str, curr_date: str, max_stale_days: int) -> bool:\n    data = load_ohlcv(symbol, curr_date)\n    if data is None or data.empty:\n        return False\n    dates = _coerce_ohlcv_dates(data)\n    if dates.empty:\n        return False\n    latest = dates.max().normalize()\n    return (pd.to_datetime(curr_date).normalize() - latest).days <= max_stale_days","typeGuard":null,"tryCatchPattern":"from tradingagents.dataflows.errors import NoMarketDataError\n\ntry:\n    out = get_stock_data_indicators_window_sma(sym, curr_date, 10, 10)\nexcept NoMarketDataError as e:\n    # stale data is a verdict, not a transient error: skip/report, don't retry blindly\n    log_unavailable(e.symbol, e.detail)","preventionTips":["Set max_stale_days with the market calendar in mind (weekends, holidays = 3-4+ days)","Align curr_date to actual trading days in backtests","Refresh per-day caches when rerunning later dates; don't reuse a morning snapshot for evening runs"],"tags":["data-quality","stale-data","ohlcv","backtesting","market-calendar"],"backgroundTag":null,"analyzedSha":"a33fd4c0f134485a43553a2c23a63cb14adbd88f","analyzedAt":"2026-08-14T19:45:16.920Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}