@@ -26,6 +26,7 @@ The following are the publicly available classes, and functions exposed by the `
|
||||
- :attr:`Industry <yfinance.Industry>`: Domain class for accessing industry information.
|
||||
- :attr:`EquityQuery <yfinance.EquityQuery>`: Class to build equity query filters.
|
||||
- :attr:`FundQuery <yfinance.FundQuery>`: Class to build fund query filters.
|
||||
- :attr:`ETFQuery <yfinance.ETFQuery>`: Class to build ETF query filters.
|
||||
- :attr:`screen <yfinance.screen>`: Run equity/fund queries.
|
||||
- :attr:`config.debug.logging <yfinance.config>`: Enable verbose debug logging (``yf.config.debug.logging = True``).
|
||||
- :attr:`set_tz_cache_location <yfinance.set_tz_cache_location>`: Function to set the timezone cache location.
|
||||
|
||||
@@ -13,6 +13,7 @@ The `Sector` and `Industry` modules allow you to access the sector and industry
|
||||
|
||||
EquityQuery
|
||||
FundQuery
|
||||
ETFQuery
|
||||
screen
|
||||
|
||||
.. seealso::
|
||||
@@ -24,4 +25,8 @@ The `Sector` and `Industry` modules allow you to access the sector and industry
|
||||
supported operand values for query
|
||||
:attr:`FundQuery.valid_values <yfinance.FundQuery.valid_values>`
|
||||
supported `EQ query operand parameters`
|
||||
:attr:`ETFQuery.valid_fields <yfinance.ETFQuery.valid_fields>`
|
||||
supported operand values for query
|
||||
:attr:`ETFQuery.valid_values <yfinance.ETFQuery.valid_values>`
|
||||
supported `EQ query operand parameters`
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ from .domain.industry import Industry
|
||||
from .domain.market import Market
|
||||
from .config import YfConfig as config
|
||||
|
||||
from .screener.query import EquityQuery, FundQuery
|
||||
from .screener.query import EquityQuery, FundQuery, ETFQuery
|
||||
from .screener.screener import screen, PREDEFINED_SCREENER_QUERIES
|
||||
|
||||
__version__ = version.version
|
||||
@@ -45,7 +45,7 @@ warnings.filterwarnings('default', category=DeprecationWarning, module='^yfinanc
|
||||
|
||||
__all__ = ['download', 'Market', 'Search', 'Lookup', 'Ticker', 'Tickers', 'enable_debug_mode', 'set_tz_cache_location', 'Sector', 'Industry', 'WebSocket', 'AsyncWebSocket', 'Calendars']
|
||||
# screener stuff:
|
||||
__all__ += ['EquityQuery', 'FundQuery', 'screen', 'PREDEFINED_SCREENER_QUERIES']
|
||||
__all__ += ['EquityQuery', 'FundQuery', 'ETFQuery', 'screen', 'PREDEFINED_SCREENER_QUERIES']
|
||||
|
||||
# Config stuff:
|
||||
_NOTSET=object()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -759,6 +759,381 @@ EQUITY_SCREENER_FIELDS = {
|
||||
"highest_controversy"}
|
||||
}
|
||||
EQUITY_SCREENER_FIELDS = merge_two_level_dicts(EQUITY_SCREENER_FIELDS, COMMON_SCREENER_FIELDS)
|
||||
ETF_SCREENER_EQ_MAP = {
|
||||
"exchange": {
|
||||
'ae': {'DFM'},
|
||||
'ar': {'BUE'},
|
||||
'at': {'VIE'},
|
||||
'au': {'ASX', 'CXA'},
|
||||
'be': {'BRU'},
|
||||
'br': {'SAO'},
|
||||
'ca': {'CNQ', 'NEO', 'TOR', 'VAN'},
|
||||
'ch': {'EBS'},
|
||||
'cl': {'SGO'},
|
||||
'cn': {'SHH', 'SHZ'},
|
||||
'co': {'BVC'},
|
||||
'cz': {'PRA'},
|
||||
'de': {'BER', 'DUS', 'EUX', 'FRA', 'HAM', 'HAN', 'GER', 'MUN', 'STU'},
|
||||
'dk': {'CPH'},
|
||||
'ee': {'TAL'},
|
||||
'eg': {'CAI'},
|
||||
'es': {'MAD', 'MCE'},
|
||||
'fi': {'HEL'},
|
||||
'fr': {'ENX', 'PAR'},
|
||||
'gb': {'AQS', 'CXE', 'IOB', 'LSE'},
|
||||
'gr': {'ATH'},
|
||||
'hk': {'HKG'},
|
||||
'hu': {'BUD'},
|
||||
'id': {'JKT'},
|
||||
'ie': {'ISE'},
|
||||
'il': {'TLV'},
|
||||
'in': {'BSE', 'NSI'},
|
||||
'is': {'ICE'},
|
||||
'it': {'MDD', 'MIL', 'TLO'},
|
||||
'jp': {'FKA', 'JPX', 'OSA', 'SAP'},
|
||||
'kr': {'KOE', 'KSC'},
|
||||
'kw': {'KUW'},
|
||||
'lk': {'CSE'},
|
||||
'lt': {'LIT'},
|
||||
'lv': {'RIS'},
|
||||
'mx': {'MEX'},
|
||||
'my': {'KLS'},
|
||||
'nl': {'AMS', 'DXE'},
|
||||
'no': {'OSL'},
|
||||
'nz': {'NZE'},
|
||||
'pe': {},
|
||||
'ph': {'PHP', 'PHS'},
|
||||
'pk': {'KAR'},
|
||||
'pl': {'WSE'},
|
||||
'pt': {'LIS'},
|
||||
'qa': {'DOH'},
|
||||
'ro': {'BVB'},
|
||||
'ru': {'MCX'},
|
||||
'sa': {'SAU'},
|
||||
'se': {'STO'},
|
||||
'sg': {'SES'},
|
||||
'sr': {},
|
||||
'th': {'SET'},
|
||||
'tr': {'IST'},
|
||||
'tw': {'TAI', 'TWO'},
|
||||
'us': {'ASE', 'BTS', 'CXI', 'NAE', 'NCM', 'NGM', 'NMS', 'NYQ', 'OEM', 'OQB', 'OQX', 'PCX', 'PNK', 'YHD'},
|
||||
've': {'CCS'},
|
||||
'vn': {'VSE'},
|
||||
'za': {'JNB'}
|
||||
},
|
||||
"categoryname": {
|
||||
"Allocation--15% to 30% Equity",
|
||||
"Allocation--30% to 50% Equity",
|
||||
"Allocation--50% to 70% Equity",
|
||||
"Allocation--70% to 85% Equity",
|
||||
"Allocation--85%+ Equity",
|
||||
"Bank Loan",
|
||||
"Bear Market",
|
||||
"China Region",
|
||||
"Commodities Agriculture",
|
||||
"Commodities Broad Basket",
|
||||
"Convertibles",
|
||||
"Corporate Bond",
|
||||
"Diversified Emerging Mkts",
|
||||
"Diversified Pacific/Asia",
|
||||
"Emerging Markets Bond",
|
||||
"Emerging-Markets Local-Currency Bond",
|
||||
"Energy Limited Partnership",
|
||||
"Equity Energy",
|
||||
"Equity Precious Metals",
|
||||
"Europe Stock",
|
||||
"Financial",
|
||||
"Foreign Large Blend",
|
||||
"Foreign Large Growth",
|
||||
"Foreign Large Value",
|
||||
"Foreign Small/Mid Blend",
|
||||
"Foreign Small/Mid Growth",
|
||||
"Foreign Small/Mid Value",
|
||||
"Global Real Estate",
|
||||
"Health",
|
||||
"High Yield Bond",
|
||||
"High Yield Muni",
|
||||
"Inflation-Protected Bond",
|
||||
"Infrastructure",
|
||||
"Intermediate Government",
|
||||
"Intermediate-Term Bond",
|
||||
"Japan Stock",
|
||||
"Large Blend",
|
||||
"Large Growth",
|
||||
"Large Value",
|
||||
"Long Government",
|
||||
"Long-Short Credit",
|
||||
"Long-Short Equity",
|
||||
"Long-Term Bond",
|
||||
"Managed Futures",
|
||||
"Market Neutral",
|
||||
"Mid-Cap Blend",
|
||||
"Mid-Cap Growth",
|
||||
"Mid-Cap Value",
|
||||
"Miscellaneous Region",
|
||||
"Multialternative",
|
||||
"Multicurrency",
|
||||
"Multisector Bond",
|
||||
"Muni California Intermediate",
|
||||
"Muni California Long",
|
||||
"Muni Massachusetts",
|
||||
"Muni Minnesota",
|
||||
"Muni National Interm",
|
||||
"Muni National Long",
|
||||
"Muni National Short",
|
||||
"Muni New Jersey",
|
||||
"Muni New York Intermediate",
|
||||
"Muni New York Long",
|
||||
"Muni Ohio",
|
||||
"Muni Pennsylvania",
|
||||
"Muni Single State Interm",
|
||||
"Muni Single State Long",
|
||||
"Muni Single State Short",
|
||||
"Natural Resources",
|
||||
"Nontraditional Bond",
|
||||
"Option Writing",
|
||||
"Other",
|
||||
"Other Allocation",
|
||||
"Pacific/Asia ex-Japan Stk",
|
||||
"Preferred Stock",
|
||||
"Real Estate",
|
||||
"Short Government",
|
||||
"Short-Term Bond",
|
||||
"Small Blend",
|
||||
"Small Growth",
|
||||
"Small Value",
|
||||
"Tactical Allocation",
|
||||
"Target-Date 2000-2010",
|
||||
"Target-Date 2015",
|
||||
"Target-Date 2020",
|
||||
"Target-Date 2025",
|
||||
"Target-Date 2030",
|
||||
"Target-Date 2035",
|
||||
"Target-Date 2040",
|
||||
"Target-Date 2045",
|
||||
"Target-Date 2050",
|
||||
"Target-Date 2055",
|
||||
"Target-Date 2060+",
|
||||
"Target-Date Retirement",
|
||||
"Technology",
|
||||
"Trading - Leveraged/Inverse Commodities",
|
||||
"Trading - Leveraged/Inverse Equity",
|
||||
"Trading--Inverse Equity",
|
||||
"Trading--Leveraged Equity",
|
||||
"Ultrashort Bond",
|
||||
"Utilities",
|
||||
"World Allocation",
|
||||
"World Bond",
|
||||
"World Stock"
|
||||
},
|
||||
"fundfamilyname": {
|
||||
"ALPS",
|
||||
"AMG Funds",
|
||||
"AQR Funds",
|
||||
"Aberdeen",
|
||||
"Alger",
|
||||
"AllianceBernstein",
|
||||
"Allianz Funds",
|
||||
"American Beacon",
|
||||
"American Century Investments",
|
||||
"American Funds",
|
||||
"Aquila",
|
||||
"Artisan",
|
||||
"BMO Funds",
|
||||
"BNY Mellon Funds",
|
||||
"Baird",
|
||||
"Barclays Funds",
|
||||
"Barings Funds",
|
||||
"Baron Capital Group",
|
||||
"BlackRock",
|
||||
"Brown Advisory Funds",
|
||||
"Calamos",
|
||||
"Calvert Investments",
|
||||
"Catalyst Mutual Funds",
|
||||
"Cohen & Steers",
|
||||
"Columbia",
|
||||
"Commerz Funds Solutions SA",
|
||||
"Commerzbank AG, Frankfurt am Main",
|
||||
"Davis Funds",
|
||||
"Delaware Investments",
|
||||
"Deutsche Asset Management",
|
||||
"Deutsche Bank AG",
|
||||
"Diamond Hill Funds",
|
||||
"Dimensional Fund Advisors",
|
||||
"Direxion Funds",
|
||||
"DoubleLine",
|
||||
"Dreyfus",
|
||||
"Dunham Funds",
|
||||
"Eagle Funds",
|
||||
"Eaton Vance",
|
||||
"Federated",
|
||||
"Fidelity Investments",
|
||||
"First Investors",
|
||||
"First Trust",
|
||||
"Flexshares Trust",
|
||||
"Franklin Templeton Investments",
|
||||
"GMO",
|
||||
"Gabelli",
|
||||
"Global X Funds",
|
||||
"Goldman Sachs",
|
||||
"Great-West Funds",
|
||||
"Guggenheim Investments",
|
||||
"GuideStone Funds",
|
||||
"HSBC",
|
||||
"Hancock Horizon",
|
||||
"Harbor",
|
||||
"Hartford Mutual Funds",
|
||||
"Henderson Global",
|
||||
"Hennessy",
|
||||
"Highland Funds",
|
||||
"ICON Funds",
|
||||
"Invesco",
|
||||
"Ivy Funds",
|
||||
"JPMorgan",
|
||||
"Janus",
|
||||
"John Hancock",
|
||||
"Lazard",
|
||||
"Legg Mason",
|
||||
"Lord Abbett",
|
||||
"MFS",
|
||||
"Madison Funds",
|
||||
"MainStay",
|
||||
"Manning & Napier",
|
||||
"Market Vectors",
|
||||
"MassMutual",
|
||||
"Matthews Asia Funds",
|
||||
"Morgan Stanley",
|
||||
"Nationwide",
|
||||
"Natixis Funds",
|
||||
"Neuberger Berman",
|
||||
"Northern Funds",
|
||||
"Nuveen",
|
||||
"OppenheimerFunds",
|
||||
"PNC Funds",
|
||||
"Pacific funds series trust",
|
||||
"Pax World",
|
||||
"Paydenfunds",
|
||||
"Pimco",
|
||||
"Pioneer Investments",
|
||||
"PowerShares",
|
||||
"Principal Funds",
|
||||
"ProFunds",
|
||||
"ProShares",
|
||||
"Prudential Investments",
|
||||
"Putnam",
|
||||
"RBC Global Asset Management.",
|
||||
"RidgeWorth",
|
||||
"Royce",
|
||||
"Russell",
|
||||
"Rydex Funds",
|
||||
"SEI",
|
||||
"SPDR State Street Global Advisors",
|
||||
"Salient Funds",
|
||||
"Saratoga",
|
||||
"Schwab Funds",
|
||||
"Sentinel",
|
||||
"Shelton Capital Management",
|
||||
"State Farm",
|
||||
"State Street Global Advisors (Chicago)",
|
||||
"Sterling Capital Funds",
|
||||
"SunAmerica",
|
||||
"T. Rowe Price",
|
||||
"TCW",
|
||||
"TIAA-CREF Asset Management",
|
||||
"Teton Westwood Funds",
|
||||
"Thornburg",
|
||||
"Thrivent",
|
||||
"Timothy Plan",
|
||||
"Touchstone",
|
||||
"Transamerica",
|
||||
"UBS",
|
||||
"UBS Group AG",
|
||||
"USAA",
|
||||
"VALIC",
|
||||
"Vanguard",
|
||||
"Vantagepoint Funds",
|
||||
"Victory",
|
||||
"Virtus",
|
||||
"Voya",
|
||||
"Waddell & Reed",
|
||||
"Wasatch",
|
||||
"Wells Fargo Funds",
|
||||
"William Blair",
|
||||
"WisdomTree",
|
||||
"iShares"
|
||||
},
|
||||
"morningstar_economic_moat": {
|
||||
"Wide",
|
||||
"Narrow",
|
||||
"None"
|
||||
},
|
||||
"morningstar_stewardship": {
|
||||
"Exemplary",
|
||||
"Standard",
|
||||
"Poor"
|
||||
},
|
||||
"morningstar_uncertainty": {
|
||||
"Low",
|
||||
"Medium",
|
||||
"High",
|
||||
"Very High",
|
||||
"Extreme"
|
||||
},
|
||||
"morningstar_moat_trend": {
|
||||
"Stable",
|
||||
"Positive",
|
||||
"Negative"
|
||||
},
|
||||
"morningstar_rating_change": {
|
||||
"Upgrade",
|
||||
"Downgrade"
|
||||
}
|
||||
}
|
||||
ETF_SCREENER_FIELDS = {
|
||||
"eq_fields": {
|
||||
"categoryname",
|
||||
"fundfamilyname",
|
||||
"region",
|
||||
"primary_sector",
|
||||
"morningstar_economic_moat",
|
||||
"morningstar_stewardship",
|
||||
"morningstar_uncertainty",
|
||||
"morningstar_moat_trend",
|
||||
"morningstar_rating_change"},
|
||||
"fundamentals": {
|
||||
"fundnetassets",
|
||||
"ticker"},
|
||||
"feesandexpenses": {
|
||||
"annualreportgrossexpenseratio",
|
||||
"annualreportnetexpenseratio",
|
||||
"turnoverratio"},
|
||||
"historicalperformance": {
|
||||
"annualreturnnavy1",
|
||||
"annualreturnnavy1categoryrank",
|
||||
"annualreturnnavy3",
|
||||
"annualreturnnavy5"},
|
||||
"keystats": {
|
||||
"avgdailyvol3m",
|
||||
"dayvolume",
|
||||
"eodvolume",
|
||||
"fiftytwowkpercentchange",
|
||||
"percentchange"},
|
||||
"morningstar_rating": {
|
||||
"morningstar_last_close_price_to_fair_value",
|
||||
"morningstar_rating",
|
||||
"morningstar_rating_updated_time"},
|
||||
"portfoliostatistics": {
|
||||
"marketcapitalvaluelong"},
|
||||
"purchasedetails": {
|
||||
"initialinvestment"},
|
||||
"trailingperformance": {
|
||||
"performanceratingoverall",
|
||||
"quarterendtrailingreturnytd",
|
||||
"riskratingoverall",
|
||||
"trailing_3m_return",
|
||||
"trailing_ytd_return"}
|
||||
}
|
||||
ETF_SCREENER_FIELDS = merge_two_level_dicts(ETF_SCREENER_FIELDS, COMMON_SCREENER_FIELDS)
|
||||
|
||||
USER_AGENTS = [
|
||||
# Chrome
|
||||
|
||||
@@ -358,26 +358,35 @@ class PriceHistory:
|
||||
|
||||
if splits is not None:
|
||||
splits = utils.set_df_tz(splits, interval, tz_exchange)
|
||||
self._splits = splits
|
||||
self._splits = splits['Stock Splits'].rename_axis('Date')
|
||||
else:
|
||||
self._splits = pd.Series()
|
||||
if dividends is not None:
|
||||
dividends = utils.set_df_tz(dividends, interval, tz_exchange)
|
||||
self._dividends = dividends
|
||||
if dividends is not None and 'currency' in dividends.columns:
|
||||
# Rare, only seen with Vietnam market
|
||||
# or companies that distribute dividends in a different currency
|
||||
price_currency = self._history_metadata['currency']
|
||||
if price_currency is None:
|
||||
price_currency = ''
|
||||
f_currency_mismatch = dividends['currency'] != price_currency
|
||||
if f_currency_mismatch.any():
|
||||
if repair and price_currency != '':
|
||||
# Attempt repair = currency conversion
|
||||
dividends = self._dividends_convert_fx(dividends, price_currency, repair)
|
||||
dividends = dividends.drop('currency', axis=1)
|
||||
if 'currency' in dividends.columns:
|
||||
# Rare, only seen with Vietnam market, or
|
||||
# companies that distribute dividends in a different currency
|
||||
self._dividends = dividends.rename_axis('Date')
|
||||
|
||||
price_currency = self._history_metadata['currency']
|
||||
if price_currency is None:
|
||||
price_currency = ''
|
||||
f_currency_mismatch = dividends['currency'] != price_currency
|
||||
if f_currency_mismatch.any():
|
||||
if repair and price_currency != '':
|
||||
# Attempt repair = currency conversion
|
||||
dividends = self._dividends_convert_fx(dividends, price_currency, repair)
|
||||
dividends = dividends.drop('currency', axis=1)
|
||||
else:
|
||||
self._dividends = dividends['Dividends'].rename_axis('Date')
|
||||
else:
|
||||
self._dividends = pd.Series()
|
||||
|
||||
if capital_gains is not None:
|
||||
capital_gains = utils.set_df_tz(capital_gains, interval, tz_exchange)
|
||||
self._capital_gains = capital_gains
|
||||
self._capital_gains = capital_gains['Capital Gains'].rename_axis('Date')
|
||||
else:
|
||||
self._capital_gains = pd.Series()
|
||||
if start is not None:
|
||||
if not quotes.empty:
|
||||
start_d = quotes.index[0].floor('D')
|
||||
@@ -562,7 +571,7 @@ class PriceHistory:
|
||||
df = data['prices']
|
||||
divs = data['dividends']
|
||||
|
||||
if divs is not None and 'currency' in divs.columns:
|
||||
if divs is not None and isinstance(divs, pd.DataFrame) and 'currency' in divs.columns:
|
||||
# Add dividends currency column
|
||||
df = utils.safe_merge_dfs(df.drop('Dividends', axis=1), divs, '1d')
|
||||
df['currency'] = df['currency'].fillna('')
|
||||
@@ -573,6 +582,7 @@ class PriceHistory:
|
||||
actions = df[[c for c in cols if c in df.columns]]
|
||||
|
||||
cols_numeric = ['Dividends', 'Stock Splits', 'Capital Gains']
|
||||
cols_numeric = [c for c in cols_numeric if c in actions.columns]
|
||||
actions = actions[(actions[cols_numeric]!=0).any(axis=1)]
|
||||
for c in cols_numeric:
|
||||
if (actions[c] == 0.0).all():
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from .query import EquityQuery
|
||||
from .query import EquityQuery, FundQuery, ETFQuery
|
||||
from .screener import screen, PREDEFINED_SCREENER_QUERIES
|
||||
|
||||
__all__ = ['EquityQuery', 'FundQuery', 'screen', 'PREDEFINED_SCREENER_QUERIES']
|
||||
__all__ = ['EquityQuery', 'FundQuery', 'ETFQuery', 'screen', 'PREDEFINED_SCREENER_QUERIES']
|
||||
|
||||
@@ -4,6 +4,7 @@ from typing import List, Union, Dict, TypeVar, Literal
|
||||
|
||||
from yfinance.const import EQUITY_SCREENER_EQ_MAP, EQUITY_SCREENER_FIELDS
|
||||
from yfinance.const import FUND_SCREENER_EQ_MAP, FUND_SCREENER_FIELDS
|
||||
from yfinance.const import ETF_SCREENER_EQ_MAP, ETF_SCREENER_FIELDS
|
||||
from yfinance.exceptions import YFNotImplementedError
|
||||
from ..utils import dynamic_docstring, generate_list_table_from_dict_universal
|
||||
|
||||
@@ -218,3 +219,41 @@ class FundQuery(QueryBase):
|
||||
"""
|
||||
return FUND_SCREENER_EQ_MAP
|
||||
|
||||
class ETFQuery(QueryBase):
|
||||
"""
|
||||
The `ETFQuery` class constructs filters for ETFs based on specific criteria such as category, fund family, exchange, and performance ratings.
|
||||
|
||||
Start with value operations: `EQ` (equals), `IS-IN` (is in), `BTWN` (between), `GT` (greater than), `LT` (less than), `GTE` (greater or equal), `LTE` (less or equal).
|
||||
|
||||
Combine them with logical operations: `AND`, `OR`.
|
||||
|
||||
Example:
|
||||
Predefined Yahoo query `top_etfs_us`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from yfinance import ETFQuery
|
||||
|
||||
ETFQuery('and', [
|
||||
ETFQuery('gt', ['intradayprice', 10]),
|
||||
ETFQuery('is-in', ['performanceratingoverall', 4, 5]),
|
||||
ETFQuery('eq', ['region', 'us'])
|
||||
])
|
||||
"""
|
||||
@dynamic_docstring({"valid_operand_fields_table": generate_list_table_from_dict_universal(ETF_SCREENER_FIELDS)})
|
||||
@property
|
||||
def valid_fields(self) -> Dict:
|
||||
"""
|
||||
Valid operands, grouped by category.
|
||||
{valid_operand_fields_table}
|
||||
"""
|
||||
return ETF_SCREENER_FIELDS
|
||||
|
||||
@dynamic_docstring({"valid_values_table": generate_list_table_from_dict_universal(ETF_SCREENER_EQ_MAP)})
|
||||
@property
|
||||
def valid_values(self) -> Dict:
|
||||
"""
|
||||
Most operands take number values, but some have a restricted set of valid values.
|
||||
{valid_values_table}
|
||||
"""
|
||||
return ETF_SCREENER_EQ_MAP
|
||||
@@ -9,7 +9,8 @@ from ..utils import dynamic_docstring, generate_list_table_from_dict_universal
|
||||
|
||||
from .query import EquityQuery as EqyQy
|
||||
from .query import FundQuery as FndQy
|
||||
from .query import QueryBase, EquityQuery, FundQuery
|
||||
from .query import ETFQuery as EtfQy
|
||||
from .query import QueryBase, EquityQuery, FundQuery, ETFQuery
|
||||
|
||||
_SCREENER_URL_ = f"{_QUERY1_URL_}/v1/finance/screener"
|
||||
_PREDEFINED_URL_ = f"{_SCREENER_URL_}/predefined/saved"
|
||||
@@ -48,11 +49,19 @@ PREDEFINED_SCREENER_QUERIES = {
|
||||
'solid_midcap_growth_funds': {"sortType":"DESC", "sortField":"fundnetassets",
|
||||
"query": FndQy('and', [FndQy('eq', ['categoryname', 'Mid-Cap Growth']), FndQy('is-in', ['performanceratingoverall', 4, 5]), FndQy('lt', ['initialinvestment', 100001]), FndQy('lt', ['annualreturnnavy1categoryrank', 50]), FndQy('eq', ['exchange', 'NAS'])])},
|
||||
'top_mutual_funds': {"sortType":"DESC", "sortField":"percentchange",
|
||||
"query": FndQy('and', [FndQy('gt', ['intradayprice', 15]), FndQy('is-in', ['performanceratingoverall', 4, 5]), FndQy('gt', ['initialinvestment', 1000]), FndQy('eq', ['exchange', 'NAS'])])}
|
||||
"query": FndQy('and', [FndQy('gt', ['intradayprice', 15]), FndQy('is-in', ['performanceratingoverall', 4, 5]), FndQy('gt', ['initialinvestment', 1000]), FndQy('eq', ['exchange', 'NAS'])])},
|
||||
'top_etfs_us': {"sortField":"percentchange", "sortType":"DESC",
|
||||
"query": EtfQy('and', [EtfQy('gt', ['intradayprice', 10]), EtfQy('is-in', ['performanceratingoverall', 4, 5]), EtfQy('eq', ['region', 'us'])])},
|
||||
'top_performing_etfs': {"sortField":"annualreportnetexpenseratio", "sortType":"ASC",
|
||||
"query": EtfQy('and', [EtfQy('eq', ['region', 'us']), EtfQy('is-in', ['performanceratingoverall', 4, 5]), EtfQy('gt', ['intradayprice', 10])])},
|
||||
'technology_etfs': {"sortField":"annualreportnetexpenseratio", "sortType":"ASC",
|
||||
"query": EtfQy('and', [EtfQy('eq', ['region', 'us']), EtfQy('eq', ['categoryname', 'Technology'])])},
|
||||
'bond_etfs': {"sortField":"annualreportnetexpenseratio", "sortType":"ASC",
|
||||
"query": EtfQy('and', [EtfQy('eq', ['region', 'us']), EtfQy('is-in', ['categoryname', 'Corporate Bond', 'Emerging Markets Bond', 'Emerging-Markets Local-Currency Bond', 'High Yield Bond', 'Intermediate-Term Bond', 'Long-Term Bond', 'Inflation-Protected Bond', 'Multisector Bond', 'Nontraditional Bond', 'Short-Term Bond', 'Ultrashort Bond', 'World Bond'])])}
|
||||
}
|
||||
|
||||
@dynamic_docstring({"predefined_screeners": generate_list_table_from_dict_universal(PREDEFINED_SCREENER_QUERIES, bullets=True, title='Predefined queries (Dec-2024)')})
|
||||
def screen(query: Union[str, EquityQuery, FundQuery],
|
||||
def screen(query: Union[str, EquityQuery, FundQuery, ETFQuery],
|
||||
offset: int = None,
|
||||
size: int = None,
|
||||
count: int = None,
|
||||
@@ -65,7 +74,7 @@ def screen(query: Union[str, EquityQuery, FundQuery],
|
||||
Run a screen: predefined query, or custom query.
|
||||
|
||||
:Parameters:
|
||||
* Defaults only apply if query = EquityQuery or FundQuery
|
||||
* Defaults only apply if query = EquityQuery, FundQuery, or ETFQuery
|
||||
query : str | Query:
|
||||
The query to execute, either name of predefined or custom query.
|
||||
For predefined list run yf.PREDEFINED_SCREENER_QUERIES.keys()
|
||||
@@ -194,6 +203,8 @@ def screen(query: Union[str, EquityQuery, FundQuery],
|
||||
post_query['quoteType'] = 'EQUITY'
|
||||
elif isinstance(post_query['query'], FndQy):
|
||||
post_query['quoteType'] = 'MUTUALFUND'
|
||||
elif isinstance(post_query['query'], EtfQy):
|
||||
post_query['quoteType'] = 'ETF'
|
||||
post_query['query'] = post_query['query'].to_dict()
|
||||
data = dumps(post_query, separators=(",", ":"), ensure_ascii=False)
|
||||
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user