From 4e1abb2d2f1e680931206187d1313ccf2b8ff51f Mon Sep 17 00:00:00 2001 From: jmaslek Date: Wed, 19 Jan 2022 15:52:45 -0500 Subject: [PATCH] Improve plotly interface for plots (#1213) * Improve plotly interface for plots * add interactiv linear/log button * change volexch to mpl default Co-authored-by: Colin Delahunty <72827203+colin99d@users.noreply.github.com> --- .../stocks/dark_pool_shorts/dps_controller.py | 14 +- .../stocks/stocks_controller.py | 37 +++- gamestonk_terminal/stocks/stocks_helper.py | 168 +++++++++++------- 3 files changed, 142 insertions(+), 77 deletions(-) diff --git a/gamestonk_terminal/stocks/dark_pool_shorts/dps_controller.py b/gamestonk_terminal/stocks/dark_pool_shorts/dps_controller.py index 7c10a4ceccf..36dedc240ae 100644 --- a/gamestonk_terminal/stocks/dark_pool_shorts/dps_controller.py +++ b/gamestonk_terminal/stocks/dark_pool_shorts/dps_controller.py @@ -544,12 +544,12 @@ class DarkPoolShortsController(BaseController): default=False, ) parser.add_argument( - "-m", - "--mpl", - help="Display plot using matplotlb.", - dest="mpl", - action="store_true", - default=False, + "-p", + "--plotly", + help="Display plot using interactive plotly.", + dest="plotly", + action="store_false", + default=True, ) ns_parser = parse_known_args_and_warn( parser, other_args, export_allowed=EXPORT_BOTH_RAW_DATA_AND_FIGURES @@ -561,7 +561,7 @@ class DarkPoolShortsController(BaseController): raw=ns_parser.raw, sort=ns_parser.sort, asc=ns_parser.asc, - mpl=ns_parser.mpl, + mpl=ns_parser.plotly, export=ns_parser.export, ) else: diff --git a/gamestonk_terminal/stocks/stocks_controller.py b/gamestonk_terminal/stocks/stocks_controller.py index 9f362e2a557..e9343eaeb3b 100644 --- a/gamestonk_terminal/stocks/stocks_controller.py +++ b/gamestonk_terminal/stocks/stocks_controller.py @@ -290,12 +290,12 @@ Stock: [/param]{stock_text} description="Shows historic data for a stock", ) parser.add_argument( - "-m", - "--matplotlib", - dest="matplotlib", - action="store_true", - default=False, - help="Flag to show matplotlib instead of interactive plot using plotly.", + "-p", + "--plotly", + dest="plotly", + action="store_false", + default=True, + help="Flag to show interactive plotly chart.", ) parser.add_argument( "--sort", @@ -337,6 +337,21 @@ Stock: [/param]{stock_text} dest="num", default=20, ) + parser.add_argument( + "-t", + "--trend", + action="store_true", + default=False, + help="Flag to add high and low trends to candle.", + dest="trendlines", + ) + parser.add_argument( + "--ma", + dest="mov_avg", + type=str, + help="Add moving averaged to plot", + default="", + ) ns_parser = parse_known_args_and_warn( parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED @@ -361,13 +376,21 @@ Stock: [/param]{stock_text} ) else: + data = stocks_helper.process_candle(self.stock) + mov_avgs = ( + tuple(int(num) for num in ns_parser.mov_avg.split(",")) + if ns_parser.mov_avg + else None + ) stocks_helper.display_candle( s_ticker=self.ticker, df_stock=data, - use_matplotlib=ns_parser.matplotlib, + use_matplotlib=ns_parser.plotly, intraday=self.interval != "1440min", + add_trend=ns_parser.trendlines, + ma=mov_avgs, ) else: console.print("No ticker loaded. First use `load {ticker}`\n") diff --git a/gamestonk_terminal/stocks/stocks_helper.py b/gamestonk_terminal/stocks/stocks_helper.py index 901944fd568..2533e48b88f 100644 --- a/gamestonk_terminal/stocks/stocks_helper.py +++ b/gamestonk_terminal/stocks/stocks_helper.py @@ -1,12 +1,14 @@ """Main helper""" __docformat__ = "numpy" + import argparse import json from datetime import datetime, timedelta -from typing import List, Union +from typing import List, Union, Tuple, Optional import matplotlib.pyplot as plt import mplfinance as mpf +import numpy as np import pandas as pd import pandas_market_calendars as mcal import plotly.graph_objects as go @@ -266,7 +268,12 @@ def load( def display_candle( - s_ticker: str, df_stock: pd.DataFrame, use_matplotlib: bool, intraday: bool = False + s_ticker: str, + df_stock: pd.DataFrame, + use_matplotlib: bool, + intraday: bool = False, + add_trend: bool = False, + ma: Optional[Tuple[int, ...]] = None, ): """Shows candle plot of loaded ticker. [Source: Yahoo Finance, IEX Cloud or Alpha Vantage] @@ -280,10 +287,15 @@ def display_candle( Flag to use matplotlib instead of interactive plotly chart intraday: bool Flag for intraday data for plotly range breaks + add_trend: bool + Flag to add high and low trends to chart + mov_avg: Tuple[int] + Moving averages to add to the candle """ - if (df_stock.index[1] - df_stock.index[0]).total_seconds() >= 86400: - df_stock = find_trendline(df_stock, "OC_High", "high") - df_stock = find_trendline(df_stock, "OC_Low", "low") + if add_trend: + if (df_stock.index[1] - df_stock.index[0]).total_seconds() >= 86400: + df_stock = find_trendline(df_stock, "OC_High", "high") + df_stock = find_trendline(df_stock, "OC_Low", "low") if use_matplotlib: mc = mpf.make_marketcolors( @@ -298,24 +310,24 @@ def display_candle( s = mpf.make_mpf_style(marketcolors=mc, gridstyle=":", y_on_right=True) ap0 = [] + if add_trend: + if "OC_High_trend" in df_stock.columns: + ap0.append( + mpf.make_addplot(df_stock["OC_High_trend"], color="g"), + ) - 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 "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() + kwargs = {"mav": ma} if ma else {} mpf.plot( df_stock, type="candle", - mav=(20, 50), volume=True, title=f"\nStock {s_ticker}", addplot=ap0, @@ -327,6 +339,8 @@ def display_candle( update_width_config=dict( candle_linewidth=1.0, candle_width=0.8, volume_linewidth=1.0 ), + warn_too_much_data=10000, + **kwargs, ) else: fig = make_subplots( @@ -349,60 +363,70 @@ def display_candle( 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 ma: + plotly_colors = [ + "black", + "teal", + "blue", + "purple", + "orange", + "gray", + "deepskyblue", + ] + for idx, ma_val in enumerate(ma): + temp = df_stock["Adj Close"].copy() + temp[f"ma{ma_val}"] = df_stock["Adj Close"].rolling(ma_val).mean() + temp = temp.dropna() + fig.add_trace( + go.Scatter( + x=temp.index, + y=temp[f"ma{ma_val}"], + name=f"MA{ma_val}", + mode="lines", + line=go.scatter.Line( + color=plotly_colors[np.mod(idx, len(plotly_colors))] + ), + ), + 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="green"), - ), - 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="red"), - ), - row=1, - col=1, - ) + if add_trend: + 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="green"), + ), + 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="red"), + ), + row=1, + col=1, + ) + colors = [ + "red" if row.Open < row["Adj Close"] else "green" + for _, row in df_stock.iterrows() + ] fig.add_trace( go.Bar( x=df_stock.index, y=df_stock.Volume, name="Volume", - marker_color="#696969", + marker_color=colors, ), row=2, col=1, @@ -440,11 +464,29 @@ def display_candle( type="date", ), ) + + fig.update_layout( + updatemenus=[ + dict( + buttons=[ + dict( + label="linear", + method="relayout", + args=[{"yaxis.type": "linear"}], + ), + dict( + label="log", method="relayout", args=[{"yaxis.type": "log"}] + ), + ] + ) + ] + ) + if intraday: fig.update_xaxes( rangebreaks=[ dict(bounds=["sat", "mon"]), - dict(bounds=[16, 9.5], pattern="hour"), + dict(bounds=[20, 9], pattern="hour"), ] )