4 Commits

Author SHA1 Message Date
vatsal f7592d5d79 Merge pull request #11 from alphabench/feat/refining-metrics-for-portfolio
docs: remove iframe from README, post1 release
2026-02-18 19:17:46 +05:30
porcelaincode 87545683eb docs: remove iframe from README, post1 release 2026-02-18 19:15:15 +05:30
vatsal eb5335e809 Merge pull request #10 from alphabench/feat/refining-metrics-for-portfolio
feat: add payoff_ratio and recovery_factor metrics, bump to 0.3.2
2026-02-18 18:58:08 +05:30
porcelaincode 0ff67e7fe2 feat: add payoff_ratio and recovery_factor metrics, bump to 0.3.2
Add two new risk/reward metrics to BacktestMetrics:
- payoff_ratio: avg winning return / avg losing return (absolute)
- recovery_factor: net profit / max drawdown in absolute terms
Computed in both StreamingMetrics::finalize() and PortfolioEngine.
Exposed via PyO3 with #[pyo3(get)] on PyBacktestMetrics.
Updated README with API reference and changelog.
2026-02-18 18:57:42 +05:30
9 changed files with 177 additions and 8 deletions
Generated
+1 -1
View File
@@ -502,7 +502,7 @@ dependencies = [
[[package]]
name = "raptorbt"
version = "0.3.1"
version = "0.3.2"
dependencies = [
"approx",
"criterion",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "raptorbt"
version = "0.3.1"
version = "0.3.2"
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>"]
+111 -4
View File
@@ -79,9 +79,10 @@ RaptorBT was built to address the performance limitations of VectorBT. Benchmark
### Key Features
- **5 Strategy Types**: Single instrument, basket/collective, pairs trading, options, and multi-strategy
- **30+ Metrics**: Full parity with VectorBT including Sharpe, Sortino, Calmar, Omega, SQN, and more
- **10 Technical Indicators**: SMA, EMA, RSI, MACD, Stochastic, ATR, Bollinger Bands, ADX, VWAP, Supertrend
- **6 Strategy Types**: Single instrument, basket/collective, pairs trading, options, spreads, and multi-strategy
- **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
- **Stop/Target Management**: Fixed, ATR-based, and trailing stops with risk-reward targets
- **100% Deterministic**: No JIT compilation variance between runs
- **Native Parallelism**: Rayon-based parallel processing with explicit SIMD optimizations
@@ -127,6 +128,7 @@ raptorbt/
│ ├── 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
@@ -134,6 +136,7 @@ raptorbt/
│ │ ├── basket.rs # Basket/collective strategies
│ │ ├── pairs.rs # Pairs trading
│ │ ├── options.rs # Options strategies
│ │ ├── spreads.rs # Multi-leg spread strategies
│ │ └── multi.rs # Multi-strategy combining
│ │
│ ├── indicators/ # Technical indicators
@@ -141,7 +144,8 @@ raptorbt/
│ │ ├── momentum.rs # RSI, MACD, Stochastic
│ │ ├── volatility.rs # ATR, Bollinger Bands
│ │ ├── strength.rs # ADX
│ │ ── volume.rs # VWAP
│ │ ── volume.rs # VWAP
│ │ └── rolling.rs # Rolling Min/Max (LLV/HHV)
│ │
│ ├── metrics/ # Performance metrics
│ │ ├── streaming.rs # Streaming metric calculations
@@ -158,6 +162,12 @@ raptorbt/
│ │ ├── atr.rs # ATR-based stops
│ │ └── trailing.rs # Trailing stops
│ │
│ ├── 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
│ │
│ ├── python/ # PyO3 bindings
│ │ ├── bindings.rs # Python function exports
│ │ └── numpy_bridge.rs # NumPy array conversion
@@ -529,6 +539,68 @@ 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.
```python
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
| Field | Type | Description |
| --------------------- | -------------------------- | ---------------------------------------------------------- |
| `expected_return` | `float` | Expected return as percentage over the horizon |
| `probability_of_loss` | `float` | Probability that final value < initial value (0.0 to 1.0) |
| `var_95` | `float` | Value at Risk at 95% confidence (percentage) |
| `cvar_95` | `float` | Conditional VaR at 95% confidence (percentage) |
| `percentile_paths` | `List[Tuple[float, List]]` | Portfolio paths at 5th, 25th, 50th, 75th, 95th percentiles |
| `final_values` | `numpy.ndarray` | Terminal portfolio values for all simulations |
---
## VectorBT Comparison
RaptorBT is designed as a drop-in replacement for VectorBT. Here's a side-by-side comparison:
@@ -643,10 +715,27 @@ inst_config.set_fixed_target(0.05)
```
**Fields:**
- `lot_size` - Minimum tradeable quantity. Position sizes are rounded down to nearest lot_size multiple. Use `1.0` for equities, `50.0` for NIFTY F&O, `0.01` for forex.
- `alloted_capital` - Per-instrument capital cap (capped at available cash).
- `existing_qty` / `avg_price` - Reserved for future live-to-backtest transitions.
### simulate_portfolio_mc
```python
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
```python
@@ -699,6 +788,8 @@ 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 (VectorBT format)
stats_dict = metrics.to_dict()
@@ -833,6 +924,22 @@ MIT License - see [LICENSE](LICENSE) for details.
## Changelog
### 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)
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "maturin"
[project]
name = "raptorbt"
version = "0.3.1"
version = "0.3.2.post1"
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"
+1 -1
View File
@@ -43,7 +43,7 @@ from raptorbt._raptorbt import (
rolling_max,
)
__version__ = "0.3.1"
__version__ = "0.3.2.post1"
__all__ = [
# Config classes
+4
View File
@@ -376,6 +376,10 @@ pub struct BacktestMetrics {
pub avg_holding_period: f64,
/// Exposure time percentage (time in market).
pub exposure_pct: f64,
/// Payoff ratio (avg win / avg loss).
pub payoff_ratio: f64,
/// Recovery factor (net profit / max drawdown).
pub recovery_factor: f64,
}
/// Complete backtest result.
+26
View File
@@ -513,6 +513,30 @@ impl StreamingMetrics {
let worst_trade_pct =
if self.worst_trade_pct == f64::INFINITY { 0.0 } else { self.worst_trade_pct };
// Payoff ratio: average win / average loss (absolute value)
let payoff_ratio = if avg_loss_pct.abs() > 0.0 {
avg_win_pct / avg_loss_pct.abs()
} else if avg_win_pct > 0.0 {
f64::INFINITY
} else {
0.0
};
// Recovery factor: net profit / max drawdown (absolute value)
let net_profit = final_value - initial_capital;
let recovery_factor = if self.max_drawdown_pct > 0.0 && initial_capital > 0.0 {
let max_dd_absolute = self.max_drawdown_pct / 100.0 * initial_capital;
if max_dd_absolute > 0.0 {
net_profit / max_dd_absolute
} else {
0.0
}
} else if net_profit > 0.0 {
f64::INFINITY
} else {
0.0
};
BacktestMetrics {
total_return_pct,
sharpe_ratio,
@@ -545,6 +569,8 @@ impl StreamingMetrics {
max_consecutive_losses: self.max_consecutive_losses,
avg_holding_period,
exposure_pct: 0.0, // TODO: calculate based on time in market
payoff_ratio,
recovery_factor,
}
}
+26
View File
@@ -591,6 +591,30 @@ impl PortfolioEngine {
0.0
};
// Payoff ratio: average win / average loss (absolute value)
let payoff_ratio = if avg_loss_pct.abs() > 0.0 {
avg_win_pct / avg_loss_pct.abs()
} else if avg_win_pct > 0.0 {
f64::INFINITY
} else {
0.0
};
// Recovery factor: net profit / max drawdown (absolute value)
let net_profit = end_value - start_value;
let recovery_factor = if max_drawdown_pct > 0.0 && start_value > 0.0 {
let max_dd_absolute = max_drawdown_pct / 100.0 * start_value;
if max_dd_absolute > 0.0 {
net_profit / max_dd_absolute
} else {
0.0
}
} else if net_profit > 0.0 {
f64::INFINITY
} else {
0.0
};
BacktestMetrics {
total_return_pct,
sharpe_ratio,
@@ -623,6 +647,8 @@ impl PortfolioEngine {
max_consecutive_losses,
avg_holding_period,
exposure_pct,
payoff_ratio,
recovery_factor,
}
}
+6
View File
@@ -419,6 +419,10 @@ pub struct PyBacktestMetrics {
pub avg_holding_period: f64,
#[pyo3(get)]
pub exposure_pct: f64,
#[pyo3(get)]
pub payoff_ratio: f64,
#[pyo3(get)]
pub recovery_factor: f64,
}
#[pymethods]
@@ -1203,6 +1207,8 @@ fn convert_result(result: crate::core::types::BacktestResult) -> PyBacktestResul
max_consecutive_losses: result.metrics.max_consecutive_losses,
avg_holding_period: result.metrics.avg_holding_period,
exposure_pct: result.metrics.exposure_pct,
payoff_ratio: result.metrics.payoff_ratio,
recovery_factor: result.metrics.recovery_factor,
};
let trades: Vec<PyTrade> = result