use crate::finance::agent::AgentMetrics;
pub struct RewardCalculator {
roi_scale: f32,
drawdown_penalty: f32,
scar_penalty: f32,
win_rate_bonus: f32,
}
impl RewardCalculator {
pub fn new() -> Self {
Self {
roi_scale: 10.0, drawdown_penalty: 5.0, scar_penalty: 1.0, win_rate_bonus: 2.0, }
}
pub fn calculate_immediate_reward(
&self,
previous_metrics: &AgentMetrics,
current_metrics: &AgentMetrics,
) -> f32 {
let mut reward = 0.0;
if current_metrics.capital > previous_metrics.capital {
let capital_gain = current_metrics.capital as f32 - previous_metrics.capital as f32;
let roi = capital_gain / previous_metrics.capital as f32;
reward += roi * self.roi_scale;
} else if current_metrics.capital < previous_metrics.capital {
let capital_loss = previous_metrics.capital as f32 - current_metrics.capital as f32;
let loss_ratio = capital_loss / previous_metrics.capital as f32;
reward -= loss_ratio * self.roi_scale * 2.0; }
if current_metrics.current_drawdown > previous_metrics.current_drawdown {
let drawdown_increase = current_metrics.current_drawdown - previous_metrics.current_drawdown;
reward -= drawdown_increase * self.drawdown_penalty;
}
if current_metrics.scar_count > previous_metrics.scar_count {
let new_scars = (current_metrics.scar_count - previous_metrics.scar_count) as f32;
reward -= new_scars * self.scar_penalty;
}
if current_metrics.win_rate > previous_metrics.win_rate {
let win_rate_improvement = current_metrics.win_rate - previous_metrics.win_rate;
reward += (win_rate_improvement / 100.0) * self.win_rate_bonus;
}
reward
}
pub fn calculate_episode_reward(
&self,
initial_capital: u64,
final_metrics: &AgentMetrics,
) -> f32 {
let mut reward = 0.0;
let roi = (final_metrics.capital as f32 / initial_capital as f32) - 1.0;
reward += roi * self.roi_scale * 100.0;
reward -= final_metrics.max_drawdown * self.drawdown_penalty;
reward -= final_metrics.scar_count as f32 * self.scar_penalty * 10.0;
if final_metrics.win_rate > 50.0 {
let excess_wins = final_metrics.win_rate - 50.0;
reward += excess_wins * self.win_rate_bonus;
}
if final_metrics.capital < (initial_capital / 2) {
reward -= 1000.0;
}
reward
}
pub fn with_weights(
roi_scale: f32,
drawdown_penalty: f32,
scar_penalty: f32,
win_rate_bonus: f32,
) -> Self {
Self {
roi_scale,
drawdown_penalty,
scar_penalty,
win_rate_bonus,
}
}
}
impl Default for RewardCalculator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_metrics(capital: u64, win_rate: f32, scar_count: u32, drawdown: f32) -> AgentMetrics {
AgentMetrics {
capital,
total_trades: 10,
win_rate,
max_drawdown: drawdown,
current_drawdown: drawdown,
peak_capital: capital,
total_fees_paid: 0,
scar_count,
trust_score: 50.0,
generation: 0,
}
}
#[test]
fn test_profit_reward() {
let calc = RewardCalculator::new();
let prev = create_metrics(10000, 50.0, 0, 0.0);
let curr = create_metrics(11000, 50.0, 0, 0.0);
let reward = calc.calculate_immediate_reward(&prev, &curr);
assert!(reward > 0.0, "Profitable trade should give positive reward");
}
#[test]
fn test_loss_penalty() {
let calc = RewardCalculator::new();
let prev = create_metrics(10000, 50.0, 0, 0.0);
let curr = create_metrics(9000, 50.0, 0, 5.0);
let reward = calc.calculate_immediate_reward(&prev, &curr);
assert!(reward < 0.0, "Loss should give negative reward");
}
#[test]
fn test_scar_penalty() {
let calc = RewardCalculator::new();
let prev = create_metrics(10000, 50.0, 0, 0.0);
let curr = create_metrics(10000, 50.0, 1, 5.0);
let reward = calc.calculate_immediate_reward(&prev, &curr);
assert!(reward < 0.0, "Scar should give negative reward");
}
}