//! AlgoArena - Strategy Backtesting Battleground. //! //! This demo showcases multi-agent trading simulation where different strategies //! compete head-to-head in a simulated market environment. pub mod agents; pub mod arena; pub mod market; pub mod sample_data; use algoarena_shared::{TournamentConfig, TournamentResult}; use thiserror::Error; use arena::TradingArena; /// Errors that can occur during tournament simulation. #[derive(Debug, Error)] pub enum AlgoArenaError { /// No agents provided. #[error("No agents provided for tournament")] NoAgents, /// No assets provided. #[error("No assets provided for simulation")] NoAssets, /// Invalid configuration. #[error("Invalid configuration: {0}")] InvalidConfig(String), /// Simulation failed. #[error("Simulation failed: {0}")] SimulationFailed(String), } /// Main AlgoArena system. #[derive(Debug)] pub struct AlgoArena { /// Trading arena. arena: TradingArena, } impl Default for AlgoArena { fn default() -> Self { Self::new() } } impl AlgoArena { /// Create a new AlgoArena. #[must_use] pub fn new() -> Self { Self { arena: TradingArena::new(), } } /// Run a tournament. pub fn run_tournament( &mut self, config: &TournamentConfig, ) -> Result { // Validate config if config.agents.is_empty() { return Err(AlgoArenaError::NoAgents); } if config.assets.is_empty() { return Err(AlgoArenaError::NoAssets); } if config.num_steps == 0 { return Err(AlgoArenaError::InvalidConfig( "num_steps must be > 0".to_string(), )); } // Run simulation self.arena.run(config) } /// Get the trading arena. #[must_use] pub fn arena(&self) -> &TradingArena { &self.arena } } /// Run the full demo. pub fn run_demo() -> Result { let mut arena = AlgoArena::new(); let config = sample_data::create_sample_tournament(); arena.run_tournament(&config) } #[cfg(test)] mod tests { use super::*; use algoarena_shared::{ AgentConfig, AgentParameters, AssetConfig, StrategyType, sample_tournament, }; #[test] fn test_algoarena_creation() { let arena = AlgoArena::new(); assert!(std::mem::size_of_val(&arena) > 0); } #[test] fn test_run_tournament() { let mut arena = AlgoArena::new(); let config = sample_tournament(); let result = arena.run_tournament(&config); assert!(result.is_ok()); let tournament = result.unwrap(); assert!(!tournament.rankings.is_empty()); assert_eq!(tournament.rankings[0].rank, 1); } #[test] fn test_no_agents_error() { let mut arena = AlgoArena::new(); let config = TournamentConfig { name: "Empty".to_string(), num_steps: 100, assets: vec![AssetConfig { symbol: "TEST".to_string(), initial_price: 100.0, volatility: 0.01, drift: 0.0, }], agents: vec![], transaction_cost_bps: 10.0, seed: Some(42), }; let result = arena.run_tournament(&config); assert!(matches!(result, Err(AlgoArenaError::NoAgents))); } #[test] fn test_no_assets_error() { let mut arena = AlgoArena::new(); let config = TournamentConfig { name: "Empty".to_string(), num_steps: 100, assets: vec![], agents: vec![AgentConfig { id: "test".to_string(), name: "Test".to_string(), strategy: StrategyType::BuyAndHold, initial_capital: 100_000.0, parameters: AgentParameters::default(), }], transaction_cost_bps: 10.0, seed: Some(42), }; let result = arena.run_tournament(&config); assert!(matches!(result, Err(AlgoArenaError::NoAssets))); } #[test] fn test_run_demo() { let result = run_demo(); assert!(result.is_ok()); let tournament = result.unwrap(); assert!(tournament.rankings.len() >= 3); } }