Get the FREE Ultimate OpenClaw Setup Guide →

vectorbt-expert

Scanned
npx machina-cli add skill marketcalls/vectorbt-backtesting-skills/vectorbt-expert --openclaw
Files (1)
SKILL.md
9.9 KB

VectorBT Backtesting Expert Skill

Environment

  • Python with vectorbt, pandas, numpy, plotly
  • Data sources: OpenAlgo (Indian markets), yfinance (US/Global), CCXT (Crypto), custom providers
  • API keys loaded from single root .env via python-dotenv + find_dotenv() — never hardcode keys
  • Technical indicators: TA-Lib (ALWAYS - never use VectorBT built-in indicators)
  • Specialty indicators: openalgo.ta for Supertrend, Donchian, Ichimoku, HMA, KAMA, ALMA, ZLEMA, VWMA
  • Signal cleaning: openalgo.ta for exrem, crossover, crossunder, flip
  • Fee model: Indian market standard (STT + statutory charges + Rs 20/order)
  • Benchmark: NIFTY 50 via OpenAlgo (NSE_INDEX) by default
  • Charts: Plotly with template="plotly_dark"
  • Environment variables loaded from single .env at project root via find_dotenv() (walks up from script dir)
  • Scripts go in backtesting/{strategy_name}/ directories (created on-demand, not pre-created)
  • Never use icons/emojis in code or logger output

Critical Rules

  1. ALWAYS use TA-Lib for ALL technical indicators (EMA, SMA, RSI, MACD, BBANDS, ATR, ADX, STDDEV, MOM). NEVER use vbt.MA.run(), vbt.RSI.run(), or any VectorBT built-in indicator.
  2. Use OpenAlgo ta for indicators NOT in TA-Lib: Supertrend, Donchian, Ichimoku, HMA, KAMA, ALMA, ZLEMA, VWMA.
  3. Use OpenAlgo ta for signal utilities: ta.exrem(), ta.crossover(), ta.crossunder(), ta.flip().
  4. Always clean signals with ta.exrem() after generating raw buy/sell signals. Always .fillna(False) before exrem.
  5. Market-specific fees: India (indian-market-costs), US (us-market-costs), Crypto (crypto-market-costs). Auto-select based on user's market.
  6. Default benchmarks: India=NIFTY via OpenAlgo, US=S&P 500 (^GSPC), Crypto=Bitcoin (BTC-USD). See data-fetching Market Selection Guide.
  7. Always produce a Strategy vs Benchmark comparison table after every backtest.
  8. Always explain the backtest report in plain language so even normal traders understand risk and strength.
  9. Plotly candlestick charts must use xaxis type="category" to avoid weekend gaps.
  10. Whole shares: Always set min_size=1, size_granularity=1 for equities.

Modular Rule Files

Detailed reference for each topic is in rules/:

Rule FileTopic
data-fetchingOpenAlgo (India), yfinance (US), CCXT (Crypto), custom providers, .env setup
simulation-modesfrom_signals, from_orders, from_holding, direction types
position-sizingAmount/Value/Percent/TargetPercent sizing
indicators-signalsTA-Lib indicator reference, signal generation
openalgo-ta-helpersOpenAlgo ta: exrem, crossover, Supertrend, Donchian, Ichimoku, MAs
stop-loss-take-profitFixed SL, TP, trailing stop
parameter-optimizationBroadcasting and loop-based optimization
performance-analysisStats, metrics, benchmark comparison, CAGR
plottingCandlestick (category x-axis), VectorBT plots, custom Plotly
indian-market-costsIndian market fee model by segment
us-market-costsUS market fee model (stocks, options, futures)
crypto-market-costsCrypto fee model (spot, USDT-M, COIN-M futures)
futures-backtestingLot sizes (SEBI revised Dec 2025), value sizing
long-short-tradingSimultaneous long/short, direction comparison
csv-data-resamplingLoading CSV, resampling with Indian market alignment
walk-forwardWalk-forward analysis, WFE ratio
robustness-testingMonte Carlo, noise test, parameter sensitivity, delay test
pitfallsCommon mistakes and checklist before going live
strategy-catalogStrategy reference with code snippets
quantstats-tearsheetQuantStats HTML reports, metrics, plots, Monte Carlo

Strategy Templates (in rules/assets/)

Production-ready scripts with realistic fees, NIFTY benchmark, comparison table, and plain-language report:

TemplatePathDescription
EMA Crossoverassets/ema_crossover/backtest.pyEMA 10/20 crossover
RSIassets/rsi/backtest.pyRSI(14) oversold/overbought
Donchianassets/donchian/backtest.pyDonchian channel breakout
Supertrendassets/supertrend/backtest.pySupertrend with intraday sessions
MACDassets/macd/backtest.pyMACD signal-candle breakout
SDA2assets/sda2/backtest.pySDA2 trend following
Momentumassets/momentum/backtest.pyDouble momentum (MOM + MOM-of-MOM)
Dual Momentumassets/dual_momentum/backtest.pyQuarterly ETF rotation
Buy & Holdassets/buy_hold/backtest.pyStatic multi-asset allocation
RSI Accumulationassets/rsi_accumulation/backtest.pyWeekly RSI slab-wise accumulation
Walk-Forwardassets/walk_forward/template.pyWalk-forward analysis template
Realistic Costsassets/realistic_costs/template.pyTransaction cost impact comparison

Quick Template: Standard Backtest Script

import os
from datetime import datetime, timedelta
from pathlib import Path

import numpy as np
import pandas as pd
import talib as tl
import vectorbt as vbt
from dotenv import find_dotenv, load_dotenv
from openalgo import api, ta

# --- Config ---
script_dir = Path(__file__).resolve().parent
load_dotenv(find_dotenv(), override=False)

SYMBOL = "SBIN"
EXCHANGE = "NSE"
INTERVAL = "D"
INIT_CASH = 1_000_000
FEES = 0.00111              # Indian delivery equity (STT + statutory)
FIXED_FEES = 20             # Rs 20 per order
ALLOCATION = 0.75
BENCHMARK_SYMBOL = "NIFTY"
BENCHMARK_EXCHANGE = "NSE_INDEX"

# --- Fetch Data ---
client = api(
    api_key=os.getenv("OPENALGO_API_KEY"),
    host=os.getenv("OPENALGO_HOST", "http://127.0.0.1:5000"),
)

end_date = datetime.now().date()
start_date = end_date - timedelta(days=365 * 3)

df = client.history(
    symbol=SYMBOL, exchange=EXCHANGE, interval=INTERVAL,
    start_date=start_date.strftime("%Y-%m-%d"),
    end_date=end_date.strftime("%Y-%m-%d"),
)
if "timestamp" in df.columns:
    df["timestamp"] = pd.to_datetime(df["timestamp"])
    df = df.set_index("timestamp")
else:
    df.index = pd.to_datetime(df.index)
df = df.sort_index()
if df.index.tz is not None:
    df.index = df.index.tz_convert(None)

close = df["close"]

# --- Strategy: EMA Crossover (TA-Lib) ---
ema_fast = pd.Series(tl.EMA(close.values, timeperiod=10), index=close.index)
ema_slow = pd.Series(tl.EMA(close.values, timeperiod=20), index=close.index)

buy_raw = (ema_fast > ema_slow) & (ema_fast.shift(1) <= ema_slow.shift(1))
sell_raw = (ema_fast < ema_slow) & (ema_fast.shift(1) >= ema_slow.shift(1))

entries = ta.exrem(buy_raw.fillna(False), sell_raw.fillna(False))
exits = ta.exrem(sell_raw.fillna(False), buy_raw.fillna(False))

# --- Backtest ---
pf = vbt.Portfolio.from_signals(
    close, entries, exits,
    init_cash=INIT_CASH, size=ALLOCATION, size_type="percent",
    fees=FEES, fixed_fees=FIXED_FEES, direction="longonly",
    min_size=1, size_granularity=1, freq="1D",
)

# --- Benchmark ---
df_bench = client.history(
    symbol=BENCHMARK_SYMBOL, exchange=BENCHMARK_EXCHANGE, interval=INTERVAL,
    start_date=start_date.strftime("%Y-%m-%d"),
    end_date=end_date.strftime("%Y-%m-%d"),
)
if "timestamp" in df_bench.columns:
    df_bench["timestamp"] = pd.to_datetime(df_bench["timestamp"])
    df_bench = df_bench.set_index("timestamp")
else:
    df_bench.index = pd.to_datetime(df_bench.index)
df_bench = df_bench.sort_index()
if df_bench.index.tz is not None:
    df_bench.index = df_bench.index.tz_convert(None)
bench_close = df_bench["close"].reindex(close.index).ffill().bfill()
pf_bench = vbt.Portfolio.from_holding(bench_close, init_cash=INIT_CASH, fees=FEES, freq="1D")

# --- Results ---
print(pf.stats())

# --- Strategy vs Benchmark ---
comparison = pd.DataFrame({
    "Strategy": [
        f"{pf.total_return() * 100:.2f}%", f"{pf.sharpe_ratio():.2f}",
        f"{pf.sortino_ratio():.2f}", f"{pf.max_drawdown() * 100:.2f}%",
        f"{pf.trades.win_rate() * 100:.1f}%", f"{pf.trades.count()}",
        f"{pf.trades.profit_factor():.2f}",
    ],
    f"Benchmark ({BENCHMARK_SYMBOL})": [
        f"{pf_bench.total_return() * 100:.2f}%", f"{pf_bench.sharpe_ratio():.2f}",
        f"{pf_bench.sortino_ratio():.2f}", f"{pf_bench.max_drawdown() * 100:.2f}%",
        "-", "-", "-",
    ],
}, index=["Total Return", "Sharpe Ratio", "Sortino Ratio", "Max Drawdown",
          "Win Rate", "Total Trades", "Profit Factor"])
print(comparison.to_string())

# --- Explain ---
print(f"* Total Return: {pf.total_return() * 100:.2f}% vs NIFTY {pf_bench.total_return() * 100:.2f}%")
print(f"* Max Drawdown: {pf.max_drawdown() * 100:.2f}%")
print(f"  -> On Rs {INIT_CASH:,}, worst temporary loss = Rs {abs(pf.max_drawdown()) * INIT_CASH:,.0f}")

# --- Plot ---
fig = pf.plot(subplots=['value', 'underwater', 'cum_returns'], template="plotly_dark")
fig.show()

# --- Export ---
pf.positions.records_readable.to_csv(script_dir / f"{SYMBOL}_trades.csv", index=False)

Source

git clone https://github.com/marketcalls/vectorbt-backtesting-skills/blob/master/.claude/skills/vectorbt-expert/SKILL.mdView on GitHub

Overview

This skill acts as a VectorBT backtesting expert to build, backtest, and analyze trading strategies using TA-Lib indicators and OpenAlgo ta helpers. It supports creating entry/exit signals, optimizing parameters, fetching historical data, comparing strategies, handling position sizing, and producing equity curves and drawdown charts. It also generates a Strategy vs Benchmark report and organizes outputs under backtesting/{strategy_name}/.

How This Skill Works

Core indicators are computed with TA-Lib, while non-TA-Lib indicators and helpers (e.g., Supertrend, Donchian, Ichimoku) come from OpenAlgo ta. Signals are created, cleaned with ta.exrem, and filled before running backtests in vectorbt, applying market-specific fees and a default benchmark. Results are presented as Plotly charts (candles with category axis) and a Strategy vs Benchmark table, with a plain-language risk summary.

When to Use It

  • Backtest a new or existing strategy across Indian, US, or crypto markets.
  • Generate entry/exit signals using TA-Lib indicators or OpenAlgo ta helpers (exrem, crossover, crossunder, flip).
  • Analyze portfolio performance, including equity curves and drawdown charts, for decision support.
  • Optimize parameters and compare multiple strategies to find robust settings.
  • Fetch historical data from OpenAlgo, yfinance, CCXT or custom providers and produce trade analysis with market-specific costs.

Quick Start

  1. Step 1: Install dependencies (vectorbt, TA-Lib, OpenAlgo ta) and load API keys from a central .env.
  2. Step 2: Define indicators with TA-Lib and OpenAlgo ta, generate raw signals, then clean with ta.exrem.
  3. Step 3: Run the backtest under backtesting/{strategy_name}/, review the Strategy vs Benchmark table, and inspect Plotly charts.

Best Practices

  • Use TA-Lib for core indicators (EMA, SMA, RSI, MACD, BBANDS, ATR, ADX, STDDEV, MOM) and OpenAlgo ta for non-TA-Lib indicators.
  • Rely on ta.exrem to clean signals and always fillna(False) before exrem.
  • Always generate a Strategy vs Benchmark table after every backtest and explain results in plain language.
  • Auto-select and apply market costs (India US Crypto) based on the active market and set min_size=1, size_granularity=1 for equities.
  • Plot candlestick charts with Plotly using xaxis type='category' to avoid weekend gaps.

Example Use Cases

  • Backtest a 50/200 EMA crossover strategy on NIFTY 50 using OpenAlgo ta signals and compare to the benchmark.
  • Optimize MACD and RSI thresholds for a US large-cap strategy and review parameter sensitivity in the report.
  • Compare two momentum strategies on the S&P 500 with yfinance data and generate equity curves and drawdowns.
  • Crypto backtest for BTC-USD using Donchian and Supertrend from OpenAlgo ta, evaluating performance and risk.
  • Fetch multi-source historical data and produce a Strategy vs Benchmark summary for a given strategy across markets.

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers