mirror of
https://github.com/KhizarImran/backtestingfx.git
synced 2026-08-15 21:18:04 +00:00
new: better stats
This commit is contained in:
@@ -3,7 +3,6 @@ use backtestingfx::broker::Broker;
|
|||||||
use backtestingfx::strategy::Strategy;
|
use backtestingfx::strategy::Strategy;
|
||||||
use backtestingfx::engine::Engine;
|
use backtestingfx::engine::Engine;
|
||||||
use backtestingfx::data::load_csv;
|
use backtestingfx::data::load_csv;
|
||||||
use backtestingfx::stats::Stats;
|
|
||||||
|
|
||||||
|
|
||||||
struct BuyEveryBar;
|
struct BuyEveryBar;
|
||||||
@@ -20,9 +19,7 @@ fn main() {
|
|||||||
let mut engine = Engine::new(data, 10_000.0, 0.0, 0.00010);
|
let mut engine = Engine::new(data, 10_000.0, 0.0, 0.00010);
|
||||||
let mut strategy = BuyEveryBar;
|
let mut strategy = BuyEveryBar;
|
||||||
|
|
||||||
engine.run(&mut strategy); // main line that runs the strategy
|
let stats = engine.run(&mut strategy);
|
||||||
|
|
||||||
let stats = Stats::compute(&engine.broker, 10_000.0);
|
|
||||||
println!("{}", stats);
|
println!("{}", stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-1
@@ -3,6 +3,7 @@ use crate::types::{Position, Trade};
|
|||||||
|
|
||||||
pub struct Broker{
|
pub struct Broker{
|
||||||
pub cash: f64,
|
pub cash: f64,
|
||||||
|
pub initial_cash: f64,
|
||||||
pub positions: Vec<Position>,
|
pub positions: Vec<Position>,
|
||||||
pub trade_history: Vec<Trade>,
|
pub trade_history: Vec<Trade>,
|
||||||
pub commission: f64,
|
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
|
pub fn new(initial_cash: f64, commission: f64, spread: f64) -> Self { // does not need &mut because it initialises something new
|
||||||
Broker {
|
Broker {
|
||||||
cash: initial_cash,
|
cash: initial_cash,
|
||||||
|
initial_cash,
|
||||||
positions: Vec::new(),
|
positions: Vec::new(),
|
||||||
trade_history: Vec::new(),
|
trade_history: Vec::new(),
|
||||||
commission,
|
commission,
|
||||||
@@ -37,7 +39,7 @@ impl Broker {
|
|||||||
let fill_price = price - self.spread; // buy at ask
|
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.cash -= self.commission * lot_size; // pay commission // needs to modify the broker with new position. (.push works with the Vec::)
|
||||||
self.positions.push(Position {
|
self.positions.push(Position {
|
||||||
id : 0,
|
id : self.positions.len() as u64,
|
||||||
entry_price: fill_price,
|
entry_price: fill_price,
|
||||||
lot_size,
|
lot_size,
|
||||||
is_long: false,
|
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
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-3
@@ -1,23 +1,29 @@
|
|||||||
use crate::types::Bar;
|
use crate::types::Bar;
|
||||||
use crate::broker::Broker;
|
use crate::broker::Broker;
|
||||||
use crate::strategy::Strategy;
|
use crate::strategy::Strategy;
|
||||||
|
use crate::stats::Stats;
|
||||||
|
|
||||||
pub struct Engine {
|
pub struct Engine {
|
||||||
pub data: Vec<Bar>,
|
pub data: Vec<Bar>,
|
||||||
pub broker: Broker
|
pub broker: Broker,
|
||||||
|
pub equity_curve: Vec<f64>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
pub fn new(data: Vec<Bar>, initial_cash: f64, commission: f64, spread: f64) -> Self {
|
pub fn new(data: Vec<Bar>, initial_cash: f64, commission: f64, spread: f64) -> Self {
|
||||||
Engine {
|
Engine {
|
||||||
data,
|
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 {
|
for bar in &self.data {
|
||||||
strategy.next(bar, &mut self.broker);
|
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 best_trade: f64,
|
||||||
pub worst_trade: f64,
|
pub worst_trade: f64,
|
||||||
pub profit_factor: 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 {
|
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 num_trades = broker.trade_history.len();
|
||||||
|
let initial_cash = broker.initial_cash;
|
||||||
let final_cash = broker.cash;
|
let final_cash = broker.cash;
|
||||||
let total_return_pct = (final_cash - initial_cash) / initial_cash * 100.0;
|
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();
|
.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 {
|
let profit_factor = if gross_loss > 0.0 { gross_profit / gross_loss } else {
|
||||||
f64::INFINITY };
|
f64::INFINITY };
|
||||||
|
let max_drawdown_pct = max_drawdown(equity_curve);
|
||||||
|
|
||||||
Stats {
|
Stats {
|
||||||
initial_cash,
|
initial_cash,
|
||||||
@@ -51,6 +73,7 @@ use crate::broker::Broker;
|
|||||||
best_trade: if num_trades > 0 { best_trade } else { 0.0 },
|
best_trade: if num_trades > 0 { best_trade } else { 0.0 },
|
||||||
worst_trade: if num_trades > 0 { worst_trade } else { 0.0 },
|
worst_trade: if num_trades > 0 { worst_trade } else { 0.0 },
|
||||||
profit_factor,
|
profit_factor,
|
||||||
|
max_drawdown_pct
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,10 +90,12 @@ use crate::broker::Broker;
|
|||||||
Avg PnL: {:.5}\n\
|
Avg PnL: {:.5}\n\
|
||||||
Best Trade: {:.5}\n\
|
Best Trade: {:.5}\n\
|
||||||
Worst 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.initial_cash, self.final_cash, self.total_return_pct,
|
||||||
self.num_trades, self.win_rate_pct, self.avg_pnl,
|
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;
|
use crate::broker::Broker;
|
||||||
|
|
||||||
pub trait Strategy {
|
pub trait Strategy {
|
||||||
|
fn init(&mut self, _data: &[Bar]) {}
|
||||||
fn next(&mut self, bar: &Bar, broker: &mut Broker);
|
fn next(&mut self, bar: &Bar, broker: &mut Broker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user