# Technical Analysis with Pandas ([pandas_ta](https://github.com/twopirllc/pandas-ta))
* Below contains examples of simple charts that can be made from pandas_ta indicators
* Examples below are for **educational purposes only**
* **NOTE:** The **watchlist** module is independent of Pandas TA. To easily use it, copy it from your local pandas_ta installation directory into your project directory.

# Technical Analysis with Pandas ([pandas_ta](https://github.com/twopirllc/pandas-ta))
"""
* Below contains examples of simple charts that can be made from pandas_ta indicators
* Examples below are for **educational purposes only**
* **NOTE:** The **watchlist** module is independent of Pandas TA. To easily use it, copy it from your local pandas_ta installation directory into your project directory.
"""
# Install necessary libraries
!pip install mplfinance
!pip install yfinance
!pip install pandas_ta
# !pip install watchlist

%matplotlib inline
import datetime as dt
import random as rnd

import numpy as np
import pandas as pd
import mplfinance as mpf
import yfinance as yf

import pandas_ta as ta

# from watchlist import colors # Ensure this is correctly installed or copied locally

print(f"\nPandas TA v{ta.version}\nTo install the Latest Version:\n$ pip install -U git+https://github.com/twopirllc/pandas-ta\n")

%pylab inline
%matplotlib inline

### List of Indicators (post an [issue](https://github.com/twopirllc/pandas-ta/issues) if the indicator doc needs updating)

e = pd.DataFrame()
e.ta.indicators()

### Individual Indicator help

help(ta.ema)

### Load Daily SPY from [AlphaVantage](http://www.alphavantage.com) and clean it

def fetch_data(ticker="SPY", period="max", interval="1d", drop_cols=["Dividends", "Stock Splits"]):
    """
    Fetch historical market data for a given ticker using yfinance.

    Parameters:
    - ticker (str): Stock ticker symbol.
    - period (str): Data period (e.g., "max", "5y", "1y").
    - interval (str): Data interval (e.g., "1d", "1wk", "1mo").
    - drop_cols (list): Columns to drop from the DataFrame.

    Returns:
    - pd.DataFrame: Cleaned DataFrame with historical data.
    """
    df = yf.download(ticker, period=period, interval=interval, auto_adjust=False, progress=False)
    df.drop(columns=drop_cols, inplace=True, errors="ignore")
    df.index = pd.to_datetime(df.index)

    # **Standardize column names to lowercase**
    df.columns = df.columns.str.lower()

    df.name = ticker
    return df

def ctitle(indicator_name, ticker="SPY", length=100):
    return f"{ticker}: {indicator_name} from {recent_startdate.date()} to {recent_enddate.date()} ({length})"

def ta_ylim(series: pd.Series, percent: float = 0.1):
    smin, smax = series.min(), series.max()
    if isinstance(percent, float) and 0 <= float(percent) <= 1:
        y_min = (1 + percent) * smin if smin < 0 else (1 - percent) * smin
        y_max = (1 - percent) * smax if smax < 0 else (1 + percent) * smax
        return (y_min, y_max)
    return (smin, smax)

### Initialization

price_size = (16, 8)
ind_size = (16, 3.25)
ticker = "SPY"

def recent_bars(df, tf: str = "1y"):
    """
    Determine the number of recent bars based on the time frame.

    Parameters:
    - df (pd.DataFrame): DataFrame with historical data.
    - tf (str): Time frame (e.g., "1y", "6mo").

    Returns:
    - int: Number of bars corresponding to the time frame.
    """
    yearly_divisor = {"all": 0, "10y": 0.1, "5y": 0.2, "4y": 0.25, "3y": 1./3, "2y": 0.5,
                     "1y": 1, "6mo": 2, "3mo": 4}
    yd = yearly_divisor.get(tf, 0)
    return int(ta.RATE["TRADING_DAYS_PER_YEAR"] / yd) if yd > 0 else df.shape[0]

### Get Ticker and take a peek

# All the Data
df = fetch_data(ticker, period="max")
print(f"{df.name} {df.shape} from {df.index[0].date()} to {df.index[-1].date()}\n{df.describe()}")
df.head()

# Recent Data
recent = recent_bars(df, tf="1y")  # Adjust the time frame as needed
recent_startdate = df.tail(recent).index[0]
recent_enddate = df.tail(recent).index[-1]
df_recent = df.tail(recent).copy()  # Use .copy() to avoid SettingWithCopyWarning
df_recent.name = ticker  # Re-assign the name attribute
print(f"{df.name} {df_recent.shape} from {recent_startdate.date()} to {recent_enddate.date()}\n{df_recent.describe()}")
df_recent.head()

### Aliases

opendf = df_recent["open"]
closedf = df_recent["close"]
volumedf = df_recent["volume"]

## DataFrame **constants**: When you need some simple lines for charting

# help(df.ta.constants) # for more info
chart_lines = np.append(np.arange(-5, 6, 1), np.arange(-100, 110, 10))
df.ta.constants(True, chart_lines)  # Adding the constants for the charts
df.ta.constants(False, np.array([-60, -40, 40, 60]))  # Removing some constants from the DataFrame
print(f"Columns: {', '.join(list(df.columns))}")

## Example Charting Class utilizing **mplfinance** panels

### Charting Example
#### Play with the parameters to see different charts and results
* This is an example chart so it's not perfect. Enough to get started with common and uncommon plots.
* There is a maximum of 10 Panels. In this example, panels 0 and 1 are reserved for Price and Volume respectively.
    *

class Chart(object):
    def __init__(self, df: pd.DataFrame = None, strategy: ta.Strategy = ta.CommonStrategy, *args, **kwargs):
        self.verbose = kwargs.pop("verbose", False)

        if isinstance(df, pd.DataFrame) and hasattr(df.ta, 'datetime_ordered') and df.ta.datetime_ordered:
            self.df = df
            # Safely get the name attribute
            df_name = str(self.df.name) if getattr(self.df, 'name', None) else "DataFrame"
            if self.verbose:
                print(f"[i] Loaded {df_name} {self.df.shape}")
        else:
            print(f"[X] Oops! Missing 'ohlcv' data or index is not datetime ordered.\n")
            return None

        self._validate_ta_strategy(strategy)
        self._validate_mpf_kwargs(**kwargs)
        self._validate_chart_kwargs(**kwargs)

        # Build TA and Plot
        self.df.ta.strategy(self.strategy, verbose=self.verbose)
        self._plot(**kwargs)

    def _validate_ta_strategy(self, strategy):
        if strategy is not None and isinstance(strategy, ta.Strategy):
            self.strategy = strategy
        elif hasattr(self, 'strategy_ta') and len(self.strategy_ta) > 0:
            print(f"[+] Strategy: {self.strategy_name}")
        else:
            self.strategy = ta.CommonStrategy

    def _validate_chart_kwargs(self, **kwargs):
        """Chart Settings"""
        self.config = {}
        self.config["last"] = kwargs.pop("last", recent_bars(self.df))
        self.config["rpad"] = kwargs.pop("rpad", 10)
        self.config["title"] = kwargs.pop("title", "Asset")
        self.config["volume"] = kwargs.pop("volume", True)

    def _validate_mpf_kwargs(self, **kwargs):
        # mpf global chart settings
        default_chart = mpf.available_styles()[-1]
        default_mpf_width = {
            'candle_linewidth': 0.6,
            'candle_width': 0.525,
            'volume_width': 0.525
        }
        mpfchart = {}

        mpf_style = kwargs.pop("style", "")
        if mpf_style == "" or mpf_style.lower() == "random":
            mpf_styles = mpf.available_styles()
            mpfchart["style"] = mpf_styles[rnd.randrange(len(mpf_styles))]
        elif mpf_style.lower() in mpf.available_styles():
            mpfchart["style"] = mpf_style
        else:
            mpfchart["style"] = default_chart

        mpfchart["figsize"] = kwargs.pop("figsize", (12, 10))
        mpfchart["non_trading"] = kwargs.pop("nontrading", False)
        mpfchart["rc"] = kwargs.pop("rc", {'figure.facecolor': '#EDEDED'})
        mpfchart["plot_ratios"] = kwargs.pop("plot_ratios", (12, 1.7))
        mpfchart["scale_padding"] = kwargs.pop("scale_padding", {'left': 1, 'top': 4, 'right': 1, 'bottom': 1})
        mpfchart["tight_layout"] = kwargs.pop("tight_layout", True)
        mpfchart["type"] = kwargs.pop("type", "candle")
        mpfchart["width_config"] = kwargs.pop("width_config", default_mpf_width)
        mpfchart["xrotation"] = kwargs.pop("xrotation", 15)

        self.mpfchart = mpfchart

    def _attribution(self):
        print(f"\nPandas v: {pd.__version__} [pip install pandas] https://github.com/pandas-dev/pandas")
        print(f"Technical Analysis with Pandas TA v: {ta.version} [pip install pandas_ta] https://github.com/twopirllc/pandas-ta")
        print(f"Charts by Matplotlib Finance v: {mpf.__version__} [pip install mplfinance] https://github.com/matplotlib/mplfinance\n")

    def _right_pad_df(self, rpad: int, delta_unit: str = "D", range_freq: str = "B"):
        if rpad > 0:
            dfpad = self.df[-rpad:].copy()
            dfpad.iloc[:, :] = np.NaN

            df_frequency = self.df.index.to_series().diff().mode()[0]  # Most common frequency
            freq_delta = pd.Timedelta(df_frequency, unit=delta_unit)
            new_dr = pd.date_range(start=self.df.index[-1] + freq_delta, periods=rpad, freq=range_freq)
            dfpad.index = new_dr  # Update the padded index with new dates
            self.df = pd.concat([self.df, dfpad])

    def _plot(self, **kwargs):
        if not isinstance(self.mpfchart["plot_ratios"], tuple):
            print(f"[X] plot_ratios must be a tuple")
            return

        # Override Chart Title Option
        chart_title = self.config["title"]
        if "title" in kwargs and isinstance(kwargs["title"], str):
            chart_title = kwargs.pop("title")

        # Override Right Bar Padding Option
        rpad = self.config["rpad"]
        if "rpad" in kwargs and kwargs["rpad"] > 0:
            rpad = int(kwargs["rpad"])

        def cpanel():
            return len(self.mpfchart['plot_ratios'])

        # Last Second Default TA Indicators
        linreg = kwargs.pop("linreg", False)
        linreg_name = self.df.ta.linreg(append=True).name if linreg else ""

        midpoint = kwargs.pop("midpoint", False)
        midpoint_name = self.df.ta.midpoint(append=True).name if midpoint else ""

        ohlc4 = kwargs.pop("ohlc4", False)
        ohlc4_name = self.df.ta.ohlc4(append=True).name if ohlc4 else ""

        clr = kwargs.pop("clr", False)
        clr_name = self.df.ta.log_return(cumulative=True, append=True).name if clr else ""

        rsi = kwargs.pop("rsi", False)
        rsi_length = kwargs.pop("rsi_length", None)
        if isinstance(rsi_length, int) and rsi_length > 1:
            rsi_name = self.df.ta.rsi(length=rsi_length, append=True).name
        elif rsi:
            rsi_name = self.df.ta.rsi(append=True).name
        else:
            rsi_name = ""

        zscore = kwargs.pop("zscore", False)
        zscore_length = kwargs.pop("zscore_length", None)
        if isinstance(zscore_length, int) and zscore_length > 1:
            zs_name = self.df.ta.zscore(length=zscore_length, append=True).name
        elif zscore:
            zs_name = self.df.ta.zscore(append=True).name
        else:
            zs_name = ""

        macd = kwargs.pop("macd", False)
        macd_name = ""
        if macd:
            macds = self.df.ta.macd(append=True)
            macd_name = macds.name

        squeeze = kwargs.pop("squeeze", False)
        lazybear = kwargs.pop("lazybear", False)
        squeeze_name = ""
        if squeeze:
            squeezes = self.df.ta.squeeze(lazybear=lazybear, detailed=True, append=True)
            squeeze_name = squeezes.name

        ama = kwargs.pop("archermas", False)
        ama_name = ""
        if ama:
            amas = self.df.ta.amat(append=True)
            ama_name = amas.name

        aobv = kwargs.pop("archerobv", False)
        aobv_name = ""
        if aobv:
            aobvs = self.df.ta.aobv(append=True)
            aobv_name = aobvs.name

        treturn = kwargs.pop("trendreturn", False)
        if treturn:
            # Long Trend requires Series Comparison (<=. <, = >, >=)
            # or Trade Logic that yields trends in binary.
            default_long = self.df["sma_10"] > self.df["sma_20"]
            long_trend = kwargs.pop("long_trend", default_long)
            if not isinstance(long_trend, pd.Series):
                raise ValueError("[X] Must be a Series that has boolean values or values of 0s and 1s")
            short_trend = ~long_trend  # Opposite/Inverse
            self.df["TR"] = ta.trend_return(self.df["close"], long_trend).iloc[:, 1]

        # Pad and trim Chart
        self._right_pad_df(rpad)
        mpfdf = self.df.tail(self.config["last"])
        mpfdf_columns = list(mpfdf.columns)

        # BEGIN: Custom TA Plots and Panels
        taplots = []  # Holds all the additional plots

        # Panel 0: Price Overlay
        if linreg_name in mpfdf_columns:
            taplots += [mpf.make_addplot(mpfdf[linreg_name], type=kwargs.pop("linreg_type", "line"),
                                        color=kwargs.pop("linreg_color", "black"),
                                        linestyle="-.", width=1.2, panel=0)]

        if midpoint_name in mpfdf_columns:
            taplots += [mpf.make_addplot(mpfdf[midpoint_name], type=kwargs.pop("midpoint_type", "scatter"),
                                        color=kwargs.pop("midpoint_color", "fuchsia"),
                                        width=0.4, panel=0)]

        if ohlc4_name in mpfdf_columns:
            taplots += [mpf.make_addplot(mpfdf[ohlc4_name], ylabel=ohlc4_name,
                                        type=kwargs.pop("ohlc4_type", "scatter"),
                                        color=kwargs.pop("ohlc4_color", "blue"),
                                        alpha=0.85, width=0.4, panel=0)]

        if self.strategy.name == ta.CommonStrategy.name:
            total_sma = 0  # Check if all the overlap indicators exist before adding plots
            for c in ["sma_10", "sma_20", "sma_50", "sma_200"]:
                if c in mpfdf_columns:
                    total_sma += 1
                else:
                    print(f"[X] Indicator: {c} missing!")
            if total_sma == 4:
                ta_smas = [
                    mpf.make_addplot(mpfdf["sma_10"], color="green", width=1.5, panel=0),
                    mpf.make_addplot(mpfdf["sma_20"], color="orange", width=2, panel=0),
                    mpf.make_addplot(mpfdf["sma_50"], color="red", width=2, panel=0),
                    mpf.make_addplot(mpfdf["sma_200"], color="maroon", width=3, panel=0),
                ]
                taplots += ta_smas

        if len(ama_name):
            amat_sr_ = mpfdf[amas.columns[-1]][mpfdf[amas.columns[-1]] > 0]
            amat_sr = amat_sr_.index.to_list()
        else:
            amat_sr = None

        # Panel 1: If volume=True, then add the VOL MA. Since we know there is only one, we immediately pop it.
        if self.config["volume"]:
            volma_list = [x for x in list(self.df.columns) if x.startswith("vol_")]
            if volma_list:
                volma = volma_list.pop()
                max_vol = mpfdf["volume"].max()
                ta_volume = [mpf.make_addplot(mpfdf[volma], color="red", width=2, panel=1,
                                            ylim=(-.2 * max_vol, 1.5 * max_vol))]
                taplots += ta_volume

        # Panels 2 - 9
        common_plot_ratio = (3,)

        if aobv_name in mpfdf_columns:
            _p = kwargs.pop("aobv_percenty", 0.2)
            aobv_ylim = ta_ylim(mpfdf[aobvs.columns[0]], _p)
            taplots += [
                mpf.make_addplot(mpfdf[aobvs.columns[0]], ylabel=aobv_name, color="black",
                                width=1.5, panel=cpanel(), ylim=aobv_ylim),
                mpf.make_addplot(mpfdf[aobvs.columns[2]], color="silver", width=1, panel=cpanel(), ylim=aobv_ylim),
                mpf.make_addplot(mpfdf[aobvs.columns[3]], color="green", width=1, panel=cpanel(), ylim=aobv_ylim),
                mpf.make_addplot(mpfdf[aobvs.columns[4]], color="red", width=1.2, panel=cpanel(), ylim=aobv_ylim),
            ]
            self.mpfchart["plot_ratios"] += common_plot_ratio  # Required to add a new Panel

        if clr_name in mpfdf_columns:
            _p = kwargs.pop("clr_percenty", 0.1)
            clr_ylim = ta_ylim(mpfdf[clr_name], _p)

            taplots += [mpf.make_addplot(mpfdf[clr_name], ylabel=clr_name, color="black",
                                        width=1.5, panel=cpanel(), ylim=clr_ylim)]
            if (1 - _p) * mpfdf[clr_name].min() < 0 and (1 + _p) * mpfdf[clr_name].max() > 0:
                taplots += [mpf.make_addplot(mpfdf["0"], color="gray", width=1.2, panel=cpanel(),
                                            ylim=clr_ylim)]
            self.mpfchart["plot_ratios"] += common_plot_ratio  # Required to add a new Panel

        if rsi_name in mpfdf_columns:
            rsi_ylim = (0, 100)
            taplots += [
                mpf.make_addplot(mpfdf[rsi_name], ylabel=rsi_name, color=kwargs.pop("rsi_color", "black"),
                                width=1.5, panel=cpanel(), ylim=rsi_ylim),
                mpf.make_addplot(mpfdf["20"], color="green", width=1, panel=cpanel(), ylim=rsi_ylim),
                mpf.make_addplot(mpfdf["50"], color="gray", width=0.8, panel=cpanel(), ylim=rsi_ylim),
                mpf.make_addplot(mpfdf["80"], color="red", width=1, panel=cpanel(), ylim=rsi_ylim),
            ]
            self.mpfchart["plot_ratios"] += common_plot_ratio  # Required to add a new Panel

        if macd_name in mpfdf_columns:
            _p = kwargs.pop("macd_percenty", 0.15)
            macd_ylim = ta_ylim(mpfdf[macd_name], _p)
            taplots += [
                mpf.make_addplot(mpfdf[macd_name], ylabel=macd_name, color="black",
                                width=1.5, panel=cpanel()),
                mpf.make_addplot(mpfdf[macds.columns[-1]], color="blue",
                                width=1.1, panel=cpanel()),
                mpf.make_addplot(mpfdf[macds.columns[1]], type="bar", alpha=0.8,
                                color="dimgray", width=0.8, panel=cpanel()),
                mpf.make_addplot(mpfdf["0"], color="black",
                                width=1.2, panel=cpanel()),
            ]
            self.mpfchart["plot_ratios"] += common_plot_ratio  # Required to add a new Panel

        if zs_name in mpfdf_columns:
            _p = kwargs.pop("zascore_percenty", 0.2)
            zs_ylim = ta_ylim(mpfdf[zs_name], _p)
            taplots += [
                mpf.make_addplot(mpfdf[zs_name], ylabel=zs_name, color="black",
                                width=1.5, panel=cpanel(), ylim=zs_ylim),
                mpf.make_addplot(mpfdf["-3"], color="red",
                                width=1.2, panel=cpanel(), ylim=zs_ylim),
                mpf.make_addplot(mpfdf["-2"], color="orange",
                                width=1, panel=cpanel(), ylim=zs_ylim),
                mpf.make_addplot(mpfdf["-1"], color="silver",
                                width=1, panel=cpanel(), ylim=zs_ylim),
                mpf.make_addplot(mpfdf["0"], color="black",
                                width=1.2, panel=cpanel(), ylim=zs_ylim),
                mpf.make_addplot(mpfdf["1"], color="silver",
                                width=1, panel=cpanel(), ylim=zs_ylim),
                mpf.make_addplot(mpfdf["2"], color="orange",
                                width=1, panel=cpanel(), ylim=zs_ylim),
                mpf.make_addplot(mpfdf["3"], color="red",
                                width=1.2, panel=cpanel(), ylim=zs_ylim)
            ]
            self.mpfchart["plot_ratios"] += common_plot_ratio  # Required to add a new Panel

        if squeeze_name in mpfdf_columns:
            _p = kwargs.pop("squeeze_percenty", 0.6)
            sqz_ylim = ta_ylim(mpfdf[squeeze_name], _p)
            taplots += [
                mpf.make_addplot(mpfdf[squeezes.columns[-4]], type="bar", color="lime",
                                alpha=0.65, width=0.8, panel=cpanel(), ylim=sqz_ylim),
                mpf.make_addplot(mpfdf[squeezes.columns[-3]], type="bar", color="green",
                                alpha=0.65, width=0.8, panel=cpanel(), ylim=sqz_ylim),
                mpf.make_addplot(mpfdf[squeezes.columns[-2]], type="bar", color="maroon",
                                alpha=0.65, width=0.8, panel=cpanel(), ylim=sqz_ylim),
                mpf.make_addplot(mpfdf[squeezes.columns[-1]], type="bar", color="red",
                                alpha=0.65, width=0.8, panel=cpanel(), ylim=sqz_ylim),
                mpf.make_addplot(mpfdf["0"], color="black",
                                width=1.2, panel=cpanel(), ylim=sqz_ylim),
                mpf.make_addplot(mpfdf[squeezes.columns[4]], ylabel=squeeze_name, color="green",
                                width=2, panel=cpanel(), ylim=sqz_ylim),
                mpf.make_addplot(mpfdf[squeezes.columns[5]], color="red",
                                width=1.8, panel=cpanel(), ylim=sqz_ylim),
            ]
            self.mpfchart["plot_ratios"] += common_plot_ratio  # Required to add a new Panel

        if treturn:
            _p = kwargs.pop("treturn_percenty", 0.33)
            treturn_ylim = ta_ylim(mpfdf["tr"], _p)
            taplots += [
                mpf.make_addplot(mpfdf["tr"], ylabel="Trend Return",
                                type="bar", color="green", alpha=0.45,
                                width=0.8, panel=cpanel(), ylim=treturn_ylim),
                mpf.make_addplot(mpfdf["tr"], color="black",
                                width=1.3, panel=cpanel(), ylim=treturn_ylim),
                mpf.make_addplot(mpfdf["0"], color="black",
                                width=1, panel=cpanel(), ylim=treturn_ylim),
            ]
            self.mpfchart["plot_ratios"] += common_plot_ratio  # Required to add a new Panel

            _p = kwargs.pop("cstreturn_percenty", 0.33)
            trcs = mpfdf["close"] / mpfdf["close"].iloc[0]
            treturncs_ylim = ta_ylim(trcs, _p)
            taplots += [
                mpf.make_addplot(trcs, ylabel="Trend B&H",
                                type="bar", color="green", alpha=0.45,
                                width=0.8, panel=cpanel(), ylim=treturncs_ylim),
                mpf.make_addplot(mpfdf["0"], color="black",
                                width=1, panel=cpanel(), ylim=treturncs_ylim),
            ]
            self.mpfchart["plot_ratios"] += common_plot_ratio  # Required to add a new Panel

        # END: Custom TA Plots and Panels

        if self.verbose:
            additional_ta = []
            chart_title = f"{chart_title} [{self.strategy.name}] (last {self.config['last']} bars)"
            chart_title += f"\nSince {mpfdf.index[0].date()} till {mpfdf.index[-1].date()}"
            if len(linreg_name) > 0:
                additional_ta.append(linreg_name)
            if len(midpoint_name) > 0:
                additional_ta.append(midpoint_name)
            if len(ohlc4_name) > 0:
                additional_ta.append(ohlc4_name)
            if len(additional_ta) > 0:
                chart_title += f"\nIncluding: {', '.join(additional_ta)}"

        if amat_sr:
            vlines_ = dict(vlines=amat_sr, alpha=0.1, colors="red")
        else:
            # Hidden because vlines needs valid arguments even if None
            vlines_ = dict(vlines=mpfdf.index[0], alpha=0, colors="white")

        # Create Final Plot
        mpf.plot(mpfdf,
                 title=chart_title,
                 type=self.mpfchart["type"],
                 style=self.mpfchart["style"],
                 datetime_format="%-m/%-d/%Y",
                 volume=self.config["volume"],
                 figsize=self.mpfchart["figsize"],
                 tight_layout=self.mpfchart["tight_layout"],
                 scale_padding=self.mpfchart["scale_padding"],
                 panel_ratios=self.mpfchart["plot_ratios"],  # This key needs to be updated above if adding more panels
                 xrotation=self.mpfchart["xrotation"],
                 update_width_config=self.mpfchart["width_config"],
                 show_nontrading=self.mpfchart["non_trading"],
                 vlines=vlines_,
                 addplot=taplots
                )

        self._attribution()

# Used for example Trend Return Long Trend Below
macd_ = ta.macd(closedf)
macdh = macd_[macd_.columns[1]]

Chart(df_recent,
      # style: which mplfinance chart style to use. Added "random" as an option.
      # rpad: how many bars to leave empty on the right of the chart
      style="yahoo", title=ticker, last=recent_bars(df_recent), rpad=10,

      # Overlap Indicators
      linreg=True, midpoint=False, ohlc4=False, archermas=True,

      # Example Indicators with default parameters
      volume=True, rsi=True, clr=True, macd=True, zscore=False, squeeze=False, lazybear=False,

      # Archer OBV and OBV MAs (https://www.tradingview.com/script/Co1ksara-Trade-Archer-On-balance-Volume-Moving-Averages-v1/)
      archerobv=False,

      # Create trends and see their returns
      trendreturn=False,
      # Example Trends or create your own. Trend must yield Booleans
      long_trend=ta.sma(closedf, 10) > ta.sma(closedf, 20),  # trend: sma(close,10) > sma(close,20) [Default Example]
      # long_trend=closedf > ta.ema(closedf,5), # trend: close > ema(close,5)
      # long_trend=ta.sma(closedf,10) > ta.ema(closedf,50), # trend: sma(close,10) > ema(close,50)
      # long_trend=macdh > 0, # trend: macd hist > 0
      # long_trend=ta.increasing(ta.sma(ta.rsi(closedf), 10), 5, asint=False), # trend: rising sma(rsi, 10) for the previous 5 periods
      show_nontrading=False,  # Intraday use if needed
      verbose=True,  # More detail
)

## Indicator Examples
* Examples of simple and complex indicators.  Most indicators return a Series, while a few return DataFrames.
* All indicators can be called one of three ways. Either way, they return the result.

### Three ways to use pandas_ta
1. Stand Alone like TA-Lib  ta.**indicator**(*kwargs*).
2. As a DataFrame Extension like df.ta.**indicator**(*kwargs*).  Where df is a DataFrame with columns named "open", "high", "low", "close, "volume" for simplicity.
3. Similar to #2, but by calling: df.ta(kind="**indicator**", *kwargs*).

### Cumulative Log Return

clr_ma_length = 8
clrdf = df.ta.log_return(cumulative=True, append=True)
clrmadf = ta.ema(clrdf, length=clr_ma_length)
clrxdf = pd.DataFrame({f"{clrdf.name}": clrdf, f"{clrmadf.name}({clrdf.name})": clrmadf})
clrxdf.tail(recent_bars(df)).plot(figsize=ind_size, color=['black', 'blue'], linewidth=1, title=ctitle(clrdf.name, ticker=ticker, length=recent_bars(df)), grid=True)

### MACD

macddf = df.ta.macd(fast=8, slow=21, signal=9, min_periods=None, append=True)
macddf[[macddf.columns[0], macddf.columns[2]]].tail(recent_bars(df)).plot(figsize=(16, 2), color=['black', 'blue'], linewidth=1.3)
macddf[macddf.columns[1]].tail(recent_bars(df)).plot.area(figsize=ind_size, stacked=False, color=["silver"], linewidth=1, title=ctitle(macddf.name, ticker=ticker, length=recent_bars(df)), grid=True).axhline(y=0, color="black", lw=1.1)

### ZScore

# Calculate Z-Score
zscoredf = df.ta.zscore(length=30, append=True)

# Create threshold columns
thresholds = [-4, -3, -2, 2, 3, 4]
for threshold in thresholds:
    df[str(threshold)] = threshold

# Select columns for plotting
plot_columns = [str(t) for t in thresholds] + [zscoredf.name]
zcols = df[plot_columns].tail(recent_bars(df))

# Define colors
zcolors = ["darkgreen", "green", "silver", "silver", "red", "maroon", "black"]

# Plot
ax = zcols.plot(figsize=ind_size, color=zcolors, linewidth=1.2,
                title=ctitle(zscoredf.name, ticker=ticker, length=recent_bars(df)),
                grid=True)
ax.axhline(y=0, color="black", lw=1.1)

# Now Volume Z Score
zvscoredf = df.ta.zscore(close="volume", length=30, prefix="VOL", append=True)
zcolors = ["darkgreen", "green", "silver", "silver", "red", "maroon", "black"]
zvcols = df[["-4", "-3", "-2", "2", "3", "4", zvscoredf.name]].tail(recent_bars(df))
zvcols.plot(figsize=ind_size, color=zcolors, linewidth=1.2, title=ctitle(zvscoredf.name, ticker=ticker, length=recent_bars(df)), grid=True).axhline(y=0, color="black", lw=1.1)

# New Features

## Squeeze Indicator (John Carter and Lazybear Versions)
Squeeze Indicator (__squeeze__)

# help(ta.squeeze)

Chart(df, style="yahoo", title=ticker, verbose=False,
    last=recent_bars(df), rpad=10, clr=True, squeeze=True,
    show_nontrading=False, # Intraday use if needed
)

# The original code has been modified to include detailed explanations of the changes.
def _plot(self, **kwargs):
    """Plot method."""
    if self.df.empty:
        print("No Data to Plot.")
        return

    # Using TA Lib, default colors are not so great.  Let's fix that.
    if "style" in kwargs and kwargs["style"] == "yahoo":
        kwargs["style"] = mpf_style
    elif "style" in kwargs:
        pass
    else:
        kwargs["style"] = mpf_style

    if not self.strategy:
        if "type" in kwargs:
            kwargs["type"] = self.type
        else:
            kwargs["type"] = self.type

    # Add Volume.
    if "volume" not in kwargs:
        kwargs["volume"] = True

    if self.verbose:
        print("kwargs", kwargs)

    # Ensure we have a copy!
    mpfdf = self.df.copy()

    # This is probably not the best way to handle this.  But...
    # If there is a squeeze, then mplfinance has issues with the bbands.
    # So, we will remove them and add them back "manually"
    squeezes = [
        col for col in mpfdf.columns if col.startswith("SQZ_")
    ]  # Find all squeeze columns
    if squeezes:  # If we have any
        squeeze_name = squeezes[0][4:]  # Get the name
        sqz_ylim = (mpfdf[squeezes].min().min(), mpfdf[squeezes].max().max())
        mpfdf.drop(columns=squeezes, inplace=True)
        if "addplot" not in kwargs:
            kwargs["addplot"] = []

        # The original code was trying to access column '0', which doesn't exist.
        # Instead, we should access the columns from the 'squeezes' list.
        kwargs["addplot"] = [
                mpf.make_addplot(mpfdf[squeezes[-1]], type="bar", color="red",
                                alpha=0.65, width=0.8, panel=cpanel(), ylim=sqz_ylim),
                # Use the appropriate column names from the 'squeezes' list
                mpf.make_addplot(mpfdf[squeezes[0]], color="black",
                                width=1.2, panel=cpanel(), ylim=sqz_ylim),
                mpf.make_addplot(mpfdf[squeezes[4]], ylabel=squeeze_name, color="green",
                                width=1.2, panel=cpanel(), ylim=sqz_ylim),
        ]

    # If there is an RSI, then mplfinance doesn't seem to have the right colors.
    rsis = [col for col in mpfdf.columns if col.startswith("RSI_")]
    if rsis:
        if "addplot" not in kwargs:
            kwargs["addplot"] = []
        for rsi in rsis:
            kwargs["addplot"].append(
                mpf.make_addplot(mpfdf[rsi], panel=1, color="#b20000", ylim=(0, 100))
            )

    if self.verbose:
        print(mpfdf.columns)

    if "figsize" not in kwargs:
        kwargs["figsize"] = self.figsize

    if "title" not in kwargs:
        kwargs["title"] = f"{self.ticker} - {self.strategy}"

    if self.last:
        mpfdf = mpfdf.tail(self.last)

    if not self.show_nontrading:
        mpfdf = mpfdf[mpfdf.Volume > 0]

    if self.clr:
        # Handle color-related options
        clr_ma_length = 8
        clrdf = mpfdf['close'].pct_change().cumsum()
        clrmadf = clrdf.ewm(span=clr_ma_length).mean()

        if "addplot" not in kwargs:
            kwargs["addplot"] = []

        kwargs["addplot"].append(
            mpf.make_addplot(clrmadf, panel=0, color='blue', secondary_y=True)
        )

    # Finally, create the plot
    mpf.plot(mpfdf, **kwargs)
    fig, axes = mpf.plot(mpfdf, **kwargs, returnfig=True)
    plt.show()


### Lazybear's TradingView Squeeze

Chart(df, style="yahoo", title=ticker, verbose=False,
    last=recent_bars(df), rpad=10, clr=True, squeeze=True, lazybear=True,
    show_nontrading=False, # Intraday use if needed
)

### Archer Moving Averages Trends
Archer Moving Average Trends (__amat__) returns the long and short run trends of fast and slow moving averages.
* The _pink_ background, on the Price chart, is when Archer MAs are bearish. Conversely, a _white_ background is bullish

Chart(df, style="yahoo", title=ticker, verbose=False,
    last=recent_bars(df), rpad=10,
    volume=True, midpoint=False, ohlc4=False,
    rsi=False, clr=True, macd=False, zscore=False, squeeze=False, lazybear=False,
    archermas=True, archerobv=False,
    show_nontrading=False, # Intraday use if needed
)

### Archer On Balance Volume
Archer On Balance Volume (__aobv__) returns a DataFrame of OBV, OBV min and max, fast and slow MAs of OBV, and the long and short run trends of the two OBV MAs.
* On the chart below, only **OBV**, **OBV min**, _fast_ and _slow_ **OBV MAs**.
* Not on the chart are: **OBV LR** and **OBV SR** trends.

Chart(df, style="yahoo", title=ticker, verbose=False,
    last=recent_bars(df), rpad=10,
    volume=True, midpoint=False, ohlc4=False,
    rsi=False, clr=True, macd=False, zscore=False, squeeze=False, lazybear=False,
    archermas=False, archerobv=True,
    show_nontrading=False, # Intraday use if needed
)

# Disclaimer
* All investments involve risk, and the past performance of a security, industry, sector, market, financial product, trading strategy, or individual’s trading does not guarantee future results or returns. Investors are fully responsible for any investment decisions they make. Such decisions should be based solely on an evaluation of their financial circumstances, investment objectives, risk tolerance, and liquidity needs.

* Any opinions, news, research, analyses, prices, or other information offered is provided as general market commentary, and does not constitute investment advice. I will not accept liability for any loss or damage, including without limitation any loss of profit, which may arise directly or indirectly from use of or reliance on such information.

I've got the error:...

[i] Loaded SPY (252, 6)
[+] Strategy: Common Price and Volume SMAs
[i] Indicator arguments: {'append': True}
[i] Multiprocessing 5 indicators with 1 chunks and 2/2 cpus.
[i] Total indicators: 5
[i] Columns added: 5
[i] Last Run: Thursday October 3, 2024, NYSE: 18:50:48, Local: 22:50:48 UTC, Day 277/365 (76.00%)
[X] Indicator: sma_10 missing!
[X] Indicator: sma_20 missing!
[X] Indicator: sma_50 missing!
[X] Indicator: sma_200 missing!
<ipython-input-11-627079895803>:78: FutureWarning: Setting an item of incompatible dtype is deprecated and will raise in a future error of pandas. Value 'nan' has dtype incompatible with int64, please explicitly cast to a compatible dtype first.
  dfpad.iloc[:, :] = np.NaN
<ipython-input-11-627079895803>:78: FutureWarning: Setting an item of incompatible dtype is deprecated and will raise in a future error of pandas. Value 'nan' has dtype incompatible with int64, please explicitly cast to a compatible dtype first.
  dfpad.iloc[:, :] = np.NaN
<ipython-input-11-627079895803>:78: FutureWarning: Setting an item of incompatible dtype is deprecated and will raise in a future error of pandas. Value 'nan' has dtype incompatible with int64, please explicitly cast to a compatible dtype first.
  dfpad.iloc[:, :] = np.NaN
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
/usr/local/lib/python3.10/dist-packages/pandas/core/indexes/base.py in get_loc(self, key)
   3804         try:
-> 3805             return self._engine.get_loc(casted_key)
   3806         except KeyError as err:

index.pyx in pandas._libs.index.IndexEngine.get_loc()

index.pyx in pandas._libs.index.IndexEngine.get_loc()

pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()

pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()

KeyError: '0'

The above exception was the direct cause of the following exception:

KeyError                                  Traceback (most recent call last)
4 frames
/usr/local/lib/python3.10/dist-packages/pandas/core/indexes/base.py in get_loc(self, key)
   3810             ):
   3811                 raise InvalidIndexError(key)
-> 3812             raise KeyError(key) from err
   3813         except TypeError:
   3814             # If we have a listlike key, _check_indexing_error will raise

KeyError: '0'