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)
658 throw new ArgumentOutOfRangeException(nameof(securityType),
Messages.
SecurityIdentifier.InvalidSecurityType(nameof(securityType)));
660 if ((
int)optionRight > 1 || optionRight < 0)
662 throw new ArgumentOutOfRangeException(nameof(optionRight), Messages.SecurityIdentifier.InvalidOptionRight(nameof(optionRight)));
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)
765 throw new ArgumentException(Messages.SecurityIdentifier.InvalidStrikePrice(str));
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 = {
' '};
850 private static bool TryParseProperties(
string value, out Exception exception, out
SecurityIdentifier identifier)
860 lock (SecurityIdentifierCache)
863 if (SecurityIdentifierCache.TryGetValue(value, out identifier))
865 return identifier !=
null;
868 if (
string.IsNullOrWhiteSpace(value) || value ==
" 0")
871 SecurityIdentifierCache[value] = identifier =
Empty;
880 var sids = value.Split(
'|');
881 for (var i = sids.Length - 1; i > -1; i--)
883 var current = sids[i];
884 var parts = current.Split(SplitSpace, StringSplitOptions.RemoveEmptyEntries);
885 if (parts.Length != 2)
887 exception =
new FormatException(Messages.SecurityIdentifier.StringIsNotSplittable);
891 var symbol = parts[0];
892 var otherData = parts[1];
893 var props = otherData.DecodeBase36();
899 GetMarketIdentifier(identifier.Market);
902 catch (Exception error)
905 Log.
Error($
@"SecurityIdentifier.TryParseProperties(): {
906 Messages.SecurityIdentifier.ErrorParsingSecurityIdentifier(value, exception)}");
907 SecurityIdentifierCache[value] =
null;
911 SecurityIdentifierCache[value] = identifier;
919 private ulong ExtractFromProperties(ulong offset, ulong width)
921 return ExtractFromProperties(offset, width, _properties);
928 private static ulong ExtractFromProperties(ulong offset, ulong width, ulong properties)
930 return (properties / offset) % width;
938 private static int GetMarketIdentifier(
string market)
940 market = market.ToLowerInvariant();
943 if (marketIdentifier.HasValue)
945 return marketIdentifier.Value;
948 throw new ArgumentOutOfRangeException(nameof(market), Messages.SecurityIdentifier.MarketNotFound(market));
952 #region Equality members and ToString
959 if (ReferenceEquals(
this, other))
964 if (ReferenceEquals(
null, other))
969 return string.Compare(
ToString(), other.
ToString(), StringComparison.Ordinal);
979 if (ReferenceEquals(
null, obj))
984 if (ReferenceEquals(
this, obj))
1006 return ReferenceEquals(
this, other) || _properties == other._properties
1007 && _symbol == other._symbol
1008 && _underlying == other._underlying;
1020 if (ReferenceEquals(
null, obj))
return false;
1021 if (obj.GetType() != GetType())
return false;
1036 _hashCode = unchecked(_symbol.GetHashCode() * 397) ^ _properties.GetHashCode();
1037 _hashCodeSet =
true;
1047 return Equals(left, right);
1055 return !
Equals(left, right);
1067 if (_stringRep ==
null)
1069 var props = _properties.EncodeBase36();
1070 props = props.Length == 0 ?
"0" : props;
1071 _stringRep =
HasUnderlying ? $
"{_symbol} {props}|{_underlying}" : $
"{_symbol} {props}";