| """ |
| Unit tests for notebook-based portfolio optimization methods. |
| Tests Hybrid, QUBO-SA, VQE, and classical optimizers via run_optimization. |
| """ |
| import pytest |
| import numpy as np |
|
|
| from services.portfolio_optimizer import run_optimization, OBJECTIVES |
| from core.quantum_inspired.quantum_annealing import ( |
| QuantumAnnealingOptimizer, |
| run_quantum_annealing_comparison, |
| QAConfig, |
| ) |
|
|
|
|
| class TestNotebookOptimizers: |
|
|
| def setup_method(self): |
| """Set up test fixtures.""" |
| self.n_assets = 10 |
| np.random.seed(42) |
| self.returns = np.random.randn(self.n_assets) * 0.1 + 0.05 |
| self.covariance = self._generate_valid_covariance(self.n_assets) |
|
|
| def _generate_valid_covariance(self, n): |
| """Generate a valid positive semi-definite covariance matrix.""" |
| A = np.random.randn(n, n) |
| return np.dot(A.T, A) / n |
|
|
| @pytest.mark.parametrize("objective", ["hybrid", "qubo_sa", "vqe", "markowitz", "min_variance", "hrp"]) |
| def test_objective_produces_valid_result(self, objective): |
| """Test each objective produces valid portfolio.""" |
| result = run_optimization(self.returns, self.covariance, objective=objective) |
| assert result.weights is not None |
| assert len(result.weights) == self.n_assets |
| assert np.abs(np.sum(result.weights) - 1.0) < 1e-5 |
| assert np.all(result.weights >= -1e-6) |
| assert np.isfinite(result.sharpe_ratio) |
| assert result.expected_return is not None |
| assert result.volatility > 0 |
|
|
| def test_quantum_annealing_optimizer(self): |
| """Test quantum annealing optimizer (legacy, still in core).""" |
| qa_optimizer = QuantumAnnealingOptimizer() |
| result = qa_optimizer.optimize(self.returns, self.covariance) |
| assert result['weights'] is not None |
| assert len(result['weights']) == self.n_assets |
| assert np.abs(np.sum(result['weights']) - 1.0) < 1e-6 |
| assert np.all(result['weights'] >= 0) |
| assert np.isfinite(result['sharpe_ratio']) |
|
|
| def test_quantum_annealing_comparison(self): |
| """Test quantum annealing vs classical comparison.""" |
| comparison = run_quantum_annealing_comparison(self.returns, self.covariance) |
| assert 'quantum_annealing' in comparison |
| assert 'classical' in comparison |
| assert np.isfinite(comparison['quantum_annealing']['sharpe_ratio']) |
| assert np.isfinite(comparison['classical']['sharpe_ratio']) |
|
|
| def test_equal_weight_baseline(self): |
| """Test equal weight baseline.""" |
| result = run_optimization(self.returns, self.covariance, objective='equal_weight') |
| expected = np.ones(self.n_assets) / self.n_assets |
| np.testing.assert_array_almost_equal(result.weights, expected) |
|
|
| def test_large_portfolio(self): |
| """Test performance on larger portfolios.""" |
| large_n = 50 |
| large_returns = np.random.randn(large_n) * 0.1 + 0.05 |
| A = np.random.randn(large_n, large_n) |
| large_covariance = np.dot(A.T, A) / large_n |
| result = run_optimization(large_returns, large_covariance, objective='hybrid') |
| assert np.isfinite(result.sharpe_ratio) |
| assert abs(np.sum(result.weights) - 1.0) < 1e-6 |
| assert len(result.weights) == large_n |
|
|
| def test_qa_configurations(self): |
| """Test different quantum annealing configurations.""" |
| for config in [ |
| QAConfig(initial_temperature=50.0, final_temperature=0.05), |
| QAConfig(quantum_fluctuation_strength=0.05), |
| ]: |
| qa_optimizer = QuantumAnnealingOptimizer(config) |
| result = qa_optimizer.optimize(self.returns, self.covariance) |
| assert len(result['weights']) == self.n_assets |
| assert abs(np.sum(result['weights']) - 1.0) < 1e-6 |
|
|
|
|
| class TestIntegration: |
|
|
| def setup_method(self): |
| """Set up test fixtures.""" |
| self.n_assets = 8 |
| np.random.seed(123) |
| self.returns = np.random.randn(self.n_assets) * 0.1 + 0.05 |
| A = np.random.randn(self.n_assets, self.n_assets) |
| self.covariance = np.dot(A.T, A) / self.n_assets |
|
|
| def test_all_methods_produce_valid_results(self): |
| """Test complete workflow with all available methods.""" |
| objectives = ['hybrid', 'qubo_sa', 'vqe', 'markowitz', 'min_variance', 'hrp', 'equal_weight'] |
| for obj in objectives: |
| result = run_optimization(self.returns, self.covariance, objective=obj) |
| assert np.isfinite(result.sharpe_ratio) |
| assert abs(np.sum(result.weights) - 1.0) < 1e-6 |
|
|
| def test_objectives_config(self): |
| """Test OBJECTIVES contains expected keys.""" |
| expected = {'hybrid', 'qubo_sa', 'vqe', 'markowitz', 'min_variance', 'hrp', 'equal_weight'} |
| assert expected.issubset(set(OBJECTIVES.keys())) |
|
|