822 lines
32 KiB
C#
822 lines
32 KiB
C#
/*
|
|
* 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
|
|
{
|
|
/// <summary>
|
|
/// Defines a unique identifier for securities
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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
|
|
/// </remarks>
|
|
[JsonConverter(typeof(SecurityIdentifierJsonConverter))]
|
|
public struct SecurityIdentifier : IEquatable<SecurityIdentifier>
|
|
{
|
|
#region Empty, DefaultDate Fields
|
|
|
|
private static readonly ConcurrentDictionary<string, SecurityIdentifier> SecurityIdentifierCache
|
|
= new ConcurrentDictionary<string, SecurityIdentifier>();
|
|
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();
|
|
|
|
/// <summary>
|
|
/// Gets an instance of <see cref="SecurityIdentifier"/> that is empty, that is, one with no symbol specified
|
|
/// </summary>
|
|
public static readonly SecurityIdentifier Empty = new SecurityIdentifier(string.Empty, 0);
|
|
|
|
/// <summary>
|
|
/// Gets the date to be used when it does not apply.
|
|
/// </summary>
|
|
public static readonly DateTime DefaultDate = DateTime.FromOADate(0);
|
|
|
|
/// <summary>
|
|
/// Gets the set of invalids symbol characters
|
|
/// </summary>
|
|
public static readonly HashSet<char> InvalidSymbolCharacters = new HashSet<char>(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
|
|
|
|
/// <summary>
|
|
/// Gets whether or not this <see cref="SecurityIdentifier"/> is a derivative,
|
|
/// that is, it has a valid <see cref="Underlying"/> property
|
|
/// </summary>
|
|
public bool HasUnderlying
|
|
{
|
|
get { return _underlying != null; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the underlying security identifier for this security identifier. When there is
|
|
/// no underlying, this property will return a value of <see cref="Empty"/>.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
public string Symbol
|
|
{
|
|
get { return _symbol; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the security type component of this security identifier.
|
|
/// </summary>
|
|
public SecurityType SecurityType { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the option strike price. This only applies to SecurityType.Option
|
|
/// and will thrown anexception if accessed otherwse.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the option type component of this security identifier. This
|
|
/// only applies to SecurityType.Open and will throw an exception if
|
|
/// accessed otherwise.
|
|
/// </summary>
|
|
public OptionRight OptionRight
|
|
{
|
|
get
|
|
{
|
|
if (SecurityType != SecurityType.Option)
|
|
{
|
|
throw new InvalidOperationException("OptionRight is only defined for SecurityType.Option");
|
|
}
|
|
return (OptionRight)ExtractFromProperties(PutCallOffset, PutCallWidth);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the option style component of this security identifier. This
|
|
/// only applies to SecurityType.Open and will throw an exception if
|
|
/// accessed otherwise.
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="SecurityIdentifier"/> class
|
|
/// </summary>
|
|
/// <param name="symbol">The base36 string encoded as a long using alpha [0-9A-Z]</param>
|
|
/// <param name="properties">Other data defining properties of the symbol including market,
|
|
/// security type, listing or expiry date, strike/call/put/style for options, ect...</param>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="SecurityIdentifier"/> class
|
|
/// </summary>
|
|
/// <param name="symbol">The base36 string encoded as a long using alpha [0-9A-Z]</param>
|
|
/// <param name="properties">Other data defining properties of the symbol including market,
|
|
/// security type, listing or expiry date, strike/call/put/style for options, ect...</param>
|
|
/// <param name="underlying">Specifies a <see cref="SecurityIdentifier"/> that represents the underlying security</param>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Generates a new <see cref="SecurityIdentifier"/> for an option
|
|
/// </summary>
|
|
/// <param name="expiry">The date the option expires</param>
|
|
/// <param name="underlying">The underlying security's symbol</param>
|
|
/// <param name="market">The market</param>
|
|
/// <param name="strike">The strike price</param>
|
|
/// <param name="optionRight">The option type, call or put</param>
|
|
/// <param name="optionStyle">The option style, American or European</param>
|
|
/// <returns>A new <see cref="SecurityIdentifier"/> representing the specified option security</returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a new <see cref="SecurityIdentifier"/> for a future
|
|
/// </summary>
|
|
/// <param name="expiry">The date the future expires</param>
|
|
/// <param name="symbol">The security's symbol</param>
|
|
/// <param name="market">The market</param>
|
|
/// <returns>A new <see cref="SecurityIdentifier"/> representing the specified futures security</returns>
|
|
public static SecurityIdentifier GenerateFuture(DateTime expiry,
|
|
string symbol,
|
|
string market)
|
|
{
|
|
return Generate(expiry, symbol, SecurityType.Future, market);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper overload that will search the mapfiles to resolve the first date. This implementation
|
|
/// uses the configured <see cref="IMapFileProvider"/> via the <see cref="Composer.Instance"/>
|
|
/// </summary>
|
|
/// <param name="symbol">The symbol as it is known today</param>
|
|
/// <param name="market">The market</param>
|
|
/// <param name="mapSymbol">Specifies if symbol should be mapped using map file provider</param>
|
|
/// <param name="mapFileProvider">Specifies the IMapFileProvider to use for resolving symbols, specify null to load from Composer</param>
|
|
/// <returns>A new <see cref="SecurityIdentifier"/> representing the specified symbol today</returns>
|
|
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<IMapFileProvider>(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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a new <see cref="SecurityIdentifier"/> for an equity
|
|
/// </summary>
|
|
/// <param name="date">The first date this security traded (in LEAN this is the first date in the map_file</param>
|
|
/// <param name="symbol">The ticker symbol this security traded under on the <paramref name="date"/></param>
|
|
/// <param name="market">The security's market</param>
|
|
/// <returns>A new <see cref="SecurityIdentifier"/> representing the specified equity security</returns>
|
|
public static SecurityIdentifier GenerateEquity(DateTime date, string symbol, string market)
|
|
{
|
|
return Generate(date, symbol, SecurityType.Equity, market);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a new <see cref="SecurityIdentifier"/> for a custom security
|
|
/// </summary>
|
|
/// <param name="symbol">The ticker symbol of this security</param>
|
|
/// <param name="market">The security's market</param>
|
|
/// <returns>A new <see cref="SecurityIdentifier"/> representing the specified base security</returns>
|
|
public static SecurityIdentifier GenerateBase(string symbol, string market)
|
|
{
|
|
return Generate(DefaultDate, symbol, SecurityType.Base, market);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a new <see cref="SecurityIdentifier"/> for a forex pair
|
|
/// </summary>
|
|
/// <param name="symbol">The currency pair in the format similar to: 'EURUSD'</param>
|
|
/// <param name="market">The security's market</param>
|
|
/// <returns>A new <see cref="SecurityIdentifier"/> representing the specified forex pair</returns>
|
|
public static SecurityIdentifier GenerateForex(string symbol, string market)
|
|
{
|
|
return Generate(DefaultDate, symbol, SecurityType.Forex, market);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a new <see cref="SecurityIdentifier"/> for a Crypto pair
|
|
/// </summary>
|
|
/// <param name="symbol">The currency pair in the format similar to: 'EURUSD'</param>
|
|
/// <param name="market">The security's market</param>
|
|
/// <returns>A new <see cref="SecurityIdentifier"/> representing the specified Crypto pair</returns>
|
|
public static SecurityIdentifier GenerateCrypto(string symbol, string market)
|
|
{
|
|
return Generate(DefaultDate, symbol, SecurityType.Crypto, market);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a new <see cref="SecurityIdentifier"/> for a CFD security
|
|
/// </summary>
|
|
/// <param name="symbol">The CFD contract symbol</param>
|
|
/// <param name="market">The security's market</param>
|
|
/// <returns>A new <see cref="SecurityIdentifier"/> representing the specified CFD security</returns>
|
|
public static SecurityIdentifier GenerateCfd(string symbol, string market)
|
|
{
|
|
return Generate(DefaultDate, symbol, SecurityType.Cfd, market);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts an upper case alpha numeric string into a long
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a long to an uppercase alpha numeric string
|
|
/// </summary>
|
|
private static string EncodeBase36(ulong data)
|
|
{
|
|
var stack = new Stack<char>();
|
|
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());
|
|
}
|
|
|
|
/// <summary>
|
|
/// The strike is normalized into deci-cents and then a scale factor
|
|
/// is also saved to bring it back to un-normalized
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Accurately performs the integer exponentiation
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Parses the specified string into a <see cref="SecurityIdentifier"/>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="value">The string value to be parsed</param>
|
|
/// <returns>A new <see cref="SecurityIdentifier"/> instance if the <paramref name="value"/> is able to be parsed.</returns>
|
|
/// <exception cref="FormatException">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</exception>
|
|
public static SecurityIdentifier Parse(string value)
|
|
{
|
|
Exception exception;
|
|
SecurityIdentifier identifier;
|
|
if (!TryParse(value, out identifier, out exception))
|
|
{
|
|
throw exception;
|
|
}
|
|
|
|
return identifier;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to parse the specified <see paramref="value"/> as a <see cref="SecurityIdentifier"/>.
|
|
/// </summary>
|
|
/// <param name="value">The string value to be parsed</param>
|
|
/// <param name="identifier">The result of parsing, when this function returns true, <paramref name="identifier"/>
|
|
/// was properly created and reflects the input string, when this function returns false <paramref name="identifier"/>
|
|
/// will equal default(SecurityIdentifier)</param>
|
|
/// <returns>True on success, otherwise false</returns>
|
|
public static bool TryParse(string value, out SecurityIdentifier identifier)
|
|
{
|
|
Exception exception;
|
|
return TryParse(value, out identifier, out exception);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper method impl to be used by parse and tryparse
|
|
/// </summary>
|
|
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 = {' '};
|
|
|
|
/// <summary>
|
|
/// Parses the string into its component ulong pieces
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extracts the embedded value from _otherData
|
|
/// </summary>
|
|
private ulong ExtractFromProperties(ulong offset, ulong width)
|
|
{
|
|
return ExtractFromProperties(offset, width, _properties);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extracts the embedded value from _otherData
|
|
/// </summary>
|
|
/// <remarks>Static so it can be used in <see cref="_lazySecurityType"/> initialization</remarks>
|
|
private static ulong ExtractFromProperties(ulong offset, ulong width, ulong properties)
|
|
{
|
|
return (properties / offset) % width;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Equality members and ToString
|
|
|
|
/// <summary>
|
|
/// Indicates whether the current object is equal to another object of the same type.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
|
|
/// </returns>
|
|
/// <param name="other">An object to compare with this object.</param>
|
|
public bool Equals(SecurityIdentifier other)
|
|
{
|
|
return _properties == other._properties
|
|
&& _symbol == other._symbol
|
|
&& _underlying == other._underlying;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// true if the specified object is equal to the current object; otherwise, false.
|
|
/// </returns>
|
|
/// <param name="obj">The object to compare with the current object. </param><filterpriority>2</filterpriority>
|
|
public override bool Equals(object obj)
|
|
{
|
|
if (ReferenceEquals(null, obj)) return false;
|
|
if (obj.GetType() != GetType()) return false;
|
|
return Equals((SecurityIdentifier)obj);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serves as a hash function for a particular type.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A hash code for the current <see cref="T:System.Object"/>.
|
|
/// </returns>
|
|
/// <filterpriority>2</filterpriority>
|
|
public override int GetHashCode() => _hashCode;
|
|
|
|
/// <summary>
|
|
/// Override equals operator
|
|
/// </summary>
|
|
public static bool operator ==(SecurityIdentifier left, SecurityIdentifier right)
|
|
{
|
|
return Equals(left, right);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Override not equals operator
|
|
/// </summary>
|
|
public static bool operator !=(SecurityIdentifier left, SecurityIdentifier right)
|
|
{
|
|
return !Equals(left, right);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a string that represents the current object.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A string that represents the current object.
|
|
/// </returns>
|
|
/// <filterpriority>2</filterpriority>
|
|
public override string ToString()
|
|
{
|
|
var props = EncodeBase36(_properties);
|
|
if (HasUnderlying)
|
|
{
|
|
return _symbol + ' ' + props + '|' + _underlying.SecurityIdentifier;
|
|
}
|
|
return _symbol + ' ' + props;
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Provides a reference type container for a security identifier instance.
|
|
/// This is used to maintain a reference to an underlying
|
|
/// </summary>
|
|
private sealed class SidBox : IEquatable<SidBox>
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|