"""Unit tests for strategy comparison module.""" from __future__ import annotations import json import tempfile from datetime import datetime from unittest.mock import Mock, patch import numpy as np import pytest from openclaw.backtest.analyzer import BacktestResult, TradeRecord from openclaw.comparison.comparator import ComparisonResult, StrategyComparator from openclaw.comparison.metrics import ( ComparisonMetrics, MetricFilter, MultiObjectiveOptimizer, RiskLevel, ) from openclaw.comparison.report import ComparisonReport, ReportFormat, generate_quick_summary from openclaw.comparison.statistical_tests import StatisticalTests def create_test_backtest_result( initial_capital: float = 100000.0, final_capital: float = 110000.0, total_trades: int = 10, ) -> BacktestResult: """Create a test BacktestResult with proper structure.""" now = datetime.now() equity_curve = [initial_capital + (final_capital - initial_capital) * i / total_trades for i in range(total_trades + 1)] trades = [] for i in range(total_trades): is_win = i % 2 == 0 # Alternate wins and losses trade = TradeRecord( entry_time=now, exit_time=now, side="long", entry_price=100.0, exit_price=110.0 if is_win else 90.0, quantity=10.0, pnl=100.0 if is_win else -50.0, is_win=is_win, ) trades.append(trade) return BacktestResult( initial_capital=initial_capital, final_capital=final_capital, equity_curve=equity_curve, timestamps=[now] * len(equity_curve), trades=trades, start_time=now, end_time=now, ) class TestRiskLevel: """Tests for RiskLevel enum.""" def test_risk_level_values(self): """Test risk level enum values.""" assert RiskLevel.CONSERVATIVE.value == "conservative" assert RiskLevel.MODERATE.value == "moderate" assert RiskLevel.AGGRESSIVE.value == "aggressive" assert RiskLevel.SPECULATIVE.value == "speculative" class TestComparisonMetrics: """Tests for ComparisonMetrics dataclass.""" def test_default_values(self): """Test default metric values.""" metrics = ComparisonMetrics(strategy_name="test") assert metrics.strategy_name == "test" assert metrics.total_return == 0.0 assert metrics.sharpe_ratio == 0.0 assert metrics.max_drawdown == 0.0 assert metrics.win_rate == 0.0 def test_risk_level_conservative(self): """Test conservative risk level classification.""" metrics = ComparisonMetrics( strategy_name="test", max_drawdown=0.05, volatility=0.10, ) assert metrics.risk_level == RiskLevel.CONSERVATIVE def test_risk_level_moderate(self): """Test moderate risk level classification.""" metrics = ComparisonMetrics( strategy_name="test", max_drawdown=0.10, volatility=0.20, ) assert metrics.risk_level == RiskLevel.MODERATE def test_risk_level_aggressive(self): """Test aggressive risk level classification.""" metrics = ComparisonMetrics( strategy_name="test", max_drawdown=0.20, volatility=0.30, ) assert metrics.risk_level == RiskLevel.AGGRESSIVE def test_risk_level_speculative(self): """Test speculative risk level classification.""" metrics = ComparisonMetrics( strategy_name="test", max_drawdown=0.30, volatility=0.50, ) assert metrics.risk_level == RiskLevel.SPECULATIVE def test_risk_score_calculation(self): """Test risk score calculation.""" metrics = ComparisonMetrics( strategy_name="test", max_drawdown=0.25, # 50 score volatility=0.30, # 60 score var_95=-0.05, # 50 score ) expected = 0.4 * 50 + 0.4 * 60 + 0.2 * 50 # 54.0 assert metrics.risk_score == pytest.approx(expected) def test_risk_score_capped_at_100(self): """Test risk score is capped at 100.""" metrics = ComparisonMetrics( strategy_name="test", max_drawdown=0.60, # Would be 120, capped at 100 volatility=0.60, # Would be 120, capped at 100 ) assert metrics.risk_score <= 100.0 def test_return_risk_ratio(self): """Test return to risk ratio calculation.""" metrics = ComparisonMetrics( strategy_name="test", total_return=0.20, max_drawdown=0.10, volatility=0.15, ) risk_score = metrics.risk_score expected_ratio = 0.20 / risk_score assert metrics.return_risk_ratio == pytest.approx(expected_ratio) def test_return_risk_ratio_infinite(self): """Test return/risk ratio when risk is zero.""" metrics = ComparisonMetrics( strategy_name="test", total_return=0.10, max_drawdown=0.0, volatility=0.0, var_95=0.0, ) assert metrics.return_risk_ratio == float("inf") def test_to_dict(self): """Test conversion to dictionary.""" metrics = ComparisonMetrics( strategy_name="test", total_return=0.10, sharpe_ratio=1.5, ) d = metrics.to_dict() assert d["strategy_name"] == "test" assert d["total_return"] == 0.10 assert d["total_return_pct"] == 10.0 assert d["sharpe_ratio"] == 1.5 assert "risk_level" in d assert "risk_score" in d def test_from_backtest_result(self): """Test creation from BacktestResult.""" result = create_test_backtest_result( initial_capital=100000.0, final_capital=115000.0, total_trades=10, ) metrics = ComparisonMetrics.from_backtest_result("test_strategy", result) assert metrics.strategy_name == "test_strategy" assert metrics.total_return == 0.15 # (115000 - 100000) / 100000 assert metrics.num_trades == 10 def test_from_backtest_result_with_trades(self): """Test creation from BacktestResult with trades.""" result = create_test_backtest_result( initial_capital=100000.0, final_capital=110000.0, total_trades=4, ) metrics = ComparisonMetrics.from_backtest_result("test", result) # Trades: [100, -50, 100, -50] -> average = (100 - 50 + 100 - 50) / 4 = 25.0 assert metrics.avg_trade == 25.0 assert metrics.num_trades == 4 class TestMetricFilter: """Tests for MetricFilter class.""" def test_matches_all_criteria(self): """Test filter matching all criteria.""" filter_criteria = MetricFilter( min_sharpe=1.0, min_return=0.10, max_drawdown=0.20, ) metrics = ComparisonMetrics( strategy_name="test", sharpe_ratio=1.5, total_return=0.15, max_drawdown=0.15, ) assert filter_criteria.matches(metrics) is True def test_fails_min_sharpe(self): """Test filter fails on min_sharpe.""" filter_criteria = MetricFilter(min_sharpe=1.0) metrics = ComparisonMetrics( strategy_name="test", sharpe_ratio=0.5, ) assert filter_criteria.matches(metrics) is False def test_fails_min_return(self): """Test filter fails on min_return.""" filter_criteria = MetricFilter(min_return=0.10) metrics = ComparisonMetrics( strategy_name="test", total_return=0.05, ) assert filter_criteria.matches(metrics) is False def test_fails_max_drawdown(self): """Test filter fails on max_drawdown.""" filter_criteria = MetricFilter(max_drawdown=0.10) metrics = ComparisonMetrics( strategy_name="test", max_drawdown=0.20, ) assert filter_criteria.matches(metrics) is False def test_fails_min_win_rate(self): """Test filter fails on min_win_rate.""" filter_criteria = MetricFilter(min_win_rate=0.50) metrics = ComparisonMetrics( strategy_name="test", win_rate=0.40, ) assert filter_criteria.matches(metrics) is False def test_fails_min_profit_factor(self): """Test filter fails on min_profit_factor.""" filter_criteria = MetricFilter(min_profit_factor=1.5) metrics = ComparisonMetrics( strategy_name="test", profit_factor=1.2, ) assert filter_criteria.matches(metrics) is False def test_fails_risk_levels(self): """Test filter fails on risk_levels.""" filter_criteria = MetricFilter(risk_levels=[RiskLevel.CONSERVATIVE]) metrics = ComparisonMetrics( strategy_name="test", max_drawdown=0.30, # speculative volatility=0.50, ) assert filter_criteria.matches(metrics) is False def test_passes_risk_levels(self): """Test filter passes on risk_levels.""" filter_criteria = MetricFilter( risk_levels=[RiskLevel.CONSERVATIVE, RiskLevel.MODERATE] ) metrics = ComparisonMetrics( strategy_name="test", max_drawdown=0.05, volatility=0.10, ) assert filter_criteria.matches(metrics) is True def test_fails_min_trades(self): """Test filter fails on min_trades.""" filter_criteria = MetricFilter(min_trades=50) metrics = ComparisonMetrics( strategy_name="test", num_trades=30, ) assert filter_criteria.matches(metrics) is False def test_empty_filter_matches_all(self): """Test empty filter matches all metrics.""" filter_criteria = MetricFilter() metrics = ComparisonMetrics(strategy_name="test") assert filter_criteria.matches(metrics) is True class TestMultiObjectiveOptimizer: """Tests for MultiObjectiveOptimizer class.""" def test_default_weights(self): """Test default optimizer weights.""" optimizer = MultiObjectiveOptimizer() assert "return" in optimizer.weights assert "sharpe" in optimizer.weights assert "drawdown" in optimizer.weights assert abs(sum(optimizer.weights.values()) - 1.0) < 0.01 def test_custom_weights(self): """Test custom optimizer weights.""" custom_weights = {"return": 0.5, "sharpe": 0.5} optimizer = MultiObjectiveOptimizer(weights=custom_weights) assert optimizer.weights == custom_weights def test_score_calculation(self): """Test score calculation.""" optimizer = MultiObjectiveOptimizer() metrics = ComparisonMetrics( strategy_name="test", total_return=0.25, # 50 score sharpe_ratio=1.0, # 50 score max_drawdown=0.25, # 50 score win_rate=0.35, # 50 score profit_factor=1.0, # 50 score ) score = optimizer.score(metrics) assert score > 0 assert score <= 100 def test_score_with_all_weights(self): """Test score with all weight types.""" weights = { "return": 0.2, "sharpe": 0.2, "drawdown": 0.2, "win_rate": 0.2, "profit_factor": 0.2, } optimizer = MultiObjectiveOptimizer(weights=weights) metrics = ComparisonMetrics( strategy_name="test", total_return=0.5, sharpe_ratio=2.0, max_drawdown=0.0, win_rate=0.7, profit_factor=2.0, ) score = optimizer.score(metrics) assert score == pytest.approx(100.0, rel=0.01) def test_score_with_calmar(self): """Test score with Calmar weight.""" weights = {"calmar": 1.0} optimizer = MultiObjectiveOptimizer(weights=weights) metrics = ComparisonMetrics( strategy_name="test", calmar_ratio=3.0, ) score = optimizer.score(metrics) assert score == pytest.approx(100.0, rel=0.01) def test_rank_strategies(self): """Test ranking strategies.""" optimizer = MultiObjectiveOptimizer() metrics_list = [ ComparisonMetrics(strategy_name="low", total_return=0.10, sharpe_ratio=0.5), ComparisonMetrics(strategy_name="high", total_return=0.30, sharpe_ratio=1.5), ComparisonMetrics(strategy_name="mid", total_return=0.20, sharpe_ratio=1.0), ] ranked = optimizer.rank(metrics_list) assert len(ranked) == 3 assert ranked[0][0].strategy_name == "high" assert ranked[-1][0].strategy_name == "low" def test_select_best(self): """Test selecting top N strategies.""" optimizer = MultiObjectiveOptimizer() metrics_list = [ ComparisonMetrics(strategy_name="low", total_return=0.10), ComparisonMetrics(strategy_name="high", total_return=0.30), ComparisonMetrics(strategy_name="mid", total_return=0.20), ] best = optimizer.select_best(metrics_list, top_n=2) assert len(best) == 2 assert best[0][0].strategy_name == "high" class TestStatisticalTests: """Tests for StatisticalTests class.""" def test_t_test_equal_means(self): """Test t-test with equal means.""" tests = StatisticalTests() returns1 = np.random.normal(0, 0.01, 100) returns2 = np.random.normal(0, 0.01, 100) t_stat, p_value = tests.t_test(returns1, returns2) assert isinstance(t_stat, float) assert isinstance(p_value, float) assert 0 <= p_value <= 1 def test_t_test_different_means(self): """Test t-test with different means.""" tests = StatisticalTests() returns1 = np.random.normal(0.001, 0.01, 100) returns2 = np.random.normal(-0.001, 0.01, 100) t_stat, p_value = tests.t_test(returns1, returns2) # Should detect significant difference assert p_value < 0.1 or t_stat > 1.0 def test_t_test_empty_arrays(self): """Test t-test with empty arrays.""" tests = StatisticalTests() t_stat, p_value = tests.t_test(np.array([]), np.array([])) assert t_stat == 0.0 assert p_value == 1.0 def test_paired_t_test(self): """Test paired t-test.""" tests = StatisticalTests() returns1 = np.random.normal(0.001, 0.01, 100) returns2 = returns1 + np.random.normal(0, 0.005, 100) t_stat, p_value = tests.paired_t_test(returns1, returns2) assert isinstance(t_stat, float) assert isinstance(p_value, float) def test_sharpe_difference_test(self): """Test Sharpe difference test.""" tests = StatisticalTests() returns1 = np.random.normal(0.001, 0.01, 100) returns2 = np.random.normal(0.0005, 0.01, 100) z_stat, p_value = tests.sharpe_difference_test(returns1, returns2) assert isinstance(z_stat, float) assert isinstance(p_value, float) def test_mann_whitney_u_test(self): """Test Mann-Whitney U test.""" tests = StatisticalTests() returns1 = np.random.normal(0.001, 0.01, 50) returns2 = np.random.normal(0, 0.01, 50) u_stat, p_value = tests.mann_whitney_u_test(returns1, returns2) assert isinstance(u_stat, float) assert isinstance(p_value, float) def test_kolmogorov_smirnov_test(self): """Test KS test.""" tests = StatisticalTests() returns1 = np.random.normal(0, 0.01, 100) returns2 = np.random.normal(0, 0.02, 100) ks_stat, p_value = tests.kolmogorov_smirnov_test(returns1, returns2) assert isinstance(ks_stat, float) assert isinstance(p_value, float) def test_levene_test(self): """Test Levene test for equal variances.""" tests = StatisticalTests() returns1 = np.random.normal(0, 0.01, 100) returns2 = np.random.normal(0, 0.02, 100) w_stat, p_value = tests.levene_test(returns1, returns2) assert isinstance(w_stat, float) assert isinstance(p_value, float) def test_jarque_bera_test_normal(self): """Test Jarque-Bera test with normal distribution.""" tests = StatisticalTests() returns = np.random.normal(0, 0.01, 1000) jb_stat, p_value = tests.jarque_bera_test(returns) assert isinstance(jb_stat, float) assert isinstance(p_value, float) # Normal distribution should have high p-value assert p_value > 0.01 def test_jarque_bera_test_non_normal(self): """Test Jarque-Bera test with non-normal distribution.""" tests = StatisticalTests() returns = np.random.standard_t(3, 1000) * 0.01 # Fat tails jb_stat, p_value = tests.jarque_bera_test(returns) # Fat-tailed distribution should reject normality assert p_value < 0.05 or jb_stat > 10 def test_is_normal_distribution(self): """Test normality check.""" tests = StatisticalTests() normal_returns = np.random.normal(0, 0.01, 1000) assert tests.is_normal_distribution(normal_returns) is True def test_confidence_interval(self): """Test confidence interval calculation.""" tests = StatisticalTests() returns = np.random.normal(0.001, 0.01, 100) lower, upper = tests.calculate_confidence_interval(returns, confidence=0.95) assert lower < upper assert lower < np.mean(returns) < upper def test_omega_ratio(self): """Test Omega ratio calculation.""" tests = StatisticalTests() returns = np.array([0.01, -0.005, 0.02, -0.01, 0.015]) omega = tests.omega_ratio(returns, threshold=0) assert omega > 0 def test_omega_ratio_no_losses(self): """Test Omega ratio with no losses.""" tests = StatisticalTests() returns = np.array([0.01, 0.02, 0.015]) omega = tests.omega_ratio(returns) assert omega == float("inf") def test_calculate_drawdown_statistics(self): """Test drawdown statistics calculation.""" tests = StatisticalTests() equity = np.array([100, 110, 105, 115, 100, 95, 110, 120]) dd_stats = tests.calculate_drawdown_statistics(equity) assert "max_drawdown" in dd_stats assert "avg_drawdown" in dd_stats assert "max_drawdown_duration" in dd_stats assert dd_stats["max_drawdown"] <= 0 def test_compare_drawdowns(self): """Test drawdown comparison.""" tests = StatisticalTests() equity1 = np.array([100, 105, 102, 108, 110]) equity2 = np.array([100, 95, 90, 95, 100]) comparison = tests.compare_drawdowns(equity1, equity2) assert "max_dd_diff" in comparison assert "max_dd_ratio" in comparison def test_calculate_information_ratio(self): """Test Information ratio calculation.""" tests = StatisticalTests() returns = np.random.normal(0.001, 0.01, 100) benchmark = np.random.normal(0.0005, 0.008, 100) ir = tests.calculate_information_ratio(returns, benchmark) assert isinstance(ir, float) def test_calculate_beta(self): """Test beta calculation.""" tests = StatisticalTests() market = np.random.normal(0.001, 0.01, 100) returns = market * 1.2 + np.random.normal(0, 0.005, 100) beta = tests.calculate_beta(returns, market) assert beta > 0 # Beta should be close to 1.2 assert abs(beta - 1.2) < 0.5 def test_calculate_beta_empty(self): """Test beta with empty arrays.""" tests = StatisticalTests() beta = tests.calculate_beta(np.array([]), np.array([])) assert beta == 1.0 def test_calculate_alpha(self): """Test alpha calculation.""" tests = StatisticalTests() market = np.random.normal(0.001, 0.01, 100) returns = market + 0.0005 # Positive alpha alpha = tests.calculate_alpha(returns, market) assert isinstance(alpha, float) class TestComparisonResult: """Tests for ComparisonResult class.""" def test_default_values(self): """Test default values.""" result = ComparisonResult() assert result.metrics == [] assert result.best_strategy == "" assert result.rankings == {} assert result.recommendations == [] def test_get_metric_found(self): """Test getting existing metric.""" metric = ComparisonMetrics(strategy_name="test") result = ComparisonResult(metrics=[metric]) found = result.get_metric("test") assert found is not None assert found.strategy_name == "test" def test_get_metric_not_found(self): """Test getting non-existent metric.""" result = ComparisonResult() found = result.get_metric("nonexistent") assert found is None def test_get_top_strategies(self): """Test getting top N strategies.""" metrics = [ ComparisonMetrics(strategy_name="low", total_return=0.10), ComparisonMetrics(strategy_name="high", total_return=0.30), ComparisonMetrics(strategy_name="mid", total_return=0.20), ] result = ComparisonResult(metrics=metrics) top = result.get_top_strategies(n=2) assert len(top) == 2 assert top[0].strategy_name == "high" def test_to_dict(self): """Test conversion to dictionary.""" metric = ComparisonMetrics(strategy_name="test", total_return=0.15) result = ComparisonResult( metrics=[metric], best_strategy="test", recommendations=["Good strategy"], ) d = result.to_dict() assert d["best_strategy"] == "test" assert len(d["metrics"]) == 1 assert len(d["recommendations"]) == 1 class TestStrategyComparator: """Tests for StrategyComparator class.""" def test_initialization(self): """Test comparator initialization.""" mock_factory = Mock() comparator = StrategyComparator(engine_factory=mock_factory, max_workers=2) assert comparator.engine_factory == mock_factory assert comparator.max_workers == 2 @patch("concurrent.futures.ThreadPoolExecutor") def test_compare_strategies(self, mock_executor_class): """Test strategy comparison.""" # Mock the executor mock_executor = Mock() mock_executor_class.return_value.__enter__ = Mock(return_value=mock_executor) mock_executor_class.return_value.__exit__ = Mock(return_value=False) # Mock future results mock_future = Mock() mock_future.result.return_value = create_test_backtest_result( initial_capital=100000.0, final_capital=110000.0, total_trades=10, ) mock_executor.submit.return_value = mock_future # Create comparator and run comparison mock_engine = Mock() mock_factory = Mock(return_value=mock_engine) comparator = StrategyComparator(engine_factory=mock_factory) strategies = { "strategy1": lambda x: x, } data = np.array([1, 2, 3, 4]) result = comparator.compare(strategies, data) assert isinstance(result, ComparisonResult) assert len(result.metrics) == 1 assert result.metrics[0].strategy_name == "strategy1" def test_calculate_rankings(self): """Test rankings calculation.""" mock_factory = Mock() comparator = StrategyComparator(engine_factory=mock_factory) metrics = [ ComparisonMetrics(strategy_name="high_return", total_return=0.30, sharpe_ratio=1.0), ComparisonMetrics(strategy_name="low_return", total_return=0.10, sharpe_ratio=1.5), ] rankings = comparator._calculate_rankings(metrics) assert "total_return" in rankings assert "sharpe_ratio" in rankings assert rankings["total_return"][0] == "high_return" assert rankings["sharpe_ratio"][0] == "low_return" def test_select_best_strategy(self): """Test best strategy selection.""" mock_factory = Mock() comparator = StrategyComparator(engine_factory=mock_factory) metrics = [ ComparisonMetrics(strategy_name="best", total_return=0.30, sharpe_ratio=1.5), ComparisonMetrics(strategy_name="worst", total_return=0.10, sharpe_ratio=0.5), ] best = comparator._select_best_strategy(metrics) assert best == "best" def test_select_best_strategy_empty(self): """Test best strategy selection with empty list.""" mock_factory = Mock() comparator = StrategyComparator(engine_factory=mock_factory) best = comparator._select_best_strategy([]) assert best == "" def test_generate_recommendations(self): """Test recommendation generation.""" mock_factory = Mock() comparator = StrategyComparator(engine_factory=mock_factory) metrics = [ ComparisonMetrics(strategy_name="high_return", total_return=0.30, sharpe_ratio=1.0), ComparisonMetrics(strategy_name="low_risk", max_drawdown=0.05, volatility=0.08), ] rankings = { "total_return": ["high_return", "low_risk"], } recs = comparator._generate_recommendations(metrics, rankings) assert len(recs) > 0 assert any("high_return" in rec for rec in recs) def test_filter_strategies(self): """Test strategy filtering.""" mock_factory = Mock() comparator = StrategyComparator(engine_factory=mock_factory) metrics = [ ComparisonMetrics(strategy_name="good", sharpe_ratio=1.5), ComparisonMetrics(strategy_name="bad", sharpe_ratio=0.5), ] filter_criteria = MetricFilter(min_sharpe=1.0) filtered = comparator.filter_strategies(metrics, filter_criteria) assert len(filtered) == 1 assert filtered[0].strategy_name == "good" def test_create_empty_result(self): """Test creating empty result for failed backtest.""" mock_factory = Mock() comparator = StrategyComparator(engine_factory=mock_factory) result = comparator._create_empty_result(initial_capital=100000.0) assert result.initial_capital == 100000.0 assert result.final_capital == 100000.0 assert len(result.equity_curve) == 1 assert result.equity_curve[0] == 100000.0 assert result.trades == [] class TestComparisonReport: """Tests for ComparisonReport class.""" def test_default_initialization(self): """Test default initialization.""" report = ComparisonReport() assert report.title == "Strategy Comparison Report" assert report.include_charts is True assert report.format == ReportFormat.MARKDOWN def test_generate_markdown(self): """Test Markdown report generation.""" report = ComparisonReport() metric = ComparisonMetrics( strategy_name="test", total_return=0.15, sharpe_ratio=1.2, ) result = ComparisonResult( metrics=[metric], best_strategy="test", rankings={"total_return": ["test"]}, recommendations=["Good strategy"], ) content = report.generate(result, format=ReportFormat.MARKDOWN) assert "# Strategy Comparison Report" in content assert "test" in content assert "15.00%" in content or "0.15" in content def test_generate_json(self): """Test JSON report generation.""" report = ComparisonReport() metric = ComparisonMetrics(strategy_name="test", total_return=0.15) result = ComparisonResult(metrics=[metric], best_strategy="test") content = report.generate(result, format=ReportFormat.JSON) data = json.loads(content) assert data["title"] == "Strategy Comparison Report" assert data["best_strategy"] == "test" def test_generate_html(self): """Test HTML report generation.""" report = ComparisonReport() metric = ComparisonMetrics(strategy_name="test", total_return=0.15) result = ComparisonResult(metrics=[metric], best_strategy="test") content = report.generate(result, format=ReportFormat.HTML) assert "" in content.lower() assert "test" in content def test_generate_csv(self): """Test CSV report generation.""" report = ComparisonReport() metric = ComparisonMetrics(strategy_name="test", total_return=0.15) result = ComparisonResult(metrics=[metric], best_strategy="test") content = report.generate(result, format=ReportFormat.CSV) assert "Strategy," in content assert "test" in content def test_save_report(self): """Test saving report to file.""" report = ComparisonReport() metric = ComparisonMetrics(strategy_name="test", total_return=0.15) result = ComparisonResult(metrics=[metric], best_strategy="test") with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f: filepath = f.name report.save(result, filepath) with open(filepath, 'r') as f: content = f.read() assert "Strategy Comparison Report" in content def test_infer_format_from_path(self): """Test format inference from file path.""" report = ComparisonReport() assert report._infer_format_from_path("test.json") == ReportFormat.JSON assert report._infer_format_from_path("test.html") == ReportFormat.HTML assert report._infer_format_from_path("test.csv") == ReportFormat.CSV assert report._infer_format_from_path("test.md") == ReportFormat.MARKDOWN assert report._infer_format_from_path("test.txt") == ReportFormat.MARKDOWN class TestQuickSummary: """Tests for quick summary function.""" def test_generate_quick_summary(self): """Test quick summary generation.""" metrics = [ ComparisonMetrics(strategy_name="best", total_return=0.30, sharpe_ratio=1.5), ComparisonMetrics(strategy_name="worst", total_return=0.10, sharpe_ratio=0.5), ] result = ComparisonResult( metrics=metrics, best_strategy="best", ) summary = generate_quick_summary(result) assert "Strategy Comparison Summary" in summary assert "Total Strategies: 2" in summary assert "Best Strategy: best" in summary assert "best" in summary def test_quick_summary_risk_analysis(self): """Test risk analysis in quick summary.""" metrics = [ ComparisonMetrics(strategy_name="conservative", max_drawdown=0.05, volatility=0.08), ComparisonMetrics(strategy_name="speculative", max_drawdown=0.30, volatility=0.50), ] result = ComparisonResult(metrics=metrics) summary = generate_quick_summary(result) assert "Risk Analysis:" in summary assert "Conservative:" in summary assert "Speculative:" in summary class TestIntegration: """Integration tests for the comparison module.""" def test_full_comparison_workflow(self): """Test full comparison workflow.""" # Create comparison result metrics = [ ComparisonMetrics( strategy_name="momentum", total_return=0.25, sharpe_ratio=1.2, max_drawdown=0.15, win_rate=0.55, profit_factor=1.5, num_trades=100, ), ComparisonMetrics( strategy_name="mean_reversion", total_return=0.15, sharpe_ratio=1.0, max_drawdown=0.10, win_rate=0.60, profit_factor=1.3, num_trades=150, ), ] result = ComparisonResult( metrics=metrics, best_strategy="momentum", rankings={ "total_return": ["momentum", "mean_reversion"], "sharpe_ratio": ["momentum", "mean_reversion"], }, recommendations=[ "Highest return: momentum", "Lowest risk: mean_reversion", ], ) # Generate report report = ComparisonReport() markdown = report.generate(result, format=ReportFormat.MARKDOWN) json_report = report.generate(result, format=ReportFormat.JSON) assert "momentum" in markdown assert "mean_reversion" in markdown data = json.loads(json_report) assert len(data["metrics"]) == 2 def test_filter_and_optimize_integration(self): """Test filter and optimizer integration.""" metrics = [ ComparisonMetrics(strategy_name="good", sharpe_ratio=1.5, total_return=0.20), ComparisonMetrics(strategy_name="bad", sharpe_ratio=0.5, total_return=0.10), ComparisonMetrics(strategy_name="excellent", sharpe_ratio=2.0, total_return=0.30), ] # Filter strategies filter_criteria = MetricFilter(min_sharpe=1.0) filtered = [m for m in metrics if filter_criteria.matches(m)] assert len(filtered) == 2 # Rank filtered strategies optimizer = MultiObjectiveOptimizer() ranked = optimizer.rank(filtered) assert ranked[0][0].strategy_name == "excellent" def test_statistical_tests_integration(self): """Test statistical tests integration.""" tests = StatisticalTests() # Generate sample returns returns1 = np.random.normal(0.001, 0.01, 100) returns2 = np.random.normal(0.0005, 0.012, 100) # Run tests t_stat, p_value = tests.t_test(returns1, returns2) sharpe_diff, sharpe_p = tests.sharpe_difference_test(returns1, returns2) lower, upper = tests.calculate_confidence_interval(returns1) # All results should be valid assert isinstance(t_stat, float) assert isinstance(sharpe_diff, float) assert lower < upper