Files
pandas-ta/examples/TA_Analysis.ipynb
T

273 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.54b0
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(7328, 7): 3235.1207 ms (3.2351 s)
[+] Saving: /Users/kj/av_data/SPY_D.csv
[i] Analysis Time: 33.3046 ms (0.0333 s) for 5 columns (avg 6.6620 ms / col).
[+] Downloading[yahoo]: QQQ[D]
[+] yf | QQQ(5786, 7): 3151.7639 ms (3.1518 s)
[+] Saving: /Users/kj/av_data/QQQ_D.csv
[i] Analysis Time: 3.4324 ms (0.0034 s) for 5 columns (avg 0.6871 ms / col).
[+] Downloading[yahoo]: AAPL[D]
[+] yf | AAPL(10395, 7): 2917.6695 ms (2.9177 s)
[+] Saving: /Users/kj/av_data/AAPL_D.csv
[i] Analysis Time: 4.1406 ms (0.0041 s) for 5 columns (avg 0.8293 ms / col).
[+] Downloading[yahoo]: TSLA[D]
[+] yf | TSLA(2942, 7): 2911.5245 ms (2.9115 s)
[+] Saving: /Users/kj/av_data/TSLA_D.csv
[i] Analysis Time: 2.8767 ms (0.0029 s) for 5 columns (avg 0.5759 ms / col).
[+] Downloading[yahoo]: BTC-USD[D]
[+] yf | BTC-USD(2726, 7): 2840.3963 ms (2.8404 s)
[+] Saving: /Users/kj/av_data/BTC-USD_D.csv
[i] Analysis Time: 4.2543 ms (0.0043 s) for 5 columns (avg 0.8517 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 (5786, 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-03-06 125.949921 126.297130 125.583433 126.084946 12026200 0.0 125.923898 124.588588 120.784825 112.861735 16201560.0
2017-03-07 125.834208 126.422534 125.650956 125.872780 15500900 0.0 125.929687 124.814274 120.978683 112.987363 16395100.0
2017-03-08 125.949941 126.490041 125.824570 126.094620 15776000 0.0 125.952836 125.028869 121.183537 113.108551 16306845.0
2017-03-09 126.065715 126.364698 125.515961 126.191086 20855300 0.0 126.032890 125.238160 121.388776 113.230891 16721155.0
2017-03-10 126.769746 126.924064 126.171780 126.721519 21922600 0.0 126.141877 125.451790 121.592664 113.345694 16940150.0
... ... ... ... ... ... ... ... ... ... ... ...
2022-02-28 342.510010 348.540009 341.320007 346.799988 77226200 0.0 344.717999 352.113498 368.444710 367.035715 80833990.0
2022-03-01 345.750000 348.079987 338.899994 341.489990 67407000 0.0 344.117999 351.035498 367.547579 367.116988 79488020.0
2022-03-02 343.079987 348.589996 340.239990 347.220001 70609000 0.0 343.226999 350.120499 366.803599 367.236776 79296820.0
2022-03-03 349.929993 350.040009 340.350006 342.260010 69815200 0.0 341.848999 348.809000 366.034999 367.342676 78848725.0
2022-03-04 339.839996 341.309998 335.140015 335.320007 29548226 0.0 340.835999 347.897501 364.957199 367.412032 75556896.3

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-02-28 0.0 344.717999 352.113498 368.444710 367.035715 80833990.0 344.307560 350.872017 362.574876 0.002979
2022-03-01 0.0 344.117999 351.035498 367.547579 367.116988 79488020.0 343.681434 350.019105 361.748018 -0.015311
2022-03-02 0.0 343.226999 350.120499 366.803599 367.236776 79296820.0 344.467782 349.764641 361.178292 0.016779
2022-03-03 0.0 341.848999 348.809000 366.034999 367.342676 78848725.0 343.977166 349.082402 360.436398 -0.014285
2022-03-04 0.0 340.835999 347.897501 364.957199 367.412032 75556896.3 342.053353 347.831275 359.451442 -0.020277

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]:
TS_Trends TS_Trades TS_Entries TS_Exits
Date
2022-02-28 0 0 0 0
2022-03-01 0 0 0 0
2022-03-02 0 0 0 0
2022-03-03 0 0 0 0
2022-03-04 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: 72.86%
Signal Entry Exit
Date
2020-04-27 1 213.766632 NaN
2020-10-01 -1 NaN 280.388519
2020-10-16 1 286.607269 NaN
2021-03-11 -1 NaN 316.515198
2021-04-13 1 339.395142 NaN
2021-05-26 -1 NaN 332.947998
2021-06-16 1 339.803680 NaN
2021-10-05 -1 NaN 356.924133
2021-11-02 1 388.553711 NaN
2022-01-07 -1 NaN 379.859985
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: (339.8400, 341.3100, 335.1400, 335.3200, 29548226), Change (%): 4.5200 (1.3300 %)\nFriday March 4, 2022, NYSE: 4:12:24, Local: 8:12:24 PST, Day 63/365 (17.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 0x10e668ee0>

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 0x132503fd0>

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 0x1325a1670>

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.