Lean  $LEAN_TAG$
QC500UniverseSelectionModel.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;
22 
24 {
25  /// <summary>
26  /// Defines the QC500 universe as a universe selection model for framework algorithm
27  /// For details: https://github.com/QuantConnect/Lean/pull/1663
28  /// </summary>
30  {
31  private const int _numberOfSymbolsCoarse = 1000;
32  private const int _numberOfSymbolsFine = 500;
33 
34  // rebalances at the start of each month
35  private int _lastMonth = -1;
36  private readonly Dictionary<Symbol, double> _dollarVolumeBySymbol = new ();
37 
38  /// <summary>
39  /// Initializes a new default instance of the <see cref="QC500UniverseSelectionModel"/>
40  /// </summary>
42  : base(true)
43  {
44  }
45 
46  /// <summary>
47  /// Initializes a new instance of the <see cref="QC500UniverseSelectionModel"/>
48  /// </summary>
49  /// <param name="universeSettings">Universe settings defines what subscription properties will be applied to selected securities</param>
51  : base(true, universeSettings)
52  {
53  }
54 
55  /// <summary>
56  /// Performs coarse selection for the QC500 constituents.
57  /// The stocks must have fundamental data
58  /// The stock must have positive previous-day close price
59  /// The stock must have positive volume on the previous trading day
60  /// </summary>
61  public override IEnumerable<Symbol> SelectCoarse(QCAlgorithm algorithm, IEnumerable<CoarseFundamental> coarse)
62  {
63  if (algorithm.Time.Month == _lastMonth)
64  {
65  return Universe.Unchanged;
66  }
67 
68  var sortedByDollarVolume =
69  (from x in coarse
70  where x.HasFundamentalData && x.Volume > 0 && x.Price > 0
71  orderby x.DollarVolume descending
72  select x).Take(_numberOfSymbolsCoarse).ToList();
73 
74  _dollarVolumeBySymbol.Clear();
75  foreach (var x in sortedByDollarVolume)
76  {
77  _dollarVolumeBySymbol[x.Symbol] = x.DollarVolume;
78  }
79 
80  // If no security has met the QC500 criteria, the universe is unchanged.
81  // A new selection will be attempted on the next trading day as _lastMonth is not updated
82  if (_dollarVolumeBySymbol.Count == 0)
83  {
84  return Universe.Unchanged;
85  }
86 
87  return _dollarVolumeBySymbol.Keys;
88  }
89 
90  /// <summary>
91  /// Performs fine selection for the QC500 constituents
92  /// The company's headquarter must in the U.S.
93  /// The stock must be traded on either the NYSE or NASDAQ
94  /// At least half a year since its initial public offering
95  /// The stock's market cap must be greater than 500 million
96  /// </summary>
97  public override IEnumerable<Symbol> SelectFine(QCAlgorithm algorithm, IEnumerable<FineFundamental> fine)
98  {
99  var filteredFine =
100  (from x in fine
101  where x.CompanyReference.CountryId == "USA" &&
102  (x.CompanyReference.PrimaryExchangeID == "NYS" || x.CompanyReference.PrimaryExchangeID == "NAS") &&
103  (algorithm.Time - x.SecurityReference.IPODate).Days > 180 &&
104  x.MarketCap > 500000000m
105  select x).ToList();
106 
107  var count = filteredFine.Count;
108 
109  // If no security has met the QC500 criteria, the universe is unchanged.
110  // A new selection will be attempted on the next trading day as _lastMonth is not updated
111  if (count == 0)
112  {
113  return Universe.Unchanged;
114  }
115 
116  // Update _lastMonth after all QC500 criteria checks passed
117  _lastMonth = algorithm.Time.Month;
118 
119  var percent = _numberOfSymbolsFine / (double)count;
120 
121  // select stocks with top dollar volume in every single sector
122  var topFineBySector =
123  (from x in filteredFine
124  // Group by sector
125  group x by x.CompanyReference.IndustryTemplateCode into g
126  let y = from item in g
127  orderby _dollarVolumeBySymbol[item.Symbol] descending
128  select item
129  let c = (int)Math.Ceiling(y.Count() * percent)
130  select new { g.Key, Value = y.Take(c) }
131  ).ToDictionary(x => x.Key, x => x.Value);
132 
133  return topFineBySector.SelectMany(x => x.Value)
134  .OrderByDescending(x => _dollarVolumeBySymbol[x.Symbol])
135  .Take(_numberOfSymbolsFine)
136  .Select(x => x.Symbol);
137  }
138  }
139 }