mirror of
https://github.com/KhizarImran/backtestingfx.git
synced 2026-07-27 20:17:44 +00:00
new: better stats
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user