Files
pandas-ta/examples/AIExample.ipynb
T

267 KiB
Raw Blame History

TA Analysis with Pandas TA and AI/ML

  • 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.32b0
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(7310, 7): 3552.4306 ms (3.5524 s)
[+] Saving: /Users/kj/av_data/SPY_D.csv
[i] Analysis Time: 33.4526 ms (0.0335 s)
[+] Downloading[yahoo]: QQQ[D]
[+] yf | QQQ(5768, 7): 3149.0533 ms (3.1491 s)
[+] Saving: /Users/kj/av_data/QQQ_D.csv
[i] Analysis Time: 3.2938 ms (0.0033 s)
[+] Downloading[yahoo]: AAPL[D]
[+] yf | AAPL(10377, 7): 3140.3905 ms (3.1404 s)
[+] Saving: /Users/kj/av_data/AAPL_D.csv
[i] Analysis Time: 3.6440 ms (0.0036 s)
[+] Downloading[yahoo]: TSLA[D]
[+] yf | TSLA(2924, 7): 2794.6956 ms (2.7947 s)
[+] Saving: /Users/kj/av_data/TSLA_D.csv
[i] Analysis Time: 2.8422 ms (0.0028 s)
[+] Downloading[yahoo]: BTC-USD[D]
[+] yf | BTC-USD(2701, 7): 3494.6843 ms (3.4947 s)
[+] Saving: /Users/kj/av_data/BTC-USD_D.csv
[i] Analysis Time: 3.6090 ms (0.0036 s)

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 (5768, 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-02-07 121.609821 122.053483 121.494090 121.802719 17541100 0.0 121.014753 119.871375 116.896796 110.775148 17233090.0
2017-02-08 121.638777 122.178877 121.407299 122.005272 12569100 0.0 121.117952 120.059447 117.052081 110.863883 17052715.0
2017-02-09 122.130652 122.718979 122.063140 122.448929 17542700 0.0 121.252978 120.253788 117.221432 110.957372 16895550.0
2017-02-10 122.718971 123.027601 122.506787 122.853996 13915700 0.0 121.405363 120.477544 117.390999 111.057047 16610210.0
2017-02-13 123.230132 123.702720 123.181913 123.548416 19120200 0.0 121.721710 120.715768 117.602149 111.166413 16719680.0
... ... ... ... ... ... ... ... ... ... ... ...
2022-02-01 364.429993 366.190002 359.140015 365.519989 74433000 0.0 354.433997 368.523997 383.935446 365.349531 94048835.0
2022-02-02 369.760010 370.100006 364.290009 368.489990 78777100 0.0 354.634995 367.124997 383.235753 365.515881 95086330.0
2022-02-03 358.529999 361.929993 352.459991 353.549988 95384800 0.0 353.817993 365.587996 382.330939 365.593182 96068580.0
2022-02-04 354.079987 361.399994 351.970001 358.010010 86127500 0.0 354.449994 364.287497 381.551680 365.713161 96834240.0
2022-02-07 358.619995 361.040009 354.950012 358.165009 42674328 0.0 354.936496 363.202748 380.749953 365.812890 95337256.4

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-01 0.0 354.433997 368.523997 383.935446 365.349531 94048835.0 357.733558 366.959410 376.636208 0.006803
2022-02-02 0.0 354.634995 367.124997 383.235753 365.515881 95086330.0 360.123876 367.098553 376.316749 0.008125
2022-02-03 0.0 353.817993 365.587996 382.330939 365.593182 96068580.0 358.663012 365.866866 375.423935 -0.040544
2022-02-04 0.0 354.449994 364.287497 381.551680 365.713161 96834240.0 358.517901 365.152606 374.741036 0.012615
2022-02-07 0.0 354.936496 363.202748 380.749953 365.812890 95337256.4 358.439480 364.517370 374.090995 0.000433

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-01 0 0 0 0
2022-02-02 0 0 0 0
2022-02-03 0 0 0 0
2022-02-04 0 0 0 0
2022-02-07 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: 74.29%
Signal Entry Exit
Date
2020-04-27 1 213.766617 NaN
2020-10-01 -1 NaN 280.388519
2020-10-16 1 286.607269 NaN
2021-03-11 -1 NaN 316.515167
2021-04-13 1 339.395111 NaN
2021-05-26 -1 NaN 332.947998
2021-06-16 1 339.803650 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]
f_date = f"{first_date.day_name()} {first_date.month}-{first_date.day}-{first_date.year}"
l_date = f"{last_date.day_name()} {last_date.month}-{last_date.day}-{last_date.year}"
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)})"
ptitle = f"\n{ticker} [{tf} for {duration}({recent} bars)] from {f_date} to {l_date}\n{last_ohlcv}\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)] from Tuesday 2-7-2017 to Monday 2-7-2022\nLast OHLCV: (358.6200, 361.0400, 354.9500, 358.1650, 42674328)\nMonday February 7, 2022, NYSE: 7:58:00, Local: 11:58:00 PST, Day 38/365 (10.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=colors()[0], alpha=0.25) # Green Area
short_trend.plot(figsize=(16, 0.85), kind="area", stacked=True, color=colors()[1], alpha=0.25) # 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=colors("GyOr"), alpha=1, grid=True).axhline(0, color="black")
Out [14]:
<matplotlib.lines.Line2D at 0x1301d8c10>

Buy and Hold Returns (PCTRET_1)

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

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")
Out [16]:
<matplotlib.lines.Line2D at 0x1302d8a00>

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.