made pyclass work for stats

This commit is contained in:
KhizarImran
2026-06-14 01:29:20 +01:00
parent 6f39fc2d34
commit 322ff54d63
2 changed files with 121 additions and 80 deletions
+1
View File
@@ -10,5 +10,6 @@ use pyo3::prelude::*;
#[pymodule] #[pymodule]
fn backtestingfx(m: &Bound<'_, PyModule>) -> PyResult<()> { fn backtestingfx(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<types::Bar>()?; m.add_class::<types::Bar>()?;
m.add_class::<stats::Stats>()?;
Ok(()) Ok(())
} }
+120 -80
View File
@@ -1,87 +1,121 @@
use crate::broker::Broker; use crate::broker::Broker;
use pyo3::prelude::*;
pub struct Stats { #[pyclass]
pub initial_cash: f64, pub struct Stats {
pub final_cash: f64, #[pyo3(get)]
pub total_return_pct: f64, pub initial_cash: f64,
pub num_trades: usize, #[pyo3(get)]
pub num_wins: usize, pub final_cash: f64,
pub win_rate_pct: f64, #[pyo3(get)]
pub avg_pnl: f64, pub total_return_pct: f64,
pub best_trade: f64, #[pyo3(get)]
pub worst_trade: f64, pub num_trades: usize,
pub profit_factor: f64, #[pyo3(get)]
pub max_drawdown_pct: f64 pub num_wins: usize,
#[pyo3(get)]
pub win_rate_pct: f64,
#[pyo3(get)]
pub avg_pnl: f64,
#[pyo3(get)]
pub best_trade: f64,
#[pyo3(get)]
pub worst_trade: f64,
#[pyo3(get)]
pub profit_factor: f64,
#[pyo3(get)]
pub max_drawdown_pct: f64,
}
} fn max_drawdown(equity_curve: &[f64]) -> f64 {
let mut peak = f64::NEG_INFINITY;
let mut max_dd = 0.0f64;
fn max_drawdown(equity_curve: &[f64]) -> f64 { for &equity in equity_curve {
let mut peak = f64::NEG_INFINITY; if equity > peak {
let mut max_dd = 0.0f64; peak = equity;
}
if peak > 0.0 {
let dd = (peak - equity) / peak * 100.0;
if dd > max_dd {
max_dd = dd;
}
}
}
max_dd
}
for &equity in equity_curve { impl Stats {
if equity > peak { pub fn compute(broker: &Broker, equity_curve: &[f64]) -> Self {
peak = equity; let num_trades = broker.trade_history.len();
} let initial_cash = broker.initial_cash;
if peak > 0.0 { let final_cash = broker.cash;
let dd = (peak - equity) / peak * 100.0; let total_return_pct = (final_cash - initial_cash) / initial_cash * 100.0;
if dd > max_dd {
max_dd = dd;
}
}
}
max_dd
}
impl Stats { let num_wins = broker.trade_history.iter().filter(|t| t.pnl > 0.0).count();
pub fn compute(broker: &Broker, equity_curve: &[f64]) -> Self { let win_rate_pct = if num_trades > 0 {
let num_trades = broker.trade_history.len(); num_wins as f64 / num_trades as f64 * 100.0
let initial_cash = broker.initial_cash; } else {
let final_cash = broker.cash; 0.0
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 avg_pnl = if num_trades > 0 {
let win_rate_pct = if num_trades > 0 { broker.trade_history.iter().map(|t| t.pnl).sum::<f64>() / num_trades as f64
num_wins as f64 / num_trades as f64 * 100.0 } else {
} else { 0.0 }; 0.0
};
let avg_pnl = if num_trades > 0 { let best_trade = broker
broker.trade_history.iter().map(|t| t.pnl).sum::<f64>() / num_trades as f64 .trade_history
} else { 0.0 }; .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 best_trade = broker.trade_history.iter().map(|t| t.pnl) let gross_profit: f64 = broker
.fold(f64::NEG_INFINITY, f64::max); .trade_history
let worst_trade = broker.trade_history.iter().map(|t| t.pnl) .iter()
.fold(f64::INFINITY, f64::min); .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
};
let max_drawdown_pct = max_drawdown(equity_curve);
let gross_profit: f64 = broker.trade_history.iter() Stats {
.filter(|t| t.pnl > 0.0).map(|t| t.pnl).sum(); initial_cash,
let gross_loss: f64 = broker.trade_history.iter() final_cash,
.filter(|t| t.pnl < 0.0).map(|t| t.pnl.abs()).sum(); total_return_pct,
let profit_factor = if gross_loss > 0.0 { gross_profit / gross_loss } else { num_trades,
f64::INFINITY }; num_wins,
let max_drawdown_pct = max_drawdown(equity_curve); 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,
max_drawdown_pct,
}
}
}
Stats { impl std::fmt::Display for Stats {
initial_cash, fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
final_cash, write!(
total_return_pct, f,
num_trades, "--- Backtest Results ---\n\
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,
max_drawdown_pct
}
}
}
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\ Initial Cash: {:.2}\n\
Final Cash: {:.2}\n\ Final Cash: {:.2}\n\
Total Return: {:.2}%\n\ Total Return: {:.2}%\n\
@@ -92,10 +126,16 @@ use crate::broker::Broker;
Worst Trade: {:.5}\n\ Worst Trade: {:.5}\n\
Profit Factor: {:.2}\n\ Profit Factor: {:.2}\n\
Max Drawdown: {:.2}%", Max Drawdown: {:.2}%",
self.initial_cash, self.final_cash, self.total_return_pct, self.initial_cash,
self.num_trades, self.win_rate_pct, self.avg_pnl, self.final_cash,
self.best_trade, self.worst_trade, self.profit_factor, self.total_return_pct,
self.max_drawdown_pct self.num_trades,
) self.win_rate_pct,
} self.avg_pnl,
} self.best_trade,
self.worst_trade,
self.profit_factor,
self.max_drawdown_pct
)
}
}