use uuid::Uuid;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::lineage::Lineage;
use crate::scar::ScarSeverity;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct AgentId(Uuid);
impl AgentId {
pub fn new() -> Self {
AgentId(Uuid::new_v4())
}
}
impl fmt::Display for AgentId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentMetrics {
pub capital: u64,
pub total_trades: u64,
pub win_rate: f32,
pub max_drawdown: f32,
pub current_drawdown: f32,
pub peak_capital: u64,
pub total_fees_paid: u64,
pub scar_count: u32,
pub trust_score: f32,
pub generation: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum FinanceAgentStatus {
Active,
Bankrupt,
TerminallyDamaged,
Terminated,
}
pub struct FinanceAgent {
pub id: AgentId,
pub lineage: Lineage,
pub strategy: String,
pub metrics: AgentMetrics,
pub trade_history: Vec<(DateTime<Utc>, String)>,
pub status: FinanceAgentStatus,
pub parent_id: Option<AgentId>,
pub max_scars: u32,
pub scar_cost_multiplier: f32,
}
impl FinanceAgent {
pub fn new(strategy: String, initial_capital: u64, generation: u32) -> Self {
let lineage = Lineage::create(initial_capital);
FinanceAgent {
id: AgentId::new(),
lineage,
strategy,
metrics: AgentMetrics {
capital: initial_capital,
total_trades: 0,
win_rate: 50.0,
max_drawdown: 0.0,
current_drawdown: 0.0,
peak_capital: initial_capital,
total_fees_paid: 0,
scar_count: 0,
trust_score: 50.0,
generation,
},
trade_history: Vec::new(),
status: FinanceAgentStatus::Active,
parent_id: None,
max_scars: 5,
scar_cost_multiplier: 1.0,
}
}
pub fn spawn_offspring(
parent_id: AgentId,
strategy: String,
inherited_capital: u64,
parent_generation: u32,
inherited_multiplier: f32,
) -> Self {
let mut agent = FinanceAgent::new(strategy, inherited_capital, parent_generation + 1);
agent.parent_id = Some(parent_id);
agent.scar_cost_multiplier = inherited_multiplier;
agent
}
pub fn record_trade(&mut self, trade_description: String, is_win: bool) {
self.trade_history.push((Utc::now(), trade_description));
let total = self.metrics.total_trades + 1;
let previous_wins = (self.metrics.win_rate / 100.0 * self.metrics.total_trades as f32) as u64;
let wins = if is_win {
previous_wins + 1
} else {
previous_wins
};
self.metrics.total_trades = total;
self.metrics.win_rate = (wins as f32 / total as f32) * 100.0;
}
pub fn inflict_financial_scar(&mut self, drawdown_percentage: f32, _severity: ScarSeverity) {
if self.metrics.current_drawdown < drawdown_percentage {
self.metrics.current_drawdown = drawdown_percentage;
}
if drawdown_percentage > self.metrics.max_drawdown {
self.metrics.max_drawdown = drawdown_percentage;
}
self.metrics.scar_count += 1;
self.scar_cost_multiplier *= 1.05; }
pub fn check_and_inflict_drawdown_scar(&mut self, current_capital: u64) -> bool {
let drawdown = ((self.metrics.peak_capital as f32 - current_capital as f32)
/ self.metrics.peak_capital as f32) * 100.0;
if drawdown > 5.0 && (self.metrics.scar_count as f32 * 5.0) <= drawdown {
self.inflict_financial_scar(drawdown, ScarSeverity::Severe);
self.metrics.current_drawdown = drawdown;
return true;
}
false
}
pub fn check_termination_criteria(&self, roi_threshold: f32, scar_threshold: u32) -> bool {
let roi = (self.metrics.capital as f32 / self.metrics.peak_capital as f32 - 1.0) * 100.0;
roi < roi_threshold || self.metrics.scar_count > scar_threshold || self.metrics.capital == 0
}
pub fn spawn_evolved_offspring(&self, mutation_rate: f32) -> Self {
let inherited_capital = (self.metrics.capital as f32 * 0.3) as u64; let mutated_strategy = format!("{}_v{}", self.strategy, self.metrics.generation + 1);
let mut offspring = FinanceAgent::spawn_offspring(
self.id,
mutated_strategy,
inherited_capital,
self.metrics.generation,
self.scar_cost_multiplier * 0.95, );
offspring.metrics.trust_score = (self.metrics.trust_score * (1.0 + mutation_rate * (rand::random::<f32>() - 0.5))).clamp(0.0, 100.0);
offspring
}
pub fn calculate_trade_cost(&self, base_cost: u64) -> u64 {
(base_cost as f32 * self.scar_cost_multiplier) as u64
}
pub fn check_status_update(&mut self) {
if self.metrics.scar_count >= self.max_scars {
self.status = FinanceAgentStatus::TerminallyDamaged;
}
}
pub fn get_capital(&self) -> u64 {
self.lineage.metabolism().energy()
}
pub fn consume_capital(&mut self, amount: u64) -> Result<(), String> {
if self.status != FinanceAgentStatus::Active {
return Err(format!("Agent status is {:?}, cannot trade", self.status));
}
if amount > self.get_capital() {
return Err(format!(
"Insufficient capital: need {}, have {}",
amount,
self.get_capital()
));
}
match self.lineage.perform_operation(
format!("Trade execution (cost {})", amount),
amount,
) {
crate::lineage::OperationResult::Success { .. } => {
self.metrics.capital = self.get_capital();
if self.metrics.capital == 0 {
self.status = FinanceAgentStatus::Bankrupt;
}
Ok(())
}
_ => Err("Operation failed".to_string()),
}
}
pub fn restore_capital_from_profit(&mut self, profit: u64) {
self.metrics.capital += profit;
if self.metrics.capital > self.metrics.peak_capital {
self.metrics.peak_capital = self.metrics.capital;
}
}
pub fn is_alive(&self) -> bool {
self.status == FinanceAgentStatus::Active
}
pub fn status_report(&self) -> String {
format!(
"Agent {} [{}]\n Capital: {}\n Trades: {}\n Win Rate: {:.2}%\n Scars: {}\n Trust: {:.2}\n Status: {:?}",
self.id.0,
self.strategy,
self.metrics.capital,
self.metrics.total_trades,
self.metrics.win_rate,
self.metrics.scar_count,
self.metrics.trust_score,
self.status,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_agent_creation() {
let agent = FinanceAgent::new("TestStrategy".to_string(), 10000, 0);
assert_eq!(agent.metrics.capital, 10000);
assert_eq!(agent.metrics.total_trades, 0);
assert_eq!(agent.status, FinanceAgentStatus::Active);
}
#[test]
fn test_trade_recording() {
let mut agent = FinanceAgent::new("TestStrategy".to_string(), 10000, 0);
agent.record_trade("BUY BTC".to_string(), true);
assert_eq!(agent.metrics.total_trades, 1);
assert_eq!(agent.metrics.win_rate, 100.0);
}
}