"""Reddit View""" __docformat__ = "numpy" import logging import os import warnings from datetime import datetime from typing import Dict, List, Optional import finviz import matplotlib.pyplot as plt import pandas as pd import praw import seaborn as sns from openbb_terminal.common.behavioural_analysis import reddit_model from openbb_terminal.config_terminal import theme from openbb_terminal.config_plot import PLOT_DPI from openbb_terminal.decorators import check_api_key from openbb_terminal.decorators import log_start_end from openbb_terminal.helper_funcs import ( export_data, plot_autoscale, print_rich_table, is_valid_axes_count, ) from openbb_terminal.rich_config import console # pylint: disable=R0913 logger = logging.getLogger(__name__) @log_start_end(log=logger) @check_api_key( [ "API_REDDIT_CLIENT_ID", "API_REDDIT_CLIENT_SECRET", "API_REDDIT_USERNAME", "API_REDDIT_USER_AGENT", "API_REDDIT_PASSWORD", ] ) def print_and_record_reddit_post( submissions_dict: Dict, submission: praw.models.reddit.submission.Submission ): """Prints reddit submission. Parameters ---------- submissions_dict : Dict Dictionary for storing reddit post information submission : praw.models.reddit.submission.Submission Submission to show """ # Refactor data s_datetime = datetime.utcfromtimestamp(submission.created_utc).strftime( "%Y-%m-%d %H:%M:%S" ) s_link = f"https://old.reddit.com{submission.permalink}" s_all_awards = "".join( f"{award['count']} {award['name']}\n" for award in submission.all_awardings ) s_all_awards = s_all_awards[:-2] # Create dictionary with data to construct dataframe allows to save data submissions_dict[submission.id] = { "created_utc": s_datetime, "subreddit": submission.subreddit, "link_flair_text": submission.link_flair_text, "title": submission.title, "score": submission.score, "link": s_link, "num_comments": submission.num_comments, "upvote_ratio": submission.upvote_ratio, "awards": s_all_awards, } # Print post data collected so far console.print(f"[yellow]{s_datetime}[/yellow] - {submission.title}") console.print(f"[blue]{s_link}[/blue]\n") columns = ["Subreddit", "Flair", "Score", "# Comments", "Upvote %", "Awards"] data = [ submission.subreddit, submission.link_flair_text, submission.score, submission.num_comments, f"{round(100 * submission.upvote_ratio)}%", s_all_awards, ] df = pd.DataFrame([data], columns=columns) print_rich_table( df, headers=list(df.columns), show_index=False, title="Reddit Submission" ) @log_start_end(log=logger) @check_api_key( [ "API_REDDIT_CLIENT_ID", "API_REDDIT_CLIENT_SECRET", "API_REDDIT_USERNAME", "API_REDDIT_USER_AGENT", "API_REDDIT_PASSWORD", ] ) def print_reddit_post(sub: tuple): """Prints reddit submission. Parameters ---------- sub : tuple Row from submissions dataframe """ sub_list = list(sub[1]) date = sub_list[0] title = sub_list[3] link = sub_list[-1] console.print(f"[yellow]{date}[/yellow] - {title}") console.print(f"[blue]{link}[/blue]\n") columns = [ "Subreddit", "Flair", "Score", "# Comments", "Upvote %", "Awards", ] print_rich_table( pd.DataFrame(sub[1][columns]).T, headers=columns, show_index=False, title="Reddit Submission", ) @log_start_end(log=logger) @check_api_key( [ "API_REDDIT_CLIENT_ID", "API_REDDIT_CLIENT_SECRET", "API_REDDIT_USERNAME", "API_REDDIT_USER_AGENT", "API_REDDIT_PASSWORD", ] ) def display_watchlist(limit: int = 5): """Prints other users watchlist. [Source: Reddit]. Parameters ---------- limit: int Maximum number of submissions to look at """ subs, d_watchlist_tickers, n_flair_posts_found = reddit_model.get_watchlists(limit) if subs: for sub in subs: print_and_record_reddit_post({}, sub) console.print("") if n_flair_posts_found > 0: lt_watchlist_sorted = sorted( d_watchlist_tickers.items(), key=lambda item: item[1], reverse=True ) s_watchlist_tickers = "" n_tickers = 0 for t_ticker in lt_watchlist_sorted: try: # If try doesn't trigger exception, it means that this stock exists on finviz # thus we can print it. finviz.get_stock(t_ticker[0]) if int(t_ticker[1]) > 1: s_watchlist_tickers += f"{t_ticker[1]} {t_ticker[0]}, " n_tickers += 1 except Exception: # console.print(e, "\n") pass if n_tickers: console.print( "The following stock tickers have been mentioned more than once across the previous watchlists:" ) console.print(s_watchlist_tickers[:-2] + "\n") @log_start_end(log=logger) @check_api_key( [ "API_REDDIT_CLIENT_ID", "API_REDDIT_CLIENT_SECRET", "API_REDDIT_USERNAME", "API_REDDIT_USER_AGENT", "API_REDDIT_PASSWORD", ] ) def display_popular_tickers( limit: int = 10, post_limit: int = 50, subreddits: str = "", export: str = "" ): """Prints table showing latest popular tickers. [Source: Reddit]. Parameters ---------- limit : int Number of top tickers to get post_limit : int How many posts to analyze in each subreddit subreddits : str, optional String of comma separated subreddits. export : str Format to export dataframe """ popular_tickers_df = reddit_model.get_popular_tickers(limit, post_limit, subreddits) if not popular_tickers_df.empty: print_rich_table( popular_tickers_df, headers=list(popular_tickers_df.columns), show_index=False, title=f"The following TOP {limit} tickers have been mentioned", ) else: console.print("No tickers found") export_data( export, os.path.dirname(os.path.abspath(__file__)), "popular", popular_tickers_df, ) @log_start_end(log=logger) @check_api_key( [ "API_REDDIT_CLIENT_ID", "API_REDDIT_CLIENT_SECRET", "API_REDDIT_USERNAME", "API_REDDIT_USER_AGENT", "API_REDDIT_PASSWORD", ] ) def display_spac_community(limit: int = 10, popular: bool = False): """Prints tickers mentioned in r/SPACs [Source: Reddit]. Parameters ---------- limit: int Number of posts to look through popular: bool Search by popular instead of new """ subs, d_watchlist_tickers = reddit_model.get_spac_community(limit, popular) if not subs.empty: for sub in subs.iterrows(): print_reddit_post(sub) console.print("") if d_watchlist_tickers: lt_watchlist_sorted = sorted( d_watchlist_tickers.items(), key=lambda item: item[1], reverse=True ) s_watchlist_tickers = "" n_tickers = 0 for t_ticker in lt_watchlist_sorted: try: # If try doesn't trigger exception, it means that this stock exists on finviz # thus we can print it. finviz.get_stock(t_ticker[0]) if int(t_ticker[1]) > 1: s_watchlist_tickers += f"{t_ticker[1]} {t_ticker[0]}, " n_tickers += 1 except Exception: # console.print(e, "\n") pass if n_tickers: console.print( "The following stock tickers have been mentioned more than once across the previous SPACs:" ) console.print(s_watchlist_tickers[:-2]) @log_start_end(log=logger) @check_api_key( [ "API_REDDIT_CLIENT_ID", "API_REDDIT_CLIENT_SECRET", "API_REDDIT_USERNAME", "API_REDDIT_USER_AGENT", "API_REDDIT_PASSWORD", ] ) def display_spac(limit: int = 5): """Prints posts containing 'spac' in top communities. Parameters ---------- limit: int Number of posts to get from each subreddit """ warnings.filterwarnings("ignore") # To avoid printing the warning subs, d_watchlist_tickers, n_flair_posts_found = reddit_model.get_spac(limit) if not subs.empty: for sub in subs.iterrows(): print_reddit_post(sub) console.print("") if n_flair_posts_found > 0: lt_watchlist_sorted = sorted( d_watchlist_tickers.items(), key=lambda item: item[1], reverse=True ) s_watchlist_tickers = "" n_tickers = 0 for t_ticker in lt_watchlist_sorted: try: # If try doesn't trigger exception, it means that this stock exists on finviz # thus we can print it. finviz.get_stock(t_ticker[0]) if int(t_ticker[1]) > 1: s_watchlist_tickers += f"{t_ticker[1]} {t_ticker[0]}, " n_tickers += 1 except Exception: pass if n_tickers: console.print( "The following stock tickers have been mentioned more than once across the previous SPACs:" ) console.print(s_watchlist_tickers[:-2]) @log_start_end(log=logger) @check_api_key( [ "API_REDDIT_CLIENT_ID", "API_REDDIT_CLIENT_SECRET", "API_REDDIT_USERNAME", "API_REDDIT_USER_AGENT", "API_REDDIT_PASSWORD", ] ) def display_wsb_community(limit: int = 10, new: bool = False): """Prints WSB posts. Parameters ---------- limit : int, optional Number of posts to look at, by default 10 new : bool, optional Flag to sort by new instead of hot, by default False """ subs = reddit_model.get_wsb_community(limit, new) if not subs.empty: for sub in subs.iterrows(): print_reddit_post(sub) console.print("") @log_start_end(log=logger) @check_api_key( [ "API_REDDIT_CLIENT_ID", "API_REDDIT_CLIENT_SECRET", "API_REDDIT_USERNAME", "API_REDDIT_USER_AGENT", "API_REDDIT_PASSWORD", ] ) def display_due_diligence( symbol: str, limit: int = 10, n_days: int = 3, show_all_flairs: bool = False ): """Prints Reddit due diligence data for a given ticker. Parameters ---------- symbol: str Stock ticker symbol limit: int Number of posts to get n_days: int Number of days back to get posts show_all_flairs: bool Search through all flairs (apart from Yolo and Meme) """ subs = reddit_model.get_due_dilligence(symbol, limit, n_days, show_all_flairs) if not subs.empty: for sub in subs.iterrows(): print_reddit_post(sub) console.print("") else: console.print(f"No DD posts found for {symbol}\n") @log_start_end(log=logger) @check_api_key( [ "API_REDDIT_CLIENT_ID", "API_REDDIT_CLIENT_SECRET", "API_REDDIT_USERNAME", "API_REDDIT_USER_AGENT", "API_REDDIT_PASSWORD", ] ) def display_redditsent( symbol: str, sortby: str = "relevance", limit: int = 100, graphic: bool = False, time_frame: str = "week", full_search: bool = True, subreddits: str = "all", display: bool = False, export: str = "", external_axes: Optional[List[plt.Axes]] = None, ): """Plots Reddit sentiment about a search term. Prints table showing if display is True. Parameters ---------- symbol: str The ticker symbol being search for in Reddit sortby: str Type of search limit: str Number of posts to get at most graphic: bool Displays box and whisker plot time_frame: str Time frame for search full_search: bool Enable comprehensive search for ticker subreddits: str Comma-separated list of subreddits display: bool Enable printing of raw sentiment values for each post export: str Format to export data external_axes: Optional[List[plt.Axes]] If supplied, expect 1 external axis """ df, polarity_scores, avg_polarity = reddit_model.get_posts_about( symbol, limit, sortby, time_frame, full_search, subreddits ) if df.empty: console.print(f"No posts for {symbol} found") return if display: print_rich_table(df=df) if graphic: if not external_axes: _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) elif is_valid_axes_count(external_axes, 1): (ax,) = external_axes else: return sns.boxplot(x=polarity_scores, ax=ax) ax.set_title(f"Sentiment Score of {symbol}") ax.set_xlabel("Sentiment Score") if not external_axes: theme.visualize_output() console.print(f"Sentiment Analysis for {symbol} is {avg_polarity}\n") export_data( export, os.path.dirname(os.path.abspath(__file__)), "polarity_scores", df, )