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

sync dev -> main #2034

Merged
merged 29 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2594eef
Merge pull request #1994 from ranaroussi/main
ValueRaider Jul 19, 2024
d70c99c
Fix error on empty options chain
stevenbischoff Jul 19, 2024
ce6bf93
Remove erroneous print statement
aaron-jencks Jul 22, 2024
59178f4
Merge pull request #1998 from aaron-jencks/patch-1
ValueRaider Jul 22, 2024
fa37798
Merge pull request #1995 from stevenbischoff/dev
ValueRaider Jul 24, 2024
614906c
Implement fetch sec-filings
ValueRaider Aug 3, 2024
d490fa3
use dict.get() to safely access key
ericpien Aug 6, 2024
89e61bb
Merge pull request #2009 from ranaroussi/feature/sec-filings
ValueRaider Aug 6, 2024
07329db
Merge pull request #2013 from ericpien/dev
ValueRaider Aug 7, 2024
76062dd
fixing minor typo
ericpien Aug 10, 2024
cd18947
Merge pull request #2020 from ericpien/dev
ValueRaider Aug 10, 2024
ffe3642
Prices: improve exceptions & logging
ValueRaider Jul 22, 2024
602fe2a
Merge pull request #2000 from ranaroussi/feature/prices-adjust-errors…
ValueRaider Aug 10, 2024
447ec68
add try except for when requests.response.json() fails
ericpien Aug 10, 2024
2b8716c
Merge pull request #2021 from ericpien/address_issue_2015
ValueRaider Aug 10, 2024
1037ec5
solves #2026
aleksfasting Aug 15, 2024
67d6859
Merge pull request #2027 from aleksfasting/fix/NSEI-quarter-offset
ValueRaider Aug 16, 2024
408d065
Implement Analysis
Fidasek009 Aug 11, 2024
bc71962
Implement tests for Analysis
Fidasek009 Aug 16, 2024
0c447d5
Update `README.md` with analysis attributes
Fidasek009 Aug 16, 2024
9cf6289
Fix ruff complaining
Fidasek009 Aug 16, 2024
4941c61
Merge pull request #2023 from Fidasek009/feature/analysis
ValueRaider Aug 16, 2024
1baecc9
Revert PR #2027, breaks with prepost=True
ValueRaider Aug 21, 2024
7c66bc3
Merge pull request #2032 from ranaroussi/revert/PR-2027-30m-resampling
ValueRaider Aug 22, 2024
2f5203d
New: repair bad dividends and div-adjusts. Plus other repair fixes.
ValueRaider Aug 21, 2024
a74ad47
Repair dividends: fine-tune logic + unit tests
ValueRaider Aug 22, 2024
7e12f20
Merge pull request #2031 from ranaroussi/feature/price-repair-div-adjust
ValueRaider Aug 22, 2024
ce9becd
Merge branch 'main' into dev
ValueRaider Aug 22, 2024
a2b5d6b
Fix 2x old tests
ValueRaider Aug 22, 2024
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
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ msft.capital_gains # only for mutual funds & etfs
msft.get_shares_full(start="2022-01-01", end=None)

# show financials:
msft.calendar
msft.sec_filings
# - income statement
msft.income_stmt
msft.quarterly_income_stmt
Expand All @@ -126,6 +128,15 @@ msft.recommendations
msft.recommendations_summary
msft.upgrades_downgrades

# show analysts data
msft.analyst_price_targets
msft.earnings_estimate
msft.revenue_estimate
msft.earnings_history
msft.eps_trend
msft.eps_revisions
msft.growth_estimates

# Show future and historic earnings dates, returns at most next 4 quarters and last 8 quarters by default.
# Note: If more are needed use msft.get_earnings_dates(limit=XX) with increased limit argument.
msft.earnings_dates
Expand Down
649 changes: 649 additions & 0 deletions tests/data/1398-HK-1d-bad-div-fixed.csv

Large diffs are not rendered by default.

649 changes: 649 additions & 0 deletions tests/data/1398-HK-1d-bad-div.csv

Large diffs are not rendered by default.

649 changes: 649 additions & 0 deletions tests/data/3988-HK-1d-bad-div-fixed.csv

Large diffs are not rendered by default.

649 changes: 649 additions & 0 deletions tests/data/3988-HK-1d-bad-div.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/ABDP-L-1d-bad-div-fixed.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/ABDP-L-1d-bad-div.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/ADIG-L-1d-bad-div-fixed.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/ADIG-L-1d-bad-div.csv

Large diffs are not rendered by default.

663 changes: 663 additions & 0 deletions tests/data/CALM-1d-no-bad-divs.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/CLC-L-1d-bad-div-fixed.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/CLC-L-1d-bad-div.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/ELCO-L-1d-bad-div-fixed.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/ELCO-L-1d-bad-div.csv

Large diffs are not rendered by default.

663 changes: 663 additions & 0 deletions tests/data/EWG-1d-no-bad-divs.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/HSBK-IL-1d-no-bad-divs.csv

Large diffs are not rendered by default.

678 changes: 678 additions & 0 deletions tests/data/IBE-MC-1d-no-bad-divs.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/KAP-IL-1d-bad-div-fixed.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/KAP-IL-1d-bad-div.csv

Large diffs are not rendered by default.

652 changes: 652 additions & 0 deletions tests/data/KEN-TA-1d-bad-div-fixed.csv

Large diffs are not rendered by default.

652 changes: 652 additions & 0 deletions tests/data/KEN-TA-1d-bad-div.csv

Large diffs are not rendered by default.

675 changes: 675 additions & 0 deletions tests/data/KME-MI-1d-bad-div-fixed.csv

Large diffs are not rendered by default.

675 changes: 675 additions & 0 deletions tests/data/KME-MI-1d-bad-div.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/KMR-L-1d-no-bad-divs.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/KWS-L-1d-bad-div-fixed.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/KWS-L-1d-bad-div.csv

Large diffs are not rendered by default.

665 changes: 665 additions & 0 deletions tests/data/LSC-L-1d-bad-div-fixed.csv

Large diffs are not rendered by default.

665 changes: 665 additions & 0 deletions tests/data/LSC-L-1d-bad-div.csv

Large diffs are not rendered by default.

663 changes: 663 additions & 0 deletions tests/data/NPK-1d-no-bad-divs.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/NVT-L-1d-bad-div-fixed.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/NVT-L-1d-bad-div.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/PSH-L-1d-bad-div-fixed.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/PSH-L-1d-bad-div.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/REL-L-1d-bad-div-fixed.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/REL-L-1d-bad-div.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/RGL-L-1d-bad-div-fixed.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/RGL-L-1d-bad-div.csv

Large diffs are not rendered by default.

663 changes: 663 additions & 0 deletions tests/data/SAND-1d-bad-div-fixed.csv

Large diffs are not rendered by default.

663 changes: 663 additions & 0 deletions tests/data/SAND-1d-bad-div.csv

Large diffs are not rendered by default.

663 changes: 663 additions & 0 deletions tests/data/SCR-TO-1d-bad-div-fixed.csv

Large diffs are not rendered by default.

663 changes: 663 additions & 0 deletions tests/data/SCR-TO-1d-bad-div.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/SERE-L-1d-bad-div-fixed.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/SERE-L-1d-bad-div.csv

Large diffs are not rendered by default.

678 changes: 678 additions & 0 deletions tests/data/SOLB-BR-1d-bad-div-fixed.csv

Large diffs are not rendered by default.

678 changes: 678 additions & 0 deletions tests/data/SOLB-BR-1d-bad-div.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/TEM-L-1d-bad-div-fixed.csv

Large diffs are not rendered by default.

666 changes: 666 additions & 0 deletions tests/data/TEM-L-1d-bad-div.csv

Large diffs are not rendered by default.

678 changes: 678 additions & 0 deletions tests/data/TEP-PA-1d-bad-div-fixed.csv

Large diffs are not rendered by default.

678 changes: 678 additions & 0 deletions tests/data/TEP-PA-1d-bad-div.csv

Large diffs are not rendered by default.

584 changes: 584 additions & 0 deletions tests/data/TISG-MI-1d-no-bad-divs.csv

Large diffs are not rendered by default.

730 changes: 730 additions & 0 deletions tests/test_price_repair.py

Large diffs are not rendered by default.

512 changes: 9 additions & 503 deletions tests/test_prices.py

Large diffs are not rendered by default.

118 changes: 101 additions & 17 deletions tests/test_ticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,17 @@
("balance_sheet", pd.DataFrame),
("quarterly_income_stmt", pd.DataFrame),
("income_stmt", pd.DataFrame),
("analyst_price_target", pd.DataFrame),
("revenue_forecasts", pd.DataFrame),
("analyst_price_targets", dict),
("earnings_estimate", pd.DataFrame),
("revenue_estimate", pd.DataFrame),
("earnings_history", pd.DataFrame),
("eps_trend", pd.DataFrame),
("eps_revisions", pd.DataFrame),
("growth_estimates", pd.DataFrame),
("sustainability", pd.DataFrame),
("options", tuple),
("news", Any),
("earnings_trend", pd.DataFrame),
("earnings_dates", pd.DataFrame),
("earnings_forecasts", pd.DataFrame),
)

def assert_attribute_type(testClass: unittest.TestCase, instance, attribute_name, expected_type):
Expand Down Expand Up @@ -715,9 +718,11 @@ def tearDownClass(cls):

def setUp(self):
self.ticker = yf.Ticker("GOOGL", session=self.session)
self.ticker_no_analysts = yf.Ticker("^GSPC", session=self.session)

def tearDown(self):
self.ticker = None
self.ticker_no_analysts = None

def test_recommendations(self):
data = self.ticker.recommendations
Expand Down Expand Up @@ -748,23 +753,102 @@ def test_upgrades_downgrades(self):
data_cached = self.ticker.upgrades_downgrades
self.assertIs(data, data_cached, "data not cached")

# Below will fail because not ported to Yahoo API
def test_analyst_price_targets(self):
data = self.ticker.analyst_price_targets
self.assertIsInstance(data, dict, "data has wrong type")

# def test_analyst_price_target(self):
# data = self.ticker.analyst_price_target
# self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
# self.assertFalse(data.empty, "data is empty")
keys = {'current', 'low', 'high', 'mean', 'median'}
self.assertEqual(data.keys(), keys, "data has wrong keys")

# data_cached = self.ticker.analyst_price_target
# self.assertIs(data, data_cached, "data not cached")
data_cached = self.ticker.analyst_price_targets
self.assertIs(data, data_cached, "data not cached")

# def test_revenue_forecasts(self):
# data = self.ticker.revenue_forecasts
# self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
# self.assertFalse(data.empty, "data is empty")
def test_earnings_estimate(self):
data = self.ticker.earnings_estimate
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
self.assertFalse(data.empty, "data is empty")

# data_cached = self.ticker.revenue_forecasts
# self.assertIs(data, data_cached, "data not cached")
columns = ['numberOfAnalysts', 'avg', 'low', 'high', 'yearAgoEps', 'growth']
self.assertEqual(data.columns.values.tolist(), columns, "data has wrong column names")

index = ['0q', '+1q', '0y', '+1y']
self.assertEqual(data.index.values.tolist(), index, "data has wrong row names")

data_cached = self.ticker.earnings_estimate
self.assertIs(data, data_cached, "data not cached")

def test_revenue_estimate(self):
data = self.ticker.revenue_estimate
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
self.assertFalse(data.empty, "data is empty")

columns = ['numberOfAnalysts', 'avg', 'low', 'high', 'yearAgoRevenue', 'growth']
self.assertEqual(data.columns.values.tolist(), columns, "data has wrong column names")

index = ['0q', '+1q', '0y', '+1y']
self.assertEqual(data.index.values.tolist(), index, "data has wrong row names")

data_cached = self.ticker.revenue_estimate
self.assertIs(data, data_cached, "data not cached")

def test_earnings_history(self):
data = self.ticker.earnings_history
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
self.assertFalse(data.empty, "data is empty")

columns = ['epsEstimate', 'epsActual', 'epsDifference', 'surprisePercent']
self.assertEqual(data.columns.values.tolist(), columns, "data has wrong column names")
self.assertIsInstance(data.index, pd.DatetimeIndex, "data has wrong index type")

data_cached = self.ticker.earnings_history
self.assertIs(data, data_cached, "data not cached")

def test_eps_trend(self):
data = self.ticker.eps_trend
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
self.assertFalse(data.empty, "data is empty")

columns = ['current', '7daysAgo', '30daysAgo', '60daysAgo', '90daysAgo']
self.assertEqual(data.columns.values.tolist(), columns, "data has wrong column names")

index = ['0q', '+1q', '0y', '+1y']
self.assertEqual(data.index.values.tolist(), index, "data has wrong row names")

data_cached = self.ticker.eps_trend
self.assertIs(data, data_cached, "data not cached")

def test_growth_estimates(self):
data = self.ticker.growth_estimates
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
self.assertFalse(data.empty, "data is empty")

columns = ['stock', 'industry', 'sector', 'index']
self.assertEqual(data.columns.values.tolist(), columns, "data has wrong column names")

index = ['0q', '+1q', '0y', '+1y', '+5y', '-5y']
self.assertEqual(data.index.values.tolist(), index, "data has wrong row names")

data_cached = self.ticker.growth_estimates
self.assertIs(data, data_cached, "data not cached")

def test_no_analysts(self):
attributes = [
'recommendations',
'upgrades_downgrades',
'earnings_estimate',
'revenue_estimate',
'earnings_history',
'eps_trend',
'growth_estimates',
]

for attribute in attributes:
try:
data = getattr(self.ticker_no_analysts, attribute)
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
self.assertTrue(data.empty, "data is not empty")
except Exception as e:
self.fail(f"Excpetion raised for attribute '{attribute}': {e}")



Expand Down
88 changes: 62 additions & 26 deletions yfinance/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@ def get_calendar(self, proxy=None) -> dict:
self._quote.proxy = proxy or self.proxy
return self._quote.calendar

def get_sec_filings(self, proxy=None) -> dict:
self._quote.proxy = proxy or self.proxy
return self._quote.sec_filings

def get_major_holders(self, proxy=None, as_dict=False):
self._holders.proxy = proxy or self.proxy
data = self._holders.major
Expand Down Expand Up @@ -240,40 +244,67 @@ def get_sustainability(self, proxy=None, as_dict=False):
return data.to_dict()
return data

def get_analyst_price_target(self, proxy=None, as_dict=False):
def get_analyst_price_targets(self, proxy=None) -> dict:
"""
Keys: current low high mean median
"""
self._analysis.proxy = proxy or self.proxy
data = self._analysis.analyst_price_target
if as_dict:
return data.to_dict()
data = self._analysis.analyst_price_targets
return data

def get_rev_forecast(self, proxy=None, as_dict=False):
def get_earnings_estimate(self, proxy=None, as_dict=False):
"""
Index: 0q +1q 0y +1y
Columns: numberOfAnalysts avg low high yearAgoEps growth
"""
self._analysis.proxy = proxy or self.proxy
data = self._analysis.rev_est
if as_dict:
return data.to_dict()
return data
data = self._analysis.earnings_estimate
return data.to_dict() if as_dict else data

def get_earnings_forecast(self, proxy=None, as_dict=False):
def get_revenue_estimate(self, proxy=None, as_dict=False):
"""
Index: 0q +1q 0y +1y
Columns: numberOfAnalysts avg low high yearAgoRevenue growth
"""
self._analysis.proxy = proxy or self.proxy
data = self._analysis.eps_est
if as_dict:
return data.to_dict()
return data
data = self._analysis.revenue_estimate
return data.to_dict() if as_dict else data

def get_earnings_history(self, proxy=None, as_dict=False):
"""
Index: pd.DatetimeIndex
Columns: epsEstimate epsActual epsDifference surprisePercent
"""
self._analysis.proxy = proxy or self.proxy
data = self._analysis.earnings_history
return data.to_dict() if as_dict else data

def get_trend_details(self, proxy=None, as_dict=False):
def get_eps_trend(self, proxy=None, as_dict=False):
"""
Index: 0q +1q 0y +1y
Columns: current 7daysAgo 30daysAgo 60daysAgo 90daysAgo
"""
self._analysis.proxy = proxy or self.proxy
data = self._analysis.analyst_trend_details
if as_dict:
return data.to_dict()
return data
data = self._analysis.eps_trend
return data.to_dict() if as_dict else data

def get_eps_revisions(self, proxy=None, as_dict=False):
"""
Index: 0q +1q 0y +1y
Columns: upLast7days upLast30days downLast7days downLast30days
"""
self._analysis.proxy = proxy or self.proxy
data = self._analysis.eps_revisions
return data.to_dict() if as_dict else data

def get_earnings_trend(self, proxy=None, as_dict=False):
def get_growth_estimates(self, proxy=None, as_dict=False):
"""
Index: 0q +1q 0y +1y +5y -5y
Columns: stock industry sector index
"""
self._analysis.proxy = proxy or self.proxy
data = self._analysis.earnings_trend
if as_dict:
return data.to_dict()
return data
data = self._analysis.growth_estimates
return data.to_dict() if as_dict else data

def get_earnings(self, proxy=None, as_dict=False, freq="yearly"):
"""
Expand Down Expand Up @@ -508,11 +539,16 @@ def get_news(self, proxy=None) -> list:
# Getting data from json
url = f"{_BASE_URL_}/v1/finance/search?q={self.ticker}"
data = self._data.cache_get(url=url, proxy=proxy)
if "Will be right back" in data.text:
if data is None or "Will be right back" in data.text:
raise RuntimeError("*** YAHOO! FINANCE IS CURRENTLY DOWN! ***\n"
"Our engineers are working quickly to resolve "
"the issue. Thank you for your patience.")
data = data.json()
try:
data = data.json()
except (_json.JSONDecodeError):
logger = utils.get_yf_logger()
logger.error(f"{self.ticker}: Failed to retrieve the news and received faulty response instead.")
data = {}

# parse news
self._news = data.get("news", [])
Expand Down
15 changes: 6 additions & 9 deletions yfinance/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,6 @@ class YFDataException(YFException):
pass


class YFChartError(YFException):
def __init__(self, ticker, description):
self.ticker = ticker
super().__init__(f"{self.ticker}: {description}")


class YFNotImplementedError(NotImplementedError):
def __init__(self, method_name):
super().__init__(f"Have not implemented fetching '{method_name}' from Yahoo API")
Expand All @@ -27,19 +21,22 @@ def __init__(self, ticker, rationale):

class YFTzMissingError(YFTickerMissingError):
def __init__(self, ticker):
super().__init__(ticker, "No timezone found")
super().__init__(ticker, "no timezone found")


class YFPricesMissingError(YFTickerMissingError):
def __init__(self, ticker, debug_info):
self.debug_info = debug_info
super().__init__(ticker, f"No price data found {debug_info}")
if debug_info != '':
super().__init__(ticker, f"no price data found {debug_info}")
else:
super().__init__(ticker, "no price data found")


class YFEarningsDateMissing(YFTickerMissingError):
# note that this does not get raised. Added in case of raising it in the future
def __init__(self, ticker):
super().__init__(ticker, "No earnings dates found")
super().__init__(ticker, "no earnings dates found")


class YFInvalidPeriodError(YFException):
Expand Down
Loading
Loading