diff --git a/discovery/alpha_vantage_api.py b/discovery/alpha_vantage_api.py new file mode 100644 index 00000000000..81ba7776a68 --- /dev/null +++ b/discovery/alpha_vantage_api.py @@ -0,0 +1,27 @@ +import argparse +from alpha_vantage.sectorperformance import SectorPerformances +import config_bot as cfg +import pandas as pd +import matplotlib.pyplot as plt + +# ---------------------------------------------------- SECTORS ---------------------------------------------------- +def sectors(l_args): + parser = argparse.ArgumentParser(prog='sectors', description='Plot sectors performance') + + try: + (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) + except SystemExit: + print("") + return + + if l_unknown_args: + print(f"The following args couldn't be interpreted: {l_unknown_args}") + + sp = SectorPerformances(key=cfg.API_KEY_ALPHAVANTAGE, output_format='pandas') + df_sectors, d_meta_data = sp.get_sector() + df_sectors['Rank A: Real-Time Performance'].plot(kind='bar') + plt.title('Real Time Performance (%) per Sector') + plt.tight_layout() + plt.grid() + plt.show() + print("") diff --git a/discovery/disc_menu.py b/discovery/disc_menu.py new file mode 100644 index 00000000000..cb2906d9f40 --- /dev/null +++ b/discovery/disc_menu.py @@ -0,0 +1,75 @@ +import config_bot as cfg +import argparse +from discovery import alpha_vantage_api +from discovery import yahoo_finance_api +from discovery import reddit_api + + +# ----------------------------------------------------------------------------------------------------------------------- +def print_discovery(): + """ Print help """ + + print(" help show this fundamental analysis menu again") + print(" q quit this menu, and shows back to main menu") + print(" quit quit to abandon program") + print("") + print(" sectors show sectors performance [Alpha Vantage]") + print(" gainers show latest top gainers [Yahoo Finance]") + print(" watchlist show other users watchlist [Reddit]") + print(" popular show popular tickers [Reddit]") + print("") + return + + +# ---------------------------------------------------- MENU ---------------------------------------------------- +def disc_menu(): + + # Add list of arguments that the discovery parser accepts + disc_parser = argparse.ArgumentParser(prog='discovery', add_help=False) + disc_parser.add_argument('cmd', choices=['help', 'q', 'quit', + 'sectors', 'gainers', 'watchlist', 'popular']) + + print_discovery() + + # Loop forever and ever + while True: + # Get input command from user + as_input = input('> ') + + # Parse fundamental analysis command of the list of possible commands + try: + (ns_known_args, l_args) = disc_parser.parse_known_args(as_input.split()) + + except SystemExit: + print("The command selected doesn't exist\n") + continue + + if ns_known_args.cmd == 'help': + print_discovery() + + elif ns_known_args.cmd == 'q': + # Just leave the DISC menu + return False + + elif ns_known_args.cmd == 'quit': + # Abandon the program + return True + + # ------------------------------------------------ ALPHA VANTAGE ----------------------------------------------- + elif ns_known_args.cmd == 'sectors': + alpha_vantage_api.sectors(l_args) + + # ------------------------------------------------ YAHOO FINANCE ------------------------------------------------ + elif ns_known_args.cmd == 'gainers': + yahoo_finance_api.gainers(l_args) + + # ---------------------------------------------------- REDDIT --------------------------------------------------- + elif ns_known_args.cmd == 'watchlist': + reddit_api.watchlist(l_args) + + elif ns_known_args.cmd == 'popular': + reddit_api.popular_tickers(l_args) + + # ------------------------------------------------------------------------------------------------------------ + else: + print("Command not recognized!") \ No newline at end of file diff --git a/discovery/reddit_api.py b/discovery/reddit_api.py new file mode 100644 index 00000000000..047381a00a7 --- /dev/null +++ b/discovery/reddit_api.py @@ -0,0 +1,260 @@ +import argparse +from prettytable import PrettyTable +from datetime import datetime, timedelta +import pandas as pd +from psaw import PushshiftAPI +import praw +from datetime import datetime +from pytz import timezone +from holidays import US as holidaysUS +from stock_market_helper_funcs import * +import re +import finviz +import config_bot as cfg + +# ------------------------------------------------------------------------------------------------------------------- +def get_last_time_market_was_open(dt): + # Check if it is a weekend + if dt.date().weekday() > 4: + dt = get_last_time_market_was_open(dt - timedelta(hours=24)) + + # Check if it is a holiday + if dt.strftime('%Y-%m-%d') in holidaysUS(): + dt = get_last_time_market_was_open(dt - timedelta(hours=24)) + + dt = dt.replace(hour=21, minute=0, second=0) + + return dt + + +# ------------------------------------------------------------------------------------------------------------------- +def watchlist(l_args): + parser = argparse.ArgumentParser(prog='watchlist', + description=""" Show other users watchlist [Reddit] """) + parser.add_argument('-l', "--limit", action="store", dest="n_limit", type=check_positive, default=5, help='Limit of posts with watchlists retrieved') + + try: + (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) + + if l_unknown_args: + print(f"The following args couldn't be interpreted: {l_unknown_args}\n") + return + + l_sub_reddits = ['pennystocks', 'RobinHoodPennyStocks', 'Daytrading', 'StockMarket', 'stocks', 'investing', 'wallstreetbets'] + + d_submission = {} + d_watchlist_tickers = {} + l_watchlist_links = list() + l_watchlist_author = list() + + 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) + + dt_last_time_market_close = get_last_time_market_was_open(datetime.now()-timedelta(hours=24)) + n_ts_after = int(dt_last_time_market_close.timestamp()) + psaw_api = PushshiftAPI() + submissions = psaw_api.search_submissions(after=n_ts_after, + subreddit=l_sub_reddits, + q='WATCHLIST|Watchlist|watchlist', + filter=['id']) + + n_flair_posts_found = 0 + while True: + submission = next(submissions, None) + if submission: + # 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: + + # Make sure there is at least 1 ticker found on the post text + l_tickers_found = set(re.findall("[A-Z]{3,5}", submission.selftext)) + 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 + + l_watchlist_links.append(f"https://www.reddit.com{submission.permalink}") + # delte below, not necessary I reckon. Probably just link? + + # Refactor data + s_datetime = datetime.utcfromtimestamp(submission.created_utc).strftime("%d/%m/%Y %H:%M:%S") + s_link = f"https://www.reddit.com{submission.permalink}" + s_all_awards = "" + for award in submission.all_awardings: + s_all_awards += f"{award['count']} {award['name']}\n" + s_all_awards = s_all_awards[:-2] + + # Create dictionary with data to construct dataframe allows to save data + d_submission[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 + print(f"\n{s_datetime} - {submission.title}") + print(f"{s_link}") + + # 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 > ns_parser.n_limit-1: + break + + # Check if search_submissions didn't get anymore posts + else: + break + + if n_flair_posts_found: + 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]) + s_watchlist_tickers += f"{t_ticker[1]} {t_ticker[0]}, " + n_tickers += 1 + except: + pass + if n_tickers: + print("The following stock tickers have been mentioned across the previous watchlists:") + print(s_watchlist_tickers[:-2]+'\n') + + except: + print("") + + +# ------------------------------------------------------------------------------------------------------------------- +def popular_tickers(l_args): + parser = argparse.ArgumentParser(prog='popular', + description=""" Latest popular tickers [Reddit] """) + parser.add_argument('-l', "--limit", action="store", dest="n_limit", type=check_positive, default=30, + help='Limit of posts retrieved per sub reddit') + parser.add_argument('-s', "--sub", action="store", dest="s_subreddit", type=str, + help="""Subreddit to look for tickers. Default: pennystocks, RobinHoodPennyStocks, Daytrading, + StockMarket, stocks, investing, wallstreetbets""") + parser.add_argument('-d', "--days", action="store", dest="n_days", type=check_positive, default=1, + help="Look for the tickers from those n past days") + + try: + (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) + + if l_unknown_args: + print(f"The following args couldn't be interpreted: {l_unknown_args}\n") + return + + n_ts_after = int((datetime.today() - timedelta(days=ns_parser.n_days)).timestamp()) + + if ns_parser.s_subreddit: + l_sub_reddits = [ns_parser.s_subreddit] + else: + l_sub_reddits = ['pennystocks', 'RobinHoodPennyStocks', 'Daytrading', 'StockMarket', 'stocks', 'investing', 'wallstreetbets'] + + d_submission = {} + d_watchlist_tickers = {} + l_watchlist_links = list() + l_watchlist_author = list() + + 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 l_sub_reddits: + print(f"Search for latest tickers under {ns_parser.n_limit} '{s_sub_reddit}' posts") + submissions = psaw_api.search_submissions(after=int(n_ts_after), + subreddit=s_sub_reddit, + limit=ns_parser.n_limit, + filter=['id']) + + n_tickers = 0 + while True: + submission = next(submissions, None) + if submission: + # 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.link_flair_text not in ['Yolo', 'Meme'] \ + and submission.author.name not in l_watchlist_author: + + # Make sure there is at least 1 ticker found on the post text + l_tickers_found = set(re.findall("[A-Z]{3,5}", submission.selftext)) + if not l_tickers_found: + l_tickers_found = set(re.findall("[A-Z]{3,5}", submission.title)) + + 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 + + # Check if search_submissions didn't get anymore posts + else: + break + + print(f" {n_tickers} tickers found.") + + + + lt_watchlist_sorted = sorted(d_watchlist_tickers.items(), key=lambda item: item[1], reverse=True) + print("\nThe following TOP10 tickers have been mentioned in the last 24 hours:") + n_top_stocks = 0 + for t_ticker in lt_watchlist_sorted: + if n_top_stocks > 9: + break + 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]) + print(f"{t_ticker[1]} {t_ticker[0]}") + n_top_stocks += 1 + except: + pass + + except: + print("") + \ No newline at end of file diff --git a/discovery/yahoo_finance_api.py b/discovery/yahoo_finance_api.py new file mode 100644 index 00000000000..d6dca6da826 --- /dev/null +++ b/discovery/yahoo_finance_api.py @@ -0,0 +1,27 @@ +import argparse +from alpha_vantage.sectorperformance import SectorPerformances +from alpha_vantage.timeseries import TimeSeries +import config_bot as cfg +import pandas as pd +#from stock_market_helper_funcs import * + + +# ---------------------------------------------------- GAINERS ---------------------------------------------------- +def gainers(l_args): + parser = argparse.ArgumentParser(prog='gainers', + description='Show top ticker gainers from Yahoo Finance') + parser.add_argument('-n', "--num", action="store", dest="n_gainers", type=int, default=5, choices=range(1, 25), + help='Number of the top gainers stocks to retrieve (default 5)') + + try: + (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) + except SystemExit: + print("") + return + + if l_unknown_args: + print(f"The following args couldn't be interpreted: {l_unknown_args}") + + df_gainers = pd.read_html('https://finance.yahoo.com/screener/predefined/day_gainers')[0] + print(df_gainers.head(ns_parser.n_gainers).to_string(index=False)) + print("")