//! Standalone Backtesting Service Binary //! //! This binary provides a standalone gRPC server for the Backtesting Service, //! providing strategy testing, performance analysis, and results management. //! //! The service listens on port 50052 and provides comprehensive backtesting functionality. use std::net::SocketAddr; use std::sync::Arc; use tokio::signal; use tonic::transport::Server; use tracing::{error, info, Level}; use tracing_subscriber::FmtSubscriber; use core::config::ConfigManager; use core::types::prelude::*; // Import proto definitions and service implementations use tli::proto::trading::backtesting_service_server::BacktestingServiceServer; #[tokio::main] async fn main() -> Result<(), Box> { // Initialize tracing let subscriber = FmtSubscriber::builder() .with_max_level(Level::INFO) .finish(); tracing::subscriber::set_global_default(subscriber).expect("Setting default subscriber failed"); info!("Starting Foxhunt Backtesting Service..."); // Load configuration let config_manager = ConfigManager::load_from_environment() .map_err(|e| format!("Failed to load configuration: {}", e))?; let config_manager = Arc::new(config_manager); // Create backtesting service implementation let backtesting_service = BacktestingServiceImpl::new(Arc::clone(&config_manager)).await?; // Server address let addr: SocketAddr = "0.0.0.0:50052".parse()?; info!("Backtesting Service listening on {}", addr); // Setup graceful shutdown let shutdown_signal = async { signal::ctrl_c() .await .expect("Failed to install CTRL+C signal handler"); info!("Received shutdown signal, stopping Backtesting Service..."); }; // Build and start the server let server = Server::builder() .add_service(BacktestingServiceServer::new(backtesting_service)) .serve_with_shutdown(addr, shutdown_signal); info!("Backtesting Service started successfully on {}", addr); if let Err(e) = server.await { error!("Backtesting Service failed: {}", e); return Err(e.into()); } info!("Backtesting Service stopped gracefully"); Ok(()) } /// Backtesting Service Implementation /// Provides comprehensive strategy testing, performance analysis, and results management pub struct BacktestingServiceImpl { config_manager: Arc, // Add additional components as needed } impl BacktestingServiceImpl { pub async fn new( config_manager: Arc, ) -> Result> { info!("Initializing Backtesting Service components..."); Ok(BacktestingServiceImpl { config_manager }) } } // Implement the gRPC service trait #[tonic::async_trait] impl tli::proto::trading::backtesting_service_server::BacktestingService for BacktestingServiceImpl { async fn start_backtest( &self, request: tonic::Request, ) -> Result, tonic::Status> { let req = request.into_inner(); info!("Starting backtest for strategy: {}", req.strategy_name); info!(" Symbols: {:?}", req.symbols); info!(" Initial Capital: ${:.2}", req.initial_capital); info!( " Time Range: {} to {}", chrono::DateTime::from_timestamp_nanos(req.start_date_unix_nanos).unwrap_or_else(chrono::Utc::now), chrono::DateTime::from_timestamp_nanos(req.end_date_unix_nanos).unwrap_or_else(chrono::Utc::now) ); // TODO: Implement actual backtest execution let backtest_id = format!("BACKTEST_{}", uuid::Uuid::new_v4()); let response = tli::proto::trading::StartBacktestResponse { success: true, backtest_id: backtest_id.clone(), message: format!("Backtest {} started successfully", backtest_id), estimated_duration_seconds: 300, // 5 minutes estimate }; Ok(tonic::Response::new(response)) } async fn get_backtest_status( &self, request: tonic::Request, ) -> Result, tonic::Status> { let req = request.into_inner(); info!("Getting backtest status for: {}", req.backtest_id); // TODO: Implement actual backtest status lookup let response = tli::proto::trading::GetBacktestStatusResponse { backtest_id: req.backtest_id, status: tli::proto::trading::BacktestStatus::Running.into(), progress_percentage: 65.0, current_date: "2024-01-15".to_string(), trades_executed: 195, current_pnl: 1250.0, started_at_unix_nanos: chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0), completed_at_unix_nanos: None, error_message: None, }; Ok(tonic::Response::new(response)) } async fn get_backtest_results( &self, request: tonic::Request, ) -> Result, tonic::Status> { let req = request.into_inner(); info!("Getting backtest results for: {}", req.backtest_id); // TODO: Implement actual backtest results retrieval let metrics = tli::proto::trading::BacktestMetrics { total_return: 0.125, // 12.5% return annualized_return: 0.18, // 18% annualized sharpe_ratio: 1.45, sortino_ratio: 1.35, max_drawdown: 0.08, // 8% max drawdown (positive value) volatility: 0.145, // 14.5% volatility win_rate: 0.62, // 62% win rate profit_factor: 1.8, total_trades: 1247, winning_trades: 773, losing_trades: 474, avg_win: 150.0, avg_loss: -85.0, largest_win: 450.0, largest_loss: -320.0, calmar_ratio: 1.25, backtest_duration_nanos: chrono::Duration::days(30).num_nanoseconds().unwrap_or(0), }; let response = tli::proto::trading::GetBacktestResultsResponse { backtest_id: req.backtest_id, metrics: Some(metrics), trades: vec![], // Empty for now, TODO: implement actual trades equity_curve: vec![], // Empty for now, TODO: implement actual curve drawdown_periods: vec![], // Empty for now, TODO: implement actual periods }; Ok(tonic::Response::new(response)) } async fn list_backtests( &self, _request: tonic::Request, ) -> Result, tonic::Status> { info!("Listing historical backtests"); // TODO: Implement actual backtest listing from storage let backtests = vec![ tli::proto::trading::BacktestSummary { backtest_id: "BACKTEST_001".to_string(), strategy_name: "MeanReversion_v1".to_string(), symbols: vec!["AAPL".to_string(), "MSFT".to_string()], status: tli::proto::trading::BacktestStatus::Completed.into(), total_return: 0.085, sharpe_ratio: 1.23, max_drawdown: 0.06, // Positive value created_at_unix_nanos: (chrono::Utc::now() - chrono::Duration::days(2)) .timestamp_nanos_opt() .unwrap_or(0), start_date_unix_nanos: (chrono::Utc::now() - chrono::Duration::days(30)) .timestamp_nanos_opt() .unwrap_or(0), end_date_unix_nanos: (chrono::Utc::now() - chrono::Duration::days(1)) .timestamp_nanos_opt() .unwrap_or(0), description: "Mean reversion strategy test on tech stocks".to_string(), }, tli::proto::trading::BacktestSummary { backtest_id: "BACKTEST_002".to_string(), strategy_name: "TrendFollowing_v2".to_string(), symbols: vec!["SPY".to_string(), "QQQ".to_string()], status: tli::proto::trading::BacktestStatus::Completed.into(), total_return: 0.142, sharpe_ratio: 1.67, max_drawdown: 0.04, // Positive value created_at_unix_nanos: (chrono::Utc::now() - chrono::Duration::days(32)) .timestamp_nanos_opt() .unwrap_or(0), start_date_unix_nanos: (chrono::Utc::now() - chrono::Duration::days(60)) .timestamp_nanos_opt() .unwrap_or(0), end_date_unix_nanos: (chrono::Utc::now() - chrono::Duration::days(31)) .timestamp_nanos_opt() .unwrap_or(0), description: "Trend following strategy test on ETFs".to_string(), }, ]; let response = tli::proto::trading::ListBacktestsResponse { backtests, total_count: 2, }; Ok(tonic::Response::new(response)) } // Stream method for backtest progress type SubscribeBacktestProgressStream = tokio_stream::wrappers::ReceiverStream< Result, >; async fn subscribe_backtest_progress( &self, request: tonic::Request, ) -> Result, tonic::Status> { let req = request.into_inner(); info!("Subscribing to backtest progress for: {}", req.backtest_id); let (tx, rx) = tokio::sync::mpsc::channel(100); let backtest_id = req.backtest_id.clone(); // TODO: Implement actual backtest progress streaming tokio::spawn(async move { let mut progress = 0.0; let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(2)); while progress < 100.0 { interval.tick().await; progress += 5.0; // Simulate 5% progress every 2 seconds let event = tli::proto::trading::BacktestProgressEvent { backtest_id: backtest_id.clone(), progress_percentage: progress, current_date: "2024-01-15".to_string(), trades_executed: (progress * 12.47) as u64, // Simulate trade count current_pnl: progress * 25.0, // Simulate P&L growth current_equity: 100000.0 + (progress * 125.0), // Simulate portfolio growth status: if progress >= 100.0 { tli::proto::trading::BacktestStatus::Completed.into() } else { tli::proto::trading::BacktestStatus::Running.into() }, timestamp_unix_nanos: chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0), }; if tx.send(Ok(event)).await.is_err() { break; } if progress >= 100.0 { break; } } }); Ok(tonic::Response::new( tokio_stream::wrappers::ReceiverStream::new(rx), )) } async fn stop_backtest( &self, request: tonic::Request, ) -> Result, tonic::Status> { let req = request.into_inner(); info!("Stopping backtest: {}", req.backtest_id); // TODO: Implement actual backtest stopping logic let response = tli::proto::trading::StopBacktestResponse { success: true, message: format!("Backtest {} stopped successfully", req.backtest_id), results_saved: req.save_partial_results, }; Ok(tonic::Response::new(response)) } }