Lean  $LEAN_TAG$
SecurityIdentifier.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 System.Numerics;
20 using Newtonsoft.Json;
21 using ProtoBuf;
25 using QuantConnect.Logging;
27 using QuantConnect.Util;
28 using static QuantConnect.Messages;
29 
30 namespace QuantConnect
31 {
32  /// <summary>
33  /// Defines a unique identifier for securities
34  /// </summary>
35  /// <remarks>
36  /// The SecurityIdentifier contains information about a specific security.
37  /// This includes the symbol and other data specific to the SecurityType.
38  /// The symbol is limited to 12 characters
39  /// </remarks>
40  [JsonConverter(typeof(SecurityIdentifierJsonConverter))]
41  [ProtoContract(SkipConstructor = true)]
42  public class SecurityIdentifier : IEquatable<SecurityIdentifier>, IComparable<SecurityIdentifier>, IComparable
43  {
44  #region Empty, DefaultDate Fields
45 
46  private static readonly Dictionary<string, Type> TypeMapping = new();
47  private static readonly Dictionary<string, SecurityIdentifier> SecurityIdentifierCache = new();
48  private static readonly char[] InvalidCharacters = {'|', ' '};
49  private static readonly Lazy<IMapFileProvider> MapFileProvider = new(Composer.Instance.GetPart<IMapFileProvider>());
50 
51  /// <summary>
52  /// Gets an instance of <see cref="SecurityIdentifier"/> that is empty, that is, one with no symbol specified
53  /// </summary>
54  public static readonly SecurityIdentifier Empty = new SecurityIdentifier(string.Empty, 0);
55 
56  /// <summary>
57  /// Gets an instance of <see cref="SecurityIdentifier"/> that is explicitly no symbol
58  /// </summary>
59  public static readonly SecurityIdentifier None = new SecurityIdentifier("NONE", 0);
60 
61  /// <summary>
62  /// Gets the date to be used when it does not apply.
63  /// </summary>
64  public static readonly DateTime DefaultDate = DateTime.FromOADate(0);
65 
66  /// <summary>
67  /// Gets the set of invalids symbol characters
68  /// </summary>
69  public static readonly HashSet<char> InvalidSymbolCharacters = new HashSet<char>(InvalidCharacters);
70 
71  #endregion
72 
73  #region Scales, Widths and Market Maps
74 
75  // these values define the structure of the 'otherData'
76  // the constant width fields are used via modulus, so the width is the number of zeros specified,
77  // {put/call:1}{oa-date:5}{style:1}{strike:6}{strike-scale:2}{market:3}{security-type:2}
78 
79  private const ulong SecurityTypeWidth = 100;
80  private const ulong SecurityTypeOffset = 1;
81 
82  private const ulong MarketWidth = 1000;
83  private const ulong MarketOffset = SecurityTypeOffset * SecurityTypeWidth;
84 
85  private const int StrikeDefaultScale = 4;
86  private static readonly ulong StrikeDefaultScaleExpanded = Pow(10, StrikeDefaultScale);
87 
88  private const ulong StrikeScaleWidth = 100;
89  private const ulong StrikeScaleOffset = MarketOffset * MarketWidth;
90 
91  private const ulong StrikeWidth = 1000000;
92  private const ulong StrikeOffset = StrikeScaleOffset * StrikeScaleWidth;
93 
94  private const ulong OptionStyleWidth = 10;
95  private const ulong OptionStyleOffset = StrikeOffset * StrikeWidth;
96 
97  private const ulong DaysWidth = 100000;
98  private const ulong DaysOffset = OptionStyleOffset * OptionStyleWidth;
99 
100  private const ulong PutCallOffset = DaysOffset * DaysWidth;
101  private const ulong PutCallWidth = 10;
102 
103  #endregion
104 
105  #region Member variables
106 
107  [ProtoMember(1)]
108  private string _symbol;
109  [ProtoMember(2)]
110  private ulong _properties;
111  [ProtoMember(3)]
112  private SecurityIdentifier _underlying;
113  private bool _hashCodeSet;
114  private int _hashCode;
115  private decimal? _strikePrice;
116  private OptionStyle? _optionStyle;
117  private OptionRight? _optionRight;
118  private DateTime? _date;
119  private string _stringRep;
120  private string _market;
121 
122  #endregion
123 
124  #region Properties
125 
126  /// <summary>
127  /// Gets whether or not this <see cref="SecurityIdentifier"/> is a derivative,
128  /// that is, it has a valid <see cref="Underlying"/> property
129  /// </summary>
130  public bool HasUnderlying
131  {
132  get { return _underlying != null; }
133  }
134 
135  /// <summary>
136  /// Gets the underlying security identifier for this security identifier. When there is
137  /// no underlying, this property will return a value of <see cref="Empty"/>.
138  /// </summary>
140  {
141  get
142  {
143  if (_underlying == null)
144  {
145  throw new InvalidOperationException(Messages.SecurityIdentifier.NoUnderlyingForIdentifier);
146  }
147  return _underlying;
148  }
149  }
150 
151  /// <summary>
152  /// Gets the date component of this identifier. For equities this
153  /// is the first date the security traded. Technically speaking,
154  /// in LEAN, this is the first date mentioned in the map_files.
155  /// For futures and options this is the expiry date of the contract.
156  /// For other asset classes, this property will throw an
157  /// exception as the field is not specified.
158  /// </summary>
159  public DateTime Date
160  {
161  get
162  {
163  if (_date.HasValue)
164  {
165  return _date.Value;
166  }
167 
168  switch (SecurityType)
169  {
170  case SecurityType.Base:
171  case SecurityType.Equity:
172  case SecurityType.Option:
173  case SecurityType.Future:
174  case SecurityType.Index:
175  case SecurityType.FutureOption:
176  case SecurityType.IndexOption:
177  case SecurityType.CryptoFuture:
178  var oadate = ExtractFromProperties(DaysOffset, DaysWidth);
179  _date = DateTime.FromOADate(oadate);
180  return _date.Value;
181  default:
182  throw new InvalidOperationException(Messages.SecurityIdentifier.DateNotSupportedBySecurityType);
183  }
184  }
185  }
186 
187  /// <summary>
188  /// Gets the original symbol used to generate this security identifier.
189  /// For equities, by convention this is the first ticker symbol for which
190  /// the security traded
191  /// </summary>
192  public string Symbol
193  {
194  get { return _symbol; }
195  }
196 
197  /// <summary>
198  /// Gets the market component of this security identifier. If located in the
199  /// internal mappings, the full string is returned. If the value is unknown,
200  /// the integer value is returned as a string.
201  /// </summary>
202  public string Market
203  {
204  get
205  {
206  if (_market == null)
207  {
208  var marketCode = ExtractFromProperties(MarketOffset, MarketWidth);
209  var market = QuantConnect.Market.Decode((int)marketCode);
210  // if we couldn't find it, send back the numeric representation
211  _market = market ?? marketCode.ToStringInvariant();
212  }
213  return _market;
214  }
215  }
216 
217  /// <summary>
218  /// Gets the security type component of this security identifier.
219  /// </summary>
220  [ProtoMember(4)]
221  public SecurityType SecurityType { get; }
222 
223  /// <summary>
224  /// Gets the option strike price. This only applies if SecurityType is Option,
225  /// IndexOption or FutureOption and will thrown anexception if accessed otherwise.
226  /// </summary>
227  public decimal StrikePrice
228  {
229  get
230  {
231  if (_strikePrice.HasValue)
232  {
233  return _strikePrice.Value;
234  }
235 
236  if (!SecurityType.IsOption())
237  {
238  throw new InvalidOperationException(Messages.SecurityIdentifier.StrikePriceNotSupportedBySecurityType);
239  }
240 
241  // performance: lets calculate strike price once
242  var scale = ExtractFromProperties(StrikeScaleOffset, StrikeScaleWidth);
243  var unscaled = ExtractFromProperties(StrikeOffset, StrikeWidth);
244  var pow = Math.Pow(10, (int)scale - StrikeDefaultScale);
245  // If the 20th bit is set to 1, we have a negative strike price.
246  // Let's normalize the strike and explicitly make it negative
247  if (((unscaled >> 19) & 1) == 1)
248  {
249  _strikePrice = -((unscaled ^ 1 << 19) * (decimal)pow);
250  }
251  else
252  {
253  _strikePrice = unscaled * (decimal)pow;
254  }
255 
256  return _strikePrice.Value;
257  }
258  }
259 
260  /// <summary>
261  /// Gets the option type component of this security identifier. This
262  /// only applies if SecurityType is Option, IndexOption or FutureOption
263  /// and will throw an exception if accessed otherwise.
264  /// </summary>
265  public OptionRight OptionRight
266  {
267  get
268  {
269  if (_optionRight.HasValue)
270  {
271  return _optionRight.Value;
272  }
273 
274  if (!SecurityType.IsOption())
275  {
276  throw new InvalidOperationException(Messages.SecurityIdentifier.OptionRightNotSupportedBySecurityType);
277  }
278  _optionRight = (OptionRight)ExtractFromProperties(PutCallOffset, PutCallWidth);
279  return _optionRight.Value;
280  }
281  }
282 
283  /// <summary>
284  /// Gets the option style component of this security identifier. This
285  /// only applies if SecurityType is Option, IndexOption or FutureOption
286  /// and will throw an exception if accessed otherwise.
287  /// </summary>
288  public OptionStyle OptionStyle
289  {
290  get
291  {
292  if (_optionStyle.HasValue)
293  {
294  return _optionStyle.Value;
295  }
296 
297  if (!SecurityType.IsOption())
298  {
299  throw new InvalidOperationException(Messages.SecurityIdentifier.OptionStyleNotSupportedBySecurityType);
300  }
301 
302  _optionStyle = (OptionStyle)(ExtractFromProperties(OptionStyleOffset, OptionStyleWidth));
303  return _optionStyle.Value;
304  }
305  }
306 
307  #endregion
308 
309  #region Constructors
310 
311  /// <summary>
312  /// Initializes a new instance of the <see cref="SecurityIdentifier"/> class
313  /// </summary>
314  /// <param name="symbol">The base36 string encoded as a long using alpha [0-9A-Z]</param>
315  /// <param name="properties">Other data defining properties of the symbol including market,
316  /// security type, listing or expiry date, strike/call/put/style for options, ect...</param>
317  public SecurityIdentifier(string symbol, ulong properties)
318  {
319  if (symbol == null)
320  {
321  throw new ArgumentNullException(nameof(symbol), Messages.SecurityIdentifier.NullSymbol);
322  }
323  if (symbol.IndexOfAny(InvalidCharacters) != -1)
324  {
325  throw new ArgumentException(Messages.SecurityIdentifier.SymbolWithInvalidCharacters, nameof(symbol));
326  }
327  _symbol = symbol;
328  _properties = properties;
329  _underlying = null;
330  _strikePrice = null;
331  _optionStyle = null;
332  _optionRight = null;
333  _date = null;
334  SecurityType = (SecurityType)ExtractFromProperties(SecurityTypeOffset, SecurityTypeWidth, properties);
335  if (!SecurityType.IsValid())
336  {
337  throw new ArgumentException(Messages.SecurityIdentifier.PropertiesDoNotMatchAnySecurityType, nameof(properties));
338  }
339  _hashCode = unchecked (symbol.GetHashCode() * 397) ^ properties.GetHashCode();
340  _hashCodeSet = true;
341  }
342 
343  /// <summary>
344  /// Initializes a new instance of the <see cref="SecurityIdentifier"/> class
345  /// </summary>
346  /// <param name="symbol">The base36 string encoded as a long using alpha [0-9A-Z]</param>
347  /// <param name="properties">Other data defining properties of the symbol including market,
348  /// security type, listing or expiry date, strike/call/put/style for options, ect...</param>
349  /// <param name="underlying">Specifies a <see cref="SecurityIdentifier"/> that represents the underlying security</param>
350  public SecurityIdentifier(string symbol, ulong properties, SecurityIdentifier underlying)
351  : this(symbol, properties)
352  {
353  if (symbol == null)
354  {
355  throw new ArgumentNullException(nameof(symbol), Messages.SecurityIdentifier.NullSymbol);
356  }
357  _symbol = symbol;
358  _properties = properties;
359  // performance: directly call Equals(SecurityIdentifier other), shortcuts Equals(object other)
360  if (!underlying.Equals(Empty))
361  {
362  _underlying = underlying;
363  }
364  }
365 
366  #endregion
367 
368  #region AddMarket, GetMarketCode, and Generate
369 
370  /// <summary>
371  /// Generates a new <see cref="SecurityIdentifier"/> for an option
372  /// </summary>
373  /// <param name="expiry">The date the option expires</param>
374  /// <param name="underlying">The underlying security's symbol</param>
375  /// <param name="market">The market</param>
376  /// <param name="strike">The strike price</param>
377  /// <param name="optionRight">The option type, call or put</param>
378  /// <param name="optionStyle">The option style, American or European</param>
379  /// <returns>A new <see cref="SecurityIdentifier"/> representing the specified option security</returns>
380  public static SecurityIdentifier GenerateOption(DateTime expiry,
381  SecurityIdentifier underlying,
382  string market,
383  decimal strike,
384  OptionRight optionRight,
385  OptionStyle optionStyle)
386  {
387  return GenerateOption(expiry, underlying, null, market, strike, optionRight, optionStyle);
388  }
389 
390  /// <summary>
391  /// Generates a new <see cref="SecurityIdentifier"/> for an option
392  /// </summary>
393  /// <param name="expiry">The date the option expires</param>
394  /// <param name="underlying">The underlying security's symbol</param>
395  /// <param name="targetOption">The target option ticker. This is useful when the option ticker does not match the underlying, e.g. SPX index and the SPXW weekly option. If null is provided will use underlying</param>
396  /// <param name="market">The market</param>
397  /// <param name="strike">The strike price</param>
398  /// <param name="optionRight">The option type, call or put</param>
399  /// <param name="optionStyle">The option style, American or European</param>
400  /// <returns>A new <see cref="SecurityIdentifier"/> representing the specified option security</returns>
401  public static SecurityIdentifier GenerateOption(DateTime expiry,
402  SecurityIdentifier underlying,
403  string targetOption,
404  string market,
405  decimal strike,
406  OptionRight optionRight,
407  OptionStyle optionStyle)
408  {
409  if (string.IsNullOrEmpty(targetOption))
410  {
411  if (underlying.SecurityType == SecurityType.Future)
412  {
413  // Futures options tickers might not match, so we need
414  // to map the provided future Symbol to the actual future option Symbol.
415  targetOption = FuturesOptionsSymbolMappings.Map(underlying.Symbol);
416  }
417  else
418  {
419  // by default the target option matches the underlying symbol
420  targetOption = underlying.Symbol;
421  }
422  }
423 
424  return Generate(expiry, targetOption, QuantConnect.Symbol.GetOptionTypeFromUnderlying(underlying.SecurityType), market, strike, optionRight, optionStyle, underlying);
425  }
426 
427  /// <summary>
428  /// Generates a new <see cref="SecurityIdentifier"/> for a future
429  /// </summary>
430  /// <param name="expiry">The date the future expires</param>
431  /// <param name="symbol">The security's symbol</param>
432  /// <param name="market">The market</param>
433  /// <returns>A new <see cref="SecurityIdentifier"/> representing the specified futures security</returns>
434  public static SecurityIdentifier GenerateFuture(DateTime expiry,
435  string symbol,
436  string market)
437  {
438  return Generate(expiry, symbol, SecurityType.Future, market);
439  }
440 
441  /// <summary>
442  /// Helper overload that will search the mapfiles to resolve the first date. This implementation
443  /// uses the configured <see cref="IMapFileProvider"/> via the <see cref="Composer.Instance"/>
444  /// </summary>
445  /// <param name="symbol">The symbol as it is known today</param>
446  /// <param name="market">The market</param>
447  /// <param name="mapSymbol">Specifies if symbol should be mapped using map file provider</param>
448  /// <param name="mapFileProvider">Specifies the IMapFileProvider to use for resolving symbols, specify null to load from Composer</param>
449  /// <param name="mappingResolveDate">The date to use to resolve the map file. Default value is <see cref="DateTime.Today"/></param>
450  /// <returns>A new <see cref="SecurityIdentifier"/> representing the specified symbol today</returns>
451  public static SecurityIdentifier GenerateEquity(string symbol, string market, bool mapSymbol = true, IMapFileProvider mapFileProvider = null, DateTime? mappingResolveDate = null)
452  {
453  var firstDate = DefaultDate;
454  if (mapSymbol)
455  {
456  var firstTickerDate = GetFirstTickerAndDate(mapFileProvider ?? MapFileProvider.Value, symbol, market, SecurityType.Equity, mappingResolveDate: mappingResolveDate);
457  firstDate = firstTickerDate.Item2;
458  symbol = firstTickerDate.Item1;
459  }
460 
461  return GenerateEquity(firstDate, symbol, market);
462  }
463 
464  /// <summary>
465  /// For the given symbol will resolve the ticker it used at the requested date
466  /// </summary>
467  /// <param name="symbol">The symbol to get the ticker for</param>
468  /// <param name="date">The date to map the symbol to</param>
469  /// <returns>The ticker for a date and symbol</returns>
470  public static string Ticker(Symbol symbol, DateTime date)
471  {
472  if (symbol.RequiresMapping())
473  {
474  var resolver = MapFileProvider.Value.Get(AuxiliaryDataKey.Create(symbol));
475  var mapfile = resolver.ResolveMapFile(symbol);
476 
477  return mapfile.GetMappedSymbol(date.Date, symbol.Value);
478  }
479 
480  return symbol.Value;
481  }
482 
483  /// <summary>
484  /// Generates a new <see cref="SecurityIdentifier"/> for an equity
485  /// </summary>
486  /// <param name="date">The first date this security traded (in LEAN this is the first date in the map_file</param>
487  /// <param name="symbol">The ticker symbol this security traded under on the <paramref name="date"/></param>
488  /// <param name="market">The security's market</param>
489  /// <returns>A new <see cref="SecurityIdentifier"/> representing the specified equity security</returns>
490  public static SecurityIdentifier GenerateEquity(DateTime date, string symbol, string market)
491  {
492  return Generate(date, symbol, SecurityType.Equity, market);
493  }
494 
495  /// <summary>
496  /// Generates a new <see cref="SecurityIdentifier"/> for a <see cref="ConstituentsUniverseData"/>.
497  /// Note that the symbol ticker is case sensitive here.
498  /// </summary>
499  /// <param name="symbol">The ticker to use for this constituent identifier</param>
500  /// <param name="securityType">The security type of this constituent universe</param>
501  /// <param name="market">The security's market</param>
502  /// <remarks>This method is special in the sense that it does not force the Symbol to be upper
503  /// which is required to determine the source file of the constituent
504  /// <see cref="ConstituentsUniverseData.GetSource(Data.SubscriptionDataConfig,DateTime,bool)"/></remarks>
505  /// <returns>A new <see cref="SecurityIdentifier"/> representing the specified constituent universe</returns>
506  public static SecurityIdentifier GenerateConstituentIdentifier(string symbol, SecurityType securityType, string market)
507  {
508  return Generate(DefaultDate, symbol, securityType, market, forceSymbolToUpper: false);
509  }
510 
511  /// <summary>
512  /// Generates the <see cref="Symbol"/> property for <see cref="QuantConnect.SecurityType.Base"/> security identifiers
513  /// </summary>
514  /// <param name="dataType">The base data custom data type if namespacing is required, null otherwise</param>
515  /// <param name="symbol">The ticker symbol</param>
516  /// <returns>The value used for the security identifier's <see cref="Symbol"/></returns>
517  public static string GenerateBaseSymbol(Type dataType, string symbol)
518  {
519  if (dataType == null)
520  {
521  return symbol;
522  }
523 
524  TypeMapping[dataType.Name] = dataType;
525  return $"{symbol.ToUpperInvariant()}.{dataType.Name}";
526  }
527 
528  /// <summary>
529  /// Tries to fetch the custom data type associated with a symbol
530  /// </summary>
531  /// <remarks>Custom data type <see cref="SecurityIdentifier"/> symbol value holds their data type</remarks>
532  public static bool TryGetCustomDataType(string symbol, out string type)
533  {
534  type = null;
535  if (!string.IsNullOrEmpty(symbol))
536  {
537  var index = symbol.LastIndexOf('.');
538  if (index != -1 && symbol.Length > index + 1)
539  {
540  type = symbol.Substring(index + 1);
541  return true;
542  }
543  }
544  return false;
545  }
546 
547  /// <summary>
548  /// Tries to fetch the custom data type associated with a symbol
549  /// </summary>
550  /// <remarks>Custom data type <see cref="SecurityIdentifier"/> symbol value holds their data type</remarks>
551  public static bool TryGetCustomDataTypeInstance(string symbol, out Type type)
552  {
553  type = null;
554  return TryGetCustomDataType(symbol, out var strType) && TypeMapping.TryGetValue(strType, out type);
555  }
556 
557  /// <summary>
558  /// Generates a new <see cref="SecurityIdentifier"/> for a custom security with the option of providing the first date
559  /// </summary>
560  /// <param name="dataType">The custom data type</param>
561  /// <param name="symbol">The ticker symbol of this security</param>
562  /// <param name="market">The security's market</param>
563  /// <param name="mapSymbol">Whether or not we should map this symbol</param>
564  /// <param name="date">First date that the security traded on</param>
565  /// <returns>A new <see cref="SecurityIdentifier"/> representing the specified base security</returns>
566  public static SecurityIdentifier GenerateBase(Type dataType, string symbol, string market, bool mapSymbol = false, DateTime? date = null)
567  {
568  var firstDate = date ?? DefaultDate;
569 
570  if (mapSymbol)
571  {
572  var firstTickerDate = GetFirstTickerAndDate(MapFileProvider.Value, symbol, market, SecurityType.Equity);
573  firstDate = firstTickerDate.Item2;
574  symbol = firstTickerDate.Item1;
575  }
576 
577  return Generate(
578  firstDate,
579  GenerateBaseSymbol(dataType, symbol),
580  SecurityType.Base,
581  market,
582  forceSymbolToUpper: false
583  );
584  }
585 
586  /// <summary>
587  /// Generates a new <see cref="SecurityIdentifier"/> for a forex pair
588  /// </summary>
589  /// <param name="symbol">The currency pair in the format similar to: 'EURUSD'</param>
590  /// <param name="market">The security's market</param>
591  /// <returns>A new <see cref="SecurityIdentifier"/> representing the specified forex pair</returns>
592  public static SecurityIdentifier GenerateForex(string symbol, string market)
593  {
594  return Generate(DefaultDate, symbol, SecurityType.Forex, market);
595  }
596 
597  /// <summary>
598  /// Generates a new <see cref="SecurityIdentifier"/> for a Crypto pair
599  /// </summary>
600  /// <param name="symbol">The currency pair in the format similar to: 'EURUSD'</param>
601  /// <param name="market">The security's market</param>
602  /// <returns>A new <see cref="SecurityIdentifier"/> representing the specified Crypto pair</returns>
603  public static SecurityIdentifier GenerateCrypto(string symbol, string market)
604  {
605  return Generate(DefaultDate, symbol, SecurityType.Crypto, market);
606  }
607 
608  /// <summary>
609  /// Generates a new <see cref="SecurityIdentifier"/> for a CryptoFuture pair
610  /// </summary>
611  /// <param name="expiry">The date the future expires</param>
612  /// <param name="symbol">The currency pair in the format similar to: 'EURUSD'</param>
613  /// <param name="market">The security's market</param>
614  /// <returns>A new <see cref="SecurityIdentifier"/> representing the specified CryptoFuture pair</returns>
615  public static SecurityIdentifier GenerateCryptoFuture(DateTime expiry, string symbol, string market)
616  {
617  return Generate(expiry, symbol, SecurityType.CryptoFuture, market);
618  }
619 
620  /// <summary>
621  /// Generates a new <see cref="SecurityIdentifier"/> for a CFD security
622  /// </summary>
623  /// <param name="symbol">The CFD contract symbol</param>
624  /// <param name="market">The security's market</param>
625  /// <returns>A new <see cref="SecurityIdentifier"/> representing the specified CFD security</returns>
626  public static SecurityIdentifier GenerateCfd(string symbol, string market)
627  {
628  return Generate(DefaultDate, symbol, SecurityType.Cfd, market);
629  }
630 
631  /// <summary>
632  /// Generates a new <see cref="SecurityIdentifier"/> for a INDEX security
633  /// </summary>
634  /// <param name="symbol">The Index contract symbol</param>
635  /// <param name="market">The security's market</param>
636  /// <returns>A new <see cref="SecurityIdentifier"/> representing the specified INDEX security</returns>
637  public static SecurityIdentifier GenerateIndex(string symbol, string market)
638  {
639  return Generate(DefaultDate, symbol, SecurityType.Index, market);
640  }
641 
642  /// <summary>
643  /// Generic generate method. This method should be used carefully as some parameters are not required and
644  /// some parameters mean different things for different security types
645  /// </summary>
646  private static SecurityIdentifier Generate(DateTime date,
647  string symbol,
648  SecurityType securityType,
649  string market,
650  decimal strike = 0,
651  OptionRight optionRight = 0,
652  OptionStyle optionStyle = 0,
653  SecurityIdentifier underlying = null,
654  bool forceSymbolToUpper = true)
655  {
656  if ((ulong)securityType >= SecurityTypeWidth || securityType < 0)
657  {
658  throw new ArgumentOutOfRangeException(nameof(securityType), Messages.SecurityIdentifier.InvalidSecurityType(nameof(securityType)));
659  }
660  if ((int)optionRight > 1 || optionRight < 0)
661  {
662  throw new ArgumentOutOfRangeException(nameof(optionRight), Messages.SecurityIdentifier.InvalidOptionRight(nameof(optionRight)));
663  }
664  if (date < Time.BeginningOfTime)
665  {
666  throw new ArgumentOutOfRangeException(date.ToStringInvariant(), $"date must be after the earliest possible date {Time.BeginningOfTime}");
667  }
668 
669  // normalize input strings
670  symbol = forceSymbolToUpper ? symbol.LazyToUpper() : symbol;
671 
672  var marketIdentifier = GetMarketIdentifier(market);
673 
674  var days = (ulong)date.ToOADate() * DaysOffset;
675  var marketCode = (ulong)marketIdentifier * MarketOffset;
676 
677  var strk = NormalizeStrike(strike, out ulong strikeScale) * StrikeOffset;
678  strikeScale *= StrikeScaleOffset;
679  var style = (ulong)optionStyle * OptionStyleOffset;
680  var putcall = (ulong)optionRight * PutCallOffset;
681 
682  var otherData = putcall + days + style + strk + strikeScale + marketCode + (ulong)securityType;
683 
684  var result = new SecurityIdentifier(symbol, otherData, underlying ?? Empty);
685 
686  // we already have these so lets set them. Massive performance improvement!
687  switch (securityType)
688  {
689  case SecurityType.Base:
690  case SecurityType.Equity:
691  case SecurityType.Future:
692  result._date = date;
693  break;
694  case SecurityType.Option:
695  case SecurityType.IndexOption:
696  case SecurityType.FutureOption:
697  result._date = date;
698  result._strikePrice = strike;
699  result._optionRight = optionRight;
700  result._optionStyle = optionStyle;
701  break;
702  }
703  return result;
704  }
705 
706  /// <summary>
707  /// Resolves the first ticker/date of the security represented by <paramref name="tickerToday"/>
708  /// </summary>
709  /// <param name="mapFileProvider">The IMapFileProvider instance used for resolving map files</param>
710  /// <param name="tickerToday">The security's ticker as it trades today</param>
711  /// <param name="market">The market the security exists in</param>
712  /// <param name="securityType">The securityType the security exists in</param>
713  /// <param name="mappingResolveDate">The date to use to resolve the map file. Default value is <see cref="DateTime.Today"/></param>
714  /// <returns>The security's first ticker/date if mapping data available, otherwise, the provided ticker and DefaultDate are returned</returns>
715  private static Tuple<string, DateTime> GetFirstTickerAndDate(IMapFileProvider mapFileProvider, string tickerToday, string market, SecurityType securityType, DateTime? mappingResolveDate = null)
716  {
717  var resolver = mapFileProvider.Get(new AuxiliaryDataKey(market, securityType));
718  var mapFile = resolver.ResolveMapFile(tickerToday, mappingResolveDate ?? DateTime.Today);
719 
720  // if we have mapping data, use the first ticker/date from there, otherwise use provided ticker and DefaultDate
721  return mapFile.Any()
722  ? Tuple.Create(mapFile.FirstTicker, mapFile.FirstDate)
723  : Tuple.Create(tickerToday, DefaultDate);
724  }
725 
726  /// <summary>
727  /// The strike is normalized into deci-cents and then a scale factor
728  /// is also saved to bring it back to un-normalized
729  /// </summary>
730  private static ulong NormalizeStrike(decimal strike, out ulong scale)
731  {
732  var str = strike;
733 
734  if (strike == 0)
735  {
736  scale = 0;
737  return 0;
738  }
739 
740  // convert strike to default scaling, this keeps the scale always positive
741  strike *= StrikeDefaultScaleExpanded;
742 
743  scale = 0;
744  while (strike % 10 == 0)
745  {
746  strike /= 10;
747  scale++;
748  }
749 
750  // Since our max precision was previously capped at 999999 and it had 20 bits set,
751  // we sacrifice a single bit from the strike price to allow for negative strike prices.
752  // 475711 is the maximum value that can be represented when setting the negative bit because
753  // any number greater than that will cause an overflow in the strike price width and increase
754  // its width to 7 digits.
755  // The idea behind this formula is to determine what number the overflow would happen at.
756  // We get the max number representable in 19 bits, subtract the width to normalize the value,
757  // and then get the difference between the 20 bit mask and the 19 bit normalized value to get
758  // the max strike price + 1. Subtract 1 to normalize the value, and we have established an exclusive
759  // upper bound.
760  const ulong negativeMask = 1 << 19;
761  const ulong maxStrikePrice = negativeMask - ((negativeMask ^ (negativeMask - 1)) - StrikeWidth) - 1;
762 
763  if (strike >= maxStrikePrice || strike <= -(long)maxStrikePrice)
764  {
765  throw new ArgumentException(Messages.SecurityIdentifier.InvalidStrikePrice(str));
766  }
767 
768  var encodedStrike = (long)strike;
769  if (strike < 0)
770  {
771  // Flip the sign
772  encodedStrike = -encodedStrike;
773 
774  // Sets the 20th bit equal to 1
775  encodedStrike |= 1 << 19;
776  }
777 
778  return (ulong)encodedStrike;
779  }
780 
781  /// <summary>
782  /// Accurately performs the integer exponentiation
783  /// </summary>
784  private static ulong Pow(uint x, int pow)
785  {
786  // don't use Math.Pow(double, double) due to precision issues
787  return (ulong)BigInteger.Pow(x, pow);
788  }
789 
790  #endregion
791 
792  #region Parsing routines
793 
794  /// <summary>
795  /// Parses the specified string into a <see cref="SecurityIdentifier"/>
796  /// The string must be a 40 digit number. The first 20 digits must be parseable
797  /// to a 64 bit unsigned integer and contain ancillary data about the security.
798  /// The second 20 digits must also be parseable as a 64 bit unsigned integer and
799  /// contain the symbol encoded from base36, this provides for 12 alpha numeric case
800  /// insensitive characters.
801  /// </summary>
802  /// <param name="value">The string value to be parsed</param>
803  /// <returns>A new <see cref="SecurityIdentifier"/> instance if the <paramref name="value"/> is able to be parsed.</returns>
804  /// <exception cref="FormatException">This exception is thrown if the string's length is not exactly 40 characters, or
805  /// if the components are unable to be parsed as 64 bit unsigned integers</exception>
806  public static SecurityIdentifier Parse(string value)
807  {
808  Exception exception;
809  SecurityIdentifier identifier;
810  if (!TryParse(value, out identifier, out exception))
811  {
812  throw exception;
813  }
814 
815  return identifier;
816  }
817 
818  /// <summary>
819  /// Attempts to parse the specified <see paramref="value"/> as a <see cref="SecurityIdentifier"/>.
820  /// </summary>
821  /// <param name="value">The string value to be parsed</param>
822  /// <param name="identifier">The result of parsing, when this function returns true, <paramref name="identifier"/>
823  /// was properly created and reflects the input string, when this function returns false <paramref name="identifier"/>
824  /// will equal default(SecurityIdentifier)</param>
825  /// <returns>True on success, otherwise false</returns>
826  public static bool TryParse(string value, out SecurityIdentifier identifier)
827  {
828  Exception exception;
829  return TryParse(value, out identifier, out exception);
830  }
831 
832  /// <summary>
833  /// Helper method impl to be used by parse and tryparse
834  /// </summary>
835  private static bool TryParse(string value, out SecurityIdentifier identifier, out Exception exception)
836  {
837  if (!TryParseProperties(value, out exception, out identifier))
838  {
839  return false;
840  }
841 
842  return true;
843  }
844 
845  private static readonly char[] SplitSpace = {' '};
846 
847  private static void CacheSid(string key, SecurityIdentifier identifier)
848  {
849  lock (SecurityIdentifierCache)
850  {
851  // limit the cache size to help with memory usage
852  if (SecurityIdentifierCache.Count >= 600000)
853  {
854  SecurityIdentifierCache.Clear();
855  }
856 
857  SecurityIdentifierCache[key] = identifier;
858  }
859  }
860 
861  /// <summary>
862  /// Parses the string into its component ulong pieces
863  /// </summary>
864  private static bool TryParseProperties(string value, out Exception exception, out SecurityIdentifier identifier)
865  {
866  exception = null;
867 
868  if (value == null)
869  {
870  identifier = Empty;
871  return true;
872  }
873 
874  lock (SecurityIdentifierCache)
875  {
876  // for performance, we first verify if we already have parsed this SecurityIdentifier
877  if (SecurityIdentifierCache.TryGetValue(value, out identifier))
878  {
879  return identifier != null;
880  }
881 
882  if (string.IsNullOrWhiteSpace(value) || value == " 0")
883  {
884  // we know it's not null already let's cache it
885  identifier = Empty;
886  CacheSid(value, identifier);
887  return true;
888  }
889 
890  // after calling TryGetValue because if it failed it will set identifier to default
891  identifier = Empty;
892 
893  try
894  {
895  var sids = value.Split('|');
896  for (var i = sids.Length - 1; i > -1; i--)
897  {
898  var current = sids[i];
899  var parts = current.Split(SplitSpace, StringSplitOptions.RemoveEmptyEntries);
900  if (parts.Length != 2)
901  {
902  exception = new FormatException(Messages.SecurityIdentifier.StringIsNotSplittable);
903  return false;
904  }
905 
906  var symbol = parts[0];
907  var otherData = parts[1];
908  var props = otherData.DecodeBase36();
909 
910  if (!identifier.Equals(Empty) || !SecurityIdentifierCache.TryGetValue(current, out var cachedIdentifier))
911  {
912  // toss the previous in as the underlying, if Empty, ignored by ctor
913  identifier = new SecurityIdentifier(symbol, props, identifier);
914 
915  // the following method will test if the market is supported/valid
916  GetMarketIdentifier(identifier.Market);
917 
918  var key = i < sids.Length - 1 ? $"{current}|{sids[i + 1]}" : current;
919  CacheSid(key, identifier);
920  }
921  else
922  {
923  // we already have this value in the cache, just return it
924  identifier = cachedIdentifier;
925  }
926  }
927  }
928  catch (Exception error)
929  {
930  exception = error;
931  Log.Error($@"SecurityIdentifier.TryParseProperties(): {
932  Messages.SecurityIdentifier.ErrorParsingSecurityIdentifier(value, exception)}");
933  CacheSid(value, null);
934  return false;
935  }
936 
937  return true;
938  }
939  }
940 
941  /// <summary>
942  /// Extracts the embedded value from _otherData
943  /// </summary>
944  private ulong ExtractFromProperties(ulong offset, ulong width)
945  {
946  return ExtractFromProperties(offset, width, _properties);
947  }
948 
949  /// <summary>
950  /// Extracts the embedded value from _otherData
951  /// </summary>
952  /// <remarks>Static so it can be used in <see cref="SecurityIdentifier"/> initialization</remarks>
953  private static ulong ExtractFromProperties(ulong offset, ulong width, ulong properties)
954  {
955  return (properties / offset) % width;
956  }
957 
958  /// <summary>
959  /// Gets the market code for the specified market. Raise exception if the market is not found
960  /// </summary>
961  /// <param name="market">The market to check for (case sensitive)</param>
962  /// <returns>The internal code used for the market. Corresponds to the value used when calling <see cref="Market.Add"/></returns>
963  private static int GetMarketIdentifier(string market)
964  {
965  market = market.ToLowerInvariant();
966 
967  var marketIdentifier = QuantConnect.Market.Encode(market);
968  if (marketIdentifier.HasValue)
969  {
970  return marketIdentifier.Value;
971  }
972 
973  throw new ArgumentOutOfRangeException(nameof(market), Messages.SecurityIdentifier.MarketNotFound(market));
974  }
975  #endregion
976 
977  #region Equality members and ToString
978 
979  /// <summary>Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. </summary>
980  /// <param name="other">An object to compare with this instance. </param>
981  /// <returns>A value that indicates the relative order of the objects being compared. The return value has these meanings: Value Meaning Less than zero This instance precedes <paramref name="other" /> in the sort order. Zero This instance occurs in the same position in the sort order as <paramref name="other" />. Greater than zero This instance follows <paramref name="other" /> in the sort order. </returns>
982  public int CompareTo(SecurityIdentifier other)
983  {
984  if (ReferenceEquals(this, other))
985  {
986  return 0;
987  }
988 
989  if (ReferenceEquals(null, other))
990  {
991  return 1;
992  }
993 
994  return string.Compare(ToString(), other.ToString(), StringComparison.Ordinal);
995  }
996 
997  /// <summary>Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object.</summary>
998  /// <param name="obj">An object to compare with this instance. </param>
999  /// <returns>A value that indicates the relative order of the objects being compared. The return value has these meanings: Value Meaning Less than zero This instance precedes <paramref name="obj" /> in the sort order. Zero This instance occurs in the same position in the sort order as <paramref name="obj" />. Greater than zero This instance follows <paramref name="obj" /> in the sort order. </returns>
1000  /// <exception cref="T:System.ArgumentException">
1001  /// <paramref name="obj" /> is not the same type as this instance. </exception>
1002  public int CompareTo(object obj)
1003  {
1004  if (ReferenceEquals(null, obj))
1005  {
1006  return 1;
1007  }
1008 
1009  if (ReferenceEquals(this, obj))
1010  {
1011  return 0;
1012  }
1013 
1014  if (!(obj is SecurityIdentifier))
1015  {
1016  throw new ArgumentException(Messages.SecurityIdentifier.UnexpectedTypeToCompareTo);
1017  }
1018 
1019  return CompareTo((SecurityIdentifier) obj);
1020  }
1021 
1022  /// <summary>
1023  /// Indicates whether the current object is equal to another object of the same type.
1024  /// </summary>
1025  /// <returns>
1026  /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
1027  /// </returns>
1028  /// <param name="other">An object to compare with this object.</param>
1029  public bool Equals(SecurityIdentifier other)
1030  {
1031  return ReferenceEquals(this, other) || _properties == other._properties
1032  && _symbol == other._symbol
1033  && _underlying == other._underlying;
1034  }
1035 
1036  /// <summary>
1037  /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
1038  /// </summary>
1039  /// <returns>
1040  /// true if the specified object is equal to the current object; otherwise, false.
1041  /// </returns>
1042  /// <param name="obj">The object to compare with the current object. </param><filterpriority>2</filterpriority>
1043  public override bool Equals(object obj)
1044  {
1045  if (ReferenceEquals(null, obj)) return false;
1046  if (obj.GetType() != GetType()) return false;
1047  return Equals((SecurityIdentifier)obj);
1048  }
1049 
1050  /// <summary>
1051  /// Serves as a hash function for a particular type.
1052  /// </summary>
1053  /// <returns>
1054  /// A hash code for the current <see cref="T:System.Object"/>.
1055  /// </returns>
1056  /// <filterpriority>2</filterpriority>
1057  public override int GetHashCode()
1058  {
1059  if (!_hashCodeSet)
1060  {
1061  _hashCode = unchecked(_symbol.GetHashCode() * 397) ^ _properties.GetHashCode();
1062  _hashCodeSet = true;
1063  }
1064  return _hashCode;
1065  }
1066 
1067  /// <summary>
1068  /// Override equals operator
1069  /// </summary>
1070  public static bool operator ==(SecurityIdentifier left, SecurityIdentifier right)
1071  {
1072  return Equals(left, right);
1073  }
1074 
1075  /// <summary>
1076  /// Override not equals operator
1077  /// </summary>
1078  public static bool operator !=(SecurityIdentifier left, SecurityIdentifier right)
1079  {
1080  return !Equals(left, right);
1081  }
1082 
1083  /// <summary>
1084  /// Returns a string that represents the current object.
1085  /// </summary>
1086  /// <returns>
1087  /// A string that represents the current object.
1088  /// </returns>
1089  /// <filterpriority>2</filterpriority>
1090  public override string ToString()
1091  {
1092  if (_stringRep == null)
1093  {
1094  var props = _properties.EncodeBase36();
1095  props = props.Length == 0 ? "0" : props;
1096  _stringRep = HasUnderlying ? $"{_symbol} {props}|{_underlying}" : $"{_symbol} {props}";
1097  }
1098 
1099  return _stringRep;
1100  }
1101 
1102  #endregion
1103  }
1104 }