/* * 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.Linq; using QuantConnect.Logging; using System.Globalization; using QuantConnect.Securities; using System.Collections.Generic; using QuantConnect.Securities.Future; using QuantConnect.Securities.FutureOption; using static QuantConnect.StringExtensions; namespace QuantConnect { /// /// Public static helper class that does parsing/generation of symbol representations (options, futures) /// public static class SymbolRepresentation { private static DateTime TodayUtc = DateTime.UtcNow; /// /// Class contains future ticker properties returned by ParseFutureTicker() /// public class FutureTickerProperties { /// /// Underlying name /// public string Underlying { get; set; } /// /// Short expiration year /// public int ExpirationYearShort { get; set; } /// /// Expiration month /// public int ExpirationMonth { get; set; } /// /// Expiration day /// public int ExpirationDay { get; set; } } /// /// Class contains option ticker properties returned by ParseOptionTickerIQFeed() /// public class OptionTickerProperties { /// /// Underlying name /// public string Underlying { get; set; } /// /// Option right /// public OptionRight OptionRight { get; set; } /// /// Option strike /// public decimal OptionStrike { get; set; } /// /// Expiration date /// public DateTime ExpirationDate { get; set; } } /// /// Function returns underlying name, expiration year, expiration month, expiration day for the future contract ticker. Function detects if /// the format used is either 1 or 2 digits year, and if day code is present (will default to 1rst day of month). Returns null, if parsing failed. /// Format [Ticker][2 digit day code OPTIONAL][1 char month code][2/1 digit year code] /// /// /// Results containing 1) underlying name, 2) short expiration year, 3) expiration month public static FutureTickerProperties ParseFutureTicker(string ticker) { var doubleDigitYear = char.IsDigit(ticker.Substring(ticker.Length - 2, 1)[0]); var doubleDigitOffset = doubleDigitYear ? 1 : 0; var expirationDayOffset = 0; var expirationDay = 1; if (ticker.Length > 4 + doubleDigitOffset) { var potentialExpirationDay = ticker.Substring(ticker.Length - 4 - doubleDigitOffset, 2); var containsExpirationDay = char.IsDigit(potentialExpirationDay[0]) && char.IsDigit(potentialExpirationDay[1]); expirationDayOffset = containsExpirationDay ? 2 : 0; if (containsExpirationDay && !int.TryParse(potentialExpirationDay, out expirationDay)) { return null; } } var expirationYearString = ticker.Substring(ticker.Length - 1 - doubleDigitOffset, 1 + doubleDigitOffset); var expirationMonthString = ticker.Substring(ticker.Length - 2 - doubleDigitOffset, 1); var underlyingString = ticker.Substring(0, ticker.Length - 2 - doubleDigitOffset - expirationDayOffset); int expirationYearShort; if (!int.TryParse(expirationYearString, out expirationYearShort)) { return null; } if (!_futuresMonthCodeLookup.ContainsKey(expirationMonthString)) { return null; } var expirationMonth = _futuresMonthCodeLookup[expirationMonthString]; return new FutureTickerProperties { Underlying = underlyingString, ExpirationYearShort = expirationYearShort, ExpirationMonth = expirationMonth, ExpirationDay = expirationDay }; } /// /// Helper method to parse and generate a future symbol from a given user friendly representation /// /// The future ticker, for example 'ESZ1' /// Clarifies the year for the current future /// The future symbol or null if failed public static Symbol ParseFutureSymbol(string ticker, int? futureYear = null) { var disambiguatedFutureYear = futureYear ?? TodayUtc.Year; var parsed = ParseFutureTicker(ticker); if (parsed == null) { return null; } var underlying = parsed.Underlying; var expirationYearShort = parsed.ExpirationYearShort; var expirationMonth = parsed.ExpirationMonth; var expirationYear = GetExpirationYear(expirationYearShort, disambiguatedFutureYear); if (!SymbolPropertiesDatabase.FromDataFolder().TryGetMarket(underlying, SecurityType.Future, out var market)) { Log.Debug($"SymbolRepresentation.ParseFutureSymbol(): Failed to get market for future '{ticker}' and underlying '{underlying}'"); return null; } var expiryFunc = FuturesExpiryFunctions.FuturesExpiryFunction(Symbol.Create(underlying, SecurityType.Future, market)); var expiryDate = expiryFunc(new DateTime(expirationYear, expirationMonth, 1)); return Symbol.CreateFuture(underlying, market, expiryDate); } /// /// Creates a future option Symbol from the provided ticker /// /// The future option ticker, for example 'ESZ0 P3590' /// Optional the future option strike scale factor public static Symbol ParseFutureOptionSymbol(string ticker, int strikeScale = 1) { var split = ticker.Split(' '); if (split.Length != 2) { return null; } var parsed = ParseFutureTicker(split[0]); if (parsed == null) { return null; } ticker = parsed.Underlying; OptionRight right; if (split[1][0] == 'P' || split[1][0] == 'p') { right = OptionRight.Put; } else if (split[1][0] == 'C' || split[1][0] == 'c') { right = OptionRight.Call; } else { return null; } var strike = split[1].Substring(1); if (parsed.ExpirationYearShort < 10) { parsed.ExpirationYearShort += 20; } var expirationYearParsed = 2000 + parsed.ExpirationYearShort; var expirationDate = new DateTime(expirationYearParsed, parsed.ExpirationMonth, 1); var strikePrice = decimal.Parse(strike, NumberStyles.Any, CultureInfo.InvariantCulture); var futureTicker = FuturesOptionsSymbolMappings.MapFromOption(ticker); if (!SymbolPropertiesDatabase.FromDataFolder().TryGetMarket(futureTicker, SecurityType.Future, out var market)) { Log.Debug($"SymbolRepresentation.ParseFutureOptionSymbol(): No market found for '{futureTicker}'"); return null; } var canonicalFuture = Symbol.Create(futureTicker, SecurityType.Future, market); var futureExpiry = FuturesExpiryFunctions.FuturesExpiryFunction(canonicalFuture)(expirationDate); var future = Symbol.CreateFuture(futureTicker, market, futureExpiry); var futureOptionExpiry = FuturesOptionsExpiryFunctions.GetFutureOptionExpiryFromFutureExpiry(future); return Symbol.CreateOption(future, market, OptionStyle.American, right, strikePrice / strikeScale, futureOptionExpiry); } /// /// Returns future symbol ticker from underlying and expiration date. Function can generate tickers of two formats: one and two digits year. /// Format [Ticker][2 digit day code][1 char month code][2/1 digit year code], more information at http://help.tradestation.com/09_01/tradestationhelp/symbology/futures_symbology.htm /// /// String underlying /// Expiration date /// True if year should represented by two digits; False - one digit /// public static string GenerateFutureTicker(string underlying, DateTime expiration, bool doubleDigitsYear = true) { var year = doubleDigitsYear ? expiration.Year % 100 : expiration.Year % 10; var month = expiration.Month; var contractMonthDelta = FuturesExpiryUtilityFunctions.GetDeltaBetweenContractMonthAndContractExpiry(underlying, expiration.Date); if (contractMonthDelta < 0) { // For futures that have an expiry after the contract month. // This is for dairy contracts, which can and do expire after the contract month. var expirationMonth = expiration.AddDays(-(expiration.Day - 1)) .AddMonths(contractMonthDelta); month = expirationMonth.Month; year = doubleDigitsYear ? expirationMonth.Year % 100 : expirationMonth.Year % 10; } else { // These futures expire in the month before or in the contract month month += contractMonthDelta; // Get the month back into the allowable range, allowing for a wrap // Below is a little algorithm for wrapping numbers with a certain bounds. // In this case, were dealing with months, wrapping to years once we get to January // As modulo works for [0, x), it's best to subtract 1 (as months are [1, 12] to convert to [0, 11]), // do the modulo/integer division, then add 1 back on to get into the correct range again month--; year += month / 12; month %= 12; month++; } return $"{underlying}{expiration.Day:00}{_futuresMonthLookup[month]}{year}"; } /// /// Returns option symbol ticker in accordance with OSI symbology /// More information can be found at http://www.optionsclearing.com/components/docs/initiatives/symbology/symbology_initiative_v1_8.pdf /// /// Symbol object to create OSI ticker from /// The OSI ticker representation public static string GenerateOptionTickerOSI(this Symbol symbol) { if (!symbol.SecurityType.IsOption()) { throw new ArgumentException(Invariant($"{nameof(GenerateOptionTickerOSI)} returns symbol to be an option, received {symbol.SecurityType}.")); } return GenerateOptionTickerOSI(symbol.Underlying.Value, symbol.ID.OptionRight, symbol.ID.StrikePrice, symbol.ID.Date); } /// /// Returns option symbol ticker in accordance with OSI symbology /// More information can be found at http://www.optionsclearing.com/components/docs/initiatives/symbology/symbology_initiative_v1_8.pdf /// /// Underlying string /// Option right /// Option strike /// Option expiration date /// The OSI ticker representation public static string GenerateOptionTickerOSI(string underlying, OptionRight right, decimal strikePrice, DateTime expiration) { if (underlying.Length > 5) underlying += " "; return Invariant($"{underlying,-6}{expiration.ToStringInvariant(DateFormat.SixCharacter)}{right.ToStringPerformance()[0]}{(strikePrice * 1000m):00000000}"); } /// /// Parses the specified OSI options ticker into a Symbol object /// /// The OSI compliant option ticker string /// The security type /// The associated market /// Symbol object for the specified OSI option ticker string public static Symbol ParseOptionTickerOSI(string ticker, SecurityType securityType = SecurityType.Option, string market = Market.USA) { var underlying = ticker.Substring(0, 6).Trim(); var expiration = DateTime.ParseExact(ticker.Substring(6, 6), DateFormat.SixCharacter, null); OptionRight right; if (ticker[12] == 'C' || ticker[12] == 'c') { right = OptionRight.Call; } else if (ticker[12] == 'P' || ticker[12] == 'p') { right = OptionRight.Put; } else { throw new FormatException($"Expected 12th character to be 'C' or 'P' for OptionRight: {ticker} but was '{ticker[12]}'"); } var strike = Parse.Decimal(ticker.Substring(13, 8)) / 1000m; SecurityIdentifier underlyingSid; if (securityType == SecurityType.Option) { underlyingSid = SecurityIdentifier.GenerateEquity(underlying, market); } else if(securityType == SecurityType.IndexOption) { underlyingSid = SecurityIdentifier.GenerateIndex(underlying, market); } else { throw new NotImplementedException($"ParseOptionTickerOSI(): security type {securityType} not implemented"); } var sid = SecurityIdentifier.GenerateOption(expiration, underlyingSid, market, strike, right, OptionStyle.American); return new Symbol(sid, ticker, new Symbol(underlyingSid, underlying)); } /// /// Function returns option contract parameters (underlying name, expiration date, strike, right) from IQFeed option ticker /// Symbology details: http://www.iqfeed.net/symbolguide/index.cfm?symbolguide=guide&displayaction=support%C2%A7ion=guide&web=iqfeed&guide=options&web=IQFeed&type=stock /// /// IQFeed option ticker /// Results containing 1) underlying name, 2) option right, 3) option strike 4) expiration date public static OptionTickerProperties ParseOptionTickerIQFeed(string ticker) { // This table describes IQFeed option symbology var symbology = new Dictionary> { { "A", Tuple.Create(1, OptionRight.Call) }, { "M", Tuple.Create(1, OptionRight.Put) }, { "B", Tuple.Create(2, OptionRight.Call) }, { "N", Tuple.Create(2, OptionRight.Put) }, { "C", Tuple.Create(3, OptionRight.Call) }, { "O", Tuple.Create(3, OptionRight.Put) }, { "D", Tuple.Create(4, OptionRight.Call) }, { "P", Tuple.Create(4, OptionRight.Put) }, { "E", Tuple.Create(5, OptionRight.Call) }, { "Q", Tuple.Create(5, OptionRight.Put) }, { "F", Tuple.Create(6, OptionRight.Call) }, { "R", Tuple.Create(6, OptionRight.Put) }, { "G", Tuple.Create(7, OptionRight.Call) }, { "S", Tuple.Create(7, OptionRight.Put) }, { "H", Tuple.Create(8, OptionRight.Call) }, { "T", Tuple.Create(8, OptionRight.Put) }, { "I", Tuple.Create(9, OptionRight.Call) }, { "U", Tuple.Create(9, OptionRight.Put) }, { "J", Tuple.Create(10, OptionRight.Call) }, { "V", Tuple.Create(10, OptionRight.Put) }, { "K", Tuple.Create(11, OptionRight.Call) }, { "W", Tuple.Create(11, OptionRight.Put) }, { "L", Tuple.Create(12, OptionRight.Call) }, { "X", Tuple.Create(12, OptionRight.Put) }, }; var letterRange = symbology.Keys .Select(x => x[0]) .ToArray(); var optionTypeDelimiter = ticker.LastIndexOfAny(letterRange); var strikePriceString = ticker.Substring(optionTypeDelimiter + 1, ticker.Length - optionTypeDelimiter - 1); var lookupResult = symbology[ticker[optionTypeDelimiter].ToStringInvariant()]; var month = lookupResult.Item1; var optionRight = lookupResult.Item2; var dayString = ticker.Substring(optionTypeDelimiter - 2, 2); var yearString = ticker.Substring(optionTypeDelimiter - 4, 2); var underlying = ticker.Substring(0, optionTypeDelimiter - 4); // if we cannot parse strike price, we ignore this contract, but log the information. decimal strikePrice; if (!Decimal.TryParse(strikePriceString, out strikePrice)) { return null; } int day; if (!int.TryParse(dayString, out day)) { return null; } int year; if (!int.TryParse(yearString, out year)) { return null; } var expirationDate = new DateTime(2000 + year, month, day); return new OptionTickerProperties { Underlying = underlying, OptionRight = optionRight, OptionStrike = strikePrice, ExpirationDate = expirationDate }; } private static IReadOnlyDictionary _futuresMonthCodeLookup = new Dictionary { { "F", 1 }, { "G", 2 }, { "H", 3 }, { "J", 4 }, { "K", 5 }, { "M", 6 }, { "N", 7 }, { "Q", 8 }, { "U", 9 }, { "V", 10 }, { "X", 11 }, { "Z", 12 } }; private static IReadOnlyDictionary _futuresMonthLookup = _futuresMonthCodeLookup.ToDictionary(kv => kv.Value, kv => kv.Key); private static int GetExpirationYear(int year, int futureYear) { var baseNum = 2000; while (baseNum + year < futureYear) { baseNum += 10; } return baseNum + year; } } }