Lean  $LEAN_TAG$
RiskParityPortfolioConstructionModel.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;
17 using System.Collections.Generic;
18 using System.Linq;
19 using Accord.Math;
20 using Python.Runtime;
22 using QuantConnect.Data;
25 using QuantConnect.Util;
26 
28 {
29  /// <summary>
30  /// Risk Parity Portfolio Construction Model
31  /// </summary>
32  /// <remarks>Spinu, F. (2013). An algorithm for computing risk parity weights. Available at SSRN 2297383.
33  /// Available at https://papers.ssrn.com/sol3/Papers.cfm?abstract_id=2297383</remarks>
35  {
36  private readonly int _lookback;
37  private readonly int _period;
38  private readonly Resolution _resolution;
39  private readonly IPortfolioOptimizer _optimizer;
40  private readonly Dictionary<Symbol, ReturnsSymbolData> _symbolDataDict;
41 
42  /// <summary>
43  /// Initialize the model
44  /// </summary>
45  /// <param name="rebalancingDateRules">The date rules used to define the next expected rebalance time in UTC</param>
46  /// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
47  /// <param name="lookback">Historical return lookback period</param>
48  /// <param name="period">The time interval of history price to calculate the weight</param>
49  /// <param name="resolution">The resolution of the history price</param>
50  /// <param name="optimizer">The portfolio optimization algorithm. If the algorithm is not provided then the default will be mean-variance optimization.</param>
51  public RiskParityPortfolioConstructionModel(IDateRule rebalancingDateRules,
52  PortfolioBias portfolioBias = PortfolioBias.LongShort,
53  int lookback = 1,
54  int period = 252,
55  Resolution resolution = Resolution.Daily,
56  IPortfolioOptimizer optimizer = null)
57  : this(rebalancingDateRules.ToFunc(), portfolioBias, lookback, period, resolution, optimizer)
58  {
59  }
60 
61  /// <summary>
62  /// Initialize the model
63  /// </summary>
64  /// <param name="rebalanceResolution">Rebalancing frequency</param>
65  /// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
66  /// <param name="lookback">Historical return lookback period</param>
67  /// <param name="period">The time interval of history price to calculate the weight</param>
68  /// <param name="resolution">The resolution of the history price</param>
69  /// <param name="optimizer">The portfolio optimization algorithm. If the algorithm is not provided then the default will be mean-variance optimization.</param>
70  public RiskParityPortfolioConstructionModel(Resolution rebalanceResolution = Resolution.Daily,
71  PortfolioBias portfolioBias = PortfolioBias.LongShort,
72  int lookback = 1,
73  int period = 252,
74  Resolution resolution = Resolution.Daily,
75  IPortfolioOptimizer optimizer = null)
76  : this(rebalanceResolution.ToTimeSpan(), portfolioBias, lookback, period, resolution, optimizer)
77  {
78  }
79 
80  /// <summary>
81  /// Initialize the model
82  /// </summary>
83  /// <param name="timeSpan">Rebalancing frequency</param>
84  /// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
85  /// <param name="lookback">Historical return lookback period</param>
86  /// <param name="period">The time interval of history price to calculate the weight</param>
87  /// <param name="resolution">The resolution of the history price</param>
88  /// <param name="optimizer">The portfolio optimization algorithm. If the algorithm is not provided then the default will be mean-variance optimization.</param>
89  public RiskParityPortfolioConstructionModel(TimeSpan timeSpan,
90  PortfolioBias portfolioBias = PortfolioBias.LongShort,
91  int lookback = 1,
92  int period = 252,
93  Resolution resolution = Resolution.Daily,
94  IPortfolioOptimizer optimizer = null)
95  : this(dt => dt.Add(timeSpan), portfolioBias, lookback, period, resolution, optimizer)
96  {
97  }
98 
99  /// <summary>
100  /// Initialize the model
101  /// </summary>
102  /// <param name="rebalance">Rebalancing func or if a date rule, timedelta will be converted into func.
103  /// For a given algorithm UTC DateTime the func returns the next expected rebalance time
104  /// or null if unknown, in which case the function will be called again in the next loop. Returning current time
105  /// 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="optimizer">The portfolio optimization algorithm. If the algorithm is not provided then the default will be mean-variance optimization.</param>
111  /// <remarks>This is required since python net can not convert python methods into func nor resolve the correct
112  /// constructor for the date rules parameter.
113  /// For performance we prefer python algorithms using the C# implementation</remarks>
114  public RiskParityPortfolioConstructionModel(PyObject rebalance,
115  PortfolioBias portfolioBias = PortfolioBias.LongShort,
116  int lookback = 1,
117  int period = 252,
118  Resolution resolution = Resolution.Daily,
119  IPortfolioOptimizer optimizer = null)
120  : this((Func<DateTime, DateTime?>)null, portfolioBias, lookback, period, resolution, optimizer)
121  {
122  SetRebalancingFunc(rebalance);
123  }
124 
125  /// <summary>
126  /// Initialize the model
127  /// </summary>
128  /// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance UTC time.
129  /// Returning current time will trigger rebalance. If null will be ignored</param>
130  /// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
131  /// <param name="lookback">Historical return lookback period</param>
132  /// <param name="period">The time interval of history price to calculate the weight</param>
133  /// <param name="resolution">The resolution of the history price</param>
134  /// <param name="optimizer">The portfolio optimization algorithm. If the algorithm is not provided then the default will be mean-variance optimization.</param>
135  public RiskParityPortfolioConstructionModel(Func<DateTime, DateTime> rebalancingFunc,
136  PortfolioBias portfolioBias = PortfolioBias.LongShort,
137  int lookback = 1,
138  int period = 252,
139  Resolution resolution = Resolution.Daily,
140  IPortfolioOptimizer optimizer = null)
141  : this(rebalancingFunc != null ? (Func<DateTime, DateTime?>)(timeUtc => rebalancingFunc(timeUtc)) : null,
142  portfolioBias,
143  lookback,
144  period,
145  resolution,
146  optimizer)
147  {
148  }
149 
150  /// <summary>
151  /// Initialize the model
152  /// </summary>
153  /// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance time
154  /// or null if unknown, in which case the function will be called again in the next loop. Returning current time
155  /// will trigger rebalance.</param>
156  /// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
157  /// <param name="lookback">Historical return lookback period</param>
158  /// <param name="period">The time interval of history price to calculate the weight</param>
159  /// <param name="resolution">The resolution of the history price</param>
160  /// <param name="optimizer">The portfolio optimization algorithm. If the algorithm is not provided then the default will be mean-variance optimization.</param>
161  public RiskParityPortfolioConstructionModel(Func<DateTime, DateTime?> rebalancingFunc,
162  PortfolioBias portfolioBias = PortfolioBias.LongShort,
163  int lookback = 1,
164  int period = 252,
165  Resolution resolution = Resolution.Daily,
166  IPortfolioOptimizer optimizer = null)
167  : base(rebalancingFunc)
168  {
169  if (portfolioBias == PortfolioBias.Short)
170  {
171  throw new ArgumentException("Long position must be allowed in RiskParityPortfolioConstructionModel.");
172  }
173 
174  _lookback = lookback;
175  _period = period;
176  _resolution = resolution;
177 
178  _optimizer = optimizer ?? new RiskParityPortfolioOptimizer();
179 
180  _symbolDataDict = new Dictionary<Symbol, ReturnsSymbolData>();
181  }
182 
183  /// <summary>
184  /// Will determine the target percent for each insight
185  /// </summary>
186  /// <param name="activeInsights">The active insights to generate a target for</param>
187  /// <returns>A target percent for each insight</returns>
188  protected override Dictionary<Insight, double> DetermineTargetPercent(List<Insight> activeInsights)
189  {
190  var targets = new Dictionary<Insight, double>();
191 
192  // If we have no insights just return an empty target list
193  if (activeInsights.IsNullOrEmpty())
194  {
195  return targets;
196  }
197 
198  var symbols = activeInsights.Select(x => x.Symbol).ToList();
199 
200  // Get symbols' returns
201  var returns = _symbolDataDict.FormReturnsMatrix(symbols);
202 
203  // The optimization method processes the data frame
204  var w = _optimizer.Optimize(returns);
205 
206  // process results
207  if (w.Length > 0)
208  {
209  var sidx = 0;
210  foreach (var symbol in symbols)
211  {
212  var weight = w[sidx];
213  targets[activeInsights.First(insight => insight.Symbol == symbol)] = weight;
214 
215  sidx++;
216  }
217  }
218 
219  return targets;
220  }
221 
222  /// <summary>
223  /// Event fired each time the we add/remove securities from the data feed
224  /// </summary>
225  /// <param name="algorithm">The algorithm instance that experienced the change in securities</param>
226  /// <param name="changes">The security additions and removals from the algorithm</param>
227  public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
228  {
229  base.OnSecuritiesChanged(algorithm, changes);
230  // clean up data for removed securities
231  foreach (var removed in changes.RemovedSecurities)
232  {
233  _symbolDataDict.Remove(removed.Symbol, out var removedSymbolData);
234  algorithm.UnregisterIndicator(removedSymbolData.ROC);
235  }
236 
237  if (changes.AddedSecurities.Count == 0)
238  {
239  return;
240  }
241 
242  // initialize data for added securities
243  foreach (var added in changes.AddedSecurities)
244  {
245  if (!_symbolDataDict.ContainsKey(added.Symbol))
246  {
247  var symbolData = new ReturnsSymbolData(added.Symbol, _lookback, _period);
248  _symbolDataDict[added.Symbol] = symbolData;
249  algorithm.RegisterIndicator(added.Symbol, symbolData.ROC, _resolution);
250  }
251  }
252 
253  // warmup our indicators by pushing history through the consolidators
254  algorithm.History(changes.AddedSecurities.Select(security => security.Symbol), _lookback * _period, _resolution)
255  .PushThrough(bar =>
256  {
257  ReturnsSymbolData symbolData;
258  if (_symbolDataDict.TryGetValue(bar.Symbol, out symbolData))
259  {
260  symbolData.Update(bar.EndTime, bar.Value);
261  }
262  });
263  }
264  }
265 }