mirror of
https://github.com/KhizarImran/backtestingfx.git
synced 2026-08-18 22:48:05 +00:00
checkpoint: spread/commisions working
This commit is contained in:
@@ -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
@@ -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
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user