Skip to content

Commit

Permalink
Added UnconstrainedMeanVariancePortfolioOptimizer tests
Browse files Browse the repository at this point in the history
MaxSharpe tests added rounding to boundaries test

Clean up pythonnet runtime exception build paths (QuantConnect#7687)

- Clean up pythonnet runtime exception build paths

Added license info to files
  • Loading branch information
wtindall1 committed Jan 11, 2024
1 parent 21d51af commit dab3952
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 24 deletions.
2 changes: 1 addition & 1 deletion Common/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ namespace QuantConnect
/// </summary>
public static class Extensions
{
private static readonly Regex LeanPathRegex = new Regex("(?:\\S*?\\\\Lean\\\\)|(?:\\S*?/Lean/)", RegexOptions.Compiled);
private static readonly Regex LeanPathRegex = new Regex("(?:\\S*?\\\\pythonnet\\\\)|(?:\\S*?\\\\Lean\\\\)|(?:\\S*?/Lean/)|(?:\\S*?/pythonnet/)", RegexOptions.Compiled);
private static readonly Dictionary<string, bool> _emptyDirectories = new ();
private static readonly HashSet<string> InvalidSecurityTypes = new HashSet<string>();
private static readonly Regex DateCheck = new Regex(@"\d{8}", RegexOptions.Compiled);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
using Accord.Statistics;
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by aaplicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

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
{
Expand Down Expand Up @@ -181,8 +192,9 @@ public void BoundariesAreNotViolated()

foreach (double x in result)
{
Assert.GreaterOrEqual(x, lower);
Assert.LessOrEqual(x, upper);
var rounded = Math.Round(x, 6);
Assert.GreaterOrEqual(rounded, lower);
Assert.LessOrEqual(rounded, upper);
};
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by aaplicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

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
{
Expand All @@ -30,13 +42,11 @@ public void Setup()
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 } };
Expand Down Expand Up @@ -107,6 +117,7 @@ public void TestOptimizeWeightings(int testCaseNumber)
_covariances[testCaseNumber]);

Assert.AreEqual(_expectedResults[testCaseNumber], result.Select(x => Math.Round(x, 6)));
Assert.AreEqual(1d, result.Select(x => Math.Round(Math.Abs(x), 6)).Sum());
}

[TestCase(5)]
Expand All @@ -123,23 +134,61 @@ public void TestOptimizeWeightingsSpecifyingTargetReturns(int testCaseNumber)
_covariances[testCaseNumber]);

Assert.AreEqual(_expectedResults[testCaseNumber], result.Select(x => Math.Round(x, 6)));
Assert.AreEqual(1d, result.Select(x => Math.Round(Math.Abs(x), 6)).Sum());
}

[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 testOptimizer = new MinimumVariancePortfolioOptimizer(upper: -1);
var expectedResult = new double[] { 0.25, 0.25, 0.25, 0.25 };

var result = testOptimizer.Optimize(_historicalReturns[testCaseNumber]);

Assert.AreEqual(expectedResult, result);
}

[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
[TestCase(4)]
public void BoundariesAreNotViolated(int testCaseNumber)
{
double lower = 0;
double upper = 0.5;
var testOptimizer = new MinimumVariancePortfolioOptimizer(lower, upper);

var result = testOptimizer.Optimize(
_historicalReturns[testCaseNumber],
_expectedReturns[testCaseNumber],
_covariances[testCaseNumber]);

foreach (double x in result)
{
var rounded = Math.Round(x, 6);
Assert.GreaterOrEqual(rounded, lower);
Assert.LessOrEqual(rounded, upper);
};
}

public void EmptyPortfolioReturnsEmptyArrayOfDouble()
{
var testOptimizer = new MinimumVariancePortfolioOptimizer();
var historicalReturns = new double[,] { { } };

var result = testOptimizer.Optimize(historicalReturns);

Assert.AreEqual(Array.Empty<double>(), result);
}

public void SingleSecurityPortfolioReturnsOne()
{
var testOptimizer = new MinimumVariancePortfolioOptimizer();
double[,] historicalReturns = new double[,] { { 0.76 }, { 0.02 }, { -0.50 } };

var result = testOptimizer.Optimize(historicalReturns);

Assert.AreEqual(1d, result);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,44 +1,101 @@
using Accord.Statistics;
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by aaplicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

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[]> _expectedReturns = new();
private Dictionary<int, double[,]> _covariances = 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 } }; ;
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;

var expectedResult1 = new double[] { -1.4234053790251934E+20, 2.124485640336107E+18, 5.948559792941101E+17 };
var expectedResult2 = new double[] { 1.0058640608512344E+18, -1.341152081134979E+18 };
double[] expectedResult1 = new double[] { -13.288136, -23.322034, 8.79661, 9.389831 };
double[] expectedResult2 = new double[] { -0.142857, -35.285714, 82.857143 };
double[] expectedResult3 = new double[] { -13.232262, -3.709534, 4.009978 };
double[] expectedResult4 = new double[] { 4.621852, -9.651736, 5.098332 };

_historicalReturns.Add(1, historicalReturns1);
_historicalReturns.Add(2, historicalReturns2);
_historicalReturns.Add(3, historicalReturns3);
_historicalReturns.Add(4, historicalReturns4);

_expectedReturns.Add(1, expectedReturns1);
_expectedReturns.Add(2, expectedReturns2);
_expectedReturns.Add(3, expectedReturns3);
_expectedReturns.Add(4, expectedReturns4);

_covariances.Add(1, covariance1);
_covariances.Add(2, covariance2);
_covariances.Add(3, covariance3);
_covariances.Add(4, covariance4);

_expectedResults.Add(1, expectedResult1);
_expectedResults.Add(2, expectedResult2);
_expectedResults.Add(3, expectedResult3);
_expectedResults.Add(4, expectedResult4);
}

[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
[TestCase(4)]
public void TestOptimizeWeightings(int testCaseNumber)
{
var testOptimizer = new UnconstrainedMeanVariancePortfolioOptimizer();

var result = testOptimizer.Optimize(_historicalReturns[testCaseNumber]);
var result = testOptimizer.Optimize(
_historicalReturns[testCaseNumber],
_expectedReturns[testCaseNumber],
_covariances[testCaseNumber]);

Assert.AreEqual(_expectedResults[testCaseNumber], result.Select(x => Math.Round(x, 6)));
}

public void EmptyPortfolioReturnsEmptyArrayOfDouble()
{
var testOptimizer = new UnconstrainedMeanVariancePortfolioOptimizer();
var historicalReturns = new double[,] { { } };

var result = testOptimizer.Optimize(historicalReturns);

Assert.AreEqual(_expectedResults[testCaseNumber], result);
Assert.AreEqual(Array.Empty<double>(), result);
}
}
}
16 changes: 16 additions & 0 deletions Tests/Common/Util/PythonUtilTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,22 @@ at CallSite.Target(Closure , CallSite , Object , PythonSlice )
at QuantConnect.AlgorithmFactory.Python.Wrappers.AlgorithmPythonWrapper.OnData(Slice slice) in /home/jhonathan/QuantConnect/Lean/AlgorithmFactory/Python/Wrappers/AlgorithmPythonWrapper.cs:line 587
at QuantConnect.Lean.Engine.AlgorithmManager.Run(AlgorithmNodePacket job, IAlgorithm algorithm, ISynchronizer synchronizer, ITransactionHandler transactions, IResultHandler results, IRealTimeHandler realtime, ILeanManager leanManager, IAlphaHandler alphas, CancellationToken token) in /home/jhonathan/QuantConnect/Lean/Engine/AlgorithmManager.cs:line 523",
0)]
[TestCase(@"
at OnData
raise ValueError(""""ASD"""")
at Python.Runtime.PythonException.ThrowLastAsClrException() at src\runtime\PythonException.cs:line 52
at Python.Runtime.PyObject.Invoke(PyTuple args in BasicTemplateAlgorithm.py: line 43
",
@" File ""D:\QuantConnect/MyLean/Lean/Algorithm.Python\BasicTemplateAlgorithm.py"", line 43, in OnData
raise ValueError(""""ASD"""")
at Python.Runtime.PythonException.ThrowLastAsClrException() in D:\QuantConnect\MyLean\pythonnet\src\runtime\PythonException.cs:line 52
at Python.Runtime.PyObject.Invoke(PyTuple args, PyDict kw) in D:\QuantConnect\MyLean\pythonnet\src\runtime\PythonTypes\PyObject.cs:line 837
at Python.Runtime.PyObject.TryInvoke(InvokeBinder binder, Object[] args, Object& result) in D:\QuantConnect\MyLean\pythonnet\src\runtime\PythonTypes\PyObject.cs:line 1320
at CallSite.Target(Closure , CallSite , Object , PythonSlice )
at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid2[T0,T1](CallSite site, T0 arg0, T1 arg1)
at QuantConnect.AlgorithmFactory.Python.Wrappers.AlgorithmPythonWrapper.OnData(Slice slice) in D:\QuantConnect\MyLean\Lean\AlgorithmFactory\Python\Wrappers\AlgorithmPythonWrapper.cs:line 693
at QuantConnect.Lean.Engine.AlgorithmManager.Run(AlgorithmNodePacket job, IAlgorithm algorithm, ISynchronizer synchronizer, ITransactionHandler transactions, IResultHandler results, IRealTimeHandler realtime, ILeanManager leanManager, CancellationToken token) in D:\QuantConnect\MyLean\Lean\Engine\AlgorithmManager.cs:line 526",
0)]
public void ParsesPythonExceptionStackTrace(string expected, string original, int shift)
{
var originalShiftValue = PythonUtil.ExceptionLineShift;
Expand Down

0 comments on commit dab3952

Please sign in to comment.