Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 997e234a85 | |||
| e049a2f968 | |||
| f7592d5d79 | |||
| 87545683eb |
Generated
+1
-1
@@ -502,7 +502,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "raptorbt"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"criterion",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "raptorbt"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
edition = "2021"
|
||||
description = "High-performance Rust backtesting engine with Python bindings. Drop-in VectorBT replacement with up insanely faster performance at fractional memory footprint."
|
||||
authors = ["Alphabench <contact@alphabench.in>"]
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
[](https://www.rust-lang.org/)
|
||||
[](https://pepy.tech/projects/raptorbt)
|
||||
|
||||
<iframe src="https://clickhouse-analytics.metabaseapp.com/public/dashboard/daa27bf9-c01e-43fe-9260-c69b679cfe83?project_name=raptorbt#&theme=night" frameborder="0" width="100%" height="600"></iframe>
|
||||
|
||||
**Blazing-fast backtesting for the modern quant.**
|
||||
|
||||
RaptorBT is a high-performance backtesting engine written in Rust with Python bindings via PyO3. It serves as a drop-in replacement for VectorBT — delivering **HFT-grade compute efficiency** with full metric parity.
|
||||
@@ -82,6 +80,7 @@ RaptorBT was built to address the performance limitations of VectorBT. Benchmark
|
||||
### Key Features
|
||||
|
||||
- **6 Strategy Types**: Single instrument, basket/collective, pairs trading, 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
|
||||
- **33 Metrics**: Full parity with VectorBT including Sharpe, Sortino, Calmar, Omega, SQN, Payoff Ratio, Recovery Factor, and more
|
||||
- **12 Technical Indicators**: SMA, EMA, RSI, MACD, Stochastic, ATR, Bollinger Bands, ADX, VWAP, Supertrend, Rolling Min, Rolling Max
|
||||
@@ -406,6 +405,50 @@ result = raptorbt.run_multi_backtest(
|
||||
- `weighted`: Weight signals by strategy weight
|
||||
- `independent`: Run strategies independently (aggregate PnL)
|
||||
|
||||
### 6. 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.
|
||||
|
||||
```python
|
||||
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}%")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Metrics
|
||||
@@ -722,6 +765,34 @@ inst_config.set_fixed_target(0.05)
|
||||
- `alloted_capital` - Per-instrument capital cap (capped at available cash).
|
||||
- `existing_qty` / `avg_price` - Reserved for future live-to-backtest transitions.
|
||||
|
||||
### PyBatchSpreadItem
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
@@ -926,6 +997,15 @@ MIT License - see [LICENSE](LICENSE) for details.
|
||||
|
||||
## Changelog
|
||||
|
||||
### 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
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "raptorbt"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
description = "High-performance Rust backtesting engine with Python bindings. Drop-in VectorBT replacement with up insanely faster performance at fractional memory footprint."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
@@ -26,6 +26,9 @@ from raptorbt._raptorbt import (
|
||||
run_pairs_backtest,
|
||||
run_multi_backtest,
|
||||
run_spread_backtest,
|
||||
# Batch backtest
|
||||
PyBatchSpreadItem,
|
||||
batch_spread_backtest,
|
||||
# Monte Carlo simulation
|
||||
simulate_portfolio_mc,
|
||||
# Indicator functions
|
||||
@@ -43,7 +46,7 @@ from raptorbt._raptorbt import (
|
||||
rolling_max,
|
||||
)
|
||||
|
||||
__version__ = "0.3.2"
|
||||
__version__ = "0.3.3"
|
||||
|
||||
__all__ = [
|
||||
# Config classes
|
||||
@@ -62,6 +65,9 @@ __all__ = [
|
||||
"run_pairs_backtest",
|
||||
"run_multi_backtest",
|
||||
"run_spread_backtest",
|
||||
# Batch backtest
|
||||
"PyBatchSpreadItem",
|
||||
"batch_spread_backtest",
|
||||
# Monte Carlo simulation
|
||||
"simulate_portfolio_mc",
|
||||
# Indicator functions
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -44,6 +44,10 @@ fn _raptorbt(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(python::bindings::run_multi_backtest, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(python::bindings::run_spread_backtest, m)?)?;
|
||||
|
||||
// Register batch spread backtest
|
||||
m.add_class::<python::bindings::PyBatchSpreadItem>()?;
|
||||
m.add_function(wrap_pyfunction!(python::bindings::batch_spread_backtest, m)?)?;
|
||||
|
||||
// Register Monte Carlo simulation
|
||||
m.add_function(wrap_pyfunction!(python::bindings::simulate_portfolio_mc, m)?)?;
|
||||
|
||||
|
||||
@@ -842,6 +842,146 @@ pub fn run_spread_backtest<'py>(
|
||||
Ok(convert_result(result))
|
||||
}
|
||||
|
||||
/// A single spread backtest item for batch execution.
|
||||
#[pyclass]
|
||||
#[derive(Clone)]
|
||||
pub struct PyBatchSpreadItem {
|
||||
#[pyo3(get, set)]
|
||||
pub strategy_id: String,
|
||||
pub legs_premiums: Vec<Vec<f64>>,
|
||||
pub leg_configs: Vec<(String, f64, i32, usize)>,
|
||||
pub entries: Vec<bool>,
|
||||
pub exits: Vec<bool>,
|
||||
#[pyo3(get, set)]
|
||||
pub spread_type: String,
|
||||
#[pyo3(get, set)]
|
||||
pub max_loss: Option<f64>,
|
||||
#[pyo3(get, set)]
|
||||
pub target_profit: Option<f64>,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyBatchSpreadItem {
|
||||
#[new]
|
||||
#[pyo3(signature = (strategy_id, legs_premiums, leg_configs, entries, exits,
|
||||
spread_type="custom", max_loss=None, target_profit=None))]
|
||||
fn new(
|
||||
strategy_id: String,
|
||||
legs_premiums: Vec<PyReadonlyArray1<f64>>,
|
||||
leg_configs: Vec<(String, f64, i32, usize)>,
|
||||
entries: PyReadonlyArray1<bool>,
|
||||
exits: PyReadonlyArray1<bool>,
|
||||
spread_type: &str,
|
||||
max_loss: Option<f64>,
|
||||
target_profit: Option<f64>,
|
||||
) -> Self {
|
||||
Self {
|
||||
strategy_id,
|
||||
legs_premiums: legs_premiums.into_iter().map(numpy_to_vec_f64).collect(),
|
||||
leg_configs,
|
||||
entries: numpy_to_vec_bool(entries),
|
||||
exits: numpy_to_vec_bool(exits),
|
||||
spread_type: spread_type.to_string(),
|
||||
max_loss,
|
||||
target_profit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run multiple spread backtests in parallel via Rayon.
|
||||
///
|
||||
/// Shared data (timestamps, underlying_close) is converted once, then each
|
||||
/// item is backtested on its own Rayon thread with the GIL released.
|
||||
///
|
||||
/// Returns a Vec of (strategy_id, PyBacktestResult) tuples.
|
||||
#[pyfunction]
|
||||
#[pyo3(signature = (timestamps, underlying_close, items, config=None))]
|
||||
pub fn batch_spread_backtest(
|
||||
py: Python<'_>,
|
||||
timestamps: PyReadonlyArray1<i64>,
|
||||
underlying_close: PyReadonlyArray1<f64>,
|
||||
items: Vec<PyBatchSpreadItem>,
|
||||
config: Option<&PyBacktestConfig>,
|
||||
) -> PyResult<Vec<(String, PyBacktestResult)>> {
|
||||
use rayon::prelude::*;
|
||||
|
||||
// Convert shared data while holding GIL
|
||||
let ts = numpy_to_vec_i64(timestamps);
|
||||
let underlying = numpy_to_vec_f64(underlying_close);
|
||||
let base_config = config.map(|c| BacktestConfig::from(c)).unwrap_or_default();
|
||||
|
||||
// Prepare each item into a self-contained struct for parallel execution
|
||||
struct PreparedItem {
|
||||
strategy_id: String,
|
||||
premiums: Vec<Vec<f64>>,
|
||||
entries: Vec<bool>,
|
||||
exits: Vec<bool>,
|
||||
spread_config: SpreadConfig,
|
||||
}
|
||||
|
||||
let prepared: Vec<PreparedItem> = items
|
||||
.into_iter()
|
||||
.map(|item| {
|
||||
let rust_leg_configs: Vec<LegConfig> = item
|
||||
.leg_configs
|
||||
.into_iter()
|
||||
.map(|(opt_type, strike, quantity, lot_size)| {
|
||||
let option_type =
|
||||
SpreadOptionType::from_str(&opt_type).unwrap_or(SpreadOptionType::Call);
|
||||
LegConfig::new(option_type, strike, quantity, lot_size)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let spread_type_enum = match item.spread_type.to_lowercase().as_str() {
|
||||
"straddle" => SpreadType::Straddle,
|
||||
"strangle" => SpreadType::Strangle,
|
||||
"vertical_call" | "verticalcall" => SpreadType::VerticalCall,
|
||||
"vertical_put" | "verticalput" => SpreadType::VerticalPut,
|
||||
"iron_condor" | "ironcondor" => SpreadType::IronCondor,
|
||||
"iron_butterfly" | "ironbutterfly" => SpreadType::IronButterfly,
|
||||
"butterfly_call" | "butterflycall" => SpreadType::ButterflyCall,
|
||||
"butterfly_put" | "butterflyput" => SpreadType::ButterflyPut,
|
||||
"calendar" => SpreadType::Calendar,
|
||||
"diagonal" => SpreadType::Diagonal,
|
||||
_ => SpreadType::Custom,
|
||||
};
|
||||
|
||||
let spread_config = SpreadConfig {
|
||||
base: base_config.clone(),
|
||||
spread_type: spread_type_enum,
|
||||
leg_configs: rust_leg_configs.clone(),
|
||||
max_loss: item.max_loss,
|
||||
target_profit: item.target_profit,
|
||||
close_at_eod: false,
|
||||
};
|
||||
|
||||
PreparedItem {
|
||||
strategy_id: item.strategy_id,
|
||||
premiums: item.legs_premiums,
|
||||
entries: item.entries,
|
||||
exits: item.exits,
|
||||
spread_config,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Release GIL and run all backtests in parallel via Rayon
|
||||
let results: Vec<(String, crate::core::types::BacktestResult)> = py.allow_threads(|| {
|
||||
prepared
|
||||
.into_par_iter()
|
||||
.map(|item| {
|
||||
let backtest = SpreadBacktest::new(item.spread_config);
|
||||
let result =
|
||||
backtest.run(&ts, &underlying, &item.premiums, &item.entries, &item.exits);
|
||||
(item.strategy_id, result)
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
|
||||
// Re-acquire GIL and convert results to Python objects
|
||||
Ok(results.into_iter().map(|(id, result)| (id, convert_result(result))).collect())
|
||||
}
|
||||
|
||||
/// Run multi-strategy backtest.
|
||||
#[pyfunction]
|
||||
#[pyo3(signature = (timestamps, open, high, low, close, volume, strategies, config=None, combine_mode="any"))]
|
||||
|
||||
Reference in New Issue
Block a user