diff --git a/src/core/types.rs b/src/core/types.rs index dab37b6..4fa1b49 100644 --- a/src/core/types.rs +++ b/src/core/types.rs @@ -104,6 +104,44 @@ impl OhlcvData { } } +/// Raw tick data series for tick-level backtesting. +/// +/// All fields are parallel arrays of length N (one entry per tick). +/// `buy_qty_delta` and `sell_qty_delta` must be per-tick deltas, not +/// cumulative session totals — callers are responsible for converting +/// Zerodha-style running sums before passing them here. +#[derive(Debug, Clone)] +pub struct TickData { + /// Nanoseconds-since-epoch timestamp for each tick. + pub timestamps: Vec, + /// Last traded price at each tick. + pub ltp: Vec, + /// Best bid price at each tick (0.0 if unavailable). + pub bid: Vec, + /// Best ask price at each tick (0.0 if unavailable). + pub ask: Vec, + /// Per-tick buy quantity delta (not cumulative). + pub buy_qty_delta: Vec, + /// Per-tick sell quantity delta (not cumulative). + pub sell_qty_delta: Vec, + /// Open interest at each tick (0 if unavailable). + pub oi: Vec, +} + +impl TickData { + /// Number of ticks. + #[inline] + pub fn len(&self) -> usize { + self.ltp.len() + } + + /// Whether the series is empty. + #[inline] + pub fn is_empty(&self) -> bool { + self.ltp.is_empty() + } +} + /// Compiled trading signals from strategy. #[derive(Debug, Clone)] pub struct CompiledSignals { @@ -214,6 +252,8 @@ pub enum ExitReason { EndOfData, /// Option expiry settlement. Settlement, + /// Max hold time exceeded (tick backtest). + TimeExit, } /// Backtest configuration. diff --git a/src/portfolio/engine.rs b/src/portfolio/engine.rs index 198ea3b..e3aff20 100644 --- a/src/portfolio/engine.rs +++ b/src/portfolio/engine.rs @@ -761,6 +761,25 @@ impl PortfolioEngine { } } +/// Compute `BacktestMetrics` from pre-built curves and trade list. +/// +/// Exposed as a standalone function so non-OHLCV strategies (e.g. tick backtest) +/// can produce identical metrics without duplicating the calculation logic. +pub fn compute_backtest_metrics( + equity_curve: &[f64], + drawdown_curve: &[f64], + returns: &[f64], + trades: &[Trade], + initial_capital: f64, +) -> BacktestMetrics { + // Delegate to a throwaway engine instance — avoids duplicating the logic. + let engine = PortfolioEngine::new(BacktestConfig { + initial_capital, + ..Default::default() + }); + engine.calculate_metrics(equity_curve, drawdown_curve, returns, trades, &StreamingMetrics::new()) +} + #[cfg(test)] mod tests { use super::*;