diff --git a/stock_market_bot.py b/stock_market_bot.py index ba623a39542..cea1c720c53 100644 --- a/stock_market_bot.py +++ b/stock_market_bot.py @@ -2,13 +2,22 @@ Is this not informative enough? + Load a specific stock + Then apply a specific TA on it + + Do this because AlphaVantage only allows 5 API calls per minutes. + This way we only need 1 call, and can apply TA to the result without + using an API request. + Finviz for data """ + from alpha_vantage.timeseries import TimeSeries import argparse import config_bot as cfg import pandas as pd +import pandas_ta as ta from datetime import datetime import matplotlib import matplotlib.pyplot as plt @@ -21,8 +30,13 @@ def print_help(): """ Print help """ print("What do you want to do?") - print("gainers show latest top gainers") print("view view a specific stock tick") + print("\n--- Screeners:") + print("gainers show latest top gainers") + print("\n--- Technical Analysis:") + print("sma simple moving average") + print("ema exponential moving average") + print("\n--- Others:") print("help help to see this menu again") print("quit to abandon the program") @@ -44,9 +58,9 @@ def valid_date(s): # ----------------------------------------------------------------------------------------------------------------------- -def plot_dataframe(df, symbol): +def plot_view_stock(df, symbol): pfig, axVolume = plt.subplots() - plt.bar(df.index, df['5. volume'].values, color='k') + plt.bar(df.index, df['5. volume'].values, color='k', alpha=0.8) plt.ylabel('Volume') axPrice = axVolume.twinx() plt.plot(df.index, df.iloc[:, :-1]) @@ -61,6 +75,21 @@ def plot_dataframe(df, symbol): plt.show() +# ----------------------------------------------------------------------------------------------------------------------- +def plot_stock_ta(df_stock, s_stock, df_ta, s_ta): + plt.plot(df_stock.index, df_stock['4. close'].values) + plt.plot(df_ta.index, df_ta.values) + plt.title(f"{s_ta} on {s_stock}") + plt.xlim(df_stock.index[0], df_stock.index[-1]) + plt.xlabel('Time') + plt.ylabel('Share Price ($)') + plt.legend([s_stock, s_ta]) + plt.grid(b=True, which='major', color='#666666', linestyle='-') + plt.minorticks_on() + plt.grid(b=True, which='minor', color='#999999', linestyle='-', alpha=0.2) + plt.show() + + # ----------------------------------------------------------------------------------------------------------------------- def main(): """ Main function of the program @@ -71,12 +100,10 @@ def main(): #parser.add_argument('-b', action="store", dest="b", help='example of string') #parser.add_argument('-c', action="store", dest="c", type=int, help='example of int') - main_parser = argparse.ArgumentParser(prog='stock_market_bot', # program name - # disable default help flag so that help can be an option - add_help=False) + main_parser = argparse.ArgumentParser(prog='stock_market_bot', add_help=False) # Add list of arguments that the main parser accepts - main_parser.add_argument('cmd', choices=['quit', 'help', 'gainers' ,'view']) + main_parser.add_argument('cmd', choices=['quit', 'help', 'gainers' ,'view', 'sma', 'ema']) # Print first welcome message and help print("\nWelcome to Didier's Stock Market Bot\n") @@ -114,6 +141,7 @@ def main(): 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)) + # ---------------------------------------------------- VIEW ---------------------------------------------------- elif ns_known_args.cmd == 'view': parser = argparse.ArgumentParser(prog='view', @@ -124,7 +152,7 @@ def main(): parser.add_argument('--type', action="store", dest="n_type", type=check_positive, default=4, help='1234 corresponds to types: 1. open; 2. high; 3.low; 4. close \ while 14 corresponds to types: 1.open; 4. close') - parser.add_argument("-s", "--start", type=s_start_date, dest="start_date", + parser.add_argument("-s", "--start", type=valid_date, dest="s_start_date", help="The starting date (format YYYY-MM-DD) of the stock") try: @@ -138,11 +166,9 @@ def main(): # Currently only supports daily stocks # TODO: Intra-daily perhaps - ts = TimeSeries(key=cfg.API_KEY, - output_format='pandas') + ts = TimeSeries(key=cfg.API_KEY, output_format='pandas') try: - df, meta_data = ts.get_daily(symbol=ns_parser.s_ticker, - outputsize='full') + df_stock, d_stock_metadata = ts.get_daily(symbol=ns_parser.s_ticker, outputsize='full') except: print("Either the ticker or the API_KEY are invalids. Try again!") continue @@ -156,13 +182,107 @@ def main(): # Slice dataframe from the starting date YYYY-MM-DD selected if ns_parser.s_start_date: - df = df[ns_parser.s_start_date:] + df_stock = df_stock[ns_parser.s_start_date:] ln_col_idx.append(4) # Append last column of df to be filtered which corresponds to: 5. Volume - plot_dataframe(df.iloc[:, ln_col_idx], ns_parser.s_ticker) + plot_view_stock(df_stock.iloc[:, ln_col_idx], ns_parser.s_ticker) + # -------------------------------------------------------------------------------------------------------------- + # -------------------------------------------- TECHNICAL ANALYSIS ---------------------------------------------- + # -------------------------------------------------------------------------------------------------------------- + + # ---------------------------------------------------- SMA ---------------------------------------------------- + elif ns_known_args.cmd == 'sma': + parser = argparse.ArgumentParser(prog='sma', + description=""" Moving Averages are used to smooth the data in an array to + help eliminate noise and identify trends. The Simple Moving Average is literally + the simplest form of a moving average. Each output value is the average of the + previous n values. In a Simple Moving Average, each value in the time period carries + equal weight, and values outside of the time period are not included in the average. + This makes it less responsive to recent changes in the data, which can be useful for + filtering out those changes. """) + parser.add_argument('-t', action="store", dest="s_ticker", required=True, help='Stock ticker') + parser.add_argument('--period', action="store", dest="n_time_period", type=check_positive, default=20, + help='Number of data points used to calculate each moving average value.') + parser.add_argument("-s", "--start", type=valid_date, dest="s_start_date", + help="The starting date (format YYYY-MM-DD) of the stock") + try: + (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) + except SystemExit: + print("") + continue + if l_unknown_args: + print(f"The following args couldn't be interpreted: {l_unknown_args}") + + try: # Get stock data + ts = TimeSeries(key=cfg.API_KEY, output_format='pandas') + df_stock, d_stock_metadata = ts.get_daily(symbol=ns_parser.s_ticker, outputsize='full') + except: + print("ERROR! Try again!") + print("Check that: 1. The ticker symbol exists and is valid") + print(" 2. Less than 5 AlphaVantage requests have been made in 1 minute") + print(" 3. The API_KEY is (still) valid") + continue + + # Slice dataframe from the starting date YYYY-MM-DD selected + if ns_parser.s_start_date: + df_stock = df_stock[ns_parser.s_start_date:] + + df_ta = ta.sma(df_stock['4. close'], timeperiod=ns_parser.n_time_period).dropna() + + plot_stock_ta(df_stock, d_stock_metadata['2. Symbol'], df_ta, f"{ns_parser.n_time_period} SMA") + + continue + + + # ---------------------------------------------------- EMA ---------------------------------------------------- + elif ns_known_args.cmd == 'ema': + parser = argparse.ArgumentParser(prog='ema', + description=""" The Exponential Moving Average is a staple of technical + analysis and is used in countless technical indicators. In a Simple Moving + Average, each value in the time period carries equal weight, and values outside + of the time period are not included in the average. However, the Exponential + Moving Average is a cumulative calculation, including all data. Past values have + a diminishing contribution to the average, while more recent values have a greater + contribution. This method allows the moving average to be more responsive to changes + in the data. """) + parser.add_argument('-t', action="store", dest="s_ticker", required=True, help='Stock ticker') + parser.add_argument('--period', action="store", dest="n_time_period", type=check_positive, default=20, + help='Number of data points used to calculate each moving average value.') + parser.add_argument("-s", "--start", type=valid_date, dest="s_start_date", + help="The starting date (format YYYY-MM-DD) of the stock") + try: + (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) + except SystemExit: + print("") + continue + + if l_unknown_args: + print(f"The following args couldn't be interpreted: {l_unknown_args}") + + try: # Get stock data + ts = TimeSeries(key=cfg.API_KEY, output_format='pandas') + df_stock, d_stock_metadata = ts.get_daily(symbol=ns_parser.s_ticker, outputsize='full') + except: + print("ERROR! Try again!") + print("Check that: 1. The ticker symbol exists and is valid") + print(" 2. Less than 5 AlphaVantage requests have been made in 1 minute") + print(" 3. The API_KEY is (still) valid") + continue + + # Slice dataframe from the starting date YYYY-MM-DD selected + if ns_parser.s_start_date: + df_stock = df_stock[ns_parser.s_start_date:] + + df_ta = ta.ema(df_stock['4. close'], timeperiod=ns_parser.n_time_period).dropna() + + plot_stock_ta(df_stock, d_stock_metadata['2. Symbol'], df_ta, f"{ns_parser.n_time_period} EMA") + + continue + + # ---------------------------------------------------- HELP ---------------------------------------------------- elif ns_known_args.cmd == 'help': print_help() @@ -179,4 +299,3 @@ def main(): if __name__ == "__main__": main() - \ No newline at end of file