mirror of
https://github.com/TauricResearch/TradingAgents.git
synced 2026-11-03 16:12:44 +02:00
fix(yahoo): read Yahoo's answer that a window has no prices as no data
- a chart with no prices, or Yahoo saying the data does not exist, is an empty answer; yfinance's same error for an error status or a service failure, and a missing time zone, stay unavailable
This commit is contained in:
@@ -240,3 +240,45 @@ def test_yahoo_answering_not_found_is_no_data(yahoo, call):
|
||||
except NoMarketDataError:
|
||||
return
|
||||
assert out.startswith("No insider transactions"), out # an empty filing list is not an error
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.parametrize("debug, reason, absent", [
|
||||
pytest.param(" (1d 2026-09-26 -> 2026-09-27)", None, True, id="a_chart_with_no_prices"),
|
||||
pytest.param(" (1d 1970-01-01 -> 1970-02-01)", "Data doesn't exist for startDate = 18000, endDate = 2696400",
|
||||
True, id="yahoo_says_the_data_does_not_exist"),
|
||||
pytest.param(" (1d 2026-09-21 -> 2026-09-25)(Yahoo status_code = 503)", None, False, id="an_http_error"),
|
||||
pytest.param(" (1d 2026-09-21 -> 2026-09-25)", "Service temporarily unavailable", False,
|
||||
id="yahoo_reports_a_service_failure"),
|
||||
])
|
||||
def test_only_yahoo_saying_a_window_has_no_prices_is_no_data(yahoo, debug, reason, absent):
|
||||
"""yfinance raises YFPricesMissingError both when Yahoo answered with no prices
|
||||
and when its answer was an error; only the first is a fact about the symbol."""
|
||||
from yfinance.exceptions import YFPricesMissingError
|
||||
|
||||
from tradingagents.dataflows.errors import NoMarketDataError
|
||||
from tradingagents.dataflows.vendors.yahoo import market
|
||||
|
||||
def no_prices(self, **kwargs):
|
||||
raise YFPricesMissingError("AAPL", debug, yahoo_reason=reason)
|
||||
|
||||
yahoo.setattr(yf.Ticker, "history", no_prices)
|
||||
|
||||
with pytest.raises(NoMarketDataError if absent else VendorUnavailableError):
|
||||
market.get_YFin_data_online("AAPL", "2026-09-21", "2026-09-25")
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_a_missing_time_zone_is_not_taken_for_an_answer(yahoo):
|
||||
"""yfinance reports a failed time-zone lookup the same way as a symbol without one."""
|
||||
from yfinance.exceptions import YFTzMissingError
|
||||
|
||||
from tradingagents.dataflows.vendors.yahoo import market
|
||||
|
||||
def no_tz(self, **kwargs):
|
||||
raise YFTzMissingError("AAPL")
|
||||
|
||||
yahoo.setattr(yf.Ticker, "history", no_tz)
|
||||
|
||||
with pytest.raises(VendorUnavailableError):
|
||||
market.get_YFin_data_online("AAPL", "2026-09-21", "2026-09-25")
|
||||
|
||||
+16
-5
@@ -4,7 +4,7 @@ import logging
|
||||
import time
|
||||
|
||||
import yfinance as yf
|
||||
from yfinance.exceptions import YFRateLimitError
|
||||
from yfinance.exceptions import YFPricesMissingError, YFRateLimitError
|
||||
|
||||
from tradingagents.dataflows.errors import NoMarketDataError, VendorUnavailableError
|
||||
from tradingagents.dataflows.net import vendor_reachable
|
||||
@@ -30,8 +30,19 @@ def raise_for_empty(symbol: str, canonical: str, what: str) -> None:
|
||||
yf.config.debug.hide_exceptions = False
|
||||
|
||||
|
||||
def _not_found(exc: Exception) -> bool:
|
||||
"""Whether Yahoo answered that it has no such symbol (HTTP 404)."""
|
||||
def _answered_empty(exc: Exception) -> bool:
|
||||
"""Whether Yahoo answered that it has nothing: no such symbol (HTTP 404), or a
|
||||
price window with no prices in it.
|
||||
|
||||
yfinance raises YFPricesMissingError for that answer and also for an answer
|
||||
that was an error (an error status, or Yahoo describing a failure), so only
|
||||
a chart with no prices, or Yahoo saying the data does not exist, counts.
|
||||
A missing time zone is not an answer: yfinance reports a failed lookup the same way.
|
||||
"""
|
||||
if isinstance(exc, YFPricesMissingError):
|
||||
reason = exc.yahoo_reason
|
||||
return "status_code" not in (exc.debug_info or "") and (
|
||||
reason is None or reason.startswith("Data doesn't exist"))
|
||||
return getattr(getattr(exc, "response", None), "status_code", None) == 404
|
||||
|
||||
|
||||
@@ -42,7 +53,7 @@ def yf_retry(func, max_retries=3, base_delay=2.0):
|
||||
retry them internally, so this wrapper retries them. A rate limit that
|
||||
outlasts the retries, or any other exception, is raised as
|
||||
VendorUnavailableError: it failed in transit and says nothing about the
|
||||
symbol. Yahoo answering that it has no such symbol (HTTP 404) returns None,
|
||||
symbol. Yahoo answering that it has nothing for the symbol returns None,
|
||||
an empty answer. ``func`` should build its own Ticker and make the request
|
||||
itself: a Ticker keeps a failed ``info`` fetch as done, so asking the same
|
||||
one again reads an empty profile.
|
||||
@@ -60,6 +71,6 @@ def yf_retry(func, max_retries=3, base_delay=2.0):
|
||||
f"Yahoo Finance rate limited after {max_retries} retries: {exc}"
|
||||
) from exc
|
||||
except Exception as exc:
|
||||
if _not_found(exc):
|
||||
if _answered_empty(exc):
|
||||
return None
|
||||
raise VendorUnavailableError(f"Yahoo Finance request failed: {type(exc).__name__}") from exc
|
||||
|
||||
Reference in New Issue
Block a user