431 lines
12 KiB
Rust
431 lines
12 KiB
Rust
//! Shared types for AlgoArena - Strategy Backtesting Battleground.
|
|
//!
|
|
//! This crate defines the IPC types for multi-agent trading simulation.
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
// ============================================================================
|
|
// Market Types
|
|
// ============================================================================
|
|
|
|
/// Market state snapshot.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MarketState {
|
|
/// Current timestamp (step).
|
|
pub step: usize,
|
|
/// Current prices for each asset.
|
|
pub prices: Vec<f64>,
|
|
/// Price history (for lookback).
|
|
pub price_history: Vec<Vec<f64>>,
|
|
/// Volume for each asset.
|
|
pub volumes: Vec<f64>,
|
|
/// Market indicators.
|
|
pub indicators: MarketIndicators,
|
|
}
|
|
|
|
/// Market-wide indicators.
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
pub struct MarketIndicators {
|
|
/// Market return (%).
|
|
pub market_return: f64,
|
|
/// Volatility.
|
|
pub volatility: f64,
|
|
/// Trend strength (positive = bullish, negative = bearish).
|
|
pub trend: f64,
|
|
}
|
|
|
|
/// Asset configuration.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AssetConfig {
|
|
/// Asset symbol.
|
|
pub symbol: String,
|
|
/// Initial price.
|
|
pub initial_price: f64,
|
|
/// Daily volatility.
|
|
pub volatility: f64,
|
|
/// Expected drift (daily).
|
|
pub drift: f64,
|
|
}
|
|
|
|
// ============================================================================
|
|
// Order and Execution Types
|
|
// ============================================================================
|
|
|
|
/// Order side.
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
|
pub enum OrderSide {
|
|
/// Buy.
|
|
Buy,
|
|
/// Sell.
|
|
Sell,
|
|
}
|
|
|
|
/// Order type.
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
|
pub enum OrderType {
|
|
/// Market order.
|
|
Market,
|
|
/// Limit order.
|
|
Limit,
|
|
}
|
|
|
|
/// A trading order.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Order {
|
|
/// Agent ID.
|
|
pub agent_id: String,
|
|
/// Asset index.
|
|
pub asset_idx: usize,
|
|
/// Side.
|
|
pub side: OrderSide,
|
|
/// Order type.
|
|
pub order_type: OrderType,
|
|
/// Quantity (shares).
|
|
pub quantity: f64,
|
|
/// Limit price (for limit orders).
|
|
pub limit_price: Option<f64>,
|
|
}
|
|
|
|
/// Order execution result.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Execution {
|
|
/// Original order.
|
|
pub order: Order,
|
|
/// Filled quantity.
|
|
pub filled_quantity: f64,
|
|
/// Average fill price.
|
|
pub fill_price: f64,
|
|
/// Transaction cost.
|
|
pub transaction_cost: f64,
|
|
/// Slippage.
|
|
pub slippage: f64,
|
|
/// Whether fully filled.
|
|
pub is_filled: bool,
|
|
}
|
|
|
|
// ============================================================================
|
|
// Agent Types
|
|
// ============================================================================
|
|
|
|
/// Strategy type for an agent.
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
|
pub enum StrategyType {
|
|
/// Buy and hold.
|
|
BuyAndHold,
|
|
/// Momentum following.
|
|
Momentum,
|
|
/// Mean reversion.
|
|
MeanReversion,
|
|
/// Trend following (moving average crossover).
|
|
TrendFollowing,
|
|
/// Random (baseline).
|
|
Random,
|
|
/// Custom / RL-based.
|
|
Custom,
|
|
}
|
|
|
|
/// Agent configuration.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AgentConfig {
|
|
/// Agent ID.
|
|
pub id: String,
|
|
/// Agent name.
|
|
pub name: String,
|
|
/// Strategy type.
|
|
pub strategy: StrategyType,
|
|
/// Initial capital.
|
|
pub initial_capital: f64,
|
|
/// Parameters.
|
|
pub parameters: AgentParameters,
|
|
}
|
|
|
|
/// Agent parameters.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AgentParameters {
|
|
/// Lookback window.
|
|
pub lookback: usize,
|
|
/// Position sizing (fraction of capital).
|
|
pub position_size: f64,
|
|
/// Stop loss (%).
|
|
pub stop_loss: Option<f64>,
|
|
/// Take profit (%).
|
|
pub take_profit: Option<f64>,
|
|
/// Short MA period (for trend following).
|
|
pub short_ma_period: usize,
|
|
/// Long MA period.
|
|
pub long_ma_period: usize,
|
|
/// Threshold for mean reversion.
|
|
pub reversion_threshold: f64,
|
|
/// Momentum threshold.
|
|
pub momentum_threshold: f64,
|
|
}
|
|
|
|
impl Default for AgentParameters {
|
|
fn default() -> Self {
|
|
Self {
|
|
lookback: 20,
|
|
position_size: 0.1,
|
|
stop_loss: Some(0.05),
|
|
take_profit: Some(0.10),
|
|
short_ma_period: 10,
|
|
long_ma_period: 50,
|
|
reversion_threshold: 2.0,
|
|
momentum_threshold: 0.01,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Agent state during simulation.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AgentState {
|
|
/// Agent ID.
|
|
pub agent_id: String,
|
|
/// Current cash.
|
|
pub cash: f64,
|
|
/// Holdings (shares per asset).
|
|
pub holdings: Vec<f64>,
|
|
/// Portfolio value.
|
|
pub portfolio_value: f64,
|
|
/// P&L history.
|
|
pub pnl_history: Vec<f64>,
|
|
/// Number of trades.
|
|
pub trade_count: usize,
|
|
}
|
|
|
|
// ============================================================================
|
|
// Tournament Types
|
|
// ============================================================================
|
|
|
|
/// Tournament configuration.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct TournamentConfig {
|
|
/// Tournament name.
|
|
pub name: String,
|
|
/// Number of simulation steps.
|
|
pub num_steps: usize,
|
|
/// Asset configurations.
|
|
pub assets: Vec<AssetConfig>,
|
|
/// Agent configurations.
|
|
pub agents: Vec<AgentConfig>,
|
|
/// Transaction cost (basis points).
|
|
pub transaction_cost_bps: f64,
|
|
/// Random seed.
|
|
pub seed: Option<u64>,
|
|
}
|
|
|
|
/// Tournament result.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct TournamentResult {
|
|
/// Tournament config.
|
|
pub config: TournamentConfig,
|
|
/// Agent results (sorted by rank).
|
|
pub rankings: Vec<AgentResult>,
|
|
/// Final price paths.
|
|
pub price_paths: Vec<Vec<f64>>,
|
|
/// Market statistics.
|
|
pub market_stats: MarketStats,
|
|
}
|
|
|
|
/// Individual agent result.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AgentResult {
|
|
/// Agent ID.
|
|
pub agent_id: String,
|
|
/// Agent name.
|
|
pub agent_name: String,
|
|
/// Strategy type.
|
|
pub strategy: StrategyType,
|
|
/// Final rank (1 = best).
|
|
pub rank: usize,
|
|
/// Final portfolio value.
|
|
pub final_value: f64,
|
|
/// Total return (%).
|
|
pub total_return: f64,
|
|
/// Sharpe ratio.
|
|
pub sharpe_ratio: f64,
|
|
/// Maximum drawdown (%).
|
|
pub max_drawdown: f64,
|
|
/// Win rate (% of profitable trades).
|
|
pub win_rate: f64,
|
|
/// Number of trades.
|
|
pub trade_count: usize,
|
|
/// P&L history.
|
|
pub pnl_history: Vec<f64>,
|
|
}
|
|
|
|
/// Market statistics.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MarketStats {
|
|
/// Market return over period.
|
|
pub market_return: f64,
|
|
/// Market volatility.
|
|
pub market_volatility: f64,
|
|
/// Best performing asset.
|
|
pub best_asset: String,
|
|
/// Worst performing asset.
|
|
pub worst_asset: String,
|
|
}
|
|
|
|
// ============================================================================
|
|
// Sample Data Functions
|
|
// ============================================================================
|
|
|
|
/// Create default asset configurations.
|
|
#[must_use]
|
|
pub fn default_assets() -> Vec<AssetConfig> {
|
|
vec![
|
|
AssetConfig {
|
|
symbol: "SPY".to_string(),
|
|
initial_price: 500.0,
|
|
volatility: 0.012,
|
|
drift: 0.0003,
|
|
},
|
|
AssetConfig {
|
|
symbol: "QQQ".to_string(),
|
|
initial_price: 400.0,
|
|
volatility: 0.015,
|
|
drift: 0.0004,
|
|
},
|
|
AssetConfig {
|
|
symbol: "TLT".to_string(),
|
|
initial_price: 90.0,
|
|
volatility: 0.010,
|
|
drift: 0.0001,
|
|
},
|
|
]
|
|
}
|
|
|
|
/// Create default agent configurations.
|
|
#[must_use]
|
|
pub fn default_agents() -> Vec<AgentConfig> {
|
|
vec![
|
|
AgentConfig {
|
|
id: "momentum_1".to_string(),
|
|
name: "Momentum Master".to_string(),
|
|
strategy: StrategyType::Momentum,
|
|
initial_capital: 100_000.0,
|
|
parameters: AgentParameters {
|
|
lookback: 20,
|
|
momentum_threshold: 0.02,
|
|
..Default::default()
|
|
},
|
|
},
|
|
AgentConfig {
|
|
id: "reversion_1".to_string(),
|
|
name: "Mean Machine".to_string(),
|
|
strategy: StrategyType::MeanReversion,
|
|
initial_capital: 100_000.0,
|
|
parameters: AgentParameters {
|
|
lookback: 30,
|
|
reversion_threshold: 2.0,
|
|
..Default::default()
|
|
},
|
|
},
|
|
AgentConfig {
|
|
id: "trend_1".to_string(),
|
|
name: "Trend Tracker".to_string(),
|
|
strategy: StrategyType::TrendFollowing,
|
|
initial_capital: 100_000.0,
|
|
parameters: AgentParameters {
|
|
short_ma_period: 10,
|
|
long_ma_period: 50,
|
|
..Default::default()
|
|
},
|
|
},
|
|
AgentConfig {
|
|
id: "buyhold_1".to_string(),
|
|
name: "Buy & Hold".to_string(),
|
|
strategy: StrategyType::BuyAndHold,
|
|
initial_capital: 100_000.0,
|
|
parameters: AgentParameters::default(),
|
|
},
|
|
AgentConfig {
|
|
id: "random_1".to_string(),
|
|
name: "Random Walker".to_string(),
|
|
strategy: StrategyType::Random,
|
|
initial_capital: 100_000.0,
|
|
parameters: AgentParameters::default(),
|
|
},
|
|
]
|
|
}
|
|
|
|
/// Create a sample tournament configuration.
|
|
#[must_use]
|
|
pub fn sample_tournament() -> TournamentConfig {
|
|
TournamentConfig {
|
|
name: "Strategy Showdown".to_string(),
|
|
num_steps: 252, // One year
|
|
assets: default_assets(),
|
|
agents: default_agents(),
|
|
transaction_cost_bps: 10.0,
|
|
seed: Some(42),
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Tests
|
|
// ============================================================================
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_order_creation() {
|
|
let order = Order {
|
|
agent_id: "test".to_string(),
|
|
asset_idx: 0,
|
|
side: OrderSide::Buy,
|
|
order_type: OrderType::Market,
|
|
quantity: 10.0,
|
|
limit_price: None,
|
|
};
|
|
|
|
assert_eq!(order.side, OrderSide::Buy);
|
|
assert_eq!(order.quantity, 10.0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_agent_parameters_default() {
|
|
let params = AgentParameters::default();
|
|
assert_eq!(params.lookback, 20);
|
|
assert!(params.stop_loss.is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn test_default_assets() {
|
|
let assets = default_assets();
|
|
assert_eq!(assets.len(), 3);
|
|
assert_eq!(assets[0].symbol, "SPY");
|
|
}
|
|
|
|
#[test]
|
|
fn test_default_agents() {
|
|
let agents = default_agents();
|
|
assert_eq!(agents.len(), 5);
|
|
|
|
// Check strategy diversity
|
|
let strategies: Vec<_> = agents.iter().map(|a| a.strategy).collect();
|
|
assert!(strategies.contains(&StrategyType::Momentum));
|
|
assert!(strategies.contains(&StrategyType::MeanReversion));
|
|
}
|
|
|
|
#[test]
|
|
fn test_sample_tournament() {
|
|
let tournament = sample_tournament();
|
|
assert_eq!(tournament.num_steps, 252);
|
|
assert!(!tournament.assets.is_empty());
|
|
assert!(!tournament.agents.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_serialization() {
|
|
let config = sample_tournament();
|
|
let json = serde_json::to_string(&config).unwrap();
|
|
assert!(json.contains("Strategy Showdown"));
|
|
|
|
let parsed: TournamentConfig = serde_json::from_str(&json).unwrap();
|
|
assert_eq!(parsed.name, config.name);
|
|
}
|
|
}
|