def view_ma( series: pd.Series, length: List[int] = None, offset: int = 0, ma_type: str = "EMA", s_ticker: str = "", export: str = "", external_axes: Optional[List[plt.Axes]] = None, ) -> None: """Plots MA technical indicator Parameters ---------- series : pd.Series Series of prices length : List[int] Length of EMA window ma_type: str Type of moving average. Either "EMA" "ZLMA" or "SMA" s_ticker : str Ticker export : str Format to export data external_axes : Optional[List[plt.Axes]], optional External axes (1 axis is expected in the list), by default None """ # Define a dataframe for adding EMA series to it price_df = pd.DataFrame(series) l_legend = [s_ticker] if not length: length = [20, 50] for win in length: if ma_type == "EMA": df_ta = overlap_model.ema(series, win, offset) l_legend.append(f"EMA {win}") elif ma_type == "SMA": df_ta = overlap_model.sma(series, win, offset) l_legend.append(f"SMA {win}") elif ma_type == "WMA": df_ta = overlap_model.wma(series, win, offset) l_legend.append(f"WMA {win}") elif ma_type == "HMA": df_ta = overlap_model.hma(series, win, offset) l_legend.append(f"HMA {win}") elif ma_type == "ZLMA": df_ta = overlap_model.zlma(series, win, offset) l_legend.append(f"ZLMA {win}") price_df = price_df.join(df_ta) plot_data = reindex_dates(price_df) # This plot has 1 axis if external_axes is None: _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) else: if len(external_axes) != 1: console.print("[red]Expected list of one axis item./n[/red]") return (ax, ) = external_axes ax.plot(plot_data.index, plot_data.iloc[:, 1].values) ax.set_xlim([plot_data.index[0], plot_data.index[-1]]) ax.set_ylabel(f"{s_ticker} Price") for idx in range(2, plot_data.shape[1]): ax.plot(plot_data.iloc[:, idx]) ax.set_title(f"{s_ticker} {ma_type.upper()}") ax.legend(l_legend) theme.style_primary_axis( ax, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) if external_axes is None: theme.visualize_output() export_data( export, os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"), f"{ma_type.lower()}{'_'.join([str(win) for win in length])}", price_df, )
def display_adosc( ohlc: pd.DataFrame, fast: int = 3, slow: int = 10, use_open: bool = False, s_ticker: str = "", export: str = "", external_axes: Optional[List[plt.Axes]] = None, ): """Display AD Osc Indicator Parameters ---------- ohlc : pd.DataFrame Dataframe of prices use_open : bool Whether to use open prices in calculation fast: int Length of fast window slow : int Length of slow window s_ticker : str Stock ticker export : str Format to export data external_axes : Optional[List[plt.Axes]], optional External axes (3 axes is expected in the list), by default None """ divisor = 1_000_000 df_vol = ohlc["Volume"] / divisor df_vol.name = "Adj Volume" df_ta = volume_model.adosc(ohlc, use_open, fast, slow) df_cal = df_ta[df_ta.columns[0]] / divisor df_cal.name = "Adj ADOSC" plot_data = pd.merge(ohlc, df_vol, how="outer", left_index=True, right_index=True) plot_data = pd.merge(plot_data, df_ta, how="outer", left_index=True, right_index=True) plot_data = pd.merge(plot_data, df_cal, how="outer", left_index=True, right_index=True) plot_data = reindex_dates(plot_data) # This plot has 3 axes if external_axes is None: _, axes = plt.subplots( 3, 1, sharex=True, figsize=plot_autoscale(), dpi=PLOT_DPI, ) ax1, ax2, ax3 = axes else: if len(external_axes) != 3: console.print("[red]Expected list of 3 axis items./n[/red]") return (ax1, ax2, ax3) = external_axes ax1.set_title(f"{s_ticker} AD Oscillator") ax1.plot(plot_data.index, plot_data["Adj Close"].values) ax1.set_xlim(plot_data.index[0], plot_data.index[-1]) ax1.set_ylabel("Price") theme.style_primary_axis( ax1, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) ax2.set_ylabel("Volume [M]") bar_colors = [ theme.down_color if x[1].Open < x[1].Close else theme.up_color for x in plot_data.iterrows() ] ax2.bar( plot_data.index, plot_data["Adj Volume"], color=bar_colors, width=theme.volume_bar_width, ) ax2.set_xlim(plot_data.index[0], plot_data.index[-1]) theme.style_primary_axis( ax2, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) ax3.set_ylabel("AD Osc [M]") ax3.plot(plot_data.index, plot_data["Adj ADOSC"], label="AD Osc") ax3.set_xlim(plot_data.index[0], plot_data.index[-1]) theme.style_primary_axis( ax3, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) if external_axes is None: theme.visualize_output() export_data( export, os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"), "adosc", df_ta, )
def display_aroon( ohlc: pd.DataFrame, length: int = 25, scalar: int = 100, s_ticker: str = "", export: str = "", external_axes: Optional[List[plt.Axes]] = None, ): """Plot Aroon indicator Parameters ---------- ohlc : pd.DataFrame Dataframe with OHLC price data length:int Length of window s_ticker : str Ticker scalar : int Scalar variable export: str Format to export data external_axes : Optional[List[plt.Axes]], optional External axes (3 axes are expected in the list), by default None """ df_ta = trend_indicators_model.aroon( high_values=ohlc["High"], low_values=ohlc["Low"], length=length, scalar=scalar, ) plot_data = pd.merge(ohlc, df_ta, how="outer", left_index=True, right_index=True) plot_data = reindex_dates(plot_data) # This plot has 3 axes if not external_axes: _, axes = plt.subplots(3, 1, sharex=True, figsize=plot_autoscale(), dpi=PLOT_DPI) ax1, ax2, ax3 = axes else: if len(external_axes) != 3: console.print("[red]Expected list of 3 axis items./n[/red]") return ax1, ax2, ax3 = external_axes ax1.plot(plot_data.index, plot_data["Adj Close"].values) ax1.set_title(f"Aroon on {s_ticker}") ax1.set_xlim(plot_data.index[0], plot_data.index[-1]) ax1.set_ylabel("Share Price ($)") theme.style_primary_axis( ax1, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) ax2.plot(plot_data.index, plot_data[df_ta.columns[0]].values, theme.down_color) ax2.plot(plot_data.index, plot_data[df_ta.columns[1]].values, theme.up_color) ax2.set_xlim(plot_data.index[0], plot_data.index[-1]) ax2.axhline(50, ls="--") ax2.legend( [f"Aroon DOWN ({df_ta.columns[0]})", f"Aroon UP ({df_ta.columns[1]})"]) theme.style_primary_axis( ax2, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) ax3.plot(plot_data.index, plot_data[df_ta.columns[2]].values) ax3.set_xlim(plot_data.index[0], plot_data.index[-1]) ax3.legend([f"Aroon OSC ({df_ta.columns[2]})"]) ax3.set_ylim([-100, 100]) theme.style_primary_axis( ax3, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) if external_axes is None: theme.visualize_output() export_data( export, os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"), "aroon", df_ta, )
def fibonacci_retracement( ohlc: pd.DataFrame, period: int = 120, start_date: Optional[Union[str, None]] = None, end_date: Optional[Union[str, None]] = None, s_ticker: str = "", export: str = "", external_axes: Optional[List[plt.Axes]] = None, ): """Calculate fibonacci retracement levels Parameters ---------- ohlc: pd.DataFrame Stock data period: int Days to lookback start_date: Optional[str, None] User picked date for starting retracement end_date: Optional[str, None] User picked date for ending retracement s_ticker:str Stock ticker export: str Format to export data external_axes : Optional[List[plt.Axes]], optional External axes (2 axis is expected in the list), by default None """ ( df_fib, min_date, max_date, min_pr, max_pr, ) = custom_indicators_model.calculate_fib_levels(ohlc, period, start_date, end_date) levels = df_fib.Price plot_data = reindex_dates(ohlc) # This plot has 1 axes if external_axes is None: _, ax1 = plt.subplots( figsize=plot_autoscale(), dpi=PLOT_DPI, ) ax2 = ax1.twinx() else: if len(external_axes) != 1: logger.error("Expected list of one axis item.") console.print("[red]Expected list of 1 axis item./n[/red]") return ax1, ax2 = external_axes ax1.plot(plot_data["Adj Close"]) if is_intraday(ohlc): date_format = "%b %d %H:%M" else: date_format = "%Y-%m-%d" min_date_index = plot_data[plot_data["date"] == min_date.strftime( date_format)].index max_date_index = plot_data[plot_data["date"] == max_date.strftime( date_format)].index ax1.plot( [min_date_index, max_date_index], [min_pr, max_pr], ) for i in levels: ax1.axhline(y=i, alpha=0.5) for i in range(5): ax1.fill_between(plot_data.index, levels[i], levels[i + 1], alpha=0.15) ax1.set_xlim(plot_data.index[0], plot_data.index[-1]) ax1.set_title(f"Fibonacci Support for {s_ticker.upper()}") ax1.set_yticks(levels) ax1.set_yticklabels([0, 0.235, 0.382, 0.5, 0.618, 1]) theme.style_primary_axis( ax1, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) ax2.set_ylim(ax1.get_ylim()) ax2.set_ylabel("Price") theme.style_primary_axis(ax2) if external_axes is None: theme.visualize_output() print_rich_table( df_fib, headers=["Fib Level", "Price"], show_index=False, title="Fibonacci retracement levels", ) console.print("") export_data( export, os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"), "fib", df_fib, )
def view_kc( ohlc: pd.DataFrame, length: int = 20, scalar: float = 2, mamode: str = "ema", offset: int = 0, s_ticker: str = "", export: str = "", external_axes: Optional[List[plt.Axes]] = None, ): """View Keltner Channels Indicator Parameters ---------- ohlc : pd.DataFrame Dataframe of stock prices length : int Length of window mamode: str Type of filter offset : int Offset value s_ticker : str Ticker export : str Format to export data external_axes : Optional[List[plt.Axes]], optional External axes (2 axes are expected in the list), by default None """ df_ta = volatility_model.kc( ohlc["High"], ohlc["Low"], ohlc["Adj Close"], length, scalar, mamode, offset, ) plot_data = pd.merge(ohlc, df_ta, how="outer", left_index=True, right_index=True) plot_data = reindex_dates(plot_data) # This plot has 1 axis if external_axes is None: _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) else: if len(external_axes) != 1: console.print("[red]Expected list of 1 axis item./n[/red]") return (ax, ) = external_axes ax.plot(plot_data.index, plot_data["Adj Close"].values) ax.plot( plot_data.index, plot_data[df_ta.columns[0]].values, linewidth=0.7, label="Upper", ) ax.plot(plot_data.index, plot_data[df_ta.columns[1]].values, linewidth=0.7, ls="--") ax.plot( plot_data.index, plot_data[df_ta.columns[2]].values, linewidth=0.7, label="Lower", ) ax.fill_between( plot_data.index, plot_data[df_ta.columns[0]].values, plot_data[df_ta.columns[2]].values, alpha=0.1, ) ax.set_title(f"{s_ticker} Keltner Channels") ax.set_xlim(plot_data.index[0], plot_data.index[-1]) ax.set_ylabel("Price") ax.legend([s_ticker, df_ta.columns[0], df_ta.columns[1], df_ta.columns[2]]) theme.style_primary_axis( ax, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) if external_axes is None: theme.visualize_output() export_data( export, os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"), "kc", df_ta, )
def display_spread( name: str, df: pd.DataFrame, target: str, window: int, export: str = "", external_axes: Optional[List[plt.Axes]] = None, ): """View rolling spread Parameters ---------- name : str Stock ticker df : pd.DataFrame Dataframe target: str Column in data to look at window : int Length of window export : str Format to export data external_axes : Optional[List[plt.Axes]], optional External axes (3 axes are expected in the list), by default None """ data = df[target] df_sd, df_var = rolling_model.get_spread(data, window) plot_data = pd.merge( data, df_sd, how="outer", left_index=True, right_index=True, suffixes=("", "_sd"), ) plot_data = pd.merge( plot_data, df_var, how="outer", left_index=True, right_index=True, suffixes=("", "_var"), ) plot_data = reindex_dates(plot_data) # This plot has 1 axis if external_axes is None: _, axes = plt.subplots( 3, 1, sharex=True, figsize=plot_autoscale(), dpi=PLOT_DPI, ) (ax1, ax2, ax3) = axes else: if len(external_axes) != 3: logger.error("Expected list of one axis item.") console.print("[red]Expected list of 1 axis items./n[/red]") return (ax1, ax2, ax3) = external_axes ax1.plot(plot_data.index, plot_data[target].values) ax1.set_xlim(plot_data.index[0], plot_data.index[-1]) ax1.set_ylabel("Value") ax1.set_title(f"Spread of {name} {target}") ax2.plot( plot_data[f"STDEV_{window}"].index, plot_data[f"STDEV_{window}"].values, label="Stdev", ) ax2.set_ylabel("Stdev") ax3.plot( plot_data[f"VAR_{window}"].index, plot_data[f"VAR_{window}"].values, label="Variance", ) ax3.set_ylabel("Variance") theme.style_primary_axis( ax1, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) theme.style_primary_axis( ax2, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) theme.style_primary_axis( ax3, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) if external_axes is None: theme.visualize_output() export_data( export, os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"), "spread", df_sd.join(df_var, lsuffix="_sd", rsuffix="_var"), )
def display_cci( ohlc: pd.DataFrame, length: int = 14, scalar: float = 0.0015, s_ticker: str = "", export: str = "", external_axes: Optional[List[plt.Axes]] = None, ): """Display CCI Indicator Parameters ---------- ohlc : pd.DataFrame Dataframe of OHLC length : int Length of window scalar : float Scalar variable s_ticker : str Stock ticker export : str Format to export data external_axes : Optional[List[plt.Axes]], optional External axes (2 axes are expected in the list), by default None """ df_ta = momentum_model.cci(ohlc["High"], ohlc["Low"], ohlc["Adj Close"], length, scalar) plot_data = pd.merge(ohlc, df_ta, how="outer", left_index=True, right_index=True) plot_data = reindex_dates(plot_data) # This plot has 2 axes if external_axes is None: _, (ax1, ax2) = plt.subplots(2, 1, figsize=plot_autoscale(), sharex=True, dpi=PLOT_DPI) else: if len(external_axes) != 2: logger.error("Expected list of two axis items.") console.print("[red]Expected list of 2 axis items./n[/red]") return ax1, ax2 = external_axes ax1.set_title(f"{s_ticker} CCI") ax1.plot( plot_data.index, plot_data["Adj Close"].values, ) ax1.set_xlim(plot_data.index[0], plot_data.index[-1]) ax1.set_ylabel("Share Price ($)") theme.style_primary_axis( ax1, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) ax2.plot(plot_data.index, plot_data[df_ta.columns[0]].values) ax2.set_xlim(plot_data.index[0], plot_data.index[-1]) ax2.axhspan(100, ax2.get_ylim()[1], facecolor=theme.down_color, alpha=0.2) ax2.axhspan(ax2.get_ylim()[0], -100, facecolor=theme.up_color, alpha=0.2) theme.style_primary_axis( ax2, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) ax3 = ax2.twinx() ax3.set_ylim(ax2.get_ylim()) ax3.axhline(100, color=theme.down_color, ls="--") ax3.axhline(-100, color=theme.up_color, ls="--") theme.style_twin_axis(ax3) ax2.set_yticks([-100, 100]) ax2.set_yticklabels(["OVERSOLD", "OVERBOUGHT"]) if external_axes is None: theme.visualize_output() export_data( export, os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"), "cci", df_ta, )
def display_adx( ohlc: pd.DataFrame, length: int = 14, scalar: int = 100, drift: int = 1, s_ticker: str = "", export: str = "", external_axes: Optional[List[plt.Axes]] = None, ): """Plot ADX indicator Parameters ---------- ohlc : pd.DataFrame Dataframe with OHLC price data length : int Length of window scalar : int Scalar variable drift : int Drift variable s_ticker : str Ticker export : str Format to export data external_axes : Optional[List[plt.Axes]], optional External axes (2 axes are expected in the list), by default None """ df_ta = trend_indicators_model.adx( high_values=ohlc["High"], low_values=ohlc["Low"], close_values=ohlc["Adj Close"], length=length, scalar=scalar, drift=drift, ) plot_data = pd.merge(ohlc, df_ta, how="outer", left_index=True, right_index=True) plot_data = reindex_dates(plot_data) # This plot has 2 axes if not external_axes: _, axes = plt.subplots( 2, 1, sharex=True, figsize=plot_autoscale(), dpi=PLOT_DPI ) ax1, ax2 = axes else: if len(external_axes) != 2: logger.error("Expected list of two axis items.") console.print("[red]Expected list of 2 axis items./n[/red]") return ax1, ax2 = external_axes ax1.plot(plot_data.index, plot_data["Close"].values) ax1.set_title(f"Average Directional Movement Index (ADX) on {s_ticker}") ax1.set_xlim(plot_data.index[0], plot_data.index[-1]) ax1.set_ylabel("Share Price ($)") theme.style_primary_axis( ax1, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) ax2.plot(plot_data.index, plot_data[df_ta.columns[0]].values) ax2.plot(plot_data.index, plot_data[df_ta.columns[1]].values, color=theme.up_color) ax2.plot( plot_data.index, plot_data[df_ta.columns[2]].values, color=theme.down_color ) ax2.set_xlim(plot_data.index[0], plot_data.index[-1]) ax2.axhline(25, ls="--") ax2.legend( [ f"ADX ({df_ta.columns[0]})", f"+DI ({df_ta.columns[1]})", f"-DI ({df_ta.columns[2]})", ] ) ax2.set_ylim([0, 100]) theme.style_primary_axis( ax2, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) if external_axes is None: theme.visualize_output() export_data( export, os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"), "adx", df_ta, )
def display_macd( series: pd.Series, n_fast: int = 12, n_slow: int = 26, n_signal: int = 9, s_ticker: str = "", export: str = "", external_axes: Optional[List[plt.Axes]] = None, ): """Plot MACD signal Parameters ---------- series : pd.Series Values to input n_fast : int Fast period n_slow : int Slow period n_signal : int Signal period s_ticker : str Stock ticker export : str Format to export data external_axes : Optional[List[plt.Axes]], optional External axes (2 axes are expected in the list), by default None """ df_ta = momentum_model.macd(series, n_fast, n_slow, n_signal) plot_data = pd.merge(series, df_ta, how="outer", left_index=True, right_index=True) plot_data = reindex_dates(plot_data) # This plot has 2 axes if external_axes is None: _, (ax1, ax2) = plt.subplots(2, 1, figsize=plot_autoscale(), sharex=True, dpi=PLOT_DPI) else: if len(external_axes) != 2: logger.error("Expected list of two axis items.") console.print("[red]Expected list of 2 axis items./n[/red]") return ax1, ax2 = external_axes ax1.set_title(f"{s_ticker} MACD") ax1.plot(plot_data.index, plot_data.iloc[:, 1].values) ax1.set_xlim(plot_data.index[0], plot_data.index[-1]) ax1.set_ylabel("Share Price ($)") theme.style_primary_axis( ax1, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) ax2.plot(plot_data.index, plot_data.iloc[:, 2].values) ax2.plot(plot_data.index, plot_data.iloc[:, 4].values, color=theme.down_color) ax2.bar( plot_data.index, plot_data.iloc[:, 3].values, width=theme.volume_bar_width, color=theme.up_color, ) ax2.legend([ f"MACD Line {plot_data.columns[2]}", f"Signal Line {plot_data.columns[4]}", f"Histogram {plot_data.columns[3]}", ]) ax2.set_xlim(plot_data.index[0], plot_data.index[-1]) theme.style_primary_axis( ax2, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) if external_axes is None: theme.visualize_output() export_data( export, os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"), "macd", df_ta, )
def display_rsi( series: pd.Series, length: int = 14, scalar: float = 100.0, drift: int = 1, s_ticker: str = "", export: str = "", external_axes: Optional[List[plt.Axes]] = None, ): """Display RSI Indicator Parameters ---------- series : pd.Series Values to input length : int Length of window scalar : float Scalar variable drift : int Drift variable s_ticker : str Stock ticker export : str Format to export data external_axes : Optional[List[plt.Axes]], optional External axes (2 axes are expected in the list), by default None """ df_ta = momentum_model.rsi(series, length, scalar, drift) # This plot has 2 axes if external_axes is None: _, (ax1, ax2) = plt.subplots(2, 1, figsize=plot_autoscale(), sharex=True, dpi=PLOT_DPI) else: if len(external_axes) != 2: logger.error("Expected list of two axis items.") console.print("[red]Expected list of 2 axis items./n[/red]") return ax1, ax2 = external_axes plot_data = pd.merge(series, df_ta, how="outer", left_index=True, right_index=True) plot_data = reindex_dates(plot_data) ax1.plot(plot_data.index, plot_data.iloc[:, 1].values) ax1.set_title(f"{s_ticker} RSI{length}") ax1.set_xlim(plot_data.index[0], plot_data.index[-1]) ax1.set_ylabel("Share Price ($)") theme.style_primary_axis( ax=ax1, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) ax2.plot(plot_data.index, plot_data[df_ta.columns[0]].values) ax2.set_xlim(plot_data.index[0], plot_data.index[-1]) ax2.axhspan(0, 30, facecolor=theme.up_color, alpha=0.2) ax2.axhspan(70, 100, facecolor=theme.down_color, alpha=0.2) ax2.set_ylim([0, 100]) theme.style_primary_axis( ax=ax2, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) ax3 = ax2.twinx() ax3.set_ylim(ax2.get_ylim()) ax3.axhline(30, color=theme.up_color, ls="--") ax3.axhline(70, color=theme.down_color, ls="--") ax2.set_yticks([30, 70]) ax2.set_yticklabels(["OVERSOLD", "OVERBOUGHT"]) if external_axes is None: theme.visualize_output() export_data( export, os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"), "rsi", df_ta, )
def display_kurtosis( name: str, df: pd.DataFrame, target: str, window: int, export: str = "", external_axes: Optional[List[plt.Axes]] = None, ): """View rolling kurtosis Parameters ---------- name : str Ticker df : pd.DataFrame Dataframe of stock prices window : int Length of window export : str Format to export data external_axes : Optional[List[plt.Axes]], optional External axes (2 axes are expected in the list), by default None """ data = df[target] df_kurt = rolling_model.get_kurtosis(data, window) plot_data = pd.merge( data, df_kurt, how="outer", left_index=True, right_index=True, ) plot_data = reindex_dates(plot_data) # This plot has 1 axis if external_axes is None: _, axes = plt.subplots( 2, 1, sharex=True, figsize=plot_autoscale(), dpi=PLOT_DPI, ) (ax1, ax2) = axes else: if len(external_axes) != 2: logger.error("Expected list of two axis items.") console.print("[red]Expected list of 2 axis items./n[/red]") return (ax1, ax2) = external_axes ax1.set_title(f"{name} {target} Kurtosis Indicator (window {str(window)})") ax1.plot(plot_data.index, plot_data[target].values) ax1.set_xlim(plot_data.index[0], plot_data.index[-1]) ax1.set_ylabel(f"{target}") ax2.plot( plot_data.index, plot_data[f"KURT_{window}"].values, ) ax2.set_ylabel("Indicator") theme.style_primary_axis( ax1, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) theme.style_primary_axis( ax2, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) if external_axes is None: theme.visualize_output() console.print("") export_data( export, os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"), "kurtosis", df_kurt, )
def display_quantile( name: str, df: pd.DataFrame, target: str, window: int, quantile: float, export: str = "", external_axes: Optional[List[plt.Axes]] = None, ): """View rolling quantile Parameters ---------- name : str Stock ticker df : pd.DataFrame Dataframe target : str Column in data to look at window : int Length of window quantile : float Quantile to get export : str Format to export data external_axes : Optional[List[plt.Axes]], optional External axes (1 axis is expected in the list), by default None """ data = df[target] df_med, df_quantile = rolling_model.get_quantile(data, window, quantile) plot_data = pd.merge( data, df_med, how="outer", left_index=True, right_index=True, suffixes=("", "_med"), ) plot_data = pd.merge( plot_data, df_quantile, how="outer", left_index=True, right_index=True, suffixes=("", "_quantile"), ) plot_data = reindex_dates(plot_data) # This plot has 1 axis if external_axes is None: _, ax = plt.subplots( figsize=plot_autoscale(), dpi=PLOT_DPI, ) else: if len(external_axes) != 1: logger.error("Expected list of one axis item.") console.print("[red]Expected list of 1 axis items./n[/red]") return (ax, ) = external_axes ax.set_title(f"{name} {target} Median & Quantile") ax.plot(plot_data.index, plot_data[target].values, label=target) ax.plot( plot_data.index, plot_data[f"MEDIAN_{window}"].values, label=f"Median w={window}", ) ax.plot( plot_data.index, plot_data[f"QTL_{window}_{quantile}"].values, label=f"Quantile q={quantile}", linestyle="--", ) ax.set_xlim(plot_data.index[0], plot_data.index[-1]) ax.set_ylabel(f"{name} Value") ax.legend() theme.style_primary_axis( ax, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) if external_axes is None: theme.visualize_output() export_data( export, os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"), "quantile", df_med.join(df_quantile), )
def display_mean_std( name: str, df: pd.DataFrame, target: str, window: int, export: str = "", external_axes: Optional[List[plt.Axes]] = None, ): """View rolling spread Parameters ---------- name : str Stock ticker df : pd.DataFrame Dataframe target : str Column in data to look at window : int Length of window export : str Format to export data external_axes : Optional[List[plt.Axes]], optional External axes (2 axes are expected in the list), by default None """ data = df[target] rolling_mean, rolling_std = rolling_model.get_rolling_avg(data, window) plot_data = pd.merge( data, rolling_mean, how="outer", left_index=True, right_index=True, suffixes=("", "_mean"), ) plot_data = pd.merge( plot_data, rolling_std, how="outer", left_index=True, right_index=True, suffixes=("", "_std"), ) plot_data = reindex_dates(plot_data) # This plot has 2 axes if external_axes is None: _, axes = plt.subplots( 2, 1, sharex=True, figsize=plot_autoscale(), dpi=PLOT_DPI, ) ax1, ax2 = axes else: if len(external_axes) != 2: logger.error("Expected list of two axis items.") console.print("[red]Expected list of 2 axis items./n[/red]") return (ax1, ax2) = external_axes ax1.plot( plot_data.index, plot_data[target].values, label=name, ) ax1.plot( plot_data.index, plot_data[target + "_mean"].values, ) ax1.set_ylabel("Values", ) ax1.legend(["Real Values", "Rolling Mean"]) ax1.set_title( f"Rolling mean and std (window {str(window)}) of {name} {target}") ax1.set_xlim([plot_data.index[0], plot_data.index[-1]]) ax2.plot( plot_data.index, plot_data[target + "_std"].values, label="Rolling std", ) ax2.legend(["Rolling std"]) ax2.set_ylabel(f"{target} Std Deviation", ) theme.style_primary_axis( ax1, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) theme.style_primary_axis( ax2, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) if external_axes is None: theme.visualize_output() export_data( export, os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"), "rolling", rolling_mean.join(rolling_std, lsuffix="_mean", rsuffix="_std"), )
def display_obv( ohlc: pd.DataFrame, s_ticker: str = "", export: str = "", external_axes: Optional[List[plt.Axes]] = None, ): """Plot OBV technical indicator Parameters ---------- ohlc : pd.DataFrame Dataframe of prices s_ticker : str Ticker export: str Format to export data as external_axes : Optional[List[plt.Axes]], optional External axes (1 axis is expected in the list), by default None """ divisor = 1_000_000 df_vol = ohlc["Volume"] / divisor df_vol.name = "Adj Volume" df_ta = volume_model.obv(ohlc) df_cal = df_ta[df_ta.columns[0]] / divisor df_cal.name = "Adj OBV" plot_data = pd.merge(ohlc, df_vol, how="outer", left_index=True, right_index=True) plot_data = pd.merge(plot_data, df_ta, how="outer", left_index=True, right_index=True) plot_data = pd.merge(plot_data, df_cal, how="outer", left_index=True, right_index=True) plot_data = reindex_dates(plot_data) # This plot has 3 axes if external_axes is None: _, axes = plt.subplots( 3, 1, sharex=True, figsize=plot_autoscale(), dpi=PLOT_DPI, ) ax1, ax2, ax3 = axes else: if len(external_axes) != 3: console.print("[red]Expected list of 3 axis items./n[/red]") return (ax1, ax2, ax3) = external_axes ax1.plot(plot_data.index, plot_data["Adj Close"].values) ax1.set_title(f"{s_ticker} OBV") ax1.set_xlim(plot_data.index[0], plot_data.index[-1]) ax1.set_ylabel("Price") theme.style_primary_axis( ax1, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) ax2.set_xlim(plot_data.index[0], plot_data.index[-1]) ax2.set_ylabel("Volume [M]") bar_colors = [ theme.down_color if x[1].Open < x[1].Close else theme.up_color for x in plot_data.iterrows() ] ax2.bar( plot_data.index, plot_data["Adj Volume"], color=bar_colors, alpha=0.8, width=theme.volume_bar_width, ) theme.style_primary_axis( ax2, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) ax3.set_ylabel("OBV [M]") ax3.plot(plot_data.index, plot_data["Adj OBV"]) ax3.set_xlim(plot_data.index[0], plot_data.index[-1]) theme.style_primary_axis( ax3, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) if external_axes is None: theme.visualize_output() export_data( export, os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"), "obv", df_ta, )
def display_stoch( ohlc: pd.DataFrame, fastkperiod: int = 14, slowdperiod: int = 3, slowkperiod: int = 3, s_ticker: str = "", export: str = "", external_axes: Optional[List[plt.Axes]] = None, ) -> None: """Plot stochastic oscillator signal Parameters ---------- ohlc : pd.DataFrame Dataframe of prices fastkperiod : int Fast k period slowdperiod : int Slow d period slowkperiod : int Slow k period s_ticker : str Stock ticker export : str Format to export data external_axes : Optional[List[plt.Axes]], optional External axes (3 axes are expected in the list), by default None """ df_ta = momentum_model.stoch( ohlc["High"], ohlc["Low"], ohlc["Adj Close"], fastkperiod, slowdperiod, slowkperiod, ) # This plot has 3 axes if not external_axes: _, axes = plt.subplots(2, 1, sharex=True, figsize=plot_autoscale(), dpi=PLOT_DPI) ax1, ax2 = axes ax3 = ax2.twinx() else: if len(external_axes) != 3: logger.error("Expected list of three axis items.") console.print("[red]Expected list of 3 axis items./n[/red]") return ax1, ax2, ax3 = external_axes plot_data = pd.merge(ohlc, df_ta, how="outer", left_index=True, right_index=True) plot_data = reindex_dates(plot_data) ax1.plot(plot_data.index, plot_data["Adj Close"].values) ax1.set_title( f"Stochastic Relative Strength Index (STOCH RSI) on {s_ticker}") ax1.set_xlim(plot_data.index[0], plot_data.index[-1]) ax1.set_ylabel("Share Price ($)") theme.style_primary_axis( ax1, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) ax2.plot(plot_data.index, plot_data[df_ta.columns[0]].values) ax2.plot(plot_data.index, plot_data[df_ta.columns[1]].values, ls="--") ax2.set_xlim(plot_data.index[0], plot_data.index[-1]) theme.style_primary_axis( ax2, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) ax3.set_ylim(ax2.get_ylim()) ax3.axhspan(80, 100, facecolor=theme.down_color, alpha=0.2) ax3.axhspan(0, 20, facecolor=theme.up_color, alpha=0.2) ax3.axhline(80, color=theme.down_color, ls="--") ax3.axhline(20, color=theme.up_color, ls="--") theme.style_twin_axis(ax3) ax2.set_yticks([20, 80]) ax2.set_yticklabels(["OVERSOLD", "OVERBOUGHT"]) ax2.legend([f"%K {df_ta.columns[0]}", f"%D {df_ta.columns[1]}"]) if external_axes is None: theme.visualize_output() export_data( export, os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"), "stoch", df_ta, )
def display_seasonal( name: str, df: pd.DataFrame, target: str, multiplicative: bool = False, export: str = "", external_axes: Optional[List[plt.Axes]] = None, ): """Display seasonal decomposition data Parameters ---------- name : str Name of dataset df : pd.DataFrame DataFrame target : str Column of data to look at multiplicative : bool Boolean to indicate multiplication instead of addition export : str Format to export trend and cycle df external_axes : Optional[List[plt.Axes]], optional External axes (6 axes are expected in the list), by default None """ data = df[target] result, cycle, trend = qa_model.get_seasonal_decomposition( data, multiplicative) plot_data = pd.merge( data, result.trend, how="outer", left_index=True, right_index=True, suffixes=("", "_result.trend"), ) plot_data = pd.merge( plot_data, result.seasonal, how="outer", left_index=True, right_index=True, suffixes=("", "_result.seasonal"), ) plot_data = pd.merge( plot_data, result.resid, how="outer", left_index=True, right_index=True, suffixes=("", "_result.resid"), ) plot_data = pd.merge( plot_data, cycle, how="outer", left_index=True, right_index=True, suffixes=("", "_cycle"), ) plot_data = pd.merge( plot_data, trend, how="outer", left_index=True, right_index=True, suffixes=("", "_trend"), ) plot_data = reindex_dates(plot_data) # This plot has 1 axis if external_axes is None: fig, axes = plt.subplots( 4, 1, sharex=True, figsize=plot_autoscale(), dpi=PLOT_DPI, ) (ax1, ax2, ax3, ax4) = axes else: if len(external_axes) != 4: logger.error("Expected list of four axis items.") console.print("[red]Expected list of 4 axis items./n[/red]") return (ax1, ax2, ax3, ax4) = external_axes colors = iter(theme.get_colors()) ax1.set_title(f"{name} (Time-Series) {target} seasonal decomposition") ax1.plot(plot_data.index, plot_data[target].values, color=next(colors), label="Values") ax1.set_xlim([plot_data.index[0], plot_data.index[-1]]) ax1.legend() # Multiplicative model ax2.plot(plot_data["trend"], color=theme.down_color, label="Cyclic-Trend") ax2.plot( plot_data["trend_cycle"], color=theme.up_color, linestyle="--", label="Cycle component", ) ax2.legend() ax3.plot(plot_data["trend_trend"], color=next(colors), label="Trend component") ax3.plot(plot_data["seasonal"], color=next(colors), label="Seasonal effect") ax3.legend() ax4.plot(plot_data["resid"], color=next(colors), label="Residuals") ax4.legend() theme.style_primary_axis(ax1) theme.style_primary_axis(ax2) theme.style_primary_axis(ax3) theme.style_primary_axis( ax4, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) if external_axes is None: fig.tight_layout(pad=theme.tight_layout_padding) fig.subplots_adjust(hspace=0.1, ) theme.visualize_output(force_tight_layout=False) # From # https://otexts.com/fpp2/seasonal-strength.html console.print("Time-Series Level is " + str(round(data.mean(), 2))) Ft = max(0, 1 - np.var(result.resid)) / np.var(result.trend + result.resid) console.print(f"Strength of Trend: {Ft:.4f}") Fs = max( 0, 1 - np.var(result.resid) / np.var(result.seasonal + result.resid), ) console.print(f"Strength of Seasonality: {Fs:.4f}\n") export_data( export, os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"), "summary", cycle.join(trend), )
def display_fisher( ohlc: pd.DataFrame, length: int = 14, s_ticker: str = "", export: str = "", external_axes: Optional[List[plt.Axes]] = None, ): """Display Fisher Indicator Parameters ---------- ohlc : pd.DataFrame Dataframe of prices length : int Length of window s_ticker : str Ticker string export : str Format to export data external_axes : Optional[List[plt.Axes]], optional External axes (3 axes are expected in the list), by default None """ df_ta = momentum_model.fisher(ohlc["High"], ohlc["Low"], length) plot_data = pd.merge(ohlc, df_ta, how="outer", left_index=True, right_index=True) plot_data = reindex_dates(plot_data) # This plot has 3 axes if not external_axes: _, axes = plt.subplots(2, 1, sharex=True, figsize=plot_autoscale(), dpi=PLOT_DPI) ax1, ax2 = axes ax3 = ax2.twinx() else: if len(external_axes) != 3: logger.error("Expected list of three axis items.") console.print("[red]Expected list of 3 axis items./n[/red]") return ax1, ax2, ax3 = external_axes ax1.set_title(f"{s_ticker} Fisher Transform") ax1.plot(plot_data.index, plot_data["Adj Close"].values) ax1.set_xlim(plot_data.index[0], plot_data.index[-1]) ax1.set_ylabel("Price") theme.style_primary_axis( ax1, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) ax2.plot( plot_data.index, plot_data[df_ta.columns[0]].values, label="Fisher", ) ax2.plot( plot_data.index, plot_data[df_ta.columns[1]].values, label="Signal", ) ax2.set_xlim(plot_data.index[0], plot_data.index[-1]) theme.style_primary_axis( ax2, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) ax3.set_ylim(ax2.get_ylim()) ax3.axhspan(2, ax2.get_ylim()[1], facecolor=theme.down_color, alpha=0.2) ax3.axhspan(ax2.get_ylim()[0], -2, facecolor=theme.up_color, alpha=0.2) ax3.axhline(2, color=theme.down_color, ls="--") ax3.axhline(-2, color=theme.up_color, ls="--") theme.style_twin_axis(ax3) ax2.set_yticks([-2, 0, 2]) ax2.set_yticklabels(["-2 STDEV", "0", "+2 STDEV"]) ax2.legend() if external_axes is None: theme.visualize_output() export_data( export, os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"), "fisher", df_ta, )
def display_donchian( ohlc: pd.DataFrame, ticker: str = "", upper_length: int = 20, lower_length: int = 20, export: str = "", external_axes: Optional[List[plt.Axes]] = None, ): """Show donchian channels Parameters ---------- ohlc : pd.DataFrame Dataframe of stock prices ticker : str Ticker upper_length : int Length of window to calculate upper channel lower_length : int Length of window to calculate lower channel export : str Format of export file external_axes : Optional[List[plt.Axes]], optional External axes (1 axis is expected in the list), by default None """ df_ta = volatility_model.donchian(ohlc["High"], ohlc["Low"], upper_length, lower_length) plot_data = pd.merge(ohlc, df_ta, how="outer", left_index=True, right_index=True) plot_data = reindex_dates(plot_data) # This plot has 1 axis if external_axes is None: _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) else: if len(external_axes) != 1: console.print("[red]Expected list of 1 axis item./n[/red]") return (ax, ) = external_axes ax.plot(plot_data.index, plot_data["Adj Close"].values) ax.plot( plot_data.index, plot_data[df_ta.columns[0]].values, linewidth=0.7, label="Upper", ) ax.plot(plot_data.index, plot_data[df_ta.columns[1]].values, linewidth=0.7, ls="--") ax.plot( plot_data.index, plot_data[df_ta.columns[2]].values, linewidth=0.7, label="Lower", ) ax.fill_between( plot_data.index, plot_data[df_ta.columns[0]].values, plot_data[df_ta.columns[2]].values, alpha=0.1, ) ax.set_title(f"{ticker} donchian") ax.set_xlim(plot_data.index[0], plot_data.index[-1]) ax.set_ylabel("Price ($)") ax.legend([ticker, df_ta.columns[0], df_ta.columns[1], df_ta.columns[2]]) theme.style_primary_axis( ax, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) if external_axes is None: theme.visualize_output() export_data( export, os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"), "donchian", df_ta, )
def display_cg( series: pd.Series, length: int = 14, s_ticker: str = "", export: str = "", external_axes: Optional[List[plt.Axes]] = None, ): """Display center of gravity Indicator Parameters ---------- series : pd.Series Series of values length : int Length of window s_ticker : str Stock ticker export : str Format to export data external_axes : Optional[List[plt.Axes]], optional External axes (2 axes are expected in the list), by default None """ df_ta = momentum_model.cg(series, length) plot_data = pd.merge(series, df_ta, how="outer", left_index=True, right_index=True) plot_data = reindex_dates(plot_data) # This plot has 2 axes if external_axes is None: _, (ax1, ax2) = plt.subplots(2, 1, figsize=plot_autoscale(), sharex=True, dpi=PLOT_DPI) else: if len(external_axes) != 2: logger.error("Expected list of two axis items.") console.print("[red]Expected list of 2 axis items./n[/red]") return ax1, ax2 = external_axes ax1.set_title(f"{s_ticker} Centre of Gravity") ax1.plot(plot_data.index, plot_data[series.name].values) ax1.set_xlim(plot_data.index[0], plot_data.index[-1]) ax1.set_ylabel("Share Price ($)") theme.style_primary_axis( ax1, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) ax2.plot(plot_data.index, plot_data[df_ta.columns[0]].values, label="CG") # shift cg 1 bar forward for signal signal = np.roll(plot_data[df_ta.columns[0]].values, 1) ax2.plot(plot_data.index, signal, label="Signal") ax2.set_xlim(plot_data.index[0], plot_data.index[-1]) ax2.legend() theme.style_primary_axis( ax2, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) if external_axes is None: theme.visualize_output() export_data( export, os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"), "cg", df_ta, )
def display_bbands( ohlc: pd.DataFrame, ticker: str = "", length: int = 15, n_std: float = 2, mamode: str = "sma", export: str = "", external_axes: Optional[List[plt.Axes]] = None, ): """Show bollinger bands Parameters ---------- ohlc : pd.DataFrame Dataframe of stock prices ticker : str Ticker length : int Length of window to calculate BB n_std : float Number of standard deviations to show mamode : str Method of calculating average export : str Format of export file external_axes : Optional[List[plt.Axes]], optional External axes (1 axis is expected in the list), by default None """ df_ta = volatility_model.bbands(ohlc["Adj Close"], length, n_std, mamode) plot_data = pd.merge(ohlc, df_ta, how="outer", left_index=True, right_index=True) plot_data = reindex_dates(plot_data) # This plot has 2 axes if not external_axes: _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) else: if len(external_axes) != 1: console.print("[red]Expected list of 2 axis items./n[/red]") return (ax, ) = external_axes ax.plot( plot_data.index, plot_data["Adj Close"].values, ) ax.plot( plot_data.index, plot_data[df_ta.columns[0]].values, theme.down_color, linewidth=0.7, ) ax.plot(plot_data.index, plot_data[df_ta.columns[1]].values, ls="--", linewidth=0.7) ax.plot( plot_data.index, plot_data[df_ta.columns[2]].values, theme.up_color, linewidth=0.7, ) ax.set_title(f"{ticker} Bollinger Bands") ax.set_xlim(plot_data.index[0], plot_data.index[-1]) ax.set_ylabel("Share Price ($)") ax.legend([ticker, df_ta.columns[0], df_ta.columns[1], df_ta.columns[2]]) ax.fill_between(df_ta.index, df_ta.iloc[:, 0].values, df_ta.iloc[:, 2].values, alpha=0.1) theme.style_primary_axis( ax, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) if external_axes is None: theme.visualize_output() export_data( export, os.path.dirname(os.path.abspath(__file__)).replace("common", "stocks"), "bbands", df_ta, )
def show_indices( indices: list, interval: str = "1d", start_date: int = None, end_date: int = None, column: str = "Adj Close", returns: bool = False, store: bool = False, raw: bool = False, external_axes: Optional[List[plt.axes]] = None, export: str = "", ): """Load (and show) the selected indices over time [Source: Yahoo Finance] Parameters ---------- indices: list A list of indices you wish to load (and plot). interval: str Valid intervals: 1m,2m,5m,15m,30m,60m,90m,1h,1d,5d,1wk,1mo,3mo Intraday data cannot extend last 60 days start_date : str The starting date, format "YEAR-MONTH-DAY", i.e. 2010-12-31. end_date : str The end date, format "YEAR-MONTH-DAY", i.e. 2020-06-05. column : str Which column to load in, by default this is the Adjusted Close. returns: bool Flag to show cumulative returns on index store : bool Whether to prevent plotting the data. raw : bool Whether to display the raw output. external_axes: Optional[List[plt.axes]] External axes to plot on export : str Export data to csv,json,xlsx or png,jpg,pdf,svg file Returns ---------- Plots the Series. """ indices_data: pd.DataFrame = pd.DataFrame() for index in indices: indices_data[index] = get_index(index, interval, start_date, end_date, column) if returns: indices_data = indices_data.pct_change().dropna() indices_data = indices_data + 1 indices_data = indices_data.cumprod() if not store: if external_axes is None: _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) else: ax = external_axes[0] for index in indices: if index.lower() in INDICES: label = INDICES[index.lower()]["name"] else: label = index if not indices_data[index].empty: data_to_percent = 100 * (indices_data[index].values - 1) ax.plot(data_to_percent, label=label) ax.set_title("Indices") if returns: ax.set_ylabel("Performance (%)") ax.legend( bbox_to_anchor=(0, 0.40, 1, -0.52), loc="upper right", mode="expand", borderaxespad=0, prop={"size": 9}, ncol=2, ) if returns: indices_data.index.name = "date" plot_data = reindex_dates(indices_data) theme.style_primary_axis( ax, data_index=plot_data.index.to_list(), tick_labels=plot_data["date"].to_list(), ) ax.set_xlim(plot_data.index[0], plot_data.index[-1]) else: theme.style_primary_axis(ax) if external_axes is None: theme.visualize_output() if raw: print_rich_table( indices_data.fillna("-").iloc[-10:], headers=list(indices_data.columns), show_index=True, title=f"Indices [column: {column}]", ) if export: export_data( export, os.path.dirname(os.path.abspath(__file__)), "index_data", indices_data, ) return indices_data