Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e91293e1a | ||
|
|
c9451069d8 |
Generated
+1
-1
@@ -502,7 +502,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "raptorbt"
|
name = "raptorbt"
|
||||||
version = "0.3.3"
|
version = "0.3.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"approx",
|
"approx",
|
||||||
"criterion",
|
"criterion",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "raptorbt"
|
name = "raptorbt"
|
||||||
version = "0.3.3"
|
version = "0.3.4"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "High-performance Rust backtesting engine with Python bindings. Drop-in VectorBT replacement with up insanely faster performance at fractional memory footprint."
|
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>"]
|
authors = ["Alphabench <contact@alphabench.in>"]
|
||||||
|
|||||||
@@ -883,7 +883,7 @@ for trade in result.trades():
|
|||||||
print(trade.pnl) # Profit/Loss
|
print(trade.pnl) # Profit/Loss
|
||||||
print(trade.return_pct) # Return percentage
|
print(trade.return_pct) # Return percentage
|
||||||
print(trade.fees) # Fees paid
|
print(trade.fees) # Fees paid
|
||||||
print(trade.exit_reason) # "Signal", "StopLoss", "TakeProfit"
|
print(trade.exit_reason) # "Signal", "StopLoss", "TakeProfit", "TrailingStop", "EndOfData", "Settlement"
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -997,6 +997,14 @@ MIT License - see [LICENSE](LICENSE) for details.
|
|||||||
|
|
||||||
## Changelog
|
## 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
|
### v0.3.3
|
||||||
|
|
||||||
- Add `batch_spread_backtest` function for running multiple spread backtests in parallel via Rayon
|
- Add `batch_spread_backtest` function for running multiple spread backtests in parallel via Rayon
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "raptorbt"
|
name = "raptorbt"
|
||||||
version = "0.3.3"
|
version = "0.3.4"
|
||||||
description = "High-performance Rust backtesting engine with Python bindings. Drop-in VectorBT replacement with up insanely faster performance at fractional memory footprint."
|
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"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ from raptorbt._raptorbt import (
|
|||||||
rolling_max,
|
rolling_max,
|
||||||
)
|
)
|
||||||
|
|
||||||
__version__ = "0.3.3"
|
__version__ = "0.3.4"
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
# Config classes
|
# Config classes
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -212,6 +212,8 @@ pub enum ExitReason {
|
|||||||
TrailingStop,
|
TrailingStop,
|
||||||
/// End of data.
|
/// End of data.
|
||||||
EndOfData,
|
EndOfData,
|
||||||
|
/// Option expiry settlement.
|
||||||
|
Settlement,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Backtest configuration.
|
/// Backtest configuration.
|
||||||
|
|||||||
+12
-1
@@ -782,7 +782,7 @@ pub fn run_pairs_backtest<'py>(
|
|||||||
|
|
||||||
/// Run spread backtest (multi-leg options).
|
/// Run spread backtest (multi-leg options).
|
||||||
#[pyfunction]
|
#[pyfunction]
|
||||||
#[pyo3(signature = (timestamps, underlying_close, legs_premiums, leg_configs, entries, exits, config=None, spread_type="custom", max_loss=None, target_profit=None))]
|
#[pyo3(signature = (timestamps, underlying_close, legs_premiums, leg_configs, entries, exits, config=None, spread_type="custom", max_loss=None, target_profit=None, leg_expiry_timestamps=None))]
|
||||||
pub fn run_spread_backtest<'py>(
|
pub fn run_spread_backtest<'py>(
|
||||||
_py: Python<'py>,
|
_py: Python<'py>,
|
||||||
timestamps: PyReadonlyArray1<i64>,
|
timestamps: PyReadonlyArray1<i64>,
|
||||||
@@ -795,6 +795,7 @@ pub fn run_spread_backtest<'py>(
|
|||||||
spread_type: &str,
|
spread_type: &str,
|
||||||
max_loss: Option<f64>,
|
max_loss: Option<f64>,
|
||||||
target_profit: Option<f64>,
|
target_profit: Option<f64>,
|
||||||
|
leg_expiry_timestamps: Option<Vec<i64>>,
|
||||||
) -> PyResult<PyBacktestResult> {
|
) -> PyResult<PyBacktestResult> {
|
||||||
let ts = numpy_to_vec_i64(timestamps);
|
let ts = numpy_to_vec_i64(timestamps);
|
||||||
let underlying = numpy_to_vec_f64(underlying_close);
|
let underlying = numpy_to_vec_f64(underlying_close);
|
||||||
@@ -824,6 +825,10 @@ pub fn run_spread_backtest<'py>(
|
|||||||
"butterfly_put" | "butterflyput" => SpreadType::ButterflyPut,
|
"butterfly_put" | "butterflyput" => SpreadType::ButterflyPut,
|
||||||
"calendar" => SpreadType::Calendar,
|
"calendar" => SpreadType::Calendar,
|
||||||
"diagonal" => SpreadType::Diagonal,
|
"diagonal" => SpreadType::Diagonal,
|
||||||
|
"long_call" | "longcall" => SpreadType::LongCall,
|
||||||
|
"long_put" | "longput" => SpreadType::LongPut,
|
||||||
|
"naked_call" | "nakedcall" => SpreadType::NakedCall,
|
||||||
|
"naked_put" | "nakedput" => SpreadType::NakedPut,
|
||||||
_ => SpreadType::Custom,
|
_ => SpreadType::Custom,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -834,6 +839,7 @@ pub fn run_spread_backtest<'py>(
|
|||||||
max_loss,
|
max_loss,
|
||||||
target_profit,
|
target_profit,
|
||||||
close_at_eod: false,
|
close_at_eod: false,
|
||||||
|
leg_expiry_timestamps,
|
||||||
};
|
};
|
||||||
|
|
||||||
let backtest = SpreadBacktest::new(spread_config);
|
let backtest = SpreadBacktest::new(spread_config);
|
||||||
@@ -943,6 +949,10 @@ pub fn batch_spread_backtest(
|
|||||||
"butterfly_put" | "butterflyput" => SpreadType::ButterflyPut,
|
"butterfly_put" | "butterflyput" => SpreadType::ButterflyPut,
|
||||||
"calendar" => SpreadType::Calendar,
|
"calendar" => SpreadType::Calendar,
|
||||||
"diagonal" => SpreadType::Diagonal,
|
"diagonal" => SpreadType::Diagonal,
|
||||||
|
"long_call" | "longcall" => SpreadType::LongCall,
|
||||||
|
"long_put" | "longput" => SpreadType::LongPut,
|
||||||
|
"naked_call" | "nakedcall" => SpreadType::NakedCall,
|
||||||
|
"naked_put" | "nakedput" => SpreadType::NakedPut,
|
||||||
_ => SpreadType::Custom,
|
_ => SpreadType::Custom,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -953,6 +963,7 @@ pub fn batch_spread_backtest(
|
|||||||
max_loss: item.max_loss,
|
max_loss: item.max_loss,
|
||||||
target_profit: item.target_profit,
|
target_profit: item.target_profit,
|
||||||
close_at_eod: false,
|
close_at_eod: false,
|
||||||
|
leg_expiry_timestamps: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
PreparedItem {
|
PreparedItem {
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ pub enum SpreadType {
|
|||||||
ButterflyPut,
|
ButterflyPut,
|
||||||
Calendar,
|
Calendar,
|
||||||
Diagonal,
|
Diagonal,
|
||||||
|
LongCall,
|
||||||
|
LongPut,
|
||||||
|
NakedCall,
|
||||||
|
NakedPut,
|
||||||
Custom,
|
Custom,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,6 +99,9 @@ pub struct SpreadConfig {
|
|||||||
pub target_profit: Option<f64>,
|
pub target_profit: Option<f64>,
|
||||||
/// Whether to close at end of day.
|
/// Whether to close at end of day.
|
||||||
pub close_at_eod: bool,
|
pub close_at_eod: bool,
|
||||||
|
/// Per-leg expiry timestamps in nanoseconds (optional, for settlement logic).
|
||||||
|
/// When provided, positions are force-closed at or after the earliest leg expiry.
|
||||||
|
pub leg_expiry_timestamps: Option<Vec<i64>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SpreadConfig {
|
impl Default for SpreadConfig {
|
||||||
@@ -106,6 +113,7 @@ impl Default for SpreadConfig {
|
|||||||
max_loss: None,
|
max_loss: None,
|
||||||
target_profit: None,
|
target_profit: None,
|
||||||
close_at_eod: false,
|
close_at_eod: false,
|
||||||
|
leg_expiry_timestamps: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,9 +259,16 @@ impl SpreadBacktest {
|
|||||||
// Calculate unrealized P&L for exit checks
|
// Calculate unrealized P&L for exit checks
|
||||||
let unrealized_pnl = position.as_ref().map(|p| p.total_unrealized_pnl()).unwrap_or(0.0);
|
let unrealized_pnl = position.as_ref().map(|p| p.total_unrealized_pnl()).unwrap_or(0.0);
|
||||||
|
|
||||||
|
// Check if any leg has expired at this bar
|
||||||
|
let is_expiry = position.is_some()
|
||||||
|
&& self.config.leg_expiry_timestamps.as_ref().map_or(false, |expiries| {
|
||||||
|
expiries.iter().any(|&exp_ts| timestamps[i] >= exp_ts)
|
||||||
|
});
|
||||||
|
|
||||||
// Check for exit signals or conditions
|
// Check for exit signals or conditions
|
||||||
let should_exit = position.is_some()
|
let should_exit = position.is_some()
|
||||||
&& (exits[i]
|
&& (exits[i]
|
||||||
|
|| is_expiry
|
||||||
|| self.check_max_loss(&position, unrealized_pnl)
|
|| self.check_max_loss(&position, unrealized_pnl)
|
||||||
|| self.check_target_profit(&position, unrealized_pnl));
|
|| self.check_target_profit(&position, unrealized_pnl));
|
||||||
|
|
||||||
@@ -267,7 +282,9 @@ impl SpreadBacktest {
|
|||||||
|
|
||||||
// Record trade
|
// Record trade
|
||||||
trade_id += 1;
|
trade_id += 1;
|
||||||
let exit_reason = if exits[i] {
|
let exit_reason = if is_expiry {
|
||||||
|
ExitReason::Settlement
|
||||||
|
} else if exits[i] {
|
||||||
ExitReason::Signal
|
ExitReason::Signal
|
||||||
} else if self.check_max_loss(&Some(pos.clone()), pnl) {
|
} else if self.check_max_loss(&Some(pos.clone()), pnl) {
|
||||||
ExitReason::StopLoss
|
ExitReason::StopLoss
|
||||||
@@ -311,8 +328,12 @@ impl SpreadBacktest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for entry signals
|
// Check for entry signals (don't re-enter after all legs expired)
|
||||||
if position.is_none() && entries[i] {
|
let all_expired =
|
||||||
|
self.config.leg_expiry_timestamps.as_ref().map_or(false, |expiries| {
|
||||||
|
expiries.iter().all(|&exp_ts| timestamps[i] >= exp_ts)
|
||||||
|
});
|
||||||
|
if position.is_none() && entries[i] && !all_expired {
|
||||||
let legs: Vec<LegPosition> = self
|
let legs: Vec<LegPosition> = self
|
||||||
.config
|
.config
|
||||||
.leg_configs
|
.leg_configs
|
||||||
|
|||||||
Reference in New Issue
Block a user