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

Feature: Ultima Insights NewsMonitor Integration #4685

Merged
merged 41 commits into from
Apr 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
489cc46
fix typing issue that prevents running
AdiSai Feb 26, 2023
fb85fc6
news monitor integration for news
AdiSai Feb 26, 2023
d8043dd
UX improvements with news monitor integration
AdiSai Feb 27, 2023
f86537a
italicize riskExtDesc
AdiSai Feb 28, 2023
8bb25c7
minor cosmetic changes
AdiSai Feb 28, 2023
2e967e3
minor cosmetic changes
AdiSai Feb 28, 2023
a46941b
minor cosmetic changes
AdiSai Feb 28, 2023
fa89951
Update ultima_newsmonitor_view.py
ovidysus0 Mar 9, 2023
38103ad
resolving merge conflicts?
AdiSai Mar 23, 2023
e5c5686
minor fixes to launch properly
AdiSai Mar 23, 2023
2ac5e3e
Merge branch 'OpenBB-finance:develop' into develop
AdiSai Mar 29, 2023
c43bdf6
setting Ultima API key
AdiSai Mar 30, 2023
7d266cd
Ultima NewsMonitor integration into terminal done
AdiSai Mar 30, 2023
cab188f
integration of the commands is done
AdiSai Mar 30, 2023
802240b
add Ultima to SDK trailmap, but SDK generation failed on M1 Mac
AdiSai Mar 30, 2023
168e4b9
add tests
AdiSai Mar 30, 2023
585f2ad
add unit tests
AdiSai Mar 30, 2023
0cc40e5
Merge branch 'OpenBB-finance:develop' into develop
AdiSai Mar 30, 2023
2491958
Linting and sdk
jmaslek Mar 31, 2023
d51fd17
display message if not supported by Ultima News Monitor
AdiSai Mar 31, 2023
ca758f5
Update api-keys.md
AdiSai Mar 31, 2023
59d63bb
fix stock controller problem + remove source filtering from Ultima
AdiSai Apr 1, 2023
9680fcb
Merge branch 'develop' of github.com:Ultima-Insights/OpenBBTerminal i…
AdiSai Apr 1, 2023
894c388
remove source filtering from Ultima
AdiSai Apr 1, 2023
0ee0dd8
add report issue message
AdiSai Apr 1, 2023
dd4376c
add some error handling in the view to ensure unsupported companies d…
AdiSai Apr 3, 2023
9ab1f0a
remove powered by Ultima Insights from console output
AdiSai Apr 3, 2023
db5baf6
ran black formatter to fix linting issues
AdiSai Apr 4, 2023
a504955
fix ruff issues
AdiSai Apr 4, 2023
009864f
fix pylint issues
AdiSai Apr 4, 2023
52753d0
Merge branch 'develop' into feature/UltimaNewsMonitor
andrewkenreich Apr 4, 2023
1e21d9f
lint fix
andrewkenreich Apr 4, 2023
9a7275f
fixed linting issue and changed keys in the Feedparser model
AdiSai Apr 4, 2023
e11864f
get all tests to pass
AdiSai Apr 4, 2023
2ba57a1
proper testing with --record-mode=rewrite
AdiSai Apr 4, 2023
ba9bda8
re-run tests for updated VCRs
AdiSai Apr 5, 2023
e75a538
redid black formatting
AdiSai Apr 5, 2023
ec924e2
re-run test_print_help in stocks controller
AdiSai Apr 5, 2023
5b2d9f0
make breaking news output consistent and re-run tests
AdiSai Apr 5, 2023
e759865
remove unused import time
AdiSai Apr 5, 2023
5235ad9
reran tests with --rewrite-expected
AdiSai Apr 5, 2023
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
12 changes: 10 additions & 2 deletions openbb_terminal/common/feedparser_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@


def get_news(
term: str = "", sources: str = "", sort: str = "published", limit: int = 10
term: str = "",
sources: str = "",
sort: str = "published",
limit: int = 10,
display_message: bool = True,
) -> pd.DataFrame:
"""Get news for a given term and source. [Source: Feedparser]

Expand All @@ -27,6 +31,8 @@ def get_news(
the column to sort by
limit : int
number of articles to display
display_message: bool
whether to display a message to the user

Returns
-------
Expand All @@ -45,6 +51,8 @@ def get_news(
os.environ["SSL_CERT_FILE"] = certifi.where()

have_data = False
if display_message:
console.print("[yellow]Fetching data. Please be patient\n[/yellow]")
n = 0

while not have_data:
Expand All @@ -68,7 +76,7 @@ def get_news(
elif n == 60: # Breaking if 60 successful requests return no data
console.print("[red]Timeout occurred. Please try again\n[/red]")
break
n = n + 1
n += 1

elif hasattr(data, "status") and data.status != 200: # If data request failed
console.print("[red]Status code not 200. Unable to retrieve data\n[/red]")
Expand Down
171 changes: 171 additions & 0 deletions openbb_terminal/common/ultima_newsmonitor_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
""" Ultima Insights News Monitor Model """
__docformat__ = "numpy"

import logging
import os
from urllib.parse import quote

import certifi
import pandas as pd

from openbb_terminal.core.session.current_user import get_current_user
from openbb_terminal.decorators import check_api_key, log_start_end
from openbb_terminal.helper_funcs import request
from openbb_terminal.rich_config import console

logger = logging.getLogger(__name__)


base_url = "https://api.ultimainsights.ai/v1"


@log_start_end(log=logger)
@check_api_key(["API_ULTIMAINSIGHTS_KEY"])
def get_news(term: str = "", sort: str = "articlePublishedDate") -> pd.DataFrame:
"""Get news for a given term and source. [Source: Ultima Insights News Monitor]

Parameters
----------
term : str
term to search on the news articles
sort: str
the column to sort by

Returns
-------
articles: pd.DataFrame
term to search on the news articles

Examples
--------
>>> from openbb_terminal.sdk import openbb
>>> openbb.news()
"""
# 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()
auth_header = {
"Authorization": f"Bearer {current_user.credentials.API_ULTIMAINSIGHTS_KEY}"
}

have_data = False
limit = 0

while not have_data:
if term:
term = quote(term)
term = term.upper()
term = term.strip()
if term in supported_terms():
data = request(
f"{base_url}/getNewsArticles/{term}", headers=auth_header
)
else:
console.print(
"[red]Ticker not supported. Unable to retrieve data\n[/red]"
)
return pd.DataFrame()
else:
console.print("[red]No term specified. Unable to retrieve data\n[/red]")
return pd.DataFrame()

if (
hasattr(data, "status") and data.status_code == 200
): # Checking if data has status attribute and if data request succeeded
if data.content:
have_data = True
elif limit == 60: # Breaking if 60 successful requests return no data
console.print("[red]Timeout occurred. Please try again\n[/red]")
break
limit = limit + 1

elif (
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]")
break
else:
# console.print("[red]Could not retrieve data\n[/red]")
break
if not data.json():
return pd.DataFrame()
df = pd.DataFrame(
data.json(),
columns=[
"articleHeadline",
"articleURL",
"articlePublishedDate",
"riskCategory",
"riskElaboratedDescription",
"relevancyScore",
],
)
df = df[df["relevancyScore"] < 5]
df = df[df["relevancyScore"] > 3.5]
df["riskElaboratedDescription"] = df["riskElaboratedDescription"].str.replace(
"\n", ""
)
df["riskElaboratedDescription"] = df["riskElaboratedDescription"].str.replace(
"\n", ""
)
df["articlePublishedDate"] = pd.to_datetime(df["articlePublishedDate"])
df = df.sort_values(by=[sort], ascending=False)
return df


@log_start_end(log=logger)
def supported_terms() -> list:
"""Get supported terms for news. [Source: Ultima Insights]

Returns
-------
terms: list
list of supported terms (tickers)

"""

# 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()
data = request(f"{base_url}/supportedTickers")
return list(data.json())


@log_start_end(log=logger)
@check_api_key(["API_ULTIMAINSIGHTS_KEY"])
def get_company_info(ticker: str) -> dict:
"""Get company info for a given ticker. [Source: Ultima Insights]

Parameters
----------
ticker : str
ticker to search for company info

Returns
-------
company_info: dict
dictionary of company info
"""

# 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()
auth_header = {
"Authorization": f"Bearer {current_user.credentials.API_ULTIMAINSIGHTS_KEY}"
}

if ticker in supported_terms():
data = request(f"{base_url}/getCompanyInfo/{ticker}", headers=auth_header)
return data.json()
console.print("[red]Ticker not supported. Unable to retrieve data\n[/red]")
return {}
106 changes: 106 additions & 0 deletions openbb_terminal/common/ultima_newsmonitor_view.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
""" News View """
__docformat__ = "numpy"

import logging
import os
from typing import Optional

import pandas as pd

from openbb_terminal.common import feedparser_model, ultima_newsmonitor_model
from openbb_terminal.decorators import log_start_end
from openbb_terminal.helper_funcs import export_data
from openbb_terminal.rich_config import console

logger = logging.getLogger(__name__)


@log_start_end(log=logger)
def display_news(
term: str,
sources: str = "",
limit: int = 5,
export: str = "",
sheet_name: Optional[str] = None,
sort: str = "articlePublishedDate",
):
"""Plots news for a given term and source. [Source: Ultima Insights]

Parameters
----------
term : str
term to search on the news articles
sources : str
sources to exclusively show news from
limit : int
number of articles to display
export : str
Export dataframe data to csv,json,xlsx file
sort: str
the column to sort by
"""
console.print()
if term not in ultima_newsmonitor_model.supported_terms():
console.print(f"[red]Unsupported ticker: {term}[/red]")
return
company_info = ultima_newsmonitor_model.get_company_info(term)
company_name = company_info.get("companyShortName", term)
# TODO: calling them all together does not work with feedparser
bbg = feedparser_model.get_news(
company_name, sources="bloomberg", sort="published", display_message=False
)
wsj = feedparser_model.get_news(
company_name, sources="wsj", sort="published", display_message=False
)
reuters = feedparser_model.get_news(
company_name, sources="reuters", sort="published", display_message=False
)
cnbc = feedparser_model.get_news(
company_name, sources="cnbc", sort="published", display_message=False
)
breaking_news = pd.concat([bbg, wsj, reuters, cnbc])
if len(breaking_news) > 0:
breaking_news = breaking_news.sort_values(by="Date", ascending=False)
breaking_news["DateNorm"] = (
pd.to_datetime(breaking_news["Date"]).dt.tz_convert(None).dt.normalize()
)
today = pd.to_datetime("today").normalize()
breaking_news = breaking_news[breaking_news["DateNorm"] == today]
if len(breaking_news) > 0:
console.print(
"Uncategorized Breaking News (Bloomberg, Reuters, WSJ, CNBC):"
)
for _, row in breaking_news.head(limit).iterrows():
console.print(f"> {row['Date']} - {row['Description']}")
console.print(row["URL"] + "\n")
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")
for _, row in articles.iterrows():
console.print(
f"> {row['articlePublishedDate']} - {row['articleHeadline']} -> [green]{row['riskCategory']}[/green] "
f"(\x1B[3m{row['riskElaboratedDescription']}\x1B[0m) -> "
f"[purple]relevancy score: {round(row['relevancyScore'], 2) if row['relevancyScore'] < 5 else 5}"
f"/5 Stars[/purple]"
)
console.print(row["articleURL"] + "\n")
console.print(
"[red]To report any issues, please visit https://beta.ultimainsights.ai/my-account/support[/red]\n"
)
console.print()

export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
f"news_{'_'.join(term)}_{'_'.join(sources)}",
articles,
sheet_name,
)


@log_start_end(log=logger)
def supported_terms() -> list:
"""Returns supported terms for news"""
return ultima_newsmonitor_model.supported_terms()
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class StocksController(model.StocksRoot):
Attributes:
`candle`: Show candle plot of loaded ticker.\n
`load`: Load a symbol to perform analysis using the string above as a template.\n
`news`: Get news for a given term and source. [Source: Feedparser]\n
`process_candle`: Process DataFrame into candle style plot.\n
`quote`: Gets ticker quote from FMP\n
`search`: Search selected query for tickers.\n
Expand Down
2 changes: 2 additions & 0 deletions openbb_terminal/core/sdk/models/keys_sdk_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class KeysRoot(Category):
`tokenterminal`: Set Token Terminal key.\n
`tradier`: Set Tradier key\n
`twitter`: Set Twitter key\n
`ultimainsights`: Set Ultima Insights key\n
`walert`: Set Walert key\n
"""

Expand Down Expand Up @@ -82,4 +83,5 @@ def __init__(self):
self.tokenterminal = lib.keys_model.set_tokenterminal_key
self.tradier = lib.keys_model.set_tradier_key
self.twitter = lib.keys_model.set_twitter_key
self.ultimainsights = lib.keys_model.set_ultimainsights_key
self.walert = lib.keys_model.set_walert_key
2 changes: 2 additions & 0 deletions openbb_terminal/core/sdk/models/stocks_sdk_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class StocksRoot(Category):
Attributes:
`candle`: Show candle plot of loaded ticker.\n
`load`: Load a symbol to perform analysis using the string above as a template.\n
`news`: Get news for a given term and source. [Source: Feedparser]\n
`process_candle`: Process DataFrame into candle style plot.\n
`quote`: Gets ticker quote from FMP\n
`search`: Search selected query for tickers.\n
Expand All @@ -23,6 +24,7 @@ def __init__(self):
super().__init__()
self.candle = lib.stocks_helper.display_candle
self.load = lib.stocks_helper.load
self.news = lib.common_ultima_newsmonitor_model.get_news
self.process_candle = lib.stocks_helper.process_candle
self.quote = lib.stocks_model.get_quote
self.search = lib.stocks_helper.search
Expand Down
2 changes: 2 additions & 0 deletions openbb_terminal/core/sdk/sdk_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
common_model,
feedparser_model as common_feedparser_model,
feedparser_view as common_feedparser_view,
ultima_newsmonitor_model as common_ultima_newsmonitor_model,
ultima_newsmonitor_view as common_ultima_newsmonitor_view,
newsapi_model as common_newsapi_model,
newsapi_view as common_newsapi_view,
)
Expand Down
4 changes: 3 additions & 1 deletion openbb_terminal/core/sdk/trail_map.csv
Original file line number Diff line number Diff line change
Expand Up @@ -310,10 +310,11 @@ keys.stocksera,keys_model.set_stocksera_key,
keys.tokenterminal,keys_model.set_tokenterminal_key,
keys.tradier,keys_model.set_tradier_key,
keys.twitter,keys_model.set_twitter_key,
keys.ultimainsights,keys_model.set_ultimainsights_key,
keys.walert,keys_model.set_walert_key,
login,sdk_session.login,
logout,sdk_session.logout,
news,common_feedparser_model.get_news,
news,common_ultima_newsmonitor_model.get_news,
portfolio.alloc.assets,portfolio_model.get_assets_allocation,
portfolio.alloc.countries,portfolio_model.get_countries_allocation,
portfolio.alloc.regions,portfolio_model.get_regions_allocation,
Expand Down Expand Up @@ -524,6 +525,7 @@ stocks.options.unu,stocks_options_fdscanner_model.unusual_options,stocks_options
stocks.options.voi,stocks_options_view.plot_voi,
stocks.options.vol,stocks_options_view.plot_vol,
stocks.options.vsurf,stocks_options_yfinance_model.get_iv_surface,stocks_options_yfinance_view.display_vol_surface
stocks.news,common_ultima_newsmonitor_model.get_news,
stocks.process_candle,stocks_helper.process_candle,
stocks.qa.beta,stocks_qa_beta_model.beta_model,stocks_qa_beta_view.beta_view
stocks.qa.capm,stocks_qa_factors_model.capm_information,
Expand Down
Loading