refactor(dataflows): group the vendors under dataflows/vendors

- vendors/yahoo: ohlcv (loader and cache), market (prices, indicators), fundamentals (profile, statements, insider), news, snapshot
- vendors/alpha_vantage is a package; sec_edgar, fred, polymarket, reddit and stocktwits sit beside it
- the one-method StockstatsUtils class is a function; the duplicate Yahoo host constant is gone
- tests are named after the modules they cover: test_ohlcv_date_column, test_yahoo_snapshot, and the ohlcv and snapshot aliases
This commit is contained in:
Yijia-Xiao
2026-09-24 04:37:40 +00:00
parent c42a2f2c61
commit 6097b582d9
45 changed files with 417 additions and 389 deletions
+38 -35
View File
@@ -14,7 +14,12 @@ import pandas as pd
import pytest
from tradingagents.agents.utils import news_data_tools, prediction_markets_tools
from tradingagents.dataflows import alpha_vantage_news, polymarket, y_finance
from tradingagents.dataflows.vendors import polymarket
from tradingagents.dataflows.vendors.alpha_vantage import news as alpha_vantage_news
from tradingagents.dataflows.vendors.yahoo import (
fundamentals as yahoo_fundamentals,
market as yahoo_market,
)
def _insider_frame(*dates):
@@ -27,8 +32,8 @@ def _insider_frame(*dates):
def _yf_insider(frame, curr_date):
ticker = mock.Mock(insider_transactions=frame)
with mock.patch.object(y_finance.yf, "Ticker", return_value=ticker):
return y_finance.get_insider_transactions("AAPL", curr_date)
with mock.patch.object(yahoo_market.yf, "Ticker", return_value=ticker):
return yahoo_fundamentals.get_insider_transactions("AAPL", curr_date)
@pytest.mark.unit
@@ -121,16 +126,14 @@ def test_insider_rows_are_dated_by_the_trade_not_the_filing():
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")
with mock.patch.object(yahoo_market.yf, "Ticker", return_value=ticker):
out = yahoo_fundamentals.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
@@ -142,13 +145,12 @@ 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",
with mock.patch.object(yahoo_market, "get_stock_stats",
side_effect=RuntimeError("cache parse failed")), \
pytest.raises(VendorError):
y_finance.get_stockstats_indicator("AAPL", "rsi", "2026-05-08")
yahoo_market.get_stockstats_indicator("AAPL", "rsi", "2026-05-08")
@pytest.mark.unit
@@ -165,12 +167,11 @@ def test_a_yfinance_failure_is_a_vendor_error_not_a_report(func, args):
"""Returning the failure as text makes the router count it as an answer, so
the chain stops and the analyst reads the error message as if it were data.
yfinance serves the default path, so this is the one that matters most."""
from tradingagents.dataflows import y_finance
from tradingagents.dataflows.errors import VendorError
with mock.patch.object(y_finance.yf, "Ticker", side_effect=RuntimeError("yahoo hiccup")), \
with mock.patch.object(yahoo_market.yf, "Ticker", side_effect=RuntimeError("yahoo hiccup")), \
pytest.raises(VendorError):
getattr(y_finance, func)(*args)
getattr(yahoo_fundamentals, func)(*args)
@pytest.mark.unit
@@ -179,13 +180,13 @@ def test_a_yfinance_failure_is_a_vendor_error_not_a_report(func, args):
("get_global_news_yfinance", ("2026-09-01", 7, 5)),
])
def test_a_yfinance_news_failure_is_a_vendor_error_not_a_report(func, args):
from tradingagents.dataflows import yfinance_news
from tradingagents.dataflows.errors import VendorError
from tradingagents.dataflows.vendors.yahoo import news as yahoo_news
target = "Ticker" if "global" not in func else "Search"
with mock.patch.object(yfinance_news.yf, target, side_effect=RuntimeError("yahoo hiccup")), \
with mock.patch.object(yahoo_news.yf, target, side_effect=RuntimeError("yahoo hiccup")), \
pytest.raises(VendorError):
getattr(yfinance_news, func)(*args)
getattr(yahoo_news, func)(*args)
@pytest.mark.unit
@@ -195,19 +196,19 @@ def test_an_unreachable_vendor_is_not_reported_as_a_missing_symbol(monkeypatch):
company has no balance sheet, when the truth is we could not ask."""
import pandas as pd
from tradingagents.dataflows import stockstats_utils, y_finance
from tradingagents.dataflows.errors import NoMarketDataError, VendorRateLimitError
from tradingagents.dataflows.vendors.yahoo import ohlcv
empty = mock.Mock(quarterly_balance_sheet=pd.DataFrame(), balance_sheet=pd.DataFrame())
monkeypatch.setattr(y_finance.yf, "Ticker", lambda s: empty)
monkeypatch.setattr(yahoo_market.yf, "Ticker", lambda s: empty)
monkeypatch.setattr(stockstats_utils, "vendor_reachable", lambda url: False)
monkeypatch.setattr(ohlcv, "vendor_reachable", lambda url: False)
with pytest.raises(VendorRateLimitError, match="unreachable"):
y_finance.get_balance_sheet("AAPL", "annual", "2026-09-01")
yahoo_fundamentals.get_balance_sheet("AAPL", "annual", "2026-09-01")
monkeypatch.setattr(stockstats_utils, "vendor_reachable", lambda url: True)
monkeypatch.setattr(ohlcv, "vendor_reachable", lambda url: True)
with pytest.raises(NoMarketDataError):
y_finance.get_balance_sheet("AAPL", "annual", "2026-09-01")
yahoo_fundamentals.get_balance_sheet("AAPL", "annual", "2026-09-01")
@pytest.mark.unit
@@ -234,27 +235,29 @@ def test_the_price_path_also_tells_an_outage_from_an_unknown_symbol(monkeypatch)
delisted symbol either."""
import pandas as pd
from tradingagents.dataflows import stockstats_utils, y_finance
from tradingagents.dataflows.errors import NoMarketDataError, VendorRateLimitError
from tradingagents.dataflows.vendors.yahoo import ohlcv
monkeypatch.setattr(y_finance.yf, "Ticker", lambda s: mock.Mock(history=lambda **k: pd.DataFrame()))
monkeypatch.setattr(yahoo_market.yf, "Ticker", lambda s: mock.Mock(history=lambda **k: pd.DataFrame()))
monkeypatch.setattr(stockstats_utils, "vendor_reachable", lambda url: False)
monkeypatch.setattr(ohlcv, "vendor_reachable", lambda url: False)
with pytest.raises(VendorRateLimitError, match="unreachable"):
y_finance.get_YFin_data_online("AAPL", "2026-09-01", "2026-09-10")
yahoo_market.get_YFin_data_online("AAPL", "2026-09-01", "2026-09-10")
monkeypatch.setattr(stockstats_utils, "vendor_reachable", lambda url: True)
monkeypatch.setattr(ohlcv, "vendor_reachable", lambda url: True)
with pytest.raises(NoMarketDataError):
y_finance.get_YFin_data_online("AAPL", "2026-09-01", "2026-09-10")
yahoo_market.get_YFin_data_online("AAPL", "2026-09-01", "2026-09-10")
@pytest.mark.unit
@pytest.mark.parametrize("func, args", [
("get_YFin_data_online", ("AAPL", "2025-06-02", "2025-06-06")),
("get_balance_sheet", ("AAPL", "quarterly", "2025-06-06")),
("get_cashflow", ("AAPL", "quarterly", "2025-06-06")),
("get_income_statement", ("AAPL", "quarterly", "2025-06-06")),
("get_insider_transactions", ("AAPL", "2025-06-06")),
pytest.param(f, a, id=f.__name__) for f, a in (
(yahoo_market.get_YFin_data_online, ("AAPL", "2025-06-02", "2025-06-06")),
(yahoo_fundamentals.get_balance_sheet, ("AAPL", "quarterly", "2025-06-06")),
(yahoo_fundamentals.get_cashflow, ("AAPL", "quarterly", "2025-06-06")),
(yahoo_fundamentals.get_income_statement, ("AAPL", "quarterly", "2025-06-06")),
(yahoo_fundamentals.get_insider_transactions, ("AAPL", "2025-06-06")),
)
])
def test_a_historical_run_is_not_told_todays_date(func, args):
"""A header stamped with the wall clock tells a backtest when it is really running."""
@@ -267,8 +270,8 @@ def test_a_historical_run_is_not_told_todays_date(func, args):
quarterly_income_stmt=statement,
insider_transactions=_insider_frame("2025-05-30"),
history=lambda **k: prices)
with mock.patch.object(y_finance.yf, "Ticker", return_value=ticker):
out = getattr(y_finance, func)(*args)
with mock.patch.object(yahoo_market.yf, "Ticker", return_value=ticker):
out = func(*args)
assert date.today().isoformat() not in out