From 7c9663b313b1910ef32b9dbfa0e4e18267fb2a17 Mon Sep 17 00:00:00 2001 From: didier Date: Sat, 26 Dec 2020 16:38:00 +0000 Subject: [PATCH] Refactor code to allow daily vs intraday data --- stock_market_cli.py | 41 +++++---- stock_market_menu.py | 129 +++++++++++++++-------------- stock_market_technical_analysis.py | 70 +++++++++------- 3 files changed, 133 insertions(+), 107 deletions(-) diff --git a/stock_market_cli.py b/stock_market_cli.py index 17989bc02b7..101c4fdfaca 100644 --- a/stock_market_cli.py +++ b/stock_market_cli.py @@ -11,6 +11,14 @@ Finviz for data + Check AlphaVantage for TA as well. + Add s_ylabel and s_legend to plot_ta or plot_stock_ta + + Daily Stock vs Intraday 5min Stock + Only show menu that allow daily TA or intraday TA, + i.e. if daily Stock don't show VWAP bc doesn't make sense! + Fix the 'help' params + """ from alpha_vantage.timeseries import TimeSeries @@ -27,7 +35,7 @@ import stock_market_technical_analysis as smta import stock_market_fundamental_analysis as smfa # ----------------------------------------------------------------------------------------------------------------------- -def print_help(s_ticker, s_start, b_is_market_open): +def print_help(s_ticker, s_start, s_interval, b_is_market_open): """ Print help """ print("What do you want to do?") @@ -40,10 +48,12 @@ def print_help(s_ticker, s_start, b_is_market_open): print(" help help to see this menu again") print(" quit to abandon the program") + s_intraday = (f'Intraday {s_interval}', 'Daily')[s_interval == "1440min"] + if s_ticker and s_start: - print(f"\nStock: {s_ticker} (from {s_start.strftime('%Y-%m-%d')})") + print(f"\n{s_intraday} Stock: {s_ticker} (from {s_start.strftime('%Y-%m-%d')})") elif s_ticker: - print(f"\nStock: {s_ticker}") + print(f"\n{s_intraday} Stock: {s_ticker}") else: print("\nStock: ?") @@ -55,7 +65,8 @@ def print_help(s_ticker, s_start, b_is_market_open): print(" sma simple moving average [daily]") print(" ema exponential moving average [daily]") print(" macd moving average convergence/divergence [daily]") - print(" vwap volume weighted average price [intraday]") + if s_interval != "1440min": + print(" vwap volume weighted average price [intraday]") print(" stoch stochastic oscillator [daily]") print(" rsi relative strength index [daily]") @@ -86,6 +97,8 @@ def main(): s_ticker = "" s_start = "" df_stock = pd.DataFrame() + b_intraday = False + s_interval = "1440min" # Set stock by default to speed up testing s_ticker = "TSLA" @@ -104,7 +117,7 @@ def main(): # Print first welcome message and help print("\nWelcome to Didier's Stock Market Bot\n") - print_help(s_ticker, s_start, b_is_stock_market_open()) + print_help(s_ticker, s_start, s_interval, b_is_stock_market_open()) print("\n") # Loop forever and ever @@ -136,17 +149,17 @@ def main(): # ---------------------------------------------------- LOAD ---------------------------------------------------- elif ns_known_args.cmd == 'load': - [s_ticker, s_start, df_stock] = smm.load(l_args, s_ticker, s_start, df_stock) + [s_ticker, s_start, s_interval, df_stock] = smm.load(l_args, s_ticker, s_start, s_interval, df_stock) continue # ---------------------------------------------------- VIEW ---------------------------------------------------- elif ns_known_args.cmd == 'view': - smm.view(l_args, s_ticker, s_start, df_stock) + smm.view(l_args, s_ticker, s_start, s_interval, df_stock) continue # ---------------------------------------------------- HELP ---------------------------------------------------- elif ns_known_args.cmd == 'help': - print_help(s_ticker, s_start, b_is_stock_market_open()) + print_help(s_ticker, s_start, s_interval, b_is_stock_market_open()) # ----------------------------------------------------- QUIT ---------------------------------------------------- elif ns_known_args.cmd == 'quit': @@ -170,32 +183,32 @@ def main(): # ---------------------------------------------------- SMA ---------------------------------------------------- elif ns_known_args.cmd == 'sma': - smta.sma(l_args, s_ticker, df_stock) + smta.sma(l_args, s_ticker, s_interval, df_stock) continue # ---------------------------------------------------- EMA ---------------------------------------------------- elif ns_known_args.cmd == 'ema': - smta.ema(l_args, s_ticker, df_stock) + smta.ema(l_args, s_ticker, s_interval, df_stock) continue # ---------------------------------------------------- MACD ---------------------------------------------------- elif ns_known_args.cmd == 'macd': - smta.macd(l_args, s_ticker, df_stock) + smta.macd(l_args, s_ticker, s_interval, df_stock) continue # ---------------------------------------------------- VWAP ---------------------------------------------------- elif ns_known_args.cmd == 'vwap': - smta.vwap(l_args, s_ticker, s_start) + smta.vwap(l_args, s_ticker, s_interval, df_stock) continue # ---------------------------------------------------- STOCH ---------------------------------------------------- elif ns_known_args.cmd == 'stoch': - smta.stoch(l_args, s_ticker, df_stock) + smta.stoch(l_args, s_ticker, s_interval, df_stock) continue # ---------------------------------------------------- RSI ---------------------------------------------------- elif ns_known_args.cmd == 'rsi': - smta.rsi(l_args, s_ticker, df_stock) + smta.rsi(l_args, s_ticker, s_interval, df_stock) continue # -------------------------------------------------------------------------------------------------------------- diff --git a/stock_market_menu.py b/stock_market_menu.py index 29b65de9d7d..36f61ad085d 100644 --- a/stock_market_menu.py +++ b/stock_market_menu.py @@ -1,5 +1,6 @@ import argparse from alpha_vantage.timeseries import TimeSeries +import re import config_bot as cfg import pandas as pd from stock_market_helper_funcs import * @@ -26,67 +27,62 @@ def gainers(l_args): print("") # ----------------------------------------------------- LOAD ----------------------------------------------------- -def load(l_args, s_ticker, s_start, df_stock): - parser = argparse.ArgumentParser(prog='load', - description=""" Given a stock market ticker, and an optional starting time, - load a stock in order to perform analysis""") - 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") +def load(l_args, s_ticker, s_start, s_interval, df_stock): + parser = argparse.ArgumentParser(prog='load', description=""" Load a stock in order to perform analysis""") + 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") + parser.add_argument('-i', "--interval", action="store", dest="n_interval", type=int, default=1440, choices=[1,5,15,30,60], help="Intraday stock minutes") try: (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) except SystemExit: print("") - return [s_ticker, s_start, df_stock] + return [s_ticker, s_start, s_interval, df_stock] if l_unknown_args: print(f"The following args couldn't be interpreted: {l_unknown_args}") + # Update values: + s_ticker = ns_parser.s_ticker + s_start = ns_parser.s_start_date + s_interval = str(ns_parser.n_interval)+'min' + try: - # Currently only supports daily stocks ts = TimeSeries(key=cfg.API_KEY_ALPHAVANTAGE, output_format='pandas') - df_stock, d_stock_metadata = ts.get_daily_adjusted(symbol=ns_parser.s_ticker, outputsize='full') + # Daily + if s_interval == "1440min": + 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) + except: print("Either the ticker or the API_KEY are invalids. Try again!") - return [s_ticker, s_start, df_stock] + return [s_ticker, s_start, s_interval, df_stock] - s_ticker = ns_parser.s_ticker - - if ns_parser.s_start_date: - s_start = ns_parser.s_start_date - - # 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:] + s_intraday = (f'Intraday {s_interval}', 'Daily')[s_interval == "1440min"] if s_start: - print(f"Loading {s_ticker} stock with starting period {s_start.strftime('%Y-%m-%d')} for analysis.\n") + # Slice dataframe from the starting date YYYY-MM-DD selected + df_stock = df_stock[ns_parser.s_start_date:] + print(f"Loading {s_intraday} {s_ticker} stock with starting period {s_start.strftime('%Y-%m-%d')} for analysis.\n") else: - print(f"Loading {s_ticker} stock for analysis.\n") + print(f"Loading {s_intraday} {s_ticker} stock for analysis.\n") - return [s_ticker, s_start, df_stock] + return [s_ticker, s_start, s_interval, df_stock] # ----------------------------------------------------- VIEW ----------------------------------------------------- -def view(l_args, s_ticker, s_start, df_stock): - parser = argparse.ArgumentParser(prog='view', - description='Given a stock market ticker, allows to see historical data of \ - the corresponding stock. Note that the alpha_vantage key is necessary \ - for this to work!') +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') else: parser.add_argument('-t', "--ticker", action="store", dest="s_ticker", required=True, help='Stock ticker') - - parser.add_argument('-i', "--intraday", action="store_true", dest="b_intraday", default=False, help='Stock ticker') - parser.add_argument("--interval", action="store", dest="n_interval", type=int, default=15, choices=[1,5,15,30,60], 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") + parser.add_argument('-s', "--start", type=valid_date, dest="s_start_date", help="The starting date (format YYYY-MM-DD) of the stock") + parser.add_argument('-i', "--interval", action="store", dest="n_interval", type=int, default=0, choices=[1,5,15,30,60], help="Intraday stock minutes") parser.add_argument("--type", action="store", dest="n_type", type=check_positive, default=5, # in case it's daily - help='12345 corresponds to types: 1. open; 2. high; 3.low; 4. close; \ - while 14 corresponds to types: 1.open; 4. close') + help='1234 corresponds to types: 1. open; 2. high; 3.low; 4. close; while 14 corresponds to types: 1.open; 4. close') try: (ns_parser, l_unknown_args) = parser.parse_known_args(l_args) @@ -97,41 +93,34 @@ def view(l_args, s_ticker, s_start, df_stock): if l_unknown_args: print(f"The following args couldn't be interpreted: {l_unknown_args}") + # Update values: + s_ticker = ns_parser.s_ticker + if ns_parser.s_start_date: + s_start = ns_parser.s_start_date + + # A new interval intraday period was given + if ns_parser.n_interval != 0: + s_interval = str(ns_parser.n_interval)+'min' + try: - # We want to view a different stock than the one we loaded - if ns_parser.s_ticker != s_ticker: - ts = TimeSeries(key=cfg.API_KEY_ALPHAVANTAGE, output_format='pandas') - df_stock, d_stock_metadata = ts.get_daily_adjusted(symbol=ns_parser.s_ticker, outputsize='full') - # We want to see intraday data about this stock - elif ns_parser.b_intraday: - ts = TimeSeries(key=cfg.API_KEY_ALPHAVANTAGE, output_format='pandas') - s_interval = str(ns_parser.n_interval)+'min' - df_stock, d_stock_metadata = ts.get_intraday(symbol=ns_parser.s_ticker, outputsize='full', interval=s_interval) + 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=s_ticker, outputsize='full') + # Intraday + else: + 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 # 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_stock = df_stock[s_start:] - # Intraday data - if ns_parser.b_intraday: - # The default doesn't exist for intradaily data - if ns_parser.n_type == 5: - ln_col_idx = [3] - else: - ln_col_idx = [int(x)-1 for x in list(str(ns_parser.n_type))] - # Check that the types given are not bigger than 3, as there are only 4 types (0-3) - if len([i for i in ln_col_idx if i > 3]) > 0: - 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) - # Daily data - else: - # The default doesn't exist for intradaily data + # Daily + if s_interval == "1440min": + # The default doesn't exist for intradaily data ln_col_idx = [int(x)-1 for x in list(str(ns_parser.n_type))] if 4 not in ln_col_idx: ln_col_idx.append(4) @@ -141,5 +130,19 @@ def view(l_args, s_ticker, s_start, df_stock): return # Append last column of df to be filtered which corresponds to: 6. Volume ln_col_idx.append(5) + # Intraday + else: + # The default doesn't exist for intradaily data + if ns_parser.n_type == 5: + ln_col_idx = [3] + else: + ln_col_idx = [int(x)-1 for x in list(str(ns_parser.n_type))] + # Check that the types given are not bigger than 3, as there are only 4 types (0-3) + if len([i for i in ln_col_idx if i > 3]) > 0: + 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) + # Plot view of the stock plot_view_stock(df_stock.iloc[:, ln_col_idx], ns_parser.s_ticker) diff --git a/stock_market_technical_analysis.py b/stock_market_technical_analysis.py index 522e8ca5894..b20f3028380 100644 --- a/stock_market_technical_analysis.py +++ b/stock_market_technical_analysis.py @@ -6,7 +6,7 @@ import config_bot as cfg from stock_market_helper_funcs import * # ----------------------------------------------------- SMA ----------------------------------------------------- -def sma(l_args, s_ticker, df_stock): +def sma(l_args, s_ticker, s_interval, df_stock): 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 @@ -28,15 +28,21 @@ def sma(l_args, s_ticker, df_stock): print(f"The following args couldn't be interpreted: {l_unknown_args}") try: - df_ta = ta.sma(df_stock['5. adjusted close'], timeperiod=ns_parser.n_time_period).dropna() - plot_stock_ta(df_stock['5. adjusted close'], s_ticker, df_ta, f"{ns_parser.n_time_period} SMA") + # Daily + if s_interval == "1440min": + df_ta = ta.sma(df_stock['5. adjusted close'], timeperiod=ns_parser.n_time_period).dropna() + plot_stock_ta(df_stock['5. adjusted close'], s_ticker, df_ta, f"{ns_parser.n_time_period} SMA") + # Intraday + else: + df_ta = ta.sma(df_stock['4. close'], timeperiod=ns_parser.n_time_period).dropna() + plot_stock_ta(df_stock['4. close'], s_ticker, df_ta, f"{ns_parser.n_time_period} SMA") except: print("") return # ----------------------------------------------------- EMA ----------------------------------------------------- -def ema(l_args, s_ticker, df_stock): +def ema(l_args, s_ticker, s_interval, df_stock): 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 @@ -59,15 +65,21 @@ def ema(l_args, s_ticker, df_stock): print(f"The following args couldn't be interpreted: {l_unknown_args}") try: - df_ta = ta.ema(df_stock['5. adjusted close'], timeperiod=ns_parser.n_time_period).dropna() - plot_stock_ta(df_stock['5. adjusted close'], s_ticker, df_ta, f"{ns_parser.n_time_period} EMA") + # Daily + if s_interval == "1440min": + df_ta = ta.ema(df_stock['5. adjusted close'], timeperiod=ns_parser.n_time_period).dropna() + plot_stock_ta(df_stock['5. adjusted close'], s_ticker, df_ta, f"{ns_parser.n_time_period} EMA") + # Intraday + else: + df_ta = ta.ema(df_stock['4. close'], timeperiod=ns_parser.n_time_period).dropna() + plot_stock_ta(df_stock['4. close'], s_ticker, df_ta, f"{ns_parser.n_time_period} EMA") except: print("") return # ----------------------------------------------------- MACD ----------------------------------------------------- -def macd(l_args, s_ticker, df_stock): +def macd(l_args, s_ticker, s_interval, df_stock): parser = argparse.ArgumentParser(prog='macd', description=""" The Moving Average Convergence Divergence (MACD) is the difference between two Exponential Moving Averages. The Signal line is an Exponential Moving @@ -98,12 +110,17 @@ def macd(l_args, s_ticker, df_stock): print(f"The following args couldn't be interpreted: {l_unknown_args}") try: - df_ta = ta.macd(df_stock['5. adjusted close'], - fast=ns_parser.n_fast, - slow=ns_parser.n_slow, - signal=ns_parser.n_signal, - offset=ns_parser.n_offset).dropna() - + # Daily + if s_interval == "1440min": + df_ta = ta.macd(df_stock['5. adjusted close'], + fast=ns_parser.n_fast, slow=ns_parser.n_slow, + signal=ns_parser.n_signal, offset=ns_parser.n_offset).dropna() + # Intraday + else: + df_ta = ta.macd(df_stock['4. close'], + fast=ns_parser.n_fast, slow=ns_parser.n_slow, + signal=ns_parser.n_signal, offset=ns_parser.n_offset).dropna() + plot_ta(s_ticker, df_ta, f"{ns_parser.n_fast}-{ns_parser.n_slow}-{ns_parser.n_signal}-{ns_parser.n_offset} MACD") except: print("") @@ -111,7 +128,7 @@ def macd(l_args, s_ticker, df_stock): # ----------------------------------------------------- VWAP ----------------------------------------------------- -def vwap(l_args, s_ticker, s_start): +def vwap(l_args, s_ticker, s_interval, df_stock): parser = argparse.ArgumentParser(prog='vwap', description=""" The Volume Weighted Average Price that measures the average typical price by volume. It is typically used with intraday charts to identify general @@ -129,27 +146,15 @@ def vwap(l_args, s_ticker, s_start): print(f"The following args couldn't be interpreted: {l_unknown_args}") try: - ts = TimeSeries(key=cfg.API_KEY_ALPHAVANTAGE, output_format='pandas') - s_interval = str(ns_parser.n_interval)+'min' - df_stock, d_stock_metadata = ts.get_intraday(symbol=s_ticker, outputsize='full', interval=s_interval) - - if s_start: - df_stock = df_stock[s_start:] - df_ta = ta.vwap(high=df_stock['2. high'], low=df_stock['3. low'], close=df_stock['4. close'], volume=df_stock['5. volume'], interval=s_interval) plot_stock_ta(df_stock['4. close'], s_ticker, df_ta, f"{s_interval} VWAP") - - # ti = TechIndicators(cfg.API_KEY_FINANCIALMODELINGPREP, output_format='pandas') - # df_ta, d_ta_metadata = ti.get_vwap(symbol=s_ticker, interval=s_interval) - # plot_ta(s_ticker, df_ta, f"{s_interval} VWAP") - except: print("") return # ----------------------------------------------------- STOCH ----------------------------------------------------- -def stoch(l_args, s_ticker, df_stock): +def stoch(l_args, s_ticker, s_interval, df_stock): parser = argparse.ArgumentParser(prog='stoch', description=""" The Stochastic Oscillator measures where the close is in relation to the recent trading range. The values range from zero to 100. %D values over 75 @@ -193,7 +198,7 @@ def stoch(l_args, s_ticker, df_stock): # ----------------------------------------------------- RSI ----------------------------------------------------- -def rsi(l_args, s_ticker, df_stock): +def rsi(l_args, s_ticker, s_interval, df_stock): parser = argparse.ArgumentParser(prog='rsi', description=""" The Relative Strength Index (RSI) calculates a ratio of the recent upward price movements to the absolute price movement. The RSI ranges @@ -214,8 +219,13 @@ def rsi(l_args, s_ticker, df_stock): print(f"The following args couldn't be interpreted: {l_unknown_args}") try: - df_ta = ta.rsi(df_stock['5. adjusted close'], time_period=ns_parser.n_timeperiod).dropna() - + # Daily + if s_interval == "1440min": + df_ta = ta.rsi(df_stock['5. adjusted close'], time_period=ns_parser.n_timeperiod).dropna() + # Intraday + else: + df_ta = ta.rsi(df_stock['4. close'], time_period=ns_parser.n_timeperiod).dropna() + plot_ta(s_ticker, df_ta, f"{ns_parser.n_timeperiod} RSI") except: print("")