added lot size

This commit is contained in:
KhizarImran
2026-06-21 00:18:44 +01:00
parent 706be103f0
commit cb9bf41ff3
3 changed files with 130 additions and 38 deletions
+20 -2
View File
@@ -48,12 +48,23 @@ class _Adapter:
class Backtest: 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._df = df
self._strategy_class = strategy_class self._strategy_class = strategy_class
self._cash = cash self._cash = cash
self._commission = commission self._commission = commission
self._spread = spread self._spread = spread
self._contract_size = contract_size
self._quote_to_account = quote_to_account
def _to_bars(self): def _to_bars(self):
bars = [] bars = []
@@ -77,6 +88,13 @@ class Backtest:
def run(self): def run(self):
bars = self._to_bars() 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() strategy = self._strategy_class()
return engine.run(_Adapter(strategy)) return engine.run(_Adapter(strategy))
+93 -34
View File
@@ -1,15 +1,16 @@
use crate::types::{Bar, Position, Trade};
use pyo3::prelude::*; use pyo3::prelude::*;
use crate::types::{Position, Trade, Bar};
#[pyclass] #[pyclass]
pub struct Broker{ pub struct Broker {
pub cash: f64, pub cash: f64,
pub initial_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,
pub spread: f64 pub spread: f64,
pub contract_size: f64,
pub quote_to_account: f64,
} }
// Rust-internal only, not exposed to Python // Rust-internal only, not exposed to Python
@@ -19,18 +20,26 @@ impl Broker {
while i < self.positions.len() { while i < self.positions.len() {
let fill = { let fill = {
let p = &self.positions[i]; 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 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); let tp_hit = p.take_profit.map_or(false, |tp| bar.high >= tp);
if sl_hit {p.stop_loss} if sl_hit {
else if tp_hit {p.take_profit} p.stop_loss
else {None} } else if tp_hit {
p.take_profit
} else {
None
}
} else { } else {
let sl_hit = p.stop_loss.map_or(false, |sl| bar.high >= sl); 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); let tp_hit = p.take_profit.map_or(false, |tp| bar.low <= tp);
if sl_hit {p.stop_loss} if sl_hit {
else if tp_hit {p.take_profit} p.stop_loss
else {None} } else if tp_hit {
p.take_profit
} else {
None
}
} }
}; };
@@ -43,9 +52,15 @@ impl Broker {
}; };
let pnl = if position.is_long { 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 { } 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.cash += pnl;
self.trade_history.push(Trade { self.trade_history.push(Trade {
@@ -55,10 +70,10 @@ impl Broker {
is_long: position.is_long, is_long: position.is_long,
pnl, pnl,
entry_timestamp: position.entry_timestamp, entry_timestamp: position.entry_timestamp,
exit_timestamp: bar.timestamp exit_timestamp: bar.timestamp,
}); });
} else { } else {
i+=1; i += 1;
} }
} }
} }
@@ -67,18 +82,33 @@ impl Broker {
#[pymethods] #[pymethods]
impl Broker { impl Broker {
#[new] #[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 { Broker {
cash: initial_cash, cash: initial_cash,
initial_cash, initial_cash,
positions: Vec::new(), positions: Vec::new(),
trade_history: Vec::new(), trade_history: Vec::new(),
commission, commission,
spread spread,
contract_size,
quote_to_account,
} }
} }
pub fn buy(&mut self, price: f64, lot_size: f64, timestamp: i64, stop_loss: Option<f64>, take_profit: Option<f64>) { pub fn buy(
&mut self,
price: f64,
lot_size: f64,
timestamp: i64,
stop_loss: Option<f64>,
take_profit: Option<f64>,
) {
let fill_price = price + self.spread; let fill_price = price + self.spread;
self.cash -= self.commission * lot_size; self.cash -= self.commission * lot_size;
self.positions.push(Position { self.positions.push(Position {
@@ -88,11 +118,18 @@ impl Broker {
is_long: true, is_long: true,
entry_timestamp: timestamp, entry_timestamp: timestamp,
stop_loss, stop_loss,
take_profit take_profit,
}); });
} }
pub fn sell(&mut self, price: f64, lot_size: f64, timestamp: i64, stop_loss: Option<f64>, take_profit: Option<f64>) { pub fn sell(
&mut self,
price: f64,
lot_size: f64,
timestamp: i64,
stop_loss: Option<f64>,
take_profit: Option<f64>,
) {
let fill_price = price - self.spread; let fill_price = price - self.spread;
self.cash -= self.commission * lot_size; self.cash -= self.commission * lot_size;
self.positions.push(Position { self.positions.push(Position {
@@ -102,7 +139,7 @@ impl Broker {
is_long: false, is_long: false,
entry_timestamp: timestamp, entry_timestamp: timestamp,
stop_loss, stop_loss,
take_profit take_profit,
}); });
} }
@@ -117,9 +154,15 @@ impl Broker {
}; };
let pnl = if position.is_long { 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 { } 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.cash += pnl;
@@ -130,7 +173,7 @@ impl Broker {
pnl, pnl,
entry_timestamp: position.entry_timestamp, entry_timestamp: position.entry_timestamp,
exit_timestamp: timestamp, exit_timestamp: timestamp,
exit_price: close_price exit_price: close_price,
}); });
} }
} }
@@ -144,9 +187,15 @@ impl Broker {
}; };
let pnl = if position.is_long { 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 { } 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.cash += pnl;
self.trade_history.push(Trade { self.trade_history.push(Trade {
@@ -156,19 +205,29 @@ impl Broker {
pnl, pnl,
entry_timestamp: position.entry_timestamp, entry_timestamp: position.entry_timestamp,
exit_timestamp: timestamp, exit_timestamp: timestamp,
exit_price: close_price exit_price: close_price,
}); });
} }
} }
pub fn equity(&self, current_price: f64) -> f64 { pub fn equity(&self, current_price: f64) -> f64 {
let unrealized: f64 = self.positions.iter().map(|p| { let unrealized: f64 = self
if p.is_long { .positions
(current_price - p.entry_price) * p.lot_size .iter()
} else { .map(|p| {
(p.entry_price - current_price) * p.lot_size if p.is_long {
} (current_price - p.entry_price)
}).sum(); * 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 self.cash + unrealized
} }
+17 -2
View File
@@ -29,10 +29,23 @@ impl Engine {
#[pymethods] #[pymethods]
impl Engine { impl Engine {
#[new] #[new]
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,
contract_size: f64,
quote_to_account: f64,
) -> Self {
Engine { Engine {
data, data,
broker: Broker::new(initial_cash, commission, spread), broker: Broker::new(
initial_cash,
commission,
spread,
contract_size,
quote_to_account,
),
equity_curve: Vec::new(), equity_curve: Vec::new(),
} }
} }
@@ -54,6 +67,8 @@ impl Engine {
self.broker.initial_cash, self.broker.initial_cash,
self.broker.commission, self.broker.commission,
self.broker.spread, self.broker.spread,
self.broker.contract_size,
self.broker.quote_to_account,
), ),
)?; )?;