private IEnumerable <Symbol> GetFutureOptionContractList(Symbol futureContractSymbol, DateTime date)
        {
            var symbols    = new List <Symbol>();
            var retries    = 0;
            var maxRetries = 5;

            while (++retries <= maxRetries)
            {
                try
                {
                    _rateGate.WaitToProceed();

                    var productResponse = _client.GetAsync(CMEProductSlateURL.Replace(CMESymbolReplace, futureContractSymbol.ID.Symbol))
                                          .SynchronouslyAwaitTaskResult();

                    productResponse.EnsureSuccessStatusCode();

                    var productResults = JsonConvert.DeserializeObject <CMEProductSlateV2ListResponse>(productResponse.Content
                                                                                                       .ReadAsStringAsync()
                                                                                                       .SynchronouslyAwaitTaskResult());

                    productResponse.Dispose();

                    // We want to gather the future product to get the future options ID
                    var futureProductId = productResults.Products.Where(p => p.Globex == futureContractSymbol.ID.Symbol && p.GlobexTraded && p.Cleared == "Futures")
                                          .Select(p => p.Id)
                                          .Single();


                    var optionsTradesAndExpiries = CMEOptionsTradeDateAndExpirations.Replace(CMEProductCodeReplace, futureProductId.ToStringInvariant());

                    _rateGate.WaitToProceed();

                    var optionsTradesAndExpiriesResponse = _client.GetAsync(optionsTradesAndExpiries).SynchronouslyAwaitTaskResult();
                    optionsTradesAndExpiriesResponse.EnsureSuccessStatusCode();

                    var tradesAndExpiriesResponse = JsonConvert.DeserializeObject <List <CMEOptionsTradeDatesAndExpiration> >(optionsTradesAndExpiriesResponse.Content
                                                                                                                              .ReadAsStringAsync()
                                                                                                                              .SynchronouslyAwaitTaskResult());

                    optionsTradesAndExpiriesResponse.Dispose();

                    // For now, only support American options on CME
                    var selectedOption = tradesAndExpiriesResponse
                                         .FirstOrDefault(x => !x.Daily && !x.Weekly && !x.Sto && x.OptionType == "AME");

                    if (selectedOption == null)
                    {
                        Log.Error($"LiveOptionChainProvider.GetFutureOptionContractList(): Found no matching future options for contract {futureContractSymbol}");
                        yield break;
                    }

                    // Gather the month code and the year's last number to query the next API, which expects an expiration as `<MONTH_CODE><YEAR_LAST_NUMBER>`
                    var canonicalFuture = Symbol.Create(futureContractSymbol.ID.Symbol, SecurityType.Future, futureContractSymbol.ID.Market);
                    var expiryFunction  = FuturesExpiryFunctions.FuturesExpiryFunction(canonicalFuture);

                    var futureContractExpiration = selectedOption.Expirations
                                                   .Select(x => new KeyValuePair <CMEOptionsExpiration, DateTime>(x, expiryFunction(new DateTime(x.Expiration.Year, x.Expiration.Month, 1))))
                                                   .FirstOrDefault(x => x.Value.Year == futureContractSymbol.ID.Date.Year && x.Value.Month == futureContractSymbol.ID.Date.Month)
                                                   .Key;

                    if (futureContractExpiration == null)
                    {
                        Log.Error($"LiveOptionChainProvider.GetFutureOptionContractList(): Found no future options with matching expiry year and month for contract {futureContractSymbol}");
                        yield break;
                    }

                    var futureContractMonthCode = futureContractExpiration.Expiration.Code;

                    _rateGate.WaitToProceed();

                    // Subtract one day from now for settlement API since settlement may not be available for today yet
                    var optionChainQuotesResponseResult = _client.GetAsync(CMEOptionChainQuotesURL
                                                                           .Replace(CMEProductCodeReplace, selectedOption.ProductId.ToStringInvariant())
                                                                           .Replace(CMEProductExpirationReplace, futureContractMonthCode)
                                                                           + Math.Floor((DateTime.UtcNow - _epoch).TotalMilliseconds).ToStringInvariant());

                    optionChainQuotesResponseResult.Result.EnsureSuccessStatusCode();

                    var futureOptionChain = JsonConvert.DeserializeObject <CMEOptionChainQuotes>(optionChainQuotesResponseResult.Result.Content
                                                                                                 .ReadAsStringAsync()
                                                                                                 .SynchronouslyAwaitTaskResult())
                                            .Quotes
                                            .DistinctBy(s => s.StrikePrice)
                                            .ToList();

                    optionChainQuotesResponseResult.Dispose();

                    // Each CME contract can have arbitrary scaling applied to the strike price, so we normalize it to the
                    // underlying's price via static entries.
                    var optionStrikePriceScaleFactor = CMEStrikePriceScalingFactors.GetScaleFactor(futureContractSymbol);
                    var canonicalOption = Symbol.CreateOption(
                        futureContractSymbol,
                        futureContractSymbol.ID.Market,
                        futureContractSymbol.SecurityType.DefaultOptionStyle(),
                        default(OptionRight),
                        default(decimal),
                        SecurityIdentifier.DefaultDate);

                    foreach (var optionChainEntry in futureOptionChain)
                    {
                        var futureOptionExpiry = FuturesOptionsExpiryFunctions.GetFutureOptionExpiryFromFutureExpiry(futureContractSymbol, canonicalOption);
                        var scaledStrikePrice  = optionChainEntry.StrikePrice / optionStrikePriceScaleFactor;

                        // Calls and puts share the same strike, create two symbols per each to avoid iterating twice.
                        symbols.Add(Symbol.CreateOption(
                                        futureContractSymbol,
                                        futureContractSymbol.ID.Market,
                                        OptionStyle.American,
                                        OptionRight.Call,
                                        scaledStrikePrice,
                                        futureOptionExpiry));

                        symbols.Add(Symbol.CreateOption(
                                        futureContractSymbol,
                                        futureContractSymbol.ID.Market,
                                        OptionStyle.American,
                                        OptionRight.Put,
                                        scaledStrikePrice,
                                        futureOptionExpiry));
                    }

                    break;
                }
                catch (HttpRequestException err)
                {
                    if (retries != maxRetries)
                    {
                        Log.Error(err, $"Failed to retrieve futures options chain from CME, retrying ({retries} / {maxRetries})");
                        continue;
                    }

                    Log.Error(err, $"Failed to retrieve futures options chain from CME, returning empty result ({retries} / {retries})");
                }
            }

            foreach (var symbol in symbols)
            {
                yield return(symbol);
            }
        }
Example #2
0
        /// <summary>
        /// Creates a future option Symbol from the provided ticker
        /// </summary>
        /// <param name="ticker">The future option ticker, for example 'ESZ0 P3590'</param>
        /// <param name="strikeScale">Optional the future option strike scale factor</param>
        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));
        }