mirror of
https://github.com/TauricResearch/TradingAgents.git
synced 2026-09-19 11:15:24 +03:00
fix(agents): say when the resolved identity is today's, not the run date's
- the vendor profile has no historical vintage, and every agent is told to anchor to it
This commit is contained in:
@@ -31,7 +31,7 @@ def test_create_run_state_settles_pending_and_carries_context(tmp_path, monkeypa
|
||||
graph.propagator = Propagator()
|
||||
settled = []
|
||||
monkeypatch.setattr(graph, "_resolve_pending_entries", settled.append, raising=False)
|
||||
monkeypatch.setattr(graph, "resolve_instrument_context", lambda t, a="stock": f"id:{t}", raising=False)
|
||||
monkeypatch.setattr(graph, "resolve_instrument_context", lambda t, a="stock", d=None: f"id:{t}", raising=False)
|
||||
monkeypatch.setattr(graph, "_memory_as_of", lambda d: d, raising=False)
|
||||
graph.memory_log.store_decision("NVDA", "2026-01-05", "Rating: Buy\nold call")
|
||||
graph.memory_log.update_with_outcome("NVDA", "2026-01-05", 0.01, 0.005, 5, "great trade", "2026-01-12")
|
||||
|
||||
@@ -87,7 +87,7 @@ def _bare_graph(tmp_path):
|
||||
graph.propagator = Propagator()
|
||||
graph.selected_analysts = ["market"]
|
||||
graph._resolve_pending_entries = lambda t: None
|
||||
graph.resolve_instrument_context = lambda t, a="stock": ""
|
||||
graph.resolve_instrument_context = lambda t, a="stock", d=None: ""
|
||||
graph._memory_as_of = lambda d: None
|
||||
return graph
|
||||
|
||||
|
||||
@@ -83,3 +83,69 @@ def test_trade_date_is_injected_not_model_visible(tool):
|
||||
assert "trade_date" in tool.func.__code__.co_varnames
|
||||
props = tool.tool_call_schema.model_json_schema()["properties"]
|
||||
assert "trade_date" not in props and "curr_date" not in props
|
||||
|
||||
|
||||
# --- the instrument's identity -------------------------------------------------
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_a_historical_run_is_told_the_identity_is_current(monkeypatch):
|
||||
"""The company name, sector and industry come from today's vendor profile.
|
||||
They are usually right for a past date, but a company that renamed or was
|
||||
reclassified since would read wrong, and every agent is told to anchor to
|
||||
this identity, so the run has to know which date it describes."""
|
||||
from tradingagents.agents.utils.agent_utils import build_instrument_context
|
||||
|
||||
identity = {"company_name": "Example Corp", "sector": "Technology",
|
||||
"industry": "Software", "exchange": "NMS"}
|
||||
|
||||
historical = build_instrument_context("EXMP", "stock", identity, curr_date="2024-03-14")
|
||||
assert "Example Corp" in historical
|
||||
assert "2024-03-14" in historical and "today" in historical.lower()
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_a_current_run_is_not_cluttered_with_a_vintage_note(monkeypatch):
|
||||
from tradingagents.agents.utils.agent_utils import build_instrument_context
|
||||
from tradingagents.dataflows.utils import get_current_date
|
||||
|
||||
today = build_instrument_context("EXMP", "stock", {"company_name": "Example Corp"},
|
||||
curr_date=get_current_date())
|
||||
assert "Example Corp" in today
|
||||
assert "resolved today" not in today.lower()
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_insider_rows_are_dated_by_the_trade_not_the_filing():
|
||||
"""yfinance reports the transaction date and carries no filing date. A trade
|
||||
becomes public when the Form 4 is filed, up to two business days later, so a
|
||||
run must not be told these rows were public on their transaction date."""
|
||||
import pandas as pd
|
||||
|
||||
from tradingagents.dataflows import y_finance
|
||||
|
||||
frame = pd.DataFrame({
|
||||
"Shares": [100, 200],
|
||||
"Text": ["Sale at price 10.00 per share.", "Sale at price 11.00 per share."],
|
||||
"Start Date": pd.to_datetime(["2026-05-01", "2026-05-20"]),
|
||||
})
|
||||
ticker = mock.Mock(insider_transactions=frame)
|
||||
with mock.patch.object(y_finance.yf, "Ticker", return_value=ticker):
|
||||
out = y_finance.get_insider_transactions("AAPL", "2026-05-10")
|
||||
|
||||
assert "2026-05-01" in out and "2026-05-20" not in out # still bounded by the date
|
||||
assert "transaction date" in out.lower() # and says what the date means
|
||||
assert "filed" in out.lower() # and that filing comes later
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_an_indicator_that_could_not_be_read_is_not_shown_as_a_blank_value():
|
||||
"""The per-day fallback returned an empty string for a failed read, so the
|
||||
table rendered a row per day with nothing after the colon: an analyst reads
|
||||
that as "no value on that day" rather than "could not be obtained"."""
|
||||
from tradingagents.dataflows import y_finance
|
||||
from tradingagents.dataflows.errors import VendorError
|
||||
|
||||
with mock.patch.object(y_finance.StockstatsUtils, "get_stock_stats",
|
||||
side_effect=RuntimeError("cache parse failed")), \
|
||||
pytest.raises(VendorError):
|
||||
y_finance.get_stockstats_indicator("AAPL", "rsi", "2026-05-08")
|
||||
|
||||
@@ -48,6 +48,8 @@ __all__ = [
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
from tradingagents.dataflows.utils import get_current_date # noqa: E402
|
||||
|
||||
|
||||
def get_language_instruction() -> str:
|
||||
"""Return a prompt instruction for the configured output language.
|
||||
@@ -137,6 +139,7 @@ def build_instrument_context(
|
||||
ticker: str,
|
||||
asset_type: str = "stock",
|
||||
identity: Mapping[str, str] | None = None,
|
||||
curr_date: str | None = None,
|
||||
) -> str:
|
||||
"""Describe the exact instrument so agents preserve identity and ticker.
|
||||
|
||||
@@ -144,6 +147,11 @@ def build_instrument_context(
|
||||
:func:`resolve_instrument_identity`), the company name and business
|
||||
classification are injected so agents anchor to the real company rather
|
||||
than pattern-matching the price chart to a wrong one (#814).
|
||||
|
||||
That profile carries no historical vintage: it describes the company today.
|
||||
For a run dated earlier, the context says so, since a company that has since
|
||||
renamed or been reclassified would otherwise anchor the whole graph to an
|
||||
identity it did not have on the analysis date.
|
||||
"""
|
||||
is_crypto = asset_type == "crypto"
|
||||
instrument_label = "asset" if is_crypto else "instrument"
|
||||
@@ -174,6 +182,13 @@ def build_instrument_context(
|
||||
"Do not substitute a different company or ticker unless a tool "
|
||||
"result explicitly disproves this resolved identity."
|
||||
)
|
||||
today = get_current_date()
|
||||
if curr_date and str(curr_date) < today:
|
||||
context += (
|
||||
f" This identity is how the vendor describes the instrument today "
|
||||
f"({today}), not necessarily on {curr_date}: a name or "
|
||||
f"classification changed since then would read as the current one."
|
||||
)
|
||||
|
||||
if is_crypto:
|
||||
context += (
|
||||
|
||||
@@ -393,7 +393,8 @@ class TradingAgentsGraph:
|
||||
if updates:
|
||||
self.memory_log.batch_update_with_outcomes(updates)
|
||||
|
||||
def resolve_instrument_context(self, ticker: str, asset_type: str = "stock") -> str:
|
||||
def resolve_instrument_context(self, ticker: str, asset_type: str = "stock",
|
||||
curr_date: str | None = None) -> str:
|
||||
"""Resolve ticker identity once and return the full instrument context.
|
||||
|
||||
Deterministic yfinance lookup (cached, fail-open) injected into a
|
||||
@@ -403,7 +404,7 @@ class TradingAgentsGraph:
|
||||
graph regardless of entry point.
|
||||
"""
|
||||
identity = resolve_instrument_identity(ticker)
|
||||
return build_instrument_context(ticker, asset_type, identity)
|
||||
return build_instrument_context(ticker, asset_type, identity, curr_date)
|
||||
|
||||
def _memory_as_of(self, trade_date) -> str | None:
|
||||
"""Point-in-time cutoff for past-context lessons (#1251).
|
||||
@@ -551,7 +552,7 @@ class TradingAgentsGraph:
|
||||
past_context=self.memory_log.get_past_context(
|
||||
company_name, as_of=self._memory_as_of(trade_date)
|
||||
),
|
||||
instrument_context=self.resolve_instrument_context(company_name, asset_type),
|
||||
instrument_context=self.resolve_instrument_context(company_name, asset_type, trade_date),
|
||||
portfolio_context=portfolio.render(company_name) if portfolio is not None else "",
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user