use super::types::TradeDirection;
#[derive(Debug, Clone)]
pub struct Trade {
pub id: String,
pub direction: TradeDirection,
pub entry_bar: usize,
pub entry_price: f64,
pub quantity: f64,
pub exit_bar: Option<usize>,
pub exit_price: Option<f64>,
pub profit_loss: Option<f64>,
}
#[derive(Debug, Clone)]
pub struct Pos {
pub direction: TradeDirection,
pub quantity: f64,
pub entry_price: f64,
pub entry_bar: usize,
}
#[derive(Debug, Clone, Default)]
pub struct StrategyMetrics {
pub total_trades: usize,
pub winning_trades: usize,
pub losing_trades: usize,
pub gross_profit: f64,
pub gross_loss: f64,
pub net_profit: f64,
pub max_drawdown: f64,
pub win_rate: f64,
pub profit_factor: f64,
}
#[derive(Clone)]
pub struct StrategyState {
pub position: Option<Pos>,
pub trades: Vec<Trade>,
pub initial_capital: f64,
pub equity: f64,
pub equity_curve: Vec<f64>,
pub metrics: StrategyMetrics,
}
impl Default for StrategyState {
fn default() -> Self {
Self {
position: None,
trades: Vec::new(),
initial_capital: 100_000.0,
equity: 100_000.0,
equity_curve: Vec::new(),
metrics: StrategyMetrics::default(),
}
}
}
impl StrategyState {
pub fn new(initial_capital: f64) -> Self {
Self {
initial_capital,
equity: initial_capital,
..Default::default()
}
}
pub fn update_metrics(&mut self) {
let closed_trades: Vec<_> = self
.trades
.iter()
.filter(|t| t.profit_loss.is_some())
.collect();
self.metrics.total_trades = closed_trades.len();
self.metrics.winning_trades = closed_trades
.iter()
.filter(|t| t.profit_loss.unwrap_or(0.0) > 0.0)
.count();
self.metrics.losing_trades = closed_trades
.iter()
.filter(|t| t.profit_loss.unwrap_or(0.0) < 0.0)
.count();
self.metrics.gross_profit = closed_trades
.iter()
.filter_map(|t| t.profit_loss)
.filter(|&pl| pl > 0.0)
.sum();
self.metrics.gross_loss = closed_trades
.iter()
.filter_map(|t| t.profit_loss)
.filter(|&pl| pl < 0.0)
.map(|pl| pl.abs())
.sum();
self.metrics.net_profit = self.metrics.gross_profit - self.metrics.gross_loss;
if self.metrics.total_trades > 0 {
self.metrics.win_rate =
self.metrics.winning_trades as f64 / self.metrics.total_trades as f64;
}
if self.metrics.gross_loss > 0.0 {
self.metrics.profit_factor = self.metrics.gross_profit / self.metrics.gross_loss;
}
if !self.equity_curve.is_empty() {
let mut peak = self.equity_curve[0];
let mut max_dd = 0.0;
for &equity in &self.equity_curve {
if equity > peak {
peak = equity;
}
let drawdown = (peak - equity) / peak;
if drawdown > max_dd {
max_dd = drawdown;
}
}
self.metrics.max_drawdown = max_dd;
}
}
}
#[derive(Debug, Clone)]
pub struct PlotOutput {
pub name: String,
pub values: Vec<f64>,
pub color: Option<String>,
}