feat(core): add TickData struct, TimeExit reason, compute_backtest_metrics pub fn
- TickData: parallel tick arrays (timestamps, ltp, bid, ask, buy_qty_delta, sell_qty_delta, oi) with len/is_empty helpers; callers must pre-convert Zerodha cumulative totals to per-tick deltas before passing - ExitReason::TimeExit: max hold time exceeded variant for tick backtest - compute_backtest_metrics: pub free fn wrapping PortfolioEngine::calculate_metrics so non-OHLCV strategies can produce identical metrics without duplication Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
7e91293e1a
commit
514c235f1c
@@ -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<Timestamp>,
|
||||
/// Last traded price at each tick.
|
||||
pub ltp: Vec<Price>,
|
||||
/// Best bid price at each tick (0.0 if unavailable).
|
||||
pub bid: Vec<Price>,
|
||||
/// Best ask price at each tick (0.0 if unavailable).
|
||||
pub ask: Vec<Price>,
|
||||
/// Per-tick buy quantity delta (not cumulative).
|
||||
pub buy_qty_delta: Vec<f64>,
|
||||
/// Per-tick sell quantity delta (not cumulative).
|
||||
pub sell_qty_delta: Vec<f64>,
|
||||
/// Open interest at each tick (0 if unavailable).
|
||||
pub oi: Vec<f64>,
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
Reference in New Issue
Block a user