From aef4af90e36612e9bee495686cf6ed403dde45b9 Mon Sep 17 00:00:00 2001 From: Yijia-Xiao Date: Fri, 18 Sep 2026 00:04:53 +0000 Subject: [PATCH] fix(dataflows): let the next vendor serve what Alpha Vantage cannot - an indicator it does not carry raises instead of returning prose the router counts as an answer - ticker news asks for the configured article limit --- tests/test_alpha_vantage_hardening.py | 28 +++++++++++++++++ .../dataflows/alpha_vantage_indicator.py | 31 +++++++++++-------- tradingagents/dataflows/alpha_vantage_news.py | 3 ++ 3 files changed, 49 insertions(+), 13 deletions(-) diff --git a/tests/test_alpha_vantage_hardening.py b/tests/test_alpha_vantage_hardening.py index 832fa4c51..3bc90164f 100644 --- a/tests/test_alpha_vantage_hardening.py +++ b/tests/test_alpha_vantage_hardening.py @@ -183,3 +183,31 @@ def test_the_news_window_includes_the_analysis_day(monkeypatch): assert seen["time_from"] == "20260310T0000" assert seen["time_to"] == "20260314T2359" + + +@pytest.mark.unit +@pytest.mark.parametrize("indicator", ["vwma", "mfi"]) +def test_an_indicator_this_vendor_lacks_lets_the_next_one_serve_it(indicator): + """Returning prose counts as success to the router, so the chain stops at a + vendor that cannot compute the indicator while the next one can.""" + from tradingagents.dataflows import alpha_vantage_indicator + from tradingagents.dataflows.errors import VendorError + + with pytest.raises(VendorError): + alpha_vantage_indicator.get_indicator("AAPL", indicator, "2026-05-08", 30) + + +@pytest.mark.unit +def test_ticker_news_asks_for_only_as_many_articles_as_configured(monkeypatch): + """The endpoint returns 50 articles with per-article sentiment arrays by + default, and the whole payload went into the prompt.""" + from tradingagents.dataflows import alpha_vantage_news + + monkeypatch.setattr(alpha_vantage_news, "get_config", lambda: {"news_article_limit": 8}) + seen = {} + monkeypatch.setattr(alpha_vantage_news, "_make_api_request", + lambda fn, params: seen.update(params) or "{}") + + alpha_vantage_news.get_news("AAPL", "2026-03-10", "2026-03-14") + + assert seen["limit"] == "8" diff --git a/tradingagents/dataflows/alpha_vantage_indicator.py b/tradingagents/dataflows/alpha_vantage_indicator.py index 5dd61f387..c53d91a5c 100644 --- a/tradingagents/dataflows/alpha_vantage_indicator.py +++ b/tradingagents/dataflows/alpha_vantage_indicator.py @@ -1,6 +1,7 @@ import logging -from .alpha_vantage_common import AlphaVantageNotConfiguredError, _make_api_request +from .alpha_vantage_common import _make_api_request +from .errors import NoMarketDataError, VendorError logger = logging.getLogger(__name__) @@ -64,8 +65,11 @@ def get_indicator( } if indicator not in supported_indicators: - raise ValueError( - f"Indicator {indicator} is not supported. Please choose from: {list(supported_indicators.keys())}" + # A vendor error, not a caller error: another vendor may compute it, and + # the router decides. yfinance rejects a name nobody serves. + raise NoMarketDataError( + symbol, symbol, + f"Alpha Vantage does not serve {indicator}; it serves {list(supported_indicators)}" ) curr_date_dt = datetime.strptime(curr_date, "%Y-%m-%d") @@ -134,12 +138,13 @@ def get_indicator( "time_period": str(time_period), "datatype": "csv" }) - elif indicator == "vwma": - # Alpha Vantage doesn't have direct VWMA, so we'll return an informative message - # In a real implementation, this would need to be calculated from OHLCV data - return f"## VWMA (Volume Weighted Moving Average) for {symbol}:\n\nVWMA calculation requires OHLCV data and is not directly available from Alpha Vantage API.\nThis indicator would need to be calculated from the raw stock data using volume-weighted price averaging.\n\n{indicator_descriptions.get('vwma', 'No description available.')}" else: - return f"Error: Indicator {indicator} not implemented yet." + # This vendor has no endpoint for the indicator. Raising lets the + # router try the next vendor, which computes it; returning prose + # counted as a successful answer and ended the chain here. + raise NoMarketDataError( + symbol, symbol, f"Alpha Vantage does not serve the {indicator} indicator" + ) # Parse CSV data and extract values for the date range lines = data.strip().split('\n') @@ -209,11 +214,11 @@ def get_indicator( return result_str - except AlphaVantageNotConfiguredError: - # Vendor unavailable (no API key). Let it propagate so the router can - # fall back / emit the no-data sentinel instead of returning this as a - # successful-looking error string. + except VendorError: + # Unavailable vendor, throttle, or an indicator this vendor does not + # serve. Let it propagate so the router falls back to a vendor that can, + # instead of returning the failure as a successful-looking string. raise except Exception as e: logger.warning("Alpha Vantage indicator %s failed: %s", indicator, e) - return f"Error retrieving {indicator} data: {str(e)}" + raise NoMarketDataError(symbol, symbol, f"{indicator} unavailable: {e}") from e diff --git a/tradingagents/dataflows/alpha_vantage_news.py b/tradingagents/dataflows/alpha_vantage_news.py index 787de8018..e323e4900 100644 --- a/tradingagents/dataflows/alpha_vantage_news.py +++ b/tradingagents/dataflows/alpha_vantage_news.py @@ -18,10 +18,13 @@ def get_news(ticker, start_date, end_date) -> dict[str, str] | str: Dictionary containing news sentiment data or JSON string. """ + # Without a limit the endpoint returns 50 articles, each with per-ticker + # sentiment arrays, and all of it reaches the prompt. params = { "tickers": ticker, "time_from": format_datetime_for_api(start_date), "time_to": format_datetime_for_api(end_date, end_of_day=True), + "limit": str(get_config()["news_article_limit"]), } return _make_api_request("NEWS_SENTIMENT", params)