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); } }
/// <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)); }