How to Build Technical Analysis and Backtesting Workflow with pandas-ta-classic, Strategy Signals, and Performance Metrics
In this tutorial, we implement how to use pandas-ta-classic to construct an entire technical evaluation and buying and selling technique workflow. We begin by putting in the required libraries, downloading historic OHLCV inventory knowledge with yfinance, cleansing the returned knowledge construction, and inspecting the accessible indicator classes contained in the library. We then calculate well-liked indicators corresponding to SMA, EMA, RSI, ATR, MACD, Bollinger Bands, candlestick patterns, and a customized distance-from-EMA function. Also, we mix each day and weekly indicators, create entry and exit logic, backtest the technique with shifted positions, calculate efficiency metrics, run a parameter sweep, and visualize worth motion, RSI habits, commerce indicators, and fairness curves in a structured approach.
import subprocess, sys
def _pip(pkgs):
subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", *pkgs])
_pip(["pandas-ta-classic", "yfinance", "matplotlib"])
import numpy as np
import pandas as pd
import yfinance as yf
import pandas_ta_classic as ta
import matplotlib.pyplot as plt
from itertools import product
pd.set_option("show.max_columns", 80)
pd.set_option("show.width", 200)
TICKER, START, END = "AAPL", "2018-01-01", "2024-12-31"
uncooked = yf.obtain(TICKER, begin=START, finish=END, auto_adjust=True, progress=False)
if isinstance(uncooked.columns, pd.MultiIndex):
uncooked.columns = uncooked.columns.get_level_values(0)
df = (uncooked.rename(columns=str.decrease)
[["open", "high", "low", "close", "volume"]]
.dropna()
.copy())
df.index.identify = "date"
print(f"[data] {TICKER}: {len(df)} rows "
f"{df.index.min().date()} → {df.index.max().date()}")
print("[lib] Categories:", checklist(ta.Category.keys()))
for cat in ("momentum", "overlap", "development", "volatility", "quantity"):
names = ta.Category.get(cat, [])
print(f"[lib] {cat:<11} ({len(names):>3}): "
f"{', '.be a part of(names[:8])}{' ...' if len(names) > 8 else ''}")
We set up the required packages and import the principle libraries wanted for technical evaluation, knowledge dealing with, plotting, and parameter mixtures. We obtain Apple’s historic OHLCV knowledge utilizing yfinance, clear the returned DataBody, and convert column names to lowercase for simpler processing. We additionally evaluation the accessible pandas-ta-classic indicator classes to perceive which technical indicators we are able to use within the tutorial.
df.ta.sma(size=20, append=True)
df.ta.sma(size=50, append=True)
df.ta.ema(size=200, append=True)
df.ta.rsi(size=14, append=True)
df.ta.atr(size=14, append=True)
df.ta.macd(append=True)
df.ta.bbands(size=20, std=2.0, append=True)
my_strategy = ta.Strategy(
identify="AdvancedDemo",
description="Trend + momentum + quantity + volatility in a single shot",
ta=[
{"kind": "hma", "length": 30},
{"kind": "adx", "length": 14},
{"kind": "aroon", "length": 14},
{"kind": "stoch", "k": 14, "d": 3},
{"kind": "obv"},
{"kind": "mfi", "length": 14},
{"kind": "willr", "length": 14},
{"kind": "cci", "length": 20},
{"kind": "kc", "length": 20, "scalar": 2},
],
)
df.ta.technique(my_strategy)
print(f"[strat] DataBody now has {df.form[1]} columns")
df["dist_ema200_pct"] = (df["close"] / df["EMA_200"] - 1.0) * 100
df.ta.cdl_doji(append=True)
df.ta.cdl_inside(append=True)
doji_col = subsequent((c for c in df.columns if c.startswith("CDL_DOJI")), None)
print(f"[cdl] Doji days detected: {int((df[doji_col] == 100).sum())}")
We apply a number of generally used technical indicators instantly by way of the .ta DataBody extension. We calculate shifting averages, RSI, ATR, MACD, Bollinger Bands, and then run a customized multi-indicator technique utilizing ta.Strategy. We additionally create a customized EMA-distance function and detect candlestick patterns corresponding to Doji and Inside candles.
weekly = (df[["open", "high", "low", "close", "volume"]]
.resample("W-FRI")
.agg({"open":"first","excessive":"max","low":"min","shut":"final","quantity":"sum"})
.dropna())
weekly["RSI_W_14"] = ta.rsi(weekly["close"], size=14)
df = df.be a part of(weekly[["RSI_W_14"]])
df["RSI_W_14"] = df["RSI_W_14"].ffill().shift(1)
development = df["SMA_20"] > df["SMA_50"]
mom_cross = (df["RSI_14"] > 50) & (df["RSI_14"].shift(1) <= 50)
mtf_ok = df["RSI_W_14"] > 50
exit_cond = (df["RSI_14"] < 45) | (df["SMA_20"] < df["SMA_50"])
place = np.zeros(len(df), dtype=int)
in_pos = False
for i in vary(len(df)):
if not in_pos and development.iat[i] and mom_cross.iat[i] and bool(mtf_ok.iat[i]):
in_pos = True
elif in_pos and exit_cond.iat[i]:
in_pos = False
place[i] = 1 if in_pos else 0
df["pos"] = place
df["ret"] = df["close"].pct_change().fillna(0.0)
df["strat_ret"] = df["pos"].shift(1).fillna(0) * df["ret"]
We create a weekly model of the each day OHLCV knowledge and calculate weekly RSI for higher-timeframe affirmation. We be a part of the weekly RSI again to the each day DataBody and shift it to keep away from utilizing future data in our buying and selling logic. We then outline the development, momentum, multi-timeframe filter, exit situation, place state, each day returns, and technique returns.
def perf(returns, ppy=252):
r = returns.dropna()
if len(r) == 0 or r.std() == 0:
return {}
cum = (1 + r).cumprod()
cagr = cum.iloc[-1] ** (ppy / len(r)) - 1
vol = r.std() * np.sqrt(ppy)
sharpe = (r.imply() / r.std()) * np.sqrt(ppy)
draw back = r[r < 0].std() * np.sqrt(ppy)
sortino = (r.imply() * ppy) / draw back if draw back > 0 else np.nan
mdd = (cum / cum.cummax() - 1).min()
nz = r[r != 0]
win = (nz > 0).imply() if len(nz) else 0.0
return {"CAGR": cagr, "Vol": vol, "Sharpe": sharpe,
"Sortino": sortino, "MaxDD": mdd, "WinRate": win,
"RemainingEquity": cum.iloc[-1]}
abstract = pd.DataBody({
"Buy & Hold": perf(df["ret"]),
"Strategy": perf(df["strat_ret"]),
}).T
print("n[perf] ----------------------------------------")
print(abstract.spherical(4))
def quick_bt(costs, quick, gradual, rsi_thr=50):
if quick >= gradual:
return None
d = costs.copy()
d["SMAf"] = ta.sma(d["close"], size=quick)
d["SMAs"] = ta.sma(d["close"], size=gradual)
d["RSI"] = ta.rsi(d["close"], size=14)
sig = ((d["SMAf"] > d["SMAs"]) & (d["RSI"] > rsi_thr)).astype(int)
sret = sig.shift(1).fillna(0) * d["close"].pct_change().fillna(0)
return perf(sret)
costs = df[["open", "high", "low", "close", "volume"]]
rows = []
for quick, gradual in product([5, 10, 20, 30], [50, 100, 150, 200]):
m = quick_bt(costs, quick, gradual)
if m:
rows.append({"quick": quick, "gradual": gradual, **m})
sweep = (pd.DataBody(rows)
.sort_values("Sharpe", ascending=False)
.reset_index(drop=True))
print("n[sweep] Top 5 (quick SMA, gradual SMA) by Sharpe:")
print(sweep.head().spherical(4))
We outline a efficiency perform that calculates key metrics, together with CAGR, volatility, Sharpe ratio, Sortino ratio, most drawdown, win charge, and ultimate fairness. We examine the technique efficiency in opposition to a easy buy-and-hold baseline to see whether or not our sign logic provides worth. We additionally run a parameter sweep throughout totally different quick and gradual SMA mixtures and rank the outcomes by Sharpe ratio.
entries = df.index[(df["pos"].diff() == 1)]
exits = df.index[(df["pos"].diff() == -1)]
fig, (ax1, ax2, ax3) = plt.subplots(
3, 1, figsize=(13, 10), sharex=True,
gridspec_kw={"height_ratios": [3, 1, 2]},
)
ax1.plot(df.index, df["close"], lw=1.1, coloration="black", label="Close")
ax1.plot(df.index, df["SMA_20"], lw=0.9, label="SMA 20")
ax1.plot(df.index, df["SMA_50"], lw=0.9, label="SMA 50")
bbu, bbl = "BBU_20_2.0", "BBL_20_2.0"
if bbu in df and bbl in df:
ax1.fill_between(df.index, df[bbl], df[bbu], alpha=0.12, label="Bollinger 20,2")
ax1.scatter(entries, df.loc[entries, "close"], marker="^", s=70,
coloration="inexperienced", zorder=5, label="Entry")
ax1.scatter(exits, df.loc[exits, "close"], marker="v", s=70,
coloration="pink", zorder=5, label="Exit")
ax1.set_title(f"{TICKER} — worth, MAs, Bollinger, indicators")
ax1.legend(loc="higher left"); ax1.grid(alpha=0.3)
ax2.plot(df.index, df["RSI_14"], lw=0.9, label="RSI 14")
ax2.axhline(70, coloration="pink", ls="--", lw=0.6)
ax2.axhline(30, coloration="inexperienced", ls="--", lw=0.6)
ax2.set_title("RSI 14"); ax2.legend(loc="higher left"); ax2.grid(alpha=0.3)
ax3.plot(df.index, (1 + df["ret"]).cumprod(), lw=1.1, label="Buy & Hold")
ax3.plot(df.index, (1 + df["strat_ret"]).cumprod(), lw=1.1, label="Strategy")
ax3.set_title("Equity curves ($1 begin)")
ax3.legend(loc="higher left"); ax3.grid(alpha=0.3)
plt.tight_layout(); plt.present()
print("nTweak TICKER, the Strategy checklist, or the sweep grid to maintain exploring.")
We determine the technique’s entry and exit factors from adjustments within the place column. We then create a three-panel chart displaying worth motion with shifting averages and Bollinger Bands, RSI habits, and fairness curves for each buy-and-hold and the technique. We use these visuals to perceive the place trades occur, how momentum behaves, and how the technique performs over time.
In conclusion, we constructed an end-to-end technical evaluation pipeline that exhibits how pandas-ta-classic can help each fast indicator technology and extra superior technique growth. We used the library to compute particular person indicators and additionally to create customized methods, add multi-timeframe affirmation, cut back look-ahead bias, consider returns, and examine the technique in opposition to buy-and-hold efficiency. We additionally ran a easy parameter sweep to perceive how totally different moving-average mixtures have an effect on outcomes and to assist us determine stronger configurations. Also, we gained a basis for experimenting with technical indicators, buying and selling indicators, backtesting logic, efficiency analysis, and monetary knowledge visualization.
Check out the Codes with Notebook. Also, be happy to observe us on Twitter and don’t neglect to be a part of our 150k+ ML SubReddit and Subscribe to our Newsletter. Wait! are you on telegram? now you can join us on telegram as well.
Need to associate with us for selling your GitHub Repo OR Hugging Face Page OR Product Release OR Webinar and many others.? Connect with us
The publish How to Build Technical Analysis and Backtesting Workflow with pandas-ta-classic, Strategy Signals, and Performance Metrics appeared first on MarkTechPost.
