rustrade_backtest/lib.rs
1#![warn(missing_docs)]
2
3//! # rustrade-backtest
4//!
5//! Deterministic backtest engine for [`rustrade_core::Brain`]s. The same
6//! `Brain` trait used by `rustrade` for live trading drives the backtest —
7//! no special "backtest-mode" code paths in the strategy, no duplicate
8//! decision logic to keep in sync.
9//!
10//! See the crate-level [`README.md`](https://github.com/nuniesmith/rustrade/blob/main/crates/rustrade-backtest/README.md)
11//! for a quickstart.
12//!
13//! # Determinism
14//!
15//! Given the same `(Brain, Vec<Candle>, BacktestConfig)` inputs the engine
16//! produces the same [`BacktestResult`] every run. There is no random
17//! number source and no thread interleaving — the loop is single-threaded
18//! and synchronous through every step that affects state.
19//!
20//! # Risk-gate parity with live
21//!
22//! The live `ExecutionService` blocks decisions behind a per-symbol
23//! session-PnL halt and circuit breaker. Thread the same configs through
24//! [`BacktestConfig::session_pnl`] / [`BacktestConfig::circuit_breaker`]
25//! and the replay engine applies the same gates — driven by **candle
26//! time** (the daily halt rolls over at 00:00 UTC in replay time, the
27//! breaker's window and cooldown run on candle timestamps), so a brain
28//! that relies on live gating backtests the way it trades. Blocked
29//! decisions are counted in [`BacktestResult::orders_blocked`]. Both
30//! gates default to off.
31//!
32//! # Fill models
33//!
34//! [`BacktestConfig::fill_model`] (builder: `.fill_model(FillModel::…)`)
35//! selects how orders become fills. The default,
36//! [`FillModel::TakerAtClose`], keeps the engine's historical
37//! single-candle semantics — existing backtests are bit-for-bit
38//! unchanged. [`FillModel::Resting`] opts in to honest resting-order
39//! semantics: non-marketable limits rest across candles and fill when a
40//! later candle crosses their level (at the limit price, or at the
41//! candle's open when it gaps through), stops trigger on a cross and
42//! fill at the level or worse, standalone (stop-only / TP-only)
43//! protective orders are honoured, and same-candle ambiguity resolves
44//! conservatively. See [`FillModel`] for the full convention list.
45//!
46//! # Perp funding
47//!
48//! [`BacktestConfig::funding`] (builder: `.funding(FundingModel::…)`)
49//! books perp funding cashflows against open positions at settlement
50//! timestamps — a historical [`FundingModel::Series`] or a constant
51//! rate + interval fallback. Positive rate → longs pay, shorts receive;
52//! cashflows land in the equity curve, accrue onto each closed trade's
53//! [`TradeOutcome::funding`], and total up in
54//! [`BacktestResult::funding_received`] / [`BacktestResult::funding_paid`].
55//! Defaults to [`FundingModel::None`] — existing backtests are
56//! bit-for-bit unchanged.
57//!
58//! # Status
59//!
60//! Phase 4b — adds CSV candle loader, Sharpe/Sortino metrics, and
61//! multi-symbol replay. Parquet loaders and book-walk slippage remain
62//! deferred. See the workspace `TODO.md`.
63
64pub mod config;
65pub mod engine;
66pub mod error;
67pub mod fees;
68pub mod funding;
69pub mod loaders;
70pub mod metrics;
71pub mod result;
72pub mod slippage;
73
74pub use config::{BacktestConfig, BacktestConfigBuilder, FillModel};
75pub use engine::Backtest;
76pub use error::{Error, Result};
77pub use fees::FeeModel;
78pub use funding::{EIGHT_HOURS_MS, FundingModel};
79pub use loaders::{load_csv, load_csv_str, sort_chronological};
80pub use metrics::TradeOutcome;
81pub use result::{BacktestResult, EquityPoint};
82pub use slippage::SlippageModel;