RaptorBT

High-performance backtesting engine written in Rust with Python bindings

PyPIGitHubMIT License

RaptorBT

Getting Started

RaptorBT is a high-performance backtesting engine written in Rust with Python bindings via PyO3, delivering HFT-grade compute efficiency with 30+ performance metrics.

  • Sub-millisecond backtesting on 1K bars (0.25ms)
  • <10 MB disk footprint
  • 100% deterministic results with no JIT compilation variance
  • 30+ performance metrics including Sharpe, Sortino, Calmar, Omega, SQN, and more
  • 6 strategy types: single, basket, pairs, options, spreads, and multi-strategy
  • Batch spread backtesting: Run multiple spread backtests in parallel via Rayon with GIL released
  • Monte Carlo simulation: Correlated multi-asset forward projection via GBM + Cholesky decomposition
  • 12 technical indicators: SMA, EMA, RSI, MACD, Stochastic, ATR, Bollinger Bands, ADX, VWAP, Supertrend, Rolling Min, Rolling Max
pip install raptorbt
import numpy as np
import raptorbt

config = raptorbt.PyBacktestConfig(initial_capital=100000, fees=0.001)

result = raptorbt.run_single_backtest(
    timestamps=timestamps, open=open, high=high, low=low, close=close,
    volume=volume, entries=entries, exits=exits,
    direction=1, weight=1.0, symbol="AAPL", config=config,
)

print(f"Return: {result.metrics.total_return_pct:.2f}%")
print(f"Sharpe: {result.metrics.sharpe_ratio:.2f}")

Installation

From Pre-built Wheel

pip install raptorbt

From Source

cd raptorbt
maturin develop --release

Verify Installation

import raptorbt
print("RaptorBT installed successfully!")

Quick Start

Basic Single Instrument Backtest

import numpy as np
import pandas as pd
import raptorbt

# Prepare data
df = pd.read_csv("your_data.csv", index_col=0, parse_dates=True)

# Generate signals (SMA crossover example)
sma_fast = df['close'].rolling(10).mean()
sma_slow = df['close'].rolling(20).mean()
entries = (sma_fast > sma_slow) & (sma_fast.shift(1) <= sma_slow.shift(1))
exits = (sma_fast < sma_slow) & (sma_fast.shift(1) >= sma_slow.shift(1))

# Configure backtest
config = raptorbt.PyBacktestConfig(
    initial_capital=100000,
    fees=0.001,        # 0.1% per trade
    slippage=0.0005,   # 0.05% slippage
    upon_bar_close=True
)

# Optional: Add stop-loss
config.set_fixed_stop(0.02)  # 2% stop-loss

# Optional: Add take-profit
config.set_fixed_target(0.04)  # 4% take-profit

# Run backtest
result = raptorbt.run_single_backtest(
    timestamps=df.index.astype('int64').values,
    open=df['open'].values,
    high=df['high'].values,
    low=df['low'].values,
    close=df['close'].values,
    volume=df['volume'].values,
    entries=entries.values,
    exits=exits.values,
    direction=1,       # 1 = Long, -1 = Short
    weight=1.0,
    symbol="AAPL",
    config=config,
)

# Access results
print(f"Total Return: {result.metrics.total_return_pct:.2f}%")
print(f"Sharpe Ratio: {result.metrics.sharpe_ratio:.2f}")
print(f"Max Drawdown: {result.metrics.max_drawdown_pct:.2f}%")
print(f"Win Rate: {result.metrics.win_rate_pct:.2f}%")
print(f"Total Trades: {result.metrics.total_trades}")

# Get equity curve
equity = result.equity_curve()  # Returns numpy array

# Get trades
trades = result.trades()  # Returns list of PyTrade objects

Performance

Benchmark Results

Tested on Apple Silicon M-series with random walk price data and SMA crossover strategy:

Data SizeRaptorBT
1,000 bars0.25 ms
5,000 bars0.24 ms
10,000 bars0.46 ms
50,000 bars1.68 ms

Metric Accuracy

RaptorBT produces 100% deterministic results with no JIT compilation variance between runs.


Architecture

raptorbt/
├── src/
│   ├── core/              # Core types and error handling
│   │   ├── types.rs       # BacktestConfig, BacktestResult, Trade, Metrics
│   │   ├── error.rs       # RaptorError enum
│   │   ├── session.rs     # SessionTracker, SessionConfig (intraday sessions)
│   │   └── timeseries.rs  # Time series utilities
│   │
│   ├── strategies/        # Strategy implementations
│   │   ├── single.rs      # Single instrument backtest
│   │   ├── basket.rs      # Basket/collective strategies
│   │   ├── pairs.rs       # Pairs trading
│   │   ├── options.rs     # Options strategies
│   │   ├── spreads.rs     # Multi-leg spread strategies (straddles, iron condors, etc.)
│   │   └── multi.rs       # Multi-strategy combining
│   │
│   ├── indicators/        # Technical indicators
│   │   ├── trend.rs       # SMA, EMA, Supertrend
│   │   ├── momentum.rs    # RSI, MACD, Stochastic
│   │   ├── volatility.rs  # ATR, Bollinger Bands
│   │   ├── strength.rs    # ADX
│   │   ├── volume.rs      # VWAP
│   │   └── rolling.rs     # Rolling Min/Max (LLV/HHV)
│   │
│   ├── portfolio/         # Portfolio-level analysis
│   │   ├── monte_carlo.rs # Monte Carlo forward simulation (GBM + Cholesky)
│   │   ├── allocation.rs  # Capital allocation
│   │   ├── engine.rs      # Portfolio engine
│   │   └── position.rs    # Position management
│   │
│   ├── metrics/           # Performance metrics
│   │   ├── streaming.rs   # Streaming metric calculations
│   │   ├── drawdown.rs    # Drawdown analysis
│   │   └── trade_stats.rs # Trade statistics
│   │
│   ├── signals/           # Signal processing
│   │   ├── processor.rs   # Entry/exit signal processing
│   │   ├── synchronizer.rs # Multi-instrument sync
│   │   └── expression.rs  # Signal expressions
│   │
│   ├── stops/             # Stop-loss implementations
│   │   ├── fixed.rs       # Fixed percentage stops
│   │   ├── atr.rs         # ATR-based stops
│   │   └── trailing.rs    # Trailing stops
│   │
│   ├── python/            # PyO3 bindings
│   │   ├── bindings.rs    # Python function exports
│   │   └── numpy_bridge.rs # NumPy array conversion
│   │
│   └── lib.rs             # Library entry point
│
├── Cargo.toml             # Rust dependencies
└── pyproject.toml         # Python package config

RaptorBT is built with native parallelism using Rayon for parallel processing and explicit SIMD optimizations for maximum throughput.


Strategy Types

Single Instrument

Basic long or short strategy on a single instrument.

result = raptorbt.run_single_backtest(
    timestamps=timestamps,
    open=open_prices, high=high_prices, low=low_prices,
    close=close_prices, volume=volume,
    entries=entries, exits=exits,
    direction=1,  # 1=Long, -1=Short
    weight=1.0,
    symbol="SYMBOL",
    config=config,
)

Basket/Collective

Trade multiple instruments with synchronized signals.

instruments = [
    (timestamps, open1, high1, low1, close1, volume1, entries1, exits1, 1, 0.33, "AAPL"),
    (timestamps, open2, high2, low2, close2, volume2, entries2, exits2, 1, 0.33, "GOOGL"),
    (timestamps, open3, high3, low3, close3, volume3, entries3, exits3, 1, 0.34, "MSFT"),
]

result = raptorbt.run_basket_backtest(
    instruments=instruments,
    config=config,
    sync_mode="all",  # "all", "any", "majority", "master"
)

Sync Modes:

  • all: Enter only when ALL instruments signal
  • any: Enter when ANY instrument signals
  • majority: Enter when >50% of instruments signal
  • master: Follow the first instrument's signals

Pairs Trading

Long one instrument, short another with optional hedge ratio.

result = raptorbt.run_pairs_backtest(
    # Long leg
    leg1_timestamps=timestamps,
    leg1_open=long_open, leg1_high=long_high,
    leg1_low=long_low, leg1_close=long_close,
    leg1_volume=long_volume,
    # Short leg
    leg2_timestamps=timestamps,
    leg2_open=short_open, leg2_high=short_high,
    leg2_low=short_low, leg2_close=short_close,
    leg2_volume=short_volume,
    # Signals
    entries=entries, exits=exits,
    direction=1,
    symbol="TCS_INFY",
    config=config,
    hedge_ratio=1.5,      # Short 1.5x the long position
    dynamic_hedge=False,  # Use rolling hedge ratio
)

Options

Backtest options strategies with strike selection.

result = raptorbt.run_options_backtest(
    timestamps=timestamps,
    open=underlying_open, high=underlying_high,
    low=underlying_low, close=underlying_close,
    volume=volume,
    option_prices=option_prices,  # Option premium series
    entries=entries, exits=exits,
    direction=1,
    symbol="NIFTY_CE",
    config=config,
    option_type="call",           # "call" or "put"
    strike_selection="atm",       # "atm", "otm1", "otm2", "itm1", "itm2"
    size_type="percent",          # "percent", "contracts", "notional", "risk"
    size_value=0.1,               # 10% of capital
    lot_size=50,                  # Options lot size
    strike_interval=50.0,         # Strike interval (e.g., 50 for NIFTY)
)

Spread Backtest

Backtest multi-leg options spread strategies with coordinated entry/exit.

import numpy as np
import raptorbt

config = raptorbt.PyBacktestConfig(initial_capital=100000, fees=0.001)

# Define leg configs: (option_type, strike, quantity, lot_size)
leg_configs = [
    ("CE", 24000.0, -1, 50),  # Short 1 lot Call at 24000
    ("PE", 24000.0, -1, 50),  # Short 1 lot Put at 24000
]

# Premium series for each leg (numpy arrays)
legs_premiums = [call_premiums, put_premiums]

result = raptorbt.run_spread_backtest(
    timestamps=timestamps,
    underlying_close=underlying_close,
    legs_premiums=legs_premiums,
    leg_configs=leg_configs,
    entries=entries,
    exits=exits,
    config=config,
    spread_type="straddle",    # "straddle", "strangle", "vertical_call", "vertical_put",
                               # "iron_condor", "iron_butterfly", "butterfly_call",
                               # "butterfly_put", "calendar", "diagonal", "custom"
    max_loss=5000.0,           # Optional: exit if net loss exceeds this
    target_profit=3000.0,      # Optional: exit if net profit exceeds this
)

print(f"Return: {result.metrics.total_return_pct:.2f}%")

Batch Spread Backtest

Run multiple spread backtests in parallel. Shared data (timestamps, underlying close) is converted once, then each item is backtested on its own Rayon thread with the GIL released for maximum throughput.

import numpy as np
import raptorbt

config = raptorbt.PyBacktestConfig(initial_capital=100000, fees=0.001)

# Create batch items — one per strategy variation
items = [
    raptorbt.PyBatchSpreadItem(
        strategy_id="straddle_24000",
        legs_premiums=[call_24000_premiums, put_24000_premiums],
        leg_configs=[("CE", 24000.0, -1, 50), ("PE", 24000.0, -1, 50)],
        entries=entries,
        exits=exits,
        spread_type="straddle",
        max_loss=5000.0,
        target_profit=3000.0,
    ),
    raptorbt.PyBatchSpreadItem(
        strategy_id="strangle_23500_24500",
        legs_premiums=[call_24500_premiums, put_23500_premiums],
        leg_configs=[("CE", 24500.0, -1, 50), ("PE", 23500.0, -1, 50)],
        entries=entries,
        exits=exits,
        spread_type="strangle",
    ),
]

# Run all in parallel — returns list of (strategy_id, result) tuples
results = raptorbt.batch_spread_backtest(
    timestamps=timestamps,
    underlying_close=underlying_close,
    items=items,
    config=config,
)

for strategy_id, result in results:
    print(f"{strategy_id}: {result.metrics.total_return_pct:.2f}%")

Supported Spread Types:

  • straddle: ATM call + put (same strike)
  • strangle: OTM call + put (different strikes)
  • vertical_call / vertical_put: Bull/bear vertical spreads
  • iron_condor: OTM put spread + OTM call spread
  • iron_butterfly: ATM straddle + OTM wings
  • butterfly_call / butterfly_put: Three-strike butterfly
  • calendar / diagonal: Different expiry spreads
  • long_call: Buy a single call option (limited risk to premium)
  • long_put: Buy a single put option (limited risk to premium)
  • naked_call: Sell a single call option without a hedge (unlimited risk)
  • naked_put: Sell a single put option without a hedge (high risk)
  • custom: Any arbitrary multi-leg combination

Multi-Strategy

Combine multiple strategies on the same instrument.

strategies = [
    (entries_sma, exits_sma, 1, 0.4, "SMA_Crossover"),    # 40% weight
    (entries_rsi, exits_rsi, 1, 0.35, "RSI_MeanRev"),     # 35% weight
    (entries_bb, exits_bb, 1, 0.25, "BB_Breakout"),       # 25% weight
]

result = raptorbt.run_multi_backtest(
    timestamps=timestamps,
    open=open_prices, high=high_prices,
    low=low_prices, close=close_prices,
    volume=volume,
    strategies=strategies,
    config=config,
    combine_mode="any",  # "any", "all", "majority", "weighted", "independent"
)

Combine Modes:

  • any: Enter when any strategy signals
  • all: Enter only when all strategies signal
  • majority: Enter when >50% of strategies signal
  • weighted: Weight signals by strategy weight
  • independent: Run strategies independently (aggregate PnL)

Metrics

RaptorBT calculates 30+ performance metrics:

Core Performance

MetricDescription
total_return_pctTotal return as percentage
sharpe_ratioRisk-adjusted return (annualized)
sortino_ratioDownside risk-adjusted return
calmar_ratioReturn / Max Drawdown
omega_ratioProbability-weighted gains/losses

Drawdown

MetricDescription
max_drawdown_pctMaximum peak-to-trough decline
max_drawdown_durationLongest drawdown period (bars)

Trade Statistics

MetricDescription
total_tradesTotal number of trades
total_closed_tradesNumber of closed trades
total_open_tradesNumber of open positions
winning_tradesNumber of profitable trades
losing_tradesNumber of losing trades
win_rate_pctPercentage of winning trades

Trade Performance

MetricDescription
profit_factorGross profit / Gross loss
expectancyAverage expected profit per trade
sqnSystem Quality Number
avg_trade_return_pctAverage trade return
avg_win_pctAverage winning trade return
avg_loss_pctAverage losing trade return
best_trade_pctBest single trade return
worst_trade_pctWorst single trade return

Duration

MetricDescription
avg_holding_periodAverage trade duration (bars)
avg_winning_durationAverage winning trade duration
avg_losing_durationAverage losing trade duration

Streaks

MetricDescription
max_consecutive_winsLongest winning streak
max_consecutive_lossesLongest losing streak

Other

MetricDescription
start_valueInitial portfolio value
end_valueFinal portfolio value
total_fees_paidTotal transaction costs
open_trade_pnlUnrealized PnL from open positions
exposure_pctPercentage of time in market

Technical Indicators

RaptorBT includes optimized technical indicators computed in native Rust:

import raptorbt

# Trend indicators
sma = raptorbt.sma(close, period=20)
ema = raptorbt.ema(close, period=20)
supertrend, direction = raptorbt.supertrend(high, low, close, period=10, multiplier=3.0)

# Momentum indicators
rsi = raptorbt.rsi(close, period=14)
macd_line, signal_line, histogram = raptorbt.macd(close, fast=12, slow=26, signal=9)
stoch_k, stoch_d = raptorbt.stochastic(high, low, close, k_period=14, d_period=3)

# Volatility indicators
atr = raptorbt.atr(high, low, close, period=14)
upper, middle, lower = raptorbt.bollinger_bands(close, period=20, std_dev=2.0)

# Strength indicators
adx = raptorbt.adx(high, low, close, period=14)

# Volume indicators
vwap = raptorbt.vwap(high, low, close, volume)

# Rolling indicators (LLV/HHV)
rolling_low = raptorbt.rolling_min(low, period=20)   # Lowest Low Value
rolling_high = raptorbt.rolling_max(high, period=20)  # Highest High Value

All indicators operate on NumPy arrays and return NumPy arrays, making them compatible with pandas DataFrames.


Stop-Loss & Take-Profit

Fixed Percentage

config = raptorbt.PyBacktestConfig(initial_capital=100000, fees=0.001)
config.set_fixed_stop(0.02)    # 2% stop-loss
config.set_fixed_target(0.04)  # 4% take-profit

ATR-Based

config.set_atr_stop(multiplier=2.0, period=14)    # 2x ATR stop
config.set_atr_target(multiplier=3.0, period=14)  # 3x ATR target

Trailing Stop

config.set_trailing_stop(0.02)  # 2% trailing stop

Risk-Reward Target

config.set_risk_reward_target(ratio=2.0)  # 2:1 risk-reward ratio

Monte Carlo Portfolio Simulation

RaptorBT includes a high-performance Monte Carlo forward simulation engine for portfolio risk analysis. It uses Geometric Brownian Motion (GBM) with Cholesky decomposition for correlated multi-asset simulation, parallelized via Rayon.

import numpy as np
import raptorbt

# Historical daily returns per strategy/asset (numpy arrays)
returns = [
    np.array([0.001, -0.002, 0.003, ...]),  # Strategy 1 returns
    np.array([0.002, 0.001, -0.001, ...]),   # Strategy 2 returns
]

# Portfolio weights (must sum to 1.0)
weights = np.array([0.6, 0.4])

# Correlation matrix (N x N)
correlation_matrix = [
    np.array([1.0, 0.3]),
    np.array([0.3, 1.0]),
]

# Run simulation
result = raptorbt.simulate_portfolio_mc(
    returns=returns,
    weights=weights,
    correlation_matrix=correlation_matrix,
    initial_value=100000.0,
    n_simulations=10000,   # Number of Monte Carlo paths (default: 10,000)
    horizon_days=252,      # Forward projection horizon (default: 252)
    seed=42,               # Random seed for reproducibility (default: 42)
)

# Results
print(f"Expected Return: {result['expected_return']:.2f}%")
print(f"Probability of Loss: {result['probability_of_loss']:.2%}")
print(f"VaR (95%): {result['var_95']:.2f}%")
print(f"CVaR (95%): {result['cvar_95']:.2f}%")

# Percentile paths: list of (percentile, path_values)
# Percentiles: 5th, 25th, 50th, 75th, 95th
for pct, path in result['percentile_paths']:
    print(f"  P{pct:.0f} final value: {path[-1]:.2f}")

# Final values: numpy array of terminal values for all simulations
final_values = result['final_values']  # numpy array, length = n_simulations

Result Fields

FieldTypeDescription
expected_returnfloatExpected return as percentage over the horizon
probability_of_lossfloatProbability that final value < initial value (0.0 to 1.0)
var_95floatValue at Risk at 95% confidence (percentage)
cvar_95floatConditional VaR at 95% confidence (percentage)
percentile_pathsList[Tuple[float, List]]Portfolio paths at 5th, 25th, 50th, 75th, 95th percentiles
final_valuesnumpy.ndarrayTerminal portfolio values for all simulations

API Reference

PyBacktestConfig

config = raptorbt.PyBacktestConfig(
    initial_capital: float = 100000.0,
    fees: float = 0.001,
    slippage: float = 0.0,
    upon_bar_close: bool = True,
)

# Stop methods
config.set_fixed_stop(percent: float)
config.set_atr_stop(multiplier: float, period: int)
config.set_trailing_stop(percent: float)

# Target methods
config.set_fixed_target(percent: float)
config.set_atr_target(multiplier: float, period: int)
config.set_risk_reward_target(ratio: float)

run_spread_backtest

result = raptorbt.run_spread_backtest(
    timestamps: np.ndarray,              # int64 nanosecond timestamps
    underlying_close: np.ndarray,        # Underlying close prices
    legs_premiums: List[np.ndarray],     # Premium series per leg
    leg_configs: List[Tuple[str, float, int, int]],  # (option_type, strike, quantity, lot_size)
    entries: np.ndarray,                 # bool entry signals
    exits: np.ndarray,                   # bool exit signals
    config: PyBacktestConfig = None,     # Optional config
    spread_type: str = "custom",         # Spread type string
    max_loss: float = None,              # Optional max loss exit
    target_profit: float = None,         # Optional target profit exit
    leg_expiry_timestamps: List[int] = None,  # Optional per-leg expiry timestamps (nanoseconds)
)

When leg_expiry_timestamps is provided, positions are force-closed at settlement when any leg expires. The exit reason is recorded as "Settlement". No re-entry is allowed after all legs have expired.

PyBatchSpreadItem

item = raptorbt.PyBatchSpreadItem(
    strategy_id: str,                    # Unique identifier for this backtest
    legs_premiums: List[np.ndarray],     # Premium series per leg
    leg_configs: List[Tuple[str, float, int, int]],  # (option_type, strike, quantity, lot_size)
    entries: np.ndarray,                 # bool entry signals
    exits: np.ndarray,                   # bool exit signals
    spread_type: str = "custom",         # Spread type string
    max_loss: float = None,              # Optional max loss exit
    target_profit: float = None,         # Optional target profit exit
)

batch_spread_backtest

results = raptorbt.batch_spread_backtest(
    timestamps: np.ndarray,              # int64 nanosecond timestamps (shared)
    underlying_close: np.ndarray,        # Underlying close prices (shared)
    items: List[PyBatchSpreadItem],      # List of spread backtest items
    config: PyBacktestConfig = None,     # Optional shared config
) -> List[Tuple[str, PyBacktestResult]]  # (strategy_id, result) pairs

Runs all spread backtests in parallel via Rayon. Timestamps and underlying close are shared across all items and converted once. The GIL is released during execution for maximum Python concurrency.

simulate_portfolio_mc

result = raptorbt.simulate_portfolio_mc(
    returns: List[np.ndarray],               # Per-asset daily returns (N arrays)
    weights: np.ndarray,                     # Portfolio weights (length N, sum to 1)
    correlation_matrix: List[np.ndarray],    # N x N correlation matrix
    initial_value: float,                    # Starting portfolio value
    n_simulations: int = 10000,              # Number of Monte Carlo paths
    horizon_days: int = 252,                 # Forward projection horizon in days
    seed: int = 42,                          # Random seed for reproducibility
) -> dict

Returns a dictionary with keys: expected_return, probability_of_loss, var_95, cvar_95, percentile_paths, final_values.

PyBacktestResult

result = raptorbt.run_single_backtest(...)

# Attributes
result.metrics        # PyBacktestMetrics object

# Methods
result.equity_curve()    # numpy.ndarray
result.drawdown_curve()  # numpy.ndarray
result.returns()         # numpy.ndarray
result.trades()          # List[PyTrade]

PyBacktestMetrics

metrics = result.metrics

# All available metrics
metrics.total_return_pct
metrics.sharpe_ratio
metrics.sortino_ratio
metrics.calmar_ratio
metrics.omega_ratio
metrics.max_drawdown_pct
metrics.max_drawdown_duration
metrics.win_rate_pct
metrics.profit_factor
metrics.expectancy
metrics.sqn
metrics.total_trades
metrics.total_closed_trades
metrics.total_open_trades
metrics.winning_trades
metrics.losing_trades
metrics.start_value
metrics.end_value
metrics.total_fees_paid
metrics.best_trade_pct
metrics.worst_trade_pct
metrics.avg_trade_return_pct
metrics.avg_win_pct
metrics.avg_loss_pct
metrics.avg_holding_period
metrics.avg_winning_duration
metrics.avg_losing_duration
metrics.max_consecutive_wins
metrics.max_consecutive_losses
metrics.exposure_pct
metrics.open_trade_pnl
metrics.payoff_ratio            # avg win / avg loss (risk/reward per trade)
metrics.recovery_factor         # net profit / max drawdown (resilience)

# Convert to dictionary
stats_dict = metrics.to_dict()

PyTrade

for trade in result.trades():
    print(trade.id)           # Trade ID
    print(trade.symbol)       # Symbol
    print(trade.entry_idx)    # Entry bar index
    print(trade.exit_idx)     # Exit bar index
    print(trade.entry_price)  # Entry price
    print(trade.exit_price)   # Exit price
    print(trade.size)         # Position size
    print(trade.direction)    # 1=Long, -1=Short
    print(trade.pnl)          # Profit/Loss
    print(trade.return_pct)   # Return percentage
    print(trade.fees)         # Fees paid
    print(trade.exit_reason)  # "Signal", "StopLoss", "TakeProfit", "TrailingStop", "EndOfData", "Settlement"

Examples

SMA Crossover with Stop-Loss

import numpy as np
import pandas as pd
import raptorbt

df = pd.read_csv("AAPL.csv", index_col=0, parse_dates=True)

# SMA crossover signals
sma_10 = df['close'].rolling(10).mean()
sma_30 = df['close'].rolling(30).mean()
entries = (sma_10 > sma_30) & (sma_10.shift(1) <= sma_30.shift(1))
exits = (sma_10 < sma_30) & (sma_10.shift(1) >= sma_30.shift(1))

config = raptorbt.PyBacktestConfig(initial_capital=100000, fees=0.001)
config.set_trailing_stop(0.03)  # 3% trailing stop

result = raptorbt.run_single_backtest(
    timestamps=df.index.astype('int64').values,
    open=df['open'].values, high=df['high'].values,
    low=df['low'].values, close=df['close'].values,
    volume=df['volume'].values,
    entries=entries.values, exits=exits.values,
    direction=1, weight=1.0, symbol="AAPL", config=config,
)

print(f"Total Return: {result.metrics.total_return_pct:.2f}%")
print(f"Sharpe Ratio: {result.metrics.sharpe_ratio:.2f}")
print(f"Max Drawdown: {result.metrics.max_drawdown_pct:.2f}%")
print(f"Win Rate: {result.metrics.win_rate_pct:.2f}%")

RSI Mean Reversion

import raptorbt
import numpy as np

# Compute RSI using built-in indicator
rsi = raptorbt.rsi(close_prices, period=14)

# Mean reversion: buy oversold, sell overbought
entries = rsi < 30
exits = rsi > 70

config = raptorbt.PyBacktestConfig(initial_capital=50000, fees=0.001)
config.set_fixed_stop(0.015)    # 1.5% stop
config.set_fixed_target(0.03)   # 3% target

result = raptorbt.run_single_backtest(
    timestamps=timestamps,
    open=open_prices, high=high_prices,
    low=low_prices, close=close_prices,
    volume=volume,
    entries=entries, exits=exits,
    direction=1, weight=1.0, symbol="SPY", config=config,
)

Multi-Strategy Portfolio

import raptorbt
import numpy as np

# Strategy 1: SMA crossover
sma_fast = raptorbt.sma(close, period=10)
sma_slow = raptorbt.sma(close, period=30)
entries_sma = sma_fast > sma_slow
exits_sma = sma_fast < sma_slow

# Strategy 2: RSI
rsi = raptorbt.rsi(close, period=14)
entries_rsi = rsi < 30
exits_rsi = rsi > 70

# Strategy 3: Bollinger Bands breakout
upper, middle, lower = raptorbt.bollinger_bands(close, period=20, std_dev=2.0)
entries_bb = close < lower
exits_bb = close > upper

strategies = [
    (entries_sma, exits_sma, 1, 0.4, "SMA"),
    (entries_rsi, exits_rsi, 1, 0.35, "RSI"),
    (entries_bb, exits_bb, 1, 0.25, "BB"),
]

config = raptorbt.PyBacktestConfig(initial_capital=100000, fees=0.001)

result = raptorbt.run_multi_backtest(
    timestamps=timestamps,
    open=open_prices, high=high_prices,
    low=low_prices, close=close_prices,
    volume=volume,
    strategies=strategies,
    config=config,
    combine_mode="weighted",
)

print(f"Combined Return: {result.metrics.total_return_pct:.2f}%")
print(f"Sharpe: {result.metrics.sharpe_ratio:.2f}")

Single-Leg Option (Long Call)

Backtest buying a single call option with expiry settlement.

import numpy as np
import raptorbt

config = raptorbt.PyBacktestConfig(initial_capital=100000, fees=0.001)

# Single leg: buy 1 lot of NIFTY 25000 CE
leg_configs = [("CE", 25000.0, 1, 50)]  # (option_type, strike, quantity=+1 long, lot_size)
legs_premiums = [call_premiums]           # Premium series for the call

# Expiry settlement: force-close at expiry with intrinsic value
# Timestamp in nanoseconds (e.g., 2026-03-06 15:30 IST)
import pandas as pd
expiry_ts = pd.Timestamp("2026-03-06 15:30", tz="Asia/Kolkata").value

result = raptorbt.run_spread_backtest(
    timestamps=timestamps,
    underlying_close=underlying_close,
    legs_premiums=legs_premiums,
    leg_configs=leg_configs,
    entries=entries,
    exits=exits,
    config=config,
    spread_type="long_call",
    leg_expiry_timestamps=[expiry_ts],  # Settlement at expiry
)

# Check if trade settled at expiry
for trade in result.trades():
    if trade.exit_reason == "Settlement":
        print(f"Trade settled at expiry: PnL={trade.pnl:.2f}")
    else:
        print(f"Trade exited via {trade.exit_reason}: PnL={trade.pnl:.2f}")

Naked Put with Settlement

Backtest selling a naked put — position settles at intrinsic value if held to expiry.

import raptorbt

config = raptorbt.PyBacktestConfig(initial_capital=200000, fees=0.001)

# Short 1 lot of NIFTY 24500 PE
leg_configs = [("PE", 24500.0, -1, 50)]  # quantity=-1 for short
legs_premiums = [put_premiums]

result = raptorbt.run_spread_backtest(
    timestamps=timestamps,
    underlying_close=underlying_close,
    legs_premiums=legs_premiums,
    leg_configs=leg_configs,
    entries=entries,
    exits=exits,
    config=config,
    spread_type="naked_put",
    max_loss=10000.0,                   # Stop out if loss exceeds 10K
    leg_expiry_timestamps=[expiry_ts],  # Settle at expiry if not stopped out
)

print(f"Return: {result.metrics.total_return_pct:.2f}%")
print(f"Trades: {result.metrics.total_trades}")

Building from Source

Prerequisites

  • Rust 1.70+ (install via rustup)
  • Python 3.10+
  • maturin (pip install maturin)

Development Build

cd raptorbt
maturin develop --release

Production Build

cd raptorbt
maturin build --release
pip install target/wheels/raptorbt-*.whl

Testing

Rust Unit Tests

cd raptorbt
cargo test

Python Integration Tests

import raptorbt
import numpy as np

config = raptorbt.PyBacktestConfig(initial_capital=100000, fees=0.001)
result = raptorbt.run_single_backtest(
    timestamps=np.arange(100, dtype=np.int64),
    open=np.random.randn(100).cumsum() + 100,
    high=np.random.randn(100).cumsum() + 101,
    low=np.random.randn(100).cumsum() + 99,
    close=np.random.randn(100).cumsum() + 100,
    volume=np.ones(100),
    entries=np.array([i % 20 == 0 for i in range(100)]),
    exits=np.array([i % 20 == 10 for i in range(100)]),
    direction=1,
    weight=1.0,
    symbol='TEST',
    config=config,
)
print(f'Total Return: {result.metrics.total_return_pct:.2f}%')
print('RaptorBT is working correctly!')

Contributing

RaptorBT is open source and contributions are welcome.

Setup

  1. Fork the repository on GitHub
  2. Clone your fork locally
  3. Install Rust 1.70+ via rustup and Python 3.10+
  4. Install maturin: pip install maturin
  5. Build in development mode: maturin develop --release

Development Workflow

  • Run Rust tests: cargo test
  • Run Python integration tests after building
  • Format Rust code: cargo fmt
  • Lint Rust code: cargo clippy

Pull Request Guidelines

  • Keep PRs focused on a single change
  • Add tests for new functionality
  • Ensure all existing tests pass
  • Follow the existing code style and conventions
  • Update documentation for any API changes

Reporting Issues

Found a bug or have a feature request? Open an issue on the GitHub repository.


License

MIT License. See LICENSE for details.


Changelog

v0.3.4

  • Add single-leg option spread types: LongCall, LongPut, NakedCall, NakedPut to SpreadType enum
  • Add ExitReason::Settlement for option expiry settlement exits
  • Add leg_expiry_timestamps parameter to run_spread_backtest for per-leg expiry tracking
  • Positions are force-closed at settlement when any leg expires, with premiums replaced by intrinsic value
  • Prevent re-entry after all legs have expired

v0.3.3

  • Add batch_spread_backtest function for running multiple spread backtests in parallel via Rayon
  • Add PyBatchSpreadItem class for defining individual items in a batch spread backtest
  • Shared data (timestamps, underlying close) is converted once and reused across all items
  • GIL released during parallel execution for maximum Python concurrency
  • Each item carries its own strategy_id, leg configs, signals, spread type, and optional max loss / target profit
  • Returns a list of (strategy_id, PyBacktestResult) tuples preserving result-to-input mapping

v0.3.2

  • Add payoff_ratio metric to BacktestMetrics — average winning trade return divided by average losing trade return (absolute), measures risk/reward per trade
  • Add recovery_factor metric to BacktestMetrics — net profit divided by maximum drawdown in absolute terms, measures how many times over the strategy recovered from its worst drawdown
  • Both metrics computed in StreamingMetrics::finalize() (single-instrument backtest) and PortfolioEngine (multi-strategy aggregation)
  • Both metrics exposed via PyO3 as #[pyo3(get)] attributes on PyBacktestMetrics
  • Handles edge cases: returns f64::INFINITY when denominator is zero with positive numerator, 0.0 otherwise

v0.3.1

  • Add Monte Carlo portfolio simulation (simulate_portfolio_mc) for forward risk projection
  • Geometric Brownian Motion (GBM) with Cholesky decomposition for correlated multi-asset simulation
  • Rayon-parallelized simulation paths with deterministic seeding (xoshiro256**)
  • Returns percentile paths (P5/P25/P50/P75/P95), VaR, CVaR, expected return, and probability of loss
  • GIL released during simulation for maximum Python concurrency

v0.3.0

  • Per-instrument configuration via PyInstrumentConfig (lot_size, alloted_capital, stop/target overrides)
  • Position sizes now correctly rounded to lot_size multiples
  • Support for per-instrument capital allocation in basket backtests
  • Automatic lot_size lookup from database via instr_repo.get_by_segment_symbol()
  • Future-ready fields: existing_qty, avg_price for live-to-backtest transitions

v0.2.2

  • Export run_spread_backtest Python binding for multi-leg options spread strategies
  • Export rolling_min and rolling_max indicator functions to Python

v0.2.1

  • Add rolling_min and rolling_max indicators for LLV (Lowest Low Value) and HHV (Highest High Value) support
  • NaN handling for warmup period

v0.2.0

  • Add multi-leg spread backtesting (run_spread_backtest) supporting straddles, strangles, vertical spreads, iron condors, iron butterflies, butterfly spreads, calendar spreads, and diagonal spreads
  • Coordinated entry/exit across all legs with net premium P&L calculation
  • Max loss and target profit exit thresholds for spreads
  • Add SessionTracker for intraday session management: market hours detection, squareoff time enforcement, session high/low/open tracking
  • Pre-built session configs for NSE equity (9:15-15:30), MCX commodity (9:00-23:30), and CDS currency (9:00-17:00)
  • Extend StreamingMetrics with equity/drawdown tracking, trade recording, and finalize() method

v0.1.0

  • Initial release
  • 5 strategy types: single, basket, pairs, options, multi
  • 30+ performance metrics
  • 10 technical indicators (SMA, EMA, RSI, MACD, Stochastic, ATR, Bollinger Bands, ADX, VWAP, Supertrend)
  • Stop-loss management: fixed, ATR-based, and trailing stops
  • Take-profit management: fixed, ATR-based, and risk-reward targets
  • PyO3 Python bindings for seamless Python integration