mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-29 13:32:08 +00:00
526 lines
17 KiB
Python
526 lines
17 KiB
Python
"""Reddit Model"""
|
|
__docformat__ = "numpy"
|
|
|
|
import logging
|
|
from datetime import datetime, timedelta
|
|
from typing import Dict, List, Tuple
|
|
|
|
import finviz
|
|
import pandas as pd
|
|
import praw
|
|
from prawcore.exceptions import ResponseException
|
|
from psaw import PushshiftAPI
|
|
from requests import HTTPError
|
|
|
|
from gamestonk_terminal import config_terminal as cfg
|
|
from gamestonk_terminal.common.behavioural_analysis.reddit_helpers import find_tickers
|
|
from gamestonk_terminal.decorators import log_start_end
|
|
from gamestonk_terminal.rich_config import console
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
l_sub_reddits = [
|
|
"Superstonk",
|
|
"pennystocks",
|
|
"RobinHoodPennyStocks",
|
|
"Daytrading",
|
|
"StockMarket",
|
|
"stocks",
|
|
"investing",
|
|
"wallstreetbets",
|
|
]
|
|
|
|
# pylint:disable=inconsistent-return-statements
|
|
|
|
|
|
@log_start_end(log=logger)
|
|
def get_watchlists(
|
|
n_to_get: int,
|
|
) -> Tuple[List[praw.models.reddit.submission.Submission], Dict, int]:
|
|
"""Get reddit users watchlists [Source: reddit]
|
|
|
|
Parameters
|
|
----------
|
|
n_to_get : int
|
|
Number of posts to look through
|
|
|
|
Returns
|
|
-------
|
|
List[praw.models.reddit.submission.Submission]:
|
|
List of reddit submissions
|
|
Dict:
|
|
Dictionary of tickers and counts
|
|
int
|
|
Count of how many posts were analyzed
|
|
"""
|
|
d_watchlist_tickers: Dict = {}
|
|
l_watchlist_author = []
|
|
subs = []
|
|
|
|
praw_api = praw.Reddit(
|
|
client_id=cfg.API_REDDIT_CLIENT_ID,
|
|
client_secret=cfg.API_REDDIT_CLIENT_SECRET,
|
|
username=cfg.API_REDDIT_USERNAME,
|
|
user_agent=cfg.API_REDDIT_USER_AGENT,
|
|
password=cfg.API_REDDIT_PASSWORD,
|
|
)
|
|
psaw_api = PushshiftAPI()
|
|
submissions = psaw_api.search_submissions(
|
|
subreddit=l_sub_reddits,
|
|
q="WATCHLIST|Watchlist|watchlist",
|
|
filter=["id"],
|
|
)
|
|
n_flair_posts_found = 0
|
|
for sub in submissions:
|
|
submission = praw_api.submission(id=sub.id)
|
|
if submission:
|
|
if (
|
|
not submission.removed_by_category
|
|
and submission.selftext
|
|
and submission.link_flair_text not in ["Yolo", "Meme"]
|
|
and submission.author.name not in l_watchlist_author
|
|
):
|
|
l_tickers_found = find_tickers(submission)
|
|
|
|
if l_tickers_found:
|
|
# Add another author's name to the parsed watchlists
|
|
l_watchlist_author.append(submission.author.name)
|
|
|
|
# Lookup stock tickers within a watchlist
|
|
for key in l_tickers_found:
|
|
if key in d_watchlist_tickers:
|
|
# Increment stock ticker found
|
|
d_watchlist_tickers[key] += 1
|
|
else:
|
|
# Initialize stock ticker found
|
|
d_watchlist_tickers[key] = 1
|
|
|
|
# Increment count of valid posts found
|
|
n_flair_posts_found += 1
|
|
subs.append(submission)
|
|
if n_flair_posts_found > n_to_get - 1:
|
|
break
|
|
else:
|
|
logger.error("Invalid submission")
|
|
console.print("[red]Invalid submission[/red]\n")
|
|
return subs, d_watchlist_tickers, n_flair_posts_found
|
|
|
|
|
|
@log_start_end(log=logger)
|
|
def get_popular_tickers(
|
|
n_top: int, posts_to_look_at: int, subreddits: str = ""
|
|
) -> pd.DataFrame:
|
|
"""Get popular tickers from list of subreddits [Source: reddit]
|
|
|
|
Parameters
|
|
----------
|
|
n_top : int
|
|
Number of top tickers to get
|
|
posts_to_look_at : int
|
|
How many posts to analyze in each subreddit
|
|
subreddits : str, optional
|
|
String of comma separated subreddits.
|
|
|
|
Returns
|
|
-------
|
|
pd.DataFrame
|
|
DataFrame of top tickers from supplied subreddits
|
|
"""
|
|
if subreddits:
|
|
sub_reddit_list = subreddits.split(",") if "," in subreddits else [subreddits]
|
|
else:
|
|
sub_reddit_list = l_sub_reddits
|
|
d_watchlist_tickers: Dict = {}
|
|
l_watchlist_author = []
|
|
|
|
praw_api = praw.Reddit(
|
|
client_id=cfg.API_REDDIT_CLIENT_ID,
|
|
client_secret=cfg.API_REDDIT_CLIENT_SECRET,
|
|
username=cfg.API_REDDIT_USERNAME,
|
|
user_agent=cfg.API_REDDIT_USER_AGENT,
|
|
password=cfg.API_REDDIT_PASSWORD,
|
|
)
|
|
|
|
psaw_api = PushshiftAPI()
|
|
for s_sub_reddit in sub_reddit_list:
|
|
console.print(
|
|
f"Search for latest tickers for {posts_to_look_at} '{s_sub_reddit}' posts"
|
|
)
|
|
submissions = psaw_api.search_submissions(
|
|
subreddit=s_sub_reddit,
|
|
limit=posts_to_look_at,
|
|
filter=["id"],
|
|
)
|
|
|
|
n_tickers = 0
|
|
for submission in submissions:
|
|
try:
|
|
# Get more information about post using PRAW api
|
|
submission = praw_api.submission(id=submission.id)
|
|
|
|
# Ensure that the post hasn't been removed by moderator in the meanwhile,
|
|
# that there is a description and it's not just an image, that the flair is
|
|
# meaningful, and that we aren't re-considering same author's content
|
|
if (
|
|
not submission.removed_by_category
|
|
and (submission.selftext or submission.title)
|
|
and submission.author.name not in l_watchlist_author
|
|
):
|
|
l_tickers_found = find_tickers(submission)
|
|
|
|
if l_tickers_found:
|
|
n_tickers += len(l_tickers_found)
|
|
|
|
# Add another author's name to the parsed watchlists
|
|
l_watchlist_author.append(submission.author.name)
|
|
|
|
# Lookup stock tickers within a watchlist
|
|
for key in l_tickers_found:
|
|
if key in d_watchlist_tickers:
|
|
# Increment stock ticker found
|
|
d_watchlist_tickers[key] += 1
|
|
else:
|
|
# Initialize stock ticker found
|
|
d_watchlist_tickers[key] = 1
|
|
|
|
except ResponseException:
|
|
logger.exception(
|
|
"Received a response from Reddit with an authorization error. Check your token."
|
|
)
|
|
console.print(
|
|
"Received a response from Reddit with an authorization error. Check your token.\n"
|
|
)
|
|
return pd.DataFrame()
|
|
|
|
console.print(f" {n_tickers} potential tickers found.")
|
|
lt_watchlist_sorted = sorted(
|
|
d_watchlist_tickers.items(), key=lambda item: item[1], reverse=True
|
|
)
|
|
|
|
if lt_watchlist_sorted:
|
|
n_top_stocks = 0
|
|
# pylint: disable=redefined-outer-name
|
|
popular_tickers = []
|
|
for t_ticker in lt_watchlist_sorted:
|
|
if n_top_stocks > n_top:
|
|
break
|
|
try:
|
|
# If try doesn't trigger exception, it means that this stock exists on finviz
|
|
# thus we can print it.
|
|
stock_info = finviz.get_stock(t_ticker[0])
|
|
popular_tickers.append(
|
|
(
|
|
t_ticker[1],
|
|
t_ticker[0],
|
|
stock_info["Company"],
|
|
stock_info["Sector"],
|
|
stock_info["Price"],
|
|
stock_info["Change"],
|
|
stock_info["Perf Month"],
|
|
f"https://finviz.com/quote.ashx?t={t_ticker[0]}",
|
|
)
|
|
)
|
|
n_top_stocks += 1
|
|
except HTTPError as e:
|
|
if e.response.status_code != 404:
|
|
logger.exception("Unexpected exception from Finviz: %s", str(e))
|
|
console.print(f"Unexpected exception from Finviz: {e}")
|
|
except Exception as e:
|
|
logger.exception(str(e))
|
|
console.print(e, "\n")
|
|
return
|
|
|
|
popular_tickers_df = pd.DataFrame(
|
|
popular_tickers,
|
|
columns=[
|
|
"Mentions",
|
|
"Ticker",
|
|
"Company",
|
|
"Sector",
|
|
"Price",
|
|
"Change",
|
|
"Perf Month",
|
|
"URL",
|
|
],
|
|
)
|
|
return popular_tickers_df
|
|
|
|
|
|
@log_start_end(log=logger)
|
|
def get_spac_community(
|
|
limit: int, popular: bool
|
|
) -> Tuple[List[praw.models.reddit.submission.Submission], Dict]:
|
|
"""Get top tickers from r/SPACs [Source: reddit]
|
|
|
|
Parameters
|
|
----------
|
|
limit : int
|
|
Number of posts to look at
|
|
popular : bool
|
|
Search by hot instead of new
|
|
|
|
Returns
|
|
-------
|
|
List[praw.models.reddit.submission.Submission]:
|
|
List of reddit submission
|
|
Dict:
|
|
Dictionary of tickers and number of mentions
|
|
"""
|
|
praw_api = praw.Reddit(
|
|
client_id=cfg.API_REDDIT_CLIENT_ID,
|
|
client_secret=cfg.API_REDDIT_CLIENT_SECRET,
|
|
username=cfg.API_REDDIT_USERNAME,
|
|
user_agent=cfg.API_REDDIT_USER_AGENT,
|
|
password=cfg.API_REDDIT_PASSWORD,
|
|
)
|
|
|
|
d_watchlist_tickers: Dict = {}
|
|
l_watchlist_author = []
|
|
|
|
if popular:
|
|
submissions = praw_api.subreddit("SPACs").hot(limit=limit)
|
|
else:
|
|
submissions = praw_api.subreddit("SPACs").new(limit=limit)
|
|
|
|
subs = []
|
|
for sub in submissions:
|
|
if not sub:
|
|
break
|
|
|
|
# Get more information about post using PRAW api
|
|
submission = praw_api.submission(id=sub.id)
|
|
|
|
# Ensure that the post hasn't been removed by moderator in the meanwhile,
|
|
# that there is a description and it's not just an image, that the flair is
|
|
# meaningful, and that we aren't re-considering same author's watchlist
|
|
if (
|
|
not submission.removed_by_category
|
|
and submission.selftext
|
|
and submission.link_flair_text not in ["Yolo", "Meme"]
|
|
and submission.author.name not in l_watchlist_author
|
|
):
|
|
l_tickers_found = find_tickers(submission)
|
|
|
|
if l_tickers_found:
|
|
# Add another author's name to the parsed watchlists
|
|
l_watchlist_author.append(submission.author.name)
|
|
subs.append(submission)
|
|
# Lookup stock tickers within a watchlist
|
|
for key in l_tickers_found:
|
|
if key in d_watchlist_tickers:
|
|
# Increment stock ticker found
|
|
d_watchlist_tickers[key] += 1
|
|
else:
|
|
# Initialize stock ticker found
|
|
d_watchlist_tickers[key] = 1
|
|
return subs, d_watchlist_tickers
|
|
|
|
|
|
@log_start_end(log=logger)
|
|
def get_spac(
|
|
limit: int = 5,
|
|
) -> Tuple[List[praw.models.reddit.submission.Submission], Dict, int]:
|
|
"""Get posts containing SPAC from top subreddits [Source: reddit]
|
|
|
|
Parameters
|
|
----------
|
|
limit : int, optional
|
|
Number of posts to get for each subreddit, by default 5
|
|
|
|
Returns
|
|
-------
|
|
List[praw.models.reddit.submission.Submission] :
|
|
List of reddit submissions
|
|
Dict :
|
|
Dictionary of tickers and counts
|
|
int :
|
|
Number of posts found.
|
|
"""
|
|
praw_api = praw.Reddit(
|
|
client_id=cfg.API_REDDIT_CLIENT_ID,
|
|
client_secret=cfg.API_REDDIT_CLIENT_SECRET,
|
|
username=cfg.API_REDDIT_USERNAME,
|
|
user_agent=cfg.API_REDDIT_USER_AGENT,
|
|
password=cfg.API_REDDIT_PASSWORD,
|
|
)
|
|
|
|
d_watchlist_tickers: Dict = {}
|
|
l_watchlist_author = []
|
|
subs = []
|
|
psaw_api = PushshiftAPI()
|
|
submissions = psaw_api.search_submissions(
|
|
subreddit=l_sub_reddits,
|
|
q="SPAC|Spac|spac|Spacs|spacs",
|
|
filter=["id"],
|
|
)
|
|
n_flair_posts_found = 0
|
|
for submission in submissions:
|
|
|
|
# Get more information about post using PRAW api
|
|
submission = praw_api.submission(id=submission.id)
|
|
|
|
# Ensure that the post hasn't been removed by moderator in the meanwhile,
|
|
# that there is a description and it's not just an image, that the flair is
|
|
# meaningful, and that we aren't re-considering same author's watchlist
|
|
if (
|
|
not submission.removed_by_category
|
|
and submission.selftext
|
|
and submission.link_flair_text not in ["Yolo", "Meme"]
|
|
and submission.author.name not in l_watchlist_author
|
|
):
|
|
l_tickers_found = find_tickers(submission)
|
|
subs.append(submission)
|
|
if l_tickers_found:
|
|
# Add another author's name to the parsed watchlists
|
|
l_watchlist_author.append(submission.author.name)
|
|
|
|
# Lookup stock tickers within a watchlist
|
|
for key in l_tickers_found:
|
|
if key in d_watchlist_tickers:
|
|
# Increment stock ticker found
|
|
d_watchlist_tickers[key] += 1
|
|
else:
|
|
# Initialize stock ticker found
|
|
d_watchlist_tickers[key] = 1
|
|
|
|
# Increment count of valid posts found
|
|
n_flair_posts_found += 1
|
|
|
|
# Check if number of wanted posts found has been reached
|
|
if n_flair_posts_found > limit - 1:
|
|
break
|
|
|
|
return subs, d_watchlist_tickers, n_flair_posts_found
|
|
|
|
|
|
@log_start_end(log=logger)
|
|
def get_wsb_community(
|
|
limit: int = 10, new: bool = False
|
|
) -> List[praw.models.reddit.submission.Submission]:
|
|
"""Get wsb posts [Source: reddit]
|
|
|
|
Parameters
|
|
----------
|
|
limit : int, optional
|
|
Number of posts to get, by default 10
|
|
new : bool, optional
|
|
Flag to sort by new instead of hot, by default False
|
|
|
|
Returns
|
|
-------
|
|
List[praw.models.reddit.submission.Submission]
|
|
List of reddit submissions
|
|
"""
|
|
praw_api = praw.Reddit(
|
|
client_id=cfg.API_REDDIT_CLIENT_ID,
|
|
client_secret=cfg.API_REDDIT_CLIENT_SECRET,
|
|
username=cfg.API_REDDIT_USERNAME,
|
|
user_agent=cfg.API_REDDIT_USER_AGENT,
|
|
password=cfg.API_REDDIT_PASSWORD,
|
|
)
|
|
|
|
if new:
|
|
submissions = praw_api.subreddit("wallstreetbets").new(limit=limit)
|
|
else:
|
|
submissions = praw_api.subreddit("wallstreetbets").hot(limit=limit)
|
|
|
|
subs = []
|
|
try:
|
|
for submission in submissions:
|
|
submission = praw_api.submission(id=submission.id)
|
|
# Ensure that the post hasn't been removed by moderator in the meanwhile,
|
|
# that there is a description and it's not just an image, that the flair is
|
|
# meaningful, and that we aren't re-considering same author's watchlist
|
|
if not submission.removed_by_category:
|
|
subs.append(submission)
|
|
except ResponseException as e:
|
|
logger.exception("Invalid response: %s", str(e))
|
|
console.print("[red]Invalid response[/red]\n")
|
|
return subs
|
|
|
|
|
|
@log_start_end(log=logger)
|
|
def get_due_dilligence(
|
|
ticker: str, limit: int = 5, n_days: int = 3, show_all_flairs: bool = False
|
|
) -> List[praw.models.reddit.submission.Submission]:
|
|
"""Gets due dilligence posts from list of subreddits [Source: reddit]
|
|
|
|
Parameters
|
|
----------
|
|
ticker: str
|
|
Stock ticker
|
|
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)
|
|
|
|
Returns
|
|
-------
|
|
List[praw.models.reddit.submission.Submission]
|
|
List of submissions
|
|
"""
|
|
praw_api = praw.Reddit(
|
|
client_id=cfg.API_REDDIT_CLIENT_ID,
|
|
client_secret=cfg.API_REDDIT_CLIENT_SECRET,
|
|
username=cfg.API_REDDIT_USERNAME,
|
|
user_agent=cfg.API_REDDIT_USER_AGENT,
|
|
password=cfg.API_REDDIT_PASSWORD,
|
|
)
|
|
|
|
psaw_api = PushshiftAPI()
|
|
|
|
n_ts_after = int((datetime.today() - timedelta(days=n_days)).timestamp())
|
|
l_flair_text = [
|
|
"DD",
|
|
"technical analysis",
|
|
"Catalyst",
|
|
"News",
|
|
"Advice",
|
|
"Chart",
|
|
"Charts and Setups",
|
|
"Fundamental Analysis",
|
|
"forex",
|
|
"Trade Idea",
|
|
]
|
|
l_sub_reddits_dd = [
|
|
"pennystocks",
|
|
"RobinHoodPennyStocks",
|
|
"Daytrading",
|
|
"StockMarket",
|
|
"stocks",
|
|
"investing",
|
|
"wallstreetbets",
|
|
"forex",
|
|
"Forexstrategy",
|
|
]
|
|
|
|
submissions = psaw_api.search_submissions(
|
|
after=int(n_ts_after), subreddit=l_sub_reddits_dd, q=ticker, filter=["id"]
|
|
)
|
|
n_flair_posts_found = 0
|
|
subs = []
|
|
for submission in submissions:
|
|
# Get more information about post using PRAW api
|
|
submission = praw_api.submission(id=submission.id)
|
|
|
|
# Ensure that the post hasn't been removed in the meanwhile
|
|
if not submission.removed_by_category:
|
|
|
|
# Either just filter out Yolo, and Meme flairs, or focus on DD, based on b_DD flag
|
|
if (
|
|
submission.link_flair_text in l_flair_text,
|
|
submission.link_flair_text not in ["Yolo", "Meme"],
|
|
)[show_all_flairs]:
|
|
|
|
subs.append(submission)
|
|
# Increment count of valid posts found
|
|
n_flair_posts_found += 1
|
|
|
|
# Check if number of wanted posts found has been reached
|
|
if n_flair_posts_found > limit - 1:
|
|
break
|
|
|
|
return subs
|