Backtesting Insider Buy Signals in Python: A Step-by-Step Tutorial
A reproducible backtest of an insider cluster-buy strategy using pandas, vectorbt, and the NexusForm4 API. Includes look-ahead controls and turnover metrics.
A backtest is a claim about the past. To make it worth trusting, it needs three things: a clean signal timeline, honest execution assumptions, and rigorous look-ahead controls. This walkthrough builds all three on top of NexusForm4's cluster-buy feed.
1. Setup
pip install pandas numpy vectorbt yfinance requests
2. Pull the signal history
Pull the trailing signals and store them locally. The critical field is filed_date — this is when a market participant could first have seen the trade. Never enter based on txn_date, which is the trade date and is not observable until the filing arrives.
import pandas as pd, requests
HOST = "nexusform4-sec-insider-trading-c-suite-signals.p.rapidapi.com"
H = {"X-RapidAPI-Key": "...", "X-RapidAPI-Host": HOST}
data = requests.get(f"https://{HOST}/signals/cluster-buys", headers=H).json()
sig = pd.DataFrame(data)
sig["entry_date"] = pd.to_datetime(sig["filed_date"]) + pd.Timedelta(days=1)3. Pull the price panel
Get adjusted daily closes for every ticker in the signal universe. Adjust for splits and dividends or your return calculation will be wrong.
import yfinance as yf tickers = sig["ticker"].unique().tolist() px = yf.download(tickers, start="2023-01-01", auto_adjust=True)["Close"]
4. Build the strategy
Enter on next open after filing, hold 60 sessions, equal-weight across concurrent signals, max 20 concurrent positions.
entries = pd.DataFrame(False, index=px.index, columns=px.columns)
exits = entries.copy()
for _, row in sig.iterrows():
d = row["entry_date"]
t = row["ticker"]
if t in entries.columns and d in entries.index:
entries.at[d, t] = True
exit_idx = entries.index.get_loc(d) + 60
if exit_idx < len(entries.index):
exits.iat[exit_idx, entries.columns.get_loc(t)] = Trueimport vectorbt as vbt pf = vbt.Portfolio.from_signals(px, entries, exits, init_cash=100_000, fees=0.0005, freq="1D") print(pf.stats())
5. Metrics that matter
- Sharpe ratio — risk-adjusted return; anything above 0.8 for a single-signal strategy is respectable.
- Max drawdown — pair with recovery time; a 30% drawdown that recovers in 2 months differs from one that takes 2 years.
- Turnover — insider strategies naturally low-turnover, so double-check you are not accidentally triggering churn.
- Hit rate and win/loss asymmetry — cluster buys should skew toward asymmetric right-tail wins, not high hit rates.
6. Common ways to fool yourself
- Using txn_date instead of filed_date for entry — pure look-ahead.
- Backtesting with survivorship-biased price data — delisted tickers matter.
- Optimizing thresholds on the full history then reporting in-sample Sharpe.
- Ignoring borrow costs when you extend to a long-short book.