Files
pandas-ta/examples/example.ipynb
T

941 KiB
Raw Blame History

Technical Analysis with Pandas (pandas_ta)

  • Below contains examples of simple charts that can be made from pandas_ta indicators
  • Examples below are for educational purposes only
In [1]:
%matplotlib inline
import datetime as dt
import matplotlib.pyplot as plt
import pandas as pd
import pandas_ta as ta
%pylab inline

from alphaVantageAPI.alphavantage import AlphaVantage  # pip install alphaVantage-api

e = pd.DataFrame()
Populating the interactive namespace from numpy and matplotlib

List of Indicators (post an issue if the indicator doc needs updating)

In [2]:
e.ta.indicators()
pandas.ta - Technical Analysis Indicators - v0.1.75b
Total Indicators: 111
Abbreviations:
    aberration, above, above_value, accbands, ad, adosc, adx, amat, ao, aobv, apo, aroon, atr, bbands, below, below_value, bias, bop, brar, cci, cdl_doji, cg, chop, cksp, cmf, cmo, coppock, cross, cross_value, decreasing, dema, donchian, dpo, efi, ema, entropy, eom, fisher, fwma, ha, hl2, hlc3, hma, ichimoku, increasing, inertia, kama, kc, kdj, kst, kurtosis, linear_decay, linreg, log_return, long_run, macd, mad, massi, median, mfi, midpoint, midprice, mom, natr, nvi, obv, ohlc4, pdist, percent_return, ppo, psar, psl, pvi, pvo, pvol, pvt, pwma, qstick, quantile, rma, roc, rsi, rvgi, rvi, short_run, sinwma, skew, slope, sma, stdev, stoch, supertrend, swma, t3, tema, trend_return, trima, trix, true_range, tsi, uo, variance, vortex, vp, vwap, vwma, wcp, willr, wma, zlma, zscore

Individual Indicator help

In [3]:
help(ta.sma)
Help on function sma in module pandas_ta.overlap.sma:

sma(close, length=None, offset=None, **kwargs)
    Simple Moving Average (SMA)
    
    The Simple Moving Average is the classic moving average that is the equally
    weighted average over n periods.
    
    Sources:
        https://www.tradingtechnologies.com/help/x-study/technical-indicator-definitions/simple-moving-average-sma/
    
    Calculation:
        Default Inputs:
            length=10
        SMA = SUM(close, length) / length
    
    Args:
        close (pd.Series): Series of 'close's
        length (int): It's period.  Default: 10
        offset (int): How many periods to offset the result.  Default: 0
    
    Kwargs:
        adjust (bool): Default: True
        presma (bool, optional): If True, uses SMA for initial value.
        fillna (value, optional): pd.DataFrame.fillna(value)
        fill_method (value, optional): Type of fill method
    
    Returns:
        pd.Series: New feature generated.

Load Daily SPY from AlphaVantage and clean it

In [4]:
def farm(ticker = 'SPY', drop=['dividend', 'split_coefficient']):
    AV = AlphaVantage(api_key="YOUR API KEY", premium=False, clean=True, output_size='full')
    df = AV.data(symbol=ticker, function='D')
    df.set_index(pd.DatetimeIndex(df['date']), inplace=True) if not df.ta.datetime_ordered else None
    df.drop(['dividend', 'split_coefficient'], axis=1, inplace=True) if 'dividend' in df.columns and 'split_coefficient' in df.columns else None
    df.name = ticker
    return df

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

Initialization

In [5]:
price_size = (16, 8)
ind_size = (16, 2)
ticker = 'SPY'
recent = 126
half_of_recent = int(0.5 * recent)

Get Ticker and take a peek

In [6]:
df = farm(ticker)
last_ = df.shape[0]
recent_startdate = df.tail(recent).index[0]
recent_enddate = df.tail(recent).index[-1]
print(f"{df.name}{df.shape} from {recent_startdate} to {recent_enddate}\n{df.describe()}")
df.head()
Out [6]:
SPY(5032, 6) from 2020-01-03 00:00:00 to 2020-07-02 00:00:00
              open         high          low        close        volume
count  5032.000000  5032.000000  5032.000000  5032.000000  5.032000e+03
mean    161.538411   162.482372   160.494256   161.538568  1.151123e+08
std      62.555181    62.736339    62.356801    62.565415  9.808883e+07
min      67.950000    70.000000    67.100000    68.110000  6.790000e+04
25%     115.467500   116.397500   114.552500   115.500000  5.054542e+07
50%     136.480000   137.170000   135.510000   136.420000  8.560940e+07
75%     204.960000   206.205000   204.112500   205.210000  1.531777e+08
max     337.790000   339.080000   337.480000   338.340000  8.708580e+08
date open high low close volume
date
2000-07-03 2000-07-03 145.4375 147.4375 145.1875 147.2812 1436600.0
2000-07-05 2000-07-05 146.3750 146.6562 144.3750 144.6250 2748200.0
2000-07-06 2000-07-06 144.9375 146.4687 144.2187 145.7500 5963200.0
2000-07-07 2000-07-07 146.6875 148.7812 146.2500 148.0937 3034800.0
2000-07-10 2000-07-10 147.8750 148.9062 147.5312 147.8437 2816100.0

Aliases

In [7]:
opendf = df['open']
closedf = df['close']
volumedf = df['volume']

Create some constants for some indicators

In [8]:
help(df.ta.constants) # for more info
df.ta.constants(True, -4, 4)
df.tail()
Out [8]:
Help on method constants in module pandas_ta.core:

constants(append, lower_bound=-100, upper_bound=100, every=1) method of pandas_ta.core.AnalysisIndicators instance
    Constants
    
    Useful for creating indicator levels or if you need some constant value
    easily added to your DataFrame.
    
    Add constant '1' to the DataFrame
    >>> df.ta.constants(True, 1, 1, 1)
    Remove constant '1' to the DataFrame
    >>> df.ta.constants(False, 1, 1, 1)
    
    Adding constants that range of constants from -4 to 4 inclusive
    >>> df.ta.constants(True, -4, 4, 1)
    Removing constants that range of constants from -4 to 4 inclusive
    >>> df.ta.constants(False, -4, 4, 1)
    
    Args:
        append (bool): Default: None.  If True, appends the range of constants to the
            working DataFrame.  If False, it removes the constant range from the working
            DataFrame.
        lower_bound (int): Default: -100.  Lowest integer for the constant range.
        upper_bound (int): Default: 100.  Largest integer for the constant range.
        every (int): Default: 10.  How often to include a new constant.
    
    Returns:
        Returns nothing to the user.  Either adds or removes constant ranges from the
        working DataFrame.

date open high low close volume -4 -3 -2 -1 0 1 2 3 4
date
2020-06-26 2020-06-26 306.1600 306.39 299.4200 300.05 127811745.0 -4 -3 -2 -1 0 1 2 3 4
2020-06-29 2020-06-29 301.4100 304.61 298.9300 304.46 79411577.0 -4 -3 -2 -1 0 1 2 3 4
2020-06-30 2020-06-30 303.9900 310.20 303.8200 308.36 112828251.0 -4 -3 -2 -1 0 1 2 3 4
2020-07-01 2020-07-01 309.5700 311.85 309.0371 310.57 71910372.0 -4 -3 -2 -1 0 1 2 3 4
2020-07-02 2020-07-02 314.2379 315.70 310.5962 312.19 69214995.0 -4 -3 -2 -1 0 1 2 3 4

Price & Volume Charts with Moving Averages

In [9]:
def cscheme(colors):
    aliases = {
        'BkBu': ['black', 'blue'],
        'gr': ['green', 'red'],
        'grays': ['silver', 'gray'],
        'mas': ['black', 'green', 'orange', 'red'],
    }
    aliases['default'] = aliases['gr']
    return aliases[colors]

def machart(kind, fast, medium, slow, append=True, last=last_, figsize=price_size, colors=cscheme('mas')):
    title = ctitle(f"{kind.upper()}s", ticker=ticker, length=last)
    ma1 = df.ta(kind=kind, length=fast, append=append)
    ma2 = df.ta(kind=kind, length=medium, append=append)
    ma3 = df.ta(kind=kind, length=slow, append=append)
    
    madf = pd.concat([closedf, df[[ma1.name, ma2.name, ma3.name]]], axis=1, sort=False).tail(last)
    madf.plot(figsize=figsize, title=title, color=colors, grid=True)    

def volumechart(kind, length=10, last=last_, figsize=ind_size, alpha=0.7, colors=cscheme('gr'), rot=None):
    title = ctitle("Volume", ticker=ticker, length=last)
    volume = pd.DataFrame({'V+': volumedf[closedf > opendf], 'V-': volumedf[closedf < opendf]}).tail(last)
    
    volume.plot(kind='bar', figsize=(figsize[0], 2), width=0.5, color=colors, alpha=alpha, stacked=True, rot=rot)
    vadf = df.ta(kind=kind, close=volumedf, length=length).tail(last)
    vadf.plot(figsize=(figsize[0], 2), lw=1.4, color='black', title=title, grid=True, rot=rot)
In [10]:
machart('ema', 8, 21, 50, last=recent)
volumechart('ema', last=recent, rot=90)

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

In [11]:
clr_ma_length = 8
clrdf = df.ta.log_return(cumulative=True, append=True)
clrmadf = ta.ema(clrdf, length=clr_ma_length)
qqdf = pd.DataFrame({f"{clrdf.name}": clrdf, f"{clrmadf.name}({clrdf.name})": clrmadf})
qqdf.tail(recent).plot(figsize=ind_size, color=cscheme('BkBu'), linewidth=1, title=ctitle(clrdf.name, ticker=ticker, length=recent), grid=True)
Out [11]:
<matplotlib.axes._subplots.AxesSubplot at 0x11db02df0>

MACD

In [12]:
macddf = df.ta.macd(fast=8, slow=21, signal=9, min_periods=None, append=True)
macddf[[macddf.columns[0], macddf.columns[2]]].tail(recent).plot(figsize=(16, 2), color=cscheme('BkBu'), linewidth=1.3)
macddf[macddf.columns[1]].tail(recent).plot.area(figsize=ind_size, stacked=False, color=['silver'], linewidth=1, title=ctitle(macddf.name, ticker=ticker, length=recent), grid=True).axhline(y=0, color="black", lw=1.1)
Out [12]:
<matplotlib.lines.Line2D at 0x11db022b0>

ZScore

In [13]:
zscoredf = df.ta.zscore(length=30, append=True)
zcolors = ['maroon', 'red', 'orange', 'silver', 'silver', 'orange', 'red', 'maroon', 'black', 'blue']
zcols = df[['-4', '-3', '-2', '-1', '1', '2', '3', '4', zscoredf.name]].tail(recent)
zcols.plot(figsize=ind_size, color=zcolors, linewidth=1, title=ctitle(zscoredf.name, ticker=ticker, length=recent), grid=True).axhline(y=0, color="black", lw=1.1)
Out [13]:
<matplotlib.lines.Line2D at 0x11dc20100>

New Features

Archer Moving Average Trends (amat) returns the long and short run trends of fast and slow moving averages.

In [14]:
matype = 'ema'
fast_length = 8
medfast_length = 21
slow_length = 50

amat = df.ta.amat(mamode=matype, fast=fast_length, slow=slow_length)
machart(matype, fast_length, medfast_length, slow_length, last=recent) # Price Chart so we can see the association with AMAT
amat.tail(recent).plot(kind='area', figsize=(16, 0.35), color=cscheme('gr'), alpha=0.4, stacked=False, title=ctitle(f"{amat.name} Trends", ticker=ticker, length=recent))
Out [14]:
<matplotlib.axes._subplots.AxesSubplot at 0x11d489280>

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.

In [15]:
matype = 'sma'
fast_length = 10
medfast_length = 20
slow_length = 50

aobvdf = ta.aobv(close=closedf, volume=volumedf, mamode=matype, fast=fast_length, slow=medfast_length)
aobv_colors = ['black', 'silver', 'silver', 'green', 'red']
aobv_trenddf = aobvdf[aobvdf.columns[-2:]]
aobv_trenddf.name = f"{aobvdf.name} Trends"
In [16]:
machart(matype, fast_length, medfast_length, slow_length, last=recent) # Price Chart so we can see the association with AOBV
volumechart('ema', length=5, last=recent, rot=90)
aobvdf[aobvdf.columns[:5]].tail(recent).plot(figsize=ind_size, color=aobv_colors, title=ctitle(aobvdf.name, ticker=ticker, length=recent), grid=True)
aobv_trenddf.tail(recent).plot(kind='area', figsize=(16, 0.35), color=cscheme('gr'), alpha=0.5, title=ctitle(aobv_trenddf.name), stacked=False)
Out [16]:
<matplotlib.axes._subplots.AxesSubplot at 0x11b37d7f0>

Long Run and Short Run

Long Run (long_run) returns 1 if fast and slow averages approach each other from the below or both are increasing. Otherwise returns zero.

Conversely, Short Run (short_run) returns 1 if fast and slow averages approach each other from above or both are decreasing. Otherwise returns 0.

In [17]:
matype = 'sma'
fast_length = 10
medfast_length = 20
slow_length = 50

machart(matype, fast_length, medfast_length, slow_length, last=half_of_recent)
In [18]:
maf = df.ta(kind=matype, length=fast_length)
mam = df.ta(kind=matype, length=medfast_length)
lrun = df.ta.long_run(maf, mam, append=False)  # Long Run of Fast MA and Slow MA
srun = df.ta.short_run(maf, mam, append=False) # Short Run of Fast MA and Slow MA
                   
srun.tail(half_of_recent).plot(kind='bar', figsize=(16,0.25), color=['red'], linewidth=1, alpha=0.45)#, rot=45)
lrun.tail(half_of_recent).plot(kind='bar', figsize=(16,0.25), color=['green'], linewidth=1, alpha=0.45, title=ctitle(f"{maf.name} & {mam.name} ({lrun.name}(green) & {maf.name} & {mam.name}{srun.name}(red))", length=half_of_recent))#, rot=45)
Out [18]:
<matplotlib.axes._subplots.AxesSubplot at 0x11e2ce6a0>

Cross

Cross (cross) returns 1 if two series cross and 0 if they do not. By default, above=True.

In [19]:
machart(matype, fast_length, medfast_length, slow_length, last=half_of_recent)
In [20]:
maf = df.ta(kind=matype, length=fast_length)
cross_above = ta.cross(closedf, maf, above=True)
cross_above.tail(int(0.5 * recent)).plot(kind='bar', figsize=(16, 0.5), color=['green'], linewidth=1, alpha=0.55, stacked=False)#, rot=45)

cross_below = ta.cross(closedf, maf, above=False)
cross_below.tail(int(0.5 * recent)).plot(kind='bar', figsize=(16, 0.5), color=['red'], linewidth=1, alpha=0.55, stacked=False, title=ctitle(f"{cross_above.name} (orange) & {cross_below.name} (blue)", length=int(0.5 * recent)))#, rot=45)
Out [20]:
<matplotlib.axes._subplots.AxesSubplot at 0x11b293790>
In [21]:
def recent_crosses(series, **kwargs):
    last = kwargs.pop('last', 5)
    timestamp = list(series[series > 0].tail(last).index[::-1])
    return [t.strftime('%Y-%m-%d') for t in timestamp]

last_n_crosses = 5
recent_crosses_above = recent_crosses(cross_above, last=last_n_crosses)
recent_crosses_below = recent_crosses(cross_below, last=last_n_crosses)
print(f"\nMost recent {cross_above.name} Dates:\n    {', '.join(recent_crosses_above)}")
print(f"Most recent {cross_below.name} Dates:\n    {', '.join(recent_crosses_below)}", "\n")
Most recent close_XA_SMA_10 Dates:
    2020-06-30, 2020-06-23, 2020-05-18, 2020-05-07, 2020-05-05
Most recent close_XB_SMA_10 Dates:
    2020-06-24, 2020-06-11, 2020-05-12, 2020-05-06, 2020-05-01 

Trend Return

  • Trend Return (trend_return) calculates the cumulative log returns from a specified trend.
  • There is no limit on how to construct a trend. It can be as simple as: close > SMA(close, 50).
In [22]:
def simple_ma_strategies(kind, fast, slow, cumulative=True, last=last_, figsize=(16, 2), colors=cscheme('default'), alpha=0.35):
    """A very basic long/short cumulative log return model proof of concept (NOT A STRATEGY RECOMMENDATION)"""
    title = ctitle(f"{'Cumulative ' if cumulative else ''}Trend Returns of {kind.upper()}s")
    last = last if last is not None else df.shape[0]
    closedf = df['close']
    maf = df.ta(kind=kind, length=fast)
    mas = df.ta(kind=kind, length=slow)
    
    def ma_return_name(name):
        return f"{name} {' Cumulative' if cumulative else ''} Trend Return"
    
    # Trade Logic
    long = (closedf > maf) & (maf > mas)
    short = ~long
    cum_long_return = ta.trend_return(closedf, long, cumulative=cumulative)
    cum_short_return = ta.trend_return(closedf, short, cumulative=cumulative)
    
    tdf = pd.DataFrame({
        ma_return_name(f"long: {maf.name} > {mas.name}"): cum_long_return,
        ma_return_name(f"short: {maf.name} < {mas.name}"): cum_short_return,
    })
    tdf.set_index(closedf.index, inplace=True)
    window = tdf.tail(last)
    window.plot(kind='area', figsize=figsize, color=colors, linewidth=1, alpha=alpha, title=title, stacked=False, grid=True).axhline(y=0, color="black", lw=1.1)
In [23]:
matype = 'ema'
fast_length = 10
medfast_length = 20
slow_length = 50

simple_ma_strategies(matype, fast=fast_length, slow=slow_length, last=recent, colors=cscheme('gr'), alpha=0.5)
machart(matype, fast_length, medfast_length, slow_length, last=recent)
volumechart(matype, last=recent)

Disclaimer

  • All investments involve risk, and the past performance of a security, industry, sector, market, financial product, trading strategy, or individuals 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.

In [ ]: