/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Numerics; using Newtonsoft.Json; using ProtoBuf; using QuantConnect.Configuration; using QuantConnect.Data.UniverseSelection; using QuantConnect.Interfaces; using QuantConnect.Logging; using QuantConnect.Securities.Future; using QuantConnect.Util; using static QuantConnect.StringExtensions; namespace QuantConnect { /// /// Defines a unique identifier for securities /// /// /// The SecurityIdentifier contains information about a specific security. /// This includes the symbol and other data specific to the SecurityType. /// The symbol is limited to 12 characters /// [JsonConverter(typeof(SecurityIdentifierJsonConverter))] [ProtoContract(SkipConstructor = true)] public class SecurityIdentifier : IEquatable { #region Empty, DefaultDate Fields private static readonly ConcurrentDictionary SecurityIdentifierCache = new ConcurrentDictionary(); private static readonly string MapFileProviderTypeName = Config.Get("map-file-provider", "LocalDiskMapFileProvider"); private static readonly char[] InvalidCharacters = {'|', ' '}; private static readonly Lazy MapFileProvider = new Lazy( () => Composer.Instance.GetExportedValueByTypeName(MapFileProviderTypeName) ); /// /// Gets an instance of that is empty, that is, one with no symbol specified /// public static readonly SecurityIdentifier Empty = new SecurityIdentifier(string.Empty, 0); /// /// Gets an instance of that is explicitly no symbol /// public static readonly SecurityIdentifier None = new SecurityIdentifier("NONE", 0); /// /// Gets the date to be used when it does not apply. /// public static readonly DateTime DefaultDate = DateTime.FromOADate(0); /// /// Gets the set of invalids symbol characters /// public static readonly HashSet InvalidSymbolCharacters = new HashSet(InvalidCharacters); #endregion #region Scales, Widths and Market Maps // these values define the structure of the 'otherData' // the constant width fields are used via modulus, so the width is the number of zeros specified, // {put/call:1}{oa-date:5}{style:1}{strike:6}{strike-scale:2}{market:3}{security-type:2} private const ulong SecurityTypeWidth = 100; private const ulong SecurityTypeOffset = 1; private const ulong MarketWidth = 1000; private const ulong MarketOffset = SecurityTypeOffset * SecurityTypeWidth; private const int StrikeDefaultScale = 4; private static readonly ulong StrikeDefaultScaleExpanded = Pow(10, StrikeDefaultScale); private const ulong StrikeScaleWidth = 100; private const ulong StrikeScaleOffset = MarketOffset * MarketWidth; private const ulong StrikeWidth = 1000000; private const ulong StrikeOffset = StrikeScaleOffset * StrikeScaleWidth; private const ulong OptionStyleWidth = 10; private const ulong OptionStyleOffset = StrikeOffset * StrikeWidth; private const ulong DaysWidth = 100000; private const ulong DaysOffset = OptionStyleOffset * OptionStyleWidth; private const ulong PutCallOffset = DaysOffset * DaysWidth; private const ulong PutCallWidth = 10; #endregion #region Member variables [ProtoMember(1)] private string _symbol; [ProtoMember(2)] private ulong _properties; [ProtoMember(3)] private SecurityIdentifier _underlying; private bool _hashCodeSet; private int _hashCode; private decimal? _strikePrice; private OptionStyle? _optionStyle; private OptionRight? _optionRight; private DateTime? _date; private string _stringRep; private string _market; #endregion #region Properties /// /// Gets whether or not this is a derivative, /// that is, it has a valid property /// public bool HasUnderlying { get { return _underlying != null; } } /// /// Gets the underlying security identifier for this security identifier. When there is /// no underlying, this property will return a value of . /// public SecurityIdentifier Underlying { get { if (_underlying == null) { throw new InvalidOperationException("No underlying specified for this identifier. Check that HasUnderlying is true before accessing the Underlying property."); } return _underlying; } } /// /// Gets the date component of this identifier. For equities this /// is the first date the security traded. Technically speaking, /// in LEAN, this is the first date mentioned in the map_files. /// For futures and options this is the expiry date of the contract. /// For other asset classes, this property will throw an /// exception as the field is not specified. /// public DateTime Date { get { try { return _date.Value; } catch (InvalidOperationException) { switch (SecurityType) { case SecurityType.Base: case SecurityType.Equity: case SecurityType.Option: case SecurityType.Future: case SecurityType.FutureOption: var oadate = ExtractFromProperties(DaysOffset, DaysWidth); _date = DateTime.FromOADate(oadate); return _date.Value; default: throw new InvalidOperationException("Date is only defined for SecurityType.Equity, SecurityType.Option, SecurityType.Future, SecurityType.FutureOption, and SecurityType.Base"); } } } } /// /// Gets the original symbol used to generate this security identifier. /// For equities, by convention this is the first ticker symbol for which /// the security traded /// public string Symbol { get { return _symbol; } } /// /// Gets the market component of this security identifier. If located in the /// internal mappings, the full string is returned. If the value is unknown, /// the integer value is returned as a string. /// public string Market { get { if (_market == null) { var marketCode = ExtractFromProperties(MarketOffset, MarketWidth); var market = QuantConnect.Market.Decode((int)marketCode); // if we couldn't find it, send back the numeric representation _market = market ?? marketCode.ToStringInvariant(); } return _market; } } /// /// Gets the security type component of this security identifier. /// [ProtoMember(4)] public SecurityType SecurityType { get; } /// /// Gets the option strike price. This only applies to SecurityType.Option /// and will thrown anexception if accessed otherwse. /// public decimal StrikePrice { get { try { // will throw 'InvalidOperationException' if not set return _strikePrice.Value; } catch (InvalidOperationException) { if (SecurityType != SecurityType.Option && SecurityType != SecurityType.FutureOption) { throw new InvalidOperationException("OptionType is only defined for SecurityType.Option and SecurityType.FutureOption"); } // performance: lets calculate strike price once var scale = ExtractFromProperties(StrikeScaleOffset, StrikeScaleWidth); var unscaled = ExtractFromProperties(StrikeOffset, StrikeWidth); var pow = Math.Pow(10, (int)scale - StrikeDefaultScale); // If the 20th bit is set to 1, we have a negative strike price. // Let's normalize the strike and explicitly make it negative if (((unscaled >> 19) & 1) == 1) { _strikePrice = -((unscaled ^ 1 << 19) * (decimal)pow); } else { _strikePrice = unscaled * (decimal)pow; } return _strikePrice.Value; } } } /// /// Gets the option type component of this security identifier. This /// only applies to SecurityType.Open and will throw an exception if /// accessed otherwise. /// public OptionRight OptionRight { get { try { // will throw 'InvalidOperationException' if not set return _optionRight.Value; } catch (InvalidOperationException) { if (SecurityType != SecurityType.Option && SecurityType != SecurityType.FutureOption) { throw new InvalidOperationException("OptionRight is only defined for SecurityType.Option and SecurityType.FutureOption"); } _optionRight = (OptionRight)ExtractFromProperties(PutCallOffset, PutCallWidth); return _optionRight.Value; } } } /// /// Gets the option style component of this security identifier. This /// only applies to SecurityType.Open and will throw an exception if /// accessed otherwise. /// public OptionStyle OptionStyle { get { try { // will throw 'InvalidOperationException' if not set return _optionStyle.Value; } catch (InvalidOperationException) { if (SecurityType != SecurityType.Option && SecurityType != SecurityType.FutureOption) { throw new InvalidOperationException("OptionStyle is only defined for SecurityType.Option and SecurityType.FutureOption"); } _optionStyle = (OptionStyle)(ExtractFromProperties(OptionStyleOffset, OptionStyleWidth)); return _optionStyle.Value; } } } #endregion #region Constructors /// /// Initializes a new instance of the class /// /// The base36 string encoded as a long using alpha [0-9A-Z] /// Other data defining properties of the symbol including market, /// security type, listing or expiry date, strike/call/put/style for options, ect... public SecurityIdentifier(string symbol, ulong properties) { if (symbol == null) { throw new ArgumentNullException(nameof(symbol), "SecurityIdentifier requires a non-null string 'symbol'"); } if (symbol.IndexOfAny(InvalidCharacters) != -1) { throw new ArgumentException("symbol must not contain the characters '|' or ' '.", nameof(symbol)); } _symbol = symbol; _properties = properties; _underlying = null; _strikePrice = null; _optionStyle = null; _optionRight = null; _date = null; SecurityType = (SecurityType)ExtractFromProperties(SecurityTypeOffset, SecurityTypeWidth, properties); if (!SecurityType.IsValid()) { throw new ArgumentException($"The provided properties do not match with a valid {nameof(SecurityType)}", "properties"); } _hashCode = unchecked (symbol.GetHashCode() * 397) ^ properties.GetHashCode(); _hashCodeSet = true; } /// /// Initializes a new instance of the class /// /// The base36 string encoded as a long using alpha [0-9A-Z] /// Other data defining properties of the symbol including market, /// security type, listing or expiry date, strike/call/put/style for options, ect... /// Specifies a that represents the underlying security public SecurityIdentifier(string symbol, ulong properties, SecurityIdentifier underlying) : this(symbol, properties) { if (symbol == null) { throw new ArgumentNullException(nameof(symbol), "SecurityIdentifier requires a non-null string 'symbol'"); } _symbol = symbol; _properties = properties; // performance: directly call Equals(SecurityIdentifier other), shortcuts Equals(object other) if (!underlying.Equals(Empty)) { _underlying = underlying; } } #endregion #region AddMarket, GetMarketCode, and Generate /// /// Generates a new for an option /// /// The date the option expires /// The underlying security's symbol /// The market /// The strike price /// The option type, call or put /// The option style, American or European /// A new representing the specified option security public static SecurityIdentifier GenerateOption(DateTime expiry, SecurityIdentifier underlying, string market, decimal strike, OptionRight optionRight, OptionStyle optionStyle) { return Generate(expiry, underlying.Symbol, QuantConnect.Symbol.GetOptionTypeFromUnderlying(underlying.SecurityType), market, strike, optionRight, optionStyle, underlying); } /// /// Generates a new for a future /// /// The date the future expires /// The security's symbol /// The market /// A new representing the specified futures security public static SecurityIdentifier GenerateFuture(DateTime expiry, string symbol, string market) { return Generate(expiry, symbol, SecurityType.Future, market); } /// /// Helper overload that will search the mapfiles to resolve the first date. This implementation /// uses the configured via the /// /// The symbol as it is known today /// The market /// Specifies if symbol should be mapped using map file provider /// Specifies the IMapFileProvider to use for resolving symbols, specify null to load from Composer /// The date to use to resolve the map file. Default value is /// A new representing the specified symbol today public static SecurityIdentifier GenerateEquity(string symbol, string market, bool mapSymbol = true, IMapFileProvider mapFileProvider = null, DateTime? mappingResolveDate = null) { var firstDate = DefaultDate; if (mapSymbol) { var firstTickerDate = GetFirstTickerAndDate(mapFileProvider ?? MapFileProvider.Value, symbol, market, mappingResolveDate: mappingResolveDate); firstDate = firstTickerDate.Item2; symbol = firstTickerDate.Item1; } return GenerateEquity(firstDate, symbol, market); } /// /// Generates a new for an equity /// /// The first date this security traded (in LEAN this is the first date in the map_file /// The ticker symbol this security traded under on the /// The security's market /// A new representing the specified equity security public static SecurityIdentifier GenerateEquity(DateTime date, string symbol, string market) { return Generate(date, symbol, SecurityType.Equity, market); } /// /// Generates a new for a . /// Note that the symbol ticker is case sensitive here. /// /// The ticker to use for this constituent identifier /// The security type of this constituent universe /// The security's market /// This method is special in the sense that it does not force the Symbol to be upper /// which is required to determine the source file of the constituent /// /// A new representing the specified constituent universe public static SecurityIdentifier GenerateConstituentIdentifier(string symbol, SecurityType securityType, string market) { return Generate(DefaultDate, symbol, securityType, market, forceSymbolToUpper: false); } /// /// Generates the property for security identifiers /// /// The base data custom data type if namespacing is required, null otherwise /// The ticker symbol /// The value used for the security identifier's public static string GenerateBaseSymbol(Type dataType, string symbol) { if (dataType == null) { return symbol; } return $"{symbol.ToUpperInvariant()}.{dataType.Name}"; } /// /// Generates a new for a custom security with the option of providing the first date /// /// The custom data type /// The ticker symbol of this security /// The security's market /// Whether or not we should map this symbol /// First date that the security traded on /// A new representing the specified base security public static SecurityIdentifier GenerateBase(Type dataType, string symbol, string market, bool mapSymbol = false, DateTime? date = null) { var firstDate = date ?? DefaultDate; if (mapSymbol) { var firstTickerDate = GetFirstTickerAndDate(MapFileProvider.Value, symbol, market); firstDate = firstTickerDate.Item2; symbol = firstTickerDate.Item1; } return Generate( firstDate, GenerateBaseSymbol(dataType, symbol), SecurityType.Base, market, forceSymbolToUpper: false ); } /// /// Generates a new for a forex pair /// /// The currency pair in the format similar to: 'EURUSD' /// The security's market /// A new representing the specified forex pair public static SecurityIdentifier GenerateForex(string symbol, string market) { return Generate(DefaultDate, symbol, SecurityType.Forex, market); } /// /// Generates a new for a Crypto pair /// /// The currency pair in the format similar to: 'EURUSD' /// The security's market /// A new representing the specified Crypto pair public static SecurityIdentifier GenerateCrypto(string symbol, string market) { return Generate(DefaultDate, symbol, SecurityType.Crypto, market); } /// /// Generates a new for a CFD security /// /// The CFD contract symbol /// The security's market /// A new representing the specified CFD security public static SecurityIdentifier GenerateCfd(string symbol, string market) { return Generate(DefaultDate, symbol, SecurityType.Cfd, market); } /// /// Generic generate method. This method should be used carefully as some parameters are not required and /// some parameters mean different things for different security types /// private static SecurityIdentifier Generate(DateTime date, string symbol, SecurityType securityType, string market, decimal strike = 0, OptionRight optionRight = 0, OptionStyle optionStyle = 0, SecurityIdentifier underlying = null, bool forceSymbolToUpper = true) { if ((ulong)securityType >= SecurityTypeWidth || securityType < 0) { throw new ArgumentOutOfRangeException(nameof(securityType), "securityType must be between 0 and 99"); } if ((int)optionRight > 1 || optionRight < 0) { throw new ArgumentOutOfRangeException(nameof(optionRight), "optionType must be either 0 or 1"); } // normalize input strings market = market.ToLowerInvariant(); symbol = forceSymbolToUpper ? symbol.LazyToUpper() : symbol; if (securityType == SecurityType.FutureOption) { // Futures options tickers might not match, so we need // to map the provided future Symbol to the actual future option Symbol. symbol = FuturesOptionsSymbolMappings.Map(symbol); } var marketIdentifier = QuantConnect.Market.Encode(market); if (!marketIdentifier.HasValue) { throw new ArgumentOutOfRangeException(nameof(market), "The specified market wasn't found in the markets lookup. " + $"Requested: {market}. You can add markets by calling QuantConnect.Market.AddMarket(string,ushort)" ); } var days = (ulong)date.ToOADate() * DaysOffset; var marketCode = (ulong)marketIdentifier * MarketOffset; ulong strikeScale; var strk = NormalizeStrike(strike, out strikeScale) * StrikeOffset; strikeScale *= StrikeScaleOffset; var style = (ulong)optionStyle * OptionStyleOffset; var putcall = (ulong)optionRight * PutCallOffset; var otherData = putcall + days + style + strk + strikeScale + marketCode + (ulong)securityType; var result = new SecurityIdentifier(symbol, otherData, underlying ?? Empty); // we already have these so lets set them switch (securityType) { case SecurityType.Base: case SecurityType.Equity: case SecurityType.Future: result._date = date; break; case SecurityType.Option: case SecurityType.FutureOption: result._date = date; result._strikePrice = strike; result._optionRight = optionRight; result._optionStyle = optionStyle; break; } return result; } /// /// Resolves the first ticker/date of the security represented by /// /// The IMapFileProvider instance used for resolving map files /// The security's ticker as it trades today /// The market the security exists in /// The date to use to resolve the map file. Default value is /// The security's first ticker/date if mapping data available, otherwise, the provided ticker and DefaultDate are returned private static Tuple GetFirstTickerAndDate(IMapFileProvider mapFileProvider, string tickerToday, string market, DateTime? mappingResolveDate = null) { var resolver = mapFileProvider.Get(market); var mapFile = resolver.ResolveMapFile(tickerToday, mappingResolveDate ?? DateTime.Today); // if we have mapping data, use the first ticker/date from there, otherwise use provided ticker and DefaultDate return mapFile.Any() ? Tuple.Create(mapFile.FirstTicker, mapFile.FirstDate) : Tuple.Create(tickerToday, DefaultDate); } /// /// Converts an upper case alpha numeric string into a long /// private static ulong DecodeBase36(string symbol) { var result = 0ul; var baseValue = 1ul; for (var i = symbol.Length - 1; i > -1; i--) { var c = symbol[i]; // assumes alpha numeric upper case only strings var value = (uint)(c <= 57 ? c - '0' : c - 'A' + 10); result += baseValue * value; baseValue *= 36; } return result; } /// /// Converts a long to an uppercase alpha numeric string /// private static string EncodeBase36(ulong data) { var stack = new Stack(15); while (data != 0) { var value = data % 36; var c = value < 10 ? (char)(value + '0') : (char)(value - 10 + 'A'); stack.Push(c); data /= 36; } return new string(stack.ToArray()); } /// /// The strike is normalized into deci-cents and then a scale factor /// is also saved to bring it back to un-normalized /// private static ulong NormalizeStrike(decimal strike, out ulong scale) { var str = strike; if (strike == 0) { scale = 0; return 0; } // convert strike to default scaling, this keeps the scale always positive strike *= StrikeDefaultScaleExpanded; scale = 0; while (strike % 10 == 0) { strike /= 10; scale++; } // Since our max precision was previously capped at 999999 and it had 20 bits set, // we sacrifice a single bit from the strike price to allow for negative strike prices. // 475711 is the maximum value that can be represented when setting the negative bit because // any number greater than that will cause an overflow in the strike price width and increase // its width to 7 digits. // The idea behind this formula is to determine what number the overflow would happen at. // We get the max number representable in 19 bits, subtract the width to normalize the value, // and then get the difference between the 20 bit mask and the 19 bit normalized value to get // the max strike price + 1. Subtract 1 to normalize the value, and we have established an exclusive // upper bound. const ulong negativeMask = 1 << 19; const ulong maxStrikePrice = negativeMask - ((negativeMask ^ (negativeMask - 1)) - StrikeWidth) - 1; if (strike >= maxStrikePrice || strike <= -(long)maxStrikePrice) { throw new ArgumentException(Invariant($"The specified strike price\'s precision is too high: {str}")); } var encodedStrike = (long)strike; if (strike < 0) { // Flip the sign encodedStrike = -encodedStrike; // Sets the 20th bit equal to 1 encodedStrike |= 1 << 19; } return (ulong)encodedStrike; } /// /// Accurately performs the integer exponentiation /// private static ulong Pow(uint x, int pow) { // don't use Math.Pow(double, double) due to precision issues return (ulong)BigInteger.Pow(x, pow); } #endregion #region Parsing routines /// /// Parses the specified string into a /// The string must be a 40 digit number. The first 20 digits must be parseable /// to a 64 bit unsigned integer and contain ancillary data about the security. /// The second 20 digits must also be parseable as a 64 bit unsigned integer and /// contain the symbol encoded from base36, this provides for 12 alpha numeric case /// insensitive characters. /// /// The string value to be parsed /// A new instance if the is able to be parsed. /// This exception is thrown if the string's length is not exactly 40 characters, or /// if the components are unable to be parsed as 64 bit unsigned integers public static SecurityIdentifier Parse(string value) { Exception exception; SecurityIdentifier identifier; if (!TryParse(value, out identifier, out exception)) { throw exception; } return identifier; } /// /// Attempts to parse the specified as a . /// /// The string value to be parsed /// The result of parsing, when this function returns true, /// was properly created and reflects the input string, when this function returns false /// will equal default(SecurityIdentifier) /// True on success, otherwise false public static bool TryParse(string value, out SecurityIdentifier identifier) { Exception exception; return TryParse(value, out identifier, out exception); } /// /// Helper method impl to be used by parse and tryparse /// private static bool TryParse(string value, out SecurityIdentifier identifier, out Exception exception) { if (!TryParseProperties(value, out exception, out identifier)) { return false; } return true; } private static readonly char[] SplitSpace = {' '}; /// /// Parses the string into its component ulong pieces /// private static bool TryParseProperties(string value, out Exception exception, out SecurityIdentifier identifier) { exception = null; if (string.IsNullOrWhiteSpace(value) || value == " 0") { identifier = Empty; return true; } // for performance, we first verify if we already have parsed this SecurityIdentifier if (SecurityIdentifierCache.TryGetValue(value, out identifier)) { return true; } // after calling TryGetValue because if it failed it will set identifier to default identifier = Empty; try { var sids = value.Split('|'); for (var i = sids.Length - 1; i > -1; i--) { var current = sids[i]; var parts = current.Split(SplitSpace, StringSplitOptions.RemoveEmptyEntries); if (parts.Length != 2) { exception = new FormatException("The string must be splittable on space into two parts."); return false; } var symbol = parts[0]; var otherData = parts[1]; var props = DecodeBase36(otherData); // toss the previous in as the underlying, if Empty, ignored by ctor identifier = new SecurityIdentifier(symbol, props, identifier); } } catch (Exception error) { exception = error; Log.Error($"SecurityIdentifier.TryParseProperties(): Error parsing SecurityIdentifier: '{value}', Exception: {exception}"); return false; } SecurityIdentifierCache.TryAdd(value, identifier); return true; } /// /// Extracts the embedded value from _otherData /// private ulong ExtractFromProperties(ulong offset, ulong width) { return ExtractFromProperties(offset, width, _properties); } /// /// Extracts the embedded value from _otherData /// /// Static so it can be used in initialization private static ulong ExtractFromProperties(ulong offset, ulong width, ulong properties) { return (properties / offset) % width; } #endregion #region Equality members and ToString /// /// Indicates whether the current object is equal to another object of the same type. /// /// /// true if the current object is equal to the parameter; otherwise, false. /// /// An object to compare with this object. public bool Equals(SecurityIdentifier other) { return ReferenceEquals(this, other) || _properties == other._properties && _symbol == other._symbol && _underlying == other._underlying; } /// /// Determines whether the specified is equal to the current . /// /// /// true if the specified object is equal to the current object; otherwise, false. /// /// The object to compare with the current object. 2 public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (obj.GetType() != GetType()) return false; return Equals((SecurityIdentifier)obj); } /// /// Serves as a hash function for a particular type. /// /// /// A hash code for the current . /// /// 2 public override int GetHashCode() { if (!_hashCodeSet) { _hashCode = unchecked(_symbol.GetHashCode() * 397) ^ _properties.GetHashCode(); _hashCodeSet = true; } return _hashCode; } /// /// Override equals operator /// public static bool operator ==(SecurityIdentifier left, SecurityIdentifier right) { return Equals(left, right); } /// /// Override not equals operator /// public static bool operator !=(SecurityIdentifier left, SecurityIdentifier right) { return !Equals(left, right); } /// /// Returns a string that represents the current object. /// /// /// A string that represents the current object. /// /// 2 public override string ToString() { if (_stringRep == null) { var props = EncodeBase36(_properties); props = props.Length == 0 ? "0" : props; _stringRep = HasUnderlying ? $"{_symbol} {props}|{_underlying}" : $"{_symbol} {props}"; } return _stringRep; } #endregion } }