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
This commit is contained in:
Yijia-Xiao
2026-09-17 23:37:43 +00:00
parent f0a1cf6290
commit c3bb991974

View File

@@ -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"<insider transactions unavailable for {canonical} as of {curr_date}: "
f"Yahoo serves recent filings only (coverage starts {filed.min():%Y-%m-%d})>"
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