Files
quantconnect--lean/Algorithm.Framework/Portfolio/MinimumVariancePortfolioOptimizer.cs
Alexandre Catarino f1d55c3b0b Improves MinimumVariancePortfolioOptimizer Values Handling (#7106)
* Improves MinimumVariancePortfolioOptimizer Values Handling

The C# version of the `MinimumVariancePortfolioOptimizer` generated NaN resulting in unit tests failing.
If the solver returns NaN for an item, we set it to zero. if all items are NaN or Zero, we return the initial guess, since the sum cannot be zero.

* Fixes `RiskParityPortfolioConstructionModelTests`

We need to add insights to insight manager before we call `CreateTargets`.
2023-03-21 11:24:58 -03:00

137 lines
5.6 KiB
C#

/*
* 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 applicable 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 System.Collections.Generic;
using System.Linq;
using Accord.Math;
using Accord.Math.Optimization;
using Accord.Statistics;
namespace QuantConnect.Algorithm.Framework.Portfolio
{
/// <summary>
/// Provides an implementation of a minimum variance portfolio optimizer that calculate the optimal weights
/// with the weight range from -1 to 1 and minimize the portfolio variance with a target return of 2%
/// </summary>
/// <remarks>The budged constrain is scaled down/up to ensure that the sum of the absolute value of the weights is 1.</remarks>
public class MinimumVariancePortfolioOptimizer : IPortfolioOptimizer
{
private double _lower;
private double _upper;
private double _targetReturn;
/// <summary>
/// Initialize a new instance of <see cref="MinimumVariancePortfolioOptimizer"/>
/// </summary>
/// <param name="lower">Lower bound</param>
/// <param name="upper">Upper bound</param>
/// <param name="targetReturn">Target return</param>
public MinimumVariancePortfolioOptimizer(double lower = -1, double upper = 1, double targetReturn = 0.02)
{
_lower = lower;
_upper = upper;
_targetReturn = targetReturn;
}
/// <summary>
/// Sum of all weight is one: 1^T w = 1 / Σw = 1
/// </summary>
/// <param name="size">number of variables</param>
/// <returns>linear constaraint object</returns>
protected LinearConstraint GetBudgetConstraint(int size)
{
return new LinearConstraint(size)
{
CombinedAs = Vector.Create(size, 1.0),
ShouldBe = ConstraintType.EqualTo,
Value = 1.0
};
}
/// <summary>
/// Boundary constraints on weights: lw ≤ w ≤ up
/// </summary>
/// <param name="size">number of variables</param>
/// <returns>enumeration of linear constaraint objects</returns>
protected IEnumerable<LinearConstraint> GetBoundaryConditions(int size)
{
for (var i = 0; i < size; i++)
{
yield return new LinearConstraint(1)
{
VariablesAtIndices = new[] { i },
ShouldBe = ConstraintType.GreaterThanOrEqualTo,
Value = _lower
};
yield return new LinearConstraint(1)
{
VariablesAtIndices = new[] { i },
ShouldBe = ConstraintType.LesserThanOrEqualTo,
Value = _upper
};
}
}
/// <summary>
/// Perform portfolio optimization for a provided matrix of historical returns and an array of expected returns
/// </summary>
/// <param name="historicalReturns">Matrix of annualized historical returns where each column represents a security and each row returns for the given date/time (size: K x N).</param>
/// <param name="expectedReturns">Array of double with the portfolio annualized expected returns (size: K x 1).</param>
/// <param name="covariance">Multi-dimensional array of double with the portfolio covariance of annualized returns (size: K x K).</param>
/// <returns>Array of double with the portfolio weights (size: K x 1)</returns>
public double[] Optimize(double[,] historicalReturns, double[] expectedReturns = null, double[,] covariance = null)
{
covariance ??= historicalReturns.Covariance();
var size = covariance.GetLength(0);
var returns = expectedReturns ?? historicalReturns.Mean(0);
var constraints = new List<LinearConstraint>
{
// w^T µ ≥ β
new (size)
{
CombinedAs = returns,
ShouldBe = ConstraintType.EqualTo,
Value = _targetReturn
},
// Σw = 1
GetBudgetConstraint(size),
};
// lw ≤ w ≤ up
constraints.AddRange(GetBoundaryConditions(size));
// Setup solver
var optfunc = new QuadraticObjectiveFunction(covariance, Vector.Create(size, 0.0));
var solver = new GoldfarbIdnani(optfunc, constraints);
// Solve problem
var x0 = Vector.Create(size, 1.0 / size);
var success = solver.Minimize(Vector.Copy(x0));
if (!success) return x0;
// We cannot accept NaN
var solution = solver.Solution
.Select(x => x.IsNaNOrInfinity() ? 0 : x).ToArray();
// Scale the solution to ensure that the sum of the absolute weights is 1
var sumOfAbsoluteWeights = solution.Abs().Sum();
if (sumOfAbsoluteWeights.IsNaNOrZero()) return x0;
return solution.Divide(sumOfAbsoluteWeights);
}
}
}