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()
|
graph.propagator = Propagator()
|
||||||
settled = []
|
settled = []
|
||||||
monkeypatch.setattr(graph, "_resolve_pending_entries", settled.append, raising=False)
|
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)
|
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.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")
|
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.propagator = Propagator()
|
||||||
graph.selected_analysts = ["market"]
|
graph.selected_analysts = ["market"]
|
||||||
graph._resolve_pending_entries = lambda t: None
|
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
|
graph._memory_as_of = lambda d: None
|
||||||
return graph
|
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
|
assert "trade_date" in tool.func.__code__.co_varnames
|
||||||
props = tool.tool_call_schema.model_json_schema()["properties"]
|
props = tool.tool_call_schema.model_json_schema()["properties"]
|
||||||
assert "trade_date" not in props and "curr_date" not in props
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
from tradingagents.dataflows.utils import get_current_date # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
def get_language_instruction() -> str:
|
def get_language_instruction() -> str:
|
||||||
"""Return a prompt instruction for the configured output language.
|
"""Return a prompt instruction for the configured output language.
|
||||||
@@ -137,6 +139,7 @@ def build_instrument_context(
|
|||||||
ticker: str,
|
ticker: str,
|
||||||
asset_type: str = "stock",
|
asset_type: str = "stock",
|
||||||
identity: Mapping[str, str] | None = None,
|
identity: Mapping[str, str] | None = None,
|
||||||
|
curr_date: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Describe the exact instrument so agents preserve identity and ticker.
|
"""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
|
:func:`resolve_instrument_identity`), the company name and business
|
||||||
classification are injected so agents anchor to the real company rather
|
classification are injected so agents anchor to the real company rather
|
||||||
than pattern-matching the price chart to a wrong one (#814).
|
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"
|
is_crypto = asset_type == "crypto"
|
||||||
instrument_label = "asset" if is_crypto else "instrument"
|
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 "
|
"Do not substitute a different company or ticker unless a tool "
|
||||||
"result explicitly disproves this resolved identity."
|
"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:
|
if is_crypto:
|
||||||
context += (
|
context += (
|
||||||
|
|||||||
@@ -393,7 +393,8 @@ class TradingAgentsGraph:
|
|||||||
if updates:
|
if updates:
|
||||||
self.memory_log.batch_update_with_outcomes(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.
|
"""Resolve ticker identity once and return the full instrument context.
|
||||||
|
|
||||||
Deterministic yfinance lookup (cached, fail-open) injected into a
|
Deterministic yfinance lookup (cached, fail-open) injected into a
|
||||||
@@ -403,7 +404,7 @@ class TradingAgentsGraph:
|
|||||||
graph regardless of entry point.
|
graph regardless of entry point.
|
||||||
"""
|
"""
|
||||||
identity = resolve_instrument_identity(ticker)
|
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:
|
def _memory_as_of(self, trade_date) -> str | None:
|
||||||
"""Point-in-time cutoff for past-context lessons (#1251).
|
"""Point-in-time cutoff for past-context lessons (#1251).
|
||||||
@@ -551,7 +552,7 @@ class TradingAgentsGraph:
|
|||||||
past_context=self.memory_log.get_past_context(
|
past_context=self.memory_log.get_past_context(
|
||||||
company_name, as_of=self._memory_as_of(trade_date)
|
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 "",
|
portfolio_context=portfolio.render(company_name) if portfolio is not None else "",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user