new: better stats

This commit is contained in:
Khizar Imran
2026-06-08 23:55:42 +01:00
parent bbfe87edb7
commit 26e625d6f8
5 changed files with 53 additions and 11 deletions
+1 -4
View File
@@ -3,7 +3,6 @@ use backtestingfx::broker::Broker;
use backtestingfx::strategy::Strategy;
use backtestingfx::engine::Engine;
use backtestingfx::data::load_csv;
use backtestingfx::stats::Stats;
struct BuyEveryBar;
@@ -20,9 +19,7 @@ fn main() {
let mut engine = Engine::new(data, 10_000.0, 0.0, 0.00010);
let mut strategy = BuyEveryBar;
engine.run(&mut strategy); // main line that runs the strategy
let stats = Stats::compute(&engine.broker, 10_000.0);
let stats = engine.run(&mut strategy);
println!("{}", stats);
}
+14 -1
View File
@@ -3,6 +3,7 @@ use crate::types::{Position, Trade};
pub struct Broker{
pub cash: f64,
pub initial_cash: f64,
pub positions: Vec<Position>,
pub trade_history: Vec<Trade>,
pub commission: f64,
@@ -13,6 +14,7 @@ impl Broker {
pub fn new(initial_cash: f64, commission: f64, spread: f64) -> Self { // does not need &mut because it initialises something new
Broker {
cash: initial_cash,
initial_cash,
positions: Vec::new(),
trade_history: Vec::new(),
commission,
@@ -37,7 +39,7 @@ impl Broker {
let fill_price = price - self.spread; // buy at ask
self.cash -= self.commission * lot_size; // pay commission // needs to modify the broker with new position. (.push works with the Vec::)
self.positions.push(Position {
id : 0,
id : self.positions.len() as u64,
entry_price: fill_price,
lot_size,
is_long: false,
@@ -88,5 +90,16 @@ impl Broker {
}
}
pub fn equity(&self, current_price: f64) -> f64 { // computes unrealised positions from the opened positions
let unrealized: f64 = self.positions.iter().map(|p| {
if p.is_long {
(current_price - p.entry_price) * p.lot_size
} else {
(p.entry_price - current_price) * p.lot_size
}
}).sum();
self.cash + unrealized
}
}
+10 -4
View File
@@ -1,23 +1,29 @@
use crate::types::Bar;
use crate::broker::Broker;
use crate::strategy::Strategy;
use crate::stats::Stats;
pub struct Engine {
pub data: Vec<Bar>,
pub broker: Broker
pub broker: Broker,
pub equity_curve: Vec<f64>
}
impl Engine {
pub fn new(data: Vec<Bar>, initial_cash: f64, commission: f64, spread: f64) -> Self {
Engine {
data,
broker : Broker::new(initial_cash, commission, spread) // it takes initial cash and not Broker as Engine is responsible for broker not the user
broker : Broker::new(initial_cash, commission, spread), // it takes initial cash and not Broker as Engine is responsible for broker not the user
equity_curve: Vec::new()
}
}
pub fn run (&mut self, strategy: &mut dyn Strategy) { //&mut dyn allows class inheritence
pub fn run (&mut self, strategy: &mut dyn Strategy) -> Stats {
strategy.init(&self.data);
for bar in &self.data {
strategy.next(bar, &mut self.broker);
}
self.equity_curve.push(self.broker.equity(bar.close));
}
Stats::compute(&self.broker, &self.equity_curve)
}
}
+28 -3
View File
@@ -11,11 +11,32 @@ use crate::broker::Broker;
pub best_trade: f64,
pub worst_trade: f64,
pub profit_factor: f64,
pub max_drawdown_pct: f64
}
fn max_drawdown(equity_curve: &[f64]) -> f64 {
let mut peak = f64::NEG_INFINITY;
let mut max_dd = 0.0f64;
for &equity in equity_curve {
if equity > peak {
peak = equity;
}
if peak > 0.0 {
let dd = (peak - equity) / peak * 100.0;
if dd > max_dd {
max_dd = dd;
}
}
}
max_dd
}
impl Stats {
pub fn compute(broker: &Broker, initial_cash: f64) -> Self {
pub fn compute(broker: &Broker, equity_curve: &[f64]) -> Self {
let num_trades = broker.trade_history.len();
let initial_cash = broker.initial_cash;
let final_cash = broker.cash;
let total_return_pct = (final_cash - initial_cash) / initial_cash * 100.0;
@@ -39,6 +60,7 @@ use crate::broker::Broker;
.filter(|t| t.pnl < 0.0).map(|t| t.pnl.abs()).sum();
let profit_factor = if gross_loss > 0.0 { gross_profit / gross_loss } else {
f64::INFINITY };
let max_drawdown_pct = max_drawdown(equity_curve);
Stats {
initial_cash,
@@ -51,6 +73,7 @@ use crate::broker::Broker;
best_trade: if num_trades > 0 { best_trade } else { 0.0 },
worst_trade: if num_trades > 0 { worst_trade } else { 0.0 },
profit_factor,
max_drawdown_pct
}
}
}
@@ -67,10 +90,12 @@ use crate::broker::Broker;
Avg PnL: {:.5}\n\
Best Trade: {:.5}\n\
Worst Trade: {:.5}\n\
Profit Factor: {:.2}",
Profit Factor: {:.2}\n\
Max Drawdown: {:.2}%",
self.initial_cash, self.final_cash, self.total_return_pct,
self.num_trades, self.win_rate_pct, self.avg_pnl,
self.best_trade, self.worst_trade, self.profit_factor
self.best_trade, self.worst_trade, self.profit_factor,
self.max_drawdown_pct
)
}
}
+1
View File
@@ -2,6 +2,7 @@ use crate::types::Bar;
use crate::broker::Broker;
pub trait Strategy {
fn init(&mut self, _data: &[Bar]) {}
fn next(&mut self, bar: &Bar, broker: &mut Broker);
}