/* * 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 QuantConnect.Configuration; using QuantConnect.Data.Auxiliary; using QuantConnect.Interfaces; using QuantConnect.Logging; using QuantConnect.Util; 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))] public struct 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 IMapFileProvider _mapFileProvider; private static readonly object _mapFileProviderLock = new object(); /// /// 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 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 private readonly string _symbol; private readonly ulong _properties; private readonly SidBox _underlying; private readonly int _hashCode; private decimal _strikePrice; #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.SecurityIdentifier; } } /// /// 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 options this is the expiry date. For futures this is the /// settlement date. For forex and cfds this property will throw an /// exception as the field is not specified. /// public DateTime Date { get { var stype = SecurityType; switch (stype) { case SecurityType.Equity: case SecurityType.Option: case SecurityType.Future: var oadate = ExtractFromProperties(DaysOffset, DaysWidth); return DateTime.FromOADate(oadate); default: throw new InvalidOperationException("Date is only defined for SecurityType.Equity, SecurityType.Option and SecurityType.Future"); } } } /// /// 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 { var marketCode = ExtractFromProperties(MarketOffset, MarketWidth); var market = QuantConnect.Market.Decode((int)marketCode); // if we couldn't find it, send back the numeric representation return market ?? marketCode.ToString(); } } /// /// Gets the security type component of this security identifier. /// 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 { if (SecurityType != SecurityType.Option) { throw new InvalidOperationException("OptionType is only defined for SecurityType.Option"); } // performance: lets calculate strike price once if (_strikePrice == -1) { var scale = ExtractFromProperties(StrikeScaleOffset, StrikeScaleWidth); var unscaled = ExtractFromProperties(StrikeOffset, StrikeWidth); var pow = Math.Pow(10, (int)scale - StrikeDefaultScale); _strikePrice = unscaled * (decimal)pow; } return _strikePrice; } } /// /// 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 { if (SecurityType != SecurityType.Option) { throw new InvalidOperationException("OptionRight is only defined for SecurityType.Option"); } return (OptionRight)ExtractFromProperties(PutCallOffset, PutCallWidth); } } /// /// 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 { if (SecurityType != SecurityType.Option) { throw new InvalidOperationException("OptionStyle is only defined for SecurityType.Option"); } return (OptionStyle)(ExtractFromProperties(OptionStyleOffset, OptionStyleWidth)); } } #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("symbol", "SecurityIdentifier requires a non-null string 'symbol'"); } if (symbol.IndexOfAny(InvalidCharacters) != -1) { throw new ArgumentException("symbol must not contain the characters '|' or ' '.", "symbol"); } _symbol = symbol; _properties = properties; _underlying = null; _strikePrice = -1; SecurityType = (SecurityType)ExtractFromProperties(SecurityTypeOffset, SecurityTypeWidth, properties); _hashCode = unchecked (symbol.GetHashCode() * 397) ^ properties.GetHashCode(); } /// /// 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("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 = new SidBox(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, SecurityType.Option, 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 /// A new representing the specified symbol today public static SecurityIdentifier GenerateEquity(string symbol, string market, bool mapSymbol = true, IMapFileProvider mapFileProvider = null) { if (mapSymbol) { MapFile mapFile; if (mapFileProvider == null) { lock (_mapFileProviderLock) { if (_mapFileProvider == null) { _mapFileProvider = Composer.Instance.GetExportedValueByTypeName(MapFileProviderTypeName); } mapFile = GetMapFile(_mapFileProvider, market, symbol); } } else { mapFile = GetMapFile(mapFileProvider, market, symbol); } var firstDate = mapFile.FirstDate; if (mapFile.Any()) { symbol = mapFile.FirstTicker; } return GenerateEquity(firstDate, symbol, market); } else { return GenerateEquity(DefaultDate, symbol, market); } } public static MapFile GetMapFile(IMapFileProvider mapFileProvider, string market, string symbol) { var resolver = mapFileProvider.Get(market); var mapFile = resolver.ResolveMapFile(symbol, DateTime.Today); return mapFile; } /// /// 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 custom security /// /// The ticker symbol of this security /// The security's market /// A new representing the specified base security public static SecurityIdentifier GenerateBase(string symbol, string market) { return Generate(DefaultDate, symbol, SecurityType.Base, market); } /// /// 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) { if ((ulong)securityType >= SecurityTypeWidth || securityType < 0) { throw new ArgumentOutOfRangeException("securityType", "securityType must be between 0 and 99"); } if ((int)optionRight > 1 || optionRight < 0) { throw new ArgumentOutOfRangeException("optionRight", "optionType must be either 0 or 1"); } // normalize input strings market = market.ToLower(); symbol = symbol.LazyToUpper(); var marketIdentifier = QuantConnect.Market.Encode(market); if (!marketIdentifier.HasValue) { throw new ArgumentOutOfRangeException("market", string.Format("The specified market wasn't found in the markets lookup. Requested: {0}. " + "You can add markets by calling QuantConnect.Market.AddMarket(string,ushort)", market)); } 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; return new SecurityIdentifier(symbol, otherData, underlying ?? Empty); } /// /// 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(); 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++; } if (strike >= 1000000) { throw new ArgumentException("The specified strike price's precision is too high: " + str); } return (ulong)strike; } /// /// 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)) { 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: '{0}', Exception: {1}", value, 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 _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() => _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() { var props = EncodeBase36(_properties); if (HasUnderlying) { return _symbol + ' ' + props + '|' + _underlying.SecurityIdentifier; } return _symbol + ' ' + props; } #endregion /// /// Provides a reference type container for a security identifier instance. /// This is used to maintain a reference to an underlying /// private sealed class SidBox : IEquatable { public readonly SecurityIdentifier SecurityIdentifier; public SidBox(SecurityIdentifier securityIdentifier) { SecurityIdentifier = securityIdentifier; } public bool Equals(SidBox other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; return SecurityIdentifier.Equals(other.SecurityIdentifier); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; return obj is SidBox && Equals((SidBox)obj); } public override int GetHashCode() { return SecurityIdentifier.GetHashCode(); } public static bool operator ==(SidBox left, SidBox right) { return Equals(left, right); } public static bool operator !=(SidBox left, SidBox right) { return !Equals(left, right); } } } }