forked from QuantConnect/Lean
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
MaximumSharpeRatio and MinVariance optimizer tests
added boundary constraints test Added MinimumVariancePortfolioOptimizer test class Added more test cases for max sharpe ratio optimizer Changed MaxSharpeRatio optimizer test cases inputs to 2dp Added min variance test cases
- Loading branch information
Showing
3 changed files
with
381 additions
and
0 deletions.
There are no files selected for viewing
191 changes: 191 additions & 0 deletions
191
Tests/Algorithm/Framework/Portfolio/MaximumSharpeRatioPortfolioOptimizerTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
using Accord.Statistics; | ||
using NUnit.Framework; | ||
using QLNet; | ||
using QuantConnect.Algorithm.Framework.Portfolio; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio | ||
{ | ||
[TestFixture] | ||
public class MaximumSharpeRatioPortfolioOptimizerTests | ||
{ | ||
private Dictionary<int, double[,]> _historicalReturns = new(); | ||
private Dictionary<int, double[]> _expectedReturns = new(); | ||
private Dictionary<int, double[,]> _covariances = new(); | ||
private Dictionary<int, double[]> _expectedResults = new(); | ||
|
||
[OneTimeSetUp] | ||
public void Setup() | ||
{ | ||
double[,] historicalReturns1 = new double[,] { { 0.02, -0.02, 0.28 }, { -0.50, -0.29, -0.13 }, { 0.81, 0.29, 0.31 }, { -0.03, -0.00, 0.01 } }; | ||
double[,] historicalReturns2 = new double[,] { { 0.10, 0.20, 0.4 }, { 0.12, 0.25, 0.4 }, { 0.11, 0.22, 0.4 } }; | ||
double[,] historicalReturns3 = new double[,] { { -0.19, 0.50, 0.45 }, { -0.62, -0.65, 0.07 }, { -0.14, 1.02, 0.01 }, { 0.00, -0.03, 0.01 } }; | ||
double[,] historicalReturns4 = new double[,] { { 0.46, 0.28, 0.58, 0.26, 0.14 }, { 0.52, 0.31, 0.43, 7.43, -0.00 }, { 0.13, 0.65, 0.52, 0.50, -0.08 }, { -0.41, -0.39, -0.28, -0.65, -0.20 }, { 0.77, 0.58, 0.58, 1.02, 0.03 }, { -0.03, -0.01, -0.01, -0.03, 0.07 } }; | ||
double[,] historicalReturns5 = new double[,] { { -0.50, -0.13 }, { 0.81, 0.31 }, { -0.02, 0.01 } }; | ||
double[,] historicalReturns6 = new double[,] { { 0.31, 0.25, 0.43 }, { 0.65, 0.60, 0.52 }, { -0.39, -0.22, -0.28 }, { 0.58, 0.13, 0.58 }, { -0.01, -0.00, -0.01 } }; | ||
double[,] historicalReturns7 = new double[,] { { 0.13, 0.65, 1.25 }, { -0.41, -0.39, -0.50 }, { 0.77, 0.58, 2.39 }, { -0.03, -0.01, 0.04 } }; | ||
double[,] historicalReturns8 = new double[,] { { 0.31, 0.43, 1.22, 0.03 }, { 0.65, 0.52, 1.25, 0.67 }, { -0.39, -0.28, -0.50, -0.10 }, { 0.58, 0.58, 2.39, -0.41 }, { -0.01, -0.01, 0.04, 0.03 } }; | ||
|
||
|
||
double[] expectedReturns1 = new double[] { 0.08, -0.01, 0.12 }; | ||
double[] expectedReturns2 = new double[] { 0.11, 0.23, 0.4 }; | ||
double[] expectedReturns3 = new double[] { -0.24, 0.21, 0.14 }; | ||
double[] expectedReturns4 = null; | ||
double[] expectedReturns5 = new double[] { 0.10, 0.06 }; | ||
double[] expectedReturns6 = new double[] { 0.23, 0.15, 0.25 }; | ||
double[] expectedReturns7 = null; | ||
double[] expectedReturns8 = new double[] { 0.23, 0.25, 0.88, 0.04 }; | ||
|
||
double[,] covariance1 = new double[,] { { 0.29, 0.13, 0.10 }, { 0.13, 0.06, 0.04 }, { 0.10, 0.04, 0.05 } }; | ||
double[,] covariance2 = null; | ||
double[,] covariance3 = new double[,] { { 0.07, 0.12, -0.00 }, { 0.12, 0.51, 0.03 }, { -0.00, 0.03, 0.04 } }; | ||
double[,] covariance4 = null; | ||
double[,] covariance5 = new double[,] { { 0.44, 0.15 }, { 0.15, 0.05 } }; | ||
double[,] covariance6 = new double[,] { { 0.19, 0.11, 0.16 }, { 0.11, 0.09, 0.09 }, { 0.16, 0.09, 0.14 } }; | ||
double[,] covariance7 = new double[,] { { 0.24, 0.20, 0.61 }, { 0.20, 0.25, 0.58 }, { 0.61, 0.58, 1.67 } }; | ||
double[,] covariance8 = new double[,] { { 0.19, 0.16, 0.44, 0.05 }, { 0.16, 0.14, 0.40, 0.02 }, { 0.44, 0.40, 1.29, -0.06 }, { 0.05, 0.02, -0.06, 0.15 } }; | ||
|
||
|
||
double[] expectedResult1 = new double[] { -0.562396, 0.608942, 0.953453}; | ||
double[] expectedResult2 = new double[] { 0.686025, -0.269589, 0.583023}; | ||
double[] expectedResult3 = new double[] { 0.26394, -0.043374, 0.779434 }; | ||
double[] expectedResult4 = new double[] { -0.223905, 0.401036, 1, 0.065329, -0.24246 }; | ||
double[] expectedResult5 = new double[] { 0.5, 0.5 }; | ||
double[] expectedResult6 = new double[] { -0.5, 0.5, 1 }; | ||
double[] expectedResult7 = new double[] { -0.242647, 1, 0.242647 }; | ||
double[] expectedResult8 = new double[] { -1, 0.922902, 0.364512, 0.712585 }; | ||
|
||
_historicalReturns.Add(1, historicalReturns1); | ||
_historicalReturns.Add(2, historicalReturns2); | ||
_historicalReturns.Add(3, historicalReturns3); | ||
_historicalReturns.Add(4, historicalReturns4); | ||
_historicalReturns.Add(5, historicalReturns5); | ||
_historicalReturns.Add(6, historicalReturns6); | ||
_historicalReturns.Add(7, historicalReturns7); | ||
_historicalReturns.Add(8, historicalReturns8); | ||
|
||
_expectedReturns.Add(1, expectedReturns1); | ||
_expectedReturns.Add(2, expectedReturns2); | ||
_expectedReturns.Add(3, expectedReturns3); | ||
_expectedReturns.Add(4, expectedReturns4); | ||
_expectedReturns.Add(5, expectedReturns5); | ||
_expectedReturns.Add(6, expectedReturns6); | ||
_expectedReturns.Add(7, expectedReturns7); | ||
_expectedReturns.Add(8, expectedReturns8); | ||
|
||
_covariances.Add(1, covariance1); | ||
_covariances.Add(2, covariance2); | ||
_covariances.Add(3, covariance3); | ||
_covariances.Add(4, covariance4); | ||
_covariances.Add(5, covariance5); | ||
_covariances.Add(6, covariance6); | ||
_covariances.Add(7, covariance7); | ||
_covariances.Add(8, covariance8); | ||
|
||
_expectedResults.Add(1, expectedResult1); | ||
_expectedResults.Add(2, expectedResult2); | ||
_expectedResults.Add(3, expectedResult3); | ||
_expectedResults.Add(4, expectedResult4); | ||
_expectedResults.Add(5, expectedResult5); | ||
_expectedResults.Add(6, expectedResult6); | ||
_expectedResults.Add(7, expectedResult7); | ||
_expectedResults.Add(8, expectedResult8); | ||
} | ||
|
||
[TestCase(1)] | ||
[TestCase(2)] | ||
[TestCase(3)] | ||
[TestCase(4)] | ||
[TestCase(5)] | ||
[TestCase(6)] | ||
[TestCase(7)] | ||
[TestCase(8)] | ||
public void TestOptimizeWeightings(int testCaseNumber) | ||
{ | ||
var testOptimizer = new MaximumSharpeRatioPortfolioOptimizer(); | ||
|
||
var result = testOptimizer.Optimize( | ||
_historicalReturns[testCaseNumber], | ||
_expectedReturns[testCaseNumber], | ||
_covariances[testCaseNumber]); | ||
|
||
Assert.AreEqual(_expectedResults[testCaseNumber], result.Select(x => Math.Round(x, 6))); | ||
} | ||
|
||
[TestCase(1)] | ||
public void TestOptimizeWeightingsSpecifyingLowerBoundAndRiskFreeRate(int testCaseNumber) | ||
{ | ||
var testOptimizer = new MaximumSharpeRatioPortfolioOptimizer(lower: 0, riskFreeRate: 0.04); | ||
double[] expectedResult = new double[] { 0, 0.44898, 0.55102 }; | ||
|
||
var result = testOptimizer.Optimize(_historicalReturns[testCaseNumber]); | ||
|
||
Assert.AreEqual(expectedResult, result.Select(x => Math.Round(x, 6))); | ||
} | ||
|
||
[Test] | ||
public void SingleSecurityPortfolioReturnsNaN() | ||
{ | ||
var testOptimizer = new MaximumSharpeRatioPortfolioOptimizer(); | ||
var historicalReturns = new double[,] { { -0.1 } }; | ||
var expectedReturns = new double[] { -0.1 }; | ||
|
||
var expectedResult = new double[] { double.NaN }; | ||
|
||
var result = testOptimizer.Optimize(historicalReturns, expectedReturns); | ||
|
||
Assert.AreEqual(result, expectedResult); | ||
} | ||
|
||
[Test] | ||
public void EmptyPortfolioReturnsEmptyArrayOfDouble() | ||
{ | ||
var testOptimizer = new MaximumSharpeRatioPortfolioOptimizer(); | ||
var historicalReturns = new double[,] { { } }; | ||
|
||
var expectedResult = Array.Empty<double>(); | ||
|
||
var result = testOptimizer.Optimize(historicalReturns); | ||
|
||
Assert.AreEqual(result, expectedResult); | ||
} | ||
|
||
[Test] | ||
public void EqualWeightingsWhenNoSolutionFound() | ||
{ | ||
var testOptimizer = new MaximumSharpeRatioPortfolioOptimizer(); | ||
var historicalReturns = new double[,] { { -0.10, -0.20 }, { -0.12, -0.25 } }; | ||
var expectedReturns = new double[] { -0.10, -0.25 }; | ||
var covariance = new double[,] { { 0.25, 0.12 }, { 0.45, 0.2 } }; // non positive definite | ||
|
||
var expectedResult = new double[] { 0.5, 0.5 }; | ||
|
||
var result = testOptimizer.Optimize(historicalReturns, expectedReturns, covariance); | ||
|
||
Assert.AreEqual(result, expectedResult); | ||
} | ||
|
||
[Test] | ||
public void BoundariesAreNotViolated() | ||
{ | ||
var testCaseNumber = 1; | ||
double lower = 0d; | ||
double upper = 0.5d; | ||
var testOptimizer = new MaximumSharpeRatioPortfolioOptimizer(lower, upper); | ||
|
||
var result = testOptimizer.Optimize(_historicalReturns[testCaseNumber], null, _covariances[testCaseNumber]); | ||
|
||
foreach (double x in result) | ||
{ | ||
Assert.GreaterOrEqual(x, lower); | ||
Assert.LessOrEqual(x, upper); | ||
}; | ||
} | ||
} | ||
} | ||
|
||
|
146 changes: 146 additions & 0 deletions
146
Tests/Algorithm/Framework/Portfolio/MinimumVariancePortfolioOptimizerTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
using NUnit.Framework; | ||
using QuantConnect.Algorithm.Framework.Portfolio; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.ComponentModel.DataAnnotations; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio | ||
{ | ||
public class MinimumVariancePortfolioOptimizerTests | ||
{ | ||
private Dictionary<int, double> _targetReturns = new(); | ||
private Dictionary<int, double[,]> _historicalReturns = new(); | ||
private Dictionary<int, double[]> _expectedReturns = new(); | ||
private Dictionary<int, double[,]> _covariances = new(); | ||
private Dictionary<int, double[]> _expectedResults = new(); | ||
|
||
[OneTimeSetUp] | ||
public void Setup() | ||
{ | ||
double targetReturn1 = 0.15; | ||
double targetReturn2 = 0.25; | ||
double targetReturn3 = 0.5; | ||
double targetReturn4 = 0.125; | ||
|
||
double[,] historicalReturns1 = new double[,] { { 0.76, -0.06, 1.22, 0.17 }, { 0.02, 0.28, 1.25, -0.00 }, { -0.50, -0.13, -0.50, -0.03 }, { 0.81, 0.31, 2.39, 0.26 }, { -0.02, 0.02, 0.06, 0.01 } }; | ||
double[,] historicalReturns2 = new double[,] { { -0.15, 0.67, 0.45 }, { -0.44, -0.10, 0.07 }, { 0.04, -0.41, 0.01 }, { 0.01, 0.03, 0.02 } }; | ||
double[,] historicalReturns3 = new double[,] { { -0.02, 0.65, 1.25 }, { -0.29, -0.39, -0.50 }, { 0.29, 0.58, 2.39 }, { 0.00, -0.01, 0.06 } }; | ||
double[,] historicalReturns4 = new double[,] { { 0.76, 0.25, 0.21 }, { 0.02, -0.15, 0.45 }, { -0.50, -0.44, 0.07 }, { 0.81, 0.04, 0.01 }, { -0.02, 0.01, 0.02 } }; | ||
|
||
|
||
double[] expectedReturns1 = new double[] { 0.21, 0.08, 0.88, 0.08 }; | ||
double[] expectedReturns2 = new double[] { -0.13, 0.05, 0.14 }; | ||
double[] expectedReturns3 = null; | ||
double[] expectedReturns4 = null; | ||
|
||
|
||
double[,] covariance1 = new double[,] { { 0.31, 0.05, 0.55, 0.07 }, { 0.05, 0.04, 0.18, 0.01 }, { 0.55, 0.18, 1.28, 0.12 }, { 0.07, 0.01, 0.12, 0.02 } }; | ||
double[,] covariance2 = new double[,] { { 0.05, -0.02, -0.01 }, { -0.02, 0.21, 0.09 }, { -0.01, 0.09, 0.04 } }; | ||
double[,] covariance3 = new double[,] { { 0.06, 0.09, 0.28 }, { 0.09, 0.25, 0.58 }, { 0.28, 0.58, 1.66 } }; | ||
double[,] covariance4 = null; | ||
|
||
double[] expectedResult1 = new double[] { -0.089212, 0.23431, -0.040975, 0.635503}; | ||
double[] expectedResult2 = new double[] { 0.366812, -0.139738, 0.49345 }; | ||
double[] expectedResult3 = new double[] { 0.562216, 0.36747, -0.070314 }; | ||
double[] expectedResult4 = new double[] { -0.119241, 0.443464, 0.437295 }; | ||
double[] expectedResult5 = new double[] { -0.215505, 0.130699, 0.084806, 0.56899 }; | ||
double[] expectedResult6 = new double[] { -0.275, 0.275, 0.45 }; | ||
double[] expectedResult7 = new double[] { -0.129512, 0.551139, 0.319349 }; | ||
double[] expectedResult8 = new double[] { 0.052859, 0.144177, 0.802964 }; | ||
|
||
_targetReturns.Add(5, targetReturn1); | ||
_targetReturns.Add(6, targetReturn2); | ||
_targetReturns.Add(7, targetReturn3); | ||
_targetReturns.Add(8, targetReturn4); | ||
|
||
_expectedReturns.Add(1, expectedReturns1); | ||
_expectedReturns.Add(2, expectedReturns2); | ||
_expectedReturns.Add(3, expectedReturns3); | ||
_expectedReturns.Add(4, expectedReturns4); | ||
_expectedReturns.Add(5, expectedReturns1); | ||
_expectedReturns.Add(6, expectedReturns2); | ||
_expectedReturns.Add(7, expectedReturns3); | ||
_expectedReturns.Add(8, expectedReturns4); | ||
|
||
_historicalReturns.Add(1, historicalReturns1); | ||
_historicalReturns.Add(2, historicalReturns2); | ||
_historicalReturns.Add(3, historicalReturns3); | ||
_historicalReturns.Add(4, historicalReturns4); | ||
_historicalReturns.Add(5, historicalReturns1); | ||
_historicalReturns.Add(6, historicalReturns2); | ||
_historicalReturns.Add(7, historicalReturns3); | ||
_historicalReturns.Add(8, historicalReturns4); | ||
|
||
_covariances.Add(1, covariance1); | ||
_covariances.Add(2, covariance2); | ||
_covariances.Add(3, covariance3); | ||
_covariances.Add(4, covariance4); | ||
_covariances.Add(5, covariance1); | ||
_covariances.Add(6, covariance2); | ||
_covariances.Add(7, covariance3); | ||
_covariances.Add(8, covariance4); | ||
|
||
_expectedResults.Add(1, expectedResult1); | ||
_expectedResults.Add(2, expectedResult2); | ||
_expectedResults.Add(3, expectedResult3); | ||
_expectedResults.Add(4, expectedResult4); | ||
_expectedResults.Add(5, expectedResult5); | ||
_expectedResults.Add(6, expectedResult6); | ||
_expectedResults.Add(7, expectedResult7); | ||
_expectedResults.Add(8, expectedResult8); | ||
} | ||
|
||
[TestCase(1)] | ||
[TestCase(2)] | ||
[TestCase(3)] | ||
[TestCase(4)] | ||
public void TestOptimizeWeightings(int testCaseNumber) | ||
{ | ||
var testOptimizer = new MinimumVariancePortfolioOptimizer(); | ||
|
||
var result = testOptimizer.Optimize( | ||
_historicalReturns[testCaseNumber], | ||
_expectedReturns[testCaseNumber], | ||
_covariances[testCaseNumber]); | ||
|
||
Assert.AreEqual(_expectedResults[testCaseNumber], result.Select(x => Math.Round(x, 6))); | ||
} | ||
|
||
[TestCase(5)] | ||
[TestCase(6)] | ||
[TestCase(7)] | ||
[TestCase(8)] | ||
public void TestOptimizeWeightingsSpecifyingTargetReturns(int testCaseNumber) | ||
{ | ||
var testOptimizer = new MinimumVariancePortfolioOptimizer(targetReturn: _targetReturns[testCaseNumber]); | ||
|
||
var result = testOptimizer.Optimize( | ||
_historicalReturns[testCaseNumber], | ||
_expectedReturns[testCaseNumber], | ||
_covariances[testCaseNumber]); | ||
|
||
Assert.AreEqual(_expectedResults[testCaseNumber], result.Select(x => Math.Round(x, 6))); | ||
} | ||
|
||
[TestCase(1)] | ||
public void EqualWeightingsWhenNoSolutionFound(int testCaseNumber) | ||
{ | ||
var historicalReturns = new double[,] { { } } | ||
var testOptimizer = new MinimumVariancePortfolioOptimizer(lower: 0); | ||
var expectedResult = new double[] { 0.5d, 0.5d }; | ||
|
||
var result = testOptimizer.Optimize(_historicalReturns[testCaseNumber]); | ||
|
||
Assert.AreEqual(expectedResult, result); | ||
} | ||
|
||
public void BoundariesAreNotViolated(int testCaseNumber) | ||
{ | ||
|
||
} | ||
} | ||
} | ||
|
44 changes: 44 additions & 0 deletions
44
Tests/Algorithm/Framework/Portfolio/UnconstrainedMeanVariancePortfolioOptimizerTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
using Accord.Statistics; | ||
using NUnit.Framework; | ||
using QuantConnect.Algorithm.Framework.Portfolio; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio | ||
{ | ||
public class UnconstrainedMeanVariancePortfolioOptimizerTests | ||
{ | ||
private Dictionary<int, double[,]> _historicalReturns = new(); | ||
private Dictionary<int, double[]> _expectedResults = new(); | ||
|
||
[OneTimeSetUp] | ||
public void Setup() | ||
{ | ||
var historicalReturns1 = new double[,] { { 0.02, 0.05, -0.10 }, { 0.022, 0.10, 0.20 }, { 0.02, -0.02, 0.15 } }; | ||
var historicalReturns2 = new double[,] { { 0.25, 0.01 }, { 0.21, -0.02 } }; ; | ||
|
||
var expectedResult1 = new double[] { -1.4234053790251934E+20, 2.124485640336107E+18, 5.948559792941101E+17 }; | ||
var expectedResult2 = new double[] { 1.0058640608512344E+18, -1.341152081134979E+18 }; | ||
|
||
_historicalReturns.Add(1, historicalReturns1); | ||
_historicalReturns.Add(2, historicalReturns2); | ||
|
||
_expectedResults.Add(1, expectedResult1); | ||
_expectedResults.Add(2, expectedResult2); | ||
} | ||
|
||
[TestCase(1)] | ||
[TestCase(2)] | ||
public void TestOptimizeWeightings(int testCaseNumber) | ||
{ | ||
var testOptimizer = new UnconstrainedMeanVariancePortfolioOptimizer(); | ||
|
||
var result = testOptimizer.Optimize(_historicalReturns[testCaseNumber]); | ||
|
||
Assert.AreEqual(_expectedResults[testCaseNumber], result); | ||
} | ||
} | ||
} |