Lean  $LEAN_TAG$
MinimumVariancePortfolioOptimizer.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using System.Collections.Generic;
17 using System.Linq;
18 using Accord.Math;
19 using Accord.Math.Optimization;
20 using Accord.Statistics;
21 
23 {
24  /// <summary>
25  /// Provides an implementation of a minimum variance portfolio optimizer that calculate the optimal weights
26  /// with the weight range from -1 to 1 and minimize the portfolio variance with a target return of 2%
27  /// </summary>
28  /// <remarks>The budged constrain is scaled down/up to ensure that the sum of the absolute value of the weights is 1.</remarks>
30  {
31  private double _lower;
32  private double _upper;
33  private double _targetReturn;
34 
35  /// <summary>
36  /// Initialize a new instance of <see cref="MinimumVariancePortfolioOptimizer"/>
37  /// </summary>
38  /// <param name="lower">Lower bound</param>
39  /// <param name="upper">Upper bound</param>
40  /// <param name="targetReturn">Target return</param>
41  public MinimumVariancePortfolioOptimizer(double lower = -1, double upper = 1, double targetReturn = 0.02)
42  {
43  _lower = lower;
44  _upper = upper;
45  _targetReturn = targetReturn;
46  }
47 
48  /// <summary>
49  /// Sum of all weight is one: 1^T w = 1 / Σw = 1
50  /// </summary>
51  /// <param name="size">number of variables</param>
52  /// <returns>linear constaraint object</returns>
53  protected LinearConstraint GetBudgetConstraint(int size)
54  {
55  return new LinearConstraint(size)
56  {
57  CombinedAs = Vector.Create(size, 1.0),
58  ShouldBe = ConstraintType.EqualTo,
59  Value = 1.0
60  };
61  }
62 
63  /// <summary>
64  /// Boundary constraints on weights: lw ≤ w ≤ up
65  /// </summary>
66  /// <param name="size">number of variables</param>
67  /// <returns>enumeration of linear constaraint objects</returns>
68  protected IEnumerable<LinearConstraint> GetBoundaryConditions(int size)
69  {
70  for (var i = 0; i < size; i++)
71  {
72  yield return new LinearConstraint(1)
73  {
74  VariablesAtIndices = new[] { i },
75  ShouldBe = ConstraintType.GreaterThanOrEqualTo,
76  Value = _lower
77  };
78  yield return new LinearConstraint(1)
79  {
80  VariablesAtIndices = new[] { i },
81  ShouldBe = ConstraintType.LesserThanOrEqualTo,
82  Value = _upper
83  };
84  }
85  }
86 
87  /// <summary>
88  /// Perform portfolio optimization for a provided matrix of historical returns and an array of expected returns
89  /// </summary>
90  /// <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>
91  /// <param name="expectedReturns">Array of double with the portfolio annualized expected returns (size: K x 1).</param>
92  /// <param name="covariance">Multi-dimensional array of double with the portfolio covariance of annualized returns (size: K x K).</param>
93  /// <returns>Array of double with the portfolio weights (size: K x 1)</returns>
94  public double[] Optimize(double[,] historicalReturns, double[] expectedReturns = null, double[,] covariance = null)
95  {
96  covariance ??= historicalReturns.Covariance();
97  var size = covariance.GetLength(0);
98  var returns = expectedReturns ?? historicalReturns.Mean(0);
99 
100  var constraints = new List<LinearConstraint>
101  {
102  // w^T µ ≥ β
103  new (size)
104  {
105  CombinedAs = returns,
106  ShouldBe = ConstraintType.EqualTo,
107  Value = _targetReturn
108  },
109  // Σw = 1
110  GetBudgetConstraint(size),
111  };
112 
113  // lw ≤ w ≤ up
114  constraints.AddRange(GetBoundaryConditions(size));
115 
116  // Setup solver
117  var optfunc = new QuadraticObjectiveFunction(covariance, Vector.Create(size, 0.0));
118  var solver = new GoldfarbIdnani(optfunc, constraints);
119 
120  // Solve problem
121  var x0 = Vector.Create(size, 1.0 / size);
122  var success = solver.Minimize(Vector.Copy(x0));
123  if (!success) return x0;
124 
125  // We cannot accept NaN
126  var solution = solver.Solution
127  .Select(x => x.IsNaNOrInfinity() ? 0 : x).ToArray();
128 
129  // Scale the solution to ensure that the sum of the absolute weights is 1
130  var sumOfAbsoluteWeights = solution.Abs().Sum();
131  if (sumOfAbsoluteWeights.IsNaNOrZero()) return x0;
132 
133  return solution.Divide(sumOfAbsoluteWeights);
134  }
135  }
136 }