Files
pandas-ta/examples/TA_Analysis.ipynb
T

276 KiB
Raw Blame History

TA Analysis with Pandas TA

  • This is a Work in Progress and subject to change!
  • Contributions are welcome and accepted!
  • 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.

Required Packages

Uncomment the packages you need to install or are missing
In [1]:
#!pip install numpy
#!pip install pandas
#!pip install mplfinance
#!pip install pandas-datareader
#!pip install requests_cache
#!pip install tqdm
#!pip install alphaVantage-api # Required for Watchlist
In [2]:
%pylab inline
import datetime as dt
import random as rnd
from sys import float_info as sflt

from tqdm import tqdm

import numpy as np
import pandas as pd
pd.set_option("max_rows", 100)
pd.set_option("max_columns", 20)

import mplfinance as mpf
import pandas_ta as ta

from tqdm.notebook import trange, tqdm

from watchlist import colors, Watchlist # Is this failing? If so, copy it locally. See above.

print(f"Numpy v{np.__version__}")
print(f"Pandas v{pd.__version__}")
print(f"mplfinance v{mpf.__version__}")
print(f"\nPandas TA v{ta.version}\nTo install the Latest Version:\n$ pip install -U git+https://github.com/twopirllc/pandas-ta\n")
%matplotlib inline
Populating the interactive namespace from numpy and matplotlib
Numpy v1.20.3
Pandas v1.3.0
mplfinance v0.12.7a17

Pandas TA v0.3.63b0
To install the Latest Version:
$ pip install -U git+https://github.com/twopirllc/pandas-ta

MISC Function(s)

In [3]:
def recent_bars(df, tf: str = "1y"):
    # All Data: 0, Last Four Years: 0.25, Last Two Years: 0.5, This Year: 1, Last Half Year: 2, Last Quarter: 4
    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[tf] if tf in yearly_divisor.keys() else 0
    return int(ta.RATE["TRADING_DAYS_PER_YEAR"] / yd) if yd > 0 else df.shape[0]

Data Collection

In [4]:
tf = "D"
tickers = ["SPY", "QQQ", "AAPL", "TSLA", "BTC-USD"]
watch = Watchlist(tickers, tf=tf, ds_name="yahoo", timed=True)
# watch.study = ta.CommonStudy # If you have a Custom Study, you can use it here.
watch.load(tickers, analyze=True, verbose=False)
[!] Loading All: SPY, QQQ, AAPL, TSLA, BTC-USD
[+] Downloading[yahoo]: SPY[D]
[+] yf | SPY(7367, 7): 3196.9914 ms (3.1970 s)
[+] Saving: /Users/kj/av_data/SPY_D.csv
[i] Analysis Time: 34.3883 ms (0.0344 s) for 5 columns (avg 6.8791 ms / col)
[+] Downloading[yahoo]: QQQ[D]
[+] yf | QQQ(5825, 7): 4333.8306 ms (4.3338 s)
[+] Saving: /Users/kj/av_data/QQQ_D.csv
[i] Analysis Time: 3.3304 ms (0.0033 s) for 5 columns (avg 0.6667 ms / col)
[+] Downloading[yahoo]: AAPL[D]
[+] yf | AAPL(10434, 7): 4274.9140 ms (4.2749 s)
[+] Saving: /Users/kj/av_data/AAPL_D.csv
[i] Analysis Time: 3.6997 ms (0.0037 s) for 5 columns (avg 0.7406 ms / col)
[+] Downloading[yahoo]: TSLA[D]
[+] yf | TSLA(2981, 7): 3129.0620 ms (3.1291 s)
[+] Saving: /Users/kj/av_data/TSLA_D.csv
[i] Analysis Time: 3.0117 ms (0.0030 s) for 5 columns (avg 0.6030 ms / col)
[+] Downloading[yahoo]: BTC-USD[D]
[+] yf | BTC-USD(2784, 7): 3533.1249 ms (3.5331 s)
[+] Saving: /Users/kj/av_data/BTC-USD_D.csv
[i] Analysis Time: 3.6089 ms (0.0036 s) for 5 columns (avg 0.7226 ms / col)

Asset Selection

In [5]:
ticker = tickers[1] # change tickers by changing the index
print(f"{ticker} {watch.data[ticker].shape}\nColumns: {', '.join(list(watch.data[ticker].columns))}")
QQQ (5825, 12)
Columns: Open, High, Low, Close, Volume, Dividends, Stock Splits, SMA_10, SMA_20, SMA_50, SMA_200, VOL_SMA_20

Trim it

In [6]:
duration = "5y"
asset = watch.data[ticker]
recent = recent_bars(asset, duration)
asset.columns = asset.columns.str.lower()
asset.drop(columns=["dividends", "split"], errors="ignore", inplace=True)
asset = asset.copy().tail(recent)
asset
Out [6]:
open high low close volume stock splits sma_10 sma_20 sma_50 sma_200 vol_sma_20
Date
2017-05-01 131.702961 132.610327 131.645046 132.436569 24815500 0.0 129.452889 128.293103 127.098378 117.352839 20653180.0
2017-05-02 132.600685 132.716529 132.262846 132.658600 18345100 0.0 130.046537 128.540698 127.250689 117.480589 20385420.0
2017-05-03 132.359333 132.407600 131.876692 132.233856 23827600 0.0 130.577444 128.756921 127.382175 117.602671 20906800.0
2017-05-04 132.214573 132.658594 131.818804 132.282135 14630100 0.0 131.006993 129.001620 127.513663 117.726958 20037745.0
2017-05-05 132.619938 132.764725 132.127651 132.764725 19330700 0.0 131.487700 129.267070 127.664244 117.847383 20062670.0
... ... ... ... ... ... ... ... ... ... ... ...
2022-04-25 323.730011 329.899994 322.429993 329.579987 101755900 0.0 338.041000 349.745001 344.924885 367.586838 67700730.0
2022-04-26 327.470001 327.660004 316.859985 317.140015 105819600 0.0 335.666000 347.356502 344.335056 367.366986 70150015.0
2022-04-27 317.239990 322.880005 315.000000 316.760010 111204200 0.0 333.397000 344.635002 343.729037 367.145234 72306575.0
2022-04-28 321.850006 329.890015 317.519989 328.010010 99450000 0.0 331.563000 342.681003 343.175432 366.976494 73841190.0
2022-04-29 323.700012 327.230011 312.600006 313.250000 91856700 0.0 329.045001 340.216502 342.328425 366.746658 75083455.0

1260 rows × 11 columns

Trend Creation

A Trend is the result of some calculation or condition of one or more indicators. For simplicity, a Trend is either True or 1 and No Trend is False or 0. Using the Hello World of Trends, the Golden/Death Cross, it's Trend is Long when long = ma(close, 50) > ma(close, 200) and Short when short = ma(close, 50) < ma(close, 200) .

In [7]:
# Example Long Trends
# long = ta.sma(asset.close, 50) > ta.sma(asset.close, 200) # SMA(50) > SMA(200) "Golden/Death Cross"
long = ta.sma(asset.close, 20) > ta.sma(asset.close, 50) # SMA(20) > SMA(50)
# long = ta.ema(asset.close, 8) > ta.ema(asset.close, 21) # EMA(8) > EMA(21)
# long = ta.increasing(ta.ema(asset.close, 20))
# long = ta.macd(asset.close).iloc[:,1] > 0 # MACD Histogram is positive

# long &= ta.increasing(ta.ema(asset.close, 50), 2) # Uncomment for further long restrictions, in this case when EMA(50) is increasing/sloping upwards
# long = 1 - long # uncomment to create a short signal of the trend

asset.ta.ema(length=8, sma=False, append=True)
asset.ta.ema(length=21, sma=False, append=True)
asset.ta.ema(length=50, sma=False, append=True)
asset.ta.percent_return(append=True, cumulative=False)
print("TA Columns Added:")
asset[asset.columns[5:]].tail()
Out [7]:
TA Columns Added:
stock splits sma_10 sma_20 sma_50 sma_200 vol_sma_20 EMA_8 EMA_21 EMA_50 PCTRET_1
Date
2022-04-25 0.0 338.041000 349.745001 344.924885 367.586838 67700730.0 336.456901 343.546748 348.865113 0.012846
2022-04-26 0.0 335.666000 347.356502 344.335056 367.366986 70150015.0 332.164260 341.146136 347.620992 -0.037745
2022-04-27 0.0 333.397000 344.635002 343.729037 367.145234 72306575.0 328.741093 338.929215 346.410757 -0.001198
2022-04-28 0.0 331.563000 342.681003 343.175432 366.976494 73841190.0 328.578630 337.936560 345.689159 0.035516
2022-04-29 0.0 329.045001 340.216502 342.328425 366.746658 75083455.0 325.172268 335.692328 344.417035 -0.044999

Trend Signals

Given a Trend, Trend Signals returns the Trend, Trades, Entries and Exits as boolean integers. When asbool=True, it returns Trends, Entries and Exits as boolean values which is helpful when combined with the vectorbt backtesting package.

In [8]:
trendy = asset.ta.tsignals(long, asbool=False, append=True)
trendy.tail()
Out [8]:
tsignals
TS_Trends TS_Trades TS_Entries TS_Exits
Date
2022-04-25 1 0 0 0
2022-04-26 1 0 0 0
2022-04-27 1 0 0 0
2022-04-28 0 -1 0 1
2022-04-29 0 0 0 0

Trend Entries & Exits & Trade Table

This is a simple way to reduce the Asset DataFrame to a Trade Table with Dates, Signals, and Entries and Exits. Gives you an idea what to expect before running through a backtester such as vectorbt.

In [9]:
entries = trendy.TS_Entries * asset.close
entries = entries[~np.isclose(entries, 0)]
entries.dropna(inplace=True)
entries.name = "Entry"

exits = trendy.TS_Exits * asset.close
exits = exits[~np.isclose(exits, 0)]
exits.dropna(inplace=True)
exits.name = "Exit"

total_trades = trendy.TS_Trades.abs().sum()
rt_trades = int(trendy.TS_Trades.abs().sum() // 2)

all_trades = trendy.TS_Trades.copy().fillna(0)
all_trades = all_trades[all_trades != 0]

trades = pd.DataFrame({
    "Signal": all_trades,
    entries.name: entries,
    exits.name: exits
})

# Show some stats if there is an active trade (when there is an odd number of round trip trades)
if total_trades % 2 != 0:
    unrealized_pnl = asset.close.iloc[-1] - entries.iloc[-1]
    unrealized_pnl_pct_change = 100 * ((asset.close.iloc[-1] / entries.iloc[-1]) - 1)
    print("Current Trade:")
    print(f"Price Entry | Last:\t{entries.iloc[-1]:.4f} | {asset.close.iloc[-1]:.4f}")
    print(f"Unrealized PnL | %:\t{unrealized_pnl:.4f} | {unrealized_pnl_pct_change:.4f}%")
print(f"\nTrades Total | Round Trip:\t{total_trades} | {rt_trades}")
print(f"Trade Coverage: {100 * asset.TS_Trends.sum() / asset.shape[0]:.2f}%")

tradelist = trades
if rt_trades > 10:
    tradelist = trades.tail(10)
tradelist
Out [9]:
Trades Total | Round Trip:	22 | 11
Trade Coverage: 71.19%
Signal Entry Exit
Date
2020-10-16 1 286.253357 NaN
2021-03-11 -1 NaN 316.124359
2021-04-13 1 338.976044 NaN
2021-05-26 -1 NaN 332.536896
2021-06-16 1 339.384125 NaN
2021-10-05 -1 NaN 356.483398
2021-11-02 1 388.073944 NaN
2022-01-07 -1 NaN 379.390961
2022-04-05 1 361.100006 NaN
2022-04-28 -1 NaN 328.010010
In [ ]:

Visualization

Chart Display Strings

In [10]:
extime = ta.get_time(to_string=True)
first_date, last_date = asset.index[0], asset.index[-1]
last_ohlcv = f"Last OHLCV: ({asset.iloc[-1].open:.4f}, {asset.iloc[-1].high:.4f}, {asset.iloc[-1].low:.4f}, {asset.iloc[-1].close:.4f}, {int(asset.iloc[-1].volume)})"
_oc_change = asset.iloc[-1].open - asset.iloc[-1].close
_oc_change_pct = _oc_change / asset.iloc[-1].open
oc_change = f"{_oc_change:.4f} ({100 * _oc_change_pct:.4f} %)"
ptitle = f"\n{ticker} [{tf} for {duration}({recent} bars)]\n{last_ohlcv}, Change (%): {oc_change}\n{extime}"

Trade Chart

In [11]:
# chart = asset["close"] #asset[["close", "SMA_10", "SMA_20", "SMA_50", "SMA_200"]]
# chart = asset[["close", "SMA_10", "SMA_20"]]
chart = asset[["close", "EMA_8", "EMA_21", "EMA_50"]]
chart.plot(figsize=(16, 10), color=colors("BkGrOrRd"), title=ptitle, grid=True)
Out [11]:
<AxesSubplot:title={'center':'\nQQQ [D for 5y(1260 bars)]\nLast OHLCV: (323.7000, 327.2300, 312.6000, 313.2500, 91856700), Change (%): 10.4500 (3.2283 %)\nSunday May 1, 2022, NYSE: 14:15:43, Local: 18:15:43 PDT, Day 121/365 (33.00%)'}, xlabel='Date'>

Trends are either a Trend (1) or No Trend (0) depending on the Trend passed into *Trend Signals

In [12]:
long_trend = trendy.TS_Trends
short_trend = 1 - long_trend

long_trend.plot(figsize=(16, 0.85), kind="area", stacked=True, color=["limegreen"], alpha=0.65) # Green Area
short_trend.plot(figsize=(16, 0.85), kind="area", stacked=True, color=["orangered"], alpha=0.65) # Red Area
Out [12]:
<AxesSubplot:xlabel='Date'>

Trades or Trade Signals

The Trades are either Enter (1) or Exit (-1) or No Position/Action (0). These are based on the Trend passed into Trend Signals whether they are Long or Short Trends.

In [13]:
trendy.TS_Trades.plot(figsize=(16, 1.5), color=colors("BkBl")[0], grid=True)
Out [13]:
<AxesSubplot:xlabel='Date'>

Active Returns

Active Returns are returns made during the course of the Trend. They are simply the product of the returns and the Trend

In [14]:
asset["ACTRET_1"] = trendy.TS_Trends.shift(1) * asset.PCTRET_1
asset[["PCTRET_1", "ACTRET_1"]].plot(figsize=(16, 3), color=["gray", "limegreen"], alpha=1, grid=True).axhline(0, color="black")
Out [14]:
<matplotlib.lines.Line2D at 0x132855d00>

Buy and Hold Returns (PCTRET_1)

In [15]:
((asset.PCTRET_1 + 1).cumprod() - 1).plot(figsize=(16, 3), kind="area", stacked=False, color=["limegreen"], title="B&H Percent Returns", alpha=0.9, grid=True).axhline(0, color="black")
Out [15]:
<matplotlib.lines.Line2D at 0x13206da60>

Cum. Active Returns (ACTRET_1)

In [16]:
# ((asset.ACTRET_1 + 1).cumprod() - 1).plot(figsize=(16, 3), kind="area", stacked=False, color=colors("GyOr")[-1], title="B&H Cum. Active Returns", alpha=0.4, grid=True).axhline(0, color="black")
((asset.ACTRET_1 + 1).cumprod() - 1).plot(figsize=(16, 3), kind="area", stacked=False, color=["limegreen"], title="B&H Cum. Active Returns", alpha=0.65, grid=True).axhline(0, color="black")
Out [16]:
<matplotlib.lines.Line2D at 0x133835ee0>

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.