Lean  $LEAN_TAG$
SectorWeightingPortfolioConstructionModel.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 Python.Runtime;
25 
27 {
28  /// <summary>
29  /// Provides an implementation of <see cref="IPortfolioConstructionModel"/> that generates percent targets based on the
30  /// <see cref="CompanyReference.IndustryTemplateCode"/>.
31  /// The target percent holdings of each sector is 1/S where S is the number of sectors and
32  /// the target percent holdings of each security is 1/N where N is the number of securities of each sector.
33  /// For insights of direction <see cref="InsightDirection.Up"/>, long targets are returned and for insights of direction
34  /// <see cref="InsightDirection.Down"/>, short targets are returned.
35  /// It will ignore <see cref="Insight"/> for symbols that have no <see cref="CompanyReference.IndustryTemplateCode"/> value.
36  /// </summary>
38  {
39  private readonly Dictionary<Symbol, string> _sectorCodeBySymbol = new Dictionary<Symbol, string>();
40 
41  /// <summary>
42  /// Initialize a new instance of <see cref="SectorWeightingPortfolioConstructionModel"/>
43  /// </summary>
44  /// <param name="rebalancingDateRules">The date rules used to define the next expected rebalance time
45  /// in UTC</param>
47  : base(rebalancingDateRules.ToFunc())
48  {
49  }
50 
51  /// <summary>
52  /// Initialize a new instance of <see cref="SectorWeightingPortfolioConstructionModel"/>
53  /// </summary>
54  /// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance time
55  /// or null if unknown, in which case the function will be called again in the next loop. Returning current time
56  /// will trigger rebalance. If null will be ignored</param>
57  public SectorWeightingPortfolioConstructionModel(Func<DateTime, DateTime?> rebalancingFunc)
58  : base(rebalancingFunc)
59  {
60  }
61 
62  /// <summary>
63  /// Initialize a new instance of <see cref="SectorWeightingPortfolioConstructionModel"/>
64  /// </summary>
65  /// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance UTC time.
66  /// Returning current time will trigger rebalance. If null will be ignored</param>
67  public SectorWeightingPortfolioConstructionModel(Func<DateTime, DateTime> rebalancingFunc)
68  : this(rebalancingFunc != null ? (Func<DateTime, DateTime?>)(timeUtc => rebalancingFunc(timeUtc)) : null)
69  {
70  }
71 
72  /// <summary>
73  /// Initialize a new instance of <see cref="SectorWeightingPortfolioConstructionModel"/>
74  /// </summary>
75  /// <param name="rebalance">Rebalancing func or if a date rule, timedelta will be converted into func.
76  /// For a given algorithm UTC DateTime the func returns the next expected rebalance time
77  /// or null if unknown, in which case the function will be called again in the next loop. Returning current time
78  /// will trigger rebalance. If null will be ignored</param>
79  /// <remarks>This is required since python net can not convert python methods into func nor resolve the correct
80  /// constructor for the date rules parameter.
81  /// For performance we prefer python algorithms using the C# implementation</remarks>
82  public SectorWeightingPortfolioConstructionModel(PyObject rebalance)
83  : this((Func<DateTime, DateTime?>)null)
84  {
85  SetRebalancingFunc(rebalance);
86  }
87 
88  /// <summary>
89  /// Initialize a new instance of <see cref="SectorWeightingPortfolioConstructionModel"/>
90  /// </summary>
91  /// <param name="timeSpan">Rebalancing frequency</param>
93  : this(dt => dt.Add(timeSpan))
94  {
95  }
96 
97  /// <summary>
98  /// Initialize a new instance of <see cref="SectorWeightingPortfolioConstructionModel"/>
99  /// </summary>
100  /// <param name="resolution">Rebalancing frequency</param>
102  : this(resolution.ToTimeSpan())
103  {
104  }
105 
106  /// <summary>
107  /// Method that will determine if the portfolio construction model should create a
108  /// target for this insight
109  /// </summary>
110  /// <param name="insight">The insight to create a target for</param>
111  /// <returns>True if the portfolio should create a target for the insight</returns>
112  protected override bool ShouldCreateTargetForInsight(Insight insight)
113  {
114  return _sectorCodeBySymbol.ContainsKey(insight.Symbol);
115  }
116 
117  /// <summary>
118  /// Will determine the target percent for each insight
119  /// </summary>
120  /// <param name="activeInsights">The active insights to generate a target for</param>
121  /// <returns>A target percent for each insight</returns>
122  protected override Dictionary<Insight, double> DetermineTargetPercent(List<Insight> activeInsights)
123  {
124  var result = new Dictionary<Insight, double>();
125 
126  var insightBySectorCode = new Dictionary<string, List<Insight>>();
127 
128  foreach (var insight in activeInsights)
129  {
130  if (insight.Direction == InsightDirection.Flat)
131  {
132  result[insight] = 0;
133  continue;
134  }
135 
136  List<Insight> insights;
137  var sectorCode = _sectorCodeBySymbol[insight.Symbol];
138  if (insightBySectorCode.TryGetValue(sectorCode, out insights))
139  {
140  insights.Add(insight);
141  }
142  else
143  {
144  insightBySectorCode[sectorCode] = new List<Insight> { insight };
145  }
146  }
147 
148  // give equal weighting to each sector
149  var sectorPercent = insightBySectorCode.Count == 0 ? 0 : 1m / insightBySectorCode.Count;
150 
151  foreach (var kvp in insightBySectorCode)
152  {
153  var insights = kvp.Value;
154 
155  // give equal weighting to each security
156  var count = insights.Count();
157  var percent = count == 0 ? 0 : sectorPercent / count;
158 
159  foreach (var insight in insights)
160  {
161  result[insight] = (double)((int)insight.Direction * percent);
162  }
163  }
164 
165  return result;
166  }
167 
168  /// <summary>
169  /// Event fired each time the we add/remove securities from the data feed
170  /// </summary>
171  /// <param name="algorithm">The algorithm instance that experienced the change in securities</param>
172  /// <param name="changes">The security additions and removals from the algorithm</param>
173  public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
174  {
175  foreach (var security in changes.RemovedSecurities)
176  {
177  // Removes the symbol from the _sectorCodeBySymbol dictionary
178  // since we cannot emit PortfolioTarget for removed securities
179  var symbol = security.Symbol;
180  if (_sectorCodeBySymbol.ContainsKey(symbol))
181  {
182  _sectorCodeBySymbol.Remove(symbol);
183  }
184  }
185 
186  foreach (var security in changes.AddedSecurities)
187  {
188  var sectorCode = GetSectorCode(security);
189  if (!string.IsNullOrEmpty(sectorCode))
190  {
191  _sectorCodeBySymbol[security.Symbol] = sectorCode;
192  }
193  }
194  base.OnSecuritiesChanged(algorithm, changes);
195  }
196 
197  /// <summary>
198  /// Gets the sector code
199  /// </summary>
200  /// <param name="security">The security to create a sector code for</param>
201  /// <returns>The value of the sector code for the security</returns>
202  /// <remarks>Other sectors can be defined using <see cref="AssetClassification"/></remarks>
203  protected virtual string GetSectorCode(Security security)
204  {
206  }
207  }
208 }