From a28e6c9f6a9d54d14b984418c324ff94306685ab Mon Sep 17 00:00:00 2001 From: jmaslek Date: Tue, 26 Oct 2021 14:17:41 -0400 Subject: [PATCH] General bug fixes (#862) * Bug fixes * make all yfinance data non tz aware * Get rid of namespace input * candle -> display_candle * Didier I hope you read this. pycharm would not have let me miss committing this file Co-authored-by: James Maslek --- .../common/quantitative_analysis/qa_view.py | 11 +- .../technical_analysis/trendline_api.py | 7 +- .../quantitative_analysis/qa_controller.py | 45 ++- .../stocks/stocks_controller.py | 49 ++- gamestonk_terminal/stocks/stocks_helper.py | 327 ++++++++---------- 5 files changed, 245 insertions(+), 194 deletions(-) diff --git a/gamestonk_terminal/common/quantitative_analysis/qa_view.py b/gamestonk_terminal/common/quantitative_analysis/qa_view.py index f52deada9a6..89d44d25a7e 100644 --- a/gamestonk_terminal/common/quantitative_analysis/qa_view.py +++ b/gamestonk_terminal/common/quantitative_analysis/qa_view.py @@ -500,7 +500,7 @@ def display_unitroot( def display_raw( - df: pd.DataFrame, export: str, sort: str, des: bool, num: int = 20 + df: pd.DataFrame, sort: str = "", des: bool = False, num: int = 20, export: str = "" ) -> None: """Return raw stock data @@ -508,14 +508,14 @@ def display_raw( ---------- df : DataFrame DataFrame with historical information - export : str - Export data as CSV, JSON, XLSX sort : str The column to sort by des : bool Whether to sort descending num : int Number of rows to show + export : str + Export data as CSV, JSON, XLSX """ export_data( @@ -525,13 +525,16 @@ def display_raw( df, ) + if isinstance(df, pd.Series): + df = pd.DataFrame(df) + if sort: df = df.sort_values(by=sort, ascending=des) if gtff.USE_TABULATE_DF: print( tabulate( - df.head(num), + df.tail(num), headers=[x.title() if x != "" else "Date" for x in df.columns], tablefmt="fancy_grid", showindex=True, diff --git a/gamestonk_terminal/common/technical_analysis/trendline_api.py b/gamestonk_terminal/common/technical_analysis/trendline_api.py index 818001ddba4..2331f1ac750 100644 --- a/gamestonk_terminal/common/technical_analysis/trendline_api.py +++ b/gamestonk_terminal/common/technical_analysis/trendline_api.py @@ -1,19 +1,22 @@ """Technical Analysis Trendline Module""" __docformat__ = "numpy" +from typing import Union +from datetime import datetime + import yfinance as yf from pandas.core.frame import DataFrame from scipy.stats import linregress -def load_ticker(ticker: str, start_date: str) -> DataFrame: +def load_ticker(ticker: str, start_date: Union[str, datetime]) -> DataFrame: """Loads a ticker data from Yahoo Finance, adds a data index column data_id and Open-Close High/Low columns. Parameters ---------- ticker : str The stock ticker. - start_date : str + start_date : Union[str,datetime] Start date to load stock ticker data formatted YYYY-MM-DD. Returns diff --git a/gamestonk_terminal/stocks/quantitative_analysis/qa_controller.py b/gamestonk_terminal/stocks/quantitative_analysis/qa_controller.py index 4126d48dce7..579dcc44679 100644 --- a/gamestonk_terminal/stocks/quantitative_analysis/qa_controller.py +++ b/gamestonk_terminal/stocks/quantitative_analysis/qa_controller.py @@ -18,10 +18,12 @@ from gamestonk_terminal.common.quantitative_analysis import ( from gamestonk_terminal import feature_flags as gtff from gamestonk_terminal.stocks.stocks_helper import load from gamestonk_terminal.helper_funcs import ( + EXPORT_ONLY_RAW_DATA_ALLOWED, get_flair, check_positive, check_proportion_range, parse_known_args_and_warn, + try_except, ) from gamestonk_terminal.menu import session from gamestonk_terminal.stocks.quantitative_analysis.factors_view import capm_view @@ -31,7 +33,7 @@ class QaController: """Quantitative Analysis Controller class""" # Command choices - CHOICES = ["?", "cls", "help", "q", "quit", "load", "pick"] + CHOICES = ["?", "cls", "help", "q", "quit", "load", "pick", "raw"] CHOICES_COMMANDS = [ "summary", @@ -118,6 +120,7 @@ Rolling Metrics: skew rolling skewness of distribution of prices kurtosis rolling kurtosis of distribution of prices Other: + raw print raw data decompose decomposition in cyclic-trend, season, and residuals of prices cusum detects abrupt changes using cumulative sum algorithm of prices capm capital asset pricing model @@ -216,6 +219,46 @@ Other: except Exception as e: print(e, "\n") + @try_except + def call_raw(self, other_args: List[str]): + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + add_help=False, + prog="raw", + description=""" + Print raw data to console + """, + ) + parser.add_argument( + "-n", + "--num", + help="Number to show", + type=check_positive, + default=20, + dest="num", + ) + parser.add_argument( + "-d", + "--descend", + action="store_true", + default=False, + dest="descend", + help="Sort in descending order", + ) + + ns_parser = parse_known_args_and_warn( + parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED + ) + if not ns_parser: + return + qa_view.display_raw( + self.stock[self.target], + num=ns_parser.num, + sort="", + des=ns_parser.descend, + export=ns_parser.export, + ) + def call_summary(self, other_args: List[str]): """Process summary command""" parser = argparse.ArgumentParser( diff --git a/gamestonk_terminal/stocks/stocks_controller.py b/gamestonk_terminal/stocks/stocks_controller.py index e21e59416f7..7f78b39edad 100644 --- a/gamestonk_terminal/stocks/stocks_controller.py +++ b/gamestonk_terminal/stocks/stocks_controller.py @@ -30,7 +30,7 @@ from gamestonk_terminal.stocks.government import gov_controller from gamestonk_terminal.stocks.insider import insider_controller from gamestonk_terminal.stocks.research import res_controller from gamestonk_terminal.stocks.screener import screener_controller -from gamestonk_terminal.stocks.stocks_helper import candle, load, quote +from gamestonk_terminal.stocks.stocks_helper import display_candle, load, quote from gamestonk_terminal.stocks.technical_analysis import ta_controller from gamestonk_terminal.helper_funcs import ( valid_date, @@ -220,9 +220,24 @@ Market {('CLOSED', 'OPEN')[b_is_stock_market_open()]} parser = argparse.ArgumentParser( add_help=False, formatter_class=argparse.ArgumentDefaultsHelpFormatter, - prog="data", + prog="candle", description="Shows historic data for a stock", ) + parser.add_argument( + "-s", + "--start_date", + dest="s_start", + type=valid_date, + default=(datetime.now() - timedelta(days=366)).strftime("%Y-%m-%d"), + help="Start date for candle data", + ) + parser.add_argument( + "--plotly", + dest="plotly", + action="store_true", + default=False, + help="Flag to show interactive plot using plotly.", + ) parser.add_argument( "--export", choices=["csv", "json", "xlsx"], @@ -232,7 +247,6 @@ Market {('CLOSED', 'OPEN')[b_is_stock_market_open()]} help="Export dataframe data to csv,json,xlsx file", ) parser.add_argument( - "-s", "--sort", choices=[ "AdjClose", @@ -264,24 +278,39 @@ Market {('CLOSED', 'OPEN')[b_is_stock_market_open()]} default=False, help="Shows raw data instead of chart", ) + parser.add_argument( + "-n", + "--num", + type=check_positive, + help="Number to show if raw selected", + dest="num", + default=20, + ) try: ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return + if not self.ticker: + print("No ticker loaded. First use `load {ticker}`\n") + return if ns_parser.raw: qa_view.display_raw( - self.stock, - ns_parser.export, - ns_parser.sort, - ns_parser.descending, + df=self.stock, + export=ns_parser.export, + sort=ns_parser.sort, + des=ns_parser.descending, + num=ns_parser.num, ) else: - candle( - self.ticker + "." + self.suffix if self.suffix else self.ticker, - other_args, + display_candle( + s_ticker=self.ticker + "." + self.suffix + if self.suffix + else self.ticker, + s_start=ns_parser.s_start, + plotly=ns_parser.plotly, ) except Exception as e: diff --git a/gamestonk_terminal/stocks/stocks_helper.py b/gamestonk_terminal/stocks/stocks_helper.py index da5c4f9c5eb..44482866628 100644 --- a/gamestonk_terminal/stocks/stocks_helper.py +++ b/gamestonk_terminal/stocks/stocks_helper.py @@ -203,6 +203,8 @@ def load( print("") return [s_ticker, s_start, s_interval, df_stock] + df_stock_candidate.index = df_stock_candidate.index.tz_localize(None) + # pylint: disable=no-member df_stock_candidate.sort_index(ascending=True, inplace=True) @@ -329,6 +331,8 @@ def load( print("") return [s_ticker, s_start, s_interval, df_stock] + df_stock_candidate.index = df_stock_candidate.index.tz_localize(None) + if s_start_dt > ns_parser.s_start_date: s_start = pytz.utc.localize(s_start_dt) else: @@ -403,215 +407,184 @@ def load( return [s_ticker, s_start, s_interval, df_stock] -def candle(s_ticker: str, other_args: List[str]): +def display_candle(s_ticker: str, s_start: datetime, plotly: bool): """Shows candle plot of loaded ticker Parameters ---------- s_ticker: str Ticker to display - other_args: str - Argparse arguments + s_start: datetime + Starting date for candle + plotly: bool + Flag to show interactive plotly chart + """ - parser = argparse.ArgumentParser( - add_help=False, - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - prog="candle", - description="Displays candle chart of loaded ticker", - ) - parser.add_argument( - "-s", - "--start_date", - dest="s_start", - type=valid_date, - default=(datetime.now() - timedelta(days=366)).strftime("%Y-%m-%d"), - help="Start date for candle data", - ) - parser.add_argument( - "--plotly", - dest="plotly", - action="store_true", - default=False, - help="Flag to show interactive plot using plotly.", - ) # TODO: Add option to customize plot even further + print(type(s_start)) + df_stock = trend.load_ticker(s_ticker, s_start) + df_stock["ma20"] = df_stock["Close"].rolling(20).mean().fillna(method="bfill") + df_stock["ma50"] = df_stock["Close"].rolling(50).mean().fillna(method="bfill") - try: - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - if not s_ticker: - print("No ticker loaded. First use `load {ticker}`", "\n") - return + df_stock = trend.find_trendline(df_stock, "OC_High", "high") + df_stock = trend.find_trendline(df_stock, "OC_Low", "low") + if not plotly: + mc = mpf.make_marketcolors( + up="green", + down="red", + edge="black", + wick="black", + volume="in", + ohlc="i", + ) - df_stock = trend.load_ticker(s_ticker, ns_parser.s_start) - df_stock["ma20"] = df_stock["Close"].rolling(20).mean().fillna(method="bfill") - df_stock["ma50"] = df_stock["Close"].rolling(50).mean().fillna(method="bfill") + s = mpf.make_mpf_style(marketcolors=mc, gridstyle=":", y_on_right=True) - df_stock = trend.find_trendline(df_stock, "OC_High", "high") - df_stock = trend.find_trendline(df_stock, "OC_Low", "low") - if not ns_parser.plotly: - mc = mpf.make_marketcolors( - up="green", - down="red", - edge="black", - wick="black", - volume="in", - ohlc="i", + ap0 = [] + + if "OC_High_trend" in df_stock.columns: + ap0.append( + mpf.make_addplot(df_stock["OC_High_trend"], color="g"), ) - s = mpf.make_mpf_style(marketcolors=mc, gridstyle=":", y_on_right=True) - - ap0 = [] - - if "OC_High_trend" in df_stock.columns: - ap0.append( - mpf.make_addplot(df_stock["OC_High_trend"], color="g"), - ) - - if "OC_Low_trend" in df_stock.columns: - ap0.append( - mpf.make_addplot(df_stock["OC_Low_trend"], color="b"), - ) - - if gtff.USE_ION: - plt.ion() - - mpf.plot( - df_stock, - type="candle", - mav=(20, 50), - volume=True, - title=f"\n{s_ticker} - Starting {ns_parser.s_start.strftime('%Y-%m-%d')}", - addplot=ap0, - xrotation=10, - style=s, - figratio=(10, 7), - figscale=1.10, - figsize=(plot_autoscale()), - update_width_config=dict( - candle_linewidth=1.0, candle_width=0.8, volume_linewidth=1.0 - ), - ) - else: - fig = make_subplots( - rows=2, - cols=1, - shared_xaxes=True, - vertical_spacing=0.06, - subplot_titles=(f"{s_ticker}", "Volume"), - row_width=[0.2, 0.7], - ) - fig.add_trace( - go.Candlestick( - x=df_stock.index, - open=df_stock.Open, - high=df_stock.High, - low=df_stock.Low, - close=df_stock.Close, - name="OHLC", - ), - row=1, - col=1, + if "OC_Low_trend" in df_stock.columns: + ap0.append( + mpf.make_addplot(df_stock["OC_Low_trend"], color="b"), ) + + if gtff.USE_ION: + plt.ion() + + mpf.plot( + df_stock, + type="candle", + mav=(20, 50), + volume=True, + title=f"\n{s_ticker} - Starting {s_start.strftime('%Y-%m-%d')}", + addplot=ap0, + xrotation=10, + style=s, + figratio=(10, 7), + figscale=1.10, + figsize=(plot_autoscale()), + update_width_config=dict( + candle_linewidth=1.0, candle_width=0.8, volume_linewidth=1.0 + ), + ) + else: + fig = make_subplots( + rows=2, + cols=1, + shared_xaxes=True, + vertical_spacing=0.06, + subplot_titles=(f"{s_ticker}", "Volume"), + row_width=[0.2, 0.7], + ) + fig.add_trace( + go.Candlestick( + x=df_stock.index, + open=df_stock.Open, + high=df_stock.High, + low=df_stock.Low, + close=df_stock.Close, + name="OHLC", + ), + row=1, + col=1, + ) + fig.add_trace( + go.Scatter( + x=df_stock.index, + y=df_stock["ma20"], + name="MA20", + mode="lines", + line=go.scatter.Line(color="royalblue"), + ), + row=1, + col=1, + ) + fig.add_trace( + go.Scatter( + x=df_stock.index, + y=df_stock["ma50"], + name="MA50", + mode="lines", + line=go.scatter.Line(color="black"), + ), + row=1, + col=1, + ) + + if "OC_High_trend" in df_stock.columns: fig.add_trace( go.Scatter( x=df_stock.index, - y=df_stock["ma20"], - name="MA20", + y=df_stock["OC_High_trend"], + name="High Trend", mode="lines", - line=go.scatter.Line(color="royalblue"), + line=go.scatter.Line(color="red"), ), row=1, col=1, ) + if "OC_Low_trend" in df_stock.columns: fig.add_trace( go.Scatter( x=df_stock.index, - y=df_stock["ma50"], - name="MA50", + y=df_stock["OC_Low_trend"], + name="Low Trend", mode="lines", - line=go.scatter.Line(color="black"), + line=go.scatter.Line(color="green"), ), row=1, col=1, ) - - if "OC_High_trend" in df_stock.columns: - fig.add_trace( - go.Scatter( - x=df_stock.index, - y=df_stock["OC_High_trend"], - name="High Trend", - mode="lines", - line=go.scatter.Line(color="red"), - ), - row=1, - col=1, - ) - if "OC_Low_trend" in df_stock.columns: - fig.add_trace( - go.Scatter( - x=df_stock.index, - y=df_stock["OC_Low_trend"], - name="Low Trend", - mode="lines", - line=go.scatter.Line(color="green"), - ), - row=1, - col=1, - ) - fig.add_trace( - go.Bar( - x=df_stock.index, - y=df_stock.Volume, - name="Volume", - marker_color="black", + fig.add_trace( + go.Bar( + x=df_stock.index, + y=df_stock.Volume, + name="Volume", + marker_color="black", + ), + row=2, + col=1, + ) + fig.update_layout( + yaxis_title="Stock Price ($)", + xaxis=dict( + rangeselector=dict( + buttons=list( + [ + dict( + count=1, + label="1m", + step="month", + stepmode="backward", + ), + dict( + count=3, + label="3m", + step="month", + stepmode="backward", + ), + dict(count=1, label="YTD", step="year", stepmode="todate"), + dict( + count=1, + label="1y", + step="year", + stepmode="backward", + ), + dict(step="all"), + ] + ) ), - row=2, - col=1, - ) - fig.update_layout( - yaxis_title="Stock Price ($)", - xaxis=dict( - rangeselector=dict( - buttons=list( - [ - dict( - count=1, - label="1m", - step="month", - stepmode="backward", - ), - dict( - count=3, - label="3m", - step="month", - stepmode="backward", - ), - dict( - count=1, label="YTD", step="year", stepmode="todate" - ), - dict( - count=1, - label="1y", - step="year", - stepmode="backward", - ), - dict(step="all"), - ] - ) - ), - rangeslider=dict(visible=False), - type="date", - ), - ) + rangeslider=dict(visible=False), + type="date", + ), + ) - fig.show() - print("") - - except Exception as e: - print(e, "\n") + fig.show() + print("") def quote(other_args: List[str], s_ticker: str):