From 3250f1bbda8e0241e72afa767e4129e168442f1f Mon Sep 17 00:00:00 2001 From: didier Date: Mon, 22 Feb 2021 00:25:51 +0000 Subject: [PATCH] Minor improvements in overall code for consistency --- config_neural_network_models.py | 28 +-- due_diligence/business_insider_api.py | 86 +++---- due_diligence/dd_menu.py | 10 +- due_diligence/financial_modeling_prep_api.py | 9 +- due_diligence/finviz_api.py | 39 +-- due_diligence/market_watch_api.py | 47 ++-- due_diligence/quandl_api.py | 17 +- due_diligence/reddit_api.py | 27 ++- fundamental_analysis/fa_menu.py | 16 +- gamestonk_terminal.py | 36 +-- helper_funcs.py | 55 ++++- prediction_techniques/arima.py | 30 +-- prediction_techniques/pred_menu.py | 8 +- sentiment/reddit_api.py | 239 ++++++++----------- technical_analysis/ta_menu.py | 6 +- 15 files changed, 331 insertions(+), 322 deletions(-) diff --git a/config_neural_network_models.py b/config_neural_network_models.py index b15e58c69ce..1278bbe47a9 100644 --- a/config_neural_network_models.py +++ b/config_neural_network_models.py @@ -19,33 +19,33 @@ # - The loss function MultiLayer_Perceptron \ - = [ {'Dense': + = [ {'Dense': {'units':50, 'activation':'relu'} }, - {'Dense': + {'Dense': {'units':100, 'activation':'relu'} }, - {'Dense': + {'Dense': {'units':80, 'activation':'relu'} }, - {'Dense': + {'Dense': {'units':30, 'activation':'relu'} }, - {'Dense': + {'Dense': {'activation':'linear'} }] Recurrent_Neural_Network \ - = [ {'SimpleRNN': + = [ {'SimpleRNN': {'units':100, 'activation':'linear', 'return_sequences':True} }, - {'SimpleRNN': + {'SimpleRNN': {'units':50, 'activation':'linear', 'return_sequences':True} }, - {'Dropout': + {'Dropout': {'rate':0.2} }, - {'SimpleRNN': + {'SimpleRNN': {'units':21, 'activation':'linear', 'return_sequences':False} }, - {'Dense': + {'Dense': {'activation':'linear'} }] Long_Short_Term_Memory \ - = [ {'LSTM': + = [ {'LSTM': {'units':25, 'activation':'tanh', 'return_sequences':True} }, - {'LSTM': + {'LSTM': {'units':15, 'activation':'tanh', 'return_sequences':False} }, - {'Dense': - {'activation':'linear'} }] + {'Dense': + {'activation':'linear'} }] diff --git a/due_diligence/business_insider_api.py b/due_diligence/business_insider_api.py index e3865dc0e40..204cd22a19e 100644 --- a/due_diligence/business_insider_api.py +++ b/due_diligence/business_insider_api.py @@ -16,10 +16,11 @@ register_matplotlib_converters() # ------------------------------------------- PRICE_TARGET_FROM_ANALYSTS ------------------------------------------- def price_target_from_analysts(l_args, df_stock, s_ticker, s_start, s_interval): - parser = argparse.ArgumentParser(prog='price_target_from_analysts', - description="""Price target from analysts [Source: Business Insider API]""") - - parser.add_argument('-n', "--num", action="store", dest="n_num", type=check_positive, default=10, help='Number of latest price targets') + parser = argparse.ArgumentParser(prog='pt', + description="""Prints price target from analysts. [Source: Business Insider]""") + + parser.add_argument('-n', "--num", action="store", dest="n_num", type=check_positive, default=10, + help='number of latest price targets from analysts to print.') try: (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) @@ -38,14 +39,14 @@ def price_target_from_analysts(l_args, df_stock, s_ticker, s_start, s_interval): s_analyst_data = script.get_text().split("config: ",1)[1].split(",\r\n",1)[0] d_analyst_data = json.loads(s_analyst_data) break - + #pprint.pprint(d_analyst_data) - df_analyst_data = pd.DataFrame.from_dict(d_analyst_data['Markers']) + df_analyst_data = pd.DataFrame.from_dict(d_analyst_data['Markers']) df_analyst_data = df_analyst_data[['DateLabel', 'Company', 'InternalRating', 'PriceTarget']] df_analyst_data.columns = ['Date', 'Company', 'Rating', 'Price Target'] df_analyst_data - df_analyst_data['Rating'].replace({'gut': 'BUY', - 'neutral': 'HOLD', + df_analyst_data['Rating'].replace({'gut': 'BUY', + 'neutral': 'HOLD', 'schlecht':'SELL'}, inplace=True) df_analyst_data['Date'] = pd.to_datetime(df_analyst_data['Date']) df_analyst_data = df_analyst_data.set_index('Date') @@ -56,7 +57,7 @@ def price_target_from_analysts(l_args, df_stock, s_ticker, s_start, s_interval): if s_interval == "1440min": plt.plot(df_stock.index, df_stock['5. adjusted close'].values, lw=3) - # Intraday + # Intraday else: plt.plot(df_stock.index, df_stock['4. close'].values, lw=3) @@ -82,7 +83,7 @@ def price_target_from_analysts(l_args, df_stock, s_ticker, s_start, s_interval): pd.set_option('display.max_colwidth', -1) print(df_analyst_data.sort_index(ascending=False).head(ns_parser.n_num).to_string()) print("") - + except: print("") return @@ -90,8 +91,8 @@ def price_target_from_analysts(l_args, df_stock, s_ticker, s_start, s_interval): # ----------------------------------------------- ESTIMATES ----------------------------------------------- def estimates(l_args, s_ticker): - parser = argparse.ArgumentParser(prog='estimates', - description="""Yearly and quarter earnings estimates [Source: Business Insider API]""") + parser = argparse.ArgumentParser(prog='est', + description="""Yearly estimates and quarter earnings/revenues [Source: Business Insider]""") try: (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) @@ -115,7 +116,7 @@ def estimates(l_args, s_ticker): l_estimates_year_metric = list() for estimates_year_metric in text_soup_market_business_insider.findAll('td', {'class': 'table__td black'}): l_estimates_year_metric.append(estimates_year_metric.text) - + l_estimates_quarter_metric = list() for estimates_quarter_metric in text_soup_market_business_insider.findAll('td', {'class': 'table__td font-color-dim-gray'}): l_estimates_quarter_metric.append(estimates_quarter_metric.text) @@ -127,32 +128,32 @@ def estimates(l_args, s_ticker): n_metrics = 0 b_year = True for idx, metric_value in enumerate(text_soup_market_business_insider.findAll('td', {'class': 'table__td text-right'})): - + if b_year: # YEAR metrics l_metrics.append(metric_value.text.strip()) - + # Check if we have processed all year metrics if n_metrics > len(l_estimates_year_metric)-1: b_year = False n_metrics = 0 l_metrics = list() idx_y = idx - + # Add value to dictionary if (idx+1)%len(l_estimates_year_header) == 0: d_metric_year[l_estimates_year_metric[n_metrics]] = l_metrics l_metrics = list() n_metrics += 1 - - if not b_year: + + if not b_year: # QUARTER metrics l_metrics.append(metric_value.text.strip()) - + # Check if we have processed all quarter metrics if n_metrics > len(l_estimates_quarter_metric)-1: break - + # Add value to dictionary if (idx-idx_y+1)%len(l_estimates_quarter_header) == 0: if n_metrics < 4: @@ -177,7 +178,7 @@ def estimates(l_args, s_ticker): l_date.append('ending ' + re.split(' ending',quarter_title)[1].strip()) else: l_date.append('-') - + df_quarter_earnings.columns = l_quarter df_quarter_earnings.loc["Date"] = l_date df_quarter_earnings = df_quarter_earnings.reindex(["Date", "No. of Analysts", "Average Estimate", "Year Ago", "Publish Date"]) @@ -197,7 +198,7 @@ def estimates(l_args, s_ticker): print(text_soup_market_business_insider.find('div', {'class': "text_right instrument-description"}).text.strip()) print("") - + except: print("") return @@ -205,9 +206,10 @@ def estimates(l_args, s_ticker): # ----------------------------------------------- INSIDER_ACTIVITY ----------------------------------------------- def insider_activity(l_args, df_stock, s_ticker, s_start, s_interval): - parser = argparse.ArgumentParser(prog='insider_activity', - description="""Insider activity over time [Source: Business Insider API]""") - parser.add_argument('-n', "--num", action="store", dest="n_num", type=check_positive, default=10, help='Number of latest insider activity') + parser = argparse.ArgumentParser(prog='ins', + description="""Prints insider activity over time [Source: Business Insider]""") + parser.add_argument('-n', "--num", action="store", dest="n_num", type=check_positive, default=10, + help='number of latest insider activity.') try: (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) @@ -218,7 +220,7 @@ def insider_activity(l_args, df_stock, s_ticker, s_start, s_interval): url_market_business_insider = f"https://markets.businessinsider.com/stocks/{s_ticker.lower()}-stock" text_soup_market_business_insider = BeautifulSoup(requests.get(url_market_business_insider).text, "lxml") - + d_insider = dict() l_insider_vals = list() for idx, insider_val in enumerate(text_soup_market_business_insider.findAll('td', {'class':"table__td text-center"})): @@ -248,7 +250,7 @@ def insider_activity(l_args, df_stock, s_ticker, s_start, s_interval): if s_interval == "1440min": plt.plot(df_stock.index, df_stock['5. adjusted close'].values, lw=3) - else: # Intraday + else: # Intraday plt.plot(df_stock.index, df_stock['4. close'].values, lw=3) plt.title(f"{s_ticker.upper()} (Time Series) and Price Target") @@ -269,33 +271,33 @@ def insider_activity(l_args, df_stock, s_ticker, s_start, s_interval): ind_dt = ind else: ind_dt = get_next_stock_market_days(ind, 1)[0] - + n_stock_price = 0 if s_interval == "1440min": - n_stock_price = df_stock['5. adjusted close'][ind_dt] + n_stock_price = df_stock['5. adjusted close'][ind_dt] else: n_stock_price = df_stock['4. close'][ind_dt] - - plt.vlines(x=ind_dt, - ymin= n_stock_price + n_proportion * float(df_insider[df_insider['Type'] == 'Sell'].groupby(by=['Date']).sum()['Trade'][ind]), - ymax= n_stock_price, + + plt.vlines(x=ind_dt, + ymin= n_stock_price + n_proportion * float(df_insider[df_insider['Type'] == 'Sell'].groupby(by=['Date']).sum()['Trade'][ind]), + ymax= n_stock_price, colors='red', ls='-', lw=5) - + for ind in df_insider[df_insider['Type'] == 'Buy'].groupby(by=['Date']).sum().index: if ind in df_stock.index: ind_dt = ind else: ind_dt = get_next_stock_market_days(ind, 1)[0] - + n_stock_price = 0 if s_interval == "1440min": - n_stock_price = df_stock['5. adjusted close'][ind_dt] + n_stock_price = df_stock['5. adjusted close'][ind_dt] else: n_stock_price = df_stock['4. close'][ind_dt] - - plt.vlines(x=ind_dt, - ymin=n_stock_price, - ymax=n_stock_price + n_proportion * float(df_insider[df_insider['Type'] == 'Buy'].groupby(by=['Date']).sum()['Trade'][ind]), + + plt.vlines(x=ind_dt, + ymin=n_stock_price, + ymax=n_stock_price + n_proportion * float(df_insider[df_insider['Type'] == 'Buy'].groupby(by=['Date']).sum()['Trade'][ind]), colors='green', ls='-', lw=5) plt.grid(b=True, which='major', color='#666666', linestyle='-') @@ -305,12 +307,12 @@ def insider_activity(l_args, df_stock, s_ticker, s_start, s_interval): l_names = list() for s_name in text_soup_market_business_insider.findAll('a', {'onclick':"silentTrackPI()"}): - l_names.append(s_name.text.strip()) + l_names.append(s_name.text.strip()) df_insider['Insider'] = l_names print(df_insider.sort_index(ascending=False).head(n=ns_parser.n_num).to_string()) print("") - + except: print("") return diff --git a/due_diligence/dd_menu.py b/due_diligence/dd_menu.py index f22c69b0c31..193817b787c 100644 --- a/due_diligence/dd_menu.py +++ b/due_diligence/dd_menu.py @@ -53,8 +53,8 @@ def print_due_diligence(s_ticker, s_start, s_interval): def dd_menu(df_stock, s_ticker, s_start, s_interval): # Add list of arguments that the due diligence parser accepts - dd_parser = argparse.ArgumentParser(prog='due_diligence', add_help=False) - dd_parser.add_argument('cmd', choices=['info', 'help', 'q', 'quit', + dd_parser = argparse.ArgumentParser(prog='dd', add_help=False) + dd_parser.add_argument('cmd', choices=['info', 'help', 'q', 'quit', 'red', # Reddit 'short', # Quandl 'rating', # FMP @@ -68,7 +68,7 @@ def dd_menu(df_stock, s_ticker, s_start, s_interval): while True: # Get input command from user as_input = input('> ') - + # Parse due diligence command of the list of possible commands try: (ns_known_args, l_args) = dd_parser.parse_known_args(as_input.split()) @@ -76,7 +76,7 @@ def dd_menu(df_stock, s_ticker, s_start, s_interval): except SystemExit: print("The command selected doesn't exist\n") continue - + if ns_known_args.cmd == 'help': print_due_diligence(s_ticker, s_start, s_interval) @@ -91,7 +91,7 @@ def dd_menu(df_stock, s_ticker, s_start, s_interval): # REDDIT API elif ns_known_args.cmd == 'red': r_api.due_diligence(l_args, s_ticker) - + # FINVIZ API elif ns_known_args.cmd == 'insider': fvz_api.insider(l_args, s_ticker) diff --git a/due_diligence/financial_modeling_prep_api.py b/due_diligence/financial_modeling_prep_api.py index 62e1a35526b..9f47e18cecd 100644 --- a/due_diligence/financial_modeling_prep_api.py +++ b/due_diligence/financial_modeling_prep_api.py @@ -8,11 +8,11 @@ import pandas as pd # ---------------------------------------------------- RATING ---------------------------------------------------- def rating(l_args, s_ticker): - parser = argparse.ArgumentParser(prog='rating', - description="""Based on specific ratios, provides information whether the company + parser = argparse.ArgumentParser(prog='rating', + description="""Based on specific ratios, prints information whether the company is a (strong) buy, neutral or a (strong) sell. The following fields are expected: - P/B, ROA, DCF, P/E, ROE, and D/E. [Source: Financial Modeling Prep API]""") - + P/B, ROA, DCF, P/E, ROE, and D/E. [Source: Financial Modeling Prep]""") + try: (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) @@ -29,4 +29,3 @@ def rating(l_args, s_ticker): print("") return - \ No newline at end of file diff --git a/due_diligence/finviz_api.py b/due_diligence/finviz_api.py index 3fbc812ff58..542c2860e16 100644 --- a/due_diligence/finviz_api.py +++ b/due_diligence/finviz_api.py @@ -5,12 +5,13 @@ import pandas as pd # ---------------------------------------------------- INSIDER ---------------------------------------------------- def insider(l_args, s_ticker): - parser = argparse.ArgumentParser(prog='insider', - description="""Gives information about inside traders. The following fields are expected: - Date, Relationship, Transaction, #Shares, Cost, Value ($), #Shares Total, Insider Trading, - SEC Form 4. [Source: Finviz API]""") - - parser.add_argument('-n', "--num", action="store", dest="n_num", type=check_positive, default=10, help='Number of latest inside traders') + parser = argparse.ArgumentParser(prog='insider', + description="""Prints information about inside traders. The following fields are expected: + Date, Relationship, Transaction, #Shares, Cost, Value ($), #Shares Total, Insider Trading, + SEC Form 4. [Source: Finviz]""") + + parser.add_argument('-n', "--num", action="store", dest="n_num", type=check_positive, default=10, + help='number of latest inside traders.') try: (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) @@ -21,7 +22,7 @@ def insider(l_args, s_ticker): d_finviz_insider = finviz.get_insider(s_ticker) df_fa = pd.DataFrame.from_dict(d_finviz_insider) - df_fa.set_index("Date", inplace=True) + df_fa.set_index("Date", inplace=True) df_fa = df_fa[['Relationship', 'Transaction', '#Shares', 'Cost', 'Value ($)', '#Shares Total', 'Insider Trading', 'SEC Form 4']] print(df_fa.head(n=ns_parser.n_num)) @@ -34,11 +35,12 @@ def insider(l_args, s_ticker): # ---------------------------------------------------- NEWS ---------------------------------------------------- def news(l_args, s_ticker): - parser = argparse.ArgumentParser(prog='news', - description="""Gives latest news about company. The following fields are expected: - Title, and http link. [Source: Finviz API]""") - - parser.add_argument('-n', "--num", action="store", dest="n_num", type=check_positive, default=5, help='Number of latest inside traders') + parser = argparse.ArgumentParser(prog='news', + description="""Prints latest news about company, including title and web link. + [Source: Finviz]""") + + parser.add_argument('-n', "--num", action="store", dest="n_num", type=check_positive, default=5, + help='Number of latest news being printed.') try: (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) @@ -53,7 +55,7 @@ def news(l_args, s_ticker): print(f"-> {s_news_title}") print(f"{s_news_link}\n") i+=1 - + if i > (ns_parser.n_num-1): break @@ -66,11 +68,11 @@ def news(l_args, s_ticker): # ---------------------------------------------------- ANALYST ---------------------------------------------------- def analyst(l_args, s_ticker): - parser = argparse.ArgumentParser(prog='analyst', - description="""Gives analyst prices and ratings of the company. The following fields + parser = argparse.ArgumentParser(prog='analyst', + description="""Print analyst prices and ratings of the company. The following fields are expected: date, analyst, category, price from, price to, and rating. - [Source: Finviz API]""") - + [Source: Finviz]""") + try: (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) @@ -80,9 +82,8 @@ def analyst(l_args, s_ticker): d_finviz_analyst_price = finviz.get_analyst_price_targets(s_ticker) df_fa = pd.DataFrame.from_dict(d_finviz_analyst_price) - df_fa.set_index("date", inplace=True) + df_fa.set_index("date", inplace=True) print(df_fa) - print("") except: diff --git a/due_diligence/market_watch_api.py b/due_diligence/market_watch_api.py index 4c1ad93d8be..9489c29276b 100644 --- a/due_diligence/market_watch_api.py +++ b/due_diligence/market_watch_api.py @@ -8,12 +8,13 @@ from helper_funcs import * # ---------------------------------------------------- SEC FILLINGS ---------------------------------------------------- def sec_fillings(l_args, s_ticker): - parser = argparse.ArgumentParser(prog='sec', - description="""Gives SEC filings of the company. The following fields + parser = argparse.ArgumentParser(prog='sec', + description="""Prints SEC filings of the company. The following fields are expected: Filing Date, Document Date, Type, Category, Amended, and Link. - [Source: Market Watch BS]""") - - parser.add_argument('-n', "--num", action="store", dest="n_num", type=check_positive, default=5, help='Number of latest SEC filings') + [Source: Market Watch]""") + + parser.add_argument('-n', "--num", action="store", dest="n_num", type=check_positive, default=5, + help='number of latest SEC filings.') try: (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) @@ -25,7 +26,7 @@ def sec_fillings(l_args, s_ticker): pd.set_option('display.max_colwidth', -1) url_financials = f"https://www.marketwatch.com/investing/stock/{s_ticker}/financials/secfilings" - + text_soup_financials = BeautifulSoup(requests.get(url_financials).text,"lxml") a_financials_header = list() @@ -33,20 +34,20 @@ def sec_fillings(l_args, s_ticker): soup_financials = text_soup_financials.findAll('tr', {'class': 'table__row'}) for financials_info in soup_financials: a_financials = financials_info.text.split('\n') - + # If header has been processed and dataframe created ready to populate the SEC information if b_ready_to_process_info: - l_financials_info = [a_financials[2]] + l_financials_info = [a_financials[2]] l_financials_info.extend(a_financials[5:-1]) l_financials_info.append(financials_info.a['href']) # Append data values to financials df_financials.loc[len(df_financials.index)] = l_financials_info - + if 'Filing Date' in a_financials: - l_financials_header = [a_financials[2]] + l_financials_header = [a_financials[2]] l_financials_header.extend(a_financials[5:-1]) l_financials_header.append('Link') - + df_financials = pd.DataFrame(columns=l_financials_header) df_financials.set_index('Filing Date') b_ready_to_process_info = True @@ -56,7 +57,7 @@ def sec_fillings(l_args, s_ticker): print(df_financials.head(n=ns_parser.n_num).to_string()) print("") - + except: print("") return @@ -64,13 +65,17 @@ def sec_fillings(l_args, s_ticker): # ---------------------------------------------------- Sean Seah Warnings ---------------------------------------------------- def sean_seah_warnings(l_args, s_ticker): - parser = argparse.ArgumentParser(prog='warnings', + parser = argparse.ArgumentParser(prog='warnings', description="""Sean Seah warnings. Check: Consistent historical earnings per share; - Consistently high return on equity; Consistently high return on assets; 5x Net - Income > Long-Term Debt; and Interest coverage ratio more than 3. [Source: Market Watch BS]""") + Consistently high return on equity; Consistently high return on assets; 5x Net + Income > Long-Term Debt; and Interest coverage ratio more than 3. See + https://www.drwealth.com/gone-fishing-with-buffett-by-sean-seah/comment-page-1/ + [Source: Market Watch]""") - parser.add_argument('-i', "--info", action="store_true", default=False, dest="b_info", help='Give Sean Seah books information') - parser.add_argument('-d', "--debug", action="store_true", default=False, dest="b_debug", help='Show data used to calculate warnings') + parser.add_argument('-i', "--info", action="store_true", default=False, dest="b_info", + help='provide more information about Sean Seah warning rules.') + parser.add_argument('-d', "--debug", action="store_true", default=False, dest="b_debug", + help='print insights into warnings calculation.') try: (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) @@ -88,7 +93,7 @@ def sean_seah_warnings(l_args, s_ticker): line = fp.readline() print("") - # From INCOME STATEMENT, get: 'EPS (Basic)', 'Net Income', 'Interest Expense', 'EBITDA' + # From INCOME STATEMENT, get: 'EPS (Basic)', 'Net Income', 'Interest Expense', 'EBITDA' url_financials = f"https://www.marketwatch.com/investing/stock/{s_ticker}/financials/income" text_soup_financials = BeautifulSoup(requests.get(url_financials).text,"lxml") @@ -103,7 +108,7 @@ def sean_seah_warnings(l_args, s_ticker): soup_financials += text_soup_financials.findAll('tr', {'class': 'table__row is-highlighted'}) for financials_info in soup_financials: a_financials_info = financials_info.text.split('\n') - l_financials = [a_financials_info[2]] + l_financials = [a_financials_info[2]] l_financials.extend(a_financials_info[5:-2]) # Append data values to financials df_financials.loc[len(df_financials.index)] = l_financials @@ -114,7 +119,7 @@ def sean_seah_warnings(l_args, s_ticker): df_sean_seah = df_financials.loc[['EPS (Basic)', 'Net Income', 'Interest Expense', 'EBITDA']] # From BALANCE SHEET, get: 'Liabilities & Shareholders\' Equity', 'Long-Term Debt' - url_financials = f"https://www.marketwatch.com/investing/stock/{s_ticker}/financials/balance-sheet" + url_financials = f"https://www.marketwatch.com/investing/stock/{s_ticker}/financials/balance-sheet" text_soup_financials = BeautifulSoup(requests.get(url_financials).text,"lxml") # Define financials columns @@ -130,7 +135,7 @@ def sean_seah_warnings(l_args, s_ticker): soup_financials += text_soup_financials.findAll('tr', {'class': 'table__row is-highlighted'}) for financials_info in soup_financials: a_financials_info = financials_info.text.split('\n') - l_financials = [a_financials_info[2]] + l_financials = [a_financials_info[2]] l_financials.extend(a_financials_info[5:-2]) # Append data values to financials df_financials.loc[len(df_financials.index)] = l_financials diff --git a/due_diligence/quandl_api.py b/due_diligence/quandl_api.py index 0f3aa916ba5..62606b1d4f6 100644 --- a/due_diligence/quandl_api.py +++ b/due_diligence/quandl_api.py @@ -10,12 +10,14 @@ from helper_funcs import * # -------------------------------------------------------- SHORT_INTEREST -------------------------------------------------------- def short_interest(l_args, s_ticker, s_start): - parser = argparse.ArgumentParser(prog='short', - description="""Short interest corresponds to the number of shares that have been sold short - but have not yet been covered or closed out. [Source: Quandl API]""") + parser = argparse.ArgumentParser(prog='short', + description="""Plots the short interest of a stock. This corresponds to the number of shares that + have been sold short but have not yet been covered or closed out. Either NASDAQ or NYSE [Source: Quandl]""") - parser.add_argument('-n', "--nyse", action="store_true", default=False, dest="b_nyse", help='Data from NYSE') - parser.add_argument('-d', "--days", action="store", dest="n_days", type=check_positive, default=10, help='Number of latest days to see data') + parser.add_argument('-n', "--nyse", action="store_true", default=False, dest="b_nyse", + help='data from NYSE flag.') + parser.add_argument('-d', "--days", action="store", dest="n_days", type=check_positive, default=10, + help='number of latest days to print data.') try: (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) @@ -45,7 +47,7 @@ def short_interest(l_args, s_ticker, s_start): ax.set_title(f"{('NASDAQ', 'NYSE')[ns_parser.b_nyse]} Short Interest on {s_ticker} from {s_start.date()}") else: ax.set_title(f"{('NASDAQ', 'NYSE')[ns_parser.b_nyse]} Short Interest on {s_ticker}") - + ax.legend(labels=['Short Volume', 'Total Volume']) ax.tick_params(axis='both', which='major') ax.yaxis.set_major_formatter(ticker.EngFormatter()) @@ -56,7 +58,7 @@ def short_interest(l_args, s_ticker, s_start): ax_twin.tick_params(axis='y', which='major', color='green') ax_twin.yaxis.set_major_formatter(ticker.FormatStrFormatter('%.0f%%')) plt.xlim([df_short_interest.index[0], df_short_interest.index[-1]]) - + df_short_interest['% of Volume Shorted'] = df_short_interest['% of Volume Shorted'].apply(lambda x: f'{x/100:.2%}') df_short_interest = df_short_interest.applymap(lambda x: long_number_format(x)).sort_index(ascending=False) @@ -69,4 +71,3 @@ def short_interest(l_args, s_ticker, s_start): except: print("") return - \ No newline at end of file diff --git a/due_diligence/reddit_api.py b/due_diligence/reddit_api.py index 1b62bc3a7fc..cdd685914d0 100644 --- a/due_diligence/reddit_api.py +++ b/due_diligence/reddit_api.py @@ -14,15 +14,16 @@ import config_terminal as cfg # ------------------------------------------------------------------------------------------------------------------- def due_diligence(l_args, s_ticker): - parser = argparse.ArgumentParser(prog='dd', - description=""" Search for a stock's due diligence [Reddit] """) - parser.add_argument('-l', "--limit", action="store", dest="n_limit", type=check_positive, default=5, - help='Limit of posts with the watchlist word') + parser = argparse.ArgumentParser(prog='red', + description="""Print top stock's due diligence from other users. [Source: Reddit] """) + + parser.add_argument('-l', "--limit", action="store", dest="n_limit", type=check_positive, default=5, + help='limit of posts to retrieve.') parser.add_argument('-d', "--days", action="store", dest="n_days", type=check_positive, default=3, - help="Look for a watchlist created on the past n days") + help="number of prior days to look for.") parser.add_argument('-a', "--all", action="store_true", dest="b_all", default=False, - help=""" Search through all flairs (apart from Yolo and Meme), otherwise we focus on the - flairs: DD, technical analysis, Catalyst, News, Advice, Chart """) + help="""search through all flairs (apart from Yolo and Meme), otherwise we focus on + specific flairs: DD, technical analysis, Catalyst, News, Advice, Chart """) try: (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) @@ -54,12 +55,12 @@ def due_diligence(l_args, s_ticker): 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 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, + if (submission.link_flair_text in l_flair_text, submission.link_flair_text not in ['Yolo', 'Meme'])[ns_parser.b_all]: # Refactor data @@ -87,20 +88,20 @@ def due_diligence(l_args, s_ticker): print(f"{s_datetime} - {submission.title}") print(f"{s_link}") t_post = PrettyTable(['Subreddit', 'Flair', 'Score', '# Comments', 'Upvote %', "Awards"]) - t_post.add_row([submission.subreddit, submission.link_flair_text, submission.score, + t_post.add_row([submission.subreddit, submission.link_flair_text, submission.score, submission.num_comments,f"{round(100*submission.upvote_ratio)}%", s_all_awards]) print(t_post) print("\n") - + # If needed, submission.comments could give us the top comments # 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 diff --git a/fundamental_analysis/fa_menu.py b/fundamental_analysis/fa_menu.py index 69d964028dc..850a30e2879 100644 --- a/fundamental_analysis/fa_menu.py +++ b/fundamental_analysis/fa_menu.py @@ -75,10 +75,10 @@ def print_fundamental_analysis(s_ticker, s_start, s_interval): # ---------------------------------------------------- INFO ---------------------------------------------------- def info(l_args, s_ticker): - parser = argparse.ArgumentParser(prog='info', + parser = argparse.ArgumentParser(prog='info', description="""Provides information about main key metrics. Namely: EBITDA, EPS, P/E, PEG, FCF, P/B, ROE, DPR, P/S, Dividend Yield Ratio, D/E, and Beta.""") - + try: (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) @@ -96,13 +96,13 @@ def info(l_args, s_ticker): except: print("ERROR!\n") return - + # ---------------------------------------------------- MENU ---------------------------------------------------- def fa_menu(s_ticker, s_start, s_interval): # Add list of arguments that the fundamental analysis parser accepts - fa_parser = argparse.ArgumentParser(prog='fundamental_analysis', add_help=False) + fa_parser = argparse.ArgumentParser(prog='fa', add_help=False) fa_parser.add_argument('cmd', choices=['help', 'q', 'quit', # 'screener', # Finviz 'mgmt', # Business Insider @@ -118,7 +118,7 @@ def fa_menu(s_ticker, s_start, s_interval): 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) = fa_parser.parse_known_args(as_input.split()) @@ -129,7 +129,7 @@ def fa_menu(s_ticker, s_start, s_interval): #if ns_known_args.cmd == 'info': # info(l_args, s_ticker) - + if ns_known_args.cmd == 'help': print_fundamental_analysis(s_ticker, s_start, s_interval) @@ -180,7 +180,7 @@ def fa_menu(s_ticker, s_start, s_interval): elif ns_known_args.cmd == 'cal': yf_api.calendar_earnings(l_args, s_ticker) - + # ALPHA VANTAGE API elif ns_known_args.cmd == 'overview': av_api.overview(l_args, s_ticker) @@ -204,7 +204,7 @@ def fa_menu(s_ticker, s_start, s_interval): elif ns_known_args.cmd == 'quote': fmp_api.quote(l_args, s_ticker) - + elif ns_known_args.cmd == 'enterprise': fmp_api.enterprise(l_args, s_ticker) diff --git a/gamestonk_terminal.py b/gamestonk_terminal.py index 0cb9df81d0d..73d590a82d1 100644 --- a/gamestonk_terminal.py +++ b/gamestonk_terminal.py @@ -44,13 +44,13 @@ def load(l_args, s_ticker, s_start, s_interval, df_stock): ts = TimeSeries(key=cfg.API_KEY_ALPHAVANTAGE, output_format='pandas') # Daily if s_interval == "1440min": - df_stock, d_stock_metadata = ts.get_daily_adjusted(symbol=ns_parser.s_ticker, outputsize='full') + df_stock, d_stock_metadata = ts.get_daily_adjusted(symbol=ns_parser.s_ticker, outputsize='full') # Intraday - else: - df_stock, d_stock_metadata = ts.get_intraday(symbol=ns_parser.s_ticker, outputsize='full', interval=s_interval) + else: + df_stock, d_stock_metadata = ts.get_intraday(symbol=ns_parser.s_ticker, outputsize='full', interval=s_interval) df_stock.sort_index(ascending=True, inplace=True) - + except: print("Either the ticker or the API_KEY are invalids. Try again!") return [s_ticker, s_start, s_interval, df_stock] @@ -72,7 +72,7 @@ def load(l_args, s_ticker, s_start, s_interval, df_stock): def view(l_args, s_ticker, s_start, s_interval, df_stock): parser = argparse.ArgumentParser(prog='view', description='Visualise historical data of a stock. An alpha_vantage key is necessary.') if s_ticker: - parser.add_argument('-t', "--ticker", action="store", dest="s_ticker", default=s_ticker, help='Stock ticker') + parser.add_argument('-t', "--ticker", action="store", dest="s_ticker", default=s_ticker, help='Stock ticker') else: parser.add_argument('-t', "--ticker", action="store", dest="s_ticker", required=True, help='Stock ticker') parser.add_argument('-s', "--start", type=valid_date, dest="s_start_date", help="The starting date (format YYYY-MM-DD) of the stock") @@ -85,7 +85,7 @@ def view(l_args, s_ticker, s_start, s_interval, df_stock): except SystemExit: print("") return - + if l_unknown_args: print(f"The following args couldn't be interpreted: {l_unknown_args}") @@ -105,8 +105,8 @@ def view(l_args, s_ticker, s_start, s_interval, df_stock): df_stock, d_stock_metadata = ts.get_daily_adjusted(symbol=s_ticker, outputsize='full') # Intraday else: - df_stock, d_stock_metadata = ts.get_intraday(symbol=s_ticker, outputsize='full', interval=s_interval) - + df_stock, d_stock_metadata = ts.get_intraday(symbol=s_ticker, outputsize='full', interval=s_interval) + except: print("Either the ticker or the API_KEY are invalids. Try again!") return @@ -125,7 +125,7 @@ def view(l_args, s_ticker, s_start, s_interval, df_stock): print("An index bigger than 4 was given, which is wrong. Try again") return # Append last column of df to be filtered which corresponds to: 6. Volume - ln_col_idx.append(5) + ln_col_idx.append(5) # Intraday else: # The default doesn't exist for intradaily data @@ -138,7 +138,7 @@ def view(l_args, s_ticker, s_start, s_interval, df_stock): print("An index bigger than 3 was given, which is wrong. Try again") return # Append last column of df to be filtered which corresponds to: 5. Volume - ln_col_idx.append(4) + ln_col_idx.append(4) # Plot view of the stock plot_view_stock(df_stock.iloc[:, ln_col_idx], ns_parser.s_ticker) @@ -155,7 +155,7 @@ def print_help(s_ticker, s_start, s_interval, b_is_market_open): print(" clear clear a specific stock ticker from analysis") print(" load load a specific stock ticker for analysis") print(" view view and load a specific stock ticker for technical analysis") - + s_intraday = (f'Intraday {s_interval}', 'Daily')[s_interval == "1440min"] if s_ticker and s_start: print(f"\n{s_intraday} Stock: {s_ticker} (from {s_start.strftime('%Y-%m-%d')})") @@ -179,7 +179,7 @@ def print_help(s_ticker, s_start, s_interval, b_is_market_open): # ----------------------------------------------------------------------------------------------------------------------- def main(): """ Main function of the program - """ + """ s_ticker = "" s_start = "" @@ -188,12 +188,12 @@ def main(): s_interval = "1440min" # Set stock by default to speed up testing - s_ticker = "AAPL" + s_ticker = "BB" s_start = datetime.strptime("2020-06-04", "%Y-%m-%d") ts = TimeSeries(key=cfg.API_KEY_ALPHAVANTAGE, output_format='pandas') df_stock, d_stock_metadata = ts.get_daily_adjusted(symbol=s_ticker, outputsize='full') df_stock.sort_index(ascending=True, inplace=True) - df_stock = df_stock[s_start:] + df_stock = df_stock[s_start:] # Delete above in the future # Add list of arguments that the main parser accepts @@ -201,7 +201,7 @@ def main(): menu_parser.add_argument('opt', choices=['help', 'quit', 'q', 'clear', 'load', 'view', 'disc', 'sen', 'res', 'fa', 'ta', 'dd', 'pred']) - + # Print first welcome message and help print("\nWelcome to Didier's Gamestonk Terminal\n") print_help(s_ticker, s_start, s_interval, b_is_stock_market_open()) @@ -215,7 +215,7 @@ def main(): if not len(as_input): print("") continue - + # Parse main command of the list of possible commands try: (ns_known_args, l_args) = menu_parser.parse_known_args(as_input.split()) @@ -230,7 +230,7 @@ def main(): elif (ns_known_args.opt == 'quit') or (ns_known_args.opt == 'q'): print("Hope you made money today. Good bye my lover, good bye my friend.\n") return - + elif ns_known_args.opt == 'clear': print("Clearing stock ticker to be used for analysis") s_ticker = "" @@ -304,7 +304,7 @@ def main(): # PREDICTION TECHNIQUES elif ns_known_args.opt == 'pred': - + if s_interval == "1440min": b_quit = pm.pred_menu(df_stock, s_ticker, s_start, s_interval) # If stock data is intradaily, we need to get data again as prediction techniques work on daily adjusted data diff --git a/helper_funcs.py b/helper_funcs.py index 27a0aa17f08..e98f26af571 100644 --- a/helper_funcs.py +++ b/helper_funcs.py @@ -2,7 +2,10 @@ from pytz import timezone from holidays import US as holidaysUS from datetime import datetime, time as Time +import re +import numpy as np import sys +import iso8601 import matplotlib import matplotlib.pyplot as plt from datetime import timedelta @@ -147,11 +150,11 @@ def long_number_format(num): magnitude = 0 while abs(num) >= 1000: magnitude += 1 - num /= 1000.0 + num /= 1000.0 if num.is_integer(): return '%d%s' % (num, ['', ' K', ' M', ' B', ' T', ' P'][magnitude]) else: - return '%.3f%s' % (num, ['', ' K', ' M', ' B', ' T', ' P'][magnitude]) + return '%.3f%s' % (num, ['', ' K', ' M', ' B', ' T', ' P'][magnitude]) if isinstance(num, int): num = str(num) if num.lstrip("-").isdigit(): @@ -160,11 +163,11 @@ def long_number_format(num): magnitude = 0 while abs(num) >= 1000: magnitude += 1 - num /= 1000.0 + num /= 1000.0 if num.is_integer(): return '%d%s' % (num, ['', ' K', ' M', ' B', ' T', ' P'][magnitude]) else: - return '%.3f%s' % (num, ['', ' K', ' M', ' B', ' T', ' P'][magnitude]) + return '%.3f%s' % (num, ['', ' K', ' M', ' B', ' T', ' P'][magnitude]) return num @@ -175,10 +178,10 @@ def clean_data_values_to_float(val): val = val[1:] if val.endswith(')'): val = val[:-1] - + if val == '-': val = '0' - + # Convert percentage to decimal if val.endswith('%'): val = float(val[:-1])/100.0 @@ -206,9 +209,9 @@ def int_or_round_float(x): # ----------------------------------------------------------------------------------------------------------------------- -def divide_chunks(l, n): - # looping till length l - for i in range(0, len(l), n): +def divide_chunks(l, n): + # looping till length l + for i in range(0, len(l), n): yield l[i:i + n] @@ -230,5 +233,37 @@ def get_next_stock_market_days(last_stock_day, n_next_days): else: n_days += 1 l_pred_days.append(last_stock_day) - + return l_pred_days + + +# ----------------------------------------------------------------------------------------------------------------------- +def get_data(tweet): + if '+' in tweet['created_at']: + s_datetime = tweet['created_at'].split(' +')[0] + else: + s_datetime = iso8601.parse_date(tweet['created_at']).strftime("%Y-%m-%d %H:%M:%S") + + if 'full_text' in tweet.keys(): + s_text = tweet['full_text'] + else: + s_text = tweet['text'] + + data = {'created_at': s_datetime, + 'text': s_text } + return data + + +# ----------------------------------------------------------------------------------------------------------------------- +def clean_tweet(tweet, s_ticker): + whitespace = re.compile(r"\s+") + web_address = re.compile(r"(?i)http(s):\/\/[a-z0-9.~_\-\/]+") + ticker = re.compile(r"(?i)@{}(?=\b)".format(s_ticker)) + user = re.compile(r"(?i)@[a-z0-9_]+") + + tweet = whitespace.sub(' ', tweet) + tweet = web_address.sub('', tweet) + tweet = ticker.sub(s_ticker, tweet) + tweet = user.sub('', tweet) + + return tweet diff --git a/prediction_techniques/arima.py b/prediction_techniques/arima.py index 8aa651c33b0..0cdcf998f5e 100644 --- a/prediction_techniques/arima.py +++ b/prediction_techniques/arima.py @@ -10,25 +10,25 @@ from statsmodels.tsa.arima.model import ARIMA # -------------------------------------------------- ARIMA -------------------------------------------------- def arima(l_args, s_ticker, s_interval, df_stock): parser = argparse.ArgumentParser(prog='arima', - description="""In statistics and econometrics, and in particular in time - series analysis, an autoregressive integrated moving average (ARIMA) model - is a generalization of an autoregressive moving average (ARMA) model. Both - of these models are fitted to time series data either to better understand - the data or to predict future points in the series (forecasting). - ARIMA(p,d,q) where parameters p, d, and q are non-negative integers, p is - the order (number of time lags) of the autoregressive model, d is the degree - of differencing (the number of times the data have had past values subtracted), + description="""In statistics and econometrics, and in particular in time + series analysis, an autoregressive integrated moving average (ARIMA) model + is a generalization of an autoregressive moving average (ARMA) model. Both + of these models are fitted to time series data either to better understand + the data or to predict future points in the series (forecasting). + ARIMA(p,d,q) where parameters p, d, and q are non-negative integers, p is + the order (number of time lags) of the autoregressive model, d is the degree + of differencing (the number of times the data have had past values subtracted), and q is the order of the moving-average model.""") - parser.add_argument('-d', "--days", action="store", dest="n_days", type=check_positive, default=5, + parser.add_argument('-d', "--days", action="store", dest="n_days", type=check_positive, default=5, help='prediction days.') - parser.add_argument('-i', "--ic", action="store", dest="s_ic", type=str, default='aic', + parser.add_argument('-i', "--ic", action="store", dest="s_ic", type=str, default='aic', choices=['aic', 'aicc', 'bic', 'hqic', 'oob'], help='information criteria.') - parser.add_argument('-s', "--seasonal", action="store_true", default=False, dest="b_seasonal", - help='weekly seasonality flag.') - parser.add_argument('-o', "--order", action="store", dest="s_order", type=str, - help='arima model order.') - parser.add_argument('-r', "--results", action="store_true", dest="b_results", default=False, + parser.add_argument('-s', "--seasonal", action="store_true", default=False, dest="b_seasonal", + help='Use weekly seasonal data.') + parser.add_argument('-o', "--order", action="store", dest="s_order", type=str, + help='arima model order (p,d,q) in format: pdq.') + parser.add_argument('-r', "--results", action="store_true", dest="b_results", default=False, help='results about ARIMA summary flag.') try: diff --git a/prediction_techniques/pred_menu.py b/prediction_techniques/pred_menu.py index 3f4978bb7b8..b04defbed93 100644 --- a/prediction_techniques/pred_menu.py +++ b/prediction_techniques/pred_menu.py @@ -45,14 +45,14 @@ def print_prediction(s_ticker, s_start, s_interval): print(" lstm Long-Short Term Memory") print("") - + # ---------------------------------------------------- MENU ---------------------------------------------------- def pred_menu(df_stock, s_ticker, s_start, s_interval): # Add list of arguments that the prediction techniques parser accepts - pred_parser = argparse.ArgumentParser(prog='technical_analysis', add_help=False) + pred_parser = argparse.ArgumentParser(prog='pred', add_help=False) pred_parser.add_argument('cmd', choices=['help', 'q', 'quit', - 'sma', 'knn', + 'sma', 'knn', 'linear', 'quadratic', 'cubic', 'regression', 'arima', 'prophet', 'mlp', 'rnn', 'lstm']) @@ -62,7 +62,7 @@ def pred_menu(df_stock, s_ticker, s_start, s_interval): while True: # Get input command from user as_input = input('> ') - + # Parse prediction techniques command of the list of possible commands try: (ns_known_args, l_args) = pred_parser.parse_known_args(as_input.split()) diff --git a/sentiment/reddit_api.py b/sentiment/reddit_api.py index 6f554178409..33e8c19c7b9 100644 --- a/sentiment/reddit_api.py +++ b/sentiment/reddit_api.py @@ -18,11 +18,11 @@ 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 @@ -30,9 +30,10 @@ def get_last_time_market_was_open(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') + parser = argparse.ArgumentParser(prog='watchlist', + description="""Print other users watchlist. [Source: 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) @@ -61,21 +62,21 @@ def watchlist(l_args): 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: @@ -120,18 +121,18 @@ def watchlist(l_args): print(f"\n{s_datetime} - {submission.title}") print(f"{s_link}") t_post = PrettyTable(['Subreddit', 'Flair', 'Score', '# Comments', 'Upvote %', "Awards"]) - t_post.add_row([submission.subreddit, submission.link_flair_text, submission.score, + t_post.add_row([submission.subreddit, submission.link_flair_text, submission.score, submission.num_comments,f"{round(100*submission.upvote_ratio)}%", s_all_awards]) print(t_post) - print("\n") + print("") # 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 @@ -160,15 +161,15 @@ def watchlist(l_args): # ------------------------------------------------------------------------------------------------------------------- def popular_tickers(l_args): - parser = argparse.ArgumentParser(prog='popular', + 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, + 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") + help="look for the tickers from those n past days.") try: (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) @@ -176,7 +177,7 @@ def popular_tickers(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: @@ -211,41 +212,41 @@ def popular_tickers(l_args): # 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, + # 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) + # 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)) - # Add another author's name to the parsed watchlists - l_watchlist_author.append(submission.author.name) + if l_tickers_found: - # 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 + 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:") @@ -265,15 +266,16 @@ def popular_tickers(l_args): except: print("") - + # ------------------------------------------------------------------------------------------------------------------- def spac(l_args): - parser = argparse.ArgumentParser(prog='spac', + parser = argparse.ArgumentParser(prog='spac', description=""" Show other users SPACs announcement [Reddit] """) - parser.add_argument('-l', "--limit", action="store", dest="n_limit", type=check_positive, default=5, help='Limit of posts with SPACs retrieved') + parser.add_argument('-l', "--limit", action="store", dest="n_limit", type=check_positive, default=5, + help='limit of posts with SPACs retrieved') parser.add_argument('-d', "--days", action="store", dest="n_days", type=check_positive, default=5, - help="Look for the tickers from those n past days") + help="look for the tickers from those n past days") try: (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) @@ -295,7 +297,7 @@ def spac(l_args): n_ts_after = int((datetime.today() - timedelta(days=ns_parser.n_days)).timestamp()) l_sub_reddits = ['pennystocks', 'RobinHoodPennyStocks', 'Daytrading', 'StockMarket', 'stocks', 'investing', 'wallstreetbets'] - + warnings.filterwarnings("ignore") # To avoid printing the warning psaw_api = PushshiftAPI() submissions = psaw_api.search_submissions(after=n_ts_after, @@ -308,14 +310,14 @@ def spac(l_args): 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 not l_tickers_found: @@ -362,18 +364,18 @@ def spac(l_args): print(f"{s_datetime} - {submission.title}") print(f"{s_link}") t_post = PrettyTable(['Subreddit', 'Flair', 'Score', '# Comments', 'Upvote %', "Awards"]) - t_post.add_row([submission.subreddit, submission.link_flair_text, submission.score, + t_post.add_row([submission.subreddit, submission.link_flair_text, submission.score, submission.num_comments,f"{round(100*submission.upvote_ratio)}%", s_all_awards]) print(t_post) print("\n") # 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 @@ -402,10 +404,12 @@ def spac(l_args): # ------------------------------------------------------------------------------------------------------------------- def spac_community(l_args): - parser = argparse.ArgumentParser(prog='spac_c', - description=""" Show other users SPACs announcement under subreddit SPACs [Reddit] """) - parser.add_argument('-l', "--limit", action="store", dest="n_limit", type=check_positive, default=10, help='Limit of posts with SPACs retrieved') - parser.add_argument('-p', "--popular", action="store_true", default=False, dest="b_popular", help='If popular, the posts retrieved are based on score rather than time') + parser = argparse.ArgumentParser(prog='spac_c', + description="""Print other users SPACs announcement under subreddit 'SPACs' [Source: Reddit]""") + parser.add_argument('-l', "--limit", action="store", dest="n_limit", type=check_positive, default=10, + help='limit of posts with SPACs retrieved') + parser.add_argument('-p', "--popular", action="store_true", default=False, dest="b_popular", + help='popular flag, if true the posts retrieved are based on score rather than time') try: (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) @@ -437,18 +441,18 @@ def spac_community(l_args): 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.author.name not in l_watchlist_author: # and submission.link_flair_text not in ['Yolo', 'Meme'] \ - + # 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: # Add another author's name to the parsed watchlists l_watchlist_author.append(submission.author.name) @@ -490,11 +494,11 @@ def spac_community(l_args): print(f"{s_datetime} - {submission.title}") print(f"{s_link}") t_post = PrettyTable(['Subreddit', 'Flair', 'Score', '# Comments', 'Upvote %', "Awards"]) - t_post.add_row([submission.subreddit, submission.link_flair_text, submission.score, + t_post.add_row([submission.subreddit, submission.link_flair_text, submission.score, submission.num_comments,f"{round(100*submission.upvote_ratio)}%", s_all_awards]) print(t_post) print("\n") - + # Check if search_submissions didn't get anymore posts else: break @@ -516,17 +520,19 @@ def spac_community(l_args): print("The following stock tickers have been mentioned across the previous SPACs:") print(s_watchlist_tickers[:-2]) print("") - + except: print("") # ------------------------------------------------------------------------------------------------------------------- def wsb_community(l_args): - parser = argparse.ArgumentParser(prog='wsb', - description="""Show what WSB gang are up to in subreddit wallstreetbets [Reddit] """) - parser.add_argument('-l', "--limit", action="store", dest="n_limit", type=check_positive, default=10, help='Limit of posts') - parser.add_argument('-n', "--new", action="store_true", default=False, dest="b_new", help='If new, the posts retrieved are based on more recent rather than score') + parser = argparse.ArgumentParser(prog='wsb', + description="""Print what WSB gang are up to in subreddit wallstreetbets. [Source: Reddit]""") + parser.add_argument('-l', "--limit", action="store", dest="n_limit", type=check_positive, + default=10, help='limit of posts to print.') + parser.add_argument('-n', "--new", action="store_true", default=False, dest="b_new", + help='new flag, if true the posts retrieved are based on being more recent rather than their score.') try: (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) @@ -542,9 +548,7 @@ def wsb_community(l_args): password=cfg.API_REDDIT_PASSWORD) d_submission = {} - d_watchlist_tickers = {} l_watchlist_links = list() - l_watchlist_author = list() psaw_api = PushshiftAPI() @@ -552,91 +556,52 @@ def wsb_community(l_args): submissions = praw_api.subreddit('wallstreetbets').new(limit=ns_parser.n_limit) else: submissions = praw_api.subreddit('wallstreetbets').hot(limit=ns_parser.n_limit) - + 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.author.name not in l_watchlist_author: # and submission.link_flair_text not in ['Yolo', 'Meme'] \ - - # 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: - # Add another author's name to the parsed watchlists - l_watchlist_author.append(submission.author.name) + if not submission.removed_by_category: - # 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}") - 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] - # 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 + } - # 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"{s_datetime} - {submission.title}") - print(f"{s_link}") - t_post = PrettyTable(['Subreddit', 'Flair', 'Score', '# Comments', 'Upvote %', "Awards"]) - t_post.add_row([submission.subreddit, submission.link_flair_text, submission.score, - submission.num_comments,f"{round(100*submission.upvote_ratio)}%", s_all_awards]) - print(t_post) - print("\n") - + # Print post data collected so far + print(f"{s_datetime} - {submission.title}") + print(f"{s_link}") + t_post = PrettyTable(['Subreddit', 'Flair', 'Score', '# Comments', 'Upvote %', "Awards"]) + t_post.add_row([submission.subreddit, submission.link_flair_text, submission.score, + submission.num_comments,f"{round(100*submission.upvote_ratio)}%", s_all_awards]) + print(t_post) + print("") # Check if search_submissions didn't get anymore posts else: break - - 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]) - 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 SPACs:") - print(s_watchlist_tickers[:-2]) - print("") - + print("") except: print("") \ No newline at end of file diff --git a/technical_analysis/ta_menu.py b/technical_analysis/ta_menu.py index f243f720cfd..ffea7aa291f 100644 --- a/technical_analysis/ta_menu.py +++ b/technical_analysis/ta_menu.py @@ -55,7 +55,7 @@ def print_technical_analysis(s_ticker, s_start, s_interval): def ta_menu(df_stock, s_ticker, s_start, s_interval): # Add list of arguments that the technical analysis parser accepts - ta_parser = argparse.ArgumentParser(prog='technical_analysis', add_help=False) + ta_parser = argparse.ArgumentParser(prog='ta', add_help=False) ta_parser.add_argument('cmd', choices=['help', 'q', 'quit', 'ema', 'sma', 'vwap', # overlap 'cci', 'macd', 'rsi', 'stoch', # momentum @@ -69,7 +69,7 @@ def ta_menu(df_stock, s_ticker, s_start, s_interval): 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) = ta_parser.parse_known_args(as_input.split()) @@ -111,7 +111,7 @@ def ta_menu(df_stock, s_ticker, s_start, s_interval): elif ns_known_args.cmd == 'stoch': ta_momentum.stoch(l_args, s_ticker, s_interval, df_stock) - + # ---------------------------------------------------- TREND ---------------------------------------------------- elif ns_known_args.cmd == 'adx': ta_trend.adx(l_args, s_ticker, s_interval, df_stock)