From 821848bb8261c666822335ece9b3d7fadaa5aa0d Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Mon, 7 Sep 2026 22:21:20 +0000 Subject: [PATCH] docs: tighten the comments on the point-in-time guards - keep what the code cannot state itself: which session a closeless bar is, where the drop actually happens, and why the trim must stay unguarded - drop the field-by-field enumeration, the account of what the previous behaviour got wrong, and the restatements of adjacent calls --- tradingagents/dataflows/alpha_vantage_common.py | 4 ++-- tradingagents/dataflows/date_window.py | 15 +++++---------- tradingagents/dataflows/stockstats_utils.py | 10 +++------- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/tradingagents/dataflows/alpha_vantage_common.py b/tradingagents/dataflows/alpha_vantage_common.py index b35feccd9..ecbe4dcec 100644 --- a/tradingagents/dataflows/alpha_vantage_common.py +++ b/tradingagents/dataflows/alpha_vantage_common.py @@ -130,8 +130,8 @@ def _filter_csv_by_date_range(csv_data: str, start_date: str, end_date: str) -> # Deliberately unguarded: TIME_SERIES_DAILY_ADJUSTED returns the full series # up to today, so this trim is the only thing keeping bars after end_date out - # of a historical run. Returning the untrimmed body on failure would leak - # future prices, so a parse failure propagates and the caller fails closed. + # of a historical run. Swallowing a parse failure would serve the untrimmed + # body, and with it future prices. df = pd.read_csv(StringIO(csv_data)) # Assume the first column is the date column (timestamp) diff --git a/tradingagents/dataflows/date_window.py b/tradingagents/dataflows/date_window.py index cd27cdc32..3a7dc0206 100644 --- a/tradingagents/dataflows/date_window.py +++ b/tradingagents/dataflows/date_window.py @@ -36,16 +36,11 @@ def withhold_live_profile(curr_date: str | None, label: str) -> str | None: """Notice to serve instead of a live-only company profile, or None to serve it. Vendor "company overview" endpoints (yfinance ``Ticker.info``, Alpha Vantage - ``OVERVIEW``) return only present-day values: market cap, valuation - multiples, the 52-week range and TTM income all move with today's quote, and - even name, sector and industry shift when a company renames or is - reclassified. None of it carries a historical vintage, so serving it into a - run dated in the past puts post-decision information into the analyst's - context (#1300). - - Centralized so every fundamentals vendor withholds on the same rule and says - the same thing; point-in-time statements come from the balance sheet, income - statement and cash flow tools, which filter on ``curr_date``. + ``OVERVIEW``) carry no historical vintage — not even name, sector and + industry, which move when a company renames or is reclassified — so serving + one into a run dated in the past leaks post-decision information (#1300). + Every fundamentals vendor withholds on this rule, so switching between them + cannot reintroduce the leak. """ if not curr_date: return None diff --git a/tradingagents/dataflows/stockstats_utils.py b/tradingagents/dataflows/stockstats_utils.py index b29612ece..1bdd02ab8 100644 --- a/tradingagents/dataflows/stockstats_utils.py +++ b/tradingagents/dataflows/stockstats_utils.py @@ -250,14 +250,10 @@ def load_ohlcv(symbol: str, curr_date: str) -> pd.DataFrame: # Filter to curr_date to prevent look-ahead bias in backtesting. data = data[data["Date"] <= curr_date_dt] - # A newest bar with no close is usually an unsettled session — mid-session, - # a holiday, or a thinly traded instrument — not a symbol without data. + # A closeless newest bar is an unsettled session, not a symbol without data. # _fill_price_gaps below drops it, here and mid-series alike, so the frame - # ends at the last settled bar rather than carrying a fabricated close - # (#1201). Refusing the whole frame instead reported a tradable symbol as - # invalid or delisted (#1289), so only a range with no close anywhere is - # treated as no data; the staleness check decides whether what remains is - # recent enough for curr_date. + # ends at the last settled bar; only a range with no close anywhere is no + # data (#1201, #1289). if not data.empty and pd.isna(data["Close"].iloc[-1]): settled = data["Close"].notna().to_numpy().nonzero()[0] if settled.size == 0: