Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement support for funds data #2041

Merged
merged 1 commit into from
Sep 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,31 @@ opt = msft.option_chain('YYYY-MM-DD')
# data available via: opt.calls, opt.puts
```

For tickers that are ETFs/Mutual Funds, `Ticker.funds_data` provides access to fund related data.

Funds' Top Holdings and other data with category average is returned as `pd.DataFrame`.

```python
import yfinance as yf
spy = yf.Ticker('SPY')
data = spy.funds_data

# show fund description
data.description

# show operational information
data.fund_overview
data.fund_operations

# show holdings related information
data.asset_classes
data.top_holdings
data.equity_holdings
data.bond_holdings
data.bond_ratings
data.sector_weightings
```

If you want to use a proxy server for downloading data, use:

```python
Expand Down
84 changes: 81 additions & 3 deletions tests/test_ticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from .context import yfinance as yf
from .context import session_gbl
from yfinance.exceptions import YFChartError, YFInvalidPeriodError, YFNotImplementedError, YFTickerMissingError, YFTzMissingError
from yfinance.exceptions import YFPricesMissingError, YFInvalidPeriodError, YFNotImplementedError, YFTickerMissingError, YFTzMissingError, YFDataException


import unittest
Expand Down Expand Up @@ -142,14 +142,14 @@ def test_prices_missing(self):
# META call option, 2024 April 26th @ strike of 180000
tkr = 'META240426C00180000'
dat = yf.Ticker(tkr, session=self.session)
with self.assertRaises(YFChartError):
with self.assertRaises(YFPricesMissingError):
dat.history(period="5d", interval="1m", raise_errors=True)

def test_ticker_missing(self):
tkr = 'ATVI'
dat = yf.Ticker(tkr, session=self.session)
# A missing ticker can trigger either a niche error or the generalized error
with self.assertRaises((YFTickerMissingError, YFTzMissingError, YFChartError)):
with self.assertRaises((YFTickerMissingError, YFTzMissingError, YFPricesMissingError)):
dat.history(period="3mo", interval="1d", raise_errors=True)

def test_goodTicker(self):
Expand Down Expand Up @@ -997,7 +997,84 @@ def test_complementary_info(self):
# else:
# raise

class TestTickerFundsData(unittest.TestCase):
session = None

@classmethod
def setUpClass(cls):
cls.session = session_gbl

@classmethod
def tearDownClass(cls):
if cls.session is not None:
cls.session.close()

def setUp(self):
self.test_tickers = [yf.Ticker("SPY", session=self.session), # equity etf
yf.Ticker("JNK", session=self.session), # bonds etf
yf.Ticker("VTSAX", session=self.session)] # mutual fund

def tearDown(self):
self.ticker = None

def test_fetch_and_parse(self):
try:
for ticker in self.test_tickers:
ticker.funds_data._fetch_and_parse()

except Exception as e:
self.fail(f"_fetch_and_parse raised an exception unexpectedly: {e}")

with self.assertRaises(YFDataException):
ticker = yf.Ticker("AAPL", session=self.session) # stock, not funds
ticker.funds_data._fetch_and_parse()
self.fail("_fetch_and_parse should have failed when calling for non-funds data")

def test_description(self):
for ticker in self.test_tickers:
description = ticker.funds_data.description
self.assertIsInstance(description, str)
self.assertTrue(len(description) > 0)

def test_fund_overview(self):
for ticker in self.test_tickers:
fund_overview = ticker.funds_data.fund_overview
self.assertIsInstance(fund_overview, dict)

def test_fund_operations(self):
for ticker in self.test_tickers:
fund_operations = ticker.funds_data.fund_operations
self.assertIsInstance(fund_operations, pd.DataFrame)

def test_asset_classes(self):
for ticker in self.test_tickers:
asset_classes = ticker.funds_data.asset_classes
self.assertIsInstance(asset_classes, dict)

def test_top_holdings(self):
for ticker in self.test_tickers:
top_holdings = ticker.funds_data.top_holdings
self.assertIsInstance(top_holdings, pd.DataFrame)

def test_equity_holdings(self):
for ticker in self.test_tickers:
equity_holdings = ticker.funds_data.equity_holdings
self.assertIsInstance(equity_holdings, pd.DataFrame)

def test_bond_holdings(self):
for ticker in self.test_tickers:
bond_holdings = ticker.funds_data.bond_holdings
self.assertIsInstance(bond_holdings, pd.DataFrame)

def test_bond_ratings(self):
for ticker in self.test_tickers:
bond_ratings = ticker.funds_data.bond_ratings
self.assertIsInstance(bond_ratings, dict)

def test_sector_weightings(self):
for ticker in self.test_tickers:
sector_weightings = ticker.funds_data.sector_weightings
self.assertIsInstance(sector_weightings, dict)

def suite():
suite = unittest.TestSuite()
Expand All @@ -1007,6 +1084,7 @@ def suite():
suite.addTest(TestTickerHistory('Test Ticker history'))
suite.addTest(TestTickerMiscFinancials('Test misc financials'))
suite.addTest(TestTickerInfo('Test info & fast_info'))
suite.addTest(TestTickerFundsData('Test Funds Data'))
return suite


Expand Down
8 changes: 8 additions & 0 deletions yfinance/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from .scrapers.holders import Holders
from .scrapers.quote import Quote, FastInfo
from .scrapers.history import PriceHistory
from .scrapers.funds import FundsData

from .const import _BASE_URL_, _ROOT_URL_

Expand Down Expand Up @@ -70,6 +71,7 @@ def __init__(self, ticker, session=None, proxy=None):
self._holders = Holders(self._data, self.ticker)
self._quote = Quote(self._data, self.ticker)
self._fundamentals = Fundamentals(self._data, self.ticker)
self._funds_data = None

self._fast_info = None

Expand Down Expand Up @@ -647,3 +649,9 @@ def get_earnings_dates(self, limit=12, proxy=None) -> Optional[pd.DataFrame]:

def get_history_metadata(self, proxy=None) -> dict:
return self._lazy_load_price_history().get_history_metadata(proxy)

def get_funds_data(self, proxy=None) -> Optional[FundsData]:
if not self._funds_data:
self._funds_data = FundsData(self._data, self.ticker)

return self._funds_data
Loading
Loading