diff --git a/ROADMAP.md b/ROADMAP.md
index f5c466f3a9a..86e76c6d2ac 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -33,6 +33,7 @@
* [x] Top Losers (@didier) - [PR #171](https://github.com/DidierRLopes/GamestonkTerminal/pull/171)
* [x] ARK orders (@aia) - [PR #140](https://github.com/DidierRLopes/GamestonkTerminal/pull/140)
* [x] Analyse sectors/industry performance, overview and valuation from Finviz (@didier) - [PR #315](https://github.com/DidierRLopes/GamestonkTerminal/pull/315)
+* [x] Add top ratings updates (@hinxx) - [PR #350](https://github.com/DidierRLopes/GamestonkTerminal/pull/350)
**NEXT**
diff --git a/gamestonk_terminal/README.md b/gamestonk_terminal/README.md
index 2b6d79c7b2e..e41212591eb 100644
--- a/gamestonk_terminal/README.md
+++ b/gamestonk_terminal/README.md
@@ -79,6 +79,7 @@ Command|Description|Source
`valuation` |valuation of sectors, industry, country |[Finviz](https://finviz.com)
`performance` |performance of sectors, industry, country |[Finviz](https://finviz.com)
`spectrum` |spectrum of sectors, industry, country |[Finviz](https://finviz.com)
+`ratings ` |top ratings updates |[MarketBeat](https://marketbeat.com)
@@ -351,4 +352,4 @@ technical |technical (e.g. Beta, SMA50, 52W Low, RSI, Change)
signals |view filter signals (e.g. -s top_gainers)
-
\ No newline at end of file
+
diff --git a/gamestonk_terminal/discovery/README.md b/gamestonk_terminal/discovery/README.md
index 517d142d334..d1920d1975d 100644
--- a/gamestonk_terminal/discovery/README.md
+++ b/gamestonk_terminal/discovery/README.md
@@ -32,6 +32,8 @@ This menu aims to discover new stocks, and the usage of the following commands a
* performance of sectors, industry, country [Finviz]
* [spectrum](#spectrum)
* spectrum of sectors, industry, country [Finviz]
+* [ratings](#ratings)
+ * top ratings updates [MarketBeat]
## map
@@ -218,3 +220,18 @@ Spectrum of sectors, industry, country. [Source: Finviz]
* -g : Data group (sector, industry or country). Default: Sector.

+
+
+## ratings
+
+```
+usage: ratings [-t N_THRESHOLD]
+```
+
+Top ratings updates. [Source: MarketBeat]
+
+* -t : Minimum threshold in percentage change between current and target price to show ratings. Default: 100.
+
+
+
+
diff --git a/gamestonk_terminal/discovery/disc_controller.py b/gamestonk_terminal/discovery/disc_controller.py
index 07bb33a3302..6125a377773 100644
--- a/gamestonk_terminal/discovery/disc_controller.py
+++ b/gamestonk_terminal/discovery/disc_controller.py
@@ -20,6 +20,7 @@ from gamestonk_terminal.discovery import (
spachero_view,
unusual_whales_view,
yahoo_finance_view,
+ marketbeat_view,
)
@@ -46,6 +47,7 @@ class DiscoveryController:
"valuation",
"performance",
"spectrum",
+ "ratings",
]
def __init__(self):
@@ -87,6 +89,7 @@ class DiscoveryController:
print(" valuation valuation of sectors, industry, country [Finviz]")
print(" performance performance of sectors, industry, country [Finviz]")
print(" spectrum spectrum of sectors, industry, country [Finviz]")
+ print(" ratings top ratings updates [MarketBeat]")
print("")
def switch(self, an_input: str):
@@ -187,6 +190,10 @@ class DiscoveryController:
other_args, "spectrum"
)
+ def call_ratings(self, other_args: List[str]):
+ """Process ratings command"""
+ marketbeat_view.ratings_view(other_args)
+
def menu():
"""Discovery Menu"""
diff --git a/gamestonk_terminal/discovery/marketbeat_model.py b/gamestonk_terminal/discovery/marketbeat_model.py
new file mode 100755
index 00000000000..b8bd10241e4
--- /dev/null
+++ b/gamestonk_terminal/discovery/marketbeat_model.py
@@ -0,0 +1,93 @@
+""" MarketBeat Model """
+__docformat__ = "numpy"
+
+from typing import List
+import requests
+from bs4 import BeautifulSoup
+
+from gamestonk_terminal.helper_funcs import get_user_agent
+
+
+def get_ratings_html(url_ratings: str) -> str:
+ """Wraps HTTP requests.get for testibility
+
+ Parameters
+ ----------
+ url_ratings : str
+ Ratings URL
+
+ Returns
+ -------
+ str
+ HTML page of ratings
+ """
+ ratings_html = requests.get(
+ url_ratings, headers={"User-Agent": get_user_agent()}
+ ).text
+
+ return ratings_html
+
+
+def get_ratings() -> List[dict]:
+ """Returns a list of ratings
+
+ Parameters
+ ----------
+ None
+
+ Returns
+ -------
+ list
+ Ratings list
+ """
+
+ ratings = list()
+ url_ratings = "https://www.marketbeat.com/ratings/"
+
+ text_soup_ratings = BeautifulSoup(
+ get_ratings_html(url_ratings),
+ "lxml",
+ )
+
+ for stock_rows in text_soup_ratings.findAll("tr"):
+ tds = stock_rows.findAll("td")
+ if len(tds) == 8:
+ rating = {}
+ # company
+ td = tds[0]
+ company = td.find("div", {"class": "title-area"}).text
+ ticker = td.find("div", {"class": "ticker-area"}).text
+ rating["company"] = company
+ rating["ticker"] = ticker
+ # action
+ td = tds[1]
+ action = td.text
+ rating["action"] = action
+ # brokerage
+ td = tds[2]
+ brokerage = td.find("a").text
+ rating["brokerage"] = brokerage
+ # current price
+ td = tds[3]
+ if td.find("span"):
+ td.span.extract()
+ current_price = td.text
+ rating["current_price"] = current_price
+ # target price
+ td = tds[4]
+ target_price = td.text
+ rating["target_price"] = target_price
+ # new rating
+ td = tds[5]
+ new_rating = td.text
+ rating["rating"] = new_rating
+ # impact on price
+ td = tds[6]
+ if td.find("a"):
+ impact = td.find("a").text
+ else:
+ impact = td.text
+ rating["impact"] = impact
+ ratings.append(rating)
+
+ return ratings
diff --git a/gamestonk_terminal/discovery/marketbeat_view.py b/gamestonk_terminal/discovery/marketbeat_view.py
new file mode 100644
index 00000000000..13ec048ee5c
--- /dev/null
+++ b/gamestonk_terminal/discovery/marketbeat_view.py
@@ -0,0 +1,116 @@
+""" MarketBeat View """
+__docformat__ = "numpy"
+
+import argparse
+import numpy as np
+import pandas as pd
+from typing import List
+from gamestonk_terminal.helper_funcs import (
+ check_positive,
+ parse_known_args_and_warn,
+)
+
+from gamestonk_terminal.discovery import marketbeat_model
+
+
+def ratings_view(other_args: List[str]):
+ """Prints top ratings updates
+
+ Parameters
+ ----------
+ other_args : List[str]
+ argparse other args - ["-t", "100"]
+ """
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ prog="ratings",
+ description="""Top ratings updates. [Source: MarketBeat]""",
+ )
+ parser.add_argument(
+ "-t",
+ "--threshold",
+ action="store",
+ dest="n_threshold",
+ type=check_positive,
+ default=100,
+ help="Minimum threshold in percentage change between current and target price to show ratings",
+ )
+
+ if other_args:
+ if "-" not in other_args[0]:
+ other_args.insert(0, "-t")
+
+ ns_parser = parse_known_args_and_warn(parser, other_args)
+ if not ns_parser:
+ return
+
+ try:
+ ratings = marketbeat_model.get_ratings()
+
+ df_ratings = pd.DataFrame(ratings)
+ df_ratings = df_ratings[df_ratings["target_price"] != ""]
+ df_ratings["old_target"] = df_ratings["target_price"].apply(
+ lambda x: x.split(" ➝ ")[0] if "➝" in x else ""
+ )
+ df_ratings["new_target"] = df_ratings["target_price"].apply(
+ lambda x: x.split(" ➝ ")[1] if "➝" in x else x
+ )
+
+ df_ratings["clean_current_price"] = df_ratings["current_price"].apply(
+ lambda x: "".join([c if c.isdigit() or c == "." else "" for c in str(x)])
+ )
+ df_ratings["clean_current_price"] = df_ratings["clean_current_price"].astype(
+ float
+ )
+ df_ratings["clean_target_price"] = df_ratings["new_target"].apply(
+ lambda x: "".join([c if c.isdigit() or c == "." else "" for c in str(x)])
+ )
+ df_ratings["clean_target_price"] = df_ratings["clean_target_price"].astype(
+ float
+ )
+
+ df_ratings["pct_increase"] = (
+ 100
+ * (df_ratings["clean_target_price"] - df_ratings["clean_current_price"])
+ / df_ratings["clean_current_price"]
+ )
+ df_ratings["pct_increase"] = df_ratings["pct_increase"].round(2)
+
+ df_ratings["pct_abs"] = np.abs(df_ratings["pct_increase"])
+ df_ratings["pct_abs"] = df_ratings["pct_abs"].round(2)
+
+ df_ratings = df_ratings.sort_values("pct_abs", ascending=False)
+
+ df_ratings_top = df_ratings[df_ratings["pct_abs"] > ns_parser.n_threshold][
+ [
+ "ticker",
+ "brokerage",
+ "rating",
+ "current_price",
+ "old_target",
+ "new_target",
+ "pct_increase",
+ ]
+ ]
+
+ df_ratings_top["pct_increase"] = df_ratings_top["pct_increase"].apply(
+ lambda x: str(x) + " %"
+ )
+
+ print(
+ df_ratings_top.rename(
+ columns={
+ "ticker": "Ticker",
+ "brokerage": "Brokerage",
+ "rating": "Rating",
+ "current_price": "Current Price",
+ "old_target": "Old Target",
+ "new_target": "New Target",
+ "pct_increase": "Percentage Increase",
+ }
+ ).to_string(index=False)
+ )
+
+ print("")
+ except Exception as e:
+ print(e, "\n")