From cb9bf41ff3e625698468f9e7d19df39ed954a199 Mon Sep 17 00:00:00 2001 From: KhizarImran Date: Sun, 21 Jun 2026 00:18:44 +0100 Subject: [PATCH] added lot size --- python/backtest.py | 22 +++++++- src/broker.rs | 127 +++++++++++++++++++++++++++++++++------------ src/engine.rs | 19 ++++++- 3 files changed, 130 insertions(+), 38 deletions(-) diff --git a/python/backtest.py b/python/backtest.py index 0e311ec..ce71e28 100644 --- a/python/backtest.py +++ b/python/backtest.py @@ -48,12 +48,23 @@ class _Adapter: class Backtest: - def __init__(self, df, strategy_class, cash=10000.0, commission=0.0, spread=0.0): + def __init__( + self, + df, + strategy_class, + cash=10000.0, + commission=0.0, + spread=0.0, + contract_size=100000.0, + quote_to_account=1.0, + ): self._df = df self._strategy_class = strategy_class self._cash = cash self._commission = commission self._spread = spread + self._contract_size = contract_size + self._quote_to_account = quote_to_account def _to_bars(self): bars = [] @@ -77,6 +88,13 @@ class Backtest: def run(self): bars = self._to_bars() - engine = _rust.Engine(bars, self._cash, self._commission, self._spread) # type: ignore + engine = _rust.Engine( # type: ignore + bars, + self._cash, + self._commission, + self._spread, + self._contract_size, + self._quote_to_account, + ) strategy = self._strategy_class() return engine.run(_Adapter(strategy)) diff --git a/src/broker.rs b/src/broker.rs index c67d9cf..7bd94b1 100644 --- a/src/broker.rs +++ b/src/broker.rs @@ -1,15 +1,16 @@ - +use crate::types::{Bar, Position, Trade}; use pyo3::prelude::*; -use crate::types::{Position, Trade, Bar}; #[pyclass] -pub struct Broker{ +pub struct Broker { pub cash: f64, pub initial_cash: f64, pub positions: Vec, pub trade_history: Vec, pub commission: f64, - pub spread: f64 + pub spread: f64, + pub contract_size: f64, + pub quote_to_account: f64, } // Rust-internal only, not exposed to Python @@ -19,18 +20,26 @@ impl Broker { while i < self.positions.len() { let fill = { let p = &self.positions[i]; - if p.is_long{ + if p.is_long { let sl_hit = p.stop_loss.map_or(false, |sl| bar.low <= sl); let tp_hit = p.take_profit.map_or(false, |tp| bar.high >= tp); - if sl_hit {p.stop_loss} - else if tp_hit {p.take_profit} - else {None} + if sl_hit { + p.stop_loss + } else if tp_hit { + p.take_profit + } else { + None + } } else { let sl_hit = p.stop_loss.map_or(false, |sl| bar.high >= sl); let tp_hit = p.take_profit.map_or(false, |tp| bar.low <= tp); - if sl_hit {p.stop_loss} - else if tp_hit {p.take_profit} - else {None} + if sl_hit { + p.stop_loss + } else if tp_hit { + p.take_profit + } else { + None + } } }; @@ -43,9 +52,15 @@ impl Broker { }; let pnl = if position.is_long { - (close_price - position.entry_price) * position.lot_size + (close_price - position.entry_price) + * position.lot_size + * self.contract_size + * self.quote_to_account } else { - (position.entry_price - close_price) * position.lot_size + (position.entry_price - close_price) + * position.lot_size + * self.contract_size + * self.quote_to_account }; self.cash += pnl; self.trade_history.push(Trade { @@ -55,10 +70,10 @@ impl Broker { is_long: position.is_long, pnl, entry_timestamp: position.entry_timestamp, - exit_timestamp: bar.timestamp + exit_timestamp: bar.timestamp, }); } else { - i+=1; + i += 1; } } } @@ -67,18 +82,33 @@ impl Broker { #[pymethods] impl Broker { #[new] - pub fn new(initial_cash: f64, commission: f64, spread: f64) -> Self { + pub fn new( + initial_cash: f64, + commission: f64, + spread: f64, + contract_size: f64, + quote_to_account: f64, + ) -> Self { Broker { cash: initial_cash, initial_cash, positions: Vec::new(), trade_history: Vec::new(), commission, - spread + spread, + contract_size, + quote_to_account, } } - pub fn buy(&mut self, price: f64, lot_size: f64, timestamp: i64, stop_loss: Option, take_profit: Option) { + pub fn buy( + &mut self, + price: f64, + lot_size: f64, + timestamp: i64, + stop_loss: Option, + take_profit: Option, + ) { let fill_price = price + self.spread; self.cash -= self.commission * lot_size; self.positions.push(Position { @@ -88,11 +118,18 @@ impl Broker { is_long: true, entry_timestamp: timestamp, stop_loss, - take_profit + take_profit, }); } - pub fn sell(&mut self, price: f64, lot_size: f64, timestamp: i64, stop_loss: Option, take_profit: Option) { + pub fn sell( + &mut self, + price: f64, + lot_size: f64, + timestamp: i64, + stop_loss: Option, + take_profit: Option, + ) { let fill_price = price - self.spread; self.cash -= self.commission * lot_size; self.positions.push(Position { @@ -102,7 +139,7 @@ impl Broker { is_long: false, entry_timestamp: timestamp, stop_loss, - take_profit + take_profit, }); } @@ -117,9 +154,15 @@ impl Broker { }; let pnl = if position.is_long { - (close_price - position.entry_price) * position.lot_size + (close_price - position.entry_price) + * position.lot_size + * self.contract_size + * self.quote_to_account } else { - (position.entry_price - close_price) * position.lot_size + (position.entry_price - close_price) + * position.lot_size + * self.contract_size + * self.quote_to_account }; self.cash += pnl; @@ -130,7 +173,7 @@ impl Broker { pnl, entry_timestamp: position.entry_timestamp, exit_timestamp: timestamp, - exit_price: close_price + exit_price: close_price, }); } } @@ -144,9 +187,15 @@ impl Broker { }; let pnl = if position.is_long { - (close_price - position.entry_price) * position.lot_size + (close_price - position.entry_price) + * position.lot_size + * self.contract_size + * self.quote_to_account } else { - (position.entry_price - close_price) * position.lot_size + (position.entry_price - close_price) + * position.lot_size + * self.contract_size + * self.quote_to_account }; self.cash += pnl; self.trade_history.push(Trade { @@ -156,19 +205,29 @@ impl Broker { pnl, entry_timestamp: position.entry_timestamp, exit_timestamp: timestamp, - exit_price: close_price + exit_price: close_price, }); } } pub fn equity(&self, current_price: f64) -> f64 { - 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(); + let unrealized: f64 = self + .positions + .iter() + .map(|p| { + if p.is_long { + (current_price - p.entry_price) + * p.lot_size + * self.contract_size + * self.quote_to_account + } else { + (p.entry_price - current_price) + * p.lot_size + * self.contract_size + * self.quote_to_account + } + }) + .sum(); self.cash + unrealized } diff --git a/src/engine.rs b/src/engine.rs index 44ce7f0..2953071 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -29,10 +29,23 @@ impl Engine { #[pymethods] impl Engine { #[new] - pub fn new(data: Vec, initial_cash: f64, commission: f64, spread: f64) -> Self { + pub fn new( + data: Vec, + initial_cash: f64, + commission: f64, + spread: f64, + contract_size: f64, + quote_to_account: f64, + ) -> Self { Engine { data, - broker: Broker::new(initial_cash, commission, spread), + broker: Broker::new( + initial_cash, + commission, + spread, + contract_size, + quote_to_account, + ), equity_curve: Vec::new(), } } @@ -54,6 +67,8 @@ impl Engine { self.broker.initial_cash, self.broker.commission, self.broker.spread, + self.broker.contract_size, + self.broker.quote_to_account, ), )?;