title: TS (Time Series) Mode Guide description: Time series vectorization for fast backtesting
TS (Time Series) Mode Guide¶
TS (Time Series) mode is a performance optimization feature that uses vectorized operations with pandas and NumPy to accelerate backtesting. This guide explains how to use TS mode effectively.
What is TS Mode?¶
TS mode enables vectorized backtestingby processing entire time series at once rather than bar-by-bar. This approach leverages:
pandas DataFrame/Series operationsfor efficient data manipulation
NumPy array operationsfor numerical calculations
Cython acceleration for performance-critical functions
How It Works¶
In standard backtrader mode, data flows bar-by-bar:
# Standard mode: bar-by-bar processing
for i in range(len(data)):
indicator.calculate(i)
strategy.next(i)
```bash
In TS mode, data is processed in vectorized batches:
```python
# TS mode: vectorized processing
indicator.once(0, len(data)) # Calculate all values at once
```bash
## Performance Benefits
| Operation | Standard Mode | TS Mode | Speedup |
|-----------|--------------|---------|---------|
| SMA(20) calculation | 1x | 10-20x | 10-20x faster |
| EMA(20) calculation | 1x | 15-25x | 15-25x faster |
| RSI calculation | 1x | 8-15x | 8-15x faster |
| Full backtest (100K bars) | Baseline | 3-5x | 3-5x faster |
- Actual performance depends on strategy complexity and data size*
## Enabling TS Mode
### Method 1: cerebro.run() Parameter
```python
import backtrader as bt
cerebro = bt.Cerebro()
# Add your strategy, data, indicators...
cerebro.adddata(data)
cerebro.addstrategy(MyStrategy)
# Enable TS mode
cerebro.run(ts_mode=True)
```bash
### Method 2: Environment Variable
```bash
# Set environment variable before running
export BACKTRADER_TS_MODE=1
python my_backtest.py
```bash
### Method 3: Configuration File
```python
# backtrader_config.py
ts_mode = {
'enabled': True,
'use_cython': True,
}
```bash
## When to Use TS Mode
### Ideal Use Cases
1. **Large datasets**: 100K+ bars
2. **Multiple indicators**: 5+ technical indicators
3. **Optimization runs**: Parameter sweeps
4. **Historical backtesting**: No live trading requirements
5. **Simple strategies**: Strategies without complex state management
### When NOT to Use TS Mode
1. **Live trading**: Requires real-time bar-by-bar processing
2. **Complex state**: Strategies with cross-period dependencies
3. **Custom indicators**: Indicators without vectorized `once()` methods
4. **Multiple data feeds**: Strategies with unsynchronized data feeds
5. **Tick data**: High-frequency data (use tick mode instead)
## Code Examples
### Example 1: Simple SMA Crossover
```python
import backtrader as bt
import pandas as pd
class SMACross(bt.Strategy):
params = (('fast', 10), ('slow', 30))
def __init__(self):
# These indicators support vectorized calculation
self.fast_sma = bt.indicators.SMA(self.data.close, period=self.p.fast)
self.slow_sma = bt.indicators.SMA(self.data.close, period=self.p.slow)
self.crossover = bt.indicators.CrossOver(self.fast_sma, self.slow_sma)
def next(self):
if not self.position:
if self.crossover[0] > 0:
self.buy()
elif self.crossover[0] < 0:
self.close()
# Load data
df = pd.read_csv('data.csv', parse_dates=['datetime'], index_col='datetime')
data = bt.feeds.PandasData(dataname=df)
# Create cerebro and run with TS mode
cerebro = bt.Cerebro()
cerebro.adddata(data)
cerebro.addstrategy(SMACross)
# Enable TS mode
result = cerebro.run(ts_mode=True)
```bash
### Example 2: Multi-Indicator Strategy
```python
import backtrader as bt
class MultiIndicator(bt.Strategy):
params = (
('rsi_period', 14),
('atr_period', 14),
('bb_period', 20),
)
def __init__(self):
# All these support vectorized once() methods
self.rsi = bt.indicators.RSI(self.data.close, period=self.p.rsi_period)
self.atr = bt.indicators.ATR(self.data, period=self.p.atr_period)
self.bollinger = bt.indicators.BollingerBands(
self.data.close, period=self.p.bb_period
)
# Custom calculation using built-in operations
self.signal = (
(self.rsi < 30) & # Oversold
(self.data.close < self.bollinger.lines.bot) # Below lower band
)
def next(self):
if self.signal[0] and not self.position:
size = cerebro.broker.getcash() * 0.95 / self.data.close[0]
self.buy(size=size)
cerebro = bt.Cerebro()
# ... add data ...
cerebro.addstrategy(MultiIndicator)
# TS mode provides significant speedup with multiple indicators
result = cerebro.run(ts_mode=True)
```bash
### Example 3: Custom Vectorized Indicator
```python
import backtrader as bt
import numpy as np
class VectorizedMomentum(bt.Indicator):
"""Custom momentum indicator with vectorized calculation"""
lines = ('momentum',)
params = (('period', 10),)
def __init__(self):
# Calculate in standard mode (bar-by-bar)
# TS mode will use once() if available
def next(self):
# Standard bar-by-bar calculation
self.lines.momentum[0] = (
self.data.close[0] - self.data.close[-self.p.period]
)
def once(self, start, end):
"""Vectorized calculation for TS mode"""
# Access underlying arrays for batch processing
src = self.data.close.array
dst = self.lines.momentum.array
for i in range(start, end):
if i >= self.p.period:
dst[i] = src[i] - src[i - self.p.period]
else:
dst[i] = float('nan')
# Use in strategy
class MomentumStrategy(bt.Strategy):
def __init__(self):
self.mom = VectorizedMomentum(self.data.close, period=20)
def next(self):
if self.mom[0] > 0 and not self.position:
self.buy()
elif self.mom[0] < 0:
self.close()
cerebro = bt.Cerebro()
# ... add data ...
cerebro.addstrategy(MomentumStrategy)
result = cerebro.run(ts_mode=True) # Uses vectorized once()
```bash
## Cython Acceleration
TS mode can use Cython-accelerated functions for additional performance:
### Compiling Cython Extensions
```bash
# Navigate to backtrader directory
cd backtrader
# Compile Cython files (Unix/Mac)
python -W ignore compile_cython_numba_files.py
# Compile Cython files (Windows)
python -W ignore compile_cython_numba_files.py
# Install with Cython extensions
cd ..
pip install -U .
```bash
### Verifying Cython is Available
```python
import backtrader as bt
# Check if Cython acceleration is available
print(f"Cython available: {bt.use_cython()}")
# Run with Cython enabled
cerebro = bt.Cerebro()
# ... setup ...
result = cerebro.run(ts_mode=True, use_cython=True)
```bash
## Performance Benchmarks
### Benchmark Configuration
| Parameter | Value |
|-----------|-------|
| Data points | 100,000 bars |
| Indicators | SMA(10), SMA(30), RSI(14), ATR(14) |
| Strategy | Simple crossover |
| Hardware | M1 Pro, 16GB RAM |
### Results
| Mode | Execution Time | Bars/Second |
|------|---------------|-------------|
| Standard | 12.5s | 8,000 |
| TS Mode (Python) | 4.2s | 23,800 |
| TS Mode (Cython) | 2.8s | 35,700 |
### Benchmarking Your Strategy
```python
import time
import backtrader as bt
# Standard mode
start = time.time()
result_standard = cerebro.run()
standard_time = time.time() - start
# TS mode
start = time.time()
result_ts = cerebro.run(ts_mode=True)
ts_time = time.time() - start
print(f"Standard mode: {standard_time:.2f}s")
print(f"TS mode: {ts_time:.2f}s")
print(f"Speedup: {standard_time/ts_time:.2f}x")
```bash
## Limitations and Considerations
### 1. Strategy Compatibility
Not all strategies work well with TS mode:
```python
# This works with TS mode
class GoodStrategy(bt.Strategy):
def __init__(self):
self.sma = bt.indicators.SMA(self.data.close, period=20)
def next(self):
if self.data.close[0] > self.sma[0]:
self.buy()
# This may not work with TS mode
class ProblematicStrategy(bt.Strategy):
def __init__(self):
self.counter = 0
def next(self):
# Complex state tracking
self.counter += 1
if self.counter > 5:
self.counter = 0
# Some action based on counter
```bash
### 2. Data Feed Requirements
TS mode requires:
- **Preloaded data**: Use `preload=True` (default)
- **Single timeframe**: No resampling filters in TS mode
- **Consistent data**: No gaps or missing bars
```python
# Correct for TS mode
data = bt.feeds.PandasData(
dataname=df,
preload=True, # Required for TS mode
)
# May not work with TS mode
data = bt.feeds.CSVGeneric(
dataname='data.csv',
preload=False # TS mode requires preloaded data
)
```bash
### 3. Indicator Requirements
For best performance in TS mode, indicators should implement `once()`:
```python
class MyIndicator(bt.Indicator):
lines = ('output',)
def next(self):
# Fallback for standard mode
self.lines.output[0] = self.data.close[0] *2
def once(self, start, end):
# Vectorized implementation for TS mode
for i in range(start, end):
self.lines.output.array[i] = self.data.close.array[i]* 2
```bash
### 4. Memory Usage
TS mode may use more memory:
```python
# For very large datasets, control memory
cerebro = bt.Cerebro()
# Use qbuffer to limit memory even in TS mode
data = bt.feeds.PandasData(dataname=df)
data.qbuffer(10000) # Keep only 10K bars in memory
cerebro.adddata(data)
```bash
## Advanced Configuration
### Fine-Tuning TS Mode
```python
cerebro.run(
ts_mode=True, # Enable TS mode
ts_batch_size=10000, # Process in batches (optional)
runonce=True, # Use once() methods
preload=True, # Preload all data
)
```bash
### Disabling Specific Optimizations
```python
# Disable specific TS features if needed
cerebro.run(
ts_mode=True,
ts_use_numpy=False, # Use pure Python instead of NumPy
ts_vectorize=False, # Disable vectorization
)
```bash
## Troubleshooting
### Issue: Strategy Results Differ
If results differ between standard and TS mode:
1. **Check indicator `once()` implementation**:
```python
# Ensure once() produces same results as next()
Verify data loading:
Ensure preload=True¶
1. **Check for state dependencies**:
```python
# TS mode may not preserve complex state
Issue: No Performance Improvement¶
Verify TS mode is enabled:
print(f"TS mode active: {cerebro.p.ts_mode}")
Check indicator compatibility:
Indicators must implement once() for speedup¶
print(hasattr(my_indicator, ‘once’))
1. **Use Cython extensions**:
```bash
python setup.py build_ext --inplace
Comparison: TS Mode vs CS Mode¶
| Feature | TS Mode | CS Mode |
|———|———|———|
| Purpose| Time series vectorization | Cross-section optimization |
|Use case| Single asset, long history | Multi-asset portfolio |
|Data structure| 2D (time x features) | 3D (time x assets x features) |
|Typical speedup| 3-5x | 2-3x |
|Memory usage| Moderate | Higher |
Best Practices¶
1.Always preload datafor TS mode:
data = bt.feeds.PandasData(dataname=df, preload=True)
2.Use built-in indicatorsthat support once():
# Good: built-in indicators with once()
sma = bt.indicators.SMA(self.data.close, period=20)
3.Profile before optimizing:
# Verify TS mode actually helps your specific strategy
Test thoroughly:
Verify TS mode produces same results as standard mode¶
1. **Use Cython for production**:
```bash
# Compile Cython extensions for maximum performance
Next Steps¶
CS Mode Guide - Cross-section optimization
Performance Optimization - General optimization techniques
Strategy API - Strategy development
Indicators Reference - Built-in indicators