Lean  $LEAN_TAG$
BaseSymbolGenerator.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.Util;
18 using System;
19 using System.Collections.Generic;
20 using System.Linq;
21 
23 {
24  /// <summary>
25  /// Provide the base symbol generator implementation
26  /// </summary>
27  public abstract class BaseSymbolGenerator
28  {
29 
30  /// <summary>
31  /// <see cref="IRandomValueGenerator"/> instance producing random values for use in random data generation
32  /// </summary>
33  protected IRandomValueGenerator Random { get; }
34 
35  /// <summary>
36  /// Settings of current random data generation run
37  /// </summary>
39 
40  /// <summary>
41  /// Exchange hours and raw data times zones in various markets
42  /// </summary>
44 
45  /// <summary>
46  /// Access to specific properties for various symbols
47  /// </summary>
49 
50  // used to prevent generating duplicates, but also caps
51  // the memory allocated to checking for duplicates
52  private readonly FixedSizeHashQueue<Symbol> _symbols;
53 
54  /// <summary>
55  /// Base constructor implementation for Symbol generator
56  /// </summary>
57  /// <param name="settings">random data generation run settings</param>
58  /// <param name="random">produces random values for use in random data generation</param>
60  {
61  Settings = settings;
62  Random = random;
63  _symbols = new FixedSizeHashQueue<Symbol>(1000);
66  }
67 
68  /// <summary>
69  /// Creates a ad-hoc symbol generator depending on settings
70  /// </summary>
71  /// <param name="settings">random data generator settings</param>
72  /// <param name="random">produces random values for use in random data generation</param>
73  /// <returns>New symbol generator</returns>
75  {
76  if (settings is null)
77  {
78  throw new ArgumentNullException(nameof(settings), "Settings cannot be null or empty");
79  }
80 
81  if (random is null)
82  {
83  throw new ArgumentNullException(nameof(random), "Randomizer cannot be null");
84  }
85 
86  switch (settings.SecurityType)
87  {
88  case SecurityType.Option:
89  return new OptionSymbolGenerator(settings, random, 100m, 75m);
90 
91  case SecurityType.Future:
92  return new FutureSymbolGenerator(settings, random);
93 
94  default:
95  return new DefaultSymbolGenerator(settings, random);
96  }
97  }
98 
99  /// <summary>
100  /// Generates specified number of symbols
101  /// </summary>
102  /// <returns>Set of random symbols</returns>
103  public IEnumerable<Symbol> GenerateRandomSymbols()
104  {
105  if (!Settings.Tickers.IsNullOrEmpty())
106  {
107  foreach (var symbol in Settings.Tickers.SelectMany(GenerateAsset))
108  {
109  yield return symbol;
110  }
111  }
112  else
113  {
114  for (var i = 0; i < Settings.SymbolCount; i++)
115  {
116  foreach (var symbol in GenerateAsset())
117  {
118  yield return symbol;
119  }
120  }
121  }
122  }
123 
124  /// <summary>
125  /// Generates a random asset
126  /// </summary>
127  /// <param name="ticker">Optionally can provide a ticker that should be used</param>
128  /// <returns>Random asset</returns>
129  protected abstract IEnumerable<Symbol> GenerateAsset(string ticker = null);
130 
131  /// <summary>
132  /// Generates random symbol, used further down for asset
133  /// </summary>
134  /// <param name="securityType">security type</param>
135  /// <param name="market">market</param>
136  /// <param name="ticker">Optionally can provide a ticker to use</param>
137  /// <returns>Random symbol</returns>
138  public Symbol NextSymbol(SecurityType securityType, string market, string ticker = null)
139  {
140  if (securityType == SecurityType.Option || securityType == SecurityType.Future)
141  {
142  throw new ArgumentException("Please use OptionSymbolGenerator or FutureSymbolGenerator for SecurityType.Option and SecurityType.Future respectively.");
143  }
144 
145  if (ticker == null)
146  {
147  // we must return a Symbol matching an entry in the Symbol properties database
148  // if there is a wildcard entry, we can generate a truly random Symbol
149  // if there is no wildcard entry, the symbols we can generate are limited by the entries in the database
151  {
152  // let's make symbols all have 3 chars as it's acceptable for all security types with wildcard entries
153  ticker = NextUpperCaseString(3, 3);
154  }
155  else
156  {
157  ticker = NextTickerFromSymbolPropertiesDatabase(securityType, market);
158  }
159  }
160 
161  // by chance we may generate a ticker that actually exists, and if map files exist that match this
162  // ticker then we'll end up resolving the first trading date for use in the SID, otherwise, all
163  // generated Symbol will have a date equal to SecurityIdentifier.DefaultDate
164  var symbol = Symbol.Create(ticker, securityType, market);
165  if (_symbols.Add(symbol))
166  {
167  return symbol;
168  }
169 
170  // lo' and behold, we created a duplicate --recurse to find a unique value
171  // this is purposefully done as the last statement to enable the compiler to
172  // unroll this method into a tail-recursion loop :)
173  return NextSymbol(securityType, market);
174  }
175 
176  /// <summary>
177  /// Return a Ticker matching an entry in the Symbol properties database
178  /// </summary>
179  /// <param name="securityType">security type</param>
180  /// <param name="market"></param>
181  /// <returns>Random Ticker matching an entry in the Symbol properties database</returns>
182  protected string NextTickerFromSymbolPropertiesDatabase(SecurityType securityType, string market)
183  {
184  // prevent returning a ticker matching any previously generated Symbol
185  var existingTickers = _symbols
186  .Where(sym => sym.ID.Market == market && sym.ID.SecurityType == securityType)
187  .Select(sym => sym.Value);
188 
189  // get the available tickers from the Symbol properties database and remove previously generated tickers
190  var availableTickers = Enumerable.Except(SymbolPropertiesDatabase.GetSymbolPropertiesList(market, securityType)
191  .Select(kvp => kvp.Key.Symbol), existingTickers)
192  .ToList();
193 
194  // there is a limited number of entries in the Symbol properties database so we may run out of tickers
195  if (availableTickers.Count == 0)
196  {
197  throw new NoTickersAvailableException(securityType, market);
198  }
199 
200  return availableTickers[Random.NextInt(availableTickers.Count)];
201  }
202 
203  /// <summary>
204  /// Generates random expiration date on a friday within specified time range
205  /// </summary>
206  /// <param name="marketHours">market hours</param>
207  /// <param name="minExpiry">minimum expiration date</param>
208  /// <param name="maxExpiry">maximum expiration date</param>
209  /// <returns>Random date on a friday within specified time range</returns>
210  protected DateTime GetRandomExpiration(SecurityExchangeHours marketHours, DateTime minExpiry, DateTime maxExpiry)
211  {
212  // generate a random expiration date on a friday
213  var expiry = Random.NextDate(minExpiry, maxExpiry, DayOfWeek.Friday);
214 
215  // check to see if we're open on this date and if not, back track until we are
216  // we're using the equity market hours as a proxy since we haven't generated the option Symbol yet
217  while (!marketHours.IsDateOpen(expiry))
218  {
219  expiry = expiry.AddDays(-1);
220  }
221 
222  return expiry;
223  }
224 
225  /// <summary>
226  /// Generates a random <see cref="string"/> within the specified lengths.
227  /// </summary>
228  /// <param name="minLength">The minimum length, inclusive</param>
229  /// <param name="maxLength">The maximum length, inclusive</param>
230  /// <returns>A new upper case string within the specified lengths</returns>
231  public string NextUpperCaseString(int minLength, int maxLength)
232  {
233  var str = string.Empty;
234  var length = Random.NextInt(minLength, maxLength);
235  for (int i = 0; i < length; i++)
236  {
237  // A=65 - inclusive lower bound
238  // Z=90 - inclusive upper bound
239  var c = (char)Random.NextInt(65, 91);
240  str += c;
241  }
242 
243  return str;
244  }
245 
246  /// <summary>
247  /// Returns the number of symbols with the specified parameters can be generated.
248  /// Returns int.MaxValue if there is no limit for the given parameters.
249  /// </summary>
250  /// <returns>The number of available symbols for the given parameters, or int.MaxValue if no limit</returns>
251  public abstract int GetAvailableSymbolCount();
252  }
253 }