Lean  $LEAN_TAG$
BlackLittermanOptimizationPortfolioConstructionModel.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 
17 using QuantConnect.Data;
19 using System;
20 using System.Collections.Generic;
21 using System.Linq;
22 using Accord.Statistics;
23 using Accord.Math;
24 using Python.Runtime;
26 
28 {
29  /// <summary>
30  /// Provides an implementation of Black-Litterman portfolio optimization. The model adjusts equilibrium market
31  /// returns by incorporating views from multiple alpha models and therefore to get the optimal risky portfolio
32  /// reflecting those views. If insights of all alpha models have None magnitude or there are linearly dependent
33  /// vectors in link matrix of views, the expected return would be the implied excess equilibrium return.
34  /// The interval of weights in optimization method can be changed based on the long-short algorithm.
35  /// The default model uses the 0.0025 as weight-on-views scalar parameter tau. The optimization method
36  /// maximizes the Sharpe ratio with the weight range from -1 to 1.
37  /// </summary>
39  {
40  private readonly IPortfolioOptimizer _optimizer;
41  private readonly PortfolioBias _portfolioBias;
42  private readonly Resolution _resolution;
43  private readonly double _riskFreeRate;
44  private readonly double _delta;
45  private readonly int _lookback;
46  private readonly double _tau;
47  private readonly int _period;
48 
49  private readonly Dictionary<Symbol, ReturnsSymbolData> _symbolDataDict;
50 
51  /// <summary>
52  /// Initialize the model
53  /// </summary>
54  /// <param name="timeSpan">Rebalancing frequency</param>
55  /// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
56  /// <param name="lookback">Historical return lookback period</param>
57  /// <param name="period">The time interval of history price to calculate the weight</param>
58  /// <param name="resolution">The resolution of the history price</param>
59  /// <param name="riskFreeRate">The risk free rate</param>
60  /// <param name="delta">The risk aversion coeffficient of the market portfolio</param>
61  /// <param name="tau">The model parameter indicating the uncertainty of the CAPM prior</param>
62  /// <param name="optimizer">The portfolio optimization algorithm. If no algorithm is explicitly provided then the default will be max Sharpe ratio optimization.</param>
64  PortfolioBias portfolioBias = PortfolioBias.LongShort,
65  int lookback = 1,
66  int period = 63,
67  Resolution resolution = Resolution.Daily,
68  double riskFreeRate = 0.0,
69  double delta = 2.5,
70  double tau = 0.05,
71  IPortfolioOptimizer optimizer = null)
72  : this(dt => dt.Add(timeSpan), portfolioBias, lookback, period, resolution, riskFreeRate, delta, tau, optimizer)
73  {
74  }
75 
76  /// <summary>
77  /// Initialize the model
78  /// </summary>
79  /// <param name="rebalanceResolution">Rebalancing frequency</param>
80  /// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
81  /// <param name="lookback">Historical return lookback period</param>
82  /// <param name="period">The time interval of history price to calculate the weight</param>
83  /// <param name="resolution">The resolution of the history price</param>
84  /// <param name="riskFreeRate">The risk free rate</param>
85  /// <param name="delta">The risk aversion coeffficient of the market portfolio</param>
86  /// <param name="tau">The model parameter indicating the uncertainty of the CAPM prior</param>
87  /// <param name="optimizer">The portfolio optimization algorithm. If no algorithm is explicitly provided then the default will be max Sharpe ratio optimization.</param>
89  PortfolioBias portfolioBias = PortfolioBias.LongShort,
90  int lookback = 1,
91  int period = 63,
92  Resolution resolution = Resolution.Daily,
93  double riskFreeRate = 0.0,
94  double delta = 2.5,
95  double tau = 0.05,
96  IPortfolioOptimizer optimizer = null)
97  : this(rebalanceResolution.ToTimeSpan(), portfolioBias, lookback, period, resolution, riskFreeRate, delta, tau, optimizer)
98  {
99  }
100 
101  /// <summary>
102  /// Initialize the model
103  /// </summary>
104  /// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance UTC time.
105  /// Returning current time will trigger rebalance. If null will be ignored</param>
106  /// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
107  /// <param name="lookback">Historical return lookback period</param>
108  /// <param name="period">The time interval of history price to calculate the weight</param>
109  /// <param name="resolution">The resolution of the history price</param>
110  /// <param name="riskFreeRate">The risk free rate</param>
111  /// <param name="delta">The risk aversion coeffficient of the market portfolio</param>
112  /// <param name="tau">The model parameter indicating the uncertainty of the CAPM prior</param>
113  /// <param name="optimizer">The portfolio optimization algorithm. If no algorithm is explicitly provided then the default will be max Sharpe ratio optimization.</param>
114  public BlackLittermanOptimizationPortfolioConstructionModel(Func<DateTime, DateTime> rebalancingFunc,
115  PortfolioBias portfolioBias = PortfolioBias.LongShort,
116  int lookback = 1,
117  int period = 63,
118  Resolution resolution = Resolution.Daily,
119  double riskFreeRate = 0.0,
120  double delta = 2.5,
121  double tau = 0.05,
122  IPortfolioOptimizer optimizer = null)
123  : this(rebalancingFunc != null ? (Func<DateTime, DateTime?>)(timeUtc => rebalancingFunc(timeUtc)) : null,
124  portfolioBias,
125  lookback,
126  period,
127  resolution,
128  riskFreeRate,
129  delta,
130  tau,
131  optimizer)
132  {
133  }
134 
135  /// <summary>
136  /// Initialize the model
137  /// </summary>
138  /// <param name="rebalancingDateRules">The date rules used to define the next expected rebalance time
139  /// in UTC</param>
140  /// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
141  /// <param name="lookback">Historical return lookback period</param>
142  /// <param name="period">The time interval of history price to calculate the weight</param>
143  /// <param name="resolution">The resolution of the history price</param>
144  /// <param name="riskFreeRate">The risk free rate</param>
145  /// <param name="delta">The risk aversion coeffficient of the market portfolio</param>
146  /// <param name="tau">The model parameter indicating the uncertainty of the CAPM prior</param>
147  /// <param name="optimizer">The portfolio optimization algorithm. If no algorithm is explicitly provided then the default will be max Sharpe ratio optimization.</param>
149  PortfolioBias portfolioBias = PortfolioBias.LongShort,
150  int lookback = 1,
151  int period = 63,
152  Resolution resolution = Resolution.Daily,
153  double riskFreeRate = 0.0,
154  double delta = 2.5,
155  double tau = 0.05,
156  IPortfolioOptimizer optimizer = null)
157  : this(rebalancingDateRules.ToFunc(), portfolioBias, lookback, period, resolution, riskFreeRate, delta, tau, optimizer)
158  {
159  }
160 
161  /// <summary>
162  /// Initialize the model
163  /// </summary>
164  /// <param name="rebalance">Rebalancing func or if a date rule, timedelta will be converted into func.
165  /// For a given algorithm UTC DateTime the func returns the next expected rebalance time
166  /// or null if unknown, in which case the function will be called again in the next loop. Returning current time
167  /// will trigger rebalance. If null will be ignored</param>
168  /// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
169  /// <param name="lookback">Historical return lookback period</param>
170  /// <param name="period">The time interval of history price to calculate the weight</param>
171  /// <param name="resolution">The resolution of the history price</param>
172  /// <param name="riskFreeRate">The risk free rate</param>
173  /// <param name="delta">The risk aversion coeffficient of the market portfolio</param>
174  /// <param name="tau">The model parameter indicating the uncertainty of the CAPM prior</param>
175  /// <param name="optimizer">The portfolio optimization algorithm. If no algorithm is explicitly provided then the default will be max Sharpe ratio optimization.</param>
176  /// <remarks>This is required since python net can not convert python methods into func nor resolve the correct
177  /// constructor for the date rules parameter.
178  /// For performance we prefer python algorithms using the C# implementation</remarks>
180  PortfolioBias portfolioBias = PortfolioBias.LongShort,
181  int lookback = 1,
182  int period = 63,
183  Resolution resolution = Resolution.Daily,
184  double riskFreeRate = 0.0,
185  double delta = 2.5,
186  double tau = 0.05,
187  IPortfolioOptimizer optimizer = null)
188  : this((Func<DateTime, DateTime?>)null, portfolioBias, lookback, period, resolution, riskFreeRate, delta, tau, optimizer)
189  {
190  SetRebalancingFunc(rebalance);
191  }
192 
193  /// <summary>
194  /// Initialize the model
195  /// </summary>
196  /// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance time
197  /// or null if unknown, in which case the function will be called again in the next loop. Returning current time
198  /// will trigger rebalance.</param>
199  /// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
200  /// <param name="lookback">Historical return lookback period</param>
201  /// <param name="period">The time interval of history price to calculate the weight</param>
202  /// <param name="resolution">The resolution of the history price</param>
203  /// <param name="riskFreeRate">The risk free rate</param>
204  /// <param name="delta">The risk aversion coeffficient of the market portfolio</param>
205  /// <param name="tau">The model parameter indicating the uncertainty of the CAPM prior</param>
206  /// <param name="optimizer">The portfolio optimization algorithm. If no algorithm is explicitly provided then the default will be max Sharpe ratio optimization.</param>
207  public BlackLittermanOptimizationPortfolioConstructionModel(Func<DateTime, DateTime?> rebalancingFunc,
208  PortfolioBias portfolioBias = PortfolioBias.LongShort,
209  int lookback = 1,
210  int period = 63,
211  Resolution resolution = Resolution.Daily,
212  double riskFreeRate = 0.0,
213  double delta = 2.5,
214  double tau = 0.05,
215  IPortfolioOptimizer optimizer = null)
216  : base(rebalancingFunc)
217  {
218  _lookback = lookback;
219  _period = period;
220  _resolution = resolution;
221  _riskFreeRate = riskFreeRate;
222  _delta = delta;
223  _tau = tau;
224 
225  var lower = portfolioBias == PortfolioBias.Long ? 0 : -1;
226  var upper = portfolioBias == PortfolioBias.Short ? 0 : 1;
227  _optimizer = optimizer ?? new MaximumSharpeRatioPortfolioOptimizer(lower, upper, riskFreeRate);
228  _portfolioBias = portfolioBias;
229  _symbolDataDict = new Dictionary<Symbol, ReturnsSymbolData>();
230  }
231 
232  /// <summary>
233  /// Method that will determine if the portfolio construction model should create a
234  /// target for this insight
235  /// </summary>
236  /// <param name="insight">The insight to create a target for</param>
237  /// <returns>True if the portfolio should create a target for the insight</returns>
238  protected override bool ShouldCreateTargetForInsight(Insight insight)
239  {
240  return FilterInvalidInsightMagnitude(Algorithm, new []{ insight }).Length != 0;
241  }
242 
243  /// <summary>
244  /// Will determine the target percent for each insight
245  /// </summary>
246  /// <param name="lastActiveInsights">The active insights to generate a target for</param>
247  /// <returns>A target percent for each insight</returns>
248  protected override Dictionary<Insight, double> DetermineTargetPercent(List<Insight> lastActiveInsights)
249  {
250  var targets = new Dictionary<Insight, double>();
251 
252  if (TryGetViews(lastActiveInsights, out var P, out var Q))
253  {
254  // Updates the ReturnsSymbolData with insights
255  foreach (var insight in lastActiveInsights)
256  {
257  if (_symbolDataDict.TryGetValue(insight.Symbol, out var symbolData))
258  {
259  if (insight.Magnitude == null)
260  {
261  Algorithm.SetRunTimeError(new ArgumentNullException("BlackLittermanOptimizationPortfolioConstructionModel does not accept \'null\' as Insight.Magnitude. Please make sure your Alpha Model is generating Insights with the Magnitude property set."));
262  return targets;
263  }
264  symbolData.Add(insight.GeneratedTimeUtc, insight.Magnitude.Value.SafeDecimalCast());
265  }
266  }
267  // Get symbols' returns
268  var symbols = lastActiveInsights.Select(x => x.Symbol).Distinct().ToList();
269  var returns = _symbolDataDict.FormReturnsMatrix(symbols);
270 
271  // Calculate posterior estimate of the mean and uncertainty in the mean
272  var Π = GetEquilibriumReturns(returns, out var Σ);
273 
274  ApplyBlackLittermanMasterFormula(ref Π, ref Σ, P, Q);
275 
276  // Create portfolio targets from the specified insights
277  var W = _optimizer.Optimize(returns, Π, Σ);
278  var sidx = 0;
279  foreach (var symbol in symbols)
280  {
281  var weight = W[sidx];
282 
283  // don't trust the optimizer
284  if (_portfolioBias != PortfolioBias.LongShort
285  && Math.Sign(weight) != (int)_portfolioBias)
286  {
287  weight = 0;
288  }
289  targets[lastActiveInsights.First(insight => insight.Symbol == symbol)] = weight;
290 
291  sidx++;
292  }
293  }
294 
295  return targets;
296  }
297 
298  /// <summary>
299  /// Gets the target insights to calculate a portfolio target percent for
300  /// </summary>
301  /// <returns>An enumerable of the target insights</returns>
302  protected override List<Insight> GetTargetInsights()
303  {
304  // Get insight that haven't expired of each symbol that is still in the universe
305  var activeInsights = Algorithm.Insights.GetActiveInsights(Algorithm.UtcTime).Where(ShouldCreateTargetForInsight);
306 
307  // Get the last generated active insight for each symbol
308  return (from insight in activeInsights
309  group insight by new { insight.Symbol, insight.SourceModel } into g
310  select g.OrderBy(x => x.GeneratedTimeUtc).Last())
311  .OrderBy(x => x.Symbol).ToList();
312  }
313 
314  /// <summary>
315  /// Event fired each time the we add/remove securities from the data feed
316  /// </summary>
317  /// <param name="algorithm">The algorithm instance that experienced the change in securities</param>
318  /// <param name="changes">The security additions and removals from the algorithm</param>
319  public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
320  {
321  base.OnSecuritiesChanged(algorithm, changes);
322 
323  foreach (var symbol in changes.RemovedSecurities.Select(x => x.Symbol))
324  {
325  if (_symbolDataDict.ContainsKey(symbol))
326  {
327  _symbolDataDict[symbol].Reset();
328  _symbolDataDict.Remove(symbol);
329  }
330  }
331 
332  // initialize data for added securities
333  var addedSymbols = changes.AddedSecurities.ToDictionary(x => x.Symbol, x => x.Exchange.TimeZone);
334  algorithm.History(addedSymbols.Keys, _lookback * _period, _resolution)
335  .PushThrough(bar =>
336  {
337  ReturnsSymbolData symbolData;
338  if (!_symbolDataDict.TryGetValue(bar.Symbol, out symbolData))
339  {
340  symbolData = new ReturnsSymbolData(bar.Symbol, _lookback, _period);
341  _symbolDataDict.Add(bar.Symbol, symbolData);
342  }
343  // Convert the data timestamp to UTC
344  var utcTime = bar.EndTime.ConvertToUtc(addedSymbols[bar.Symbol]);
345  symbolData.Update(utcTime, bar.Value);
346  });
347  }
348 
349  /// <summary>
350  /// Calculate equilibrium returns and covariance
351  /// </summary>
352  /// <param name="returns">Matrix of returns where each column represents a security and each row returns for the given date/time (size: K x N)</param>
353  /// <param name="Σ">Multi-dimensional array of double with the portfolio covariance of returns (size: K x K).</param>
354  /// <returns>Array of double of equilibrium returns</returns>
355  public virtual double[] GetEquilibriumReturns(double[,] returns, out double[,] Σ)
356  {
357  // equal weighting scheme
358  var W = Vector.Create(returns.GetLength(1), 1.0 / returns.GetLength(1));
359  // annualized covariance
360  Σ = returns.Covariance().Multiply(252);
361  //annualized return
362  var annualReturn = W.Dot(Elementwise.Add(returns.Mean(0), 1.0).Pow(252.0).Subtract(1.0));
363  //annualized variance of return
364  var annualVariance = W.Dot(Σ.Dot(W));
365  // the risk aversion coefficient
366  var riskAversion = (annualReturn - _riskFreeRate) / annualVariance;
367  // the implied excess equilibrium return Vector (N x 1 column vector)
368  return Σ.Dot(W).Multiply(riskAversion);
369  }
370 
371  /// <summary>
372  /// Generate views from multiple alpha models
373  /// </summary>
374  /// <param name="insights">Array of insight that represent the investors' views</param>
375  /// <param name="P">A matrix that identifies the assets involved in the views (size: K x N)</param>
376  /// <param name="Q">A view vector (size: K x 1)</param>
377  protected bool TryGetViews(ICollection<Insight> insights, out double[,] P, out double[] Q)
378  {
379  try
380  {
381  var symbols = insights.Select(insight => insight.Symbol).ToHashSet();
382 
383  var tmpQ = insights.GroupBy(insight => insight.SourceModel)
384  .Select(values =>
385  {
386  var upInsightsSum = values.Where(i => i.Direction == InsightDirection.Up).Sum(i => Math.Abs(i.Magnitude.Value));
387  var dnInsightsSum = values.Where(i => i.Direction == InsightDirection.Down).Sum(i => Math.Abs(i.Magnitude.Value));
388  return new { View = values.Key, Q = upInsightsSum > dnInsightsSum ? upInsightsSum : dnInsightsSum };
389  })
390  .Where(x => x.Q != 0)
391  .ToDictionary(k => k.View, v => v.Q);
392 
393  var tmpP = insights.GroupBy(insight => insight.SourceModel)
394  .Select(values =>
395  {
396  var q = tmpQ[values.Key];
397  var results = values.ToDictionary(x => x.Symbol, insight =>
398  {
399  var value = (int)insight.Direction * Math.Abs(insight.Magnitude.Value);
400  return value / q;
401  });
402  // Add zero for other symbols that are listed but active insight
403  foreach (var symbol in symbols)
404  {
405  if (!results.ContainsKey(symbol))
406  {
407  results.Add(symbol, 0d);
408  }
409  }
410  return new { View = values.Key, Results = results };
411  })
412  .Where(r => !r.Results.Select(v => Math.Abs(v.Value)).Sum().IsNaNOrZero())
413  .ToDictionary(k => k.View, v => v.Results);
414 
415  P = Matrix.Create(tmpP.Select(d => d.Value.Values.ToArray()).ToArray());
416  Q = tmpQ.Values.ToArray();
417  }
418  catch
419  {
420  P = null;
421  Q = null;
422  return false;
423  }
424  return true;
425  }
426 
427  /// <summary>
428  /// Apply Black-Litterman master formula
429  /// http://www.blacklitterman.org/cookbook.html
430  /// </summary>
431  /// <param name="Π">Prior/Posterior mean array</param>
432  /// <param name="Σ">Prior/Posterior covariance matrix</param>
433  /// <param name="P">A matrix that identifies the assets involved in the views (size: K x N)</param>
434  /// <param name="Q">A view vector (size: K x 1)</param>
435  private void ApplyBlackLittermanMasterFormula(ref double[] Π, ref double[,] Σ, double[,] P, double[] Q)
436  {
437  // Create the diagonal covariance matrix of error terms from the expressed views
438  var eye = Matrix.Diagonal(Q.GetLength(0), 1);
439  var Ω = Elementwise.Multiply(P.Dot(Σ).DotWithTransposed(P).Multiply(_tau), eye);
440  if (Ω.Determinant() != 0)
441  {
442  // Define matrices Στ and A to avoid recalculations
443  var Στ = Σ.Multiply(_tau);
444  var A = Στ.DotWithTransposed(P).Dot(P.Dot(Στ).DotWithTransposed(P).Add(Ω).Inverse());
445 
446  // Compute posterior estimate of the mean: Black-Litterman "master equation"
447  Π = Π.Add(A.Dot(Q.Subtract(P.Dot(Π))));
448 
449  // Compute posterior estimate of the uncertainty in the mean
450  var M = Στ.Subtract(A.Dot(P).Dot(Στ));
451  Σ = Σ.Add(M).Multiply(_delta);
452  }
453  }
454  }
455 }