chore: remove all VectorBT references — raptorbt stands on its own
- README: remove VectorBT Comparison section and TOC entry, rewrite Overview/Performance as standalone benchmarks, clean metric-mapping table reference, update feature list to 7 strategy types including tick - Cargo.toml / pyproject.toml: rewrite description without VectorBT mention - __init__.py: rewrite module docstring without comparative framing - Rust comments (engine.rs, position.rs, signals/processor.rs, core/types.rs, python/bindings.rs): replace "matching VectorBT behavior/formula/methodology" with plain descriptions of what the code does Co-Authored-By: porcelaincode <contact@alphabench.in>
This commit is contained in:
co-authored by
porcelaincode
parent
3a9f7564ad
commit
fc0c756203
+1
-1
@@ -473,7 +473,7 @@ pub struct Position {
|
||||
pub highest_since_entry: Price,
|
||||
/// Lowest price since entry (for trailing stops).
|
||||
pub lowest_since_entry: Price,
|
||||
/// Entry fees (to include in trade PnL like VectorBT).
|
||||
/// Entry fees included in trade PnL.
|
||||
pub entry_fees: f64,
|
||||
}
|
||||
|
||||
|
||||
@@ -237,8 +237,8 @@ impl PortfolioEngine {
|
||||
.map(|cap| cap.min(cash))
|
||||
.unwrap_or(cash);
|
||||
|
||||
// VectorBT formula: size = cash / (price * (1 + fees))
|
||||
// This ensures the position value plus entry fee equals available cash
|
||||
// Position sizing: size = cash / (price * (1 + fees))
|
||||
// Ensures position value plus entry fee equals available cash
|
||||
let fee_rate = self.config.fees;
|
||||
let raw_size = if let Some(ref sizes) = signals.position_sizes {
|
||||
sizes[i] * available / (adjusted_price * (1.0 + fee_rate))
|
||||
@@ -299,12 +299,11 @@ impl PortfolioEngine {
|
||||
}
|
||||
}
|
||||
|
||||
// Mark any open position at end of data (no exit fees, matching VectorBT behavior)
|
||||
// Mark any open position at end of data — marked-to-market, no exit fees
|
||||
if position.is_in_position() {
|
||||
let last_idx = n - 1;
|
||||
let exit_price = ohlcv.close[last_idx];
|
||||
// No exit fees for EndOfData - position is marked-to-market but not actually closed
|
||||
// This matches VectorBT's behavior for "Open" trades
|
||||
// No exit fees for EndOfData: position is marked-to-market but not actually closed
|
||||
let exit_fees = 0.0;
|
||||
|
||||
if let Some(trade) = position.close_position(
|
||||
@@ -572,11 +571,9 @@ impl PortfolioEngine {
|
||||
};
|
||||
|
||||
// Risk-adjusted metrics (calculated from daily portfolio returns, not trade returns)
|
||||
// This matches VectorBT's calculation methodology
|
||||
let (sharpe_ratio, sortino_ratio, omega_ratio) = self.calculate_risk_metrics(returns);
|
||||
|
||||
// Calmar ratio: CAGR / max drawdown
|
||||
// VectorBT uses Compound Annual Growth Rate (CAGR)
|
||||
let num_periods = equity_curve.len().max(1) as f64;
|
||||
let years = num_periods / 365.25; // Convert to years using 365.25 days
|
||||
let total_return_frac = total_return_pct / 100.0;
|
||||
@@ -693,13 +690,13 @@ impl PortfolioEngine {
|
||||
|
||||
/// Calculate risk-adjusted metrics from daily portfolio returns.
|
||||
/// Returns (sharpe_ratio, sortino_ratio, omega_ratio).
|
||||
/// Uses 365 days for annualization to match VectorBT.
|
||||
/// Uses 365 calendar days for annualization.
|
||||
fn calculate_risk_metrics(&self, returns: &[f64]) -> (f64, f64, f64) {
|
||||
if returns.len() < 2 {
|
||||
return (0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
// VectorBT uses 365 days (calendar days) for annualization
|
||||
// 365 calendar days for annualization
|
||||
let periods_per_year: f64 = 365.0;
|
||||
let _n = returns.len() as f64;
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ impl PositionManager {
|
||||
let pos = &self.position;
|
||||
let multiplier = pos.direction.multiplier();
|
||||
|
||||
// Calculate P&L (matching VectorBT: gross - entry_fees - exit_fees)
|
||||
// Calculate P&L: gross - entry_fees - exit_fees
|
||||
let gross_pnl = (exit_price - pos.entry_price) * pos.size * multiplier;
|
||||
let total_fees = pos.entry_fees + exit_fees;
|
||||
let pnl = gross_pnl - total_fees;
|
||||
|
||||
@@ -435,7 +435,7 @@ impl PyBacktestMetrics {
|
||||
)
|
||||
}
|
||||
|
||||
/// Convert to dictionary matching VectorBT stats() format.
|
||||
/// Convert to dictionary of all metrics.
|
||||
fn to_dict(&self, py: Python) -> PyResult<PyObject> {
|
||||
let dict = pyo3::types::PyDict::new(py);
|
||||
dict.set_item("Start Value", self.start_value)?;
|
||||
|
||||
@@ -35,14 +35,13 @@ impl SignalProcessor {
|
||||
|
||||
/// Clean entry/exit signals to ensure proper alternation.
|
||||
///
|
||||
/// Rules (matching VectorBT behavior):
|
||||
/// Rules:
|
||||
/// 1. First signal must be an entry
|
||||
/// 2. After an entry, ignore further entries (unless pyramiding)
|
||||
/// 3. After an exit, ignore further exits
|
||||
/// 4. Entries and exits must alternate properly
|
||||
/// 5. Same-bar conflict: If both entry AND exit signals are True on the same bar
|
||||
/// when in position, VectorBT stays in position (ignores the exit).
|
||||
/// This matches VectorBT's "entry takes priority" behavior.
|
||||
/// when in position, entry takes priority — stay in position (ignore the exit).
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `entries` - Raw entry signals
|
||||
@@ -75,8 +74,7 @@ impl SignalProcessor {
|
||||
// Ignore exits when not in position
|
||||
} else {
|
||||
// In position - looking for exit (or pyramid entry)
|
||||
// VectorBT behavior: If both entry and exit are True, stay in position
|
||||
// (entry signal "cancels" the exit signal)
|
||||
// Same-bar conflict: entry takes priority — stay in position
|
||||
if exits[i] && !entries[i] {
|
||||
// Only exit if there's no conflicting entry signal
|
||||
clean_exits[i] = true;
|
||||
|
||||
Reference in New Issue
Block a user