17 using System.Collections.Generic;
19 using System.Numerics;
20 using Newtonsoft.Json;
41 [ProtoContract(SkipConstructor =
true)]
42 public class SecurityIdentifier : IEquatable<SecurityIdentifier>, IComparable<SecurityIdentifier>, IComparable
44 #region Empty, DefaultDate Fields
46 private static readonly Dictionary<string, Type> TypeMapping =
new();
47 private static readonly Dictionary<string, SecurityIdentifier> SecurityIdentifierCache =
new();
48 private static readonly
char[] InvalidCharacters = {
'|',
' '};
64 public static readonly DateTime
DefaultDate = DateTime.FromOADate(0);
73 #region Scales, Widths and Market Maps
79 private const ulong SecurityTypeWidth = 100;
80 private const ulong SecurityTypeOffset = 1;
82 private const ulong MarketWidth = 1000;
83 private const ulong MarketOffset = SecurityTypeOffset * SecurityTypeWidth;
85 private const int StrikeDefaultScale = 4;
86 private static readonly ulong StrikeDefaultScaleExpanded = Pow(10, StrikeDefaultScale);
88 private const ulong StrikeScaleWidth = 100;
89 private const ulong StrikeScaleOffset = MarketOffset * MarketWidth;
91 private const ulong StrikeWidth = 1000000;
92 private const ulong StrikeOffset = StrikeScaleOffset * StrikeScaleWidth;
94 private const ulong OptionStyleWidth = 10;
95 private const ulong OptionStyleOffset = StrikeOffset * StrikeWidth;
97 private const ulong DaysWidth = 100000;
98 private const ulong DaysOffset = OptionStyleOffset * OptionStyleWidth;
100 private const ulong PutCallOffset = DaysOffset * DaysWidth;
101 private const ulong PutCallWidth = 10;
105 #region Member variables
108 private string _symbol;
110 private ulong _properties;
113 private bool _hashCodeSet;
114 private int _hashCode;
115 private decimal? _strikePrice;
118 private DateTime? _date;
119 private string _stringRep;
120 private string _market;
132 get {
return _underlying !=
null; }
143 if (_underlying ==
null)
178 var oadate = ExtractFromProperties(DaysOffset, DaysWidth);
179 _date = DateTime.FromOADate(oadate);
194 get {
return _symbol; }
208 var marketCode = ExtractFromProperties(MarketOffset, MarketWidth);
211 _market = market ?? marketCode.ToStringInvariant();
231 if (_strikePrice.HasValue)
233 return _strikePrice.Value;
242 var scale = ExtractFromProperties(StrikeScaleOffset, StrikeScaleWidth);
243 var unscaled = ExtractFromProperties(StrikeOffset, StrikeWidth);
244 var pow = Math.Pow(10, (
int)scale - StrikeDefaultScale);
247 if (((unscaled >> 19) & 1) == 1)
249 _strikePrice = -((unscaled ^ 1 << 19) * (decimal)pow);
253 _strikePrice = unscaled * (decimal)pow;
256 return _strikePrice.Value;
269 if (_optionRight.HasValue)
271 return _optionRight.Value;
278 _optionRight = (
OptionRight)ExtractFromProperties(PutCallOffset, PutCallWidth);
279 return _optionRight.Value;
292 if (_optionStyle.HasValue)
294 return _optionStyle.Value;
302 _optionStyle = (
OptionStyle)(ExtractFromProperties(OptionStyleOffset, OptionStyleWidth));
303 return _optionStyle.Value;
323 if (symbol.IndexOfAny(InvalidCharacters) != -1)
328 _properties = properties;
339 _hashCode = unchecked (symbol.GetHashCode() * 397) ^ properties.GetHashCode();
351 : this(symbol, properties)
358 _properties = properties;
362 _underlying = underlying;
368 #region AddMarket, GetMarketCode, and Generate
387 return GenerateOption(expiry, underlying,
null, market, strike, optionRight, optionStyle);
409 if (
string.IsNullOrEmpty(targetOption))
420 targetOption = underlying.
Symbol;
438 return Generate(expiry, symbol,
SecurityType.Future, market);
456 var firstTickerDate = GetFirstTickerAndDate(mapFileProvider ?? MapFileProvider.Value, symbol, market,
SecurityType.Equity, mappingResolveDate: mappingResolveDate);
457 firstDate = firstTickerDate.Item2;
458 symbol = firstTickerDate.Item1;
472 if (symbol.RequiresMapping())
475 var mapfile = resolver.ResolveMapFile(symbol);
477 return mapfile.GetMappedSymbol(date.Date, symbol.
Value);
492 return Generate(date, symbol,
SecurityType.Equity, market);
508 return Generate(
DefaultDate, symbol, securityType, market, forceSymbolToUpper:
false);
519 if (dataType ==
null)
524 TypeMapping[dataType.Name] = dataType;
525 return $
"{symbol.ToUpperInvariant()}.{dataType.Name}";
535 if (!
string.IsNullOrEmpty(symbol))
537 var index = symbol.LastIndexOf(
'.');
538 if (index != -1 && symbol.Length > index + 1)
540 type = symbol.Substring(index + 1);
554 return TryGetCustomDataType(symbol, out var strType) && TypeMapping.TryGetValue(strType, out type);
572 var firstTickerDate = GetFirstTickerAndDate(MapFileProvider.Value, symbol, market,
SecurityType.Equity);
573 firstDate = firstTickerDate.Item2;
574 symbol = firstTickerDate.Item1;
582 forceSymbolToUpper:
false
617 return Generate(expiry, symbol,
SecurityType.CryptoFuture, market);
654 bool forceSymbolToUpper =
true)
656 if ((ulong)securityType >= SecurityTypeWidth || securityType < 0)
660 if ((
int)optionRight > 1 || optionRight < 0)
664 if (date < Time.BeginningOfTime)
666 throw new ArgumentOutOfRangeException(date.ToStringInvariant(), $
"date must be after the earliest possible date {Time.BeginningOfTime}");
670 symbol = forceSymbolToUpper ? symbol.LazyToUpper() : symbol;
672 var marketIdentifier = GetMarketIdentifier(market);
674 var days = (ulong)date.ToOADate() * DaysOffset;
675 var marketCode = (ulong)marketIdentifier * MarketOffset;
677 var strk = NormalizeStrike(strike, out ulong strikeScale) * StrikeOffset;
678 strikeScale *= StrikeScaleOffset;
679 var style = (ulong)optionStyle * OptionStyleOffset;
680 var putcall = (ulong)optionRight * PutCallOffset;
682 var otherData = putcall + days + style + strk + strikeScale + marketCode + (ulong)securityType;
687 switch (securityType)
698 result._strikePrice = strike;
699 result._optionRight = optionRight;
700 result._optionStyle = optionStyle;
715 private static Tuple<string, DateTime> GetFirstTickerAndDate(
IMapFileProvider mapFileProvider,
string tickerToday,
string market,
SecurityType securityType, DateTime? mappingResolveDate =
null)
718 var mapFile = resolver.
ResolveMapFile(tickerToday, mappingResolveDate ?? DateTime.Today);
722 ? Tuple.Create(mapFile.FirstTicker, mapFile.FirstDate)
730 private static ulong NormalizeStrike(decimal strike, out ulong scale)
741 strike *= StrikeDefaultScaleExpanded;
744 while (strike % 10 == 0)
760 const ulong negativeMask = 1 << 19;
761 const ulong maxStrikePrice = negativeMask - ((negativeMask ^ (negativeMask - 1)) - StrikeWidth) - 1;
763 if (strike >= maxStrikePrice || strike <= -(
long)maxStrikePrice)
768 var encodedStrike = (long)strike;
772 encodedStrike = -encodedStrike;
775 encodedStrike |= 1 << 19;
778 return (ulong)encodedStrike;
784 private static ulong Pow(uint x,
int pow)
787 return (ulong)BigInteger.Pow(x, pow);
792 #region Parsing routines
810 if (!
TryParse(value, out identifier, out exception))
829 return TryParse(value, out identifier, out exception);
837 if (!TryParseProperties(value, out exception, out identifier))
845 private static readonly
char[] SplitSpace = {
' '};
849 lock (SecurityIdentifierCache)
852 if (SecurityIdentifierCache.Count >= 600000)
854 SecurityIdentifierCache.Clear();
857 SecurityIdentifierCache[key] = identifier;
864 private static bool TryParseProperties(
string value, out Exception exception, out
SecurityIdentifier identifier)
874 lock (SecurityIdentifierCache)
877 if (SecurityIdentifierCache.TryGetValue(value, out identifier))
879 return identifier !=
null;
882 if (
string.IsNullOrWhiteSpace(value) || value ==
" 0")
886 CacheSid(value, identifier);
895 var sids = value.Split(
'|');
896 for (var i = sids.Length - 1; i > -1; i--)
898 var current = sids[i];
899 var parts = current.Split(SplitSpace, StringSplitOptions.RemoveEmptyEntries);
900 if (parts.Length != 2)
906 var symbol = parts[0];
907 var otherData = parts[1];
908 var props = otherData.DecodeBase36();
910 if (!identifier.Equals(
Empty) || !SecurityIdentifierCache.TryGetValue(current, out var cachedIdentifier))
916 GetMarketIdentifier(identifier.Market);
918 var key = i < sids.Length - 1 ? $
"{current}|{sids[i + 1]}" : current;
919 CacheSid(key, identifier);
924 identifier = cachedIdentifier;
928 catch (Exception error)
931 Log.
Error($
@"SecurityIdentifier.TryParseProperties(): {
932 Messages.SecurityIdentifier.ErrorParsingSecurityIdentifier(value, exception)}");
933 CacheSid(value,
null);
944 private ulong ExtractFromProperties(ulong offset, ulong width)
946 return ExtractFromProperties(offset, width, _properties);
953 private static ulong ExtractFromProperties(ulong offset, ulong width, ulong properties)
955 return (properties / offset) % width;
963 private static int GetMarketIdentifier(
string market)
965 market = market.ToLowerInvariant();
968 if (marketIdentifier.HasValue)
970 return marketIdentifier.Value;
977 #region Equality members and ToString
984 if (ReferenceEquals(
this, other))
989 if (ReferenceEquals(
null, other))
994 return string.Compare(
ToString(), other.
ToString(), StringComparison.Ordinal);
1004 if (ReferenceEquals(
null, obj))
1009 if (ReferenceEquals(
this, obj))
1031 return ReferenceEquals(
this, other) || _properties == other._properties
1032 && _symbol == other._symbol
1033 && _underlying == other._underlying;
1045 if (ReferenceEquals(
null, obj))
return false;
1046 if (obj.GetType() != GetType())
return false;
1061 _hashCode = unchecked(_symbol.GetHashCode() * 397) ^ _properties.GetHashCode();
1062 _hashCodeSet =
true;
1072 return Equals(left, right);
1080 return !
Equals(left, right);
1092 if (_stringRep ==
null)
1094 var props = _properties.EncodeBase36();
1095 props = props.Length == 0 ?
"0" : props;
1096 _stringRep =
HasUnderlying ? $
"{_symbol} {props}|{_underlying}" : $
"{_symbol} {props}";