Skip to content

Commit

Permalink
MaximumSharpeRatio and MinVariance optimizer tests
Browse files Browse the repository at this point in the history
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
wtindall1 committed Jan 11, 2024
1 parent abd092a commit 21d51af
Show file tree
Hide file tree
Showing 3 changed files with 381 additions and 0 deletions.
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);
};
}
}
}


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)
{

}
}
}

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);
}
}
}

0 comments on commit 21d51af

Please sign in to comment.