From c3bb9919745172115460e168d5a6257c0ed97171 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Thu, 17 Sep 2026 23:37:43 +0000 Subject: [PATCH] fix(dataflows): date insider rows by the trade, and say when it became public - rows carry the transaction date; a Form 4 is filed up to two business days later - an indicator that could not be read is unavailable, not a blank value for the day --- tradingagents/dataflows/y_finance.py | 30 +++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/tradingagents/dataflows/y_finance.py b/tradingagents/dataflows/y_finance.py index 7ec45a227..ad5db396e 100644 --- a/tradingagents/dataflows/y_finance.py +++ b/tradingagents/dataflows/y_finance.py @@ -267,8 +267,12 @@ def get_stockstats_indicator( except NoMarketDataError: raise # Unknown/delisted symbol — let the router emit the sentinel except Exception as e: - logger.warning("Stockstats indicator %s failed on %s: %s", indicator, curr_date, e) - return "" + # An empty string renders as "2026-05-08: " in the indicator table, which + # reads as no value that day rather than a read that failed. Raise so the + # router can try the next vendor or report the series unavailable. + raise NoMarketDataError( + symbol, symbol, f"{indicator} could not be read for {curr_date}: {e}" + ) from e return str(indicator_value) @@ -457,6 +461,17 @@ def get_income_statement( return f"Error retrieving income statement for {ticker}: {str(e)}" +# Rows are dated by the transaction, which is when the insider traded, not when +# the market learned of it: a Form 4 is filed up to two business days later and +# this vendor reports no filing date, so the most recent rows may not have been +# public on the analysis date. +_TRANSACTION_DATE_VINTAGE = ( + "# Rows are dated by transaction date. A trade becomes public when its Form 4 " + "is filed, up to two business days later, so the newest rows may not have been " + "known on this date.\n\n" +) + + # This vendor dates a statement by the period it covers, not by the day it was # filed, and carries no filing date to do better. A company files weeks after its # period ends, so a run dated in that gap can be served figures that were not yet @@ -470,7 +485,7 @@ _PERIOD_END_VINTAGE = ( def get_insider_transactions( ticker: Annotated[str, "ticker symbol of the company"], - curr_date: Annotated[str | None, "only filings on or before this date, yyyy-mm-dd"] = None, + curr_date: Annotated[str | None, "only transactions on or before this date, yyyy-mm-dd"] = None, ): """Get insider transactions data from yfinance.""" canonical = normalize_symbol(ticker) @@ -484,12 +499,12 @@ def get_insider_transactions( return f"No insider transactions reported for symbol '{canonical}'" if curr_date: - filed = data["Start Date"] - kept = data[filed <= pd.Timestamp(curr_date)] + traded = data["Start Date"] + kept = data[traded <= pd.Timestamp(curr_date)] if kept.empty: return ( f"" + f"Yahoo serves recent transactions only (coverage starts {traded.min():%Y-%m-%d})>" ) data = kept @@ -498,7 +513,8 @@ def get_insider_transactions( # Add header information header = f"# Insider Transactions data for {canonical}\n" - header += f"# Data retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n" + header += f"# Data retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n" + header += _TRANSACTION_DATE_VINTAGE return header + csv_string