Add valuation measures

This commit is contained in:
Ethan Balakumar
2026-04-06 22:44:49 -05:00
parent 172ec0f0a3
commit eac7f2ffa9
4 changed files with 100 additions and 0 deletions
+50
View File
@@ -19,6 +19,7 @@ from yfinance.config import YfConfig
import unittest
# import requests_cache
from unittest.mock import patch, MagicMock
from typing import Union, Any, get_args, _GenericAlias
# from urllib.parse import urlparse, parse_qs, urlencode, urlunparse
@@ -1235,6 +1236,54 @@ class TestTickerFundsData(unittest.TestCase):
sector_weightings = ticker.funds_data.sector_weightings
self.assertIsInstance(sector_weightings, dict)
class TestTickerValuationMeasures(unittest.TestCase):
_MOCK_HTML = """<html><body>
<table>
<tr><td></td><td>Current</td><td>12/31/2025</td><td>9/30/2025</td></tr>
<tr><td>Market Cap</td><td>3.76T</td><td>4.00T</td><td>3.76T</td></tr>
<tr><td>Enterprise Value</td><td>3.78T</td><td>4.04T</td><td>3.81T</td></tr>
<tr><td>Trailing P/E</td><td>32.39</td><td>36.44</td><td>38.64</td></tr>
<tr><td>Forward P/E</td><td>29.76</td><td>32.79</td><td>31.65</td></tr>
<tr><td>PEG Ratio (5yr expected)</td><td>2.27</td><td>2.75</td><td>2.44</td></tr>
<tr><td>Price/Sales</td><td>8.77</td><td>9.80</td><td>9.41</td></tr>
<tr><td>Price/Book</td><td>42.60</td><td>54.21</td><td>57.14</td></tr>
<tr><td>Enterprise Value/Revenue</td><td>8.68</td><td>9.71</td><td>9.32</td></tr>
<tr><td>Enterprise Value/EBITDA</td><td>24.73</td><td>27.92</td><td>26.87</td></tr>
</table>
</body></html>"""
def _make_ticker_with_mock(self, html):
mock_response = MagicMock()
mock_response.text = html
with patch("yfinance.data.YfData.cache_get", return_value=mock_response):
dat = yf.Ticker("AAPL")
data = dat.valuation_measures
return data
def test_valuation_measures(self):
data = self._make_ticker_with_mock(self._MOCK_HTML)
self.assertEqual(data.shape, (9, 3), "unexpected shape")
self.assertListEqual(list(data.columns), ["Current", "12/31/2025", "9/30/2025"])
self.assertIn("Market Cap", data.index)
self.assertIn("Trailing P/E", data.index)
self.assertIn("Enterprise Value/EBITDA", data.index)
self.assertIsNone(data.index.name)
self.assertEqual(data.loc["Market Cap", "Current"], "3.76T")
self.assertEqual(data.loc["Forward P/E", "12/31/2025"], "32.79")
def test_valuation_measures_no_table(self):
data = self._make_ticker_with_mock("<html><body><p>No tables here</p></body></html>")
self.assertIsInstance(data, pd.DataFrame)
self.assertTrue(data.empty)
def test_valuation_measures_fetch_error(self):
with patch("yfinance.data.YfData.cache_get", side_effect=Exception("network error")):
dat = yf.Ticker("AAPL")
data = dat.valuation_measures
self.assertIsInstance(data, pd.DataFrame)
self.assertTrue(data.empty)
def suite():
suite = unittest.TestSuite()
suite.addTest(TestTicker('Test ticker'))
@@ -1244,6 +1293,7 @@ def suite():
suite.addTest(TestTickerMiscFinancials('Test misc financials'))
suite.addTest(TestTickerInfo('Test info & fast_info'))
suite.addTest(TestTickerFundsData('Test Funds Data'))
suite.addTest(TestTickerValuationMeasures('Test valuation measures'))
return suite
+4
View File
@@ -287,6 +287,10 @@ class TickerBase:
self._fast_info = FastInfo(self)
return self._fast_info
def get_valuation_measures(self):
data = self._quote.valuation_measures
return data
def get_sustainability(self, as_dict=False):
data = self._quote.sustainability
if as_dict:
+42
View File
@@ -3,6 +3,7 @@ import datetime
import json
import numpy as _np
import pandas as pd
from bs4 import BeautifulSoup
from yfinance import utils
from yfinance.config import YfConfig
@@ -492,6 +493,7 @@ class Quote:
self._upgrades_downgrades = None
self._calendar = None
self._sec_filings = None
self._valuation_measures = None
self._already_scraped = False
self._already_fetched = False
@@ -572,6 +574,12 @@ class Quote:
self._sec_filings = {} if f is None else f
return self._sec_filings
@property
def valuation_measures(self) -> pd.DataFrame:
if self._valuation_measures is None:
self._fetch_valuation_measures()
return self._valuation_measures
@staticmethod
def valid_modules():
return quote_summary_valid_modules
@@ -661,6 +669,40 @@ class Quote:
self._info = {k: _format(k, v) for k, v in query1_info.items()}
def _fetch_valuation_measures(self):
url = f"https://finance.yahoo.com/quote/{self._symbol}/key-statistics"
try:
response = self._data.cache_get(url=url)
except Exception as e:
if not YfConfig.debug.hide_exceptions:
raise
utils.get_yf_logger().error(f"Failed to fetch key-statistics page: {e}")
self._valuation_measures = pd.DataFrame()
return
try:
soup = BeautifulSoup(response.text, "html.parser")
table = soup.find("table")
if table is None:
self._valuation_measures = pd.DataFrame()
return
headers = [th.get_text(strip=True) for th in table.find("tr").find_all(["th", "td"])]
rows = []
for tr in table.find_all("tr")[1:]:
cells = [td.get_text(strip=True) for td in tr.find_all(["th", "td"])]
rows.append(cells)
df = pd.DataFrame(rows, columns=headers)
df = df.set_index(df.columns[0])
df.index.name = None
self._valuation_measures = df
except Exception as e:
if not YfConfig.debug.hide_exceptions:
raise
utils.get_yf_logger().error(f"Failed to parse key-statistics page: {e}")
self._valuation_measures = pd.DataFrame()
def _fetch_complementary(self):
if self._already_fetched_complementary:
return
+4
View File
@@ -162,6 +162,10 @@ class Ticker(TickerBase):
def fast_info(self):
return self.get_fast_info()
@property
def valuation(self) -> _pd.DataFrame:
return self.get_valuation_measures()
@property
def calendar(self) -> dict:
"""