Backtesting¶
Simulate strategy performance on historical data.
Overview¶
Backtesting in sigc:
- Loads historical data from your data section
- Computes signals at each time point
- Constructs portfolios based on weights
- Simulates trading with realistic costs
- Reports metrics for analysis
Quick Example¶
Text Only
data:
source = "prices.parquet"
format = parquet
signal momentum:
emit zscore(ret(prices, 60))
portfolio main:
weights = rank(momentum).long_short(top=0.2, bottom=0.2)
backtest from 2020-01-01 to 2024-12-31
Run the backtest:
Backtest Options¶
Date Range¶
Rebalancing Frequency¶
Text Only
portfolio main:
weights = ...
// Rebalance every 21 trading days (~monthly)
backtest rebal=21 from 2020-01-01 to 2024-12-31
Common frequencies:
| Setting | Frequency |
|---|---|
rebal=1 |
Daily |
rebal=5 |
Weekly |
rebal=21 |
Monthly |
rebal=63 |
Quarterly |
rebal=252 |
Annually |
Benchmark¶
Text Only
portfolio main:
weights = ...
backtest rebal=21 benchmark=SPY from 2020-01-01 to 2024-12-31
Transaction Costs¶
Text Only
portfolio main:
weights = ...
costs = tc.bps(5) # 5 basis points
backtest from 2020-01-01 to 2024-12-31
See Cost Models for details.
Backtest Output¶
Console Output¶
Text Only
Backtest Results: momentum_strategy
===================================
Period: 2020-01-01 to 2024-12-31
Rebalancing: 21 days
Benchmark: SPY
Performance Metrics:
Total Return: 85.2%
Annualized Return: 13.1%
Volatility: 15.2%
Sharpe Ratio: 0.86
Max Drawdown: -18.5%
Calmar Ratio: 0.71
vs Benchmark (SPY):
Alpha: 4.2%
Beta: 0.72
Information Ratio: 0.45
Tracking Error: 9.3%
Risk Metrics:
VaR (95%): -2.1%
CVaR (95%): -3.2%
Skewness: -0.15
Kurtosis: 3.8
Detailed Report¶
Generates HTML report with:
- Cumulative return chart
- Drawdown chart
- Monthly returns heatmap
- Position concentration over time
- Turnover analysis
- Factor exposures
Export Results¶
Bash
# CSV export
sigc run strategy.sig --output results.csv
# JSON export
sigc run strategy.sig --output results.json
# Parquet export
sigc run strategy.sig --output results.parquet
Backtest Process¶
Text Only
┌───────────────────────────────────────────────────────────┐
│ For each rebalance date: │
│ │
│ 1. Load data up to current date (no look-ahead) │
│ 2. Compute all signals │
│ 3. Calculate target weights │
│ 4. Apply constraints (if any) │
│ 5. Calculate trades needed │
│ 6. Apply transaction costs │
│ 7. Execute trades (update positions) │
│ 8. Mark-to-market portfolio │
│ 9. Record metrics │
│ │
└───────────────────────────────────────────────────────────┘
Key Concepts¶
Point-in-Time¶
sigc only uses data available at each historical date:
Text Only
signal no_lookahead:
// At 2020-01-15, only data up to 2020-01-15 is visible
emit zscore(ret(prices, 60))
Rebalancing¶
Trades only occur on rebalance dates:
Text Only
Rebal=21 (monthly):
Day 1: Compute weights → Trade
Day 2-20: Hold positions
Day 21: Compute weights → Trade
Day 22-41: Hold positions
...
Transaction Costs¶
Applied when positions change:
Text Only
Old Position: +5% AAPL
New Position: +3% AAPL
Trade: Sell 2% of portfolio
Cost: 2% × cost_rate
Multiple Portfolios¶
Test variations:
Text Only
signal momentum:
emit zscore(ret(prices, 60))
portfolio monthly:
weights = rank(momentum).long_short(top=0.2, bottom=0.2)
backtest rebal=21 from 2020-01-01 to 2024-12-31
portfolio weekly:
weights = rank(momentum).long_short(top=0.2, bottom=0.2)
backtest rebal=5 from 2020-01-01 to 2024-12-31
portfolio capped:
weights = rank(momentum).long_short(top=0.2, bottom=0.2, cap=0.03)
backtest rebal=21 from 2020-01-01 to 2024-12-31
Performance Metrics¶
Key metrics computed automatically:
| Metric | Description |
|---|---|
| Total Return | Cumulative return over period |
| CAGR | Compound annual growth rate |
| Volatility | Annualized standard deviation |
| Sharpe Ratio | Risk-adjusted return |
| Max Drawdown | Largest peak-to-trough decline |
| Calmar Ratio | CAGR / Max Drawdown |
See Metrics for complete list.
Best Practices¶
1. Use Realistic Costs¶
2. Match Rebalancing to Signal¶
Text Only
// Fast signal (mean reversion)
backtest rebal=5 ... // Weekly
// Slow signal (value, momentum)
backtest rebal=21 ... // Monthly
3. Test Multiple Periods¶
Text Only
// Out-of-sample testing
portfolio in_sample:
backtest from 2010-01-01 to 2019-12-31
portfolio out_of_sample:
backtest from 2020-01-01 to 2024-12-31
4. Include Benchmark¶
5. Check Turnover¶
High turnover erodes returns:
Common Pitfalls¶
Look-Ahead Bias¶
Using future information:
Text Only
// BAD: Using same-day close to trade at close
signal bad:
emit zscore(prices) // Can't use today's price to trade today
// GOOD: Use previous day
signal good:
emit zscore(lag(prices, 1))
Survivorship Bias¶
Only testing on current stocks:
Text Only
// Use point-in-time data that includes delisted stocks
data:
source = "pit_prices.parquet" // Point-in-time
Overfitting¶
Testing too many variations:
Text Only
Problem: Testing 1000 parameter combinations
Finding "best" parameters
Parameters don't work out-of-sample
Solution: Use walk-forward validation. See Walk-Forward.
Documentation Index¶
- Metrics - Performance and risk metrics
- Cost Models - Transaction cost modeling
- Walk-Forward - Out-of-sample testing
- Constraints - Position and risk constraints
- Benchmark Analysis - Benchmark comparison
- Attribution - Return attribution
Next Steps¶
- Metrics - Understanding performance metrics
- Cost Models - Modeling trading costs
- Walk-Forward - Proper validation