//! Market simulation. //! //! Generates price paths and handles order execution. use algoarena_shared::{ AssetConfig, Execution, MarketIndicators, MarketState, Order, OrderSide, OrderType, }; /// Market simulator. #[derive(Debug)] pub struct MarketSimulator { /// Asset configurations. assets: Vec, /// Current prices. prices: Vec, /// Price history. price_history: Vec>, /// Current step. step: usize, /// RNG state. rng: SimpleRng, /// Transaction cost (basis points). transaction_cost_bps: f64, } impl MarketSimulator { /// Create a new market simulator. #[must_use] pub fn new(assets: Vec, transaction_cost_bps: f64, seed: u64) -> Self { let prices: Vec = assets.iter().map(|a| a.initial_price).collect(); let price_history: Vec> = assets.iter().map(|a| vec![a.initial_price]).collect(); Self { assets, prices, price_history, step: 0, rng: SimpleRng::new(seed), transaction_cost_bps, } } /// Advance one time step. pub fn step(&mut self) { self.step += 1; for (i, asset) in self.assets.iter().enumerate() { // Geometric Brownian Motion let z = self.rng.normal(); let return_val = asset.drift + asset.volatility * z; self.prices[i] *= 1.0 + return_val; self.price_history[i].push(self.prices[i]); } } /// Get current market state. #[must_use] pub fn get_state(&self) -> MarketState { let volumes: Vec = self.prices.iter().map(|p| p * 1_000_000.0).collect(); // Calculate market indicators let market_return = if self.step > 0 { let initial: f64 = self.price_history.iter().map(|h| h[0]).sum(); let current: f64 = self.prices.iter().sum(); (current - initial) / initial * 100.0 } else { 0.0 }; let volatility = self.calculate_realized_volatility(); let trend = self.calculate_trend(); MarketState { step: self.step, prices: self.prices.clone(), price_history: self.price_history.clone(), volumes, indicators: MarketIndicators { market_return, volatility, trend, }, } } /// Execute an order. #[must_use] pub fn execute_order(&mut self, order: &Order) -> Execution { let price = self.prices[order.asset_idx]; // Slippage based on order size (simplified) let slippage_pct = 0.0005 * order.quantity / 100.0; let slippage = price * slippage_pct; let fill_price = match order.side { OrderSide::Buy => price + slippage, OrderSide::Sell => price - slippage, }; // Check limit orders let is_filled = match order.order_type { OrderType::Market => true, OrderType::Limit => match order.side { OrderSide::Buy => order.limit_price.is_none_or(|limit| fill_price <= limit), OrderSide::Sell => order.limit_price.is_none_or(|limit| fill_price >= limit), }, }; let filled_quantity = if is_filled { order.quantity } else { 0.0 }; let transaction_cost = filled_quantity * fill_price * self.transaction_cost_bps / 10_000.0; Execution { order: order.clone(), filled_quantity, fill_price, transaction_cost, slippage, is_filled, } } /// Get current prices. #[must_use] pub fn prices(&self) -> &[f64] { &self.prices } /// Get price history. #[must_use] pub fn price_history(&self) -> &[Vec] { &self.price_history } /// Calculate realized volatility. fn calculate_realized_volatility(&self) -> f64 { if self.step < 2 { return 0.0; } let lookback = self.step.min(20); let mut returns = Vec::new(); for i in (self.step - lookback + 1)..=self.step { let ret = (self.price_history[0][i] / self.price_history[0][i - 1]).ln(); returns.push(ret); } if returns.is_empty() { return 0.0; } let mean: f64 = returns.iter().sum::() / returns.len() as f64; let variance: f64 = returns.iter().map(|r| (r - mean).powi(2)).sum::() / returns.len() as f64; variance.sqrt() * (252.0_f64).sqrt() * 100.0 // Annualized % } /// Calculate market trend. fn calculate_trend(&self) -> f64 { if self.step < 10 { return 0.0; } // Short MA vs Long MA let short_period = 10.min(self.step); let long_period = 50.min(self.step); let short_ma: f64 = self.price_history[0][(self.step - short_period + 1)..=self.step] .iter() .sum::() / short_period as f64; let long_ma: f64 = self.price_history[0][(self.step - long_period + 1)..=self.step] .iter() .sum::() / long_period as f64; (short_ma - long_ma) / long_ma * 100.0 } } /// Simple pseudo-random number generator. struct SimpleRng { state: u64, } impl std::fmt::Debug for SimpleRng { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("SimpleRng").finish() } } impl SimpleRng { fn new(seed: u64) -> Self { Self { state: seed } } fn next(&mut self) -> u64 { self.state = self .state .wrapping_mul(6364136223846793005) .wrapping_add(1442695040888963407); self.state } fn uniform(&mut self) -> f64 { (self.next() >> 11) as f64 / (1u64 << 53) as f64 } fn normal(&mut self) -> f64 { let u1 = self.uniform() + 1e-10; let u2 = self.uniform(); (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos() } } #[cfg(test)] mod tests { use super::*; use algoarena_shared::default_assets; #[test] fn test_market_creation() { let assets = default_assets(); let market = MarketSimulator::new(assets.clone(), 10.0, 42); assert_eq!(market.prices().len(), assets.len()); } #[test] fn test_market_step() { let assets = default_assets(); let mut market = MarketSimulator::new(assets, 10.0, 42); let initial_price = market.prices()[0]; market.step(); // Price should have changed assert_ne!(market.prices()[0], initial_price); } #[test] fn test_get_state() { let assets = default_assets(); let mut market = MarketSimulator::new(assets, 10.0, 42); for _ in 0..10 { market.step(); } let state = market.get_state(); assert_eq!(state.step, 10); assert_eq!(state.price_history[0].len(), 11); // Initial + 10 steps } #[test] fn test_execute_order() { let assets = default_assets(); let mut market = MarketSimulator::new(assets, 10.0, 42); let order = Order { agent_id: "test".to_string(), asset_idx: 0, side: OrderSide::Buy, order_type: OrderType::Market, quantity: 10.0, limit_price: None, }; let execution = market.execute_order(&order); assert!(execution.is_filled); assert_eq!(execution.filled_quantity, 10.0); } }