From c55b598f7bda52893b114a642c8d09e8a3ec1a3b Mon Sep 17 00:00:00 2001 From: Jackson Newman Date: Fri, 24 Oct 2025 15:20:44 -0700 Subject: [PATCH 1/8] Fixed improper JSON response from apis that use quote summary --- yahooquery/base.py | 56 ++++++++++++++++++++++++++++++++++++++++++++ yahooquery/ticker.py | 45 +++++++++++++++++++++++------------ 2 files changed, 86 insertions(+), 15 deletions(-) diff --git a/yahooquery/base.py b/yahooquery/base.py index de66d30..6cc4da6 100644 --- a/yahooquery/base.py +++ b/yahooquery/base.py @@ -328,3 +328,59 @@ def _construct_data(self, json, response_field, **kwargs): except TypeError: data = json return data + + def _normalize_quote_summary_response(self, data, module_name="quoteSummary"): + """ + Normalize quoteSummary response to ensure consistent structure across all modules. + + Many quoteSummary endpoints can return either: + 1. A dictionary with nested module data (normal case) + 2. A string error message when no data is found (error case) + + This method ensures that error cases return a consistent structure + instead of a plain string, making it easier for consumers to handle + the response predictably across all quoteSummary-based properties. + + Parameters + ---------- + data : dict + Raw response from _quote_summary + module_name : str, optional + Name of the module being normalized (for logging/debugging) + + Returns + ------- + dict + Normalized response where each symbol maps to either: + - A dict with module data (success case) + - A dict with error information (error case): + { + "error": { + "code": 404, + "type": "NotFoundError", + "message": "No fundamentals data found for symbol: EAI", + "symbol": "EAI" + } + } + """ + if not isinstance(data, dict): + return data + + normalized_data = {} + for symbol, module_data in data.items(): + + if isinstance(module_data, str): + # Convert string error messages to a consistent error structure + normalized_data[symbol] = { + "error": { + "code": 404, + "type": "NotFoundError", + "message": module_data, + "symbol": symbol + } + } + else: + # Keep successful responses as-is + normalized_data[symbol] = module_data + + return normalized_data diff --git a/yahooquery/ticker.py b/yahooquery/ticker.py index 1c45246..1bc1ea6 100644 --- a/yahooquery/ticker.py +++ b/yahooquery/ticker.py @@ -208,7 +208,8 @@ def asset_profile(self): dict assetProfile module data """ - return self._quote_summary(["assetProfile"]) + data = self._quote_summary(["assetProfile"]) + return self._normalize_quote_summary_response(data, "asset_profile") @property def calendar_events(self): @@ -222,7 +223,8 @@ def calendar_events(self): dict calendarEvents module data """ - return self._quote_summary(["calendarEvents"]) + data = self._quote_summary(["calendarEvents"]) + return self._normalize_quote_summary_response(data, "calendar_events") @property def earnings(self): @@ -235,7 +237,8 @@ def earnings(self): dict earnings module data """ - return self._quote_summary(["earnings"]) + data = self._quote_summary(["earnings"]) + return self._normalize_quote_summary_response(data, "earnings") @property def earnings_trend(self): @@ -249,7 +252,8 @@ def earnings_trend(self): dict earningsTrend module data """ - return self._quote_summary(["earningsTrend"]) + data = self._quote_summary(["earningsTrend"]) + return self._normalize_quote_summary_response(data, "earnings_trend") @property def esg_scores(self): @@ -263,7 +267,8 @@ def esg_scores(self): dict esgScores module data """ - return self._quote_summary(["esgScores"]) + data = self._quote_summary(["esgScores"]) + return self._normalize_quote_summary_response(data, "esg_scores") @property def financial_data(self): @@ -276,7 +281,8 @@ def financial_data(self): dict financialData module data """ - return self._quote_summary(["financialData"]) + data = self._quote_summary(["financialData"]) + return self._normalize_quote_summary_response(data, "financial_data") def news(self, count=25, start=None): """News articles related to given symbol(s) @@ -320,7 +326,8 @@ def index_trend(self): dict indexTrend module data """ - return self._quote_summary(["indexTrend"]) + data = self._quote_summary(["indexTrend"]) + return self._normalize_quote_summary_response(data, "index_trend") @property def industry_trend(self): @@ -333,7 +340,8 @@ def industry_trend(self): dict industryTrend module data """ - return self._quote_summary(["industryTrend"]) + data = self._quote_summary(["industryTrend"]) + return self._normalize_quote_summary_response(data, "industry_trend") @property def key_stats(self): @@ -346,7 +354,8 @@ def key_stats(self): dict defaultKeyStatistics module data """ - return self._quote_summary(["defaultKeyStatistics"]) + data = self._quote_summary(["defaultKeyStatistics"]) + return self._normalize_quote_summary_response(data, "key_stats") @property def major_holders(self): @@ -360,7 +369,8 @@ def major_holders(self): dict majorHoldersBreakdown module data """ - return self._quote_summary(["majorHoldersBreakdown"]) + data = self._quote_summary(["majorHoldersBreakdown"]) + return self._normalize_quote_summary_response(data, "major_holders") @property def page_views(self): @@ -373,7 +383,8 @@ def page_views(self): dict pageViews module data """ - return self._quote_summary(["pageViews"]) + data = self._quote_summary(["pageViews"]) + return self._normalize_quote_summary_response(data, "page_views") @property def price(self): @@ -387,7 +398,8 @@ def price(self): dict price module data """ - return self._quote_summary(["price"]) + data = self._quote_summary(["price"]) + return self._normalize_quote_summary_response(data, "price") @property def quote_type(self): @@ -446,7 +458,8 @@ def share_purchase_activity(self): dict netSharePurchaseActivity module data """ - return self._quote_summary(["netSharePurchaseActivity"]) + data = self._quote_summary(["netSharePurchaseActivity"]) + return self._normalize_quote_summary_response(data, "share_purchase_activity") @property def summary_detail(self): @@ -459,7 +472,8 @@ def summary_detail(self): dict summaryDetail module data """ - return self._quote_summary(["summaryDetail"]) + data = self._quote_summary(["summaryDetail"]) + return self._normalize_quote_summary_response(data, "summary_detail") @property def summary_profile(self): @@ -472,7 +486,8 @@ def summary_profile(self): dict summaryProfile module data """ - return self._quote_summary(["summaryProfile"]) + data = self._quote_summary(["summaryProfile"]) + return self._normalize_quote_summary_response(data, "summary_profile") @property def technical_insights(self): From 1a14192c41eb4fae03bf3161a9819a26681cefaa Mon Sep 17 00:00:00 2001 From: Jackson Newman Date: Wed, 19 Nov 2025 17:50:37 -0800 Subject: [PATCH 2/8] Applied quote summary fix tot he get modules function --- yahooquery/ticker.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/yahooquery/ticker.py b/yahooquery/ticker.py index 1bc1ea6..532c676 100644 --- a/yahooquery/ticker.py +++ b/yahooquery/ticker.py @@ -195,7 +195,8 @@ def get_modules(self, modules): One of {} is not a valid value. Valid values are {}. """.format(", ".join(modules), ", ".join(all_modules)) ) - return self._quote_summary(modules) + data = self._quote_summary(modules) + return self._normalize_quote_summary_response(data, "quoteSummary") @property def asset_profile(self): From 98a516eb1c13ddaea052a70e078643f87918dbfc Mon Sep 17 00:00:00 2001 From: Jackson Newman Date: Thu, 20 Nov 2025 12:38:01 -0800 Subject: [PATCH 3/8] Fixed valuation measures api --- yahooquery/constants.py | 10 +++---- yahooquery/ticker.py | 58 +++++++++++++++++++++++++++++++++++------ 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/yahooquery/constants.py b/yahooquery/constants.py index d99eb5a..b9d64ea 100644 --- a/yahooquery/constants.py +++ b/yahooquery/constants.py @@ -587,15 +587,15 @@ "UnrealizedGainLossOnInvestmentSecurities", ], "valuation": [ + "MarketCap", + "EnterpriseValue", + "PeRatio", "ForwardPeRatio", + "PegRatio", "PsRatio", "PbRatio", - "EnterprisesValueEBITDARatio", "EnterprisesValueRevenueRatio", - "PeRatio", - "MarketCap", - "EnterpriseValue", - "PegRatio", + "EnterprisesValueEBITDARatio", ], } diff --git a/yahooquery/ticker.py b/yahooquery/ticker.py index 532c676..fa18d36 100644 --- a/yahooquery/ticker.py +++ b/yahooquery/ticker.py @@ -573,7 +573,7 @@ def _financials_dataframes(self, data, period_type): df = pd.DataFrame.from_records(data[data_type]) if period_type: df["reportedValue"] = df["reportedValue"].apply( - lambda x: x.get("raw") if isinstance(x, dict) else x + lambda x: self._extract_reported_value(x) ) df["dataType"] = data_type df["symbol"] = symbol @@ -587,6 +587,22 @@ def _financials_dataframes(self, data, period_type): # No data is available for that type pass + def _extract_reported_value(self, reported_value): + """Extract the raw value from reportedValue, handling nested structures""" + if isinstance(reported_value, dict): + raw_value = reported_value.get("raw") + if isinstance(raw_value, dict): + # Handle nested raw structure (e.g., for MarketCap, EnterpriseValue) + raw_value = raw_value.get("parsedValue", raw_value.get("source")) + if isinstance(raw_value, str): + try: + return float(raw_value) + except (ValueError, TypeError): + pass + return raw_value + return reported_value + + def all_financial_data(self, frequency="a"): """ Retrieve all financial data, including income statement, @@ -651,17 +667,29 @@ def corporate_guidance(self): trailing=False, ) - @property - def valuation_measures(self): + def valuation_measures(self, frequency="q", trailing=True): """Valuation Measures - Retrieves valuation measures for most recent four quarters as well - as the most recent date + + Retrieves valuation measures for most recent quarters or years Notes ----- Only quarterly data is available for non-premium subscribers + + Parameters + ---------- + frequency: str, default 'q', optional + Specify either annual or quarterly. Value should be 'a' or 'q'. + trailing: bool, default True, optional + Specify whether or not you'd like trailing twelve month (TTM) + data returned + + Returns + ------- + pandas.DataFrame """ - return self._financials("valuation", "q") + return self._financials("valuation", frequency, trailing=trailing) + def balance_sheet(self, frequency="a"): """Balance Sheet @@ -1186,12 +1214,26 @@ def p_ideas(self, idea_id): def p_technical_events(self): return self._get_data("technical_events") - def p_valuation_measures(self, frequency="q"): + def p_valuation_measures(self, frequency="q", trailing=True): """Valuation Measures + Retrieves valuation measures for all available dates for given symbol(s) + + Parameters + ---------- + frequency: str, default 'q', optional + Specify either annual or quarterly. Value should be 'a' or 'q'. + trailing: bool, default True, optional + Specify whether or not you'd like trailing twelve month (TTM) + data returned + + Returns + ------- + pandas.DataFrame """ - return self._financials("valuation", frequency, premium=True) + return self._financials("valuation", frequency, premium=True, trailing=trailing) + @property def p_value_analyzer(self): From e248d79944aeba9531e5798ecdba88edb60c3528 Mon Sep 17 00:00:00 2001 From: Jackson Newman Date: Thu, 20 Nov 2025 14:05:06 -0800 Subject: [PATCH 4/8] Added current valuation measures api to get the most recent stats of a single ticker --- docs/docs/guide/ticker/financials.md | 14 ++++++++ tests/test_ticker.py | 12 +++++++ yahooquery/ticker.py | 50 ++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) diff --git a/docs/docs/guide/ticker/financials.md b/docs/docs/guide/ticker/financials.md index fdf6617..6c503ee 100644 --- a/docs/docs/guide/ticker/financials.md +++ b/docs/docs/guide/ticker/financials.md @@ -125,6 +125,20 @@ | aapl | 2020-07-29 00:00:00 | TTM | 1.6632e+12 | nan | nan | 25.1256 | 1.64774e+12 | 21.0104 | 29.7232 | 2.0905 | 6.37702 | | aapl | 2020-07-30 00:00:00 | TTM | nan | 20.2772 | 6.2064 | nan | nan | nan | nan | nan | nan | +### **current_valuation_measures** + +=== "Details" + + - *Description*: Retrieves a JSON-serializable mapping of symbol to the most recent TTM valuation metrics. + - *Return*: `dict` + +=== "Example" + + ```python + aapl = Ticker('aapl') + aapl.current_valuation_measures() + ``` + ## Multiple diff --git a/tests/test_ticker.py b/tests/test_ticker.py index 775452a..b113fce 100644 --- a/tests/test_ticker.py +++ b/tests/test_ticker.py @@ -29,6 +29,7 @@ "all_financial_data", "p_all_financial_data", "p_valuation_measures", + "current_valuation_measures", ] SEPERATE_ENDPOINTS = [ @@ -185,6 +186,17 @@ def test_history_bad_args(ticker, period, interval): def test_adj_ohlc(ticker): + + def test_current_valuation_measures(ticker): + res = ticker.current_valuation_measures() + assert res is not None + if isinstance(res, str): + assert "unavailable" in res.lower() + else: + assert isinstance(res, dict) + for sym, metrics in res.items(): + # Ensure there is an asOfDate for the current selected entry + assert "asOfDate" in metrics assert ticker.history(period="max", adj_ohlc=True) is not None diff --git a/yahooquery/ticker.py b/yahooquery/ticker.py index fa18d36..7831cfa 100644 --- a/yahooquery/ticker.py +++ b/yahooquery/ticker.py @@ -691,6 +691,56 @@ def valuation_measures(self, frequency="q", trailing=True): return self._financials("valuation", frequency, trailing=trailing) + def current_valuation_measures(self, frequency="q"): + """Current Valuation Measures + + Returns a JSON-serializable dict of the most recent Trailing Twelve Months + (TTM) valuation metrics for each symbol. If TTM is not available for a + symbol, falls back to the most recent date. + + Parameters + ---------- + frequency: str, default 'q' + Specify either annual or quarterly. Value should be 'a' or 'q'. + + Returns + ------- + dict | str + JSON-serializable dict keyed by symbol mapping to valuation measure + fields for the most recent TTM period. If the underlying API returns + an error string or dict, that value is returned directly. + """ + # Retrieve valuation measures with trailing enabled to ensure TTM rows + df = self.valuation_measures(frequency, trailing=True) + # If _financials returned an error message or nested dict, just return it + if isinstance(df, (str, dict)): + return df + try: + # Ensure `periodType` and `asOfDate` exist + if "periodType" not in df.columns or "asOfDate" not in df.columns: + return {} + # Normalize periodType to upper case and filter for TTM rows + ttm_df = df[df["periodType"].astype(str).str.upper() == "TTM"] + if ttm_df.empty: + # Fallback to most recent 'asOfDate' per symbol + latest = df.sort_values("asOfDate").groupby(df.index).last() + else: + latest = ttm_df.sort_values("asOfDate").groupby(ttm_df.index).last() + + # Convert to JSON-serializable dict and format asOfDate + out = {} + records = latest.to_dict(orient="index") + for symbol, rec in records.items(): + if isinstance(rec.get("asOfDate"), (pd.Timestamp,)): + rec["asOfDate"] = rec["asOfDate"].isoformat() + out[symbol] = rec + return out + except Exception: + # If anything unexpected happens, return an empty dict rather than + # raising, to keep API behavior consistent with other endpoints. + return {} + + def balance_sheet(self, frequency="a"): """Balance Sheet From c6107bb5eb00194e12923376559fa227b53667f3 Mon Sep 17 00:00:00 2001 From: Jackson Newman Date: Thu, 20 Nov 2025 15:40:03 -0800 Subject: [PATCH 5/8] Changed version to reflect new function and fix --- CHANGELOG.rst | 8 ++++++++ docs/docs/release_notes.md | 8 ++++++++ pyproject.toml | 2 +- yahooquery/__init__.py | 2 +- 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 638a394..daa31e3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,14 @@ Change Log ========== +2.4.2 +----- +## Add +- Added a new function and minor improvements (see PR/commit for details) + +## Fix +- Minor bug and doc updates related to the new function + 2.4.1 ----- ## Fix diff --git a/docs/docs/release_notes.md b/docs/docs/release_notes.md index a98c4de..b87f4d6 100644 --- a/docs/docs/release_notes.md +++ b/docs/docs/release_notes.md @@ -1,5 +1,13 @@ # Release Notes +2.4.2 +----- +## Add +- Added a new function and minor improvements (see PR/commit for details) + +## Fix +- Minor bug and doc updates related to the new function + 2.4.0 ----- ## Update diff --git a/pyproject.toml b/pyproject.toml index 6ccdd70..827e79f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "yahooquery" -version = "2.4.1" +version = "2.4.2" description = "Python wrapper for an unofficial Yahoo Finance API" authors = [ {name = "Doug Guthrie", email = "douglas.p.guthrie@gmail.com"} diff --git a/yahooquery/__init__.py b/yahooquery/__init__.py index 2115fcb..02e8f27 100644 --- a/yahooquery/__init__.py +++ b/yahooquery/__init__.py @@ -1,7 +1,7 @@ """Python interface to unofficial Yahoo Finance API endpoints""" name = "yahooquery" -__version__ = "2.4.1" +__version__ = "2.4.2" from .misc import ( # noqa From 0d1957240175cc0086436ad1ae2154b35ea780f2 Mon Sep 17 00:00:00 2001 From: Jackson Newman Date: Thu, 20 Nov 2025 16:05:34 -0800 Subject: [PATCH 6/8] Fixed bug where multi ticker calls for current valuation measures was grouping wrong --- tests/test_ticker.py | 41 +++++++++++++++++++++++++++++++++++++++++ yahooquery/ticker.py | 17 +++++++++++++++-- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/tests/test_ticker.py b/tests/test_ticker.py index b113fce..42ccda2 100644 --- a/tests/test_ticker.py +++ b/tests/test_ticker.py @@ -200,6 +200,47 @@ def test_current_valuation_measures(ticker): assert ticker.history(period="max", adj_ohlc=True) is not None +def test_current_valuation_measures_batch_ttm(monkeypatch): + """Ensure batch current_valuation_measures returns most recent TTM per symbol""" + # Create a Ticker with two symbols + ticker = Ticker("AAPL MSFT") + # Craft a MultiIndex DataFrame to mimic the API response + symbols = ["AAPL", "MSFT"] + dates = pd.to_datetime(["2020-01-01", "2020-02-01", "2020-03-01"]) + idx = pd.MultiIndex.from_product([symbols, dates], names=["symbol", "date"]) + df = pd.DataFrame(index=idx) + # Make TTM present for the middle date for each symbol + df["periodType"] = "TTM" + df["asOfDate"] = df.index.get_level_values(1) + df["value"] = range(len(df)) + + # Monkeypatch valuation_measures to return our DataFrame + monkeypatch.setattr(ticker, "valuation_measures", lambda frequency, trailing=True: df) + res = ticker.current_valuation_measures() + # Expect keys to be the two symbols and values have asOfDate equal to most recent TTM per symbol + assert set(res.keys()) == set(symbols) + for symbol in symbols: + assert res[symbol]["asOfDate"] == pd.Timestamp("2020-03-01").isoformat() + + +def test_current_valuation_measures_batch_no_ttm(monkeypatch): + """Ensure fallback to most recent asOfDate per symbol when TTM is absent""" + ticker = Ticker("AAPL MSFT") + symbols = ["AAPL", "MSFT"] + dates = pd.to_datetime(["2020-01-01", "2020-02-01", "2020-03-01"]) + idx = pd.MultiIndex.from_product([symbols, dates], names=["symbol", "date"]) + df = pd.DataFrame(index=idx) + # No TTM rows + df["periodType"] = "MRQ" + df["asOfDate"] = df.index.get_level_values(1) + df["value"] = range(len(df)) + monkeypatch.setattr(ticker, "valuation_measures", lambda frequency, trailing=True: df) + res = ticker.current_valuation_measures() + assert set(res.keys()) == set(symbols) + for symbol in symbols: + assert res[symbol]["asOfDate"] == pd.Timestamp("2020-03-01").isoformat() + + class TestHistoryDataframe: """Tests for `utils.history_dataframe` and dependencies.""" diff --git a/yahooquery/ticker.py b/yahooquery/ticker.py index 7831cfa..1af8f89 100644 --- a/yahooquery/ticker.py +++ b/yahooquery/ticker.py @@ -721,11 +721,24 @@ def current_valuation_measures(self, frequency="q"): return {} # Normalize periodType to upper case and filter for TTM rows ttm_df = df[df["periodType"].astype(str).str.upper() == "TTM"] + + # Determine grouping key — for batch calls the index is a MultiIndex + # where the first level is the symbol. We want to group by symbol + # to select the most recent record per symbol. For single-ticker + # calls it will be a single-level index (dates) and grouping by + # the index itself is sufficient. + def _group_key(dfobj): + if isinstance(dfobj.index, pd.MultiIndex): + # groupby with the first level (symbol) + return dfobj.index.get_level_values(0) + # fallback to group by the index (single-ticker case) + return dfobj.index + if ttm_df.empty: # Fallback to most recent 'asOfDate' per symbol - latest = df.sort_values("asOfDate").groupby(df.index).last() + latest = df.sort_values("asOfDate").groupby(_group_key(df)).last() else: - latest = ttm_df.sort_values("asOfDate").groupby(ttm_df.index).last() + latest = ttm_df.sort_values("asOfDate").groupby(_group_key(ttm_df)).last() # Convert to JSON-serializable dict and format asOfDate out = {} From 0f5bc566db223acf041eb5ad86087d4ee43ef02f Mon Sep 17 00:00:00 2001 From: Jackson Newman Date: Thu, 20 Nov 2025 17:32:59 -0800 Subject: [PATCH 7/8] Fixed bug when using current valuation with multiple tickers --- yahooquery/ticker.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/yahooquery/ticker.py b/yahooquery/ticker.py index 1af8f89..4f45c42 100644 --- a/yahooquery/ticker.py +++ b/yahooquery/ticker.py @@ -736,9 +736,11 @@ def _group_key(dfobj): if ttm_df.empty: # Fallback to most recent 'asOfDate' per symbol - latest = df.sort_values("asOfDate").groupby(_group_key(df)).last() + sorted_df = df.sort_values("asOfDate") + latest = sorted_df.groupby(_group_key(sorted_df)).last() else: - latest = ttm_df.sort_values("asOfDate").groupby(_group_key(ttm_df)).last() + sorted_ttm = ttm_df.sort_values("asOfDate") + latest = sorted_ttm.groupby(_group_key(sorted_ttm)).last() # Convert to JSON-serializable dict and format asOfDate out = {} From 14abd084c7145da0d0de3302b165ec95ebec804b Mon Sep 17 00:00:00 2001 From: Jackson Newman Date: Fri, 12 Dec 2025 15:23:58 -0800 Subject: [PATCH 8/8] Fixed the news endpoint for the updated yahoo finance api call --- yahooquery/constants.py | 17 +++++------- yahooquery/ticker.py | 61 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 62 insertions(+), 16 deletions(-) diff --git a/yahooquery/constants.py b/yahooquery/constants.py index b9d64ea..0a51776 100644 --- a/yahooquery/constants.py +++ b/yahooquery/constants.py @@ -714,17 +714,14 @@ }, }, "news": { - "path": "https://query2.finance.yahoo.com/v2/finance/news", - "response_field": "Content", + "path": "https://finance.yahoo.com/xhr/ncp", + "response_field": "data", + "method": "post", "query": { - "start": {"required": False, "default": None}, - "count": {"required": False, "default": None}, - "symbols": {"required": True, "default": None}, - "sizeLabels": {"required": False, "default": None}, - "widths": {"required": False, "default": None}, - "tags": {"required": False, "default": None}, - "filterOldVideos": {"required": False, "default": None}, - "category": {"required": False, "default": None}, + "symbol": {"required": True, "default": None}, + "location": {"required": False, "default": "US"}, + "queryRef": {"required": False, "default": "newsAll"}, + "serviceKey": {"required": False, "default": "ncp_fin"}, }, }, "quoteSummary": { diff --git a/yahooquery/ticker.py b/yahooquery/ticker.py index 4f45c42..7dfbfdf 100644 --- a/yahooquery/ticker.py +++ b/yahooquery/ticker.py @@ -303,17 +303,66 @@ def news(self, count=25, start=None): ----- It's recommended to use only one symbol for this property as the data returned does not distinguish between what symbol the news stories - belong to + belong to. + + The start parameter is no longer supported by the Yahoo Finance API. Returns ------- dict """ - if start: - start = convert_to_timestamp(start) - return self._chunk_symbols( - "news", params={"count": count, "start": start}, list_result=True - ) + config = CONFIG["news"] + results = {} + + for symbol in self._symbols: + # Build query parameters with defaults from config + params = { + "location": self._country_params.get("region", "US"), + "queryRef": "newsAll", + "serviceKey": "ncp_fin", + "listName": f"{symbol}-news", + "lang": self._country_params.get("lang", "en-US"), + "region": self._country_params.get("region", "US"), + } + + # Add default query params (crumb, etc.) + params.update(self.default_query_params) + + # POST body + payload = { + "serviceConfig": { + "count": count or 25, + "s": [symbol] + }, + "session": { + "lang": self._country_params.get("lang", "en-US"), + "region": self._country_params.get("region", "US"), + } + } + + # Make POST request using session + response = self.session.post( + url=config["path"], + params=params, + json=payload + ) + + try: + json_data = response.json() + # Extract news items from the API response + if "data" in json_data and "tickerStream" in json_data["data"]: + stream = json_data["data"]["tickerStream"].get("stream", []) + results[symbol] = [item["content"] for item in stream if "content" in item] + else: + results[symbol] = json_data + except Exception: + try: + results[symbol] = f"Error: {response.status_code} - {response.text[:100]}" + except Exception: + results[symbol] = "Error: Unable to retrieve news" + + return results + @property def index_trend(self):