//! Repository traits for clean database abstraction in backtesting service use anyhow::Result; use async_trait::async_trait; use chrono::{DateTime, Utc}; use crate::foxhunt::tli::BacktestStatus; use crate::performance::PerformanceMetrics; use crate::storage::BacktestSummary; use crate::strategy_engine::BacktestTrade; /// Repository trait for market data operations /// /// This trait abstracts market data retrieval for backtesting, /// eliminating direct database coupling from business logic. #[async_trait] pub trait MarketDataRepository: Send + Sync { /// Load historical market data for backtesting /// /// # Arguments /// * `symbols` - List of symbols to load data for /// /// * `start_time` - Start timestamp in nanoseconds /// * `end_time` - End timestamp in nanoseconds /// /// # Returns /// /// Vector of market data events sorted by timestamp async fn load_historical_data( &self, symbols: &[String], start_time: i64, end_time: i64, ) -> Result>; } /// Repository trait for trading and backtest result operations /// /// This trait handles persistence and retrieval of backtest results, /// trading history, and performance metrics. #[async_trait] pub trait TradingRepository: Send + Sync { /// Save backtest results to storage async fn save_backtest_results( &self, backtest_id: &str, trades: &[BacktestTrade], metrics: &PerformanceMetrics, ) -> Result<()>; /// Load backtest results from storage async fn load_backtest_results( &self, backtest_id: &str, ) -> Result<(Vec, PerformanceMetrics)>; /// List historical backtests async fn list_backtests( &self, limit: u32, offset: u32, strategy_name: Option, status_filter: Option, ) -> Result>; } /// Repository trait for news and sentiment data /// /// This trait provides access to news events and sentiment data /// that can influence trading strategies. #[async_trait] pub trait NewsRepository: Send + Sync { /// Load news events for given symbols and time range async fn load_news_events( &self, symbols: &[String], start_time: DateTime, end_time: DateTime, ) -> Result>; } /// Combined repository trait for dependency injection /// /// This trait combines all repository interfaces to simplify /// dependency injection in the service layer. #[async_trait] pub trait BacktestingRepositories: Send + Sync { /// Get market data repository fn market_data(&self) -> &dyn MarketDataRepository; /// Get trading repository fn trading(&self) -> &dyn TradingRepository; /// Get news repository fn news(&self) -> &dyn NewsRepository; } /// Default implementation that provides all repositories pub struct DefaultRepositories { /// Market data repository for historical data pub market_data: Box, /// Trading repository for order and execution data pub trading: Box, /// News repository for market news events pub news: Box, } #[async_trait] impl BacktestingRepositories for DefaultRepositories { fn market_data(&self) -> &dyn MarketDataRepository { self.market_data.as_ref() } fn trading(&self) -> &dyn TradingRepository { self.trading.as_ref() } fn news(&self) -> &dyn NewsRepository { self.news.as_ref() } } #[cfg(any(test, feature = "test-utils"))] impl DefaultRepositories { /// Create a mock instance for testing /// /// This method is only available in test builds and creates /// in-memory mock repositories for unit testing. pub fn mock() -> Self { use std::sync::Arc; use tokio::sync::RwLock; use std::collections::HashMap; // Mock market data repository struct MockMarketData { data: Arc>>, } #[async_trait] impl MarketDataRepository for MockMarketData { async fn load_historical_data( &self, _symbols: &[String], _start_time: i64, _end_time: i64, ) -> Result> { Ok(self.data.read().await.clone()) } } // Mock trading repository struct MockTrading { trades: Arc>>>, metrics: Arc>>, backtests: Arc>>, } #[async_trait] impl TradingRepository for MockTrading { async fn save_backtest_results( &self, backtest_id: &str, trades: &[BacktestTrade], metrics: &PerformanceMetrics, ) -> Result<()> { self.trades.write().await.insert(backtest_id.to_string(), trades.to_vec()); self.metrics.write().await.insert(backtest_id.to_string(), metrics.clone()); Ok(()) } async fn load_backtest_results( &self, backtest_id: &str, ) -> Result<(Vec, PerformanceMetrics)> { let trades = self.trades.read().await .get(backtest_id) .cloned() .unwrap_or_default(); let metrics = self.metrics.read().await .get(backtest_id) .cloned() .unwrap_or_default(); Ok((trades, metrics)) } async fn list_backtests( &self, limit: u32, offset: u32, strategy_name: Option, status_filter: Option, ) -> Result> { let backtests = self.backtests.read().await; let filtered: Vec = backtests .iter() .filter(|bt| { let name_match = strategy_name.as_ref() .map(|n| bt.strategy_name == *n) .unwrap_or(true); let status_match = status_filter.map(|s| bt.status == s).unwrap_or(true); name_match && status_match }) .skip(offset as usize) .take(limit as usize) .cloned() .collect(); Ok(filtered) } } // Mock news repository struct MockNews { events: Arc>>, } #[async_trait] impl NewsRepository for MockNews { async fn load_news_events( &self, _symbols: &[String], _start_time: DateTime, _end_time: DateTime, ) -> Result> { Ok(self.events.read().await.clone()) } } Self { market_data: Box::new(MockMarketData { data: Arc::new(RwLock::new(Vec::new())), }), trading: Box::new(MockTrading { trades: Arc::new(RwLock::new(HashMap::new())), metrics: Arc::new(RwLock::new(HashMap::new())), backtests: Arc::new(RwLock::new(Vec::new())), }), news: Box::new(MockNews { events: Arc::new(RwLock::new(Vec::new())), }), } } }