Skip to content

Commit

Permalink
add upgrades downgrades
Browse files Browse the repository at this point in the history
add upgrades/downgrades (recommendations history)
return data is pandas dataframe
add test for upgrades/downgrades data
  • Loading branch information
ghofi-dev committed Dec 10, 2023
1 parent 1d3ef4f commit 4175885
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 9 deletions.
19 changes: 17 additions & 2 deletions tests/ticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@
("info", dict),
("calendar", pd.DataFrame),
("recommendations", Union[pd.DataFrame, dict]),
("recommendations_summary", Union[pd.DataFrame, dict]),
("upgrades_downgrades", Union[pd.DataFrame, dict]),
("recommendations_history", Union[pd.DataFrame, dict]),
("earnings", pd.DataFrame),
("quarterly_earnings", pd.DataFrame),
("recommendations_summary", Union[pd.DataFrame, dict]),
("quarterly_cashflow", pd.DataFrame),
("cashflow", pd.DataFrame),
("quarterly_balance_sheet", pd.DataFrame),
Expand Down Expand Up @@ -637,14 +639,27 @@ def test_recommendations(self):
data_cached = self.ticker.recommendations
self.assertIs(data, data_cached, "data not cached")

# def test_recommendations_summary(self):
# def test_recommendations_summary(self): # currently alias for recommendations
# data = self.ticker.recommendations_summary
# self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
# self.assertFalse(data.empty, "data is empty")

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

def test_recommendations_history(self): # alias for upgrades_downgrades
data = self.ticker.upgrades_downgrades
data_history = self.ticker.recommendations_history
self.assertTrue(data.equals(data_history))
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
self.assertFalse(data.empty, "data is empty")
self.assertTrue(len(data.columns) == 4, "data has wrong number of columns")
self.assertEqual(data.columns.values.tolist(), ['Firm', 'ToGrade', 'FromGrade', 'Action'], "data has wrong column names")
self.assertIsInstance(data.index, pd.DatetimeIndex, "data has wrong index type")

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

# def test_analyst_price_target(self):
# data = self.ticker.analyst_price_target
# self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
Expand Down
18 changes: 15 additions & 3 deletions yfinance/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1708,6 +1708,21 @@ def get_recommendations(self, proxy=None, as_dict=False):
return data.to_dict()
return data

def get_recommendations_summary(self, proxy=None, as_dict=False):
return self.get_recommendations(proxy=proxy, as_dict=as_dict)

def get_upgrades_downgrades(self, proxy=None, as_dict=False):
"""
Returns a DataFrame with the recommendations changes (upgrades/downgrades)
Index: date of grade
Columns: firm toGrade fromGrade action
"""
self._quote.proxy = proxy or self.proxy
data = self._quote.upgrades_downgrades
if as_dict:
return data.to_dict()
return data

def get_calendar(self, proxy=None, as_dict=False):
self._quote.proxy = proxy or self.proxy
data = self._quote.calendar
Expand Down Expand Up @@ -1760,9 +1775,6 @@ def get_sustainability(self, proxy=None, as_dict=False):
return data.to_dict()
return data

def get_recommendations_summary(self, proxy=None, as_dict=False):
return self.get_recommendations(proxy=proxy, as_dict=as_dict)

def get_analyst_price_target(self, proxy=None, as_dict=False):
self._analysis.proxy = proxy or self.proxy
data = self._analysis.analyst_price_target
Expand Down
18 changes: 18 additions & 0 deletions yfinance/scrapers/quote.py
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,7 @@ def __init__(self, data: YfData, symbol: str, proxy=None):
self._retired_info = None
self._sustainability = None
self._recommendations = None
self._upgrades_downgrades = None
self._calendar = None

self._already_scraped = False
Expand Down Expand Up @@ -592,6 +593,23 @@ def recommendations(self) -> pd.DataFrame:
self._recommendations = pd.DataFrame(data)
return self._recommendations

@property
def upgrades_downgrades(self) -> pd.DataFrame:
if self._upgrades_downgrades is None:
result = self._fetch(self.proxy, modules=['upgradeDowngradeHistory'])
try:
data = result["quoteSummary"]["result"][0]["upgradeDowngradeHistory"]["history"]
if len(data) == 0:
raise YFinanceDataException(f"No upgrade/downgrade history found for {self._symbol}")
df = pd.DataFrame(data)
df.rename(columns={"epochGradeDate": "GradeDate", 'firm': 'Firm', 'toGrade': 'ToGrade', 'fromGrade': 'FromGrade', 'action': 'Action'}, inplace=True)
df.set_index('GradeDate', inplace=True)
df.index = pd.to_datetime(df.index, unit='s')
self._upgrades_downgrades = df
except (KeyError, IndexError):
raise YFinanceDataException(f"Failed to parse json response from Yahoo Finance: {result}")
return self._upgrades_downgrades

@property
def calendar(self) -> pd.DataFrame:
if self._calendar is None:
Expand Down
16 changes: 12 additions & 4 deletions yfinance/ticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,18 @@ def calendar(self) -> _pd.DataFrame:
def recommendations(self):
return self.get_recommendations()

@property
def recommendations_summary(self):
return self.get_recommendations_summary()

@property
def upgrades_downgrades(self):
return self.get_upgrades_downgrades()

@property
def recommendations_history(self):
return self.get_upgrades_downgrades()

@property
def earnings(self) -> _pd.DataFrame:
return self.get_earnings()
Expand Down Expand Up @@ -217,10 +229,6 @@ def cashflow(self) -> _pd.DataFrame:
def quarterly_cashflow(self) -> _pd.DataFrame:
return self.quarterly_cash_flow

@property
def recommendations_summary(self):
return self.get_recommendations_summary()

@property
def analyst_price_target(self) -> _pd.DataFrame:
return self.get_analyst_price_target()
Expand Down

0 comments on commit 4175885

Please sign in to comment.