diff options
author | Adi Sai <adithya.sairam1@gmail.com> | 2023-05-26 12:18:08 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-05-26 16:18:08 +0000 |
commit | fcb78d71f2d1304af4fa67e2c640ab8a8072c254 (patch) | |
tree | 76c4088faaa7592f8f6f13d97150853a980e374a | |
parent | 58391c1edf9e159a8d19dd463c934256c35752e5 (diff) |
Feature/ultima top headlines (#5061)
* initial integration of top headlines
* top headlines formatting
* re-ran tests
* fixing typing issues
* passed test
* fix ruff
* guard top headlines call + pass tests
* Minor cosmetic changes
---------
Co-authored-by: James Maslek <jmaslek11@gmail.com>
21 files changed, 30327 insertions, 12634 deletions
diff --git a/openbb_terminal/common/ultima_newsmonitor_model.py b/openbb_terminal/common/ultima_newsmonitor_model.py index 676880f819b..b4e3782ac0d 100644 --- a/openbb_terminal/common/ultima_newsmonitor_model.py +++ b/openbb_terminal/common/ultima_newsmonitor_model.py @@ -180,3 +180,54 @@ def get_company_info(ticker: str) -> dict: return data.json() console.print("[red]Ticker not supported. Unable to retrieve data\n[/red]") return {} + + +@log_start_end(log=logger) +def get_top_headlines(ticker: str) -> dict: + """Get top headlines for a given ticker. [Source: Ultima Insights] + + Parameters + ---------- + ticker : str + ticker to get top headlines for + + Returns + ------- + top_headlines: dict + dictionary of top headlines + """ + + # Necessary for installer so that it can locate the correct certificates for + # API calls and https + # https://stackoverflow.com/questions/27835619/urllib-and-ssl-certificate-verify-failed-error/73270162#73270162 + os.environ["REQUESTS_CA_BUNDLE"] = certifi.where() + os.environ["SSL_CERT_FILE"] = certifi.where() + + current_user = get_current_user() + if current_user.credentials.API_ULTIMA_KEY == NO_API_KEY: + auth_header = None + else: + auth_header = { + "Authorization": f"Bearer {current_user.credentials.API_ULTIMA_KEY}" + } + + if ticker in supported_terms(): + if auth_header: + data = request(f"{BASE_URL}/getTopHeadlines/{ticker}", headers=auth_header) + else: + data = request(f"{BASE_URL}/getTopHeadlines/{ticker}") + if ( + hasattr(data, "status") and data.status_code == 429 + ): # If data request failed + console.print( + "[red]Too many requests. Please get an API Key from https://www.ultimainsights.ai/[/red]" + ) + return {} + if ( + hasattr(data, "status") and data.status_code != 200 + ): # If data request failed + console.print("[red]Status code not 200. Unable to retrieve data\n[/red]") + return {} + return data.json() + console.print("[red]Ticker not supported. Unable to retrieve data\n[/red]") + return {} diff --git a/openbb_terminal/common/ultima_newsmonitor_view.py b/openbb_terminal/common/ultima_newsmonitor_view.py index ede970f91e9..539670e5bb6 100644 --- a/openbb_terminal/common/ultima_newsmonitor_view.py +++ b/openbb_terminal/common/ultima_newsmonitor_view.py @@ -1,6 +1,7 @@ """ News View """ __docformat__ = "numpy" +import datetime as dt import logging import os from typing import Optional @@ -75,9 +76,29 @@ def display_news( console.print(row["URL"] + "\n") console.print("------------------------") + top_headlines = ultima_newsmonitor_model.get_top_headlines(term)["summary"] + if "Ultima Insights was unable to identify" in top_headlines: + console.print( + f"[red]Most Relevant Articles for {term} - {dt.datetime.now().strftime('%Y-%m-%d')}\n{top_headlines}[/red]" + ) + else: + split = top_headlines.split("\n") + header = split[0] + if "Most" not in header: + header = f'Most Relevant Articles for {term} - {dt.datetime.now().strftime("%Y-%m-%d")}' + console.print(f"[purple][bold]{header}[/bold][/purple]\n") + for idx, row in enumerate(split[1:]): + if len(row) > 0: + inner_split = row.split(" - ") + risk = inner_split[0] + description = " - ".join(inner_split[1:]) + console.print(f"[green]{risk}[/green] (\x1B[3m{description}\x1B[0m)") + if idx < len(split[1:]) - 1: + console.print() + console.print("------------------------") articles = ultima_newsmonitor_model.get_news(term, sort) articles = articles.head(limit).sort_values(by="relevancyScore", ascending=False) - # console.print(f"News Powered by [purple]Ultima Insights[/purple].\nFor more info: https://www.ultimainsights.ai\n") + console.print(f"\n[purple][bold]News for {company_name}:[/bold][/purple]\n\n") for _, row in articles.iterrows(): console.print( f"> {row['articlePublishedDate']} - {row['articleHeadline']}\n" diff --git a/openbb_terminal/stocks/stocks_controller.py b/openbb_terminal/stocks/stocks_controller.py index adc11fdf306..570257e9a96 100644 --- a/openbb_terminal/stocks/stocks_controller.py +++ b/openbb_terminal/stocks/stocks_controller.py @@ -582,7 +582,7 @@ class StocksController(StockBaseController): query = str(self.ticker).upper() if query not in ultima_newsmonitor_view.supported_terms(): console.print( - "[red]Ticker not supported by Ultima Insights News Monitor[/red]" + "[red]Ticker not supported by Ultima Insights News Monitor. Falling back to default.\n[/red]" ) feedparser_view.display_news( term=query, diff --git a/openbb_terminal/stocks/stocks_model.py b/openbb_terminal/stocks/stocks_model.py index 2ea2a7dd96f..9c86b03aa0f 100644 --- a/openbb_terminal/stocks/stocks_model.py +++ b/openbb_terminal/stocks/stocks_model.py @@ -1,6 +1,7 @@ import logging import os from datetime import datetime +from typing import List from urllib.error import HTTPError import fundamentalanalysis as fa # Financial Modeling Prep @@ -286,7 +287,7 @@ def load_stock_polygon( @log_start_end(log=logger) @check_api_key(["API_KEY_FINANCIALMODELINGPREP"]) -def get_quote(symbols: list[str]) -> pd.DataFrame: +def get_quote(symbols: List[str]) -> pd.DataFrame: """Gets ticker quote from FMP Parameters diff --git a/openbb_terminal/stocks/stocks_view.py b/openbb_terminal/stocks/stocks_view.py index 0a34065cde5..cd54ada2b4f 100644 --- a/openbb_terminal/stocks/stocks_view.py +++ b/openbb_terminal/stocks/stocks_view.py @@ -1,6 +1,6 @@ import logging import os -from typing import Optional +from typing import List, Optional from openbb_terminal.decorators import check_api_key, log_start_end from openbb_terminal.helper_funcs import export_data, print_rich_table @@ -13,13 +13,13 @@ logger = logging.getLogger(__name__) @log_start_end(log=logger) @check_api_key(["API_KEY_FINANCIALMODELINGPREP"]) def display_quote( - symbols: list[str], export: str = "", sheet_name: Optional[str] = None + symbols: List[str], export: str = "", sheet_name: Optional[str] = None ): """Financial Modeling Prep ticker(s) quote. Parameters ---------- - symbols : list[str] + symbols : List[str] A list of ticker symbols. sheet_name: str Optionally specify the name of the sheet the data is exported to. diff --git a/tests/openbb_terminal/common/cassettes/test_ultima_newsmonitor_model/test_get_news[AAPL].yaml b/tests/openbb_terminal/common/cassettes/test_ultima_newsmonitor_model/test_get_news[AAPL].yaml index 9831593579a..6ee942e016a 100644 --- a/tests/openbb_terminal/common/cassettes/test_ultima_newsmonitor_model/test_get_news[AAPL].yaml +++ b/tests/openbb_terminal/common/cassettes/test_ultima_newsmonitor_model/test_get_news[AAPL].yaml @@ -9,7 +9,7 @@ interactions: Connection: - keep-alive User-Agent: - - Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:86.0) Gecko/20100101 Firefox/86.0 + - Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.10; rv:83.0) Gecko/20100101 Firefox/83.0 method: GET uri: https://api.ultimainsights.ai/v1/supportedTickers response: @@ -58,7 +58,7 @@ interactions: Content-Type: - text/html; charset=utf-8 Date: - - Sat, 08 Apr 2023 04:00:39 GMT + - Thu, 25 May 2023 17:35:46 GMT Server: - nginx/1.18.0 (Ubuntu) Transfer-Encoding: @@ -78,2376 +78,4329 @@ interactions: Connection: - keep-alive User-Agent: - - Mozilla/5.0 (Windows NT 10.0; WOW64; rv:86.0) Gecko/20100101 Firefox/86.0 + - Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.10; rv:83.0) Gecko/20100101 Firefox/83.0 method: GET uri: https://api.ultimainsights.ai/v1/getNewsArticles/AAPL response: body: string: !!binary | - H4sIAAAAAAAEA+y9jXPbVpbt+6/gdaVf91QZEr8/PPXqlmI7ibvtxGPZydz3pqoLIiEKMUlwCFKy - cu/9399v7XMOAFKQbHc63U6CqpmORYLAwfnYZ5+11177//tff0i2u2y2TJ/P//A46nYHg8FkNJxM - Rv1BZ/ooCt++yXbLlAv+8DLnH7fRk+0+jZ4XBf97vkt26Spd76J8Hb3MZu+il8m2+FP0p7fry3y7 - 26/5+k/Ri+Sm2Ge7P1S3/CZN5stsbXd9c5VGF8l6HiWz3T5ZLm+jH/N3KX9e5PtdtLvKCt38zU22 - 26VbruQh2TrqdXqd6OYqXUfbZLPhi78m69s0+iEtdlGyXuf79Yx7cOubZL3jX7s8UoPfrrPrdFsk - Sy6aR+f5+vaRvrq5ymZX0TfJze4q367T6Js0W1ztipOTk1qj375+oV642u02xePT02W+n99k2/Rk - lq9OV9Y18YyuiYvQKfGKHolX9Ei8dF1wWrvdq/3FMiuu0q1uWr9ZwzXzp3SkruO1+3FnEHdGUa/7 - uDN+3Bnp8m1WvHvCJYt8e6vLXqQLXvE1n4Zvn6bFbJttdlm+1gVnm82SQVzPTqLLZJYW0dJ+oftE - c/qJPtnkOwY24z4b7swQZ+vLbbZeuPH2L1Q8imb55nar7rrvAgYg2233DEy2vmZ8skWiZvBTjUG+ - owui2TLJVnzimrHZ5rM0nfMwf9EiZ9TWNtEO72Hjo1Y/WzJftjR0fv+bMpPS95u8cNOh9saX23z1 - Wb9lxCIp0kgvWkSzZB0l/DONrN30X7Tnj/wy2qWzq3W+zBe3UU6f5sx8Lc2bNW98cUt3u87esOyZ - 91xiN0hms33hhkQ30XDFzOlNyrCxWqLNlpWZMUlOIjdt6MdkWWhBXfyYzlijOc9aMaHe24jOrpL1 - grGLNEfsk2262C/9E27y7ZJ1M08f+VWn11liDw7n3DJLLrIlDUiZAtl6tk0TjdssL3YHEyeZaz2n - UXp5SVPMVKhDntAeDMKfiuhiX2BoiqKaKe93jVPkKuHiFINSe60kuqan0x39eVlbIq5Jy71m6M+f - N5i6e1bICSsUS9ftPvI9f0MbMWM2mljjfJsvk+hlbj11G2Fym1rj1pnZzG6v6U7fZ9i83X/e//si - 3e0w1wu74ov+aBKtsuWSNRwa2G+67XmyKvb+Rw0mpN6sQf331Qp3L6rxfLbf5puUmcLArrKi4NnW - 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 |