diff --git a/tests/test_news_analyst_prompt.py b/tests/test_news_analyst_prompt.py index 4af6b04e8..9b024121a 100644 --- a/tests/test_news_analyst_prompt.py +++ b/tests/test_news_analyst_prompt.py @@ -1,7 +1,8 @@ """Guard the news analyst prompt against tool-signature drift (#1116). -The prompt used to advertise ``get_news(query, ...)`` while the tool takes a -``ticker``, tricking the LLM into hallucinating free-text query calls. +The prompt used to advertise ``get_news(query, ...)``, tricking the LLM into +hallucinating free-text query calls. The instrument now comes from the run's +state, so the model passes only the window. """ import inspect @@ -12,14 +13,13 @@ from tradingagents.agents.tools import get_news @pytest.mark.unit -def test_get_news_takes_ticker_not_query(): - arg_names = set(get_news.args.keys()) - assert "ticker" in arg_names - assert "query" not in arg_names +def test_get_news_takes_the_window_not_a_query(): + arg_names = set(get_news.tool_call_schema.model_json_schema()["properties"]) + assert arg_names == {"start_date", "end_date"} @pytest.mark.unit def test_news_prompt_matches_get_news_signature(): src = inspect.getsource(na) - assert "get_news(ticker, start_date, end_date)" in src + assert "get_news(start_date, end_date)" in src assert "get_news(query" not in src diff --git a/tests/test_tool_date_enforcement.py b/tests/test_tool_date_enforcement.py index c313a2db7..cb968d688 100644 --- a/tests/test_tool_date_enforcement.py +++ b/tests/test_tool_date_enforcement.py @@ -68,8 +68,31 @@ def test_trade_date_is_hidden_from_the_model(tool): assert "trade_date" not in tool.tool_call_schema.model_json_schema()["properties"] +INSTRUMENT_TOOLS = [ + tools.get_stock_data, tools.get_indicators, tools.get_verified_market_snapshot, + tools.get_fundamentals, tools.get_balance_sheet, tools.get_cashflow, + tools.get_income_statement, tools.get_news, tools.get_insider_transactions, +] + + +@pytest.mark.unit +@pytest.mark.parametrize("tool", INSTRUMENT_TOOLS, ids=lambda t: t.name) +def test_the_instrument_is_hidden_from_the_model(tool): + """The model once passed an indicator name as the symbol ("rsi", "atr" are + real tickers), and another company's data reached the report.""" + properties = tool.tool_call_schema.model_json_schema()["properties"] + assert "symbol" not in properties and "ticker" not in properties + + +@pytest.mark.unit +def test_a_tool_serves_the_run_instrument_whatever_the_model_passes(): + args = _run(tools.get_indicators, {"symbol": "RSI", "indicator": "rsi", "curr_date": TRADE_DATE}, tools) + assert args[1] == "NVDA" + + class _State(MessagesState): trade_date: str + company_of_interest: str def _run(tool, args, module): @@ -82,28 +105,27 @@ def _run(tool, args, module): graph.compile().invoke({ "messages": [AIMessage("", tool_calls=[{"name": tool.name, "args": args, "id": "1"}])], "trade_date": TRADE_DATE, + "company_of_interest": "NVDA", }) return routed.call_args.args @pytest.mark.unit def test_statement_tool_with_omitted_date_uses_the_run_date(): - args = _run(tools.get_balance_sheet, {"ticker": "AAPL"}, tools) + args = _run(tools.get_balance_sheet, {}, tools) assert args[-1] == TRADE_DATE # #1331: an omitted date no longer means unfiltered @pytest.mark.unit def test_future_curr_date_from_the_model_is_clamped(): - args = _run(tools.get_fundamentals, - {"ticker": "AAPL", "curr_date": "2026-09-14"}, tools) - assert args == ("get_fundamentals", "AAPL", TRADE_DATE) + args = _run(tools.get_fundamentals, {"curr_date": "2026-09-14"}, tools) + assert args == ("get_fundamentals", "NVDA", TRADE_DATE) @pytest.mark.unit def test_future_window_from_the_model_is_clamped(): - args = _run(tools.get_stock_data, - {"symbol": "AAPL", "start_date": "2026-08-01", "end_date": "2026-09-14"}, tools) - assert args == ("get_stock_data", "AAPL", "2026-08-01", TRADE_DATE) + args = _run(tools.get_stock_data, {"start_date": "2026-08-01", "end_date": "2026-09-14"}, tools) + assert args == ("get_stock_data", "NVDA", "2026-08-01", TRADE_DATE) @pytest.mark.unit diff --git a/tradingagents/agents/analysts/news_analyst.py b/tradingagents/agents/analysts/news_analyst.py index fc9e0c9c9..e0307c8e1 100644 --- a/tradingagents/agents/analysts/news_analyst.py +++ b/tradingagents/agents/analysts/news_analyst.py @@ -25,7 +25,7 @@ def create_news_analyst(llm): instrument_context = get_instrument_context_from_state(state) system_message = ( - f"You are a news researcher tasked with analyzing recent news and trends over the past week. Please write a comprehensive report of the current state of the world that is relevant for trading and macroeconomics. Use the available tools: get_news(ticker, start_date, end_date) for {asset_label}-specific news by ticker symbol, get_global_news(curr_date, look_back_days, limit) for broader macroeconomic news, get_macro_indicators(indicator, curr_date, look_back_days) to ground macro commentary in actual data from FRED (e.g. 'cpi', 'core_pce', 'unemployment', 'fed_funds_rate', '10y_treasury', 'yield_curve'), and get_prediction_markets(topic, limit) for live market-implied probabilities of forward-looking events (e.g. 'Fed rate cut', 'recession 2026', geopolitical or sector events). Provide specific, actionable insights with supporting evidence to help traders make informed decisions." + f"You are a news researcher tasked with analyzing recent news and trends over the past week. Please write a comprehensive report of the current state of the world that is relevant for trading and macroeconomics. Use the available tools: get_news(start_date, end_date) for news about the {asset_label} under analysis, get_global_news(curr_date, look_back_days, limit) for broader macroeconomic news, get_macro_indicators(indicator, curr_date, look_back_days) to ground macro commentary in actual data from FRED (e.g. 'cpi', 'core_pce', 'unemployment', 'fed_funds_rate', '10y_treasury', 'yield_curve'), and get_prediction_markets(topic, limit) for live market-implied probabilities of forward-looking events (e.g. 'Fed rate cut', 'recession 2026', geopolitical or sector events). Provide specific, actionable insights with supporting evidence to help traders make informed decisions." + """ Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read.""" + get_language_instruction() ) diff --git a/tradingagents/agents/context.py b/tradingagents/agents/context.py index 354531e27..e2a754369 100644 --- a/tradingagents/agents/context.py +++ b/tradingagents/agents/context.py @@ -116,7 +116,7 @@ def build_instrument_context( instrument_label = "asset" if is_crypto else "instrument" context = ( f"The {instrument_label} to analyze is `{ticker}`. " - "Use this exact ticker in every tool call, report, and recommendation, " + "The tools serve this instrument; refer to it by this exact ticker in every report and recommendation, " "preserving any exchange suffix (e.g. `.TO`, `.L`, `.HK`, `.T`, `-USD`)." ) diff --git a/tradingagents/agents/tools.py b/tradingagents/agents/tools.py index 712dd90f3..872df4481 100644 --- a/tradingagents/agents/tools.py +++ b/tradingagents/agents/tools.py @@ -17,20 +17,19 @@ from tradingagents.dataflows.vendors.yahoo.snapshot import build_verified_market @tool def get_stock_data( - symbol: Annotated[str, "ticker symbol of the company"], + symbol: Annotated[str, InjectedState("company_of_interest")], start_date: Annotated[str, "Start date in yyyy-mm-dd format"], end_date: Annotated[str, "End date in yyyy-mm-dd format"], trade_date: Annotated[str, InjectedState("trade_date")] = "", ) -> str: """ - Retrieve stock price data (OHLCV) for a given ticker symbol. + Retrieve stock price data (OHLCV) for the instrument under analysis. Uses the configured core_stock_apis vendor. Args: - symbol (str): Ticker symbol of the company, e.g. AAPL, TSM start_date (str): Start date in yyyy-mm-dd format end_date (str): End date in yyyy-mm-dd format Returns: - str: A formatted dataframe containing the stock price data for the specified ticker symbol in the specified date range. + str: A formatted dataframe containing the price data for the instrument over the date range. """ start_date, end_date = as_of_window(start_date, end_date, trade_date) return route_to_vendor("get_stock_data", symbol, start_date, end_date) @@ -38,22 +37,21 @@ def get_stock_data( @tool def get_indicators( - symbol: Annotated[str, "ticker symbol of the company"], + symbol: Annotated[str, InjectedState("company_of_interest")], indicator: Annotated[str, "technical indicator to get the analysis and report of"], curr_date: Annotated[str, "The current trading date you are trading on, YYYY-mm-dd"], look_back_days: Annotated[int, "how many days to look back"] = 30, trade_date: Annotated[str, InjectedState("trade_date")] = "", ) -> str: """ - Retrieve a single technical indicator for a given ticker symbol. + Retrieve a single technical indicator for the instrument under analysis. Uses the configured technical_indicators vendor. Args: - symbol (str): Ticker symbol of the company, e.g. AAPL, TSM indicator (str): A single technical indicator name, e.g. 'rsi', 'macd'. Call this tool once per indicator. curr_date (str): The current trading date you are trading on, YYYY-mm-dd look_back_days (int): How many days to look back, default is 30 Returns: - str: A formatted dataframe containing the technical indicators for the specified ticker symbol and indicator. + str: A formatted dataframe containing the technical indicators for the instrument and indicator. """ # LLMs sometimes pass multiple indicators as a comma-separated string; # split and process each individually. @@ -70,7 +68,7 @@ def get_indicators( @tool def get_verified_market_snapshot( - symbol: Annotated[str, "ticker symbol of the company"], + symbol: Annotated[str, InjectedState("company_of_interest")], curr_date: Annotated[str, "the current trading date, YYYY-mm-dd"], look_back_days: Annotated[ int, "number of recent trading rows to include for sanity-checking" @@ -93,15 +91,14 @@ def get_verified_market_snapshot( @tool def get_fundamentals( - ticker: Annotated[str, "ticker symbol"], + ticker: Annotated[str, InjectedState("company_of_interest")], curr_date: Annotated[str, "current date you are trading at, yyyy-mm-dd"], trade_date: Annotated[str, InjectedState("trade_date")] = "", ) -> str: """ - Retrieve comprehensive fundamental data for a given ticker symbol. + Retrieve comprehensive fundamental data for the instrument under analysis. Uses the configured fundamental_data vendor. Args: - ticker (str): Ticker symbol of the company curr_date (str): Current date you are trading at, yyyy-mm-dd Returns: str: A formatted report containing comprehensive fundamental data @@ -111,16 +108,15 @@ def get_fundamentals( @tool def get_balance_sheet( - ticker: Annotated[str, "ticker symbol"], + ticker: Annotated[str, InjectedState("company_of_interest")], freq: Annotated[str, "reporting frequency: annual/quarterly"] = "quarterly", curr_date: Annotated[str, "current date you are trading at, yyyy-mm-dd"] = None, trade_date: Annotated[str, InjectedState("trade_date")] = "", ) -> str: """ - Retrieve balance sheet data for a given ticker symbol. + Retrieve balance sheet data for the instrument under analysis. Uses the configured fundamental_data vendor. Args: - ticker (str): Ticker symbol of the company freq (str): Reporting frequency: annual/quarterly (default quarterly) curr_date (str): Current date you are trading at, yyyy-mm-dd Returns: @@ -131,16 +127,15 @@ def get_balance_sheet( @tool def get_cashflow( - ticker: Annotated[str, "ticker symbol"], + ticker: Annotated[str, InjectedState("company_of_interest")], freq: Annotated[str, "reporting frequency: annual/quarterly"] = "quarterly", curr_date: Annotated[str, "current date you are trading at, yyyy-mm-dd"] = None, trade_date: Annotated[str, InjectedState("trade_date")] = "", ) -> str: """ - Retrieve cash flow statement data for a given ticker symbol. + Retrieve cash flow statement data for the instrument under analysis. Uses the configured fundamental_data vendor. Args: - ticker (str): Ticker symbol of the company freq (str): Reporting frequency: annual/quarterly (default quarterly) curr_date (str): Current date you are trading at, yyyy-mm-dd Returns: @@ -151,16 +146,15 @@ def get_cashflow( @tool def get_income_statement( - ticker: Annotated[str, "ticker symbol"], + ticker: Annotated[str, InjectedState("company_of_interest")], freq: Annotated[str, "reporting frequency: annual/quarterly"] = "quarterly", curr_date: Annotated[str, "current date you are trading at, yyyy-mm-dd"] = None, trade_date: Annotated[str, InjectedState("trade_date")] = "", ) -> str: """ - Retrieve income statement data for a given ticker symbol. + Retrieve income statement data for the instrument under analysis. Uses the configured fundamental_data vendor. Args: - ticker (str): Ticker symbol of the company freq (str): Reporting frequency: annual/quarterly (default quarterly) curr_date (str): Current date you are trading at, yyyy-mm-dd Returns: @@ -171,16 +165,15 @@ def get_income_statement( @tool def get_news( - ticker: Annotated[str, "Ticker symbol"], + ticker: Annotated[str, InjectedState("company_of_interest")], start_date: Annotated[str, "Start date in yyyy-mm-dd format"], end_date: Annotated[str, "End date in yyyy-mm-dd format"], trade_date: Annotated[str, InjectedState("trade_date")] = "", ) -> str: """ - Retrieve news data for a given ticker symbol. + Retrieve news data for the instrument under analysis. Uses the configured news_data vendor. Args: - ticker (str): Ticker symbol start_date (str): Start date in yyyy-mm-dd format end_date (str): End date in yyyy-mm-dd format Returns: @@ -216,14 +209,12 @@ def get_global_news( @tool def get_insider_transactions( - ticker: Annotated[str, "ticker symbol"], + ticker: Annotated[str, InjectedState("company_of_interest")], trade_date: Annotated[str, InjectedState("trade_date")] = "", ) -> str: """ Retrieve insider transaction information about a company. Uses the configured news_data vendor. - Args: - ticker (str): Ticker symbol of the company Returns: str: A report of insider transaction data """