checkpoint: spread/commisions working

This commit is contained in:
Khizar Imran
2026-06-06 17:16:32 +01:00
parent 1dc728078f
commit bbfe87edb7
5 changed files with 100 additions and 12 deletions
+5 -3
View File
@@ -3,6 +3,8 @@ 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;
@@ -15,13 +17,13 @@ impl Strategy for BuyEveryBar {
fn main() { fn main() {
let data = load_csv("examples/data/eurusd_lse_1h.csv"); let data = load_csv("examples/data/eurusd_lse_1h.csv");
let mut engine = Engine::new(data, 10000.0); 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 engine.run(&mut strategy); // main line that runs the strategy
println!("Final cash: {}", engine.broker.cash); let stats = Stats::compute(&engine.broker, 10_000.0);
println!("Trades executed: {}", engine.broker.trade_history.len()); println!("{}", stats);
} }
+16 -7
View File
@@ -4,32 +4,41 @@ use crate::types::{Position, Trade};
pub struct Broker{ pub struct Broker{
pub cash: f64, pub cash: f64,
pub positions: Vec<Position>, pub positions: Vec<Position>,
pub trade_history: Vec<Trade> pub trade_history: Vec<Trade>,
pub commission: f64,
pub spread: f64
} }
impl Broker { impl Broker {
pub fn new(initial_cash: 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,
positions: Vec::new(), positions: Vec::new(),
trade_history: Vec::new() trade_history: Vec::new(),
commission,
spread
} }
} }
pub fn buy(&mut self, price: f64, lot_size: f64, timestamp: i64) { // needs to modify the broker with new position. (.push works with the Vec::) pub fn buy(&mut self, price: f64, lot_size: f64, timestamp: i64) { // needs to modify the broker with new position. (.push works with the Vec::)
let fill_price = price + self.spread; // buy at ask
self.cash -= self.commission * lot_size; // pay commission
self.positions.push(Position { self.positions.push(Position {
id: 0, id: self.positions.len() as u64,
entry_price: price, entry_price: fill_price,
lot_size, lot_size,
is_long: true, is_long: true,
entry_timestamp: timestamp, entry_timestamp: timestamp,
}); });
} }
pub fn sell(&mut self, price: f64, lot_size: f64, timestamp: i64) { // needs to modify the broker with new position. (.push works with the Vec::) pub fn sell(&mut self, price: f64, lot_size: f64, timestamp: i64) {
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 { self.positions.push(Position {
id : 0, id : 0,
entry_price: price, entry_price: fill_price,
lot_size, lot_size,
is_long: false, is_long: false,
entry_timestamp: timestamp entry_timestamp: timestamp
+2 -2
View File
@@ -8,10 +8,10 @@ pub struct Engine {
} }
impl Engine { impl Engine {
pub fn new(data: Vec<Bar>, initial_cash: 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) // 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
} }
} }
+1
View File
@@ -3,3 +3,4 @@ pub mod strategy;
pub mod broker; pub mod broker;
pub mod engine; pub mod engine;
pub mod data; pub mod data;
pub mod stats;
+76
View File
@@ -0,0 +1,76 @@
use crate::broker::Broker;
pub struct Stats {
pub initial_cash: f64,
pub final_cash: f64,
pub total_return_pct: f64,
pub num_trades: usize,
pub num_wins: usize,
pub win_rate_pct: f64,
pub avg_pnl: f64,
pub best_trade: f64,
pub worst_trade: f64,
pub profit_factor: f64,
}
impl Stats {
pub fn compute(broker: &Broker, initial_cash: f64) -> Self {
let num_trades = broker.trade_history.len();
let final_cash = broker.cash;
let total_return_pct = (final_cash - initial_cash) / initial_cash * 100.0;
let num_wins = broker.trade_history.iter().filter(|t| t.pnl > 0.0).count();
let win_rate_pct = if num_trades > 0 {
num_wins as f64 / num_trades as f64 * 100.0
} else { 0.0 };
let avg_pnl = if num_trades > 0 {
broker.trade_history.iter().map(|t| t.pnl).sum::<f64>() / num_trades as f64
} else { 0.0 };
let best_trade = broker.trade_history.iter().map(|t| t.pnl)
.fold(f64::NEG_INFINITY, f64::max);
let worst_trade = broker.trade_history.iter().map(|t| t.pnl)
.fold(f64::INFINITY, f64::min);
let gross_profit: f64 = broker.trade_history.iter()
.filter(|t| t.pnl > 0.0).map(|t| t.pnl).sum();
let gross_loss: f64 = broker.trade_history.iter()
.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 };
Stats {
initial_cash,
final_cash,
total_return_pct,
num_trades,
num_wins,
win_rate_pct,
avg_pnl,
best_trade: if num_trades > 0 { best_trade } else { 0.0 },
worst_trade: if num_trades > 0 { worst_trade } else { 0.0 },
profit_factor,
}
}
}
impl std::fmt::Display for Stats {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f,
"--- Backtest Results ---\n\
Initial Cash: {:.2}\n\
Final Cash: {:.2}\n\
Total Return: {:.2}%\n\
Trades: {}\n\
Win Rate: {:.1}%\n\
Avg PnL: {:.5}\n\
Best Trade: {:.5}\n\
Worst Trade: {:.5}\n\
Profit Factor: {:.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
)
}
}